summaryrefslogtreecommitdiffstats
path: root/sys-utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
commit30ff6afe596eddafacf22b1a5b2d1a3d6254ea15 (patch)
tree9b788335f92174baf7ee18f03ca8330b8c19ce2b /sys-utils
parentInitial commit. (diff)
downloadutil-linux-30ff6afe596eddafacf22b1a5b2d1a3d6254ea15.tar.xz
util-linux-30ff6afe596eddafacf22b1a5b2d1a3d6254ea15.zip
Adding upstream version 2.36.1.upstream/2.36.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sys-utils')
-rw-r--r--sys-utils/Makemodule.am513
-rw-r--r--sys-utils/adjtime_config.564
-rw-r--r--sys-utils/blkdiscard.890
-rw-r--r--sys-utils/blkdiscard.c318
-rw-r--r--sys-utils/blkzone.8133
-rw-r--r--sys-utils/blkzone.c470
-rw-r--r--sys-utils/chcpu.8104
-rw-r--r--sys-utils/chcpu.c390
-rw-r--r--sys-utils/chmem.8114
-rw-r--r--sys-utils/chmem.c452
-rw-r--r--sys-utils/choom.183
-rw-r--r--sys-utils/choom.c159
-rw-r--r--sys-utils/ctrlaltdel.858
-rw-r--r--sys-utils/ctrlaltdel.c113
-rw-r--r--sys-utils/dmesg.1266
-rw-r--r--sys-utils/dmesg.c1587
-rw-r--r--sys-utils/eject.1196
-rw-r--r--sys-utils/eject.c1055
-rw-r--r--sys-utils/fallocate.1191
-rw-r--r--sys-utils/fallocate.c420
-rw-r--r--sys-utils/flock.1206
-rw-r--r--sys-utils/flock.c384
-rw-r--r--sys-utils/fsfreeze.887
-rw-r--r--sys-utils/fsfreeze.c150
-rw-r--r--sys-utils/fstab.5249
-rw-r--r--sys-utils/fstrim.8146
-rw-r--r--sys-utils/fstrim.c547
-rw-r--r--sys-utils/fstrim.service.in16
-rw-r--r--sys-utils/fstrim.timer13
-rw-r--r--sys-utils/hwclock-cmos.c387
-rw-r--r--sys-utils/hwclock-parse-date.c3382
-rw-r--r--sys-utils/hwclock-parse-date.y1629
-rw-r--r--sys-utils/hwclock-rtc.c454
-rw-r--r--sys-utils/hwclock.8993
-rw-r--r--sys-utils/hwclock.8.in993
-rw-r--r--sys-utils/hwclock.c1602
-rw-r--r--sys-utils/hwclock.h82
-rw-r--r--sys-utils/ipcmk.155
-rw-r--r--sys-utils/ipcmk.c166
-rw-r--r--sys-utils/ipcrm.1118
-rw-r--r--sys-utils/ipcrm.c423
-rw-r--r--sys-utils/ipcs.1135
-rw-r--r--sys-utils/ipcs.c672
-rw-r--r--sys-utils/ipcutils.c536
-rw-r--r--sys-utils/ipcutils.h187
-rw-r--r--sys-utils/irq-common.c415
-rw-r--r--sys-utils/irq-common.h61
-rw-r--r--sys-utils/irqtop.175
-rw-r--r--sys-utils/irqtop.c341
-rw-r--r--sys-utils/ldattach.8155
-rw-r--r--sys-utils/ldattach.c488
-rw-r--r--sys-utils/losetup.8217
-rw-r--r--sys-utils/losetup.c925
-rw-r--r--sys-utils/lscpu-arm.c274
-rw-r--r--sys-utils/lscpu-dmi.c312
-rw-r--r--sys-utils/lscpu.1204
-rw-r--r--sys-utils/lscpu.c2545
-rw-r--r--sys-utils/lscpu.h214
-rw-r--r--sys-utils/lsipc.1140
-rw-r--r--sys-utils/lsipc.c1338
-rw-r--r--sys-utils/lsirq.159
-rw-r--r--sys-utils/lsirq.c146
-rw-r--r--sys-utils/lsmem.1100
-rw-r--r--sys-utils/lsmem.c761
-rw-r--r--sys-utils/lsns.892
-rw-r--r--sys-utils/lsns.c1101
-rw-r--r--sys-utils/mount.82903
-rw-r--r--sys-utils/mount.c996
-rw-r--r--sys-utils/mountpoint.161
-rw-r--r--sys-utils/mountpoint.c215
-rw-r--r--sys-utils/nsenter.1269
-rw-r--r--sys-utils/nsenter.c492
-rw-r--r--sys-utils/pivot_root.875
-rw-r--r--sys-utils/pivot_root.c79
-rw-r--r--sys-utils/prlimit.1120
-rw-r--r--sys-utils/prlimit.c647
-rw-r--r--sys-utils/readprofile.8151
-rw-r--r--sys-utils/readprofile.c409
-rw-r--r--sys-utils/renice.1119
-rw-r--r--sys-utils/renice.c194
-rw-r--r--sys-utils/rfkill.8120
-rw-r--r--sys-utils/rfkill.c751
-rw-r--r--sys-utils/rtcwake.8189
-rw-r--r--sys-utils/rtcwake.8.in189
-rw-r--r--sys-utils/rtcwake.c686
-rw-r--r--sys-utils/setarch.8143
-rw-r--r--sys-utils/setarch.c479
-rw-r--r--sys-utils/setpriv.1251
-rw-r--r--sys-utils/setpriv.c1067
-rw-r--r--sys-utils/setsid.142
-rw-r--r--sys-utils/setsid.c123
-rw-r--r--sys-utils/swapoff.81
-rw-r--r--sys-utils/swapoff.c281
-rw-r--r--sys-utils/swapon-common.c117
-rw-r--r--sys-utils/swapon-common.h25
-rw-r--r--sys-utils/swapon.8280
-rw-r--r--sys-utils/swapon.c1015
-rw-r--r--sys-utils/switch_root.861
-rw-r--r--sys-utils/switch_root.c262
-rw-r--r--sys-utils/tunelp.8122
-rw-r--r--sys-utils/tunelp.c319
-rw-r--r--sys-utils/umount.8306
-rw-r--r--sys-utils/umount.c626
-rw-r--r--sys-utils/unshare.1414
-rw-r--r--sys-utils/unshare.c710
-rw-r--r--sys-utils/wdctl.874
-rw-r--r--sys-utils/wdctl.c721
-rw-r--r--sys-utils/zramctl.8131
-rw-r--r--sys-utils/zramctl.c768
109 files changed, 47354 insertions, 0 deletions
diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am
new file mode 100644
index 0000000..d954f62
--- /dev/null
+++ b/sys-utils/Makemodule.am
@@ -0,0 +1,513 @@
+if BUILD_LSMEM
+usrbin_exec_PROGRAMS += lsmem
+dist_man_MANS += sys-utils/lsmem.1
+lsmem_SOURCES = sys-utils/lsmem.c
+lsmem_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lsmem_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_CHMEM
+usrbin_exec_PROGRAMS += chmem
+dist_man_MANS += sys-utils/chmem.8
+chmem_SOURCES = sys-utils/chmem.c
+chmem_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_FLOCK
+usrbin_exec_PROGRAMS += flock
+dist_man_MANS += sys-utils/flock.1
+flock_SOURCES = sys-utils/flock.c lib/monotonic.c lib/timer.c
+flock_LDADD = $(LDADD) libcommon.la $(REALTIME_LIBS)
+endif
+
+if BUILD_CHOOM
+usrbin_exec_PROGRAMS += choom
+dist_man_MANS += sys-utils/choom.1
+choom_SOURCES = sys-utils/choom.c
+choom_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_IPCMK
+usrbin_exec_PROGRAMS += ipcmk
+dist_man_MANS += sys-utils/ipcmk.1
+ipcmk_SOURCES = sys-utils/ipcmk.c
+ipcmk_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_IPCRM
+usrbin_exec_PROGRAMS += ipcrm
+dist_man_MANS += sys-utils/ipcrm.1
+ipcrm_SOURCES = sys-utils/ipcrm.c
+ipcrm_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_IPCS
+usrbin_exec_PROGRAMS += ipcs
+dist_man_MANS += sys-utils/ipcs.1
+ipcs_SOURCES = sys-utils/ipcs.c \
+ sys-utils/ipcutils.c \
+ sys-utils/ipcutils.h
+ipcs_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_IRQTOP
+usrbin_exec_PROGRAMS += irqtop
+dist_man_MANS += sys-utils/irqtop.1
+irqtop_SOURCES = sys-utils/irqtop.c \
+ sys-utils/irq-common.c \
+ sys-utils/irq-common.h \
+ lib/monotonic.c
+irqtop_LDADD = $(LDADD) libcommon.la $(REALTIME_LIBS) libsmartcols.la
+irqtop_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+if HAVE_SLANG
+irqtop_LDADD += -lslang
+else
+irqtop_CFLAGS += $(NCURSES_CFLAGS)
+irqtop_LDADD += $(NCURSES_LIBS)
+endif
+endif
+
+if BUILD_LSIRQ
+usrbin_exec_PROGRAMS += lsirq
+dist_man_MANS += sys-utils/lsirq.1
+lsirq_SOURCES = sys-utils/lsirq.c \
+ sys-utils/irq-common.c \
+ sys-utils/irq-common.h
+lsirq_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lsirq_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_LSIPC
+usrbin_exec_PROGRAMS += lsipc
+dist_man_MANS += sys-utils/lsipc.1
+lsipc_SOURCES = sys-utils/lsipc.c \
+ sys-utils/ipcutils.c \
+ sys-utils/ipcutils.h
+lsipc_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lsipc_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_RENICE
+usrbin_exec_PROGRAMS += renice
+dist_man_MANS += sys-utils/renice.1
+renice_SOURCES = sys-utils/renice.c
+endif
+
+if BUILD_RFKILL
+usrsbin_exec_PROGRAMS += rfkill
+dist_man_MANS += sys-utils/rfkill.8
+rfkill_SOURCES = sys-utils/rfkill.c
+rfkill_LDADD = $(LDADD) libcommon.la libsmartcols.la
+rfkill_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_SETSID
+usrbin_exec_PROGRAMS += setsid
+dist_man_MANS += sys-utils/setsid.1
+setsid_SOURCES = sys-utils/setsid.c
+endif
+
+if BUILD_READPROFILE
+usrsbin_exec_PROGRAMS += readprofile
+dist_man_MANS += sys-utils/readprofile.8
+readprofile_SOURCES = sys-utils/readprofile.c
+endif
+
+if BUILD_TUNELP
+usrsbin_exec_PROGRAMS += tunelp
+dist_man_MANS += sys-utils/tunelp.8
+tunelp_SOURCES = sys-utils/tunelp.c
+tunelp_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_FSTRIM
+sbin_PROGRAMS += fstrim
+dist_man_MANS += sys-utils/fstrim.8
+fstrim_SOURCES = sys-utils/fstrim.c
+fstrim_LDADD = $(LDADD) libcommon.la libmount.la
+fstrim_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir)
+if HAVE_SYSTEMD
+systemdsystemunit_DATA += \
+ sys-utils/fstrim.service \
+ sys-utils/fstrim.timer
+endif
+endif # BUILD_FSTRIM
+
+PATHFILES += sys-utils/fstrim.service
+EXTRA_DIST += sys-utils/fstrim.timer
+
+if BUILD_DMESG
+bin_PROGRAMS += dmesg
+dist_man_MANS += sys-utils/dmesg.1
+dmesg_SOURCES = sys-utils/dmesg.c lib/monotonic.c
+dmesg_LDADD = $(LDADD) libcommon.la libtcolors.la $(REALTIME_LIBS)
+dmesg_CFLAGS = $(AM_CFLAGS)
+check_PROGRAMS += test_dmesg
+test_dmesg_SOURCES = $(dmesg_SOURCES)
+test_dmesg_LDADD = $(dmesg_LDADD)
+test_dmesg_CFLAGS = -DTEST_DMESG $(dmesg_CFLAGS)
+endif
+
+if BUILD_CTRLALTDEL
+sbin_PROGRAMS += ctrlaltdel
+dist_man_MANS += sys-utils/ctrlaltdel.8
+ctrlaltdel_SOURCES = sys-utils/ctrlaltdel.c
+ctrlaltdel_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_FSFREEZE
+sbin_PROGRAMS += fsfreeze
+dist_man_MANS += sys-utils/fsfreeze.8
+fsfreeze_SOURCES = sys-utils/fsfreeze.c
+endif
+
+if BUILD_BLKDISCARD
+sbin_PROGRAMS += blkdiscard
+dist_man_MANS += sys-utils/blkdiscard.8
+blkdiscard_SOURCES = sys-utils/blkdiscard.c lib/monotonic.c
+blkdiscard_LDADD = $(LDADD) libblkid.la libcommon.la $(REALTIME_LIBS)
+blkdiscard_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir)
+endif
+
+if BUILD_BLKZONE
+sbin_PROGRAMS += blkzone
+dist_man_MANS += sys-utils/blkzone.8
+blkzone_SOURCES = sys-utils/blkzone.c
+blkzone_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_LDATTACH
+usrsbin_exec_PROGRAMS += ldattach
+dist_man_MANS += sys-utils/ldattach.8
+ldattach_SOURCES = sys-utils/ldattach.c
+ldattach_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_RTCWAKE
+usrsbin_exec_PROGRAMS += rtcwake
+dist_man_MANS += sys-utils/rtcwake.8
+PATHFILES += sys-utils/rtcwake.8
+rtcwake_SOURCES = sys-utils/rtcwake.c
+rtcwake_LDADD = $(LDADD) libcommon.la
+endif
+
+if BUILD_SETARCH
+usrbin_exec_PROGRAMS += setarch
+dist_man_MANS += sys-utils/setarch.8
+setarch_SOURCES = sys-utils/setarch.c
+
+SETARCH_LINKS = uname26 linux32 linux64
+
+if ARCH_S390
+SETARCH_LINKS += s390 s390x
+endif
+if ARCH_I86
+SETARCH_LINKS += i386
+endif
+if ARCH_86_64
+SETARCH_LINKS += i386 x86_64
+endif
+if ARCH_PPC
+SETARCH_LINKS += ppc ppc64 ppc32
+endif
+if ARCH_SPARC
+SETARCH_LINKS += sparc sparc64 sparc32 sparc32bash
+endif
+if ARCH_MIPS
+SETARCH_LINKS += mips mips64 mips32
+endif
+if ARCH_IA64
+SETARCH_LINKS += i386 ia64
+endif
+if ARCH_HPPA
+SETARCH_LINKS += parisc parisc64 parisc32
+endif
+
+SETARCH_MAN_LINKS = $(addprefix sys-utils/,$(SETARCH_LINKS:=.8))
+man_MANS += $(SETARCH_MAN_LINKS)
+CLEANFILES += $(SETARCH_MAN_LINKS)
+
+$(SETARCH_MAN_LINKS):
+ $(AM_V_at) $(MKDIR_P) sys-utils
+ $(AM_V_GEN)echo ".so man8/setarch.8" > $@
+
+install-exec-hook-setarch:
+ for I in $(SETARCH_LINKS); do \
+ cd $(DESTDIR)$(usrbin_execdir) && ln -sf setarch $$I ; \
+ done
+
+uninstall-hook-setarch:
+ for I in $(SETARCH_LINKS); do \
+ rm -f $(DESTDIR)$(usrbin_execdir)/$$I ; \
+ done
+
+INSTALL_EXEC_HOOKS += install-exec-hook-setarch
+UNINSTALL_HOOKS += uninstall-hook-setarch
+
+endif # BUILD_SETARCH
+
+
+if BUILD_EJECT
+usrbin_exec_PROGRAMS += eject
+eject_SOURCES = sys-utils/eject.c lib/monotonic.c
+eject_LDADD = $(LDADD) libmount.la libcommon.la $(REALTIME_LIBS)
+eject_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir)
+dist_man_MANS += sys-utils/eject.1
+endif
+
+
+if BUILD_LOSETUP
+sbin_PROGRAMS += losetup
+dist_man_MANS += sys-utils/losetup.8
+losetup_SOURCES = sys-utils/losetup.c
+losetup_LDADD = $(LDADD) libcommon.la libsmartcols.la
+losetup_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+
+if HAVE_STATIC_LOSETUP
+bin_PROGRAMS += losetup.static
+losetup_static_SOURCES = $(losetup_SOURCES)
+losetup_static_LDFLAGS = -all-static
+losetup_static_LDADD = $(losetup_LDADD)
+losetup_static_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+endif # BUILD_LOSETUP
+
+
+if BUILD_ZRAMCTL
+sbin_PROGRAMS += zramctl
+dist_man_MANS += sys-utils/zramctl.8
+zramctl_SOURCES = sys-utils/zramctl.c \
+ lib/ismounted.c
+zramctl_LDADD = $(LDADD) libcommon.la libsmartcols.la
+zramctl_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+
+if BUILD_PRLIMIT
+usrbin_exec_PROGRAMS += prlimit
+dist_man_MANS += sys-utils/prlimit.1
+prlimit_SOURCES = sys-utils/prlimit.c
+prlimit_LDADD = $(LDADD) libcommon.la libsmartcols.la
+prlimit_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+
+if BUILD_LSNS
+usrbin_exec_PROGRAMS += lsns
+dist_man_MANS += sys-utils/lsns.8
+lsns_SOURCES = sys-utils/lsns.c
+lsns_LDADD = $(LDADD) libcommon.la libsmartcols.la libmount.la
+lsns_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) -I$(ul_libmount_incdir)
+endif
+
+
+if BUILD_MOUNT
+bin_PROGRAMS += mount umount
+dist_man_MANS += \
+ sys-utils/mount.8 \
+ sys-utils/fstab.5 \
+ sys-utils/umount.8
+mount_SOURCES = sys-utils/mount.c
+mount_LDADD = $(LDADD) libcommon.la libmount.la $(SELINUX_LIBS)
+mount_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS) -I$(ul_libmount_incdir)
+mount_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+
+umount_SOURCES = sys-utils/umount.c
+umount_LDADD = $(LDADD) libcommon.la libmount.la
+umount_CFLAGS = $(AM_CFLAGS) $(SUID_CFLAGS) -I$(ul_libmount_incdir)
+umount_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+
+if HAVE_STATIC_MOUNT
+bin_PROGRAMS += mount.static
+mount_static_SOURCES = $(mount_SOURCES)
+mount_static_CFLAGS = $(mount_CFLAGS)
+mount_static_LDFLAGS = $(mount_LDFLAGS) -all-static
+mount_static_LDADD = $(mount_LDADD) $(SELINUX_LIBS_STATIC)
+endif
+
+if HAVE_STATIC_UMOUNT
+bin_PROGRAMS += umount.static
+umount_static_SOURCES = $(umount_SOURCES)
+umount_static_CFLAGS = $(umount_CFLAGS)
+umount_static_LDFLAGS = $(umount_LDFLAGS) -all-static
+umount_static_LDADD = $(umount_LDADD)
+endif
+
+install-exec-hook-mount:
+if MAKEINSTALL_DO_CHOWN
+ chown root:root $(DESTDIR)$(bindir)/mount
+endif
+if MAKEINSTALL_DO_SETUID
+ chmod 4755 $(DESTDIR)$(bindir)/mount
+endif
+if MAKEINSTALL_DO_CHOWN
+ chown root:root $(DESTDIR)$(bindir)/umount
+endif
+if MAKEINSTALL_DO_SETUID
+ chmod 4755 $(DESTDIR)$(bindir)/umount
+endif
+
+INSTALL_EXEC_HOOKS += install-exec-hook-mount
+endif # BUILD_MOUNT
+
+
+if BUILD_SWAPON
+sbin_PROGRAMS += swapon swapoff
+dist_man_MANS += \
+ sys-utils/swapoff.8 \
+ sys-utils/swapon.8
+
+swapon_SOURCES = \
+ sys-utils/swapon.c \
+ sys-utils/swapon-common.c \
+ sys-utils/swapon-common.h \
+ lib/swapprober.c \
+ include/swapprober.h
+swapon_CFLAGS = $(AM_CFLAGS) \
+ -I$(ul_libblkid_incdir) \
+ -I$(ul_libmount_incdir) \
+ -I$(ul_libsmartcols_incdir)
+swapon_LDADD = $(LDADD) \
+ libblkid.la \
+ libcommon.la \
+ libmount.la \
+ libsmartcols.la
+
+swapoff_SOURCES = \
+ sys-utils/swapoff.c \
+ sys-utils/swapon-common.c \
+ sys-utils/swapon-common.h \
+ lib/swapprober.c \
+ include/swapprober.h
+swapoff_CFLAGS = $(AM_CFLAGS) \
+ -I$(ul_libblkid_incdir) \
+ -I$(ul_libmount_incdir)
+swapoff_LDADD = $(LDADD) \
+ libmount.la \
+ libblkid.la \
+ libcommon.la
+endif
+
+if BUILD_LSCPU
+usrbin_exec_PROGRAMS += lscpu
+lscpu_SOURCES = \
+ sys-utils/lscpu.c \
+ sys-utils/lscpu.h \
+ sys-utils/lscpu-arm.c \
+ sys-utils/lscpu-dmi.c
+lscpu_LDADD = $(LDADD) libcommon.la libsmartcols.la $(RTAS_LIBS)
+lscpu_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+dist_man_MANS += sys-utils/lscpu.1
+endif
+
+if BUILD_CHCPU
+sbin_PROGRAMS += chcpu
+chcpu_SOURCES = sys-utils/chcpu.c
+chcpu_LDADD = $(LDADD) libcommon.la
+dist_man_MANS += sys-utils/chcpu.8
+endif
+
+if BUILD_WDCTL
+bin_PROGRAMS += wdctl
+dist_man_MANS += sys-utils/wdctl.8
+wdctl_SOURCES = sys-utils/wdctl.c
+wdctl_LDADD = $(LDADD) libcommon.la libsmartcols.la
+wdctl_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
+if BUILD_MOUNTPOINT
+bin_PROGRAMS += mountpoint
+mountpoint_LDADD = $(LDADD) libmount.la
+mountpoint_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir)
+dist_man_MANS += sys-utils/mountpoint.1
+mountpoint_SOURCES = sys-utils/mountpoint.c
+endif
+
+if BUILD_FALLOCATE
+usrbin_exec_PROGRAMS += fallocate
+fallocate_SOURCES = sys-utils/fallocate.c
+fallocate_LDADD = $(LDADD) libcommon.la
+dist_man_MANS += sys-utils/fallocate.1
+endif
+
+if BUILD_PIVOT_ROOT
+sbin_PROGRAMS += pivot_root
+dist_man_MANS += sys-utils/pivot_root.8
+pivot_root_SOURCES = sys-utils/pivot_root.c
+endif
+
+if BUILD_SWITCH_ROOT
+sbin_PROGRAMS += switch_root
+dist_man_MANS += sys-utils/switch_root.8
+switch_root_SOURCES = sys-utils/switch_root.c
+endif
+
+if BUILD_UNSHARE
+usrbin_exec_PROGRAMS += unshare
+dist_man_MANS += sys-utils/unshare.1
+unshare_SOURCES = sys-utils/unshare.c \
+ lib/caputils.c \
+ lib/exec_shell.c
+unshare_LDADD = $(LDADD) libcommon.la
+unshare_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir)
+
+if HAVE_STATIC_UNSHARE
+usrbin_exec_PROGRAMS += unshare.static
+unshare_static_SOURCES = $(unshare_SOURCES)
+unshare_static_LDFLAGS = -all-static
+unshare_static_LDADD = $(unshare_LDADD)
+unshare_static_CFLAGS = $(unshare_CFLAGS)
+endif
+endif
+
+if BUILD_NSENTER
+usrbin_exec_PROGRAMS += nsenter
+dist_man_MANS += sys-utils/nsenter.1
+nsenter_SOURCES = sys-utils/nsenter.c lib/exec_shell.c
+nsenter_LDADD = $(LDADD) libcommon.la $(SELINUX_LIBS)
+
+if HAVE_STATIC_NSENTER
+usrbin_exec_PROGRAMS += nsenter.static
+nsenter_static_SOURCES = $(nsenter_SOURCES)
+nsenter_static_LDFLAGS = -all-static
+nsenter_static_LDADD = $(nsenter_LDADD)
+endif
+endif
+
+if BUILD_HWCLOCK
+sbin_PROGRAMS += hwclock
+dist_man_MANS += \
+ sys-utils/hwclock.8 \
+ sys-utils/adjtime_config.5
+PATHFILES += sys-utils/hwclock.8
+hwclock_SOURCES = \
+ sys-utils/hwclock.c \
+ sys-utils/hwclock.h
+if USE_HWCLOCK_GPLv3_DATETIME
+hwclock_SOURCES += \
+ sys-utils/hwclock-parse-date.y
+endif
+hwclock_LDADD = $(LDADD) libcommon.la -lm
+hwclock_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/sys-utils
+if USE_HWCLOCK_CMOS
+hwclock_SOURCES += \
+ sys-utils/hwclock-cmos.c
+endif
+if LINUX
+hwclock_SOURCES += \
+ sys-utils/hwclock-rtc.c \
+ lib/monotonic.c
+hwclock_LDADD += $(REALTIME_LIBS)
+endif
+if HAVE_AUDIT
+hwclock_LDADD += -laudit
+endif
+endif # BUILD_HWCLOCK
+
+if BUILD_SETPRIV
+usrbin_exec_PROGRAMS += setpriv
+dist_man_MANS += sys-utils/setpriv.1
+setpriv_SOURCES = sys-utils/setpriv.c \
+ lib/caputils.c
+setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la
+endif
diff --git a/sys-utils/adjtime_config.5 b/sys-utils/adjtime_config.5
new file mode 100644
index 0000000..8085b49
--- /dev/null
+++ b/sys-utils/adjtime_config.5
@@ -0,0 +1,64 @@
+.TH ADJTIME_CONFIG 5 "August 2018" "util-linux" "File Formats"
+.SH NAME
+adjtime \- information about hardware clock setting and drift factor
+.SH SYNOPSIS
+.I /etc/adjtime
+.SH DESCRIPTION
+The file
+.I /etc/adjtime
+contains descriptive information about the hardware mode clock setting and clock drift factor.
+The file is read and write by hwclock; and read by programs like rtcwake to get RTC time mode.
+.PP
+The file is usually located in /etc, but tools like
+.BR hwclock (8)
+or
+.BR rtcwake (8)
+can use alternative location by command line options if write access to
+/etc is unwanted. The default clock mode is "UTC" if the file is missing.
+.PP
+The Hardware Clock is usually not very accurate. However, much of its inaccuracy is completely predictable - it gains
+or loses the same amount of time every day. This is called systematic drift. The util hwclock keeps the file /etc/adjtime,
+that keeps some historical information.
+For more details see "\fBThe Adjust Function\fR" and "\fBThe Adjtime File\fR" sections from
+.BR hwclock (8)
+man page.
+.PP
+.
+The format of the adjtime file is, in ASCII.
+.sp
+.SS First line
+Three numbers, separated by blanks:
+.TP
+.B "drift factor"
+the systematic drift rate in seconds per day (floating point decimal)
+.TP
+.B last adjust time
+the resulting number of seconds since 1969 UTC of most recent adjustment or calibration (decimal integer)
+.TP
+.B "adjustment status"
+zero (for compatibility with clock(8)) as a floating point decimal
+
+.SS Second line
+.TP
+.B "last calibration time"
+The resulting number of seconds since 1969 UTC of most recent calibration.
+Zero if there has been no calibration yet or it is known that any previous
+calibration is moot (for example, because the Hardware Clock has been found,
+since that calibration, not to contain a valid time). This is a decimal
+integer.
+
+.SS Third line
+.TP
+.B "clock mode"
+Supported values are "UTC" or "LOCAL". Tells whether the Hardware Clock is set
+to Coordinated Universal Time or local time. You can always override this
+value with options on the hwclock command line.
+
+.SH FILES
+.I /etc/adjtime
+.SH SEE ALSO
+.BR hwclock (8),
+.BR rtcwake (8)
+.SH AVAILABILITY
+This man page is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/blkdiscard.8 b/sys-utils/blkdiscard.8
new file mode 100644
index 0000000..d39e855
--- /dev/null
+++ b/sys-utils/blkdiscard.8
@@ -0,0 +1,90 @@
+.TH BLKDISCARD 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+blkdiscard \- discard sectors on a device
+.SH SYNOPSIS
+.B blkdiscard
+[options]
+.RB [ \-o
+.IR offset ]
+.RB [ \-l
+.IR length ]
+.I device
+.SH DESCRIPTION
+.B blkdiscard
+is used to discard device sectors. This is useful for solid-state
+drivers (SSDs) and thinly-provisioned storage. Unlike
+.BR fstrim (8),
+this command is used directly on the block device.
+.PP
+By default,
+.B blkdiscard
+will discard all blocks on the device. Options may be used to modify
+this behavior based on range or size, as explained below.
+.PP
+The
+.I device
+argument is the pathname of the block device.
+.PP
+.B WARNING: All data in the discarded region on the device will be lost!
+.SH OPTIONS
+The
+.I offset
+and
+.I length
+arguments may be followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is
+optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+.TP
+.BR \-f , " \-\-force"
+Disable all checking. Since v2.36 the block device is open in exclusive mode (O_EXCL)
+by default to avoid collision with mounted filesystem or another kernel subsystem.
+The force option disables the exclusive access mode.
+.TP
+.BR \-o , " \-\-offset \fIoffset"
+Byte offset into the device from which to start discarding. The provided value
+must be aligned to the device sector size. The default value is zero.
+.TP
+.BR \-l , " \-\-length \fIlength"
+The number of bytes to discard (counting from the starting point). The provided value
+must be aligned to the device sector size. If the specified value extends past
+the end of the device,
+.B blkdiscard
+will stop at the device size boundary. The default value extends to the end
+of the device.
+.TP
+.BR \-p , " \-\-step \fIlength"
+The number of bytes to discard within one iteration. The default is to discard
+all by one ioctl call.
+.TP
+.BR \-s , " \-\-secure"
+Perform a secure discard. A secure discard is the same as a regular discard
+except that all copies of the discarded blocks that were possibly created by
+garbage collection must also be erased. This requires support from the device.
+.TP
+.BR \-z , " \-\-zeroout"
+Zero-fill rather than discard.
+.TP
+.BR \-v , " \-\-verbose"
+Display the aligned values of
+.I offset
+and
+.IR length .
+If the \fB\-\-step\fR option is specified, it prints the discard progress every second.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+.MT lczerner@redhat.com
+Lukas Czerner
+.ME
+.SH SEE ALSO
+.BR fstrim (8)
+.SH AVAILABILITY
+The blkdiscard command is part of the util-linux package and is available
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/blkdiscard.c b/sys-utils/blkdiscard.c
new file mode 100644
index 0000000..8abc425
--- /dev/null
+++ b/sys-utils/blkdiscard.c
@@ -0,0 +1,318 @@
+/*
+ * blkdiscard.c -- discard the part (or whole) of the block device.
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+ * Written by Lukas Czerner <lczerner@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This program uses BLKDISCARD ioctl to discard part or the whole block
+ * device if the device supports it. You can specify range (start and
+ * length) to be discarded, or simply discard the whole device.
+ */
+
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <linux/fs.h>
+#include <blkid.h>
+
+#include "nls.h"
+#include "strutils.h"
+#include "c.h"
+#include "closestream.h"
+#include "monotonic.h"
+
+#ifndef BLKDISCARD
+# define BLKDISCARD _IO(0x12,119)
+#endif
+
+#ifndef BLKSECDISCARD
+# define BLKSECDISCARD _IO(0x12,125)
+#endif
+
+#ifndef BLKZEROOUT
+# define BLKZEROOUT _IO(0x12,127)
+#endif
+
+enum {
+ ACT_DISCARD = 0, /* default */
+ ACT_ZEROOUT,
+ ACT_SECURE
+};
+
+static void print_stats(int act, char *path, uint64_t stats[])
+{
+ switch (act) {
+ case ACT_ZEROOUT:
+ printf(_("%s: Zero-filled %" PRIu64 " bytes from the offset %" PRIu64"\n"), \
+ path, stats[1], stats[0]);
+ break;
+ case ACT_SECURE:
+ case ACT_DISCARD:
+ printf(_("%s: Discarded %" PRIu64 " bytes from the offset %" PRIu64"\n"), \
+ path, stats[1], stats[0]);
+ break;
+ }
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] <device>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Discard the content of sectors on a device.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -f, --force disable all checking\n"), out);
+ fputs(_(" -o, --offset <num> offset in bytes to discard from\n"), out);
+ fputs(_(" -l, --length <num> length of bytes to discard from the offset\n"), out);
+ fputs(_(" -p, --step <num> size of the discard iterations within the offset\n"), out);
+ fputs(_(" -s, --secure perform secure discard\n"), out);
+ fputs(_(" -z, --zeroout zero-fill rather than discard\n"), out);
+ fputs(_(" -v, --verbose print aligned length and offset\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(21));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<num>")));
+
+ printf(USAGE_MAN_TAIL("blkdiscard(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * Check existing signature on the open fd
+ * Returns 0 signature found
+ * 1 no signature
+ * <0 error
+ */
+static int probe_device(int fd, char *path)
+{
+ const char *type;
+ blkid_probe pr = NULL;
+ int ret = -1;
+
+ pr = blkid_new_probe();
+ if (!pr || blkid_probe_set_device(pr, fd, 0, 0))
+ return ret;
+
+ blkid_probe_enable_superblocks(pr, TRUE);
+ blkid_probe_enable_partitions(pr, TRUE);
+
+ ret = blkid_do_fullprobe(pr);
+ if (ret)
+ goto out;
+
+ if (!blkid_probe_lookup_value(pr, "TYPE", &type, NULL)) {
+ warnx("%s contains existing file system (%s).",path ,type);
+ } else if (!blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL)) {
+ warnx("%s contains existing partition (%s).",path ,type);
+ } else {
+ warnx("%s contains existing signature.", path);
+ }
+
+out:
+ blkid_free_probe(pr);
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ char *path;
+ int c, fd, verbose = 0, secsize, force = 0;
+ uint64_t end, blksize, step, range[2], stats[2];
+ struct stat sb;
+ struct timeval now, last;
+ int act = ACT_DISCARD;
+
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "offset", required_argument, NULL, 'o' },
+ { "force", no_argument, NULL, 'f' },
+ { "length", required_argument, NULL, 'l' },
+ { "step", required_argument, NULL, 'p' },
+ { "secure", no_argument, NULL, 's' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "zeroout", no_argument, NULL, 'z' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ range[0] = 0;
+ range[1] = ULLONG_MAX;
+ step = 0;
+
+ while ((c = getopt_long(argc, argv, "hfVsvo:l:p:z", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'f':
+ force = 1;
+ break;
+ case 'l':
+ range[1] = strtosize_or_err(optarg,
+ _("failed to parse length"));
+ break;
+ case 'o':
+ range[0] = strtosize_or_err(optarg,
+ _("failed to parse offset"));
+ break;
+ case 'p':
+ step = strtosize_or_err(optarg,
+ _("failed to parse step"));
+ break;
+ case 's':
+ act = ACT_SECURE;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'z':
+ act = ACT_ZEROOUT;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no device specified"));
+
+ path = argv[optind++];
+
+ if (optind != argc) {
+ warnx(_("unexpected number of arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ fd = open(path, O_RDWR | (force ? 0 : O_EXCL));
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), path);
+
+ if (fstat(fd, &sb) == -1)
+ err(EXIT_FAILURE, _("stat of %s failed"), path);
+ if (!S_ISBLK(sb.st_mode))
+ errx(EXIT_FAILURE, _("%s: not a block device"), path);
+
+ if (ioctl(fd, BLKGETSIZE64, &blksize))
+ err(EXIT_FAILURE, _("%s: BLKGETSIZE64 ioctl failed"), path);
+ if (ioctl(fd, BLKSSZGET, &secsize))
+ err(EXIT_FAILURE, _("%s: BLKSSZGET ioctl failed"), path);
+
+ /* check offset alignment to the sector size */
+ if (range[0] % secsize)
+ errx(EXIT_FAILURE, _("%s: offset %" PRIu64 " is not aligned "
+ "to sector size %i"), path, range[0], secsize);
+
+ /* is the range end behind the end of the device ?*/
+ if (range[0] > blksize)
+ errx(EXIT_FAILURE, _("%s: offset is greater than device size"), path);
+ end = range[0] + range[1];
+ if (end < range[0] || end > blksize)
+ end = blksize;
+
+ range[1] = (step > 0) ? step : end - range[0];
+
+ /* check length alignment to the sector size */
+ if (range[1] % secsize)
+ errx(EXIT_FAILURE, _("%s: length %" PRIu64 " is not aligned "
+ "to sector size %i"), path, range[1], secsize);
+
+ /* Check for existing signatures on the device */
+ switch(probe_device(fd, path)) {
+ case 0: /* signature detected */
+ /*
+ * Only require force in interactive mode to avoid
+ * breaking existing scripts
+ */
+ if (!force && isatty(STDIN_FILENO)) {
+ errx(EXIT_FAILURE,
+ _("This is destructive operation, data will " \
+ "be lost! Use the -f option to override."));
+ }
+ warnx(_("Operation forced, data will be lost!"));
+ break;
+ case 1: /* no signature */
+ break;
+ default: /* error */
+ err(EXIT_FAILURE, _("failed to probe the device"));
+ break;
+ }
+
+ stats[0] = range[0], stats[1] = 0;
+ gettime_monotonic(&last);
+
+ for (/* nothing */; range[0] < end; range[0] += range[1]) {
+ if (range[0] + range[1] > end)
+ range[1] = end - range[0];
+
+ switch (act) {
+ case ACT_ZEROOUT:
+ if (ioctl(fd, BLKZEROOUT, &range))
+ err(EXIT_FAILURE, _("%s: BLKZEROOUT ioctl failed"), path);
+ break;
+ case ACT_SECURE:
+ if (ioctl(fd, BLKSECDISCARD, &range))
+ err(EXIT_FAILURE, _("%s: BLKSECDISCARD ioctl failed"), path);
+ break;
+ case ACT_DISCARD:
+ if (ioctl(fd, BLKDISCARD, &range))
+ err(EXIT_FAILURE, _("%s: BLKDISCARD ioctl failed"), path);
+ break;
+ }
+
+ stats[1] += range[1];
+
+ /* reporting progress at most once per second */
+ if (verbose && step) {
+ gettime_monotonic(&now);
+ if (now.tv_sec > last.tv_sec &&
+ (now.tv_usec >= last.tv_usec || now.tv_sec > last.tv_sec + 1)) {
+ print_stats(act, path, stats);
+ stats[0] += stats[1], stats[1] = 0;
+ last = now;
+ }
+ }
+ }
+
+ if (verbose && stats[1])
+ print_stats(act, path, stats);
+
+ close(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/blkzone.8 b/sys-utils/blkzone.8
new file mode 100644
index 0000000..64ad23b
--- /dev/null
+++ b/sys-utils/blkzone.8
@@ -0,0 +1,133 @@
+.TH BLKZONE 8 "February 2017" "util-linux" "System Administration"
+.SH NAME
+blkzone \- run zone command on a device
+.SH SYNOPSIS
+.B blkzone
+.I command
+[options]
+.I device
+.SH DESCRIPTION
+.B blkzone
+is used to run zone command on device that support the Zoned Block Commands
+(ZBC) or Zoned-device ATA Commands (ZAC). The zones to operate on can be
+specified using the offset, count and length options.
+.PP
+The
+.I device
+argument is the pathname of the block device.
+.SH COMMANDS
+.SS report
+The command \fBblkzone report\fP is used to report device zone information.
+.PP
+By default, the command will report all zones from the start of the
+block device. Options may be used to modify this behavior, changing the
+starting zone or the size of the report, as explained below.
+
+.B Report output
+.TS
+tab(:);
+l l.
+start:Zone start sector
+len:Zone length in number of sectors
+wptr:Zone write pointer position
+reset:Reset write pointer recommended
+non-seq:Non-sequential write resources active
+cond:Zone condition
+type:Zone type
+.TE
+
+.B Zone conditions
+.TS
+tab(:);
+l l.
+cl:Closed
+nw:Not write pointer
+em:Empty
+fu:Full
+oe:Explicitly opened
+oi:Implicitly opened
+ol:Offline
+ro:Read only
+x?:Reserved conditions (should not be reported)
+.TE
+
+.SS reset
+The command \fBblkzone reset\fP is used to reset one or more zones. Unlike
+.BR sg_reset_wp (8),
+this command operates from the block layer and can reset a range of zones.
+
+.SS open
+The command \fBblkzone open\fP is used to explicitly open one or more zones.
+Unlike
+.BR sg_zone (8),
+open action, this command operates from the block layer and can open a range
+of zones.
+
+.SS close
+The command \fBblkzone close\fP is used to close one or more zones. Unlike
+.BR sg_zone (8),
+close action, this command operates from the block layer and can close a range
+of zones.
+
+.SS finish
+The command \fBblkzone finish\fP is used to finish (transition to full condition)
+one or more zones. Unlike
+.BR sg_zone (8),
+finish action, this command operates from the block layer and can finish a range
+of zones.
+
+.PP
+By default, the reset, open, close and finish commands will operate from the zone
+at device sector 0 and operate on all zones. Options may be used to modify this
+behavior as explained below.
+
+.SH OPTIONS
+The
+.I offset
+and
+.I length
+option arguments may be followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is
+optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+Additionally, the 0x prefix can be used to specify \fIoffset\fR and
+\fIlength\fR in hex.
+.TP
+.BR \-o , " \-\-offset "\fIsector\fP
+The starting zone specified as a sector offset. The provided offset in sector
+units (512 bytes) should match the start of a zone. The default value is zero.
+.TP
+.BR \-l , " \-\-length "\fIsectors\fP
+The maximum number of sectors the command should operate on. The default value
+is the number of sectors remaining after \fIoffset\fR. This option cannot be
+used together with the option \fB\-\-count\fP.
+.TP
+.BR \-c , " \-\-count "\fIcount\fP
+The maximum number of zones the command should operate on. The default value
+is the number of zones starting from \fIoffset\fR. This option cannot be
+used together with the option \fB\-\-length\fP.
+.TP
+.BR \-f , " \-\-force"
+Enforce commands to change zone status on block devices used by the system.
+.TP
+.BR \-v , " \-\-verbose"
+Display the number of zones returned in the report or the range of sectors
+reset.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+.nf
+Shaun Tancheff <shaun@tancheff.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR sg_rep_zones (8)
+.SH AVAILABILITY
+The blkzone command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/blkzone.c b/sys-utils/blkzone.c
new file mode 100644
index 0000000..ed5e68d
--- /dev/null
+++ b/sys-utils/blkzone.c
@@ -0,0 +1,470 @@
+/*
+ * blkzone.c -- the block device zone commands
+ *
+ * Copyright (C) 2015,2016 Seagate Technology PLC
+ * Written by Shaun Tancheff <shaun.tancheff@seagate.com>
+ *
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <linux/fs.h>
+#include <linux/blkzoned.h>
+
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "c.h"
+#include "closestream.h"
+#include "blkdev.h"
+#include "sysfs.h"
+#include "optutils.h"
+
+/*
+ * These ioctls are defined in linux/blkzoned.h starting with kernel 5.5.
+ */
+#ifndef BLKOPENZONE
+#define BLKOPENZONE _IOW(0x12, 134, struct blk_zone_range)
+#endif
+#ifndef BLKCLOSEZONE
+#define BLKCLOSEZONE _IOW(0x12, 135, struct blk_zone_range)
+#endif
+#ifndef BLKFINISHZONE
+#define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range)
+#endif
+
+struct blkzone_control;
+
+static int blkzone_report(struct blkzone_control *ctl);
+static int blkzone_action(struct blkzone_control *ctl);
+
+struct blkzone_command {
+ const char *name;
+ int (*handler)(struct blkzone_control *);
+ unsigned long ioctl_cmd;
+ const char *ioctl_name;
+ const char *help;
+};
+
+struct blkzone_control {
+ const char *devname;
+ const struct blkzone_command *command;
+
+ uint64_t total_sectors;
+ int secsize;
+
+ uint64_t offset;
+ uint64_t length;
+ uint32_t count;
+
+ unsigned int force : 1;
+ unsigned int verbose : 1;
+};
+
+static const struct blkzone_command commands[] = {
+ {
+ .name = "report",
+ .handler = blkzone_report,
+ .help = N_("Report zone information about the given device")
+ },{
+ .name = "reset",
+ .handler = blkzone_action,
+ .ioctl_cmd = BLKRESETZONE,
+ .ioctl_name = "BLKRESETZONE",
+ .help = N_("Reset a range of zones.")
+ },{
+ .name = "open",
+ .handler = blkzone_action,
+ .ioctl_cmd = BLKOPENZONE,
+ .ioctl_name = "BLKOPENZONE",
+ .help = N_("Open a range of zones.")
+ },{
+ .name = "close",
+ .handler = blkzone_action,
+ .ioctl_cmd = BLKCLOSEZONE,
+ .ioctl_name = "BLKCLOSEZONE",
+ .help = N_("Close a range of zones.")
+ },{
+ .name = "finish",
+ .handler = blkzone_action,
+ .ioctl_cmd = BLKFINISHZONE,
+ .ioctl_name = "BLKFINISHZONE",
+ .help = N_("Set a range of zones to Full.")
+ }
+};
+
+static const struct blkzone_command *name_to_command(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ if (strcmp(commands[i].name, name) == 0)
+ return &commands[i];
+ }
+
+ return NULL;
+}
+
+static int init_device(struct blkzone_control *ctl, int mode)
+{
+ struct stat sb;
+ int fd;
+
+ fd = open(ctl->devname, mode);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), ctl->devname);
+
+ if (fstat(fd, &sb) == -1)
+ err(EXIT_FAILURE, _("stat of %s failed"), ctl->devname);
+ if (!S_ISBLK(sb.st_mode))
+ errx(EXIT_FAILURE, _("%s: not a block device"), ctl->devname);
+
+ if (blkdev_get_sectors(fd, (unsigned long long *) &ctl->total_sectors))
+ err(EXIT_FAILURE, _("%s: blkdev_get_sectors ioctl failed"), ctl->devname);
+
+ if (blkdev_get_sector_size(fd, &ctl->secsize))
+ err(EXIT_FAILURE, _("%s: BLKSSZGET ioctl failed"), ctl->devname);
+
+ return fd;
+}
+
+/*
+ * Get the device zone size indicated by chunk sectors).
+ */
+static unsigned long blkdev_chunk_sectors(const char *dname)
+{
+ struct path_cxt *pc = NULL;
+ dev_t devno = sysfs_devname_to_devno(dname);
+ dev_t disk;
+ uint64_t sz = 0;
+ int rc;
+
+ /*
+ * Mapping /dev/sdXn -> /sys/block/sdX to read the chunk_size entry.
+ * This method masks off the partition specified by the minor device
+ * component.
+ */
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ return 0;
+
+ rc = sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk);
+ if (rc != 0)
+ goto done;
+
+ /* if @pc is not while-disk device, switch to disk */
+ if (devno != disk) {
+ rc = sysfs_blkdev_init_path(pc, disk, NULL);
+ if (rc != 0)
+ goto done;
+ }
+
+ rc = ul_path_read_u64(pc, &sz, "queue/chunk_sectors");
+done:
+ ul_unref_path(pc);
+ return rc == 0 ? sz : 0;
+}
+
+/*
+ * blkzone report
+ */
+#define DEF_REPORT_LEN (1U << 12) /* 4k zones per report (256k kzalloc) */
+
+static const char *type_text[] = {
+ "RESERVED",
+ "CONVENTIONAL",
+ "SEQ_WRITE_REQUIRED",
+ "SEQ_WRITE_PREFERRED",
+};
+
+static const char *condition_str[] = {
+ "nw", /* Not write pointer */
+ "em", /* Empty */
+ "oi", /* Implicitly opened */
+ "oe", /* Explicitly opened */
+ "cl", /* Closed */
+ "x5", "x6", "x7", "x8", "x9", "xA", "xB", "xC", /* xN: reserved */
+ "ro", /* Read only */
+ "fu", /* Full */
+ "of" /* Offline */
+};
+
+static int blkzone_report(struct blkzone_control *ctl)
+{
+ struct blk_zone_report *zi;
+ unsigned long zonesize;
+ uint32_t i, nr_zones;
+ int fd;
+
+ fd = init_device(ctl, O_RDONLY);
+
+ if (ctl->offset >= ctl->total_sectors)
+ errx(EXIT_FAILURE,
+ _("%s: offset is greater than or equal to device size"), ctl->devname);
+
+ zonesize = blkdev_chunk_sectors(ctl->devname);
+ if (!zonesize)
+ errx(EXIT_FAILURE, _("%s: unable to determine zone size"), ctl->devname);
+
+ if (ctl->count)
+ nr_zones = ctl->count;
+ else if (ctl->length)
+ nr_zones = (ctl->length + zonesize - 1) / zonesize;
+ else
+ nr_zones = 1 + (ctl->total_sectors - ctl->offset) / zonesize;
+
+ zi = xmalloc(sizeof(struct blk_zone_report) +
+ (DEF_REPORT_LEN * sizeof(struct blk_zone)));
+
+ while (nr_zones && ctl->offset < ctl->total_sectors) {
+
+ zi->nr_zones = min(nr_zones, DEF_REPORT_LEN);
+ zi->sector = ctl->offset;
+
+ if (ioctl(fd, BLKREPORTZONE, zi) == -1)
+ err(EXIT_FAILURE, _("%s: BLKREPORTZONE ioctl failed"), ctl->devname);
+
+ if (ctl->verbose)
+ printf(_("Found %d zones from 0x%"PRIx64"\n"),
+ zi->nr_zones, ctl->offset);
+
+ if (!zi->nr_zones)
+ break;
+
+ for (i = 0; i < zi->nr_zones; i++) {
+/*
+ * blk_zone_report hasn't been packed since https://github.com/torvalds/linux/commit/b3e7e7d2d668de0102264302a4d10dd9d4438a42
+ * was merged. See https://github.com/karelzak/util-linux/issues/1083
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
+ const struct blk_zone *entry = &zi->zones[i];
+#pragma GCC diagnostic pop
+ unsigned int type = entry->type;
+ uint64_t start = entry->start;
+ uint64_t wp = entry->wp;
+ uint8_t cond = entry->cond;
+ uint64_t len = entry->len;
+
+ if (!len) {
+ nr_zones = 0;
+ break;
+ }
+
+ printf(_(" start: 0x%09"PRIx64", len 0x%06"PRIx64", wptr 0x%06"PRIx64
+ " reset:%u non-seq:%u, zcond:%2u(%s) [type: %u(%s)]\n"),
+ start, len, (type == 0x1) ? 0 : wp - start,
+ entry->reset, entry->non_seq,
+ cond, condition_str[cond & (ARRAY_SIZE(condition_str) - 1)],
+ type, type_text[type]);
+
+ nr_zones--;
+ ctl->offset = start + len;
+
+ }
+
+ }
+
+ free(zi);
+ close(fd);
+
+ return 0;
+}
+
+/*
+ * blkzone reset, open, close, and finish.
+ */
+static int blkzone_action(struct blkzone_control *ctl)
+{
+ struct blk_zone_range za = { .sector = 0 };
+ unsigned long zonesize;
+ uint64_t zlen;
+ int fd;
+
+ zonesize = blkdev_chunk_sectors(ctl->devname);
+ if (!zonesize)
+ errx(EXIT_FAILURE, _("%s: unable to determine zone size"), ctl->devname);
+
+ fd = init_device(ctl, O_WRONLY | (ctl->force ? 0 : O_EXCL));
+
+ if (ctl->offset & (zonesize - 1))
+ errx(EXIT_FAILURE, _("%s: offset %" PRIu64 " is not aligned "
+ "to zone size %lu"),
+ ctl->devname, ctl->offset, zonesize);
+
+ if (ctl->offset > ctl->total_sectors)
+ errx(EXIT_FAILURE, _("%s: offset is greater than device size"), ctl->devname);
+
+ if (ctl->count)
+ zlen = ctl->count * zonesize;
+ else if (ctl->length)
+ zlen = ctl->length;
+ else
+ zlen = ctl->total_sectors;
+ if (ctl->offset + zlen > ctl->total_sectors)
+ zlen = ctl->total_sectors - ctl->offset;
+
+ if (ctl->length &&
+ (zlen & (zonesize - 1)) &&
+ ctl->offset + zlen != ctl->total_sectors)
+ errx(EXIT_FAILURE, _("%s: number of sectors %" PRIu64 " is not aligned "
+ "to zone size %lu"),
+ ctl->devname, ctl->length, zonesize);
+
+ za.sector = ctl->offset;
+ za.nr_sectors = zlen;
+
+ if (ioctl(fd, ctl->command->ioctl_cmd, &za) == -1)
+ err(EXIT_FAILURE, _("%s: %s ioctl failed"),
+ ctl->devname, ctl->command->ioctl_name);
+ else if (ctl->verbose)
+ printf(_("%s: successful %s of zones in range from %" PRIu64 ", to %" PRIu64),
+ ctl->devname,
+ ctl->command->name,
+ ctl->offset,
+ ctl->offset + zlen);
+ close(fd);
+ return 0;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s <command> [options] <device>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Run zone command on the given block device.\n"), out);
+
+ fputs(USAGE_COMMANDS, out);
+ for (i = 0; i < ARRAY_SIZE(commands); i++)
+ fprintf(out, " %-11s %s\n", commands[i].name, _(commands[i].help));
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -o, --offset <sector> start sector of zone to act (in 512-byte sectors)\n"), out);
+ fputs(_(" -l, --length <sectors> maximum sectors to act (in 512-byte sectors)\n"), out);
+ fputs(_(" -c, --count <number> maximum number of zones\n"), out);
+ fputs(_(" -f, --force enforce on block devices used by the system\n"), out);
+ fputs(_(" -v, --verbose display more details\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<sector> and <sectors>")));
+
+ printf(USAGE_MAN_TAIL("blkzone(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ struct blkzone_control ctl = {
+ .devname = NULL
+ };
+
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "count", required_argument, NULL, 'c' }, /* max #of zones to operate on */
+ { "length", required_argument, NULL, 'l' }, /* max of sectors to operate on */
+ { "offset", required_argument, NULL, 'o' }, /* starting LBA */
+ { "force", no_argument, NULL, 'f' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'c', 'l' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ if (argc >= 2 && *argv[1] != '-') {
+ ctl.command = name_to_command(argv[1]);
+ if (!ctl.command)
+ errx(EXIT_FAILURE, _("%s is not valid command name"), argv[1]);
+ argv++;
+ argc--;
+ }
+
+ while ((c = getopt_long(argc, argv, "hc:l:o:fvV", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'c':
+ ctl.count = strtou32_or_err(optarg,
+ _("failed to parse number of zones"));
+ break;
+ case 'l':
+ ctl.length = strtosize_or_err(optarg,
+ _("failed to parse number of sectors"));
+ break;
+ case 'o':
+ ctl.offset = strtosize_or_err(optarg,
+ _("failed to parse zone offset"));
+ break;
+ case 'f':
+ ctl.force = 1;
+ break;
+ case 'v':
+ ctl.verbose = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (!ctl.command)
+ errx(EXIT_FAILURE, _("no command specified"));
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no device specified"));
+ ctl.devname = argv[optind++];
+
+ if (optind != argc)
+ errx(EXIT_FAILURE,_("unexpected number of arguments"));
+
+ if (ctl.command->handler(&ctl) < 0)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+
+}
diff --git a/sys-utils/chcpu.8 b/sys-utils/chcpu.8
new file mode 100644
index 0000000..41b2d09
--- /dev/null
+++ b/sys-utils/chcpu.8
@@ -0,0 +1,104 @@
+.TH CHCPU 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+chcpu \- configure CPUs
+.SH SYNOPSIS
+.B chcpu
+.BR \-c | \-d | \-e | \-g
+.I cpu-list
+.br
+.B chcpu \-p
+.I mode
+.br
+.B chcpu
+.BR \-r | \-h | \-V
+.SH DESCRIPTION
+.B chcpu
+can modify the state of CPUs. It can enable or disable CPUs, scan for new
+CPUs, change the CPU dispatching
+.I mode
+of the underlying hypervisor, and request CPUs from the hypervisor
+(configure) or return CPUs to the hypervisor (deconfigure).
+.PP
+Some options have a
+.I cpu-list
+argument. Use this argument to specify a comma-separated list of CPUs. The
+list can contain individual CPU addresses or ranges of addresses. For
+example,
+.B 0,5,7,9-11
+makes the command applicable to the CPUs with the addresses 0, 5, 7, 9, 10,
+and 11.
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-configure " \fIcpu-list\fP
+Configure the specified CPUs. Configuring a CPU means that the hypervisor
+takes a CPU from the CPU pool and assigns it to the virtual hardware on which
+your kernel runs.
+.TP
+.BR \-d , " \-\-disable " \fIcpu-list\fP
+Disable the specified CPUs. Disabling a CPU means that the kernel sets it
+offline.
+.TP
+.BR \-e , " \-\-enable " \fIcpu-list\fP
+Enable the specified CPUs. Enabling a CPU means that the kernel sets it
+online. A CPU must be configured, see \fB\-c\fR, before it can be enabled.
+.TP
+.BR \-g , " \-\-deconfigure " \fIcpu-list\fP
+Deconfigure the specified CPUs. Deconfiguring a CPU means that the
+hypervisor removes the CPU from the virtual hardware on which the Linux
+instance runs and returns it to the CPU pool. A CPU must be offline, see
+\fB\-d\fR, before it can be deconfigured.
+.TP
+.BR \-p , " \-\-dispatch " \fImode\fP
+Set the CPU dispatching
+.I mode
+(polarization). This option has an effect only if your hardware architecture
+and hypervisor support CPU polarization. Available
+.I modes
+are:
+.RS 14
+.TP 12
+.PD 0
+.B horizontal
+The workload is spread across all available CPUs.
+.TP 12
+.B vertical
+The workload is concentrated on few CPUs.
+.RE
+.PD 1
+.TP
+.BR \-r , " \-\-rescan"
+Trigger a rescan of CPUs. After a rescan, the Linux kernel recognizes
+the new CPUs. Use this option on systems that do not
+automatically detect newly attached CPUs.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH EXIT STATUS
+.B chcpu
+has the following exit status values:
+.TP
+.B 0
+success
+.TP
+.B 1
+failure
+.TP
+.B 64
+partial success
+.SH AUTHORS
+.MT heiko.carstens@de.ibm.com
+Heiko Carstens
+.ME
+.SH COPYRIGHT
+Copyright IBM Corp. 2011
+.SH SEE ALSO
+.BR lscpu (1)
+.SH AVAILABILITY
+The chcpu command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/chcpu.c b/sys-utils/chcpu.c
new file mode 100644
index 0000000..c4e5bc7
--- /dev/null
+++ b/sys-utils/chcpu.c
@@ -0,0 +1,390 @@
+/*
+ * chcpu - CPU configuration tool
+ *
+ * Copyright IBM Corp. 2011
+ * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "cpuset.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "c.h"
+#include "strutils.h"
+#include "bitops.h"
+#include "path.h"
+#include "closestream.h"
+#include "optutils.h"
+
+#define EXCL_ERROR "--{configure,deconfigure,disable,dispatch,enable}"
+
+/* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */
+#define CHCPU_EXIT_SOMEOK 64
+
+#define _PATH_SYS_CPU "/sys/devices/system/cpu"
+
+static cpu_set_t *onlinecpus;
+static int maxcpus;
+
+#define is_cpu_online(cpu) (CPU_ISSET_S((cpu), CPU_ALLOC_SIZE(maxcpus), onlinecpus))
+#define num_online_cpus() (CPU_COUNT_S(CPU_ALLOC_SIZE(maxcpus), onlinecpus))
+
+enum {
+ CMD_CPU_ENABLE = 0,
+ CMD_CPU_DISABLE,
+ CMD_CPU_CONFIGURE,
+ CMD_CPU_DECONFIGURE,
+ CMD_CPU_RESCAN,
+ CMD_CPU_DISPATCH_HORIZONTAL,
+ CMD_CPU_DISPATCH_VERTICAL,
+};
+
+/* returns: 0 = success
+ * < 0 = failure
+ * > 0 = partial success
+ */
+static int cpu_enable(struct path_cxt *sys, cpu_set_t *cpu_set, size_t setsize, int enable)
+{
+ int cpu;
+ int online, rc;
+ int configured = -1;
+ int fails = 0;
+
+ for (cpu = 0; cpu < maxcpus; cpu++) {
+ if (!CPU_ISSET_S(cpu, setsize, cpu_set))
+ continue;
+ if (ul_path_accessf(sys, F_OK, "cpu%d", cpu) != 0) {
+ warnx(_("CPU %u does not exist"), cpu);
+ fails++;
+ continue;
+ }
+ if (ul_path_accessf(sys, F_OK, "cpu%d/online", cpu) != 0) {
+ warnx(_("CPU %u is not hot pluggable"), cpu);
+ fails++;
+ continue;
+ }
+ if (ul_path_readf_s32(sys, &online, "cpu%d/online", cpu) == 0
+ && online == 1
+ && enable == 1) {
+ printf(_("CPU %u is already enabled\n"), cpu);
+ continue;
+ }
+ if (online == 0 && enable == 0) {
+ printf(_("CPU %u is already disabled\n"), cpu);
+ continue;
+ }
+ if (ul_path_accessf(sys, F_OK, "cpu%d/configure", cpu) == 0)
+ ul_path_readf_s32(sys, &configured, "cpu%d/configure", cpu);
+ if (enable) {
+ rc = ul_path_writef_string(sys, "1", "cpu%d/online", cpu);
+ if (rc != 0 && configured == 0) {
+ warn(_("CPU %u enable failed (CPU is deconfigured)"), cpu);
+ fails++;
+ } else if (rc != 0) {
+ warn(_("CPU %u enable failed"), cpu);
+ fails++;
+ } else
+ printf(_("CPU %u enabled\n"), cpu);
+ } else {
+ if (onlinecpus && num_online_cpus() == 1) {
+ warnx(_("CPU %u disable failed (last enabled CPU)"), cpu);
+ fails++;
+ continue;
+ }
+ rc = ul_path_writef_string(sys, "0", "cpu%d/online", cpu);
+ if (rc != 0) {
+ warn(_("CPU %u disable failed"), cpu);
+ fails++;
+ } else {
+ printf(_("CPU %u disabled\n"), cpu);
+ if (onlinecpus)
+ CPU_CLR_S(cpu, setsize, onlinecpus);
+ }
+ }
+ }
+
+ return fails == 0 ? 0 : fails == maxcpus ? -1 : 1;
+}
+
+static int cpu_rescan(struct path_cxt *sys)
+{
+ if (ul_path_access(sys, F_OK, "rescan") != 0)
+ errx(EXIT_FAILURE, _("This system does not support rescanning of CPUs"));
+
+ if (ul_path_write_string(sys, "1", "rescan") != 0)
+ err(EXIT_FAILURE, _("Failed to trigger rescan of CPUs"));
+
+ printf(_("Triggered rescan of CPUs\n"));
+ return 0;
+}
+
+static int cpu_set_dispatch(struct path_cxt *sys, int mode)
+{
+ if (ul_path_access(sys, F_OK, "dispatching") != 0)
+ errx(EXIT_FAILURE, _("This system does not support setting "
+ "the dispatching mode of CPUs"));
+ if (mode == 0) {
+ if (ul_path_write_string(sys, "0", "dispatching") != 0)
+ err(EXIT_FAILURE, _("Failed to set horizontal dispatch mode"));
+
+ printf(_("Successfully set horizontal dispatching mode\n"));
+ } else {
+ if (ul_path_write_string(sys, "1", "dispatching") != 0)
+ err(EXIT_FAILURE, _("Failed to set vertical dispatch mode"));
+
+ printf(_("Successfully set vertical dispatching mode\n"));
+ }
+ return 0;
+}
+
+/* returns: 0 = success
+ * < 0 = failure
+ * > 0 = partial success
+ */
+static int cpu_configure(struct path_cxt *sys, cpu_set_t *cpu_set, size_t setsize, int configure)
+{
+ int cpu;
+ int rc, current;
+ int fails = 0;
+
+ for (cpu = 0; cpu < maxcpus; cpu++) {
+ if (!CPU_ISSET_S(cpu, setsize, cpu_set))
+ continue;
+ if (ul_path_accessf(sys, F_OK, "cpu%d", cpu) != 0) {
+ warnx(_("CPU %u does not exist"), cpu);
+ fails++;
+ continue;
+ }
+ if (ul_path_accessf(sys, F_OK, "cpu%d/configure", cpu) != 0) {
+ warnx(_("CPU %u is not configurable"), cpu);
+ fails++;
+ continue;
+ }
+ ul_path_readf_s32(sys, &current, "cpu%d/configure", cpu);
+ if (current == 1 && configure == 1) {
+ printf(_("CPU %u is already configured\n"), cpu);
+ continue;
+ }
+ if (current == 0 && configure == 0) {
+ printf(_("CPU %u is already deconfigured\n"), cpu);
+ continue;
+ }
+ if (current == 1 && configure == 0 && onlinecpus &&
+ is_cpu_online(cpu)) {
+ warnx(_("CPU %u deconfigure failed (CPU is enabled)"), cpu);
+ fails++;
+ continue;
+ }
+ if (configure) {
+ rc = ul_path_writef_string(sys, "1", "cpu%d/configure", cpu);
+ if (rc != 0) {
+ warn(_("CPU %u configure failed"), cpu);
+ fails++;
+ } else
+ printf(_("CPU %u configured\n"), cpu);
+ } else {
+ rc = ul_path_writef_string(sys, "0", "cpu%d/configure", cpu);
+ if (rc != 0) {
+ warn(_("CPU %u deconfigure failed"), cpu);
+ fails++;
+ } else
+ printf(_("CPU %u deconfigured\n"), cpu);
+ }
+ }
+
+ return fails == 0 ? 0 : fails == maxcpus ? -1 : 1;
+}
+
+static void cpu_parse(char *cpu_string, cpu_set_t *cpu_set, size_t setsize)
+{
+ int rc;
+
+ rc = cpulist_parse(cpu_string, cpu_set, setsize, 1);
+ if (rc == 0)
+ return;
+ if (rc == 2)
+ errx(EXIT_FAILURE, _("invalid CPU number in CPU list: %s"), cpu_string);
+ errx(EXIT_FAILURE, _("failed to parse CPU list: %s"), cpu_string);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fprintf(out, _(
+ "\nUsage:\n"
+ " %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Configure CPUs in a multi-processor system.\n"), out);
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(
+ " -e, --enable <cpu-list> enable cpus\n"
+ " -d, --disable <cpu-list> disable cpus\n"
+ " -c, --configure <cpu-list> configure cpus\n"
+ " -g, --deconfigure <cpu-list> deconfigure cpus\n"
+ " -p, --dispatch <mode> set dispatching mode\n"
+ " -r, --rescan trigger rescan of cpus\n"
+ ), stdout);
+ printf(USAGE_HELP_OPTIONS(31));
+
+ printf(USAGE_MAN_TAIL("chcpu(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct path_cxt *sys = NULL; /* _PATH_SYS_CPU handler */
+ cpu_set_t *cpu_set = NULL;
+ size_t setsize;
+ int cmd = -1;
+ int c, rc;
+
+ static const struct option longopts[] = {
+ { "configure", required_argument, NULL, 'c' },
+ { "deconfigure",required_argument, NULL, 'g' },
+ { "disable", required_argument, NULL, 'd' },
+ { "dispatch", required_argument, NULL, 'p' },
+ { "enable", required_argument, NULL, 'e' },
+ { "help", no_argument, NULL, 'h' },
+ { "rescan", no_argument, NULL, 'r' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'c','d','e','g','p' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ ul_path_init_debug();
+ sys = ul_new_path(_PATH_SYS_CPU);
+ if (!sys)
+ err(EXIT_FAILURE, _("failed to initialize sysfs handler"));
+
+ maxcpus = get_max_number_of_cpus();
+ if (maxcpus < 1)
+ errx(EXIT_FAILURE, _("cannot determine NR_CPUS; aborting"));
+
+ if (ul_path_access(sys, F_OK, "online") == 0)
+ ul_path_readf_cpulist(sys, &cpu_set, maxcpus, "online");
+ else
+ cpu_set = CPU_ALLOC(maxcpus);
+ if (!cpu_set)
+ err(EXIT_FAILURE, _("cpuset_alloc failed"));
+
+ setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ while ((c = getopt_long(argc, argv, "c:d:e:g:hp:rV", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'c':
+ cmd = CMD_CPU_CONFIGURE;
+ cpu_parse(argv[optind - 1], cpu_set, setsize);
+ break;
+ case 'd':
+ cmd = CMD_CPU_DISABLE;
+ cpu_parse(argv[optind - 1], cpu_set, setsize);
+ break;
+ case 'e':
+ cmd = CMD_CPU_ENABLE;
+ cpu_parse(argv[optind - 1], cpu_set, setsize);
+ break;
+ case 'g':
+ cmd = CMD_CPU_DECONFIGURE;
+ cpu_parse(argv[optind - 1], cpu_set, setsize);
+ break;
+ case 'p':
+ if (strcmp("horizontal", argv[optind - 1]) == 0)
+ cmd = CMD_CPU_DISPATCH_HORIZONTAL;
+ else if (strcmp("vertical", argv[optind - 1]) == 0)
+ cmd = CMD_CPU_DISPATCH_VERTICAL;
+ else
+ errx(EXIT_FAILURE, _("unsupported argument: %s"),
+ argv[optind -1 ]);
+ break;
+ case 'r':
+ cmd = CMD_CPU_RESCAN;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if ((argc == 1) || (argc != optind)) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ switch (cmd) {
+ case CMD_CPU_ENABLE:
+ rc = cpu_enable(sys, cpu_set, maxcpus, 1);
+ break;
+ case CMD_CPU_DISABLE:
+ rc = cpu_enable(sys, cpu_set, maxcpus, 0);
+ break;
+ case CMD_CPU_CONFIGURE:
+ rc = cpu_configure(sys, cpu_set, maxcpus, 1);
+ break;
+ case CMD_CPU_DECONFIGURE:
+ rc = cpu_configure(sys, cpu_set, maxcpus, 0);
+ break;
+ case CMD_CPU_RESCAN:
+ rc = cpu_rescan(sys);
+ break;
+ case CMD_CPU_DISPATCH_HORIZONTAL:
+ rc = cpu_set_dispatch(sys, 0);
+ break;
+ case CMD_CPU_DISPATCH_VERTICAL:
+ rc = cpu_set_dispatch(sys, 1);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ ul_unref_path(sys);
+
+ return rc == 0 ? EXIT_SUCCESS :
+ rc < 0 ? EXIT_FAILURE : CHCPU_EXIT_SOMEOK;
+}
diff --git a/sys-utils/chmem.8 b/sys-utils/chmem.8
new file mode 100644
index 0000000..0727cec
--- /dev/null
+++ b/sys-utils/chmem.8
@@ -0,0 +1,114 @@
+.TH CHMEM 8 "October 2016" "util-linux" "System Administration"
+.SH NAME
+chmem \- configure memory
+.SH SYNOPSIS
+.B chmem
+.RB [ \-h "] [" \-V "] [" \-v "] [" \-e | \-d "]"
+[\fISIZE\fP|\fIRANGE\fP|\fB\-b\fP \fIBLOCKRANGE\fP]
+[-z ZONE]
+.SH DESCRIPTION
+The chmem command sets a particular size or range of memory online or offline.
+.
+.IP "\(hy" 2
+Specify \fISIZE\fP as <size>[m|M|g|G]. With m or M, <size> specifies the memory
+size in MiB (1024 x 1024 bytes). With g or G, <size> specifies the memory size
+in GiB (1024 x 1024 x 1024 bytes). The default unit is MiB.
+.
+.IP "\(hy" 2
+Specify \fIRANGE\fP in the form 0x<start>-0x<end> as shown in the output of the
+\fBlsmem\fP command. <start> is the hexadecimal address of the first byte and <end>
+is the hexadecimal address of the last byte in the memory range.
+.
+.IP "\(hy" 2
+Specify \fIBLOCKRANGE\fP in the form <first>-<last> or <block> as shown in the
+output of the \fBlsmem\fP command. <first> is the number of the first memory block
+and <last> is the number of the last memory block in the memory
+range. Alternatively a single block can be specified. \fIBLOCKRANGE\fP requires
+the \fB\-\-blocks\fP option.
+.
+.IP "\(hy" 2
+Specify \fIZONE\fP as the name of a memory zone, as shown in the output of the
+\fBlsmem \-o +ZONES\fP command. The output shows one or more valid memory zones
+for each memory range. If multiple zones are shown, then the memory range
+currently belongs to the first zone. By default, chmem will set memory online
+to the zone Movable, if this is among the valid zones. This default can be
+changed by specifying the \fB\-\-zone\fP option with another valid zone.
+For memory ballooning, it is recommended to select the zone Movable for memory
+online and offline, if possible. Memory in this zone is much more likely to be
+able to be offlined again, but it cannot be used for arbitrary kernel
+allocations, only for migratable pages (e.g., anonymous and page cache pages).
+Use the \fB\-\-help\fR option to see all available zones.
+.
+.PP
+\fISIZE\fP and \fIRANGE\fP must be aligned to the Linux memory block size, as
+shown in the output of the \fBlsmem\fP command.
+
+Setting memory online can fail for various reasons. On virtualized systems it
+can fail if the hypervisor does not have enough memory left, for example
+because memory was overcommitted. Setting memory offline can fail if Linux
+cannot free the memory. If only part of the requested memory can be set online
+or offline, a message tells you how much memory was set online or offline
+instead of the requested amount.
+
+When setting memory online \fBchmem\fP starts with the lowest memory block
+numbers. When setting memory offline \fBchmem\fP starts with the highest memory
+block numbers.
+.SH OPTIONS
+.TP
+.BR \-b ", " \-\-blocks
+Use a \fIBLOCKRANGE\fP parameter instead of \fIRANGE\fP or \fISIZE\fP for the
+\fB\-\-enable\fP and \fB\-\-disable\fP options.
+.TP
+.BR \-d ", " \-\-disable
+Set the specified \fIRANGE\fP, \fISIZE\fP, or \fIBLOCKRANGE\fP of memory offline.
+.TP
+.BR \-e ", " \-\-enable
+Set the specified \fIRANGE\fP, \fISIZE\fP, or \fIBLOCKRANGE\fP of memory online.
+.TP
+.BR \-z ", " \-\-zone
+Select the memory \fIZONE\fP where to set the specified \fIRANGE\fP, \fISIZE\fP,
+or \fIBLOCKRANGE\fP of memory online or offline. By default, memory will be set
+online to the zone Movable, if possible.
+.TP
+.BR \-h ", " \-\-help
+Print a short help text, then exit.
+.TP
+.BR \-v ", " \-\-verbose
+Verbose mode. Causes \fBchmem\fP to print debugging messages about it's
+progress.
+.TP
+.BR \-V ", " \-\-version
+Print the version number, then exit.
+.SH EXIT STATUS
+.B chmem
+has the following exit status values:
+.TP
+.B 0
+success
+.TP
+.B 1
+failure
+.TP
+.B 64
+partial success
+.SH EXAMPLE
+.TP
+.B chmem \-\-enable 1024
+This command requests 1024 MiB of memory to be set online.
+.TP
+.B chmem \-e 2g
+This command requests 2 GiB of memory to be set online.
+.TP
+.B chmem \-\-disable 0x00000000e4000000-0x00000000f3ffffff
+This command requests the memory range starting with 0x00000000e4000000
+and ending with 0x00000000f3ffffff to be set offline.
+.TP
+.B chmem \-b \-d 10
+This command requests the memory block number 10 to be set offline.
+.SH SEE ALSO
+.BR lsmem (1)
+.SH AVAILABILITY
+The \fBchmem\fP command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/chmem.c b/sys-utils/chmem.c
new file mode 100644
index 0000000..2f231d6
--- /dev/null
+++ b/sys-utils/chmem.c
@@ -0,0 +1,452 @@
+/*
+ * chmem - Memory configuration tool
+ *
+ * Copyright IBM Corp. 2016
+ *
+ * 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 would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+#include <dirent.h>
+
+#include "c.h"
+#include "nls.h"
+#include "path.h"
+#include "strutils.h"
+#include "strv.h"
+#include "optutils.h"
+#include "closestream.h"
+#include "xalloc.h"
+
+/* partial success, otherwise we return regular EXIT_{SUCCESS,FAILURE} */
+#define CHMEM_EXIT_SOMEOK 64
+
+#define _PATH_SYS_MEMORY "/sys/devices/system/memory"
+
+struct chmem_desc {
+ struct path_cxt *sysmem; /* _PATH_SYS_MEMORY handler */
+ struct dirent **dirs;
+ int ndirs;
+ uint64_t block_size;
+ uint64_t start;
+ uint64_t end;
+ uint64_t size;
+ unsigned int use_blocks : 1;
+ unsigned int is_size : 1;
+ unsigned int verbose : 1;
+ unsigned int have_zones : 1;
+};
+
+enum {
+ CMD_MEMORY_ENABLE = 0,
+ CMD_MEMORY_DISABLE,
+ CMD_NONE
+};
+
+enum zone_id {
+ ZONE_DMA = 0,
+ ZONE_DMA32,
+ ZONE_NORMAL,
+ ZONE_HIGHMEM,
+ ZONE_MOVABLE,
+ ZONE_DEVICE,
+};
+
+static char *zone_names[] = {
+ [ZONE_DMA] = "DMA",
+ [ZONE_DMA32] = "DMA32",
+ [ZONE_NORMAL] = "Normal",
+ [ZONE_HIGHMEM] = "Highmem",
+ [ZONE_MOVABLE] = "Movable",
+ [ZONE_DEVICE] = "Device",
+};
+
+/*
+ * name must be null-terminated
+ */
+static int zone_name_to_id(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
+ if (!strcasecmp(name, zone_names[i]))
+ return i;
+ }
+ return -1;
+}
+
+static void idxtostr(struct chmem_desc *desc, uint64_t idx, char *buf, size_t bufsz)
+{
+ uint64_t start, end;
+
+ start = idx * desc->block_size;
+ end = start + desc->block_size - 1;
+ snprintf(buf, bufsz,
+ _("Memory Block %"PRIu64" (0x%016"PRIx64"-0x%016"PRIx64")"),
+ idx, start, end);
+}
+
+static int chmem_size(struct chmem_desc *desc, int enable, int zone_id)
+{
+ char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
+ uint64_t size, index;
+ const char *zn;
+ int i, rc;
+
+ size = desc->size;
+ onoff = enable ? "online" : "offline";
+ i = enable ? 0 : desc->ndirs - 1;
+
+ if (enable && zone_id >= 0) {
+ if (zone_id == ZONE_MOVABLE)
+ onoff = "online_movable";
+ else
+ onoff = "online_kernel";
+ }
+
+ for (; i >= 0 && i < desc->ndirs && size; i += enable ? 1 : -1) {
+ name = desc->dirs[i]->d_name;
+ index = strtou64_or_err(name + 6, _("Failed to parse index"));
+
+ if (ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/state", name) > 0
+ && strncmp(onoff, line, 6) == 0)
+ continue;
+
+ if (desc->have_zones) {
+ ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/valid_zones", name);
+ if (zone_id >= 0) {
+ zn = zone_names[zone_id];
+ if (enable && !strcasestr(line, zn))
+ continue;
+ if (!enable && strncasecmp(line, zn, strlen(zn)) != 0)
+ continue;
+ } else if (enable) {
+ /* By default, use zone Movable for online, if valid */
+ if (strcasestr(line, zone_names[ZONE_MOVABLE]))
+ onoff = "online_movable";
+ else
+ onoff = "online";
+ }
+ }
+
+ idxtostr(desc, index, str, sizeof(str));
+ rc = ul_path_writef_string(desc->sysmem, onoff, "%s/state", name);
+ if (rc != 0 && desc->verbose) {
+ if (enable)
+ fprintf(stdout, _("%s enable failed\n"), str);
+ else
+ fprintf(stdout, _("%s disable failed\n"), str);
+ } else if (rc == 0 && desc->verbose) {
+ if (enable)
+ fprintf(stdout, _("%s enabled\n"), str);
+ else
+ fprintf(stdout, _("%s disabled\n"), str);
+ }
+ if (rc == 0)
+ size--;
+ }
+ if (size) {
+ uint64_t bytes;
+ char *sizestr;
+
+ bytes = (desc->size - size) * desc->block_size;
+ sizestr = size_to_human_string(SIZE_SUFFIX_1LETTER, bytes);
+ if (enable)
+ warnx(_("Could only enable %s of memory"), sizestr);
+ else
+ warnx(_("Could only disable %s of memory"), sizestr);
+ free(sizestr);
+ }
+ return size == 0 ? 0 : size == desc->size ? -1 : 1;
+}
+
+static int chmem_range(struct chmem_desc *desc, int enable, int zone_id)
+{
+ char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
+ uint64_t index, todo;
+ const char *zn;
+ int i, rc;
+
+ todo = desc->end - desc->start + 1;
+ onoff = enable ? "online" : "offline";
+
+ if (enable && zone_id >= 0) {
+ if (zone_id == ZONE_MOVABLE)
+ onoff = "online_movable";
+ else
+ onoff = "online_kernel";
+ }
+
+ for (i = 0; i < desc->ndirs; i++) {
+ name = desc->dirs[i]->d_name;
+ index = strtou64_or_err(name + 6, _("Failed to parse index"));
+ if (index < desc->start)
+ continue;
+ if (index > desc->end)
+ break;
+ idxtostr(desc, index, str, sizeof(str));
+ if (ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/state", name) > 0
+ && strncmp(onoff, line, 6) == 0) {
+ if (desc->verbose && enable)
+ fprintf(stdout, _("%s already enabled\n"), str);
+ else if (desc->verbose && !enable)
+ fprintf(stdout, _("%s already disabled\n"), str);
+ todo--;
+ continue;
+ }
+
+ if (desc->have_zones) {
+ ul_path_readf_buffer(desc->sysmem, line, sizeof(line), "%s/valid_zones", name);
+ if (zone_id >= 0) {
+ zn = zone_names[zone_id];
+ if (enable && !strcasestr(line, zn)) {
+ warnx(_("%s enable failed: Zone mismatch"), str);
+ continue;
+ }
+ if (!enable && strncasecmp(line, zn, strlen(zn)) != 0) {
+ warnx(_("%s disable failed: Zone mismatch"), str);
+ continue;
+ }
+ } else if (enable) {
+ /* By default, use zone Movable for online, if valid */
+ if (strcasestr(line, zone_names[ZONE_MOVABLE]))
+ onoff = "online_movable";
+ else
+ onoff = "online";
+ }
+ }
+
+ rc = ul_path_writef_string(desc->sysmem, onoff, "%s/state", name);
+ if (rc != 0) {
+ if (enable)
+ warn(_("%s enable failed"), str);
+ else
+ warn(_("%s disable failed"), str);
+ } else if (desc->verbose) {
+ if (enable)
+ fprintf(stdout, _("%s enabled\n"), str);
+ else
+ fprintf(stdout, _("%s disabled\n"), str);
+ }
+ if (rc == 0)
+ todo--;
+ }
+ return todo == 0 ? 0 : todo == desc->end - desc->start + 1 ? -1 : 1;
+}
+
+static int filter(const struct dirent *de)
+{
+ if (strncmp("memory", de->d_name, 6) != 0)
+ return 0;
+ return isdigit_string(de->d_name + 6);
+}
+
+static void read_info(struct chmem_desc *desc)
+{
+ char line[128];
+
+ desc->ndirs = scandir(_PATH_SYS_MEMORY, &desc->dirs, filter, versionsort);
+ if (desc->ndirs <= 0)
+ err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY);
+ ul_path_read_buffer(desc->sysmem, line, sizeof(line), "block_size_bytes");
+ desc->block_size = strtoumax(line, NULL, 16);
+}
+
+static void parse_single_param(struct chmem_desc *desc, char *str)
+{
+ if (desc->use_blocks) {
+ desc->start = strtou64_or_err(str, _("Failed to parse block number"));
+ desc->end = desc->start;
+ return;
+ }
+ desc->is_size = 1;
+ desc->size = strtosize_or_err(str, _("Failed to parse size"));
+ if (isdigit(str[strlen(str) - 1]))
+ desc->size *= 1024*1024;
+ if (desc->size % desc->block_size) {
+ errx(EXIT_FAILURE, _("Size must be aligned to memory block size (%s)"),
+ size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
+ }
+ desc->size /= desc->block_size;
+}
+
+static void parse_range_param(struct chmem_desc *desc, char *start, char *end)
+{
+ if (desc->use_blocks) {
+ desc->start = strtou64_or_err(start, _("Failed to parse start"));
+ desc->end = strtou64_or_err(end, _("Failed to parse end"));
+ return;
+ }
+ if (strlen(start) < 2 || start[1] != 'x')
+ errx(EXIT_FAILURE, _("Invalid start address format: %s"), start);
+ if (strlen(end) < 2 || end[1] != 'x')
+ errx(EXIT_FAILURE, _("Invalid end address format: %s"), end);
+ desc->start = strtox64_or_err(start, _("Failed to parse start address"));
+ desc->end = strtox64_or_err(end, _("Failed to parse end address"));
+ if (desc->start % desc->block_size || (desc->end + 1) % desc->block_size) {
+ errx(EXIT_FAILURE,
+ _("Start address and (end address + 1) must be aligned to "
+ "memory block size (%s)"),
+ size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size));
+ }
+ desc->start /= desc->block_size;
+ desc->end /= desc->block_size;
+}
+
+static void parse_parameter(struct chmem_desc *desc, char *param)
+{
+ char **split;
+
+ split = strv_split(param, "-");
+ if (strv_length(split) > 2)
+ errx(EXIT_FAILURE, _("Invalid parameter: %s"), param);
+ if (strv_length(split) == 1)
+ parse_single_param(desc, split[0]);
+ else
+ parse_range_param(desc, split[0], split[1]);
+ strv_free(split);
+ if (desc->start > desc->end)
+ errx(EXIT_FAILURE, _("Invalid range: %s"), param);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Set a particular size or range of memory online or offline.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -e, --enable enable memory\n"), out);
+ fputs(_(" -d, --disable disable memory\n"), out);
+ fputs(_(" -b, --blocks use memory blocks\n"), out);
+ fputs(_(" -z, --zone <name> select memory zone (see below)\n"), out);
+ fputs(_(" -v, --verbose verbose output\n"), out);
+ printf(USAGE_HELP_OPTIONS(20));
+
+ fputs(_("\nSupported zones:\n"), out);
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++)
+ fprintf(out, " %s\n", zone_names[i]);
+
+ printf(USAGE_MAN_TAIL("chmem(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct chmem_desc _desc = { 0 }, *desc = &_desc;
+ int cmd = CMD_NONE, zone_id = -1;
+ char *zone = NULL;
+ int c, rc;
+
+ static const struct option longopts[] = {
+ {"block", no_argument, NULL, 'b'},
+ {"disable", no_argument, NULL, 'd'},
+ {"enable", no_argument, NULL, 'e'},
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {"zone", required_argument, NULL, 'z'},
+ {NULL, 0, NULL, 0}
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'd','e' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ ul_path_init_debug();
+ desc->sysmem = ul_new_path(_PATH_SYS_MEMORY);
+ if (!desc->sysmem)
+ err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_MEMORY);
+
+ read_info(desc);
+
+ while ((c = getopt_long(argc, argv, "bdehvVz:", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'd':
+ cmd = CMD_MEMORY_DISABLE;
+ break;
+ case 'e':
+ cmd = CMD_MEMORY_ENABLE;
+ break;
+ case 'b':
+ desc->use_blocks = 1;
+ break;
+ case 'v':
+ desc->verbose = 1;
+ break;
+ case 'z':
+ zone = xstrdup(optarg);
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if ((argc == 1) || (argc != optind + 1) || (cmd == CMD_NONE)) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ parse_parameter(desc, argv[optind]);
+
+
+ /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
+ if (ul_path_access(desc->sysmem, F_OK, "memory0/valid_zones") == 0)
+ desc->have_zones = 1;
+ else if (zone)
+ warnx(_("zone ignored, no valid_zones sysfs attribute present"));
+
+ if (zone && desc->have_zones) {
+ zone_id = zone_name_to_id(zone);
+ if (zone_id == -1) {
+ warnx(_("unknown memory zone: %s"), zone);
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (desc->is_size)
+ rc = chmem_size(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
+ else
+ rc = chmem_range(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
+
+ ul_unref_path(desc->sysmem);
+
+ return rc == 0 ? EXIT_SUCCESS :
+ rc < 0 ? EXIT_FAILURE : CHMEM_EXIT_SOMEOK;
+}
diff --git a/sys-utils/choom.1 b/sys-utils/choom.1
new file mode 100644
index 0000000..ce2b8b5
--- /dev/null
+++ b/sys-utils/choom.1
@@ -0,0 +1,83 @@
+.TH CHOOM 1 "April 2018" "util-linux" "User Commands"
+.SH NAME
+choom \- display and adjust OOM-killer score.
+.SH SYNOPSIS
+.B choom
+.B \-p
+.I pid
+.sp
+.B choom
+.B \-p
+.I pid
+.B \-n
+.I number
+.sp
+.B choom
+.B \-n
+.I number
+.B [\-\-]
+.IR command\ [ argument ...]
+
+.SH DESCRIPTION
+The \fBchoom\fP command displays and adjusts Out-Of-Memory killer score setting.
+
+.SH OPTIONS
+.TP
+.BR \-p ", " \-\-pid " \fIpid\fP"
+Specifies process ID.
+.TP
+.BR \-n , " \-\-adjust " \fIvalue\fP
+Specify the adjust score value.
+.TP
+.BR \-h ", " \-\-help
+Display help text and exit.
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+.SH NOTES
+Linux kernel uses the badness heuristic to select which process gets killed in
+out of memory conditions.
+
+The badness heuristic assigns a value to each candidate task ranging from 0
+(never kill) to 1000 (always kill) to determine which process is targeted. The
+units are roughly a proportion along that range of allowed memory the process
+may allocate from based on an estimation of its current memory and swap use.
+For example, if a task is using all allowed memory, its badness score will be
+1000. If it is using half of its allowed memory, its score will be 500.
+
+There is an additional factor included in the badness score: the current memory
+and swap usage is discounted by 3% for root processes.
+
+The amount of "allowed" memory depends on the context in which the oom killer
+was called. If it is due to the memory assigned to the allocating task's cpuset
+being exhausted, the allowed memory represents the set of mems assigned to that
+cpuset. If it is due to a mempolicy's node(s) being exhausted, the allowed
+memory represents the set of mempolicy nodes. If it is due to a memory
+limit (or swap limit) being reached, the allowed memory is that configured
+limit. Finally, if it is due to the entire system being out of memory, the
+allowed memory represents all allocatable resources.
+
+The adjust score value is added to the badness score before it is used to
+determine which task to kill. Acceptable values range from -1000 to +1000.
+This allows userspace to polarize the preference for oom killing either by
+always preferring a certain task or completely disabling it. The lowest
+possible value, -1000, is equivalent to disabling oom killing entirely for that
+task since it will always report a badness score of 0.
+
+Setting an adjust score value of +500, for example, is roughly equivalent to
+allowing the remainder of tasks sharing the same system, cpuset, mempolicy, or
+memory controller resources to use at least 50% more memory. A value of -500,
+on the other hand, would be roughly equivalent to discounting 50% of the task's
+allowed memory from being considered as scoring against the task.
+
+.SH AUTHORS
+.nf
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR proc (5)
+.SH AVAILABILITY
+The \fBchoom\fP command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/choom.c b/sys-utils/choom.c
new file mode 100644
index 0000000..b3d3e4d
--- /dev/null
+++ b/sys-utils/choom.c
@@ -0,0 +1,159 @@
+/*
+ * choom - Change OOM score setting
+ *
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include "nls.h"
+#include "c.h"
+#include "path.h"
+#include "strutils.h"
+#include "closestream.h"
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %1$s [options] -p pid\n"
+ " %1$s [options] -n number -p pid\n"
+ " %1$s [options] -n number [--] command [args...]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display and adjust OOM-killer score.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -n, --adjust <num> specify the adjust score value\n"), out);
+ fputs(_(" -p, --pid <num> process ID\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+ printf(USAGE_MAN_TAIL("choom(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int get_score(struct path_cxt *pc)
+{
+ int ret;
+
+ if (ul_path_read_s32(pc, &ret, "oom_score") != 0)
+ err(EXIT_FAILURE, _("failed to read OOM score value"));
+
+ return ret;
+}
+
+static int get_score_adj(struct path_cxt *pc)
+{
+ int ret;
+
+ if (ul_path_read_s32(pc, &ret, "oom_score_adj") != 0)
+ err(EXIT_FAILURE, _("failed to read OOM score adjust value"));
+
+ return ret;
+}
+
+static int set_score_adj(struct path_cxt *pc, int adj)
+{
+ return ul_path_write_s64(pc, adj, "oom_score_adj");
+}
+
+int main(int argc, char **argv)
+{
+ pid_t pid = 0;
+ int c, adj = 0, has_adj = 0;
+ struct path_cxt *pc = NULL;
+
+ static const struct option longopts[] = {
+ { "adjust", required_argument, NULL, 'n' },
+ { "pid", required_argument, NULL, 'p' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "hn:p:V", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'p':
+ pid = strtos32_or_err(optarg, _("invalid PID argument"));
+ break;
+ case 'n':
+ adj = strtos32_or_err(optarg, _("invalid adjust argument"));
+ has_adj = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind < argc && pid) {
+ warnx(_("invalid argument: %s"), argv[optind]);
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (!pid && argc - optind < 1) {
+ warnx(_("no PID or COMMAND specified"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (optind < argc && !has_adj) {
+ warnx(_("no OOM score adjust value specified"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ pc = ul_new_path("/proc/%d", (int) (pid ? pid : getpid()));
+
+ /* Show */
+ if (!has_adj) {
+ printf(_("pid %d's current OOM score: %d\n"), pid, get_score(pc));
+ printf(_("pid %d's current OOM score adjust value: %d\n"), pid, get_score_adj(pc));
+
+ /* Change */
+ } else if (pid) {
+ int old = get_score_adj(pc);
+
+ if (set_score_adj(pc, adj))
+ err(EXIT_FAILURE, _("failed to set score adjust value"));
+
+ printf(_("pid %d's OOM score adjust value changed from %d to %d\n"), pid, old, adj);
+
+ /* Start new process */
+ } else {
+ if (set_score_adj(pc, adj))
+ err(EXIT_FAILURE, _("failed to set score adjust value"));
+ ul_unref_path(pc);
+ argv += optind;
+ execvp(argv[0], argv);
+ errexec(argv[0]);
+ }
+
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/ctrlaltdel.8 b/sys-utils/ctrlaltdel.8
new file mode 100644
index 0000000..3f6657e
--- /dev/null
+++ b/sys-utils/ctrlaltdel.8
@@ -0,0 +1,58 @@
+.\" Copyright 1992, 1993 Rickard E. Faith (faith@cs.unc.edu)
+.\" May be distributed under the GNU General Public License
+.TH CTRLALTDEL 8 "October 2015" "util-linux" "System Administration"
+.SH NAME
+ctrlaltdel \- set the function of the Ctrl-Alt-Del combination
+.SH SYNOPSIS
+.BR "ctrlaltdel hard" | soft
+.SH DESCRIPTION
+Based on examination of the
+.I linux/kernel/reboot.c
+code, it is clear that there are two supported functions that the
+Ctrl-Alt-Del sequence can perform.
+.TP
+.B hard
+Immediately reboot the computer without calling
+.BR sync (2)
+and without any other preparation. This is the default.
+.TP
+.B soft
+Make the kernel send the SIGINT (interrupt) signal to the
+.B init
+process (this is always the process with PID 1). If this option is used,
+the
+.BR init (8)
+program must support this feature. Since there are now several
+.BR init (8)
+programs in the Linux community, please consult the documentation for the
+version that you are currently using.
+.PP
+When the command is run without any argument, it will display the current
+setting.
+.PP
+The function of
+.B ctrlaltdel
+is usually set in the
+.I /etc/rc.local
+file.
+.SH OPTIONS
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH FILES
+.I /etc/rc.local
+.SH AUTHORS
+.UR poe@daimi.aau.dk
+Peter Orbaek
+.UE
+.SH SEE ALSO
+.BR init (8),
+.BR systemd (1)
+.SH AVAILABILITY
+The ctrlaltdel command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/ctrlaltdel.c b/sys-utils/ctrlaltdel.c
new file mode 100644
index 0000000..303d2dc
--- /dev/null
+++ b/sys-utils/ctrlaltdel.c
@@ -0,0 +1,113 @@
+/*
+ * ctrlaltdel.c - Set the function of the Ctrl-Alt-Del combination
+ * Created 4-Jul-92 by Peter Orbaek <poe@daimi.aau.dk>
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/reboot.h>
+#include "nls.h"
+#include "c.h"
+#include "closestream.h"
+#include "pathnames.h"
+#include "path.h"
+
+#define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF
+#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s hard|soft\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fprintf(out, _("Set the function of the Ctrl-Alt-Del combination.\n"));
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("ctrlaltdel(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int get_cad(void)
+{
+ uint64_t val;
+
+ if (ul_path_read_u64(NULL, &val, _PATH_PROC_CTRL_ALT_DEL) != 0)
+ err(EXIT_FAILURE, _("cannot read %s"), _PATH_PROC_CTRL_ALT_DEL);
+
+ switch (val) {
+ case 0:
+ fputs("soft\n", stdout);
+ break;
+ case 1:
+ fputs("hard\n", stdout);
+ break;
+ default:
+ printf("%s hard\n", _("implicit"));
+ warnx(_("unexpected value in %s: %ju"), _PATH_PROC_CTRL_ALT_DEL, val);
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+static int set_cad(const char *arg)
+{
+ unsigned int cmd;
+
+ if (geteuid()) {
+ warnx(_("You must be root to set the Ctrl-Alt-Del behavior"));
+ return EXIT_FAILURE;
+ }
+ if (!strcmp("hard", arg))
+ cmd = LINUX_REBOOT_CMD_CAD_ON;
+ else if (!strcmp("soft", arg))
+ cmd = LINUX_REBOOT_CMD_CAD_OFF;
+ else {
+ warnx(_("unknown argument: %s"), arg);
+ return EXIT_FAILURE;
+ }
+ if (reboot(cmd) < 0) {
+ warn("reboot");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int ch, ret;
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (argc < 2)
+ ret = get_cad();
+ else
+ ret = set_cad(argv[1]);
+ return ret;
+}
diff --git a/sys-utils/dmesg.1 b/sys-utils/dmesg.1
new file mode 100644
index 0000000..4bec789
--- /dev/null
+++ b/sys-utils/dmesg.1
@@ -0,0 +1,266 @@
+.\" Copyright 1993 Rickard E. Faith (faith@cs.unc.edu)
+.\" May be distributed under the GNU General Public License
+.TH DMESG "1" "July 2012" "util-linux" "User Commands"
+.SH NAME
+dmesg \- print or control the kernel ring buffer
+.SH SYNOPSIS
+.B dmesg
+[options]
+.sp
+.B dmesg \-\-clear
+.br
+.BR "dmesg \-\-read\-clear " [options]
+.br
+.BI "dmesg \-\-console\-level " level
+.br
+.B dmesg \-\-console\-on
+.br
+.B dmesg \-\-console\-off
+.SH DESCRIPTION
+.B dmesg
+is used to examine or control the kernel ring buffer.
+.PP
+The default action is to display all messages from the kernel ring buffer.
+.SH OPTIONS
+The
+.BR \-\-clear ,
+.BR \-\-read\-clear ,
+.BR \-\-console\-on ,
+.BR \-\-console\-off ,
+and
+.B \-\-console\-level
+options are mutually exclusive.
+.IP "\fB\-C\fR, \fB\-\-clear\fR"
+Clear the ring buffer.
+.IP "\fB\-c\fR, \fB\-\-read\-clear\fR"
+Clear the ring buffer after first printing its contents.
+.IP "\fB\-D\fR, \fB\-\-console\-off\fR"
+Disable the printing of messages to the console.
+.IP "\fB\-d\fR, \fB\-\-show\-delta\fR"
+Display the timestamp and the time delta spent between messages. If used
+together with
+.B \-\-notime
+then only the time delta without the timestamp is printed.
+.IP "\fB\-E\fR, \fB\-\-console\-on\fR"
+Enable printing messages to the console.
+.IP "\fB\-e\fR, \fB\-\-reltime\fR"
+Display the local time and the delta in human-readable format. Be aware that
+conversion to the local time could be inaccurate (see \fB\-T\fR for more
+details).
+.IP "\fB\-F\fR, \fB\-\-file \fIfile\fR"
+Read the syslog messages from the given
+.IR file .
+Note that \fB\-F\fR does not support messages in kmsg format. The old syslog format is supported only.
+.IP "\fB\-f\fR, \fB\-\-facility \fIlist\fR"
+Restrict output to the given (comma-separated)
+.I list
+of facilities. For example:
+.PP
+.RS 14
+.B dmesg \-\-facility=daemon
+.RE
+.IP
+will print messages from system daemons only. For all supported facilities
+see the
+.B \-\-help
+output.
+.IP "\fB\-H\fR, \fB\-\-human\fR"
+Enable human-readable output. See also \fB\-\-color\fR, \fB\-\-reltime\fR
+and \fB\-\-nopager\fR.
+.IP "\fB\-k\fR, \fB\-\-kernel\fR"
+Print kernel messages.
+.IP "\fB\-L\fR, \fB\-\-color\fR[=\fIwhen\fR]"
+Colorize the output. The optional argument \fIwhen\fP
+can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted,
+it defaults to \fBauto\fR. The colors can be disabled; for the current built-in default
+see the \fB\-\-help\fR output. See also the \fBCOLORS\fR section below.
+.IP "\fB\-l\fR, \fB\-\-level \fIlist\fR"
+Restrict output to the given (comma-separated)
+.I list
+of levels. For example:
+.PP
+.RS 14
+.B dmesg \-\-level=err,warn
+.RE
+.IP
+will print error and warning messages only. For all supported levels see the
+.B \-\-help
+output.
+.IP "\fB\-n\fR, \fB\-\-console\-level \fIlevel\fR"
+Set the
+.I level
+at which printing of messages is done to the console. The
+.I level
+is a level number or abbreviation of the level name. For all supported
+levels see the
+.B \-\-help
+output.
+.sp
+For example,
+.B \-n 1
+or
+.B \-n emerg
+prevents all messages, except emergency (panic) messages, from appearing on
+the console. All levels of messages are still written to
+.IR /proc/kmsg ,
+so
+.BR syslogd (8)
+can still be used to control exactly where kernel messages appear. When the
+.B \-n
+option is used,
+.B dmesg
+will
+.I not
+print or clear the kernel ring buffer.
+.IP "\fB\-\-noescape\fR"
+The unprintable and potentially unsafe characters (e.g., broken multi-byte
+sequences, terminal controlling chars, etc.) are escaped in format \\x<hex> for
+security reason by default. This option disables this feature at all. It's
+usable for example for debugging purpose together with \fB\-\-raw\fR. Be
+careful and don't use it by default.
+.IP "\fB\-P\fR, \fB\-\-nopager\fR"
+Do not pipe output into a pager. A pager is enabled by default for \fB\-\-human\fR output.
+.IP "\fB\-p\fR, \fB\-\-force\-prefix\fR"
+Add facility, level or timestamp information to each line of a multi-line message.
+.IP "\fB\-r\fR, \fB\-\-raw\fR"
+Print the raw message buffer, i.e., do not strip the log-level prefixes, but
+all unprintable characters are still escaped (see also \fB\-\-noescape\fR).
+
+Note that the real raw format depends on the method how
+.BR dmesg (1)
+reads kernel messages. The /dev/kmsg device uses a different format than
+.BR syslog (2).
+For backward compatibility,
+.BR dmesg (1)
+returns data always in the
+.BR syslog (2)
+format. It is possible to read the real raw data from /dev/kmsg by, for example,
+the command 'dd if=/dev/kmsg iflag=nonblock'.
+.IP "\fB\-S\fR, \fB\-\-syslog\fR"
+Force \fBdmesg\fR to use the
+.BR syslog (2)
+kernel interface to read kernel messages. The default is to use /dev/kmsg rather
+than
+.BR syslog (2)
+since kernel 3.5.0.
+.IP "\fB\-s\fR, \fB\-\-buffer\-size \fIsize\fR"
+Use a buffer of
+.I size
+to query the kernel ring buffer. This is 16392 by default. (The default
+kernel syslog buffer size was 4096 at first, 8192 since 1.3.54, 16384 since
+2.1.113.) If you have set the kernel buffer to be larger than the default,
+then this option can be used to view the entire buffer.
+.IP "\fB\-T\fR, \fB\-\-ctime\fR"
+Print human-readable timestamps.
+.IP
+.B Be aware that the timestamp could be inaccurate!
+The
+.B time
+source used for the logs is
+.B not updated after
+system
+.BR SUSPEND / RESUME .
+Timestamps are adjusted according to current delta between boottime and monotonic
+clocks, this works only for messages printed after last resume.
+.IP "\fB\-t\fR, \fB\-\-notime\fR"
+Do not print kernel's timestamps.
+.IP "\fB\-\-time\-format\fR \fIformat\fR"
+Print timestamps using the given \fIformat\fR, which can be
+.BR ctime ,
+.BR reltime ,
+.B delta
+or
+.BR iso .
+The first three formats are aliases of the time-format-specific options.
+The
+.B iso
+format is a
+.B dmesg
+implementation of the ISO-8601 timestamp format. The purpose of this format is
+to make the comparing of timestamps between two systems, and any other parsing,
+easy. The definition of the \fBiso\fR timestamp is:
+YYYY-MM-DD<T>HH:MM:SS,<microseconds><-+><timezone offset from UTC>.
+.IP
+The
+.B iso
+format has the same issue as
+.BR ctime :
+the time may be inaccurate when a system is suspended and resumed.
+.TP
+.BR \-u , " \-\-userspace"
+Print userspace messages.
+.TP
+.BR \-w , " \-\-follow"
+Wait for new messages. This feature is supported only on systems with
+a readable /dev/kmsg (since kernel 3.5.0).
+.TP
+.BR \-W , " \-\-follow-new"
+Wait and print only new messages.
+.TP
+.BR \-x , " \-\-decode"
+Decode facility and level (priority) numbers to human-readable prefixes.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH COLORS
+Implicit coloring can be disabled by an empty file \fI/etc/terminal-colors.d/dmesg.disable\fR.
+See
+.BR terminal-colors.d (5)
+for more details about colorization configuration.
+.PP
+The logical color names supported by
+.B dmesg
+are:
+.TP
+.B subsys
+The message sub-system prefix (e.g., "ACPI:").
+.TP
+.B time
+The message timestamp.
+.TP
+.B timebreak
+The message timestamp in short ctime format in \fB\-\-reltime\fR
+or \fB\-\-human\fR output.
+.TP
+.B alert
+The text of the message with the alert log priority.
+.TP
+.B crit
+The text of the message with the critical log priority.
+.TP
+.B err
+The text of the message with the error log priority.
+.TP
+.B warn
+The text of the message with the warning log priority.
+.TP
+.B segfault
+The text of the message that inform about segmentation fault.
+.SH EXIT STATUS
+.B dmesg
+can fail reporting permission denied error. This is usually caused by
+.B dmesg_restrict
+kernel setting, please see
+.BR syslog (2)
+for more details.
+.SH AUTHORS
+.MT kzak@redhat.com
+Karel Zak
+.ME
+
+.B dmesg
+was originally written by
+.MT tytso@athena.mit.edu
+Theodore Ts'o
+.ME
+.SH SEE ALSO
+.BR terminal-colors.d (5),
+.BR syslogd (8)
+.SH AVAILABILITY
+The dmesg command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/dmesg.c b/sys-utils/dmesg.c
new file mode 100644
index 0000000..c775985
--- /dev/null
+++ b/sys-utils/dmesg.c
@@ -0,0 +1,1587 @@
+/*
+ * dmesg.c -- Print out the contents of the kernel ring buffer
+ *
+ * Copyright (C) 1993 Theodore Ts'o <tytso@athena.mit.edu>
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This program comes with ABSOLUTELY NO WARRANTY.
+ */
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <sys/klog.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <sys/sysinfo.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "c.h"
+#include "colors.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "widechar.h"
+#include "all-io.h"
+#include "bitops.h"
+#include "closestream.h"
+#include "optutils.h"
+#include "timeutils.h"
+#include "monotonic.h"
+#include "mangle.h"
+#include "pager.h"
+
+/* Close the log. Currently a NOP. */
+#define SYSLOG_ACTION_CLOSE 0
+/* Open the log. Currently a NOP. */
+#define SYSLOG_ACTION_OPEN 1
+/* Read from the log. */
+#define SYSLOG_ACTION_READ 2
+/* Read all messages remaining in the ring buffer. (allowed for non-root) */
+#define SYSLOG_ACTION_READ_ALL 3
+/* Read and clear all messages remaining in the ring buffer */
+#define SYSLOG_ACTION_READ_CLEAR 4
+/* Clear ring buffer. */
+#define SYSLOG_ACTION_CLEAR 5
+/* Disable printk's to console */
+#define SYSLOG_ACTION_CONSOLE_OFF 6
+/* Enable printk's to console */
+#define SYSLOG_ACTION_CONSOLE_ON 7
+/* Set level of messages printed to console */
+#define SYSLOG_ACTION_CONSOLE_LEVEL 8
+/* Return number of unread characters in the log buffer */
+#define SYSLOG_ACTION_SIZE_UNREAD 9
+/* Return size of the log buffer */
+#define SYSLOG_ACTION_SIZE_BUFFER 10
+
+/*
+ * Color scheme
+ */
+struct dmesg_color {
+ const char *scheme; /* name used in termina-colors.d/dmesg.scheme */
+ const char *dflt; /* default color ESC sequence */
+};
+
+enum {
+ DMESG_COLOR_SUBSYS,
+ DMESG_COLOR_TIME,
+ DMESG_COLOR_TIMEBREAK,
+ DMESG_COLOR_ALERT,
+ DMESG_COLOR_CRIT,
+ DMESG_COLOR_ERR,
+ DMESG_COLOR_WARN,
+ DMESG_COLOR_SEGFAULT
+};
+
+static const struct dmesg_color colors[] =
+{
+ [DMESG_COLOR_SUBSYS] = { "subsys", UL_COLOR_BROWN },
+ [DMESG_COLOR_TIME] = { "time", UL_COLOR_GREEN },
+ [DMESG_COLOR_TIMEBREAK] = { "timebreak",UL_COLOR_GREEN UL_COLOR_BOLD },
+ [DMESG_COLOR_ALERT] = { "alert", UL_COLOR_REVERSE UL_COLOR_RED },
+ [DMESG_COLOR_CRIT] = { "crit", UL_COLOR_BOLD UL_COLOR_RED },
+ [DMESG_COLOR_ERR] = { "err", UL_COLOR_RED },
+ [DMESG_COLOR_WARN] = { "warn", UL_COLOR_BOLD },
+ [DMESG_COLOR_SEGFAULT] = { "segfault", UL_COLOR_HALFBRIGHT UL_COLOR_RED }
+};
+
+#define dmesg_enable_color(_id) \
+ color_scheme_enable(colors[_id].scheme, colors[_id].dflt);
+
+/*
+ * Priority and facility names
+ */
+struct dmesg_name {
+ const char *name;
+ const char *help;
+};
+
+/*
+ * Priority names -- based on sys/syslog.h
+ */
+static const struct dmesg_name level_names[] =
+{
+ [LOG_EMERG] = { "emerg", N_("system is unusable") },
+ [LOG_ALERT] = { "alert", N_("action must be taken immediately") },
+ [LOG_CRIT] = { "crit", N_("critical conditions") },
+ [LOG_ERR] = { "err", N_("error conditions") },
+ [LOG_WARNING] = { "warn", N_("warning conditions") },
+ [LOG_NOTICE] = { "notice",N_("normal but significant condition") },
+ [LOG_INFO] = { "info", N_("informational") },
+ [LOG_DEBUG] = { "debug", N_("debug-level messages") }
+};
+
+/*
+ * sys/syslog.h uses (f << 3) for all facility codes.
+ * We want to use the codes as array indexes, so shift back...
+ *
+ * Note that libc LOG_FAC() macro returns the base codes, not the
+ * shifted code :-)
+ */
+#define FAC_BASE(f) ((f) >> 3)
+
+static const struct dmesg_name facility_names[] =
+{
+ [FAC_BASE(LOG_KERN)] = { "kern", N_("kernel messages") },
+ [FAC_BASE(LOG_USER)] = { "user", N_("random user-level messages") },
+ [FAC_BASE(LOG_MAIL)] = { "mail", N_("mail system") },
+ [FAC_BASE(LOG_DAEMON)] = { "daemon", N_("system daemons") },
+ [FAC_BASE(LOG_AUTH)] = { "auth", N_("security/authorization messages") },
+ [FAC_BASE(LOG_SYSLOG)] = { "syslog", N_("messages generated internally by syslogd") },
+ [FAC_BASE(LOG_LPR)] = { "lpr", N_("line printer subsystem") },
+ [FAC_BASE(LOG_NEWS)] = { "news", N_("network news subsystem") },
+ [FAC_BASE(LOG_UUCP)] = { "uucp", N_("UUCP subsystem") },
+ [FAC_BASE(LOG_CRON)] = { "cron", N_("clock daemon") },
+ [FAC_BASE(LOG_AUTHPRIV)] = { "authpriv", N_("security/authorization messages (private)") },
+ [FAC_BASE(LOG_FTP)] = { "ftp", N_("FTP daemon") },
+};
+
+/* supported methods to read message buffer
+ */
+enum {
+ DMESG_METHOD_KMSG, /* read messages from /dev/kmsg (default) */
+ DMESG_METHOD_SYSLOG, /* klogctl() buffer */
+ DMESG_METHOD_MMAP /* mmap file with records (see --file) */
+};
+
+enum {
+ DMESG_TIMEFTM_NONE = 0,
+ DMESG_TIMEFTM_CTIME, /* [ctime] */
+ DMESG_TIMEFTM_CTIME_DELTA, /* [ctime <delta>] */
+ DMESG_TIMEFTM_DELTA, /* [<delta>] */
+ DMESG_TIMEFTM_RELTIME, /* [relative] */
+ DMESG_TIMEFTM_TIME, /* [time] */
+ DMESG_TIMEFTM_TIME_DELTA, /* [time <delta>] */
+ DMESG_TIMEFTM_ISO8601 /* 2013-06-13T22:11:00,123456+0100 */
+};
+#define is_timefmt(c, f) ((c)->time_fmt == (DMESG_TIMEFTM_ ##f))
+
+struct dmesg_control {
+ /* bit arrays -- see include/bitops.h */
+ char levels[ARRAY_SIZE(level_names) / NBBY + 1];
+ char facilities[ARRAY_SIZE(facility_names) / NBBY + 1];
+
+ struct timeval lasttime; /* last printed timestamp */
+ struct tm lasttm; /* last localtime */
+ struct timeval boot_time; /* system boot time */
+ time_t suspended_time; /* time spent in suspended state */
+
+ int action; /* SYSLOG_ACTION_* */
+ int method; /* DMESG_METHOD_* */
+
+ size_t bufsize; /* size of syslog buffer */
+
+ int kmsg; /* /dev/kmsg file descriptor */
+ ssize_t kmsg_first_read;/* initial read() return code */
+ char kmsg_buf[BUFSIZ];/* buffer to read kmsg data */
+
+ /*
+ * For the --file option we mmap whole file. The unnecessary (already
+ * printed) pages are always unmapped. The result is that we have in
+ * memory only the currently used page(s).
+ */
+ char *filename;
+ char *mmap_buff;
+ size_t pagesize;
+ unsigned int time_fmt; /* time format */
+
+ unsigned int follow:1, /* wait for new messages */
+ end:1, /* seek to the of buffer */
+ raw:1, /* raw mode */
+ noesc:1, /* no escape */
+ fltr_lev:1, /* filter out by levels[] */
+ fltr_fac:1, /* filter out by facilities[] */
+ decode:1, /* use "facility: level: " prefix */
+ pager:1, /* pipe output into a pager */
+ color:1, /* colorize messages */
+ force_prefix:1; /* force timestamp and decode prefix
+ on each line */
+ int indent; /* due to timestamps if newline */
+};
+
+struct dmesg_record {
+ const char *mesg;
+ size_t mesg_size;
+
+ int level;
+ int facility;
+ struct timeval tv;
+
+ const char *next; /* buffer with next unparsed record */
+ size_t next_size; /* size of the next buffer */
+};
+
+#define INIT_DMESG_RECORD(_r) do { \
+ (_r)->mesg = NULL; \
+ (_r)->mesg_size = 0; \
+ (_r)->facility = -1; \
+ (_r)->level = -1; \
+ (_r)->tv.tv_sec = 0; \
+ (_r)->tv.tv_usec = 0; \
+ } while (0)
+
+static int read_kmsg(struct dmesg_control *ctl);
+
+static int set_level_color(int log_level, const char *mesg, size_t mesgsz)
+{
+ int id = -1;
+
+ switch (log_level) {
+ case LOG_ALERT:
+ id = DMESG_COLOR_ALERT;
+ break;
+ case LOG_CRIT:
+ id = DMESG_COLOR_CRIT;
+ break;
+ case LOG_ERR:
+ id = DMESG_COLOR_ERR;
+ break;
+ case LOG_WARNING:
+ id = DMESG_COLOR_WARN;
+ break;
+ default:
+ break;
+ }
+
+ /* well, sometimes the messages contains important keywords, but in
+ * non-warning/error messages
+ */
+ if (id < 0 && memmem(mesg, mesgsz, "segfault at", 11))
+ id = DMESG_COLOR_SEGFAULT;
+
+ if (id >= 0)
+ dmesg_enable_color(id);
+
+ return id >= 0 ? 0 : -1;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display or control the kernel ring buffer.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -C, --clear clear the kernel ring buffer\n"), out);
+ fputs(_(" -c, --read-clear read and clear all messages\n"), out);
+ fputs(_(" -D, --console-off disable printing messages to console\n"), out);
+ fputs(_(" -E, --console-on enable printing messages to console\n"), out);
+ fputs(_(" -F, --file <file> use the file instead of the kernel log buffer\n"), out);
+ fputs(_(" -f, --facility <list> restrict output to defined facilities\n"), out);
+ fputs(_(" -H, --human human readable output\n"), out);
+ fputs(_(" -k, --kernel display kernel messages\n"), out);
+ fprintf(out,
+ _(" -L, --color[=<when>] colorize messages (%s, %s or %s)\n"), "auto", "always", "never");
+ fprintf(out,
+ " %s\n", USAGE_COLORS_DEFAULT);
+ fputs(_(" -l, --level <list> restrict output to defined levels\n"), out);
+ fputs(_(" -n, --console-level <level> set level of messages printed to console\n"), out);
+ fputs(_(" -P, --nopager do not pipe output into a pager\n"), out);
+ fputs(_(" -p, --force-prefix force timestamp output on each line of multi-line messages\n"), out);
+ fputs(_(" -r, --raw print the raw message buffer\n"), out);
+ fputs(_(" --noescape don't escape unprintable character\n"), out);
+ fputs(_(" -S, --syslog force to use syslog(2) rather than /dev/kmsg\n"), out);
+ fputs(_(" -s, --buffer-size <size> buffer size to query the kernel ring buffer\n"), out);
+ fputs(_(" -u, --userspace display userspace messages\n"), out);
+ fputs(_(" -w, --follow wait for new messages\n"), out);
+ fputs(_(" -W, --follow-new wait and print only new messages\n"), out);
+ fputs(_(" -x, --decode decode facility and level to readable string\n"), out);
+ fputs(_(" -d, --show-delta show time delta between printed messages\n"), out);
+ fputs(_(" -e, --reltime show local time and time delta in readable format\n"), out);
+ fputs(_(" -T, --ctime show human-readable timestamp (may be inaccurate!)\n"), out);
+ fputs(_(" -t, --notime don't show any timestamp with messages\n"), out);
+ fputs(_(" --time-format <format> show timestamp using the given format:\n"
+ " [delta|reltime|ctime|notime|iso]\n"
+ "Suspending/resume will make ctime and iso timestamps inaccurate.\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(29));
+ fputs(_("\nSupported log facilities:\n"), out);
+ for (i = 0; i < ARRAY_SIZE(level_names); i++)
+ fprintf(out, " %7s - %s\n",
+ facility_names[i].name,
+ _(facility_names[i].help));
+
+ fputs(_("\nSupported log levels (priorities):\n"), out);
+ for (i = 0; i < ARRAY_SIZE(level_names); i++)
+ fprintf(out, " %7s - %s\n",
+ level_names[i].name,
+ _(level_names[i].help));
+
+ printf(USAGE_MAN_TAIL("dmesg(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * LEVEL ::= <number> | <name>
+ * <number> ::= @len is set: number in range <0..N>, where N < ARRAY_SIZE(level_names)
+ * ::= @len not set: number in range <1..N>, where N <= ARRAY_SIZE(level_names)
+ * <name> ::= case-insensitive text
+ *
+ * Note that @len argument is not set when parsing "-n <level>" command line
+ * option. The console_level is interpreted as "log level less than the value".
+ *
+ * For example "dmesg -n 8" or "dmesg -n debug" enables debug console log
+ * level by klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, 8). The @str argument
+ * has to be parsed to number in range <1..8>.
+ */
+static int parse_level(const char *str, size_t len)
+{
+ int offset = 0;
+
+ if (!str)
+ return -1;
+ if (!len) {
+ len = strlen(str);
+ offset = 1;
+ }
+ errno = 0;
+
+ if (isdigit(*str)) {
+ char *end = NULL;
+ long x = strtol(str, &end, 10) - offset;
+
+ if (!errno && end && end > str && (size_t) (end - str) == len &&
+ x >= 0 && (size_t) x < ARRAY_SIZE(level_names))
+ return x + offset;
+ } else {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(level_names); i++) {
+ const char *n = level_names[i].name;
+
+ if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0')
+ return i + offset;
+ }
+ }
+
+ if (errno)
+ err(EXIT_FAILURE, _("failed to parse level '%s'"), str);
+
+ errx(EXIT_FAILURE, _("unknown level '%s'"), str);
+ return -1;
+}
+
+/*
+ * FACILITY ::= <number> | <name>
+ * <number> ::= number in range <0..N>, where N < ARRAY_SIZE(facility_names)
+ * <name> ::= case-insensitive text
+ */
+static int parse_facility(const char *str, size_t len)
+{
+ if (!str)
+ return -1;
+ if (!len)
+ len = strlen(str);
+ errno = 0;
+
+ if (isdigit(*str)) {
+ char *end = NULL;
+ long x = strtol(str, &end, 10);
+
+ if (!errno && end && end > str && (size_t) (end - str) == len &&
+ x >= 0 && (size_t) x < ARRAY_SIZE(facility_names))
+ return x;
+ } else {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(facility_names); i++) {
+ const char *n = facility_names[i].name;
+
+ if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0')
+ return i;
+ }
+ }
+
+ if (errno)
+ err(EXIT_FAILURE, _("failed to parse facility '%s'"), str);
+
+ errx(EXIT_FAILURE, _("unknown facility '%s'"), str);
+ return -1;
+}
+
+/*
+ * Parses numerical prefix used for all messages in kernel ring buffer.
+ *
+ * Priorities/facilities are encoded into a single 32-bit quantity, where the
+ * bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
+ * (0-big number).
+ *
+ * Note that the number has to end with '>' or ',' char.
+ */
+static const char *parse_faclev(const char *str, int *fac, int *lev)
+{
+ long num;
+ char *end = NULL;
+
+ if (!str)
+ return str;
+
+ errno = 0;
+ num = strtol(str, &end, 10);
+
+ if (!errno && end && end > str) {
+ *fac = LOG_FAC(num);
+ *lev = LOG_PRI(num);
+
+ if (*lev < 0 || (size_t) *lev > ARRAY_SIZE(level_names))
+ *lev = -1;
+ if (*fac < 0 || (size_t) *fac > ARRAY_SIZE(facility_names))
+ *fac = -1;
+ return end + 1; /* skip '<' or ',' */
+ }
+
+ return str;
+}
+
+/*
+ * Parses timestamp from syslog message prefix, expected format:
+ *
+ * seconds.microseconds]
+ *
+ * the ']' is the timestamp field terminator.
+ */
+static const char *parse_syslog_timestamp(const char *str0, struct timeval *tv)
+{
+ const char *str = str0;
+ char *end = NULL;
+
+ if (!str0)
+ return str0;
+
+ errno = 0;
+ tv->tv_sec = strtol(str, &end, 10);
+
+ if (!errno && end && *end == '.' && *(end + 1)) {
+ str = end + 1;
+ end = NULL;
+ tv->tv_usec = strtol(str, &end, 10);
+ }
+ if (errno || !end || end == str || *end != ']')
+ return str0;
+
+ return end + 1; /* skip ']' */
+}
+
+/*
+ * Parses timestamp from /dev/kmsg, expected formats:
+ *
+ * microseconds,
+ * microseconds;
+ *
+ * the ',' is fields separators and ';' items terminator (for the last item)
+ */
+static const char *parse_kmsg_timestamp(const char *str0, struct timeval *tv)
+{
+ const char *str = str0;
+ char *end = NULL;
+ uint64_t usec;
+
+ if (!str0)
+ return str0;
+
+ errno = 0;
+ usec = strtoumax(str, &end, 10);
+
+ if (!errno && end && (*end == ';' || *end == ',')) {
+ tv->tv_usec = usec % 1000000;
+ tv->tv_sec = usec / 1000000;
+ } else
+ return str0;
+
+ return end + 1; /* skip separator */
+}
+
+
+static double time_diff(struct timeval *a, struct timeval *b)
+{
+ return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6;
+}
+
+static int get_syslog_buffer_size(void)
+{
+ int n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
+
+ return n > 0 ? n : 0;
+}
+
+/*
+ * Reads messages from regular file by mmap
+ */
+static ssize_t mmap_file_buffer(struct dmesg_control *ctl, char **buf)
+{
+ struct stat st;
+ int fd;
+
+ if (!ctl->filename)
+ return -1;
+
+ fd = open(ctl->filename, O_RDONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), ctl->filename);
+ if (fstat(fd, &st))
+ err(EXIT_FAILURE, _("stat of %s failed"), ctl->filename);
+
+ *buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (*buf == MAP_FAILED)
+ err(EXIT_FAILURE, _("cannot mmap: %s"), ctl->filename);
+ ctl->mmap_buff = *buf;
+ ctl->pagesize = getpagesize();
+ close(fd);
+
+ return st.st_size;
+}
+
+/*
+ * Reads messages from kernel ring buffer by klogctl()
+ */
+static ssize_t read_syslog_buffer(struct dmesg_control *ctl, char **buf)
+{
+ size_t sz;
+ int rc = -1;
+
+ if (ctl->bufsize) {
+ sz = ctl->bufsize + 8;
+ *buf = xmalloc(sz * sizeof(char));
+ rc = klogctl(ctl->action, *buf, sz);
+ } else {
+ sz = 16392;
+ while (1) {
+ *buf = xmalloc(sz * sizeof(char));
+ rc = klogctl(SYSLOG_ACTION_READ_ALL, *buf, sz);
+ if (rc < 0)
+ break;
+ if ((size_t) rc != sz || sz > (1 << 28))
+ break;
+ free(*buf);
+ *buf = NULL;
+ sz *= 4;
+ }
+
+ if (rc > 0 && ctl->action == SYSLOG_ACTION_READ_CLEAR)
+ rc = klogctl(SYSLOG_ACTION_READ_CLEAR, *buf, sz);
+ }
+
+ return rc;
+}
+
+/*
+ * Top level function to read messages
+ */
+static ssize_t read_buffer(struct dmesg_control *ctl, char **buf)
+{
+ ssize_t n = -1;
+
+ switch (ctl->method) {
+ case DMESG_METHOD_MMAP:
+ n = mmap_file_buffer(ctl, buf);
+ break;
+ case DMESG_METHOD_SYSLOG:
+ if (!ctl->bufsize)
+ ctl->bufsize = get_syslog_buffer_size();
+
+ n = read_syslog_buffer(ctl, buf);
+ break;
+ case DMESG_METHOD_KMSG:
+ /*
+ * Since kernel 3.5.0
+ */
+ n = read_kmsg(ctl);
+ if (n == 0 && ctl->action == SYSLOG_ACTION_READ_CLEAR)
+ n = klogctl(SYSLOG_ACTION_CLEAR, NULL, 0);
+ break;
+ default:
+ abort(); /* impossible method -> drop core */
+ }
+
+ return n;
+}
+
+static int fwrite_hex(const char *buf, size_t size, FILE *out)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ int rc = fprintf(out, "\\x%02hhx", buf[i]);
+ if (rc < 0)
+ return rc;
+ }
+ return 0;
+}
+
+/*
+ * Prints to 'out' and non-printable chars are replaced with \x<hex> sequences.
+ */
+static void safe_fwrite(struct dmesg_control *ctl, const char *buf, size_t size, int indent, FILE *out)
+{
+ size_t i;
+#ifdef HAVE_WIDECHAR
+ mbstate_t s;
+ wchar_t wc;
+ memset(&s, 0, sizeof (s));
+#endif
+ for (i = 0; i < size; i++) {
+ const char *p = buf + i;
+ int rc, hex = 0;
+ size_t len = 1;
+
+ if (!ctl->noesc) {
+ if (*p == '\0') {
+ hex = 1;
+ goto doprint;
+ }
+#ifdef HAVE_WIDECHAR
+ len = mbrtowc(&wc, p, size - i, &s);
+
+ if (len == 0) /* L'\0' */
+ return;
+
+ if (len == (size_t)-1 || len == (size_t)-2) { /* invalid sequence */
+ memset(&s, 0, sizeof (s));
+ len = hex = 1;
+ i += len - 1;
+ } else if (len > 1) {
+ if (!iswprint(wc) && !iswspace(wc)) /* non-printable multibyte */
+ hex = 1;
+ i += len - 1;
+ } else
+#endif
+ {
+ len = 1;
+ if (!isprint((unsigned char) *p) &&
+ !isspace((unsigned char) *p)) /* non-printable */
+ hex = 1;
+ }
+ }
+
+doprint:
+ if (hex)
+ rc = fwrite_hex(p, len, out);
+ else if (*p == '\n' && *(p + 1) && indent) {
+ rc = fwrite(p, 1, len, out) != len;
+ if (fprintf(out, "%*s", indent, "") != indent)
+ rc |= 1;
+ } else
+ rc = fwrite(p, 1, len, out) != len;
+
+ if (rc != 0) {
+ if (errno != EPIPE)
+ err(EXIT_FAILURE, _("write failed"));
+ exit(EXIT_SUCCESS);
+ }
+ }
+}
+
+static const char *skip_item(const char *begin, const char *end, const char *sep)
+{
+ while (begin < end) {
+ int c = *begin++;
+
+ if (c == '\0' || strchr(sep, c))
+ break;
+ }
+
+ return begin;
+}
+
+/*
+ * Parses one record from syslog(2) buffer
+ */
+static int get_next_syslog_record(struct dmesg_control *ctl,
+ struct dmesg_record *rec)
+{
+ size_t i;
+ const char *begin = NULL;
+
+ if (ctl->method != DMESG_METHOD_MMAP &&
+ ctl->method != DMESG_METHOD_SYSLOG)
+ return -1;
+
+ if (!rec->next || !rec->next_size)
+ return 1;
+
+ INIT_DMESG_RECORD(rec);
+
+ /*
+ * Unmap already printed file data from memory
+ */
+ if (ctl->mmap_buff && (size_t) (rec->next - ctl->mmap_buff) > ctl->pagesize) {
+ void *x = ctl->mmap_buff;
+
+ ctl->mmap_buff += ctl->pagesize;
+ munmap(x, ctl->pagesize);
+ }
+
+ for (i = 0; i < rec->next_size; i++) {
+ const char *p = rec->next + i;
+ const char *end = NULL;
+
+ if (!begin)
+ begin = p;
+ if (i + 1 == rec->next_size) {
+ end = p + 1;
+ i++;
+ } else if (*p == '\n' && *(p + 1) == '<')
+ end = p;
+
+ if (begin && !*begin)
+ begin = NULL; /* zero(s) at the end of the buffer? */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ continue; /* error or empty line? */
+
+ if (*begin == '<') {
+ if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode || ctl->color)
+ begin = parse_faclev(begin + 1, &rec->facility,
+ &rec->level);
+ else
+ begin = skip_item(begin, end, ">");
+ }
+
+ if (*begin == '[' && (*(begin + 1) == ' ' ||
+ isdigit(*(begin + 1)))) {
+
+ if (!is_timefmt(ctl, NONE))
+ begin = parse_syslog_timestamp(begin + 1, &rec->tv);
+ else
+ begin = skip_item(begin, end, "]");
+
+ if (begin < end && *begin == ' ')
+ begin++;
+ }
+
+ rec->mesg = begin;
+ rec->mesg_size = end - begin;
+
+ /* Don't count \n from the last message to the message size */
+ if (*end != '\n' && *(end - 1) == '\n')
+ rec->mesg_size--;
+
+ rec->next_size -= end - rec->next;
+ rec->next = rec->next_size > 0 ? end + 1 : NULL;
+ if (rec->next_size > 0)
+ rec->next_size--;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+static int accept_record(struct dmesg_control *ctl, struct dmesg_record *rec)
+{
+ if (ctl->fltr_lev && (rec->facility < 0 ||
+ !isset(ctl->levels, rec->level)))
+ return 0;
+
+ if (ctl->fltr_fac && (rec->facility < 0 ||
+ !isset(ctl->facilities, rec->facility)))
+ return 0;
+
+ return 1;
+}
+
+static void raw_print(struct dmesg_control *ctl, const char *buf, size_t size)
+{
+ int lastc = '\n';
+
+ if (!ctl->mmap_buff) {
+ /*
+ * Print whole ring buffer
+ */
+ safe_fwrite(ctl, buf, size, 0, stdout);
+ lastc = buf[size - 1];
+ } else {
+ /*
+ * Print file in small chunks to save memory
+ */
+ while (size) {
+ size_t sz = size > ctl->pagesize ? ctl->pagesize : size;
+ char *x = ctl->mmap_buff;
+
+ safe_fwrite(ctl, x, sz, 0, stdout);
+ lastc = x[sz - 1];
+ size -= sz;
+ ctl->mmap_buff += sz;
+ munmap(x, sz);
+ }
+ }
+
+ if (lastc != '\n')
+ putchar('\n');
+}
+
+static struct tm *record_localtime(struct dmesg_control *ctl,
+ struct dmesg_record *rec,
+ struct tm *tm)
+{
+ time_t t = ctl->boot_time.tv_sec + ctl->suspended_time + rec->tv.tv_sec;
+ return localtime_r(&t, tm);
+}
+
+static char *record_ctime(struct dmesg_control *ctl,
+ struct dmesg_record *rec,
+ char *buf, size_t bufsiz)
+{
+ struct tm tm;
+
+ record_localtime(ctl, rec, &tm);
+
+ if (strftime(buf, bufsiz, "%a %b %e %H:%M:%S %Y", &tm) == 0)
+ *buf = '\0';
+ return buf;
+}
+
+static char *short_ctime(struct tm *tm, char *buf, size_t bufsiz)
+{
+ if (strftime(buf, bufsiz, "%b%e %H:%M", tm) == 0)
+ *buf = '\0';
+ return buf;
+}
+
+static char *iso_8601_time(struct dmesg_control *ctl, struct dmesg_record *rec,
+ char *buf, size_t bufsz)
+{
+ struct timeval tv = {
+ .tv_sec = ctl->boot_time.tv_sec + ctl->suspended_time + rec->tv.tv_sec,
+ .tv_usec = rec->tv.tv_usec
+ };
+
+ if (strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA_T, buf, bufsz) != 0)
+ return NULL;
+
+ return buf;
+}
+
+static double record_count_delta(struct dmesg_control *ctl,
+ struct dmesg_record *rec)
+{
+ double delta = 0;
+
+ if (timerisset(&ctl->lasttime))
+ delta = time_diff(&rec->tv, &ctl->lasttime);
+
+ ctl->lasttime = rec->tv;
+ return delta;
+}
+
+static const char *get_subsys_delimiter(const char *mesg, size_t mesg_size)
+{
+ const char *p = mesg;
+ size_t sz = mesg_size;
+
+ while (sz > 0) {
+ const char *d = strnchr(p, sz, ':');
+ if (!d)
+ return NULL;
+ sz -= d - p + 1;
+ if (sz) {
+ if (isblank(*(d + 1)))
+ return d;
+ p = d + 1;
+ }
+ }
+ return NULL;
+}
+
+static void print_record(struct dmesg_control *ctl,
+ struct dmesg_record *rec)
+{
+ char buf[128];
+ char fpbuf[32] = "\0";
+ char tsbuf[64] = "\0";
+ size_t mesg_size = rec->mesg_size;
+ int timebreak = 0;
+ char *mesg_copy = NULL;
+ const char *line = NULL;
+
+ if (!accept_record(ctl, rec))
+ return;
+
+ if (!rec->mesg_size) {
+ putchar('\n');
+ return;
+ }
+
+ /*
+ * Compose syslog(2) compatible raw output -- used for /dev/kmsg for
+ * backward compatibility with syslog(2) buffers only
+ */
+ if (ctl->raw) {
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf),
+ "<%d>[%5ld.%06ld] ",
+ LOG_MAKEPRI(rec->facility, rec->level),
+ (long) rec->tv.tv_sec,
+ (long) rec->tv.tv_usec);
+ goto full_output;
+ }
+
+ /* Store decode information (facility & priority level) in a buffer */
+ if (ctl->decode &&
+ (rec->level > -1) && (rec->level < (int) ARRAY_SIZE(level_names)) &&
+ (rec->facility > -1) &&
+ (rec->facility < (int) ARRAY_SIZE(facility_names)))
+ snprintf(fpbuf, sizeof(fpbuf), "%-6s:%-6s: ",
+ facility_names[rec->facility].name,
+ level_names[rec->level].name);
+
+ /* Store the timestamp in a buffer */
+ switch (ctl->time_fmt) {
+ double delta;
+ struct tm cur;
+ case DMESG_TIMEFTM_NONE:
+ ctl->indent = 0;
+ break;
+ case DMESG_TIMEFTM_CTIME:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ",
+ record_ctime(ctl, rec, buf, sizeof(buf)));
+ break;
+ case DMESG_TIMEFTM_CTIME_DELTA:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s <%12.06f>] ",
+ record_ctime(ctl, rec, buf, sizeof(buf)),
+ record_count_delta(ctl, rec));
+ break;
+ case DMESG_TIMEFTM_DELTA:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[<%12.06f>] ",
+ record_count_delta(ctl, rec));
+ break;
+ case DMESG_TIMEFTM_RELTIME:
+ record_localtime(ctl, rec, &cur);
+ delta = record_count_delta(ctl, rec);
+ if (cur.tm_min != ctl->lasttm.tm_min ||
+ cur.tm_hour != ctl->lasttm.tm_hour ||
+ cur.tm_yday != ctl->lasttm.tm_yday) {
+ timebreak = 1;
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%s] ",
+ short_ctime(&cur, buf,
+ sizeof(buf)));
+ } else {
+ if (delta < 10)
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf),
+ "[ %+8.06f] ", delta);
+ else
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf),
+ "[ %+9.06f] ", delta);
+ }
+ ctl->lasttm = cur;
+ break;
+ case DMESG_TIMEFTM_TIME:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld] ",
+ (long)rec->tv.tv_sec,
+ (long)rec->tv.tv_usec);
+ break;
+ case DMESG_TIMEFTM_TIME_DELTA:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld <%12.06f>] ",
+ (long)rec->tv.tv_sec,
+ (long)rec->tv.tv_usec,
+ record_count_delta(ctl, rec));
+ break;
+ case DMESG_TIMEFTM_ISO8601:
+ ctl->indent = snprintf(tsbuf, sizeof(tsbuf), "%s ",
+ iso_8601_time(ctl, rec, buf,
+ sizeof(buf)));
+ break;
+ default:
+ abort();
+ }
+
+ ctl->indent += strlen(fpbuf);
+
+full_output:
+ /* Output the decode information */
+ if (*fpbuf)
+ fputs(fpbuf, stdout);
+
+ /* Output the timestamp buffer */
+ if (*tsbuf) {
+ /* Colorize the timestamp */
+ if (ctl->color)
+ dmesg_enable_color(timebreak ? DMESG_COLOR_TIMEBREAK :
+ DMESG_COLOR_TIME);
+ if (ctl->time_fmt != DMESG_TIMEFTM_RELTIME) {
+ fputs(tsbuf, stdout);
+ } else {
+ /*
+ * For relative timestamping, the first line's
+ * timestamp is the offset and all other lines will
+ * report an offset of 0.000000.
+ */
+ if (!line)
+ fputs(tsbuf, stdout);
+ else
+ printf("[ +0.000000] ");
+ }
+ if (ctl->color)
+ color_disable();
+ }
+
+ /*
+ * A kernel message may contain several lines of output, separated
+ * by '\n'. If the timestamp and decode outputs are forced then each
+ * line of the message must be displayed with that information.
+ */
+ if (ctl->force_prefix) {
+ if (!line) {
+ mesg_copy = xstrdup(rec->mesg);
+ line = strtok(mesg_copy, "\n");
+ if (!line)
+ goto done; /* only when something is wrong */
+ mesg_size = strlen(line);
+ }
+ } else {
+ line = rec->mesg;
+ mesg_size = rec->mesg_size;
+ }
+
+ /* Colorize kernel message output */
+ if (ctl->color) {
+ /* Subsystem prefix */
+ const char *subsys = get_subsys_delimiter(line, mesg_size);
+ int has_color = 0;
+
+ if (subsys) {
+ dmesg_enable_color(DMESG_COLOR_SUBSYS);
+ safe_fwrite(ctl, line, subsys - line, ctl->indent, stdout);
+ color_disable();
+
+ mesg_size -= subsys - line;
+ line = subsys;
+ }
+ /* Error, alert .. etc. colors */
+ has_color = set_level_color(rec->level, line, mesg_size) == 0;
+ safe_fwrite(ctl, line, mesg_size, ctl->indent, stdout);
+ if (has_color)
+ color_disable();
+ } else
+ safe_fwrite(ctl, line, mesg_size, ctl->indent, stdout);
+
+ /* Get the next line */
+ if (ctl->force_prefix) {
+ line = strtok(NULL, "\n");
+ if (line && *line) {
+ putchar('\n');
+ mesg_size = strlen(line);
+ goto full_output;
+ }
+ free(mesg_copy);
+ }
+
+done:
+ putchar('\n');
+}
+
+/*
+ * Prints the 'buf' kernel ring buffer; the messages are filtered out according
+ * to 'levels' and 'facilities' bitarrays.
+ */
+static void print_buffer(struct dmesg_control *ctl,
+ const char *buf, size_t size)
+{
+ struct dmesg_record rec = { .next = buf, .next_size = size };
+
+ if (ctl->raw) {
+ raw_print(ctl, buf, size);
+ return;
+ }
+
+ while (get_next_syslog_record(ctl, &rec) == 0)
+ print_record(ctl, &rec);
+}
+
+static ssize_t read_kmsg_one(struct dmesg_control *ctl)
+{
+ ssize_t size;
+
+ /* kmsg returns EPIPE if record was modified while reading */
+ do {
+ size = read(ctl->kmsg, ctl->kmsg_buf,
+ sizeof(ctl->kmsg_buf) - 1);
+ } while (size < 0 && errno == EPIPE);
+
+ return size;
+}
+
+static int init_kmsg(struct dmesg_control *ctl)
+{
+ int mode = O_RDONLY;
+
+ if (!ctl->follow)
+ mode |= O_NONBLOCK;
+ else
+ setlinebuf(stdout);
+
+ ctl->kmsg = open("/dev/kmsg", mode);
+ if (ctl->kmsg < 0)
+ return -1;
+
+ /*
+ * Seek after the last record available at the time
+ * the last SYSLOG_ACTION_CLEAR was issued.
+ *
+ * ... otherwise SYSLOG_ACTION_CLEAR will have no effect for kmsg.
+ */
+ lseek(ctl->kmsg, 0, ctl->end ? SEEK_END : SEEK_DATA);
+
+ /*
+ * Old kernels (<3.5) can successfully open /dev/kmsg for read-only,
+ * but read() returns -EINVAL :-(((
+ *
+ * Let's try to read the first record. The record is later processed in
+ * read_kmsg().
+ */
+ ctl->kmsg_first_read = read_kmsg_one(ctl);
+ if (ctl->kmsg_first_read < 0) {
+ close(ctl->kmsg);
+ ctl->kmsg = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * /dev/kmsg record format:
+ *
+ * faclev,seqnum,timestamp[optional, ...];message\n
+ * TAGNAME=value
+ * ...
+ *
+ * - fields are separated by ','
+ * - last field is terminated by ';'
+ *
+ */
+#define LAST_KMSG_FIELD(s) (!s || !*s || *(s - 1) == ';')
+
+static int parse_kmsg_record(struct dmesg_control *ctl,
+ struct dmesg_record *rec,
+ char *buf,
+ size_t sz)
+{
+ const char *p = buf, *end;
+
+ if (sz == 0 || !buf || !*buf)
+ return -1;
+
+ end = buf + (sz - 1);
+ INIT_DMESG_RECORD(rec);
+
+ while (p < end && isspace(*p))
+ p++;
+
+ /* A) priority and facility */
+ if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode ||
+ ctl->raw || ctl->color)
+ p = parse_faclev(p, &rec->facility, &rec->level);
+ else
+ p = skip_item(p, end, ",");
+ if (LAST_KMSG_FIELD(p))
+ goto mesg;
+
+ /* B) sequence number */
+ p = skip_item(p, end, ",;");
+ if (LAST_KMSG_FIELD(p))
+ goto mesg;
+
+ /* C) timestamp */
+ if (is_timefmt(ctl, NONE))
+ p = skip_item(p, end, ",;");
+ else
+ p = parse_kmsg_timestamp(p, &rec->tv);
+ if (LAST_KMSG_FIELD(p))
+ goto mesg;
+
+ /* D) optional fields (ignore) */
+ p = skip_item(p, end, ";");
+
+mesg:
+ /* E) message text */
+ rec->mesg = p;
+ p = skip_item(p, end, "\n");
+ if (!p)
+ return -1;
+
+ /* The message text is terminated by \n, but it's possible that the
+ * message contains another stuff behind this linebreak; in this case
+ * the previous skip_item() returns pointer to the stuff behind \n.
+ * Let's normalize all these situations and make sure we always point to
+ * the \n.
+ *
+ * Note that the next unhexmangle_to_buffer() will replace \n by \0.
+ */
+ if (*p && *p != '\n')
+ p--;
+
+ /*
+ * Kernel escapes non-printable characters, unfortunately kernel
+ * definition of "non-printable" is too strict. On UTF8 console we can
+ * print many chars, so let's decode from kernel.
+ */
+ rec->mesg_size = unhexmangle_to_buffer(rec->mesg,
+ (char *) rec->mesg, p - rec->mesg + 1);
+
+ rec->mesg_size--; /* don't count \0 */
+
+ /* F) message tags (ignore) */
+
+ return 0;
+}
+
+/*
+ * Note that each read() call for /dev/kmsg returns always one record. It means
+ * that we don't have to read whole message buffer before the records parsing.
+ *
+ * So this function does not compose one huge buffer (like read_syslog_buffer())
+ * and print_buffer() is unnecessary. All is done in this function.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int read_kmsg(struct dmesg_control *ctl)
+{
+ struct dmesg_record rec;
+ ssize_t sz;
+
+ if (ctl->method != DMESG_METHOD_KMSG || ctl->kmsg < 0)
+ return -1;
+
+ /*
+ * The very first read() call is done in kmsg_init() where we test
+ * /dev/kmsg usability. The return code from the initial read() is
+ * stored in ctl->kmsg_first_read;
+ */
+ sz = ctl->kmsg_first_read;
+
+ while (sz > 0) {
+ *(ctl->kmsg_buf + sz) = '\0'; /* for debug messages */
+
+ if (parse_kmsg_record(ctl, &rec,
+ ctl->kmsg_buf, (size_t) sz) == 0)
+ print_record(ctl, &rec);
+
+ sz = read_kmsg_one(ctl);
+ }
+
+ return 0;
+}
+
+static int which_time_format(const char *s)
+{
+ if (!strcmp(s, "notime"))
+ return DMESG_TIMEFTM_NONE;
+ if (!strcmp(s, "ctime"))
+ return DMESG_TIMEFTM_CTIME;
+ if (!strcmp(s, "delta"))
+ return DMESG_TIMEFTM_DELTA;
+ if (!strcmp(s, "reltime"))
+ return DMESG_TIMEFTM_RELTIME;
+ if (!strcmp(s, "iso"))
+ return DMESG_TIMEFTM_ISO8601;
+ errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+#ifdef TEST_DMESG
+static inline int dmesg_get_boot_time(struct timeval *tv)
+{
+ char *str = getenv("DMESG_TEST_BOOTIME");
+ uintmax_t sec, usec;
+
+ if (str && sscanf(str, "%ju.%ju", &sec, &usec) == 2) {
+ tv->tv_sec = sec;
+ tv->tv_usec = usec;
+ return tv->tv_sec >= 0 && tv->tv_usec >= 0 ? 0 : -EINVAL;
+ }
+
+ return get_boot_time(tv);
+}
+
+static inline time_t dmesg_get_suspended_time(void)
+{
+ if (getenv("DMESG_TEST_BOOTIME"))
+ return 0;
+ return get_suspended_time();
+}
+#else
+# define dmesg_get_boot_time get_boot_time
+# define dmesg_get_suspended_time get_suspended_time
+#endif
+
+int main(int argc, char *argv[])
+{
+ char *buf = NULL;
+ int c, nopager = 0;
+ int console_level = 0;
+ int klog_rc = 0;
+ int delta = 0;
+ ssize_t n;
+ static struct dmesg_control ctl = {
+ .filename = NULL,
+ .action = SYSLOG_ACTION_READ_ALL,
+ .method = DMESG_METHOD_KMSG,
+ .kmsg = -1,
+ .time_fmt = DMESG_TIMEFTM_TIME,
+ .indent = 0,
+ };
+ int colormode = UL_COLORMODE_UNDEF;
+ enum {
+ OPT_TIME_FORMAT = CHAR_MAX + 1,
+ OPT_NOESC
+ };
+
+ static const struct option longopts[] = {
+ { "buffer-size", required_argument, NULL, 's' },
+ { "clear", no_argument, NULL, 'C' },
+ { "color", optional_argument, NULL, 'L' },
+ { "console-level", required_argument, NULL, 'n' },
+ { "console-off", no_argument, NULL, 'D' },
+ { "console-on", no_argument, NULL, 'E' },
+ { "decode", no_argument, NULL, 'x' },
+ { "file", required_argument, NULL, 'F' },
+ { "facility", required_argument, NULL, 'f' },
+ { "follow", no_argument, NULL, 'w' },
+ { "follow-new", no_argument, NULL, 'W' },
+ { "human", no_argument, NULL, 'H' },
+ { "help", no_argument, NULL, 'h' },
+ { "kernel", no_argument, NULL, 'k' },
+ { "level", required_argument, NULL, 'l' },
+ { "syslog", no_argument, NULL, 'S' },
+ { "raw", no_argument, NULL, 'r' },
+ { "read-clear", no_argument, NULL, 'c' },
+ { "reltime", no_argument, NULL, 'e' },
+ { "show-delta", no_argument, NULL, 'd' },
+ { "ctime", no_argument, NULL, 'T' },
+ { "noescape", no_argument, NULL, OPT_NOESC },
+ { "notime", no_argument, NULL, 't' },
+ { "nopager", no_argument, NULL, 'P' },
+ { "userspace", no_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'V' },
+ { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
+ { "force-prefix", no_argument, NULL, 'p' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'C','D','E','c','n','r' }, /* clear,off,on,read-clear,level,raw*/
+ { 'H','r' }, /* human, raw */
+ { 'L','r' }, /* color, raw */
+ { 'S','w' }, /* syslog,follow */
+ { 'T','r' }, /* ctime, raw */
+ { 'd','r' }, /* delta, raw */
+ { 'e','r' }, /* reltime, raw */
+ { 'r','x' }, /* raw, decode */
+ { 'r','t' }, /* notime, raw */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "CcDdEeF:f:HhkL::l:n:iPprSs:TtuVWwx",
+ longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'C':
+ ctl.action = SYSLOG_ACTION_CLEAR;
+ break;
+ case 'c':
+ ctl.action = SYSLOG_ACTION_READ_CLEAR;
+ break;
+ case 'D':
+ ctl.action = SYSLOG_ACTION_CONSOLE_OFF;
+ break;
+ case 'd':
+ delta = 1;
+ break;
+ case 'E':
+ ctl.action = SYSLOG_ACTION_CONSOLE_ON;
+ break;
+ case 'e':
+ ctl.time_fmt = DMESG_TIMEFTM_RELTIME;
+ break;
+ case 'F':
+ ctl.filename = optarg;
+ ctl.method = DMESG_METHOD_MMAP;
+ break;
+ case 'f':
+ ctl.fltr_fac = 1;
+ if (string_to_bitarray(optarg,
+ ctl.facilities, parse_facility) < 0)
+ return EXIT_FAILURE;
+ break;
+ case 'H':
+ ctl.time_fmt = DMESG_TIMEFTM_RELTIME;
+ colormode = UL_COLORMODE_AUTO;
+ ctl.pager = 1;
+ break;
+ case 'k':
+ ctl.fltr_fac = 1;
+ setbit(ctl.facilities, FAC_BASE(LOG_KERN));
+ break;
+ case 'L':
+ colormode = UL_COLORMODE_AUTO;
+ if (optarg)
+ colormode = colormode_or_err(optarg,
+ _("unsupported color mode"));
+ break;
+ case 'l':
+ ctl.fltr_lev= 1;
+ if (string_to_bitarray(optarg,
+ ctl.levels, parse_level) < 0)
+ return EXIT_FAILURE;
+ break;
+ case 'n':
+ ctl.action = SYSLOG_ACTION_CONSOLE_LEVEL;
+ console_level = parse_level(optarg, 0);
+ break;
+ case 'P':
+ nopager = 1;
+ break;
+ case 'p':
+ ctl.force_prefix = 1;
+ break;
+ case 'r':
+ ctl.raw = 1;
+ break;
+ case 'S':
+ ctl.method = DMESG_METHOD_SYSLOG;
+ break;
+ case 's':
+ ctl.bufsize = strtou32_or_err(optarg,
+ _("invalid buffer size argument"));
+ if (ctl.bufsize < 4096)
+ ctl.bufsize = 4096;
+ break;
+ case 'T':
+ ctl.time_fmt = DMESG_TIMEFTM_CTIME;
+ break;
+ case 't':
+ ctl.time_fmt = DMESG_TIMEFTM_NONE;
+ break;
+ case 'u':
+ ctl.fltr_fac = 1;
+ for (n = 1; (size_t) n < ARRAY_SIZE(facility_names); n++)
+ setbit(ctl.facilities, n);
+ break;
+ case 'w':
+ ctl.follow = 1;
+ break;
+ case 'W':
+ ctl.follow = 1;
+ ctl.end = 1;
+ break;
+ case 'x':
+ ctl.decode = 1;
+ break;
+ case OPT_TIME_FORMAT:
+ ctl.time_fmt = which_time_format(optarg);
+ break;
+ case OPT_NOESC:
+ ctl.noesc = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argc != optind) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if ((is_timefmt(&ctl, RELTIME) ||
+ is_timefmt(&ctl, CTIME) ||
+ is_timefmt(&ctl, ISO8601))) {
+ if (dmesg_get_boot_time(&ctl.boot_time) != 0)
+ ctl.time_fmt = DMESG_TIMEFTM_NONE;
+ else
+ ctl.suspended_time = dmesg_get_suspended_time();
+ }
+
+ if (delta)
+ switch (ctl.time_fmt) {
+ case DMESG_TIMEFTM_CTIME:
+ ctl.time_fmt = DMESG_TIMEFTM_CTIME_DELTA;
+ break;
+ case DMESG_TIMEFTM_TIME:
+ ctl.time_fmt = DMESG_TIMEFTM_TIME_DELTA;
+ break;
+ case DMESG_TIMEFTM_ISO8601:
+ warnx(_("--show-delta is ignored when used together with iso8601 time format"));
+ break;
+ default:
+ ctl.time_fmt = DMESG_TIMEFTM_DELTA;
+ }
+
+
+ ctl.color = colors_init(colormode, "dmesg") ? 1 : 0;
+ if (ctl.follow)
+ nopager = 1;
+ ctl.pager = nopager ? 0 : ctl.pager;
+ if (ctl.pager)
+ pager_redirect();
+
+ switch (ctl.action) {
+ case SYSLOG_ACTION_READ_ALL:
+ case SYSLOG_ACTION_READ_CLEAR:
+ if (ctl.method == DMESG_METHOD_KMSG && init_kmsg(&ctl) != 0)
+ ctl.method = DMESG_METHOD_SYSLOG;
+
+ if (ctl.raw
+ && ctl.method != DMESG_METHOD_KMSG
+ && (ctl.fltr_lev || ctl.fltr_fac))
+ errx(EXIT_FAILURE, _("--raw can be used together with --level or "
+ "--facility only when reading messages from /dev/kmsg"));
+
+ /* only kmsg supports multi-line messages */
+ if (ctl.force_prefix && ctl.method != DMESG_METHOD_KMSG)
+ ctl.force_prefix = 0;
+
+ if (ctl.pager)
+ pager_redirect();
+ n = read_buffer(&ctl, &buf);
+ if (n > 0)
+ print_buffer(&ctl, buf, n);
+ if (!ctl.mmap_buff)
+ free(buf);
+ if (n < 0)
+ err(EXIT_FAILURE, _("read kernel buffer failed"));
+ if (ctl.kmsg >= 0)
+ close(ctl.kmsg);
+ break;
+ case SYSLOG_ACTION_CLEAR:
+ case SYSLOG_ACTION_CONSOLE_OFF:
+ case SYSLOG_ACTION_CONSOLE_ON:
+ klog_rc = klogctl(ctl.action, NULL, 0);
+ break;
+ case SYSLOG_ACTION_CONSOLE_LEVEL:
+ klog_rc = klogctl(ctl.action, NULL, console_level);
+ break;
+ default:
+ errx(EXIT_FAILURE, _("unsupported command"));
+ break;
+ }
+
+
+ if (klog_rc)
+ err(EXIT_FAILURE, _("klogctl failed"));
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/eject.1 b/sys-utils/eject.1
new file mode 100644
index 0000000..313f9fd
--- /dev/null
+++ b/sys-utils/eject.1
@@ -0,0 +1,196 @@
+.\" Copyright (C) 1994-2005 Jeff Tranter (tranter@pobox.com)
+.\" Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+.\"
+.\" It may be distributed under the GNU Public License, version 2, or
+.\" any higher version. See section COPYING of the GNU Public license
+.\" for conditions under which this file may be redistributed.
+.TH EJECT 1 "April 2012" "Linux" "User Commands"
+.SH NAME
+eject \- eject removable media
+.SH SYNOPSIS
+.B eject
+[options]
+.IR device | mountpoint
+.SH DESCRIPTION
+.B eject
+allows removable media (typically a CD-ROM, floppy disk, tape, JAZ, ZIP or USB
+disk) to be ejected under software control. The command can also control some
+multi-disc CD-ROM changers, the auto-eject feature supported by some devices,
+and close the disc tray of some CD-ROM drives.
+.PP
+The device corresponding to \fIdevice\fP or \fImountpoint\fP is ejected. If no
+name is specified, the default name \fB/dev/cdrom\fR is used. The device may be
+addressed by device name (e.g., 'sda'), device path (e.g., '/dev/sda'),
+UUID=\fIuuid\fR or LABEL=\fIlabel\fR tags.
+.PP
+There are four different methods of ejecting, depending on whether the device
+is a CD-ROM, SCSI device, removable floppy, or tape. By default \fBeject\fR tries
+all four methods in order until it succeeds.
+.PP
+If a device partition is specified, the whole-disk device is used.
+.PP
+If the device or a device partition is currently mounted, it is unmounted
+before ejecting. The eject is processed on exclusive open block device
+file descriptor if \fB\-\-no\-unmount\fP or \fB\-\-force\fP are not specified.
+
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-auto on" | off
+This option controls the auto-eject mode, supported by some devices. When
+enabled, the drive automatically ejects when the device is closed.
+.TP
+.BR \-c , " \-\-changerslot " \fIslot
+With this option a CD slot can be selected from an ATAPI/IDE CD-ROM changer.
+The CD-ROM drive cannot be in use (mounted data CD or playing a music CD) for
+a change request to work. Please also note that the first slot of the changer
+is referred to as 0, not 1.
+.TP
+.BR \-d , " \-\-default"
+List the default device name.
+.TP
+.BR \-F , " \-\-force"
+Force eject, don't check device type, don't open device with exclusive lock.
+The successful result may be false positive on non hot-pluggable devices.
+.TP
+.BR \-f , " \-\-floppy"
+This option specifies that the drive should be ejected using a removable floppy
+disk eject command.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-i , " \-\-manualeject on" | off
+This option controls locking of the hardware eject button. When enabled, the
+drive will not be ejected when the button is pressed. This is useful when you
+are carrying a laptop in a bag or case and don't want it to eject if the button
+is inadvertently pressed.
+.TP
+.BR \-M , " \-\-no\-partitions\-unmount"
+The option tells eject to not try to unmount other partitions on partitioned
+devices. If another partition is still mounted, the program will not attempt
+to eject the media. It will attempt to unmount only the device or mountpoint
+given on the command line.
+.TP
+.BR \-m , " \-\-no\-unmount"
+The option tells eject to not try to unmount at all. If this option is not
+specified than
+.B eject
+opens the device with O_EXCL flag to be sure that the device is not used (since
+v2.35).
+.TP
+.BR \-n , " \-\-noop"
+With this option the selected device is displayed but no action is performed.
+.TP
+.BR \-p , " \-\-proc"
+This option allows you to use /proc/mounts instead /etc/mtab. It also passes the
+\fB\-n\fR option to \fBumount\fR(8).
+.TP
+.BR \-q , " \-\-tape"
+This option specifies that the drive should be ejected using a tape drive
+offline command.
+.TP
+.BR \-r , " \-\-cdrom"
+This option specifies that the drive should be ejected using a CDROM eject
+command.
+.TP
+.BR \-s , " \-\-scsi"
+This option specifies that the drive should be ejected using SCSI commands.
+.TP
+.BR \-T , " \-\-traytoggle"
+With this option the drive is given a CD-ROM tray close command if it's opened,
+and a CD-ROM tray eject command if it's closed. Not all devices support this
+command, because it uses the above CD-ROM tray close command.
+.TP
+.BR \-t , " \-\-trayclose"
+With this option the drive is given a CD-ROM tray close command. Not all
+devices support this command.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-v , " \-\-verbose"
+Run in verbose mode; more information is displayed about what the command is
+doing.
+.TP
+.BR \-X , " \-\-listspeed"
+With this option the CD-ROM drive will be probed to detect the available
+speeds. The output is a list of speeds which can be used as an argument of the
+\fB\-x\fR option. This only works with Linux 2.6.13 or higher, on previous versions
+solely the maximum speed will be reported. Also note that some drives may not
+correctly report the speed and therefore this option does not work with them.
+.TP
+.BR \-x , " \-\-cdspeed " \fIspeed
+With this option the drive is given a CD-ROM select speed command. The
+.I speed
+argument is a number indicating the desired speed (e.g., 8 for 8X speed), or 0
+for maximum data rate. Not all devices support this command and you can only
+specify speeds that the drive is capable of. Every time the media is changed
+this option is cleared. This option can be used alone, or with the
+\fB\-t\fR and \fB\-c\fR options.
+.SH EXIT STATUS
+Returns 0 if operation was successful, 1 if operation failed or command syntax
+was not valid.
+.SH NOTES
+.B eject
+only works with devices that support one or more of the four methods of
+ejecting. This includes most CD-ROM drives (IDE, SCSI, and proprietary), some
+SCSI tape drives, JAZ drives, ZIP drives (parallel port, SCSI, and IDE
+versions), and LS120 removable floppies. Users have also reported success with
+floppy drives on Sun SPARC and Apple Macintosh systems. If
+.B eject
+does not work, it is most likely a limitation of the kernel driver for the
+device and not the
+.B eject
+program itself.
+.PP
+The \fB\-r\fR, \fB\-s\fR, \fB\-f\fR, and \fB\-q\fR options allow controlling
+which methods are used to
+eject. More than one method can be specified. If none of these options are
+specified, it tries all four (this works fine in most cases).
+.PP
+.B eject
+may not always be able to determine if the device is mounted (e.g., if it has
+several names). If the device name is a symbolic link,
+.B eject
+will follow the link and use the device that it points to.
+.PP
+If
+.B eject
+determines that the device can have multiple partitions, it will attempt to
+unmount all mounted partitions of the device before ejecting (see also
+\fB--no-partitions-unmount\fR). If an unmount fails, the program will not
+attempt to eject the media.
+.PP
+You can eject an audio CD. Some CD-ROM drives will refuse to open the tray if
+the drive is empty. Some devices do not support the tray close command.
+.PP
+If the auto-eject feature is enabled, then the drive will always be ejected
+after running this command. Not all Linux kernel CD-ROM drivers support the
+auto-eject mode. There is no way to find out the state of the auto-eject mode.
+.PP
+You need appropriate privileges to access the device files. Running as root is
+required to eject some devices (e.g., SCSI devices).
+.SH AUTHORS
+.MT tranter@\:pobox.com
+Jeff Tranter
+.ME
+- original author.
+.br
+.MT kzak@\:redhat.com
+Karel Zak
+.ME
+and
+.MT mluscon@\:redhat.com
+Michal Luscon
+.ME
+- util-linux version.
+.SH SEE ALSO
+.BR findmnt (8),
+.BR lsblk (8),
+.BR mount (8),
+.BR umount (8)
+.SH AVAILABILITY
+The eject command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/eject.c b/sys-utils/eject.c
new file mode 100644
index 0000000..e0f540e
--- /dev/null
+++ b/sys-utils/eject.c
@@ -0,0 +1,1055 @@
+/*
+ * Copyright (C) 1994-2005 Jeff Tranter (tranter@pobox.com)
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ * Copyright (C) Michal Luscon <mluscon@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <err.h>
+#include <stdarg.h>
+
+#include <getopt.h>
+#include <errno.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/mtio.h>
+#include <linux/cdrom.h>
+#include <linux/fd.h>
+#include <sys/mount.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_ioctl.h>
+#include <sys/time.h>
+
+#include <libmount.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "pathnames.h"
+#include "sysfs.h"
+#include "monotonic.h"
+
+/*
+ * sg_io_hdr_t driver_status -- see kernel include/scsi/scsi.h
+ */
+#ifndef DRIVER_SENSE
+# define DRIVER_SENSE 0x08
+#endif
+
+
+#define EJECT_DEFAULT_DEVICE "/dev/cdrom"
+
+
+/* Used by the toggle_tray() function. If ejecting the tray takes this
+ * time or less, the tray was probably already ejected, so we close it
+ * again.
+ */
+#define TRAY_WAS_ALREADY_OPEN_USECS 200000 /* about 0.2 seconds */
+
+struct eject_control {
+ struct libmnt_table *mtab;
+ char *device; /* device or mount point to be ejected */
+ int fd; /* file descriptor for device */
+ unsigned int /* command flags and arguments */
+ a_option:1,
+ c_option:1,
+ d_option:1,
+ F_option:1,
+ f_option:1,
+ i_option:1,
+ M_option:1,
+ m_option:1,
+ n_option:1,
+ p_option:1,
+ q_option:1,
+ r_option:1,
+ s_option:1,
+ T_option:1,
+ t_option:1,
+ v_option:1,
+ X_option:1,
+ x_option:1,
+ a_arg:1,
+ i_arg:1;
+
+ unsigned int force_exclusive; /* use O_EXCL */
+
+ long int c_arg; /* changer slot number */
+ long int x_arg; /* cd speed */
+};
+
+static void vinfo(const char *fmt, va_list va)
+{
+ fprintf(stdout, "%s: ", program_invocation_short_name);
+ vprintf(fmt, va);
+ fputc('\n', stdout);
+}
+
+static inline void verbose(const struct eject_control *ctl, const char *fmt, ...)
+{
+ va_list va;
+
+ if (!ctl->v_option)
+ return;
+
+ va_start(va, fmt);
+ vinfo(fmt, va);
+ va_end(va);
+}
+
+static inline void info(const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ vinfo(fmt, va);
+ va_end(va);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] [<device>|<mountpoint>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Eject removable media.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --auto <on|off> turn auto-eject feature on or off\n"
+ " -c, --changerslot <slot> switch discs on a CD-ROM changer\n"
+ " -d, --default display default device\n"
+ " -f, --floppy eject floppy\n"
+ " -F, --force don't care about device type\n"
+ " -i, --manualeject <on|off> toggle manual eject protection on/off\n"
+ " -m, --no-unmount do not unmount device even if it is mounted\n"
+ " -M, --no-partitions-unmount do not unmount another partitions\n"
+ " -n, --noop don't eject, just show device found\n"
+ " -p, --proc use /proc/mounts instead of /etc/mtab\n"
+ " -q, --tape eject tape\n"
+ " -r, --cdrom eject CD-ROM\n"
+ " -s, --scsi eject SCSI device\n"
+ " -t, --trayclose close tray\n"
+ " -T, --traytoggle toggle tray\n"
+ " -v, --verbose enable verbose output\n"
+ " -x, --cdspeed <speed> set CD-ROM max speed\n"
+ " -X, --listspeed list CD-ROM available speeds\n"),
+ out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(29));
+
+ fputs(_("\nBy default tries -r, -s, -f, and -q in order until success.\n"), out);
+ printf(USAGE_MAN_TAIL("eject(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+
+/* Handle command line options. */
+static void parse_args(struct eject_control *ctl, int argc, char **argv)
+{
+ static const struct option long_opts[] =
+ {
+ {"auto", required_argument, NULL, 'a'},
+ {"cdrom", no_argument, NULL, 'r'},
+ {"cdspeed", required_argument, NULL, 'x'},
+ {"changerslot", required_argument, NULL, 'c'},
+ {"default", no_argument, NULL, 'd'},
+ {"floppy", no_argument, NULL, 'f'},
+ {"force", no_argument, NULL, 'F'},
+ {"help", no_argument, NULL, 'h'},
+ {"listspeed", no_argument, NULL, 'X'},
+ {"manualeject", required_argument, NULL, 'i'},
+ {"noop", no_argument, NULL, 'n'},
+ {"no-unmount", no_argument, NULL, 'm'},
+ {"no-partitions-unmount", no_argument, NULL, 'M' },
+ {"proc", no_argument, NULL, 'p'},
+ {"scsi", no_argument, NULL, 's'},
+ {"tape", no_argument, NULL, 'q'},
+ {"trayclose", no_argument, NULL, 't'},
+ {"traytoggle", no_argument, NULL, 'T'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ int c;
+
+ while ((c = getopt_long(argc, argv,
+ "a:c:i:x:dfFhnqrstTXvVpmM", long_opts, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ ctl->a_option = 1;
+ ctl->a_arg = parse_switch(optarg, _("argument error"),
+ "on", "off", "1", "0", NULL);
+ break;
+ case 'c':
+ ctl->c_option = 1;
+ ctl->c_arg = strtoul_or_err(optarg, _("invalid argument to --changerslot/-c option"));
+ break;
+ case 'x':
+ ctl->x_option = 1;
+ ctl->x_arg = strtoul_or_err(optarg, _("invalid argument to --cdspeed/-x option"));
+ break;
+ case 'd':
+ ctl->d_option = 1;
+ break;
+ case 'f':
+ ctl->f_option = 1;
+ break;
+ case 'F':
+ ctl->F_option = 1;
+ break;
+ case 'i':
+ ctl->i_option = 1;
+ ctl->i_arg = parse_switch(optarg, _("argument error"),
+ "on", "off", "1", "0", NULL);
+ break;
+ case 'm':
+ ctl->m_option = 1;
+ break;
+ case 'M':
+ ctl->M_option = 1;
+ break;
+ case 'n':
+ ctl->n_option = 1;
+ break;
+ case 'p':
+ ctl->p_option = 1;
+ break;
+ case 'q':
+ ctl->q_option = 1;
+ break;
+ case 'r':
+ ctl->r_option = 1;
+ break;
+ case 's':
+ ctl->s_option = 1;
+ break;
+ case 't':
+ ctl->t_option = 1;
+ break;
+ case 'T':
+ ctl->T_option = 1;
+ break;
+ case 'X':
+ ctl->X_option = 1;
+ break;
+ case 'v':
+ ctl->v_option = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ /* check for a single additional argument */
+ if ((argc - optind) > 1)
+ errx(EXIT_FAILURE, _("too many arguments"));
+
+ if ((argc - optind) == 1)
+ ctl->device = xstrdup(argv[optind]);
+}
+
+/*
+ * Given name, such as foo, see if any of the following exist:
+ *
+ * foo (if foo starts with '.' or '/')
+ * /dev/foo
+ *
+ * If found, return the full path. If not found, return 0.
+ * Returns pointer to dynamically allocated string.
+ */
+static char *find_device(const char *name)
+{
+ if (!name)
+ return NULL;
+
+ if ((*name == '.' || *name == '/') && access(name, F_OK) == 0)
+ return xstrdup(name);
+
+ char buf[PATH_MAX];
+
+ snprintf(buf, sizeof(buf), "/dev/%s", name);
+ if (access(buf, F_OK) == 0)
+ return xstrdup(buf);
+
+ return NULL;
+}
+
+/* Set or clear auto-eject mode. */
+static void auto_eject(const struct eject_control *ctl)
+{
+ int status = -1;
+
+#if defined(CDROM_SET_OPTIONS) && defined(CDROM_CLEAR_OPTIONS)
+ if (ctl->a_arg)
+ status = ioctl(ctl->fd, CDROM_SET_OPTIONS, CDO_AUTO_EJECT);
+ else
+ status = ioctl(ctl->fd, CDROM_CLEAR_OPTIONS, CDO_AUTO_EJECT);
+#else
+ errno = ENOSYS;
+#endif
+ if (status < 0)
+ err(EXIT_FAILURE,_("CD-ROM auto-eject command failed"));
+}
+
+/*
+ * Stops CDROM from opening on manual eject button press.
+ * This can be useful when you carry your laptop
+ * in your bag while it's on and no CD inserted in it's drive.
+ * Implemented as found in Documentation/userspace-api/ioctl/cdrom.rst
+ */
+static void manual_eject(const struct eject_control *ctl)
+{
+ if (ioctl(ctl->fd, CDROM_LOCKDOOR, ctl->i_arg) < 0) {
+ switch (errno) {
+ case EDRIVE_CANT_DO_THIS:
+ errx(EXIT_FAILURE, _("CD-ROM door lock is not supported"));
+ case EBUSY:
+ errx(EXIT_FAILURE, _("other users have the drive open and not CAP_SYS_ADMIN"));
+ default:
+ err(EXIT_FAILURE, _("CD-ROM lock door command failed"));
+ }
+ }
+
+ if (ctl->i_arg)
+ info(_("CD-Drive may NOT be ejected with device button"));
+ else
+ info(_("CD-Drive may be ejected with device button"));
+}
+
+/*
+ * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
+ * CDROMLOADFROMSLOT.
+ */
+static void changer_select(const struct eject_control *ctl)
+{
+#ifdef CDROM_SELECT_DISC
+ if (ioctl(ctl->fd, CDROM_SELECT_DISC, ctl->c_arg) < 0)
+ err(EXIT_FAILURE, _("CD-ROM select disc command failed"));
+
+#elif defined CDROMLOADFROMSLOT
+ if (ioctl(ctl->fd, CDROMLOADFROMSLOT, ctl->c_arg) != 0)
+ err(EXIT_FAILURE, _("CD-ROM load from slot command failed"));
+#else
+ warnx(_("IDE/ATAPI CD-ROM changer not supported by this kernel\n") );
+#endif
+}
+
+/*
+ * Close tray. Not supported by older kernels.
+ */
+static void close_tray(int fd)
+{
+ int status;
+
+#if defined(CDROMCLOSETRAY) || defined(CDIOCCLOSE)
+#if defined(CDROMCLOSETRAY)
+ status = ioctl(fd, CDROMCLOSETRAY);
+#elif defined(CDIOCCLOSE)
+ status = ioctl(fd, CDIOCCLOSE);
+#endif
+ if (status != 0)
+ err(EXIT_FAILURE, _("CD-ROM tray close command failed"));
+#else
+ warnx(_("CD-ROM tray close command not supported by this kernel\n"));
+#endif
+}
+
+/*
+ * Eject using CDROMEJECT ioctl.
+ */
+static int eject_cdrom(int fd)
+{
+#if defined(CDROMEJECT)
+ int ret = ioctl(fd, CDROM_LOCKDOOR, 0);
+ if (ret < 0)
+ return 0;
+ return ioctl(fd, CDROMEJECT) >= 0;
+#elif defined(CDIOCEJECT)
+ return ioctl(fd, CDIOCEJECT) >= 0;
+#else
+ warnx(_("CD-ROM eject unsupported"));
+ errno = ENOSYS;
+ return 0;
+#endif
+}
+
+/*
+ * Toggle tray.
+ *
+ * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and
+ * Sybren Stuvel <sybren@thirdtower.com>
+ *
+ * Not supported by older kernels because it might use
+ * CloseTray().
+ *
+ */
+static void toggle_tray(int fd)
+{
+#ifdef CDROM_DRIVE_STATUS
+ /* First ask the CDROM for info, otherwise fall back to manual. */
+ switch (ioctl(fd, CDROM_DRIVE_STATUS)) {
+ case CDS_TRAY_OPEN:
+ close_tray(fd);
+ return;
+
+ case CDS_NO_DISC:
+ case CDS_DISC_OK:
+ if (!eject_cdrom(fd))
+ err(EXIT_FAILURE, _("CD-ROM eject command failed"));
+ return;
+ case CDS_NO_INFO:
+ warnx(_("no CD-ROM information available"));
+ return;
+ case CDS_DRIVE_NOT_READY:
+ warnx(_("CD-ROM drive is not ready"));
+ return;
+ default:
+ err(EXIT_FAILURE, _("CD-ROM status command failed"));
+ }
+#else
+ struct timeval time_start, time_stop;
+ int time_elapsed;
+
+ /* Try to open the CDROM tray and measure the time therefore
+ * needed. In my experience the function needs less than 0.05
+ * seconds if the tray was already open, and at least 1.5 seconds
+ * if it was closed. */
+ gettime_monotonic(&time_start);
+
+ /* Send the CDROMEJECT command to the device. */
+ if (!eject_cdrom(fd))
+ err(EXIT_FAILURE, _("CD-ROM eject command failed"));
+
+ /* Get the second timestamp, to measure the time needed to open
+ * the tray. */
+ gettime_monotonic(&time_stop);
+
+ time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) -
+ (time_start.tv_sec * 1000000 + time_start.tv_usec);
+
+ /* If the tray "opened" too fast, we can be nearly sure, that it
+ * was already open. In this case, close it now. Else the tray was
+ * closed before. This would mean that we are done. */
+ if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS)
+ close_tray(fd);
+#endif
+}
+
+/*
+ * Select Speed of CD-ROM drive.
+ * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
+ * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
+ */
+static void select_speed(const struct eject_control *ctl)
+{
+#ifdef CDROM_SELECT_SPEED
+ if (ioctl(ctl->fd, CDROM_SELECT_SPEED, ctl->x_arg) != 0)
+ err(EXIT_FAILURE, _("CD-ROM select speed command failed"));
+#else
+ warnx(_("CD-ROM select speed command not supported by this kernel"));
+#endif
+}
+
+/*
+ * Read Speed of CD-ROM drive. From Linux 2.6.13, the current speed
+ * is correctly reported
+ */
+static int read_speed(const char *devname)
+{
+ int drive_number = -1;
+ char *name;
+ FILE *f;
+
+ f = fopen(_PATH_PROC_CDROMINFO, "r");
+ if (!f)
+ err(EXIT_FAILURE, _("cannot open %s"), _PATH_PROC_CDROMINFO);
+
+ name = strrchr(devname, '/') + 1;
+
+ while (name && !feof(f)) {
+ char line[512];
+ char *str;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ /* find drive number in line "drive name" */
+ if (drive_number == -1) {
+ if (strncmp(line, "drive name:", 11) == 0) {
+ str = strtok(&line[11], "\t ");
+ drive_number = 0;
+ while (str && strncmp(name, str, strlen(name)) != 0) {
+ drive_number++;
+ str = strtok(NULL, "\t ");
+ if (!str)
+ errx(EXIT_FAILURE,
+ _("%s: failed to finding CD-ROM name"),
+ _PATH_PROC_CDROMINFO);
+ }
+ }
+ /* find line "drive speed" and read the correct speed */
+ } else {
+ if (strncmp(line, "drive speed:", 12) == 0) {
+ int i;
+
+ str = strtok(&line[12], "\t ");
+ for (i = 1; i < drive_number; i++)
+ str = strtok(NULL, "\t ");
+
+ if (!str)
+ errx(EXIT_FAILURE,
+ _("%s: failed to read speed"),
+ _PATH_PROC_CDROMINFO);
+ fclose(f);
+ return atoi(str);
+ }
+ }
+ }
+
+ errx(EXIT_FAILURE, _("failed to read speed"));
+}
+
+/*
+ * List Speed of CD-ROM drive.
+ */
+static void list_speeds(struct eject_control *ctl)
+{
+ int max_speed, curr_speed = 0;
+
+ select_speed(ctl);
+ max_speed = read_speed(ctl->device);
+
+ while (curr_speed < max_speed) {
+ ctl->x_arg = curr_speed + 1;
+ select_speed(ctl);
+ curr_speed = read_speed(ctl->device);
+ if (ctl->x_arg < curr_speed)
+ printf("%d ", curr_speed);
+ else
+ curr_speed = ctl->x_arg + 1;
+ }
+
+ printf("\n");
+}
+
+/*
+ * Eject using SCSI SG_IO commands. Return 1 if successful, 0 otherwise.
+ */
+static int eject_scsi(const struct eject_control *ctl)
+{
+ int status, k;
+ sg_io_hdr_t io_hdr;
+ unsigned char allowRmBlk[6] = {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0};
+ unsigned char startStop1Blk[6] = {START_STOP, 0, 0, 0, 1, 0};
+ unsigned char startStop2Blk[6] = {START_STOP, 0, 0, 0, 2, 0};
+ unsigned char inqBuff[2];
+ unsigned char sense_buffer[32];
+
+ if ((ioctl(ctl->fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ verbose(ctl, _("not an sg device, or old sg driver"));
+ return 0;
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = 6;
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.dxfer_len = 0;
+ io_hdr.dxferp = inqBuff;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 10000;
+
+ io_hdr.cmdp = allowRmBlk;
+ status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr);
+ if (status < 0 || io_hdr.host_status || io_hdr.driver_status)
+ return 0;
+
+ io_hdr.cmdp = startStop1Blk;
+ status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr);
+ if (status < 0 || io_hdr.host_status)
+ return 0;
+
+ /* Ignore errors when there is not medium -- in this case driver sense
+ * buffer sets MEDIUM NOT PRESENT (3a) bit. For more details see:
+ * http://www.tldp.org/HOWTO/archived/SCSI-Programming-HOWTO/SCSI-Programming-HOWTO-22.html#sec-sensecodes
+ * -- kzak Jun 2013
+ */
+ if (io_hdr.driver_status != 0 &&
+ !(io_hdr.driver_status == DRIVER_SENSE && io_hdr.sbp &&
+ io_hdr.sbp[12] == 0x3a))
+ return 0;
+
+ io_hdr.cmdp = startStop2Blk;
+ status = ioctl(ctl->fd, SG_IO, (void *)&io_hdr);
+ if (status < 0 || io_hdr.host_status || io_hdr.driver_status)
+ return 0;
+
+ /* force kernel to reread partition table when new disc inserted */
+ ioctl(ctl->fd, BLKRRPART);
+ return 1;
+}
+
+/*
+ * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
+ */
+static int eject_floppy(int fd)
+{
+ return ioctl(fd, FDEJECT) >= 0;
+}
+
+
+/*
+ * Rewind and eject using tape ioctl. Return 1 if successful, 0 otherwise.
+ */
+static int eject_tape(int fd)
+{
+ struct mtop op = { .mt_op = MTOFFL, .mt_count = 0 };
+
+ return ioctl(fd, MTIOCTOP, &op) >= 0;
+}
+
+
+/* umount a device. */
+static void umount_one(const struct eject_control *ctl, const char *name)
+{
+ int status;
+
+ if (!name)
+ return;
+
+ verbose(ctl, _("%s: unmounting"), name);
+
+ switch (fork()) {
+ case 0: /* child */
+ if (setgid(getgid()) < 0)
+ err(EXIT_FAILURE, _("cannot set group id"));
+
+ if (setuid(getuid()) < 0)
+ err(EXIT_FAILURE, _("cannot set user id"));
+
+ if (ctl->p_option)
+ execl("/bin/umount", "/bin/umount", name, "-n", (char *)NULL);
+ else
+ execl("/bin/umount", "/bin/umount", name, (char *)NULL);
+
+ errexec("/bin/umount");
+
+ case -1:
+ warn( _("unable to fork"));
+ break;
+
+ default: /* parent */
+ wait(&status);
+ if (WIFEXITED(status) == 0)
+ errx(EXIT_FAILURE,
+ _("unmount of `%s' did not exit normally"), name);
+
+ if (WEXITSTATUS(status) != 0)
+ errx(EXIT_FAILURE, _("unmount of `%s' failed\n"), name);
+ break;
+ }
+}
+
+/* Open a device file. */
+static void open_device(struct eject_control *ctl)
+{
+ int extra = ctl->F_option == 0 && /* never use O_EXCL on --force */
+ ctl->force_exclusive ? O_EXCL : 0;
+
+ ctl->fd = open(ctl->device, O_RDWR | O_NONBLOCK | extra);
+ if (ctl->fd < 0)
+ ctl->fd = open(ctl->device, O_RDONLY | O_NONBLOCK | extra);
+ if (ctl->fd == -1)
+ err(EXIT_FAILURE, _("cannot open %s"), ctl->device);
+}
+
+/*
+ * See if device has been mounted by looking in mount table. If so, set
+ * device name and mount point name, and return 1, otherwise return 0.
+ */
+static int device_get_mountpoint(struct eject_control *ctl, char **devname, char **mnt)
+{
+ struct libmnt_fs *fs;
+ int rc;
+
+ *mnt = NULL;
+
+ if (!ctl->mtab) {
+ struct libmnt_cache *cache;
+
+ ctl->mtab = mnt_new_table();
+ if (!ctl->mtab)
+ err(EXIT_FAILURE, _("failed to initialize libmount table"));
+
+ cache = mnt_new_cache();
+ mnt_table_set_cache(ctl->mtab, cache);
+ mnt_unref_cache(cache);
+
+ if (ctl->p_option)
+ rc = mnt_table_parse_file(ctl->mtab, _PATH_PROC_MOUNTINFO);
+ else
+ rc = mnt_table_parse_mtab(ctl->mtab, NULL);
+ if (rc)
+ err(EXIT_FAILURE, _("failed to parse mount table"));
+ }
+
+ fs = mnt_table_find_source(ctl->mtab, *devname, MNT_ITER_BACKWARD);
+ if (!fs) {
+ /* maybe 'devname' is mountpoint rather than a real device */
+ fs = mnt_table_find_target(ctl->mtab, *devname, MNT_ITER_BACKWARD);
+ if (fs) {
+ free(*devname);
+ *devname = xstrdup(mnt_fs_get_source(fs));
+ }
+ }
+
+ if (fs)
+ *mnt = xstrdup(mnt_fs_get_target(fs));
+ return *mnt ? 0 : -1;
+}
+
+static char *get_disk_devname(const char *device)
+{
+ struct stat st;
+ dev_t diskno = 0;
+ char diskname[128];
+
+ if (stat(device, &st) != 0)
+ return NULL;
+
+ /* get whole-disk devno */
+ if (sysfs_devno_to_wholedisk(st.st_rdev, diskname,
+ sizeof(diskname), &diskno) != 0)
+ return NULL;
+
+ return st.st_rdev == diskno ? NULL : find_device(diskname);
+}
+
+/* umount all partitions if -M not specified, otherwise returns
+ * number of the mounted partitions only.
+ */
+static int umount_partitions(struct eject_control *ctl)
+{
+ struct path_cxt *pc = NULL;
+ dev_t devno;
+ DIR *dir = NULL;
+ struct dirent *d;
+ int count = 0;
+
+ devno = sysfs_devname_to_devno(ctl->device);
+ if (devno)
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ return 0;
+
+ /* open /sys/block/<wholedisk> */
+ if (!(dir = ul_path_opendir(pc, NULL)))
+ goto done;
+
+ /* scan for partition subdirs */
+ while ((d = readdir(dir))) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ if (sysfs_blkdev_is_partition_dirent(dir, d, ctl->device)) {
+ char *mnt = NULL;
+ char *dev = find_device(d->d_name);
+
+ if (dev && device_get_mountpoint(ctl, &dev, &mnt) == 0) {
+ verbose(ctl, _("%s: mounted on %s"), dev, mnt);
+ if (!ctl->M_option)
+ umount_one(ctl, mnt);
+ count++;
+ }
+ free(dev);
+ free(mnt);
+ }
+ }
+
+done:
+ if (dir)
+ closedir(dir);
+ ul_unref_path(pc);
+
+ return count;
+}
+
+static int is_hotpluggable(const struct eject_control *ctl)
+{
+ struct path_cxt *pc = NULL;
+ dev_t devno;
+ int rc = 0;
+
+ devno = sysfs_devname_to_devno(ctl->device);
+ if (devno)
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ return 0;
+
+ rc = sysfs_blkdev_is_hotpluggable(pc);
+ ul_unref_path(pc);
+ return rc;
+}
+
+
+/* handle -x option */
+static void set_device_speed(struct eject_control *ctl)
+{
+ if (!ctl->x_option)
+ return;
+
+ if (ctl->x_arg == 0)
+ verbose(ctl, _("setting CD-ROM speed to auto"));
+ else
+ verbose(ctl, _("setting CD-ROM speed to %ldX"), ctl->x_arg);
+
+ open_device(ctl);
+ select_speed(ctl);
+ exit(EXIT_SUCCESS);
+}
+
+
+/* main program */
+int main(int argc, char **argv)
+{
+ char *disk = NULL;
+ char *mountpoint = NULL;
+ int worked = 0; /* set to 1 when successfully ejected */
+ struct eject_control ctl = { NULL };
+
+ setlocale(LC_ALL,"");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ /* parse the command line arguments */
+ parse_args(&ctl, argc, argv);
+
+ /* handle -d option */
+ if (ctl.d_option) {
+ info(_("default device: `%s'"), EJECT_DEFAULT_DEVICE);
+ return EXIT_SUCCESS;
+ }
+
+ if (!ctl.device) {
+ ctl.device = mnt_resolve_path(EJECT_DEFAULT_DEVICE, NULL);
+ verbose(&ctl, _("using default device `%s'"), ctl.device);
+ } else {
+ char *p;
+
+ if (ctl.device[strlen(ctl.device) - 1] == '/')
+ ctl.device[strlen(ctl.device) - 1] = '\0';
+
+ /* figure out full device or mount point name */
+ p = find_device(ctl.device);
+ if (p)
+ free(ctl.device);
+ else
+ p = ctl.device;
+
+ ctl.device = mnt_resolve_spec(p, NULL);
+ free(p);
+ }
+
+ if (!ctl.device)
+ errx(EXIT_FAILURE, _("unable to find device"));
+
+ verbose(&ctl, _("device name is `%s'"), ctl.device);
+
+ device_get_mountpoint(&ctl, &ctl.device, &mountpoint);
+ if (mountpoint)
+ verbose(&ctl, _("%s: mounted on %s"), ctl.device, mountpoint);
+ else
+ verbose(&ctl, _("%s: not mounted"), ctl.device);
+
+ disk = get_disk_devname(ctl.device);
+ if (disk) {
+ verbose(&ctl, _("%s: disc device: %s (disk device will be used for eject)"), ctl.device, disk);
+ free(ctl.device);
+ ctl.device = disk;
+ disk = NULL;
+ } else {
+ struct stat st;
+
+ if (stat(ctl.device, &st) != 0 || !S_ISBLK(st.st_mode))
+ errx(EXIT_FAILURE, _("%s: not found mountpoint or device "
+ "with the given name"), ctl.device);
+
+ verbose(&ctl, _("%s: is whole-disk device"), ctl.device);
+ }
+
+ if (ctl.F_option == 0 && is_hotpluggable(&ctl) == 0)
+ errx(EXIT_FAILURE, _("%s: is not hot-pluggable device"), ctl.device);
+
+ /* handle -n option */
+ if (ctl.n_option) {
+ info(_("device is `%s'"), ctl.device);
+ verbose(&ctl, _("exiting due to -n/--noop option"));
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -i option */
+ if (ctl.i_option) {
+ open_device(&ctl);
+ manual_eject(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -a option */
+ if (ctl.a_option) {
+ if (ctl.a_arg)
+ verbose(&ctl, _("%s: enabling auto-eject mode"), ctl.device);
+ else
+ verbose(&ctl, _("%s: disabling auto-eject mode"), ctl.device);
+ open_device(&ctl);
+ auto_eject(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -t option */
+ if (ctl.t_option) {
+ verbose(&ctl, _("%s: closing tray"), ctl.device);
+ open_device(&ctl);
+ close_tray(ctl.fd);
+ set_device_speed(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -T option */
+ if (ctl.T_option) {
+ verbose(&ctl, _("%s: toggling tray"), ctl.device);
+ open_device(&ctl);
+ toggle_tray(ctl.fd);
+ set_device_speed(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -X option */
+ if (ctl.X_option) {
+ verbose(&ctl, _("%s: listing CD-ROM speed"), ctl.device);
+ open_device(&ctl);
+ list_speeds(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* handle -x option only */
+ if (!ctl.c_option)
+ set_device_speed(&ctl);
+
+
+ /*
+ * Unmount all partitions if -m is not specified; or umount given
+ * mountpoint if -M is specified, otherwise print error of another
+ * partition is mounted.
+ */
+ if (!ctl.m_option) {
+ int ct = umount_partitions(&ctl); /* umount all, or count mounted on -M */
+
+ if (ct == 0 && mountpoint)
+ umount_one(&ctl, mountpoint); /* probably whole-device */
+
+ if (ctl.M_option) {
+ if (ct == 1 && mountpoint)
+ umount_one(&ctl, mountpoint);
+ else if (ct)
+ errx(EXIT_FAILURE, _("error: %s: device in use"), ctl.device);
+ }
+ /* Now, we assume the device is no more used, use O_EXCL to be
+ * resistant against our bugs and possible races (someone else
+ * remounted the device).
+ */
+ ctl.force_exclusive = 1;
+ }
+
+ /* handle -c option */
+ if (ctl.c_option) {
+ verbose(&ctl, _("%s: selecting CD-ROM disc #%ld"), ctl.device, ctl.c_arg);
+ open_device(&ctl);
+ changer_select(&ctl);
+ set_device_speed(&ctl);
+ return EXIT_SUCCESS;
+ }
+
+ /* if user did not specify type of eject, try all four methods */
+ if (ctl.r_option + ctl.s_option + ctl.f_option + ctl.q_option == 0)
+ ctl.r_option = ctl.s_option = ctl.f_option = ctl.q_option = 1;
+
+ /* open device */
+ open_device(&ctl);
+
+ /* try various methods of ejecting until it works */
+ if (ctl.r_option) {
+ verbose(&ctl, _("%s: trying to eject using CD-ROM eject command"), ctl.device);
+ worked = eject_cdrom(ctl.fd);
+ verbose(&ctl, worked ? _("CD-ROM eject command succeeded") :
+ _("CD-ROM eject command failed"));
+ }
+
+ if (ctl.s_option && !worked) {
+ verbose(&ctl, _("%s: trying to eject using SCSI commands"), ctl.device);
+ worked = eject_scsi(&ctl);
+ verbose(&ctl, worked ? _("SCSI eject succeeded") :
+ _("SCSI eject failed"));
+ }
+
+ if (ctl.f_option && !worked) {
+ verbose(&ctl, _("%s: trying to eject using floppy eject command"), ctl.device);
+ worked = eject_floppy(ctl.fd);
+ verbose(&ctl, worked ? _("floppy eject command succeeded") :
+ _("floppy eject command failed"));
+ }
+
+ if (ctl.q_option && !worked) {
+ verbose(&ctl, _("%s: trying to eject using tape offline command"), ctl.device);
+ worked = eject_tape(ctl.fd);
+ verbose(&ctl, worked ? _("tape offline command succeeded") :
+ _("tape offline command failed"));
+ }
+
+ if (!worked)
+ errx(EXIT_FAILURE, _("unable to eject"));
+
+ /* cleanup */
+ close(ctl.fd);
+ free(ctl.device);
+ free(mountpoint);
+
+ mnt_unref_table(ctl.mtab);
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/fallocate.1 b/sys-utils/fallocate.1
new file mode 100644
index 0000000..9e214c7
--- /dev/null
+++ b/sys-utils/fallocate.1
@@ -0,0 +1,191 @@
+.TH FALLOCATE 1 "April 2014" "util-linux" "User Commands"
+.SH NAME
+fallocate \- preallocate or deallocate space to a file
+.SH SYNOPSIS
+.B fallocate
+.RB [ \-c | \-p | \-z ]
+.RB [ \-o
+.IR offset ]
+.B \-l
+.I length
+.RB [ \-n ]
+.I filename
+.PP
+.B fallocate \-d
+.RB [ \-o
+.IR offset ]
+.RB [ \-l
+.IR length ]
+.I filename
+.PP
+.B fallocate \-x
+.RB [ \-o
+.IR offset ]
+.B \-l
+.I length
+.I filename
+.SH DESCRIPTION
+.B fallocate
+is used to manipulate the allocated disk space for a file,
+either to deallocate or preallocate it.
+For filesystems which support the fallocate system call,
+preallocation is done quickly by allocating blocks and marking them as
+uninitialized, requiring no IO to the data blocks.
+This is much faster than creating a file by filling it with zeroes.
+.PP
+The exit status returned by
+.B fallocate
+is 0 on success and 1 on failure.
+.SH OPTIONS
+The
+.I length
+and
+.I offset
+arguments may be followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB, and YiB (the "iB" is
+optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB, and YB.
+.PP
+The options
+.BR \-\-collapse\-range ", " \-\-dig\-holes ", " \-\-punch\-hole ,
+and
+.B \-\-zero\-range
+are mutually exclusive.
+.TP
+.BR \-c ", " \-\-collapse\-range
+Removes a byte range from a file, without leaving a hole.
+The byte range to be collapsed starts at
+.I offset
+and continues for
+.I length
+bytes.
+At the completion of the operation,
+the contents of the file starting at the location
+.IR offset + length
+will be appended at the location
+.IR offset ,
+and the file will be
+.I length
+bytes smaller.
+The option
+.B \-\-keep\-size
+may not be specified for the collapse-range operation.
+.sp
+Available since Linux 3.15 for ext4 (only for extent-based files) and XFS.
+.sp
+A filesystem may place limitations on the granularity of the operation, in
+order to ensure efficient implementation. Typically, offset and len must be a
+multiple of the filesystem logical block size, which varies according to the
+filesystem type and configuration. If a filesystem has such a requirement,
+the operation will fail with the error EINVAL if this requirement is violated.
+.TP
+.BR \-d ", " \-\-dig\-holes
+Detect and dig holes.
+This makes the file sparse in-place, without using extra disk space.
+The minimum size of the hole depends on filesystem I/O block size
+(usually 4096 bytes).
+Also, when using this option,
+.B \-\-keep\-size
+is implied. If no range is specified by
+.B \-\-offset
+and
+.BR \-\-length ,
+then the entire file is analyzed for holes.
+.sp
+You can think of this option as doing a
+.RB """" "cp \-\-sparse" """"
+and then renaming the destination file to the original,
+without the need for extra disk space.
+.sp
+See \fB\-\-punch\-hole\fP for a list of supported filesystems.
+.TP
+.BR \-i ", " \-\-insert\-range
+Insert a hole of
+.I length
+bytes from
+.IR offset ,
+shifting existing data.
+.TP
+.BR \-l ", " "\-\-length " \fIlength
+Specifies the length of the range, in bytes.
+.TP
+.BR \-n ", " \-\-keep\-size
+Do not modify the apparent length of the file. This may effectively allocate
+blocks past EOF, which can be removed with a truncate.
+.TP
+.BR \-o ", " "\-\-offset " \fIoffset
+Specifies the beginning offset of the range, in bytes.
+.TP
+.BR \-p ", " \-\-punch\-hole
+Deallocates space (i.e., creates a hole) in the byte range starting at
+.I offset
+and continuing for
+.I length
+bytes.
+Within the specified range, partial filesystem blocks are zeroed,
+and whole filesystem blocks are removed from the file.
+After a successful call,
+subsequent reads from this range will return zeroes.
+This option may not be specified at the same time as the
+.B \-\-zero\-range
+option.
+Also, when using this option,
+.B \-\-keep\-size
+is implied.
+.sp
+Supported for XFS (since Linux 2.6.38), ext4 (since Linux 3.0),
+Btrfs (since Linux 3.7), tmpfs (since Linux 3.5) and gfs2 (since Linux 4.16).
+.TP
+.BR \-v ", " \-\-verbose
+Enable verbose mode.
+.TP
+.BR \-x ", " \-\-posix
+Enable POSIX operation mode.
+In that mode allocation operation always completes,
+but it may take longer time when fast allocation is not supported by
+the underlying filesystem.
+.TP
+.BR \-z ", " \-\-zero\-range
+Zeroes space in the byte range starting at
+.I offset
+and continuing for
+.I length
+bytes.
+Within the specified range, blocks are preallocated for the regions
+that span the holes in the file.
+After a successful call,
+subsequent reads from this range will return zeroes.
+.sp
+Zeroing is done within the filesystem preferably by converting the
+range into unwritten extents. This approach means that the specified
+range will not be physically zeroed out on the device (except for
+partial blocks at the either end of the range), and I/O is
+(otherwise) required only to update metadata.
+.sp
+Option \fB\-\-keep\-size\fP can be specified to prevent file length
+modification.
+.sp
+Available since Linux 3.14 for ext4 (only for extent-based files) and XFS.
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+.TP
+.BR \-h ", " \-\-help
+Display help text and exit.
+.SH AUTHORS
+.MT sandeen@redhat.com
+Eric Sandeen
+.ME
+.br
+.MT kzak@redhat.com
+Karel Zak
+.ME
+.SH SEE ALSO
+.BR truncate (1),
+.BR fallocate (2),
+.BR posix_fallocate (3)
+.SH AVAILABILITY
+The fallocate command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/fallocate.c b/sys-utils/fallocate.c
new file mode 100644
index 0000000..ba97092
--- /dev/null
+++ b/sys-utils/fallocate.c
@@ -0,0 +1,420 @@
+/*
+ * fallocate - utility to use the fallocate system call
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc. All rights reserved.
+ * Written by Eric Sandeen <sandeen@redhat.com>
+ * Karel Zak <kzak@redhat.com>
+ *
+ * cvtnum routine taken from xfsprogs,
+ * Copyright (c) 2003-2005 Silicon Graphics, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <limits.h>
+#include <string.h>
+
+#ifndef HAVE_FALLOCATE
+# include <sys/syscall.h>
+#endif
+
+#if defined(HAVE_LINUX_FALLOC_H) && \
+ (!defined(FALLOC_FL_KEEP_SIZE) || !defined(FALLOC_FL_PUNCH_HOLE) || \
+ !defined(FALLOC_FL_COLLAPSE_RANGE) || !defined(FALLOC_FL_ZERO_RANGE) || \
+ !defined(FALLOC_FL_INSERT_RANGE))
+# include <linux/falloc.h> /* non-libc fallback for FALLOC_FL_* flags */
+#endif
+
+
+#ifndef FALLOC_FL_KEEP_SIZE
+# define FALLOC_FL_KEEP_SIZE 0x1
+#endif
+
+#ifndef FALLOC_FL_PUNCH_HOLE
+# define FALLOC_FL_PUNCH_HOLE 0x2
+#endif
+
+#ifndef FALLOC_FL_COLLAPSE_RANGE
+# define FALLOC_FL_COLLAPSE_RANGE 0x8
+#endif
+
+#ifndef FALLOC_FL_ZERO_RANGE
+# define FALLOC_FL_ZERO_RANGE 0x10
+#endif
+
+#ifndef FALLOC_FL_INSERT_RANGE
+# define FALLOC_FL_INSERT_RANGE 0x20
+#endif
+
+#include "nls.h"
+#include "strutils.h"
+#include "c.h"
+#include "closestream.h"
+#include "xalloc.h"
+#include "optutils.h"
+
+static int verbose;
+static char *filename;
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] <filename>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Preallocate space to, or deallocate space from a file.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -c, --collapse-range remove a range from the file\n"), out);
+ fputs(_(" -d, --dig-holes detect zeroes and replace with holes\n"), out);
+ fputs(_(" -i, --insert-range insert a hole at range, shifting existing data\n"), out);
+ fputs(_(" -l, --length <num> length for range operations, in bytes\n"), out);
+ fputs(_(" -n, --keep-size maintain the apparent size of the file\n"), out);
+ fputs(_(" -o, --offset <num> offset for range operations, in bytes\n"), out);
+ fputs(_(" -p, --punch-hole replace a range with a hole (implies -n)\n"), out);
+ fputs(_(" -z, --zero-range zero and ensure allocation of a range\n"), out);
+#ifdef HAVE_POSIX_FALLOCATE
+ fputs(_(" -x, --posix use posix_fallocate(3) instead of fallocate(2)\n"), out);
+#endif
+ fputs(_(" -v, --verbose verbose mode\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<num>")));
+
+ printf(USAGE_MAN_TAIL("fallocate(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static loff_t cvtnum(char *s)
+{
+ uintmax_t x;
+
+ if (strtosize(s, &x))
+ return -1LL;
+
+ return x;
+}
+
+static void xfallocate(int fd, int mode, off_t offset, off_t length)
+{
+ int error;
+
+#ifdef HAVE_FALLOCATE
+ error = fallocate(fd, mode, offset, length);
+#else
+ error = syscall(SYS_fallocate, fd, mode, offset, length);
+#endif
+ /*
+ * EOPNOTSUPP: The FALLOC_FL_KEEP_SIZE is unsupported
+ * ENOSYS: The filesystem does not support sys_fallocate
+ */
+ if (error < 0) {
+ if ((mode & FALLOC_FL_KEEP_SIZE) && errno == EOPNOTSUPP)
+ errx(EXIT_FAILURE, _("fallocate failed: keep size mode is unsupported"));
+ err(EXIT_FAILURE, _("fallocate failed"));
+ }
+}
+
+#ifdef HAVE_POSIX_FALLOCATE
+static void xposix_fallocate(int fd, off_t offset, off_t length)
+{
+ int error = posix_fallocate(fd, offset, length);
+ if (error < 0) {
+ err(EXIT_FAILURE, _("fallocate failed"));
+ }
+}
+#endif
+
+/* The real buffer size has to be bufsize + sizeof(uintptr_t) */
+static int is_nul(void *buf, size_t bufsize)
+{
+ typedef uintptr_t word;
+ void const *vp;
+ char const *cbuf = buf, *cp;
+ word const *wp = buf;
+
+ /* set sentinel */
+ memset((char *) buf + bufsize, '\1', sizeof(word));
+
+ /* Find first nonzero *word*, or the word with the sentinel. */
+ while (*wp++ == 0)
+ continue;
+
+ /* Find the first nonzero *byte*, or the sentinel. */
+ vp = wp - 1;
+ cp = vp;
+
+ while (*cp++ == 0)
+ continue;
+
+ return cbuf + bufsize < cp;
+}
+
+static void dig_holes(int fd, off_t file_off, off_t len)
+{
+ off_t file_end = len ? file_off + len : 0;
+ off_t hole_start = 0, hole_sz = 0;
+ uintmax_t ct = 0;
+ size_t bufsz;
+ char *buf;
+ struct stat st;
+#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
+ off_t cache_start = file_off;
+ /*
+ * We don't want to call POSIX_FADV_DONTNEED to discard cached
+ * data in PAGE_SIZE steps. IMHO it's overkill (too many syscalls).
+ *
+ * Let's assume that 1MiB (on system with 4K page size) is just
+ * a good compromise.
+ * -- kzak Feb-2014
+ */
+ const size_t cachesz = getpagesize() * 256;
+#endif
+
+ if (fstat(fd, &st) != 0)
+ err(EXIT_FAILURE, _("stat of %s failed"), filename);
+
+ bufsz = st.st_blksize;
+
+ if (lseek(fd, file_off, SEEK_SET) < 0)
+ err(EXIT_FAILURE, _("seek on %s failed"), filename);
+
+ /* buffer + extra space for is_nul() sentinel */
+ buf = xmalloc(bufsz + sizeof(uintptr_t));
+ while (file_end == 0 || file_off < file_end) {
+ /*
+ * Detect data area (skip holes)
+ */
+ off_t end, off;
+
+ off = lseek(fd, file_off, SEEK_DATA);
+ if ((off == -1 && errno == ENXIO) ||
+ (file_end && off >= file_end))
+ break;
+
+ end = lseek(fd, off, SEEK_HOLE);
+ if (file_end && end > file_end)
+ end = file_end;
+
+ if (off < 0 || end < 0)
+ break;
+
+#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
+ (void) posix_fadvise(fd, off, end, POSIX_FADV_SEQUENTIAL);
+#endif
+ /*
+ * Dig holes in the area
+ */
+ while (off < end) {
+ ssize_t rsz = pread(fd, buf, bufsz, off);
+ if (rsz < 0 && errno)
+ err(EXIT_FAILURE, _("%s: read failed"), filename);
+ if (end && rsz > 0 && off > end - rsz)
+ rsz = end - off;
+ if (rsz <= 0)
+ break;
+
+ if (is_nul(buf, rsz)) {
+ if (!hole_sz) /* new hole detected */
+ hole_start = off;
+ hole_sz += rsz;
+ } else if (hole_sz) {
+ xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
+ hole_start, hole_sz);
+ ct += hole_sz;
+ hole_sz = hole_start = 0;
+ }
+
+#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE)
+ /* discard cached data */
+ if (off - cache_start > (off_t) cachesz) {
+ size_t clen = off - cache_start;
+
+ clen = (clen / cachesz) * cachesz;
+ (void) posix_fadvise(fd, cache_start, clen, POSIX_FADV_DONTNEED);
+ cache_start = cache_start + clen;
+ }
+#endif
+ off += rsz;
+ }
+ if (hole_sz) {
+ off_t alloc_sz = hole_sz;
+ if (off >= end)
+ alloc_sz += st.st_blksize; /* meet block boundary */
+ xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
+ hole_start, alloc_sz);
+ ct += hole_sz;
+ }
+ file_off = off;
+ }
+
+ free(buf);
+
+ if (verbose) {
+ char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE, ct);
+ fprintf(stdout, _("%s: %s (%ju bytes) converted to sparse holes.\n"),
+ filename, str, ct);
+ free(str);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ int fd;
+ int mode = 0;
+ int dig = 0;
+ int posix = 0;
+ loff_t length = -2LL;
+ loff_t offset = 0;
+
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "keep-size", no_argument, NULL, 'n' },
+ { "punch-hole", no_argument, NULL, 'p' },
+ { "collapse-range", no_argument, NULL, 'c' },
+ { "dig-holes", no_argument, NULL, 'd' },
+ { "insert-range", no_argument, NULL, 'i' },
+ { "zero-range", no_argument, NULL, 'z' },
+ { "offset", required_argument, NULL, 'o' },
+ { "length", required_argument, NULL, 'l' },
+ { "posix", no_argument, NULL, 'x' },
+ { "verbose", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'c', 'd', 'p', 'z' },
+ { 'c', 'n' },
+ { 'x', 'c', 'd', 'i', 'n', 'p', 'z'},
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "hvVncpdizxl:o:", longopts, NULL))
+ != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'c':
+ mode |= FALLOC_FL_COLLAPSE_RANGE;
+ break;
+ case 'd':
+ dig = 1;
+ break;
+ case 'i':
+ mode |= FALLOC_FL_INSERT_RANGE;
+ break;
+ case 'l':
+ length = cvtnum(optarg);
+ break;
+ case 'n':
+ mode |= FALLOC_FL_KEEP_SIZE;
+ break;
+ case 'o':
+ offset = cvtnum(optarg);
+ break;
+ case 'p':
+ mode |= FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
+ break;
+ case 'z':
+ mode |= FALLOC_FL_ZERO_RANGE;
+ break;
+ case 'x':
+#ifdef HAVE_POSIX_FALLOCATE
+ posix = 1;
+ break;
+#else
+ errx(EXIT_FAILURE, _("posix_fallocate support is not compiled"));
+#endif
+ case 'v':
+ verbose++;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no filename specified"));
+
+ filename = argv[optind++];
+
+ if (optind != argc)
+ errx(EXIT_FAILURE, _("unexpected number of arguments"));
+
+ if (dig) {
+ /* for --dig-holes the default is analyze all file */
+ if (length == -2LL)
+ length = 0;
+ if (length < 0)
+ errx(EXIT_FAILURE, _("invalid length value specified"));
+ } else {
+ /* it's safer to require the range specification (--length --offset) */
+ if (length == -2LL)
+ errx(EXIT_FAILURE, _("no length argument specified"));
+ if (length <= 0)
+ errx(EXIT_FAILURE, _("invalid length value specified"));
+ }
+ if (offset < 0)
+ errx(EXIT_FAILURE, _("invalid offset value specified"));
+
+ /* O_CREAT makes sense only for the default fallocate(2) behavior
+ * when mode is no specified and new space is allocated */
+ fd = open(filename, O_RDWR | (!dig && !mode ? O_CREAT : 0),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+
+ if (dig)
+ dig_holes(fd, offset, length);
+#ifdef HAVE_POSIX_FALLOCATE
+ else if (posix)
+ xposix_fallocate(fd, offset, length);
+#endif
+ else
+ xfallocate(fd, mode, offset, length);
+
+ if (close_fd(fd) != 0)
+ err(EXIT_FAILURE, _("write failed: %s"), filename);
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/flock.1 b/sys-utils/flock.1
new file mode 100644
index 0000000..5235f83
--- /dev/null
+++ b/sys-utils/flock.1
@@ -0,0 +1,206 @@
+.\" -----------------------------------------------------------------------
+.\"
+.\" Copyright 2003-2006 H. Peter Anvin - All Rights Reserved
+.\"
+.\" Permission is hereby granted, free of charge, to any person
+.\" obtaining a copy of this software and associated documentation
+.\" files (the "Software"), to deal in the Software without
+.\" restriction, including without limitation the rights to use,
+.\" copy, modify, merge, publish, distribute, sublicense, and/or
+.\" sell copies of the Software, and to permit persons to whom
+.\" the Software is furnished to do so, subject to the following
+.\" conditions:
+.\"
+.\" The above copyright notice and this permission notice shall
+.\" be included in all copies or substantial portions of the Software.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+.\" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+.\" OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+.\" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+.\" HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+.\" WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+.\" FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+.\" OTHER DEALINGS IN THE SOFTWARE.
+.\"
+.\" -----------------------------------------------------------------------
+.TH FLOCK 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+flock \- manage locks from shell scripts
+.SH SYNOPSIS
+.B flock
+[options]
+.IR file | "directory command " [ arguments ]
+.br
+.B flock
+[options]
+.IR file | directory
+.BI \-c " command"
+.br
+.B flock
+.RI [options] " number"
+.SH DESCRIPTION
+This utility manages
+.BR flock (2)
+locks from within shell scripts or from the command line.
+.PP
+The first and second of the above forms wrap the lock around the execution of a
+.IR command ,
+in a manner similar to
+.BR su (1)
+or
+.BR newgrp (1).
+They lock a specified \fIfile\fR or \fIdirectory\fR, which is created (assuming
+appropriate permissions) if it does not already exist. By default, if the
+lock cannot be immediately acquired,
+.B flock
+waits until the lock is available.
+.PP
+The third form uses an open file by its file descriptor \fInumber\fR.
+See the examples below for how that can be used.
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-command " \fIcommand
+Pass a single \fIcommand\fR, without arguments, to the shell with
+.BR \-c .
+.TP
+.BR \-E , " \-\-conflict\-exit\-code " \fInumber
+The exit status used when the \fB\-n\fP option is in use, and the
+conflicting lock exists, or the \fB\-w\fP option is in use,
+and the timeout is reached. The default value is \fB1\fR.
+The \fInumber\fR has to be in the range of 0 to 255.
+.TP
+.BR \-F , " \-\-no\-fork"
+Do not fork before executing
+.IR command .
+Upon execution the flock process is replaced by
+.I command
+which continues to hold the lock. This option is incompatible with
+\fB\-\-close\fR as there would otherwise be nothing left to hold the lock.
+.TP
+.BR \-e , " \-x" , " \-\-exclusive"
+Obtain an exclusive lock, sometimes called a write lock. This is the
+default.
+.TP
+.BR \-n , " \-\-nb" , " \-\-nonblock"
+Fail rather than wait if the lock cannot be
+immediately acquired.
+See the
+.B \-E
+option for the exit status used.
+.TP
+.BR \-o , " \-\-close"
+Close the file descriptor on which the lock is held before executing
+.IR command .
+This is useful if
+.I command
+spawns a child process which should not be holding the lock.
+.TP
+.BR \-s , " \-\-shared"
+Obtain a shared lock, sometimes called a read lock.
+.TP
+.BR \-u , " \-\-unlock"
+Drop a lock. This is usually not required, since a lock is automatically
+dropped when the file is closed. However, it may be required in special
+cases, for example if the enclosed command group may have forked a background
+process which should not be holding the lock.
+.TP
+.BR \-w , " \-\-wait" , " \-\-timeout " \fIseconds
+Fail if the lock cannot be acquired within
+.IR seconds .
+Decimal fractional values are allowed.
+See the
+.B \-E
+option for the exit status used. The zero number of
+.I seconds
+is interpreted as \fB\-\-nonblock\fR.
+.TP
+.B \-\-verbose
+Report how long it took to acquire the lock, or why the lock could not be
+obtained.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXIT STATUS
+The command uses
+.B sysexits.h
+exit status values for everything, except when using either of the options
+.B \-n
+or
+.B \-w
+which report a failure to acquire the lock with a exit status given by the
+.B \-E
+option, or 1 by default. The exit status given by
+.B \-E has to be in the range of 0 to 255.
+.PP
+When using the \fIcommand\fR variant, and executing the child worked, then
+the exit status is that of the child command.
+.SH EXAMPLES
+Note that "shell> " in examples is a command line prompt.
+.TP
+shell1> flock /tmp \-c cat
+.TQ
+shell2> flock \-w .007 /tmp \-c echo; /bin/echo $?
+Set exclusive lock to directory /tmp and the second command will fail.
+.TP
+shell1> flock \-s /tmp \-c cat
+.TQ
+shell2> flock \-s \-w .007 /tmp \-c echo; /bin/echo $?
+Set shared lock to directory /tmp and the second command will not fail.
+Notice that attempting to get exclusive lock with second command would fail.
+.TP
+shell> flock \-x local-lock-file echo 'a b c'
+Grab the exclusive lock "local-lock-file" before running echo with 'a b c'.
+.TP
+(
+.TQ
+ flock \-n 9 || exit 1
+.TQ
+ # ... commands executed under lock ...
+.TQ
+) 9>/var/lock/mylockfile
+The form is convenient inside shell scripts. The mode used to open the file
+doesn't matter to
+.BR flock ;
+using
+.I >
+or
+.I >>
+allows the lockfile to be created if it does not already exist, however,
+write permission is required. Using
+.I <
+requires that the file already exists but only read permission is required.
+.TP
+[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock \-en "$0" "$0" "$@" || :
+This is useful boilerplate code for shell scripts. Put it at the top of the
+shell script you want to lock and it'll automatically lock itself on the first
+run. If the env var $FLOCKER is not set to the shell script that is being run,
+then execute flock and grab an exclusive non-blocking lock (using the script
+itself as the lock file) before re-execing itself with the right arguments. It
+also sets the FLOCKER env var to the right value so it doesn't run again.
+.TP
+shell> exec 4<>/var/lock/mylockfile
+.TQ
+shell> flock -n 4
+This form is convenient for locking a file without spawning a subprocess.
+The shell opens the lock file for reading and writing as file descriptor 4,
+then flock is used to lock the descriptor.
+.SH AUTHORS
+.UR hpa@zytor.com
+H. Peter Anvin
+.UE
+.SH COPYRIGHT
+Copyright \(co 2003\-2006 H. Peter Anvin.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH SEE ALSO
+.BR flock (2)
+.SH AVAILABILITY
+The flock command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/flock.c b/sys-utils/flock.c
new file mode 100644
index 0000000..670839c
--- /dev/null
+++ b/sys-utils/flock.c
@@ -0,0 +1,384 @@
+/* Copyright 2003-2005 H. Peter Anvin - All Rights Reserved
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall
+ * be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "closestream.h"
+#include "monotonic.h"
+#include "timer.h"
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(
+ _(" %1$s [options] <file>|<directory> <command> [<argument>...]\n"
+ " %1$s [options] <file>|<directory> -c <command>\n"
+ " %1$s [options] <file descriptor number>\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Manage file locks from shell scripts.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_( " -s, --shared get a shared lock\n"), stdout);
+ fputs(_( " -x, --exclusive get an exclusive lock (default)\n"), stdout);
+ fputs(_( " -u, --unlock remove a lock\n"), stdout);
+ fputs(_( " -n, --nonblock fail rather than wait\n"), stdout);
+ fputs(_( " -w, --timeout <secs> wait for a limited amount of time\n"), stdout);
+ fputs(_( " -E, --conflict-exit-code <number> exit code after conflict or timeout\n"), stdout);
+ fputs(_( " -o, --close close file descriptor before running command\n"), stdout);
+ fputs(_( " -c, --command <command> run a single command string through the shell\n"), stdout);
+ fputs(_( " -F, --no-fork execute command without forking\n"), stdout);
+ fputs(_( " --verbose increase verbosity\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(26));
+ printf(USAGE_MAN_TAIL("flock(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static sig_atomic_t timeout_expired = 0;
+
+static void timeout_handler(int sig __attribute__((__unused__)),
+ siginfo_t *info,
+ void *context __attribute__((__unused__)))
+{
+#ifdef HAVE_TIMER_CREATE
+ if (info->si_code == SI_TIMER)
+#endif
+ timeout_expired = 1;
+}
+
+static int open_file(const char *filename, int *flags)
+{
+
+ int fd;
+ int fl = *flags == 0 ? O_RDONLY : *flags;
+
+ errno = 0;
+ fl |= O_NOCTTY | O_CREAT;
+ fd = open(filename, fl, 0666);
+
+ /* Linux doesn't like O_CREAT on a directory, even though it
+ * should be a no-op; POSIX doesn't allow O_RDWR or O_WRONLY
+ */
+ if (fd < 0 && errno == EISDIR) {
+ fl = O_RDONLY | O_NOCTTY;
+ fd = open(filename, fl);
+ }
+ if (fd < 0) {
+ warn(_("cannot open lock file %s"), filename);
+ if (errno == ENOMEM || errno == EMFILE || errno == ENFILE)
+ exit(EX_OSERR);
+ if (errno == EROFS || errno == ENOSPC)
+ exit(EX_CANTCREAT);
+ exit(EX_NOINPUT);
+ }
+ *flags = fl;
+ return fd;
+}
+
+static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
+{
+ execvp(cmd_argv[0], cmd_argv);
+
+ warn(_("failed to execute %s"), cmd_argv[0]);
+ _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
+}
+
+int main(int argc, char *argv[])
+{
+ struct ul_timer timer;
+ struct itimerval timeout;
+ int have_timeout = 0;
+ int type = LOCK_EX;
+ int block = 0;
+ int open_flags = 0;
+ int fd = -1;
+ int opt, ix;
+ int do_close = 0;
+ int no_fork = 0;
+ int status;
+ int verbose = 0;
+ struct timeval time_start, time_done;
+ /*
+ * The default exit code for lock conflict or timeout
+ * is specified in man flock.1
+ */
+ int conflict_exit_code = 1;
+ char **cmd_argv = NULL, *sh_c_argv[4];
+ const char *filename = NULL;
+ enum {
+ OPT_VERBOSE = CHAR_MAX + 1
+ };
+ static const struct option long_options[] = {
+ {"shared", no_argument, NULL, 's'},
+ {"exclusive", no_argument, NULL, 'x'},
+ {"unlock", no_argument, NULL, 'u'},
+ {"nonblocking", no_argument, NULL, 'n'},
+ {"nb", no_argument, NULL, 'n'},
+ {"timeout", required_argument, NULL, 'w'},
+ {"wait", required_argument, NULL, 'w'},
+ {"conflict-exit-code", required_argument, NULL, 'E'},
+ {"close", no_argument, NULL, 'o'},
+ {"no-fork", no_argument, NULL, 'F'},
+ {"verbose", no_argument, NULL, OPT_VERBOSE},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ strutils_set_exitcode(EX_USAGE);
+
+ if (argc < 2) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EX_USAGE);
+ }
+
+ memset(&timeout, 0, sizeof timeout);
+
+ optopt = 0;
+ while ((opt =
+ getopt_long(argc, argv, "+sexnoFuw:E:hV?", long_options,
+ &ix)) != EOF) {
+ switch (opt) {
+ case 's':
+ type = LOCK_SH;
+ break;
+ case 'e':
+ case 'x':
+ type = LOCK_EX;
+ break;
+ case 'u':
+ type = LOCK_UN;
+ break;
+ case 'o':
+ do_close = 1;
+ break;
+ case 'F':
+ no_fork = 1;
+ break;
+ case 'n':
+ block = LOCK_NB;
+ break;
+ case 'w':
+ have_timeout = 1;
+ strtotimeval_or_err(optarg, &timeout.it_value,
+ _("invalid timeout value"));
+ break;
+ case 'E':
+ conflict_exit_code = strtos32_or_err(optarg,
+ _("invalid exit code"));
+ if (conflict_exit_code < 0 || conflict_exit_code > 255)
+ errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
+ break;
+ case OPT_VERBOSE:
+ verbose = 1;
+ break;
+
+ case 'V':
+ print_version(EX_OK);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EX_USAGE);
+ }
+ }
+
+ if (no_fork && do_close)
+ errx(EX_USAGE,
+ _("the --no-fork and --close options are incompatible"));
+
+ if (argc > optind + 1) {
+ /* Run command */
+ if (!strcmp(argv[optind + 1], "-c") ||
+ !strcmp(argv[optind + 1], "--command")) {
+ if (argc != optind + 3)
+ errx(EX_USAGE,
+ _("%s requires exactly one command argument"),
+ argv[optind + 1]);
+ cmd_argv = sh_c_argv;
+ cmd_argv[0] = getenv("SHELL");
+ if (!cmd_argv[0] || !*cmd_argv[0])
+ cmd_argv[0] = _PATH_BSHELL;
+ cmd_argv[1] = "-c";
+ cmd_argv[2] = argv[optind + 2];
+ cmd_argv[3] = NULL;
+ } else {
+ cmd_argv = &argv[optind + 1];
+ }
+
+ filename = argv[optind];
+ fd = open_file(filename, &open_flags);
+
+ } else if (optind < argc) {
+ /* Use provided file descriptor */
+ fd = strtos32_or_err(argv[optind], _("bad file descriptor"));
+ } else {
+ /* Bad options */
+ errx(EX_USAGE, _("requires file descriptor, file or directory"));
+ }
+
+ if (have_timeout) {
+ if (timeout.it_value.tv_sec == 0 &&
+ timeout.it_value.tv_usec == 0) {
+ /* -w 0 is equivalent to -n; this has to be
+ * special-cased because setting an itimer to zero
+ * means disabled!
+ */
+ have_timeout = 0;
+ block = LOCK_NB;
+ } else
+ if (setup_timer(&timer, &timeout, &timeout_handler))
+ err(EX_OSERR, _("cannot set up timer"));
+ }
+
+ if (verbose)
+ gettime_monotonic(&time_start);
+ while (flock(fd, type | block)) {
+ switch (errno) {
+ case EWOULDBLOCK:
+ /* -n option set and failed to lock. */
+ if (verbose)
+ warnx(_("failed to get lock"));
+ exit(conflict_exit_code);
+ case EINTR:
+ /* Signal received */
+ if (timeout_expired) {
+ /* -w option set and failed to lock. */
+ if (verbose)
+ warnx(_("timeout while waiting to get lock"));
+ exit(conflict_exit_code);
+ }
+ /* otherwise try again */
+ continue;
+ case EIO:
+ case EBADF: /* since Linux 3.4 (commit 55725513) */
+ /* Probably NFSv4 where flock() is emulated by fcntl().
+ * Let's try to reopen in read-write mode.
+ */
+ if (!(open_flags & O_RDWR) &&
+ type != LOCK_SH &&
+ filename &&
+ access(filename, R_OK | W_OK) == 0) {
+
+ close(fd);
+ open_flags = O_RDWR;
+ fd = open_file(filename, &open_flags);
+
+ if (open_flags & O_RDWR)
+ break;
+ }
+ /* fallthrough */
+ default:
+ /* Other errors */
+ if (filename)
+ warn("%s", filename);
+ else
+ warn("%d", fd);
+ exit((errno == ENOLCK
+ || errno == ENOMEM) ? EX_OSERR : EX_DATAERR);
+ }
+ }
+
+ if (have_timeout)
+ cancel_timer(&timer);
+ if (verbose) {
+ struct timeval delta;
+
+ gettime_monotonic(&time_done);
+ timersub(&time_done, &time_start, &delta);
+ printf(_("%s: getting lock took %ld.%06ld seconds\n"),
+ program_invocation_short_name, delta.tv_sec,
+ delta.tv_usec);
+ }
+ status = EX_OK;
+
+ if (cmd_argv) {
+ pid_t w, f;
+ /* Clear any inherited settings */
+ signal(SIGCHLD, SIG_DFL);
+ if (verbose)
+ printf(_("%s: executing %s\n"), program_invocation_short_name, cmd_argv[0]);
+
+ if (!no_fork) {
+ f = fork();
+ if (f < 0)
+ err(EX_OSERR, _("fork failed"));
+
+ /* child */
+ else if (f == 0) {
+ if (do_close)
+ close(fd);
+ run_program(cmd_argv);
+
+ /* parent */
+ } else {
+ do {
+ w = waitpid(f, &status, 0);
+ if (w == -1 && errno != EINTR)
+ break;
+ } while (w != f);
+
+ if (w == -1) {
+ status = EXIT_FAILURE;
+ warn(_("waitpid failed"));
+ } else if (WIFEXITED(status))
+ status = WEXITSTATUS(status);
+ else if (WIFSIGNALED(status))
+ status = WTERMSIG(status) + 128;
+ else
+ /* WTF? */
+ status = EX_OSERR;
+ }
+
+ } else
+ /* no-fork execution */
+ run_program(cmd_argv);
+ }
+
+ return status;
+}
diff --git a/sys-utils/fsfreeze.8 b/sys-utils/fsfreeze.8
new file mode 100644
index 0000000..cbde489
--- /dev/null
+++ b/sys-utils/fsfreeze.8
@@ -0,0 +1,87 @@
+.TH FSFREEZE 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+fsfreeze \- suspend access to a filesystem (Ext3/4, ReiserFS, JFS, XFS)
+.SH SYNOPSIS
+.B fsfreeze
+.BR \--freeze | \--unfreeze
+.I mountpoint
+
+.SH DESCRIPTION
+.B fsfreeze
+suspends or resumes access to a filesystem.
+.PP
+.B fsfreeze
+halts any new access to the filesystem and creates a stable image on disk.
+.B fsfreeze
+is intended to be used with hardware RAID devices that support the creation
+of snapshots.
+.PP
+.B fsfreeze
+is unnecessary for
+.B device-mapper
+devices. The device-mapper (and LVM) automatically freezes a filesystem
+on the device when a snapshot creation is requested.
+For more details see the
+.BR dmsetup (8)
+man page.
+.PP
+The
+.I mountpoint
+argument is the pathname of the directory where the filesystem
+is mounted.
+The filesystem must be mounted to be frozen (see
+.BR mount (8)).
+.PP
+Note that access-time updates are also suspended if the filesystem is mounted with
+the traditional atime behavior (mount option \fBstrictatime\fR, for more details see
+.BR mount (8)).
+
+.SH OPTIONS
+.TP
+.BR \-f , " \-\-freeze"
+This option requests the specified a filesystem to be frozen from new
+modifications. When this is selected, all ongoing transactions in the
+filesystem are allowed to complete, new write system calls are halted, other
+calls which modify the filesystem are halted, and all dirty data, metadata, and
+log information are written to disk. Any process attempting to write to the
+frozen filesystem will block waiting for the filesystem to be unfrozen.
+.sp
+Note that even after freezing, the on-disk filesystem can contain
+information on files that are still in the process of unlinking.
+These files will not be unlinked until the filesystem is unfrozen
+or a clean mount of the snapshot is complete.
+.TP
+.BR \-u , " \-\-unfreeze"
+This option is used to un-freeze the filesystem and allow operations to
+continue. Any filesystem modifications that were blocked by the freeze are
+unblocked and allowed to complete.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH FILESYSTEM SUPPORT
+This command will work only if filesystem supports has support for freezing.
+List of these filesystems include (2016-12-18)
+.BR btrfs ,
+.BR ext2/3/4 ,
+.BR f2fs ,
+.BR jfs ,
+.BR nilfs2 ,
+.BR reiserfs ,
+and
+.BR xfs .
+Previous list may be incomplete, as more filesystems get support. If in
+doubt easiest way to know if a filesystem has support is create a small
+loopback mount and test freezing it.
+.SH NOTES
+This man page is based on
+.BR xfs_freeze (8).
+.SH AUTHORS
+Written by Hajime Taira.
+.SH SEE ALSO
+.BR mount (8)
+.SH AVAILABILITY
+The fsfreeze command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/fsfreeze.c b/sys-utils/fsfreeze.c
new file mode 100644
index 0000000..cd2bb47
--- /dev/null
+++ b/sys-utils/fsfreeze.c
@@ -0,0 +1,150 @@
+/*
+ * fsfreeze.c -- Filesystem freeze/unfreeze IO for Linux
+ *
+ * Copyright (C) 2010 Hajime Taira <htaira@redhat.com>
+ * Masatake Yamato <yamato@redhat.com>
+ *
+ * This program is free software. You can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation: either version 1 or
+ * (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "blkdev.h"
+#include "nls.h"
+#include "closestream.h"
+#include "optutils.h"
+
+enum fs_operation {
+ NOOP,
+ FREEZE,
+ UNFREEZE
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] <mountpoint>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Suspend access to a filesystem.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -f, --freeze freeze the filesystem\n"), out);
+ fputs(_(" -u, --unfreeze unfreeze the filesystem\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(19));
+ printf(USAGE_MAN_TAIL("fsfreeze(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int fd = -1, c;
+ int action = NOOP, rc = EXIT_FAILURE;
+ char *path;
+ struct stat sb;
+
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "freeze", no_argument, NULL, 'f' },
+ { "unfreeze", no_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'f','u' }, /* freeze, unfreeze */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "hfuV", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'f':
+ action = FREEZE;
+ break;
+ case 'u':
+ action = UNFREEZE;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (action == NOOP)
+ errx(EXIT_FAILURE, _("neither --freeze or --unfreeze specified"));
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no filename specified"));
+ path = argv[optind++];
+
+ if (optind != argc) {
+ warnx(_("unexpected number of arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), path);
+
+ if (fstat(fd, &sb) == -1) {
+ warn(_("stat of %s failed"), path);
+ goto done;
+ }
+
+ if (!S_ISDIR(sb.st_mode)) {
+ warnx(_("%s: is not a directory"), path);
+ goto done;
+ }
+
+ switch (action) {
+ case FREEZE:
+ if (ioctl(fd, FIFREEZE, 0)) {
+ warn(_("%s: freeze failed"), path);
+ goto done;
+ }
+ break;
+ case UNFREEZE:
+ if (ioctl(fd, FITHAW, 0)) {
+ warn(_("%s: unfreeze failed"), path);
+ goto done;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ rc = EXIT_SUCCESS;
+done:
+ close(fd);
+ return rc;
+}
+
diff --git a/sys-utils/fstab.5 b/sys-utils/fstab.5
new file mode 100644
index 0000000..118df0d
--- /dev/null
+++ b/sys-utils/fstab.5
@@ -0,0 +1,249 @@
+.\" Copyright (c) 1980, 1989, 1991 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)fstab.5 6.5 (Berkeley) 5/10/91
+.\"
+.TH FSTAB 5 "February 2015" "util-linux" "File Formats"
+.SH NAME
+fstab \- static information about the filesystems
+.SH SYNOPSIS
+.I /etc/fstab
+.SH DESCRIPTION
+The file
+.B fstab
+contains descriptive information about the filesystems the system can mount.
+.B fstab
+is only read by programs, and not written; it is the duty of the system
+administrator to properly create and maintain this file. The order of records in
+.B fstab
+is important because
+.BR fsck (8),
+.BR mount (8),
+and
+.BR umount (8)
+sequentially iterate through
+.B fstab
+doing their thing.
+
+Each filesystem is described on a separate line.
+Fields on each line are separated by tabs or spaces.
+Lines starting with '#' are comments. Blank lines are ignored.
+.PP
+The following is a typical example of an
+.B fstab
+entry:
+.sp
+.RS 7
+LABEL=t-home2 /home ext4 defaults,auto_da_alloc 0 2
+.RE
+
+.B The first field
+.RI ( fs_spec ).
+.RS
+This field describes the block special device, remote filesystem or filesystem
+image for loop device to be mounted or swap file or swap partition to be enabled.
+.LP
+For ordinary mounts, it will hold (a link to) a block special
+device node (as created by
+.BR mknod (2))
+for the device to be mounted, like `/dev/cdrom' or `/dev/sdb7'.
+For NFS mounts, this field is <host>:<dir>, e.g., `knuth.aeb.nl:/'.
+For filesystems with no storage, any string can be used, and will show up in
+.BR df (1)
+output, for example. Typical usage is `proc' for procfs; `mem', `none',
+or `tmpfs' for tmpfs. Other special filesystems, like udev and sysfs,
+are typically not listed in
+.BR fstab .
+.LP
+LABEL=<label> or UUID=<uuid> may be given instead of a device name.
+This is the recommended method, as device names are often a coincidence
+of hardware detection order, and can change when other disks are added or removed.
+For example, `LABEL=Boot' or `UUID=3e6be9de\%-8139\%-11d1\%-9106\%-a43f08d823a6'.
+(Use a filesystem-specific tool like
+.BR e2label (8),
+.BR xfs_admin (8),
+or
+.BR fatlabel (8)
+to set LABELs on filesystems).
+
+It's also possible to use PARTUUID= and PARTLABEL=. These partitions identifiers
+are supported for example for GUID Partition Table (GPT).
+
+See
+.BR mount (8),
+.BR blkid (8)
+or
+.BR lsblk (8)
+for more details about device identifiers.
+
+.LP
+Note that
+.BR mount (8)
+uses UUIDs as strings. The string representation of the UUID should be based on
+lower case characters.
+.RE
+
+.B The second field
+.RI ( fs_file ).
+.RS
+This field describes the mount point (target) for the filesystem. For swap partitions, this
+field should be specified as `none'. If the name of the mount point
+contains spaces or tabs these can be escaped as `\\040' and '\\011'
+respectively.
+.RE
+
+.B The third field
+.RI ( fs_vfstype ).
+.RS
+This field describes the type of the filesystem. Linux supports many
+filesystem types: ext4, xfs, btrfs, f2fs, vfat, ntfs, hfsplus,
+tmpfs, sysfs, proc, iso9660, udf, squashfs, nfs, cifs, and many more.
+For more details, see
+.BR mount (8).
+
+An entry
+.I swap
+denotes a file or partition to be used
+for swapping, cf.\&
+.BR swapon (8).
+An entry
+.I none
+is useful for bind or move mounts.
+
+More than one type may be specified in a comma-separated list.
+
+.BR mount (8)
+and
+.BR umount (8)
+support filesystem
+.IR subtypes .
+The subtype is defined by '.subtype' suffix. For
+example 'fuse.sshfs'. It's recommended to use subtype notation rather than add
+any prefix to the first fstab field (for example 'sshfs#example.com' is
+deprecated).
+.RE
+
+.B The fourth field
+.RI ( fs_mntops ).
+.RS
+This field describes the mount options associated with the filesystem.
+
+It is formatted as a comma-separated list of options.
+It contains at least the type of mount
+.RB ( ro
+or
+.BR rw ),
+plus any additional options appropriate to the filesystem
+type (including performance-tuning options).
+For details, see
+.BR mount (8)
+or
+.BR swapon (8).
+
+Basic filesystem-independent options are:
+.TP
+.B defaults
+use default options: rw, suid, dev, exec, auto, nouser, and async.
+.TP
+.B noauto
+do not mount when "mount \-a" is given (e.g., at boot time)
+.TP
+.B user
+allow a user to mount
+.TP
+.B owner
+allow device owner to mount
+.TP
+.B comment
+or
+.B x-<name>
+for use by fstab-maintaining programs
+.TP
+.B nofail
+do not report errors for this device if it does not exist.
+.RE
+
+.B The fifth field
+.RI ( fs_freq ).
+.RS
+This field is used by
+.BR dump (8)
+to determine which filesystems need to be dumped.
+Defaults to zero (don't dump) if not present.
+.RE
+
+.B The sixth field
+.RI ( fs_passno ).
+.RS
+This field is used by
+.BR fsck (8)
+to determine the order in which filesystem checks are done at
+boot time. The root filesystem should be specified with a
+.I fs_passno
+of 1. Other filesystems should have a
+.I fs_passno
+of 2. Filesystems within a drive will be checked sequentially, but
+filesystems on different drives will be checked at the same time to utilize
+parallelism available in the hardware.
+Defaults to zero (don't fsck) if not present.
+.RE
+
+.SH FILES
+.IR /etc/fstab ,
+.I <fstab.h>
+
+.SH NOTES
+The proper way to read records from
+.B fstab
+is to use the routines
+.BR getmntent (3)
+or
+.BR libmount .
+
+The keyword
+.B ignore
+as a filesystem type (3rd field) is no longer supported by the pure
+libmount based mount utility (since util-linux v2.22).
+.SH HISTORY
+The ancestor of this
+.B fstab
+file format appeared in 4.0BSD.
+.\" But without comment convention, and options and vfs_type.
+.SH SEE ALSO
+.BR getmntent (3),
+.BR fs (5),
+.BR findmnt (8),
+.BR mount (8),
+.BR swapon (8)
+.\" Instead there was a type rw/ro/rq/sw/xx, where xx is the present 'ignore'.
+.SH AVAILABILITY
+This man page is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/fstrim.8 b/sys-utils/fstrim.8
new file mode 100644
index 0000000..52199eb
--- /dev/null
+++ b/sys-utils/fstrim.8
@@ -0,0 +1,146 @@
+.TH FSTRIM 8 "May 2019" "util-linux" "System Administration"
+.SH NAME
+fstrim \- discard unused blocks on a mounted filesystem
+.SH SYNOPSIS
+.B fstrim
+.RB [ \-Aa ]
+.RB [ \-o
+.IR offset ]
+.RB [ \-l
+.IR length ]
+.RB [ \-m
+.IR minimum-size ]
+.RB [ \-v ]
+.I mountpoint
+
+.SH DESCRIPTION
+.B fstrim
+is used on a mounted filesystem to discard (or "trim") blocks which are not in
+use by the filesystem. This is useful for solid-state drives (SSDs) and
+thinly-provisioned storage.
+.PP
+By default,
+.B fstrim
+will discard all unused blocks in the filesystem. Options may be used to
+modify this behavior based on range or size, as explained below.
+.PP
+The
+.I mountpoint
+argument is the pathname of the directory where the filesystem
+is mounted.
+.PP
+Running
+.B fstrim
+frequently, or even using
+.BR "mount \-o discard" ,
+might negatively affect the lifetime of poor-quality SSD devices. For most
+desktop and server systems a sufficient trimming frequency is once a week.
+Note that not all
+devices support a queued trim, so each trim command incurs a performance penalty
+on whatever else might be trying to use the disk at the time.
+
+.SH OPTIONS
+The \fIoffset\fR, \fIlength\fR, and \fIminimum-size\fR arguments may be
+followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB"
+is optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+
+.IP "\fB\-A, \-\-fstab\fP"
+Trim all mounted filesystems mentioned in \fI/etc/fstab\fR on devices that support the
+discard operation. The root filesystem is determined from kernel command line if missing
+in the file.
+The other supplied options, like \fB\-\-offset\fR, \fB\-\-length\fR and
+\fB-\-minimum\fR, are applied to all these devices.
+Errors from filesystems that do not support the discard operation,
+read-only devices and read-only filesystems are silently ignored.
+.IP "\fB\-a, \-\-all\fP"
+Trim all mounted filesystems on devices that support the discard operation.
+The other supplied options, like \fB\-\-offset\fR, \fB\-\-length\fR and
+\fB-\-minimum\fR, are applied to all these devices.
+Errors from filesystems that do not support the discard operation,
+read-only devices and read-only filesystems are silently ignored.
+.IP "\fB\-n, \-\-dry\-run\fP"
+This option does everything apart from actually call FITRIM ioctl.
+.IP "\fB\-o, \-\-offset\fP \fIoffset\fP"
+Byte offset in the filesystem from which to begin searching for free blocks
+to discard. The default value is zero, starting at the beginning of the
+filesystem.
+.IP "\fB\-l, \-\-length\fP \fIlength\fP"
+The number of bytes (after the starting point) to search for free blocks
+to discard. If the specified value extends past the end of the filesystem,
+.B fstrim
+will stop at the filesystem size boundary. The default value extends to
+the end of the filesystem.
+.IP "\fB\-I, \-\-listed\-in\fP \fIlist\fP"
+Specifies a colon-separated list of files in fstab or kernel mountinfo
+format. All missing or empty files are silently ignored. The evaluation of the
+\fIlist\fP stops after first non-empty file. For example: \fB--listed-in /etc/fstab:/proc/self/mountinfo\fR.
+.IP "\fB\-m, \-\-minimum\fP \fIminimum-size\fP"
+Minimum contiguous free range to discard, in bytes. (This value is internally
+rounded up to a multiple of the filesystem block size.) Free ranges smaller
+than this will be ignored and fstrim will adjust the minimum if it's smaller than
+the device's minimum, and report that (fstrim_range.minlen) back to userspace.
+By increasing this value, the fstrim operation will complete more quickly for
+filesystems with badly fragmented freespace, although not all blocks will be
+discarded. The default value is zero, discarding every free block.
+.IP "\fB\-v, \-\-verbose\fP"
+Verbose execution. With this option
+.B fstrim
+will output the number of bytes passed from the filesystem
+down the block stack to the device for potential discard. This number is a
+maximum discard amount from the storage device's perspective, because
+.I FITRIM
+ioctl called repeated will keep sending the same sectors for discard repeatedly.
+.sp
+.B fstrim
+will report the same potential discard bytes each time, but only sectors which
+had been written to between the discards would actually be discarded by the
+storage device. Further, the kernel block layer reserves the right to adjust
+the discard ranges to fit raid stripe geometry, non-trim capable devices in a
+LVM setup, etc. These reductions would not be reflected in fstrim_range.len
+(the
+.B \-\-length
+option).
+.TP
+.B \-\-quiet\-unsupported
+Suppress error messages if trim operation (ioctl) is unsupported. This option
+is meant to be used in systemd service file or in cron scripts to hide warnings
+that are result of known problems,
+such as NTFS driver
+reporting
+.I Bad file descriptor
+when device is mounted read-only, or lack of file system support for ioctl
+FITRIM call.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH EXIT STATUS
+.IP 0
+success
+.IP 1
+failure
+.IP 32
+all failed
+.IP 64
+some filesystem discards have succeeded, some failed
+.PP
+The command
+.B fstrim \-\-all
+returns 0 (all succeeded), 32 (all failed) or 64 (some failed, some succeeded).
+
+.SH AUTHORS
+.nf
+Lukas Czerner <lczerner@redhat.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR blkdiscard (8),
+.BR mount (8)
+.SH AVAILABILITY
+The fstrim command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/fstrim.c b/sys-utils/fstrim.c
new file mode 100644
index 0000000..fef33b0
--- /dev/null
+++ b/sys-utils/fstrim.c
@@ -0,0 +1,547 @@
+/*
+ * fstrim.c -- discard the part (or whole) of mounted filesystem.
+ *
+ * Copyright (C) 2010 Red Hat, Inc. All rights reserved.
+ * Written by Lukas Czerner <lczerner@redhat.com>
+ * Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * This program uses FITRIM ioctl to discard parts or the whole filesystem
+ * online (mounted). You can specify range (start and length) to be
+ * discarded, or simply discard whole filesystem.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <linux/fs.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "c.h"
+#include "closestream.h"
+#include "pathnames.h"
+#include "sysfs.h"
+#include "optutils.h"
+
+#include <libmount.h>
+
+
+#ifndef FITRIM
+struct fstrim_range {
+ uint64_t start;
+ uint64_t len;
+ uint64_t minlen;
+};
+#define FITRIM _IOWR('X', 121, struct fstrim_range)
+#endif
+
+struct fstrim_control {
+ struct fstrim_range range;
+
+ unsigned int verbose : 1,
+ quiet_unsupp : 1,
+ dryrun : 1;
+};
+
+static int is_directory(const char *path, int silent)
+{
+ struct stat sb;
+
+ if (stat(path, &sb) == -1) {
+ if (!silent)
+ warn(_("stat of %s failed"), path);
+ return 0;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ if (!silent)
+ warnx(_("%s: not a directory"), path);
+ return 0;
+ }
+ return 1;
+}
+
+/* returns: 0 = success, 1 = unsupported, < 0 = error */
+static int fstrim_filesystem(struct fstrim_control *ctl, const char *path, const char *devname)
+{
+ int fd = -1, rc;
+ struct fstrim_range range;
+ char *rpath = realpath(path, NULL);
+
+ if (!rpath) {
+ warn(_("cannot get realpath: %s"), path);
+ rc = -errno;
+ goto done;
+ }
+ /* kernel modifies the range */
+ memcpy(&range, &ctl->range, sizeof(range));
+
+ fd = open(rpath, O_RDONLY);
+ if (fd < 0) {
+ warn(_("cannot open %s"), path);
+ rc = -errno;
+ goto done;
+ }
+
+ if (ctl->dryrun) {
+ if (devname)
+ printf(_("%s: 0 B (dry run) trimmed on %s\n"), path, devname);
+ else
+ printf(_("%s: 0 B (dry run) trimmed\n"), path);
+ rc = 0;
+ goto done;
+ }
+
+ errno = 0;
+ if (ioctl(fd, FITRIM, &range)) {
+ switch (errno) {
+ case EBADF:
+ case ENOTTY:
+ case EOPNOTSUPP:
+ rc = 1;
+ break;
+ default:
+ rc = -errno;
+ }
+ if (rc < 0)
+ warn(_("%s: FITRIM ioctl failed"), path);
+ goto done;
+ }
+
+ if (ctl->verbose) {
+ char *str = size_to_human_string(
+ SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+ (uint64_t) range.len);
+ if (devname)
+ /* TRANSLATORS: The standard value here is a very large number. */
+ printf(_("%s: %s (%" PRIu64 " bytes) trimmed on %s\n"),
+ path, str, (uint64_t) range.len, devname);
+ else
+ /* TRANSLATORS: The standard value here is a very large number. */
+ printf(_("%s: %s (%" PRIu64 " bytes) trimmed\n"),
+ path, str, (uint64_t) range.len);
+
+ free(str);
+ }
+
+ rc = 0;
+done:
+ if (fd >= 0)
+ close(fd);
+ free(rpath);
+ return rc;
+}
+
+static int has_discard(const char *devname, struct path_cxt **wholedisk)
+{
+ struct path_cxt *pc = NULL;
+ uint64_t dg = 0;
+ dev_t disk = 0, dev;
+ int rc = -1, rdonly = 0;
+
+ dev = sysfs_devname_to_devno(devname);
+ if (!dev)
+ goto fail;
+
+ pc = ul_new_sysfs_path(dev, NULL, NULL);
+ if (!pc)
+ goto fail;
+
+ /*
+ * This is tricky to read the info from sys/, because the queue
+ * attributes are provided for whole devices (disk) only. We're trying
+ * to reuse the whole-disk sysfs context to optimize this stuff (as
+ * system usually have just one disk only).
+ */
+ rc = sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk);
+ if (rc != 0 || !disk)
+ goto fail;
+
+ if (dev != disk) {
+ /* Partition, try reuse whole-disk context if valid for the
+ * current device, otherwise create new context for the
+ * whole-disk.
+ */
+ if (*wholedisk && sysfs_blkdev_get_devno(*wholedisk) != disk) {
+ ul_unref_path(*wholedisk);
+ *wholedisk = NULL;
+ }
+ if (!*wholedisk) {
+ *wholedisk = ul_new_sysfs_path(disk, NULL, NULL);
+ if (!*wholedisk)
+ goto fail;
+ }
+ sysfs_blkdev_set_parent(pc, *wholedisk);
+ }
+
+ rc = ul_path_read_u64(pc, &dg, "queue/discard_granularity");
+ if (!rc)
+ ul_path_scanf(pc, "ro", "%d", &rdonly);
+
+ ul_unref_path(pc);
+ return rc == 0 && dg > 0 && rdonly == 0;
+fail:
+ ul_unref_path(pc);
+ return 1;
+}
+
+
+static int uniq_fs_target_cmp(
+ struct libmnt_table *tb __attribute__((__unused__)),
+ struct libmnt_fs *a,
+ struct libmnt_fs *b)
+{
+ return !mnt_fs_streq_target(a, mnt_fs_get_target(b));
+}
+
+static int uniq_fs_source_cmp(
+ struct libmnt_table *tb __attribute__((__unused__)),
+ struct libmnt_fs *a,
+ struct libmnt_fs *b)
+{
+ if (mnt_fs_is_pseudofs(a) || mnt_fs_is_netfs(a) ||
+ mnt_fs_is_pseudofs(b) || mnt_fs_is_netfs(b))
+ return 1;
+
+ return !mnt_fs_streq_srcpath(a, mnt_fs_get_srcpath(b));
+}
+
+/*
+ * -1 = tab empty
+ * 0 = all success
+ * 32 = all failed
+ * 64 = some failed, some success
+ */
+static int fstrim_all_from_file(struct fstrim_control *ctl, const char *filename)
+{
+ struct libmnt_fs *fs;
+ struct libmnt_iter *itr;
+ struct libmnt_table *tab;
+ struct libmnt_cache *cache = NULL;
+ struct path_cxt *wholedisk = NULL;
+ int cnt = 0, cnt_err = 0;
+ int fstab = 0;
+
+ tab = mnt_new_table_from_file(filename);
+ if (!tab)
+ err(MNT_EX_FAIL, _("failed to parse %s"), filename);
+
+ if (mnt_table_is_empty(tab)) {
+ mnt_unref_table(tab);
+ return -1;
+ }
+
+ if (streq_paths(filename, "/etc/fstab"))
+ fstab = 1;
+
+ /* de-duplicate by mountpoints */
+ mnt_table_uniq_fs(tab, 0, uniq_fs_target_cmp);
+
+ if (fstab) {
+ char *rootdev = NULL;
+
+ cache = mnt_new_cache();
+ if (!cache)
+ err(MNT_EX_FAIL, _("failed to initialize libmount cache"));
+
+ /* Make sure we trim also root FS on fstab */
+ if (mnt_table_find_target(tab, "/", MNT_ITER_FORWARD) == NULL &&
+ mnt_guess_system_root(0, cache, &rootdev) == 0) {
+
+ fs = mnt_new_fs();
+ if (!fs)
+ err(MNT_EX_FAIL, _("failed to allocate FS handler"));
+ mnt_fs_set_target(fs, "/");
+ mnt_fs_set_source(fs, rootdev);
+ mnt_fs_set_fstype(fs, "auto");
+ mnt_table_add_fs(tab, fs);
+ mnt_unref_fs(fs);
+ fs = NULL;
+ }
+ }
+
+ itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ if (!itr)
+ err(MNT_EX_FAIL, _("failed to initialize libmount iterator"));
+
+ /* Remove useless entries and canonicalize the table */
+ while (mnt_table_next_fs(tab, itr, &fs) == 0) {
+ const char *src = mnt_fs_get_srcpath(fs),
+ *tgt = mnt_fs_get_target(fs);
+
+ if (!tgt || mnt_fs_is_pseudofs(fs) || mnt_fs_is_netfs(fs)) {
+ mnt_table_remove_fs(tab, fs);
+ continue;
+ }
+
+ /* convert LABEL= (etc.) from fstab to paths */
+ if (!src && cache) {
+ const char *spec = mnt_fs_get_source(fs);
+
+ if (!spec) {
+ mnt_table_remove_fs(tab, fs);
+ continue;
+ }
+ src = mnt_resolve_spec(spec, cache);
+ mnt_fs_set_source(fs, src);
+ }
+
+ if (!src || *src != '/') {
+ mnt_table_remove_fs(tab, fs);
+ continue;
+ }
+ }
+
+ /* de-duplicate by source */
+ mnt_table_uniq_fs(tab, MNT_UNIQ_FORWARD, uniq_fs_source_cmp);
+
+ mnt_reset_iter(itr, MNT_ITER_BACKWARD);
+
+ /* Do FITRIM */
+ while (mnt_table_next_fs(tab, itr, &fs) == 0) {
+ const char *src = mnt_fs_get_srcpath(fs),
+ *tgt = mnt_fs_get_target(fs);
+ char *path;
+ int rc = 1;
+
+ /* Is it really accessible mountpoint? Not all mountpoints are
+ * accessible (maybe over mounted by another filesystem) */
+ path = mnt_get_mountpoint(tgt);
+ if (path && strcmp(path, tgt) == 0)
+ rc = 0;
+ free(path);
+ if (rc)
+ continue; /* overlaying mount */
+
+ /* FITRIM on read-only filesystem can fail, and it can fail */
+ if (access(tgt, W_OK) != 0) {
+ if (errno == EROFS)
+ continue;
+ if (errno == EACCES)
+ continue;
+ }
+
+ if (!is_directory(tgt, 1) ||
+ !has_discard(src, &wholedisk))
+ continue;
+ cnt++;
+
+ /*
+ * We're able to detect that the device supports discard, but
+ * things also depend on filesystem or device mapping, for
+ * example LUKS (by default) does not support FSTRIM.
+ *
+ * This is reason why we ignore EOPNOTSUPP and ENOTTY errors
+ * from discard ioctl.
+ */
+ rc = fstrim_filesystem(ctl, tgt, src);
+ if (rc < 0)
+ cnt_err++;
+ else if (rc == 1 && !ctl->quiet_unsupp)
+ warnx(_("%s: the discard operation is not supported"), tgt);
+ }
+ mnt_free_iter(itr);
+
+ ul_unref_path(wholedisk);
+ mnt_unref_table(tab);
+ mnt_unref_cache(cache);
+
+ if (cnt && cnt == cnt_err)
+ return MNT_EX_FAIL; /* all failed */
+ if (cnt && cnt_err)
+ return MNT_EX_SOMEOK; /* some ok */
+
+ return MNT_EX_SUCCESS;
+}
+
+/*
+ * fstrim --all follows "mount -a" return codes:
+ *
+ * 0 = all success
+ * 32 = all failed
+ * 64 = some failed, some success
+ */
+static int fstrim_all(struct fstrim_control *ctl, const char *tabs)
+{
+ char *list = xstrdup(tabs);
+ char *file;
+ int rc = MNT_EX_FAIL;
+
+ mnt_init_debug(0);
+ ul_path_init_debug();
+
+ for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) {
+ struct stat st;
+
+ if (stat(file, &st) < 0 || !S_ISREG(st.st_mode))
+ continue;
+
+ rc = fstrim_all_from_file(ctl, file);
+ if (rc >= 0)
+ break; /* stop after first non-empty file */
+ }
+ free(list);
+ return rc;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] <mount point>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Discard unused blocks on a mounted filesystem.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all trim mounted filesystems\n"), out);
+ fputs(_(" -A, --fstab trim filesystems from /etc/fstab\n"), out);
+ fputs(_(" -I, --listed-in <list> trim filesystems listed in specified files\n"), out);
+ fputs(_(" -o, --offset <num> the offset in bytes to start discarding from\n"), out);
+ fputs(_(" -l, --length <num> the number of bytes to discard\n"), out);
+ fputs(_(" -m, --minimum <num> the minimum extent length to discard\n"), out);
+ fputs(_(" -v, --verbose print number of discarded bytes\n"), out);
+ fputs(_(" --quiet-unsupported suppress error messages if trim unsupported\n"), out);
+ fputs(_(" -n, --dry-run does everything, but trim\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(21));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<num>")));
+
+ printf(USAGE_MAN_TAIL("fstrim(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ char *path = NULL;
+ char *tabs = NULL;
+ int c, rc, all = 0;
+ struct fstrim_control ctl = {
+ .range = { .len = ULLONG_MAX }
+ };
+ enum {
+ OPT_QUIET_UNSUPP = CHAR_MAX + 1
+ };
+
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "fstab", no_argument, NULL, 'A' },
+ { "help", no_argument, NULL, 'h' },
+ { "listed-in", required_argument, NULL, 'I' },
+ { "version", no_argument, NULL, 'V' },
+ { "offset", required_argument, NULL, 'o' },
+ { "length", required_argument, NULL, 'l' },
+ { "minimum", required_argument, NULL, 'm' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "quiet-unsupported", no_argument, NULL, OPT_QUIET_UNSUPP },
+ { "dry-run", no_argument, NULL, 'n' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'A','I','a' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "AahI:l:m:no:Vv", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'A':
+ all = 1;
+ tabs = _PATH_MNTTAB; /* fstab */
+ break;
+ case 'a':
+ all = 1;
+ tabs = _PATH_PROC_MOUNTINFO; /* mountinfo */
+ break;
+ case 'I':
+ all = 1;
+ tabs = optarg;
+ break;
+ case 'n':
+ ctl.dryrun = 1;
+ break;
+ case 'l':
+ ctl.range.len = strtosize_or_err(optarg,
+ _("failed to parse length"));
+ break;
+ case 'o':
+ ctl.range.start = strtosize_or_err(optarg,
+ _("failed to parse offset"));
+ break;
+ case 'm':
+ ctl.range.minlen = strtosize_or_err(optarg,
+ _("failed to parse minimum extent length"));
+ break;
+ case 'v':
+ ctl.verbose = 1;
+ break;
+ case OPT_QUIET_UNSUPP:
+ ctl.quiet_unsupp = 1;
+ break;
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (!all) {
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no mountpoint specified"));
+ path = argv[optind++];
+ }
+
+ if (optind != argc) {
+ warnx(_("unexpected number of arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (all)
+ return fstrim_all(&ctl, tabs); /* MNT_EX_* codes */
+
+ if (!is_directory(path, 0))
+ return EXIT_FAILURE;
+
+ rc = fstrim_filesystem(&ctl, path, NULL);
+ if (rc == 1 && !ctl.quiet_unsupp)
+ warnx(_("%s: the discard operation is not supported"), path);
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/sys-utils/fstrim.service.in b/sys-utils/fstrim.service.in
new file mode 100644
index 0000000..11e6e95
--- /dev/null
+++ b/sys-utils/fstrim.service.in
@@ -0,0 +1,16 @@
+[Unit]
+Description=Discard unused blocks on filesystems from /etc/fstab
+Documentation=man:fstrim(8)
+ConditionVirtualization=!container
+
+[Service]
+Type=oneshot
+ExecStart=@sbindir@/fstrim --listed-in /etc/fstab:/proc/self/mountinfo --verbose --quiet-unsupported
+PrivateDevices=no
+PrivateNetwork=yes
+PrivateUsers=no
+ProtectKernelTunables=yes
+ProtectKernelModules=yes
+ProtectControlGroups=yes
+MemoryDenyWriteExecute=yes
+SystemCallFilter=@default @file-system @basic-io @system-service
diff --git a/sys-utils/fstrim.timer b/sys-utils/fstrim.timer
new file mode 100644
index 0000000..54b3c18
--- /dev/null
+++ b/sys-utils/fstrim.timer
@@ -0,0 +1,13 @@
+[Unit]
+Description=Discard unused blocks once a week
+Documentation=man:fstrim
+ConditionVirtualization=!container
+
+[Timer]
+OnCalendar=weekly
+AccuracySec=1h
+Persistent=true
+RandomizedDelaySec=6000
+
+[Install]
+WantedBy=timers.target
diff --git a/sys-utils/hwclock-cmos.c b/sys-utils/hwclock-cmos.c
new file mode 100644
index 0000000..56ee624
--- /dev/null
+++ b/sys-utils/hwclock-cmos.c
@@ -0,0 +1,387 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * i386 CMOS starts out with 14 bytes clock data alpha has something
+ * similar, but with details depending on the machine type.
+ *
+ * byte 0: seconds 0-59
+ * byte 2: minutes 0-59
+ * byte 4: hours 0-23 in 24hr mode,
+ * 1-12 in 12hr mode, with high bit unset/set
+ * if am/pm.
+ * byte 6: weekday 1-7, Sunday=1
+ * byte 7: day of the month 1-31
+ * byte 8: month 1-12
+ * byte 9: year 0-99
+ *
+ * Numbers are stored in BCD/binary if bit 2 of byte 11 is unset/set The
+ * clock is in 12hr/24hr mode if bit 1 of byte 11 is unset/set The clock is
+ * undefined (being updated) if bit 7 of byte 10 is set. The clock is frozen
+ * (to be updated) by setting bit 7 of byte 11 Bit 7 of byte 14 indicates
+ * whether the CMOS clock is reliable: it is 1 if RTC power has been good
+ * since this bit was last read; it is 0 when the battery is dead and system
+ * power has been off.
+ *
+ * Avoid setting the RTC clock within 2 seconds of the day rollover that
+ * starts a new month or enters daylight saving time.
+ *
+ * The century situation is messy:
+ *
+ * Usually byte 50 (0x32) gives the century (in BCD, so 19 or 20 hex), but
+ * IBM PS/2 has (part of) a checksum there and uses byte 55 (0x37).
+ * Sometimes byte 127 (0x7f) or Bank 1, byte 0x48 gives the century. The
+ * original RTC will not access any century byte; some modern versions will.
+ * If a modern RTC or BIOS increments the century byte it may go from 0x19
+ * to 0x20, but in some buggy cases 0x1a is produced.
+ */
+/*
+ * A struct tm has int fields
+ * tm_sec 0-59, 60 or 61 only for leap seconds
+ * tm_min 0-59
+ * tm_hour 0-23
+ * tm_mday 1-31
+ * tm_mon 0-11
+ * tm_year number of years since 1900
+ * tm_wday 0-6, 0=Sunday
+ * tm_yday 0-365
+ * tm_isdst >0: yes, 0: no, <0: unknown
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "nls.h"
+#include "pathnames.h"
+
+/* for inb, outb */
+#ifdef HAVE_SYS_IO_H
+# include <sys/io.h>
+#elif defined(HAVE_ASM_IO_H)
+# include <asm/io.h>
+#else
+# error "no sys/io.h or asm/io.h"
+#endif /* HAVE_SYS_IO_H, HAVE_ASM_IO_H */
+
+#include "hwclock.h"
+
+#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
+#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10)
+
+#define IOPL_NOT_IMPLEMENTED -2
+
+/*
+ * POSIX uses 1900 as epoch for a struct tm, and 1970 for a time_t.
+ */
+#define TM_EPOCH 1900
+
+static unsigned short clock_ctl_addr = 0x70;
+static unsigned short clock_data_addr = 0x71;
+
+/*
+ * Hmmh, this isn't very atomic. Maybe we should force an error instead?
+ *
+ * TODO: optimize the access to CMOS by mlockall(MCL_CURRENT) and SCHED_FIFO
+ */
+static unsigned long atomic(unsigned long (*op) (unsigned long),
+ unsigned long arg)
+{
+ return (*op) (arg);
+}
+
+/*
+ * We only want to read CMOS data, but unfortunately writing to bit 7
+ * disables (1) or enables (0) NMI; since this bit is read-only we have
+ * to guess the old status. Various docs suggest that one should disable
+ * NMI while reading/writing CMOS data, and enable it again afterwards.
+ * This would yield the sequence
+ *
+ * outb (reg | 0x80, 0x70);
+ * val = inb(0x71);
+ * outb (0x0d, 0x70); // 0x0d: random read-only location
+ *
+ * Other docs state that "any write to 0x70 should be followed by an
+ * action to 0x71 or the RTC will be left in an unknown state". Most
+ * docs say that it doesn't matter at all what one does.
+ *
+ * bit 0x80: disable NMI while reading - should we? Let us follow the
+ * kernel and not disable. Called only with 0 <= reg < 128
+ */
+
+static inline unsigned long cmos_read(unsigned long reg)
+{
+ outb(reg, clock_ctl_addr);
+ return inb(clock_data_addr);
+}
+
+static inline unsigned long cmos_write(unsigned long reg, unsigned long val)
+{
+ outb(reg, clock_ctl_addr);
+ outb(val, clock_data_addr);
+ return 0;
+}
+
+static unsigned long cmos_set_time(unsigned long arg)
+{
+ unsigned char save_control, save_freq_select, pmbit = 0;
+ struct tm tm = *(struct tm *)arg;
+
+/*
+ * CMOS byte 10 (clock status register A) has 3 bitfields:
+ * bit 7: 1 if data invalid, update in progress (read-only bit)
+ * (this is raised 224 us before the actual update starts)
+ * 6-4 select base frequency
+ * 010: 32768 Hz time base (default)
+ * 111: reset
+ * all other combinations are manufacturer-dependent
+ * (e.g.: DS1287: 010 = start oscillator, anything else = stop)
+ * 3-0 rate selection bits for interrupt
+ * 0000 none (may stop RTC)
+ * 0001, 0010 give same frequency as 1000, 1001
+ * 0011 122 microseconds (minimum, 8192 Hz)
+ * .... each increase by 1 halves the frequency, doubles the period
+ * 1111 500 milliseconds (maximum, 2 Hz)
+ * 0110 976.562 microseconds (default 1024 Hz)
+ */
+ save_control = cmos_read(11); /* tell the clock it's being set */
+ cmos_write(11, (save_control | 0x80));
+ save_freq_select = cmos_read(10); /* stop and reset prescaler */
+ cmos_write(10, (save_freq_select | 0x70));
+
+ tm.tm_year %= 100;
+ tm.tm_mon += 1;
+ tm.tm_wday += 1;
+
+ if (!(save_control & 0x02)) { /* 12hr mode; the default is 24hr mode */
+ if (tm.tm_hour == 0)
+ tm.tm_hour = 24;
+ if (tm.tm_hour > 12) {
+ tm.tm_hour -= 12;
+ pmbit = 0x80;
+ }
+ }
+
+ if (!(save_control & 0x04)) { /* BCD mode - the default */
+ BIN_TO_BCD(tm.tm_sec);
+ BIN_TO_BCD(tm.tm_min);
+ BIN_TO_BCD(tm.tm_hour);
+ BIN_TO_BCD(tm.tm_wday);
+ BIN_TO_BCD(tm.tm_mday);
+ BIN_TO_BCD(tm.tm_mon);
+ BIN_TO_BCD(tm.tm_year);
+ }
+
+ cmos_write(0, tm.tm_sec);
+ cmos_write(2, tm.tm_min);
+ cmos_write(4, tm.tm_hour | pmbit);
+ cmos_write(6, tm.tm_wday);
+ cmos_write(7, tm.tm_mday);
+ cmos_write(8, tm.tm_mon);
+ cmos_write(9, tm.tm_year);
+
+ /*
+ * The kernel sources, linux/arch/i386/kernel/time.c, have the
+ * following comment:
+ *
+ * The following flags have to be released exactly in this order,
+ * otherwise the DS12887 (popular MC146818A clone with integrated
+ * battery and quartz) will not reset the oscillator and will not
+ * update precisely 500 ms later. You won't find this mentioned in
+ * the Dallas Semiconductor data sheets, but who believes data
+ * sheets anyway ... -- Markus Kuhn
+ */
+ cmos_write(11, save_control);
+ cmos_write(10, save_freq_select);
+ return 0;
+}
+
+static int hclock_read(unsigned long reg)
+{
+ return atomic(cmos_read, reg);
+}
+
+static void hclock_set_time(const struct tm *tm)
+{
+ atomic(cmos_set_time, (unsigned long)(tm));
+}
+
+static inline int cmos_clock_busy(void)
+{
+ return
+ /* poll bit 7 (UIP) of Control Register A */
+ (hclock_read(10) & 0x80);
+}
+
+static int synchronize_to_clock_tick_cmos(const struct hwclock_control *ctl
+ __attribute__((__unused__)))
+{
+ int i;
+
+ /*
+ * Wait for rise. Should be within a second, but in case something
+ * weird happens, we have a limit on this loop to reduce the impact
+ * of this failure.
+ */
+ for (i = 0; !cmos_clock_busy(); i++)
+ if (i >= 10000000)
+ return 1;
+
+ /* Wait for fall. Should be within 2.228 ms. */
+ for (i = 0; cmos_clock_busy(); i++)
+ if (i >= 1000000)
+ return 1;
+ return 0;
+}
+
+/*
+ * Read the hardware clock and return the current time via <tm> argument.
+ * Assume we have an ISA machine and read the clock directly with CPU I/O
+ * instructions.
+ *
+ * This function is not totally reliable. It takes a finite and
+ * unpredictable amount of time to execute the code below. During that time,
+ * the clock may change and we may even read an invalid value in the middle
+ * of an update. We do a few checks to minimize this possibility, but only
+ * the kernel can actually read the clock properly, since it can execute
+ * code in a short and predictable amount of time (by turning of
+ * interrupts).
+ *
+ * In practice, the chance of this function returning the wrong time is
+ * extremely remote.
+ */
+static int read_hardware_clock_cmos(const struct hwclock_control *ctl
+ __attribute__((__unused__)), struct tm *tm)
+{
+ unsigned char status = 0, pmbit = 0;
+
+ while (1) {
+ /*
+ * Bit 7 of Byte 10 of the Hardware Clock value is the
+ * Update In Progress (UIP) bit, which is on while and 244
+ * uS before the Hardware Clock updates itself. It updates
+ * the counters individually, so reading them during an
+ * update would produce garbage. The update takes 2mS, so we
+ * could be spinning here that long waiting for this bit to
+ * turn off.
+ *
+ * Furthermore, it is pathologically possible for us to be
+ * in this code so long that even if the UIP bit is not on
+ * at first, the clock has changed while we were running. We
+ * check for that too, and if it happens, we start over.
+ */
+ if (!cmos_clock_busy()) {
+ /* No clock update in progress, go ahead and read */
+ tm->tm_sec = hclock_read(0);
+ tm->tm_min = hclock_read(2);
+ tm->tm_hour = hclock_read(4);
+ tm->tm_wday = hclock_read(6);
+ tm->tm_mday = hclock_read(7);
+ tm->tm_mon = hclock_read(8);
+ tm->tm_year = hclock_read(9);
+ status = hclock_read(11);
+ /*
+ * Unless the clock changed while we were reading,
+ * consider this a good clock read .
+ */
+ if (tm->tm_sec == hclock_read(0))
+ break;
+ }
+ /*
+ * Yes, in theory we could have been running for 60 seconds
+ * and the above test wouldn't work!
+ */
+ }
+
+ if (!(status & 0x04)) { /* BCD mode - the default */
+ BCD_TO_BIN(tm->tm_sec);
+ BCD_TO_BIN(tm->tm_min);
+ pmbit = (tm->tm_hour & 0x80);
+ tm->tm_hour &= 0x7f;
+ BCD_TO_BIN(tm->tm_hour);
+ BCD_TO_BIN(tm->tm_wday);
+ BCD_TO_BIN(tm->tm_mday);
+ BCD_TO_BIN(tm->tm_mon);
+ BCD_TO_BIN(tm->tm_year);
+ }
+
+ /*
+ * We don't use the century byte of the Hardware Clock since we
+ * don't know its address (usually 50 or 55). Here, we follow the
+ * advice of the X/Open Base Working Group: "if century is not
+ * specified, then values in the range [69-99] refer to years in the
+ * twentieth century (1969 to 1999 inclusive), and values in the
+ * range [00-68] refer to years in the twenty-first century (2000 to
+ * 2068 inclusive)."
+ */
+ tm->tm_wday -= 1;
+ tm->tm_mon -= 1;
+ if (tm->tm_year < 69)
+ tm->tm_year += 100;
+ if (pmbit) {
+ tm->tm_hour += 12;
+ if (tm->tm_hour == 24)
+ tm->tm_hour = 0;
+ }
+
+ tm->tm_isdst = -1; /* don't know whether it's daylight */
+ return 0;
+}
+
+static int set_hardware_clock_cmos(const struct hwclock_control *ctl
+ __attribute__((__unused__)),
+ const struct tm *new_broken_time)
+{
+ hclock_set_time(new_broken_time);
+ return 0;
+}
+
+# if defined(HAVE_IOPL)
+static int i386_iopl(const int level)
+{
+ return iopl(level);
+}
+# else
+static int i386_iopl(const int level __attribute__ ((__unused__)))
+{
+ extern int ioperm(unsigned long from, unsigned long num, int turn_on);
+ return ioperm(clock_ctl_addr, 2, 1);
+}
+# endif
+
+static int get_permissions_cmos(void)
+{
+ int rc;
+
+ rc = i386_iopl(3);
+ if (rc == IOPL_NOT_IMPLEMENTED) {
+ warnx(_("ISA port access is not implemented"));
+ } else if (rc != 0) {
+ warn(_("iopl() port access failed"));
+ }
+ return rc;
+}
+
+static const char *get_device_path(void)
+{
+ return NULL;
+}
+
+static struct clock_ops cmos_interface = {
+ N_("Using direct ISA access to the clock"),
+ get_permissions_cmos,
+ read_hardware_clock_cmos,
+ set_hardware_clock_cmos,
+ synchronize_to_clock_tick_cmos,
+ get_device_path,
+};
+
+/*
+ * return &cmos if cmos clock present, NULL otherwise.
+ */
+struct clock_ops *probe_for_cmos_clock(void)
+{
+ return &cmos_interface;
+}
diff --git a/sys-utils/hwclock-parse-date.c b/sys-utils/hwclock-parse-date.c
new file mode 100644
index 0000000..76843d3
--- /dev/null
+++ b/sys-utils/hwclock-parse-date.c
@@ -0,0 +1,3382 @@
+/* A Bison parser, made by GNU Bison 3.4.1. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Undocumented macros, especially those whose name start with YY_,
+ are private implementation details. Do not rely on them. */
+
+/* Identify Bison output. */
+#define YYBISON 1
+
+/* Bison version. */
+#define YYBISON_VERSION "3.4.1"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 1
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 1 "sys-utils/hwclock-parse-date.y"
+
+/**
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Parse a string into an internal timestamp.
+ *
+ * This file is based on gnulib parse-datetime.y-dd7a871 with
+ * the other gnulib dependencies removed for use in util-linux.
+ *
+ * Copyright (C) 1999-2000, 2002-2017 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ *
+ * Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ * the right thing about local DST. Also modified by Paul Eggert
+ * <eggert@cs.ucla.edu> in February 2004 to support
+ * nanosecond-resolution timestamps, and in October 2004 to support
+ * TZ strings in dates.
+ */
+
+/**
+ * FIXME: Check for arithmetic overflow in all cases, not just
+ * some of them.
+ */
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "timeutils.h"
+#include "hwclock.h"
+
+/**
+ * There's no need to extend the stack, so there's no need to involve
+ * alloca.
+ */
+#define YYSTACK_USE_ALLOCA 0
+
+/**
+ * Tell Bison how much stack space is needed. 20 should be plenty for
+ * this grammar, which is not right recursive. Beware setting it too
+ * high, since that might cause problems on machines whose
+ * implementations have lame stack-overflow checking.
+ */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+/**
+ * Since the code of parse-datetime.y is not included in the Emacs executable
+ * itself, there is no need to #define static in this file. Even if
+ * the code were included in the Emacs executable, it probably
+ * wouldn't do any harm to #undef it here; this will only cause
+ * problems if we try to write to a static variable, which I don't
+ * think this code needs to do.
+ */
+#ifdef emacs
+# undef static
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <stdarg.h>
+#include "cctype.h"
+#include "nls.h"
+
+/**
+ * Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
+ * use _STDLIB_H_ as witness. Map the latter to the one bison uses.
+ * FIXME: this is temporary. Remove when we have a mechanism to ensure
+ * that the version we're using is fixed, too.
+ */
+#ifdef _STDLIB_H_
+# undef _STDLIB_H
+# define _STDLIB_H 1
+#endif
+
+/**
+ * Shift A right by B bits portably, by dividing A by 2**B and
+ * truncating towards minus infinity. A and B should be free of side
+ * effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ * INT_BITS is the number of useful bits in an int. GNU code can
+ * assume that INT_BITS is at least 32.
+ *
+ * ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ * implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ * right in the usual way when A < 0, so SHR falls back on division if
+ * ordinary A >> B doesn't seem to be the usual signed shift.
+ */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+/**
+ * Convert a possibly-signed character to an unsigned character. This is
+ * a bit safer than casting to unsigned char, since it catches some type
+ * errors that the cast doesn't.
+ */
+static unsigned char to_uchar (char ch) { return ch; }
+
+/**
+ * FIXME: It also assumes that signed integer overflow silently wraps around,
+ * but this is not true any more with recent versions of GCC 4.
+ */
+
+/**
+ * An integer value, and the number of digits in its textual
+ * representation.
+ */
+typedef struct {
+ int negative;
+ intmax_t value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct {
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+typedef struct {
+ intmax_t year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ time_t seconds;
+ int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
+/* Information passed to and from the parser. */
+typedef struct {
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ intmax_t day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and ns. */
+ textint year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and ns. */
+ relative_time rel;
+
+ /* Presence or counts of some nonterminals parsed so far. */
+ int timespec_seen;
+ int rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbreviations, null terminated. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static int time_zone_hhmm (parser_control *, textint, textint);
+
+/**
+ * Extract into *PC any date and time info from a string of digits
+ * of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ * YYYY, ...).
+ */
+static void digits_to_date_time(parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits)) {
+ pc->year = text_int;
+ } else {
+ if (4 < text_int.digits) {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ } else {
+ pc->times_seen++;
+ if (text_int.digits <= 2) {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void apply_relative_time(parser_control *pc, relative_time rel,
+ int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = 1;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss(parser_control *pc, intmax_t hour, intmax_t minutes,
+ time_t sec, int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+
+#line 352 "sys-utils/hwclock-parse-date.c"
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token type. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ tAGO = 258,
+ tDST = 259,
+ tYEAR_UNIT = 260,
+ tMONTH_UNIT = 261,
+ tHOUR_UNIT = 262,
+ tMINUTE_UNIT = 263,
+ tSEC_UNIT = 264,
+ tDAY_UNIT = 265,
+ tDAY_SHIFT = 266,
+ tDAY = 267,
+ tDAYZONE = 268,
+ tLOCAL_ZONE = 269,
+ tMERIDIAN = 270,
+ tMONTH = 271,
+ tORDINAL = 272,
+ tZONE = 273,
+ tSNUMBER = 274,
+ tUNUMBER = 275,
+ tSDECIMAL_NUMBER = 276,
+ tUDECIMAL_NUMBER = 277
+ };
+#endif
+/* Tokens. */
+#define tAGO 258
+#define tDST 259
+#define tYEAR_UNIT 260
+#define tMONTH_UNIT 261
+#define tHOUR_UNIT 262
+#define tMINUTE_UNIT 263
+#define tSEC_UNIT 264
+#define tDAY_UNIT 265
+#define tDAY_SHIFT 266
+#define tDAY 267
+#define tDAYZONE 268
+#define tLOCAL_ZONE 269
+#define tMERIDIAN 270
+#define tMONTH 271
+#define tORDINAL 272
+#define tZONE 273
+#define tSNUMBER 274
+#define tUNUMBER 275
+#define tSDECIMAL_NUMBER 276
+#define tUDECIMAL_NUMBER 277
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 294 "sys-utils/hwclock-parse-date.y"
+
+ intmax_t intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+
+#line 443 "sys-utils/hwclock-parse-date.c"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+
+int yyparse (parser_control *pc);
+
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE
+# if (defined __GNUC__ \
+ && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \
+ || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
+# define YY_ATTRIBUTE(Spec) __attribute__(Spec)
+# else
+# define YY_ATTRIBUTE(Spec) /* empty */
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_PURE
+# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__))
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 12
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 112
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 28
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 26
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 91
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 114
+
+#define YYUNDEFTOK 2
+#define YYMAXUTOK 277
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ ((unsigned) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_uint8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 26, 2, 2, 27, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 25, 2,
+ 2, 2, 2, 2, 23, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 24, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_uint16 yyrline[] =
+{
+ 0, 321, 321, 322, 326, 332, 334, 338, 341, 344,
+ 347, 350, 353, 356, 357, 358, 362, 366, 370, 374,
+ 378, 382, 386, 390, 394, 400, 402, 406, 431, 435,
+ 446, 449, 452, 456, 460, 464, 467, 473, 477, 481,
+ 485, 492, 496, 514, 521, 528, 532, 537, 541, 546,
+ 550, 559, 561, 563, 568, 570, 572, 574, 576, 578,
+ 580, 582, 584, 586, 588, 590, 592, 594, 596, 598,
+ 600, 602, 607, 612, 614, 618, 620, 622, 624, 626,
+ 628, 633, 637, 637, 640, 641, 646, 647, 652, 657,
+ 669, 670
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "$end", "error", "$undefined", "tAGO", "tDST", "tYEAR_UNIT",
+ "tMONTH_UNIT", "tHOUR_UNIT", "tMINUTE_UNIT", "tSEC_UNIT", "tDAY_UNIT",
+ "tDAY_SHIFT", "tDAY", "tDAYZONE", "tLOCAL_ZONE", "tMERIDIAN", "tMONTH",
+ "tORDINAL", "tZONE", "tSNUMBER", "tUNUMBER", "tSDECIMAL_NUMBER",
+ "tUDECIMAL_NUMBER", "'@'", "'T'", "':'", "','", "'/'", "$accept", "spec",
+ "timespec", "items", "item", "datetime", "iso_8601_datetime", "time",
+ "iso_8601_time", "o_zone_offset", "zone_offset", "local_zone", "zone",
+ "day", "date", "iso_8601_date", "rel", "relunit", "relunit_snumber",
+ "dayshift", "seconds", "signed_seconds", "unsigned_seconds", "number",
+ "hybrid", "o_colon_minutes", YY_NULLPTR
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_uint16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 64, 84, 58, 44, 47
+};
+# endif
+
+#define YYPACT_NINF -93
+
+#define yypact_value_is_default(Yystate) \
+ (!!((Yystate) == (-93)))
+
+#define YYTABLE_NINF -1
+
+#define yytable_value_is_error(Yytable_value) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ 38, 27, 77, -93, 46, -93, -93, -93, -93, -93,
+ -93, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ 62, -93, 82, -3, 66, 3, 74, -4, 83, 84,
+ 75, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ 71, -93, 93, -93, -93, -93, -93, -93, -93, 78,
+ 72, -93, -93, -93, -93, -93, -93, -93, -93, 25,
+ -93, -93, -93, -93, -93, -93, -93, -93, -93, -93,
+ -93, -93, -93, -93, -93, 21, 19, 79, 80, -93,
+ -93, -93, -93, -93, 81, -93, -93, 85, 86, -93,
+ -93, -93, -93, -93, -6, 76, 17, -93, -93, -93,
+ -93, 87, 69, -93, -93, 88, 89, -1, -93, 18,
+ -93, -93, 69, 91
+};
+
+ /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_uint8 yydefact[] =
+{
+ 5, 0, 0, 2, 3, 85, 87, 84, 86, 4,
+ 82, 83, 1, 56, 59, 65, 68, 73, 62, 81,
+ 37, 35, 28, 0, 0, 30, 0, 88, 0, 0,
+ 31, 6, 7, 16, 8, 21, 9, 10, 12, 11,
+ 49, 13, 52, 74, 53, 14, 15, 38, 29, 0,
+ 45, 54, 57, 63, 66, 69, 60, 39, 36, 90,
+ 32, 75, 76, 78, 79, 80, 77, 55, 58, 64,
+ 67, 70, 61, 40, 18, 47, 90, 0, 0, 22,
+ 89, 71, 72, 33, 0, 51, 44, 0, 0, 34,
+ 43, 48, 50, 27, 25, 41, 0, 17, 46, 91,
+ 19, 90, 0, 23, 26, 0, 0, 25, 42, 25,
+ 20, 24, 0, 25
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -93, -93, -93, -93, -93, -93, -93, -93, 20, -68,
+ -27, -93, -93, -93, -93, -93, -93, -93, 60, -93,
+ -93, -93, -92, -93, -93, 43
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ -1, 2, 3, 4, 31, 32, 33, 34, 35, 103,
+ 104, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 9, 10, 11, 45, 46, 93
+};
+
+ /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 79, 67, 68, 69, 70, 71, 72, 58, 73, 100,
+ 107, 74, 75, 101, 110, 76, 49, 50, 101, 102,
+ 113, 77, 59, 78, 61, 62, 63, 64, 65, 66,
+ 61, 62, 63, 64, 65, 66, 101, 101, 92, 111,
+ 90, 91, 106, 112, 88, 111, 5, 6, 7, 8,
+ 88, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 1, 23, 24, 25, 26, 27, 28, 29, 79,
+ 30, 51, 52, 53, 54, 55, 56, 12, 57, 61,
+ 62, 63, 64, 65, 66, 60, 48, 80, 47, 6,
+ 83, 8, 81, 82, 26, 84, 85, 86, 87, 94,
+ 95, 96, 89, 105, 97, 98, 99, 0, 108, 109,
+ 101, 0, 88
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 27, 5, 6, 7, 8, 9, 10, 4, 12, 15,
+ 102, 15, 16, 19, 15, 19, 19, 20, 19, 25,
+ 112, 25, 19, 27, 5, 6, 7, 8, 9, 10,
+ 5, 6, 7, 8, 9, 10, 19, 19, 19, 107,
+ 19, 20, 25, 25, 25, 113, 19, 20, 21, 22,
+ 25, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 23, 16, 17, 18, 19, 20, 21, 22, 96,
+ 24, 5, 6, 7, 8, 9, 10, 0, 12, 5,
+ 6, 7, 8, 9, 10, 25, 4, 27, 26, 20,
+ 30, 22, 9, 9, 19, 24, 3, 19, 26, 20,
+ 20, 20, 59, 27, 84, 20, 20, -1, 20, 20,
+ 19, -1, 25
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 23, 29, 30, 31, 19, 20, 21, 22, 48,
+ 49, 50, 0, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 16, 17, 18, 19, 20, 21, 22,
+ 24, 32, 33, 34, 35, 36, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 51, 52, 26, 4, 19,
+ 20, 5, 6, 7, 8, 9, 10, 12, 4, 19,
+ 46, 5, 6, 7, 8, 9, 10, 5, 6, 7,
+ 8, 9, 10, 12, 15, 16, 19, 25, 27, 38,
+ 46, 9, 9, 46, 24, 3, 19, 26, 25, 53,
+ 19, 20, 19, 53, 20, 20, 20, 36, 20, 20,
+ 15, 19, 25, 37, 38, 27, 25, 50, 20, 20,
+ 15, 37, 25, 50
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 28, 29, 29, 30, 31, 31, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 33, 34, 35, 35,
+ 35, 35, 36, 36, 36, 37, 37, 38, 39, 39,
+ 40, 40, 40, 40, 40, 40, 40, 41, 41, 41,
+ 41, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 43, 44, 44, 44, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 46, 46, 46, 46, 46,
+ 46, 47, 48, 48, 49, 49, 50, 50, 51, 52,
+ 53, 53
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_uint8 yyr2[] =
+{
+ 0, 2, 1, 1, 2, 0, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 3, 2, 4,
+ 6, 1, 2, 4, 6, 0, 1, 2, 1, 2,
+ 1, 1, 2, 2, 3, 1, 2, 1, 2, 2,
+ 2, 3, 5, 3, 3, 2, 4, 2, 3, 1,
+ 3, 2, 1, 1, 2, 2, 1, 2, 2, 1,
+ 2, 2, 1, 2, 2, 1, 2, 2, 1, 2,
+ 2, 2, 2, 1, 1, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+ 0, 2
+};
+
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (pc, YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Error token number */
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+#ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+#endif
+
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value, pc); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, parser_control *pc)
+{
+ FILE *yyoutput = yyo;
+ YYUSE (yyoutput);
+ YYUSE (pc);
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yytype < YYNTOKENS)
+ YYPRINT (yyo, yytoknum[yytype], *yyvaluep);
+# endif
+ YYUSE (yytype);
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo, int yytype, YYSTYPE const * const yyvaluep, parser_control *pc)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
+
+ yy_symbol_value_print (yyo, yytype, yyvaluep, pc);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule, parser_control *pc)
+{
+ unsigned long yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ yystos[yyssp[yyi + 1 - yynrhs]],
+ &yyvsp[(yyi + 1) - (yynrhs)]
+ , pc);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule, pc); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+static YYSIZE_T
+yystrlen (const char *yystr)
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ else
+ goto append;
+
+ append:
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return (YYSIZE_T) (yystpcpy (yyres, yystr) - yyres);
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+ about the unexpected token YYTOKEN for the state stack whose top is
+ YYSSP.
+
+ Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is
+ not large enough to hold the message. In that case, also set
+ *YYMSG_ALLOC to the required number of bytes. Return 2 if the
+ required number of bytes is too large to store. */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+ yytype_int16 *yyssp, int yytoken)
+{
+ YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
+ YYSIZE_T yysize = yysize0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+ /* Internationalized format string. */
+ const char *yyformat = YY_NULLPTR;
+ /* Arguments of yyformat. */
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ /* Number of reported tokens (one for the "unexpected", one per
+ "expected"). */
+ int yycount = 0;
+
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yychar) is if
+ this state is a consistent state with a default action. Thus,
+ detecting the absence of a lookahead is sufficient to determine
+ that there is no unexpected or expected token to report. In that
+ case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is a
+ consistent state with a default action. There might have been a
+ previous inconsistent state, consistent state with a non-default
+ action, or user semantic action that manipulated yychar.
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+ if (yytoken != YYEMPTY)
+ {
+ int yyn = yypact[*yyssp];
+ yyarg[yycount++] = yytname[yytoken];
+ if (!yypact_value_is_default (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yyx;
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+ && !yytable_value_is_error (yytable[yyx + yyn]))
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ {
+ YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
+ if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)
+ yysize = yysize1;
+ else
+ return 2;
+ }
+ }
+ }
+ }
+
+ switch (yycount)
+ {
+# define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ default: /* Avoid compiler warnings. */
+ YYCASE_(0, YY_("syntax error"));
+ YYCASE_(1, YY_("syntax error, unexpected %s"));
+ YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+ }
+
+ {
+ YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+ if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)
+ yysize = yysize1;
+ else
+ return 2;
+ }
+
+ if (*yymsg_alloc < yysize)
+ {
+ *yymsg_alloc = 2 * yysize;
+ if (! (yysize <= *yymsg_alloc
+ && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+ *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+ return 1;
+ }
+
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ {
+ char *yyp = *yymsg;
+ int yyi = 0;
+ while ((*yyp = *yyformat) != '\0')
+ if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyformat += 2;
+ }
+ else
+ {
+ yyp++;
+ yyformat++;
+ }
+ }
+ return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, parser_control *pc)
+{
+ YYUSE (yyvaluep);
+ YYUSE (pc);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YYUSE (yytype);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (parser_control *pc)
+{
+/* The lookahead symbol. */
+int yychar;
+
+
+/* The semantic value of the lookahead symbol. */
+/* Default value used for initialization, for pacifying older GCCs
+ or non-GCC compilers. */
+YY_INITIAL_VALUE (static YYSTYPE yyval_default;)
+YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default);
+
+ /* Number of syntax errors so far. */
+ int yynerrs;
+
+ int yystate;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+
+ /* The stacks and their tools:
+ 'yyss': related to states.
+ 'yyvs': related to semantic values.
+
+ Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs;
+ YYSTYPE *yyvsp;
+
+ YYSIZE_T yystacksize;
+
+ int yyn;
+ int yyresult;
+ /* Lookahead token as an internal (translated) token number. */
+ int yytoken = 0;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ yyssp = yyss = yyssa;
+ yyvsp = yyvs = yyvsa;
+ yystacksize = YYINITDEPTH;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yynewstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ *yyssp = (yytype_int16) yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = (YYSIZE_T) (yyssp - yyss + 1);
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = yylex (&yylval, pc);
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 4:
+#line 326 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->seconds = (yyvsp[0].timespec);
+ pc->timespec_seen = 1;
+ }
+#line 1630 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 7:
+#line 338 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->times_seen++; pc->dates_seen++;
+ }
+#line 1638 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 8:
+#line 341 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->times_seen++;
+ }
+#line 1646 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 9:
+#line 344 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->local_zones_seen++;
+ }
+#line 1654 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 10:
+#line 347 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->zones_seen++;
+ }
+#line 1662 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 11:
+#line 350 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->dates_seen++;
+ }
+#line 1670 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 12:
+#line 353 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->days_seen++;
+ }
+#line 1678 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 18:
+#line 370 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-1].textintval).value, 0, 0, 0);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1687 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 19:
+#line 374 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-3].textintval).value, (yyvsp[-1].textintval).value, 0, 0);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1696 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 20:
+#line 378 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-5].textintval).value, (yyvsp[-3].textintval).value, (yyvsp[-1].timespec).tv_sec, (yyvsp[-1].timespec).tv_nsec);
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1705 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 22:
+#line 386 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-1].textintval).value, 0, 0, 0);
+ pc->meridian = MER24;
+ }
+#line 1714 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 23:
+#line 390 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-3].textintval).value, (yyvsp[-1].textintval).value, 0, 0);
+ pc->meridian = MER24;
+ }
+#line 1723 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 24:
+#line 394 "sys-utils/hwclock-parse-date.y"
+ {
+ set_hhmmss (pc, (yyvsp[-5].textintval).value, (yyvsp[-3].textintval).value, (yyvsp[-1].timespec).tv_sec, (yyvsp[-1].timespec).tv_nsec);
+ pc->meridian = MER24;
+ }
+#line 1732 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 27:
+#line 406 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->zones_seen++;
+ if (! time_zone_hhmm (pc, (yyvsp[-1].textintval), (yyvsp[0].textintval))) YYABORT;
+ }
+#line 1741 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 28:
+#line 431 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->local_isdst = (yyvsp[0].intval);
+ pc->dsts_seen += (0 < (yyvsp[0].intval));
+ }
+#line 1750 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 29:
+#line 435 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < (yyvsp[-1].intval)) + 1;
+ }
+#line 1759 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 30:
+#line 446 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = (yyvsp[0].intval);
+ }
+#line 1767 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 31:
+#line 449 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = HOUR(7);
+ }
+#line 1775 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 32:
+#line 452 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = (yyvsp[-1].intval);
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 1784 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 33:
+#line 456 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = HOUR(7);
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 1793 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 34:
+#line 460 "sys-utils/hwclock-parse-date.y"
+ {
+ if (! time_zone_hhmm (pc, (yyvsp[-1].textintval), (yyvsp[0].textintval))) YYABORT;
+ pc->time_zone += (yyvsp[-2].intval);
+ }
+#line 1802 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 35:
+#line 464 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = (yyvsp[0].intval) + 60;
+ }
+#line 1810 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 36:
+#line 467 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->time_zone = (yyvsp[-1].intval) + 60;
+ }
+#line 1818 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 37:
+#line 473 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1827 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 38:
+#line 477 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day_ordinal = 0;
+ pc->day_number = (yyvsp[-1].intval);
+ }
+#line 1836 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 39:
+#line 481 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day_ordinal = (yyvsp[-1].intval);
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1845 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 40:
+#line 485 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day_ordinal = (yyvsp[-1].textintval).value;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1854 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 41:
+#line 492 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1863 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 42:
+#line 496 "sys-utils/hwclock-parse-date.y"
+ {
+ /**
+ * Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ * otherwise as MM/DD/YY.
+ * The goal in recognizing YYYY/MM/DD is solely to support legacy
+ * machine-generated dates like those in an RCS log listing. If
+ * you want portability, use the ISO 8601 format.
+ */
+ if (4 <= (yyvsp[-4].textintval).digits) {
+ pc->year = (yyvsp[-4].textintval);
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ } else {
+ pc->month = (yyvsp[-4].textintval).value;
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+ }
+#line 1886 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 43:
+#line 514 "sys-utils/hwclock-parse-date.y"
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year.value = -(yyvsp[0].textintval).value;
+ pc->year.digits = (yyvsp[0].textintval).digits;
+ }
+#line 1898 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 44:
+#line 521 "sys-utils/hwclock-parse-date.y"
+ {
+ /* e.g. JUN-17-1992. */
+ pc->month = (yyvsp[-2].intval);
+ pc->day = -(yyvsp[-1].textintval).value;
+ pc->year.value = -(yyvsp[0].textintval).value;
+ pc->year.digits = (yyvsp[0].textintval).digits;
+ }
+#line 1910 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 45:
+#line 528 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->month = (yyvsp[-1].intval);
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1919 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 46:
+#line 532 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->month = (yyvsp[-3].intval);
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1929 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 47:
+#line 537 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day = (yyvsp[-1].textintval).value;
+ pc->month = (yyvsp[0].intval);
+ }
+#line 1938 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 48:
+#line 541 "sys-utils/hwclock-parse-date.y"
+ {
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1948 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 50:
+#line 550 "sys-utils/hwclock-parse-date.y"
+ {
+ /* ISO 8601 format.YYYY-MM-DD. */
+ pc->year = (yyvsp[-2].textintval);
+ pc->month = -(yyvsp[-1].textintval).value;
+ pc->day = -(yyvsp[0].textintval).value;
+ }
+#line 1959 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 51:
+#line 560 "sys-utils/hwclock-parse-date.y"
+ { apply_relative_time (pc, (yyvsp[-1].rel), (yyvsp[0].intval)); }
+#line 1965 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 52:
+#line 562 "sys-utils/hwclock-parse-date.y"
+ { apply_relative_time (pc, (yyvsp[0].rel), 1); }
+#line 1971 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 53:
+#line 564 "sys-utils/hwclock-parse-date.y"
+ { apply_relative_time (pc, (yyvsp[0].rel), 1); }
+#line 1977 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 54:
+#line 569 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].intval); }
+#line 1983 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 55:
+#line 571 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].textintval).value; }
+#line 1989 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 56:
+#line 573 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = 1; }
+#line 1995 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 57:
+#line 575 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].intval); }
+#line 2001 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 58:
+#line 577 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].textintval).value; }
+#line 2007 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 59:
+#line 579 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = 1; }
+#line 2013 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 60:
+#line 581 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].intval) * (yyvsp[0].intval); }
+#line 2019 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 61:
+#line 583 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 2025 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 62:
+#line 585 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[0].intval); }
+#line 2031 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 63:
+#line 587 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].intval); }
+#line 2037 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 64:
+#line 589 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].textintval).value; }
+#line 2043 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 65:
+#line 591 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = 1; }
+#line 2049 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 66:
+#line 593 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].intval); }
+#line 2055 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 67:
+#line 595 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].textintval).value; }
+#line 2061 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 68:
+#line 597 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = 1; }
+#line 2067 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 69:
+#line 599 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].intval); }
+#line 2073 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 70:
+#line 601 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].textintval).value; }
+#line 2079 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 71:
+#line 602 "sys-utils/hwclock-parse-date.y"
+ {
+ (yyval.rel) = RELATIVE_TIME_0;
+ (yyval.rel).seconds = (yyvsp[-1].timespec).tv_sec;
+ (yyval.rel).ns = (yyvsp[-1].timespec).tv_nsec;
+ }
+#line 2089 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 72:
+#line 607 "sys-utils/hwclock-parse-date.y"
+ {
+ (yyval.rel) = RELATIVE_TIME_0;
+ (yyval.rel).seconds = (yyvsp[-1].timespec).tv_sec;
+ (yyval.rel).ns = (yyvsp[-1].timespec).tv_nsec;
+ }
+#line 2099 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 73:
+#line 613 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = 1; }
+#line 2105 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 75:
+#line 619 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).year = (yyvsp[-1].textintval).value; }
+#line 2111 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 76:
+#line 621 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).month = (yyvsp[-1].textintval).value; }
+#line 2117 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 77:
+#line 623 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 2123 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 78:
+#line 625 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).hour = (yyvsp[-1].textintval).value; }
+#line 2129 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 79:
+#line 627 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).minutes = (yyvsp[-1].textintval).value; }
+#line 2135 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 80:
+#line 629 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).seconds = (yyvsp[-1].textintval).value; }
+#line 2141 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 81:
+#line 634 "sys-utils/hwclock-parse-date.y"
+ { (yyval.rel) = RELATIVE_TIME_0; (yyval.rel).day = (yyvsp[0].intval); }
+#line 2147 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 85:
+#line 642 "sys-utils/hwclock-parse-date.y"
+ { (yyval.timespec).tv_sec = (yyvsp[0].textintval).value; (yyval.timespec).tv_nsec = 0; }
+#line 2153 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 87:
+#line 648 "sys-utils/hwclock-parse-date.y"
+ { (yyval.timespec).tv_sec = (yyvsp[0].textintval).value; (yyval.timespec).tv_nsec = 0; }
+#line 2159 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 88:
+#line 653 "sys-utils/hwclock-parse-date.y"
+ { digits_to_date_time (pc, (yyvsp[0].textintval)); }
+#line 2165 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 89:
+#line 657 "sys-utils/hwclock-parse-date.y"
+ {
+ /**
+ * Hybrid all-digit and relative offset, so that we accept e.g.,
+ * "YYYYMMDD +N days" as well as "YYYYMMDD N days".
+ */
+ digits_to_date_time (pc, (yyvsp[-1].textintval));
+ apply_relative_time (pc, (yyvsp[0].rel), 1);
+ }
+#line 2178 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 90:
+#line 669 "sys-utils/hwclock-parse-date.y"
+ { (yyval.textintval).value = (yyval.textintval).digits = 0; }
+#line 2184 "sys-utils/hwclock-parse-date.c"
+ break;
+
+ case 91:
+#line 670 "sys-utils/hwclock-parse-date.y"
+ {
+ (yyval.textintval) = (yyvsp[0].textintval);
+ }
+#line 2192 "sys-utils/hwclock-parse-date.c"
+ break;
+
+
+#line 2196 "sys-utils/hwclock-parse-date.c"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (pc, YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+ yyssp, yytoken)
+ {
+ char const *yymsgp = YY_("syntax error");
+ int yysyntax_error_status;
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ if (yysyntax_error_status == 0)
+ yymsgp = yymsg;
+ else if (yysyntax_error_status == 1)
+ {
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+ if (!yymsg)
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ yysyntax_error_status = 2;
+ }
+ else
+ {
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ yymsgp = yymsg;
+ }
+ }
+ yyerror (pc, yymsgp);
+ if (yysyntax_error_status == 2)
+ goto yyexhaustedlab;
+ }
+# undef YYSYNTAX_ERROR
+#endif
+ }
+
+
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval, pc);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp, pc);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (pc, YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+
+/*-----------------------------------------------------.
+| yyreturn -- parsing is finished, return the result. |
+`-----------------------------------------------------*/
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval, pc);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp, pc);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ return yyresult;
+}
+#line 675 "sys-utils/hwclock-parse-date.y"
+
+
+static table const meridian_table[] = {
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] = {
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] = {
+ { "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 }
+};
+
+static table const time_units_table[] = {
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "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 relative_time_table[] = {
+ { "TOMORROW", tDAY_SHIFT, 1 },
+ { "YESTERDAY",tDAY_SHIFT, -1 },
+ { "TODAY", tDAY_SHIFT, 0 },
+ { "NOW", tDAY_SHIFT, 0 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+ /*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
+ { "AGO", tAGO, -1 },
+ { "HENCE", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The universal time zone table. These labels can be used even for
+ * timestamps that would not otherwise be valid, e.g., GMT timestamps
+ * in London during summer.
+ */
+static table const universal_time_zone_table[] = {
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The time zone table. This table is necessarily incomplete, as time
+ * zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ * as Eastern time in Australia, not as US Eastern Standard Time.
+ * You cannot rely on parse_date to handle arbitrary time zone
+ * abbreviations; use numeric abbreviations like "-0500" instead.
+ */
+static table const time_zone_table[] = {
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "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 */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/**
+ * Military time zone table.
+ *
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+static table const military_table[] = {
+ { "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", 'T', 0 },
+ { "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 }
+};
+
+/**
+ * Convert a time offset expressed as HH:MM or HHMM into an integer count of
+ * minutes. If hh is more than 2 digits then it is of the form HHMM and must be
+ * delimited; in that case 'mm' is required to be absent. Otherwise, hh and mm
+ * are used ('mm' contains digits that were prefixed with a colon).
+ *
+ * POSIX TZ and ISO 8601 both define the maximum offset as 24:59. POSIX also
+ * allows seconds, but currently the parser rejects them. Both require minutes
+ * to be zero padded (2 digits). ISO requires hours to be zero padded, POSIX
+ * does not, either is accepted; which means an invalid ISO offset could pass.
+ */
+
+static int time_zone_hhmm(parser_control *pc, textint hh, textint mm)
+{
+ int h, m;
+
+ if (hh.digits > 2 && hh.digits < 5 && mm.digits == 0) {
+ h = hh.value / 100;
+ m = hh.value % 100;
+ } else if (hh.digits < 3 && (mm.digits == 0 || mm.digits == 2)) {
+ h = hh.value;
+ m = hh.negative ? -mm.value : mm.value;
+ } else
+ return 0;
+
+ if (abs(h) > 24 || abs(m) > 59)
+ return 0;
+
+ pc->time_zone = h * 60 + m;
+ return 1;
+}
+
+static int to_hour(intmax_t hours, int meridian)
+{
+ switch (meridian) {
+ default: /* Pacify GCC. */
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ }
+}
+
+static long int to_year(textint textyear)
+{
+ intmax_t year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /**
+ * XPG4 suggests that years 00-68 map to 2000-2068, and
+ * years 69-99 map to 1969-1999.
+ */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const * lookup_zone(parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ /**
+ * Try local zone abbreviations before those in time_zone_table, as
+ * the local ones are more likely to be right.
+ */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+#if ! HAVE_TM_GMTOFF
+/**
+ * Yield the difference between *A and *B,
+ * measured in seconds, ignoring leap seconds.
+ * The body of this function is taken directly from the GNU C Library;
+ * see src/strftime.c.
+ */
+static int tm_diff(struct tm const *a, struct tm const *b)
+{
+ /**
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations.
+ */
+ int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
+ int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = SHR (a100, 2);
+ int b400 = SHR (b100, 2);
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const * lookup_word(parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int period_found;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ *p = c_toupper (to_uchar (*p));
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) :
+ strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S') {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = 1;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int yylex (union YYSTYPE *lvalp, parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;) {
+ while (c = *pc->input, c_isspace (c))
+ pc->input++;
+
+ if (c_isdigit (c) || c == '-' || c == '+') {
+ char const *p;
+ int sign;
+ uintmax_t value;
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ while (c = *++pc->input, c_isspace (c))
+ continue;
+ if (! c_isdigit (c))
+ /* skip the '-' sign */
+ continue;
+ } else
+ sign = 0;
+ p = pc->input;
+ for (value = 0; ; value *= 10) {
+ uintmax_t value1 = value + (c - '0');
+ if (value1 < value)
+ return '?';
+ value = value1;
+ c = *++p;
+ if (! c_isdigit (c))
+ break;
+ if (UINTMAX_MAX / 10 < value)
+ return '?';
+ }
+ if ((c == '.' || c == ',') && c_isdigit (p[1])) {
+ time_t s;
+ int ns;
+ int digits;
+ uintmax_t value1;
+
+ /* Check for overflow when converting value to
+ * time_t.
+ */
+ if (sign < 0) {
+ s = - value;
+ if (0 < s)
+ return '?';
+ value1 = -s;
+ } else {
+ s = value;
+ if (s < 0)
+ return '?';
+ value1 = s;
+ }
+ if (value != value1)
+ return '?';
+
+ /* Accumulate fraction, to ns precision. */
+ p++;
+ ns = *p++ - '0';
+ for (digits = 2;
+ digits <= LOG10_BILLION; digits++) {
+ ns *= 10;
+ if (c_isdigit (*p))
+ ns += *p++ - '0';
+ }
+
+ /* Skip excess digits, truncating toward
+ * -Infinity.
+ */
+ if (sign < 0)
+ for (; c_isdigit (*p); p++)
+ if (*p != '0') {
+ ns++;
+ break;
+ }
+ while (c_isdigit (*p))
+ p++;
+
+ /* Adjust to the timespec convention, which is
+ * that tv_nsec is always a positive offset even
+ * if tv_sec is negative.
+ */
+ if (sign < 0 && ns) {
+ s--;
+ if (! (s < 0))
+ return '?';
+ ns = BILLION - ns;
+ }
+
+ lvalp->timespec.tv_sec = s;
+ lvalp->timespec.tv_nsec = ns;
+ pc->input = p;
+ return
+ sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
+ } else {
+ lvalp->textintval.negative = sign < 0;
+ if (sign < 0) {
+ lvalp->textintval.value = - value;
+ if (0 < lvalp->textintval.value)
+ return '?';
+ } else {
+ lvalp->textintval.value = value;
+ if (lvalp->textintval.value < 0)
+ return '?';
+ }
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ }
+
+ if (c_isalpha (c)) {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (c_isalpha (c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp) {
+ return '?';
+ }
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return to_uchar (*pc->input++);
+
+ count = 0;
+ do {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int yyerror(parser_control const *pc __attribute__((__unused__)),
+ char const *s __attribute__((__unused__)))
+{
+ return 0;
+}
+
+/**
+ * If *TM0 is the old and *TM1 is the new value of a struct tm after
+ * passing it to mktime, return 1 if it's OK that mktime returned T.
+ * It's not OK if *TM0 has out-of-range members.
+ */
+
+static int mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t)
+{
+ if (t == (time_t) -1) {
+ /**
+ * Guard against falsely reporting an error when parsing a
+ * timestamp that happens to equal (time_t) -1, on a host that
+ * supports such a timestamp.
+ */
+ tm1 = localtime (&t);
+ if (!tm1)
+ return 0;
+ }
+
+ return ! ((tm0->tm_sec ^ tm1->tm_sec)
+ | (tm0->tm_min ^ tm1->tm_min)
+ | (tm0->tm_hour ^ tm1->tm_hour)
+ | (tm0->tm_mday ^ tm1->tm_mday)
+ | (tm0->tm_mon ^ tm1->tm_mon)
+ | (tm0->tm_year ^ tm1->tm_year));
+}
+
+/**
+ * A reasonable upper bound for the size of ordinary TZ strings.
+ * Use heap allocation if TZ's length exceeds this.
+ */
+enum { TZBUFSIZE = 100 };
+
+/**
+ * Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
+ * otherwise.
+ */
+static char * get_tz(char tzbuf[TZBUFSIZE])
+{
+ char *tz = getenv ("TZ");
+ if (tz) {
+ size_t tzsize = strlen (tz) + 1;
+ tz = (tzsize <= TZBUFSIZE
+ ? memcpy (tzbuf, tz, tzsize)
+ : strdup (tz));
+ }
+ return tz;
+}
+
+/**
+ * Parse a date/time string, storing the resulting time value into *result.
+ * The string itself is pointed to by *p. Return 1 if successful.
+ * *p can be an incomplete or relative time specification; if so, use
+ * *now as the basis for the returned time.
+ */
+int parse_date(struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ intmax_t Start_ns;
+ struct tm const *tmp;
+ struct tm tm;
+ struct tm tm0;
+ parser_control pc;
+ struct timespec gettime_buffer;
+ unsigned char c;
+ int tz_was_altered = 0;
+ char *tz0 = NULL;
+ char tz0buf[TZBUFSIZE];
+ int ok = 1;
+ struct timeval tv;
+
+ if (! now) {
+ gettimeofday (&tv, NULL);
+ gettime_buffer.tv_sec = tv.tv_sec;
+ gettime_buffer.tv_nsec = tv.tv_usec * 1000;
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = localtime (&now->tv_sec);
+ if (! tmp)
+ return 0;
+
+ while (c = *p, c_isspace (c))
+ p++;
+
+ if (strncmp (p, "TZ=\"", 4) == 0) {
+ char const *tzbase = p + 4;
+ size_t tzsize = 1;
+ char const *s;
+
+ for (s = tzbase; *s; s++, tzsize++)
+ if (*s == '\\') {
+ s++;
+ if (! (*s == '\\' || *s == '"'))
+ break;
+ } else if (*s == '"') {
+ char *z;
+ char *tz1;
+ char tz1buf[TZBUFSIZE];
+ int large_tz = TZBUFSIZE < tzsize;
+ int setenv_ok;
+
+ tz0 = get_tz (tz0buf);
+ if (!tz0)
+ goto fail;
+
+ if (large_tz) {
+ z = tz1 = malloc (tzsize);
+ if (!tz1)
+ goto fail;
+ } else
+ z = tz1 = tz1buf;
+
+ for (s = tzbase; *s != '"'; s++)
+ *z++ = *(s += *s == '\\');
+ *z = '\0';
+ setenv_ok = setenv ("TZ", tz1, 1) == 0;
+ if (large_tz)
+ free (tz1);
+ if (!setenv_ok)
+ goto fail;
+ tz_was_altered = 1;
+
+ p = s + 1;
+ while (c = *p, c_isspace (c))
+ p++;
+
+ break;
+ }
+ }
+
+ /**
+ * As documented, be careful to treat the empty string just like
+ * a date string of "0". Without this, an empty string would be
+ * declared invalid when parsed during a DST transition.
+ */
+ if (*p == '\0')
+ p = "0";
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year;
+ pc.year.value += TM_YEAR_BASE;
+ pc.year.digits = 0;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = 0;
+ pc.rels_seen = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+
+#if HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = NULL;
+
+ /**
+ * Probe the names used in the next three calendar quarters, looking
+ * for a tm_isdst different from the one we already have.
+ */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++) {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm const *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst
+ != pc.local_time_zone_table[0].value) {
+ {
+ pc.local_time_zone_table[1].name
+ = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type
+ = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value
+ = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name
+ = NULL;
+ }
+ break;
+ }
+ }
+ }
+#else
+#if HAVE_TZNAME
+ {
+# if !HAVE_DECL_TZNAME
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++) {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = NULL;
+ }
+#else
+ pc.local_time_zone_table[0].name = NULL;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name)) {
+ /**
+ * This locale uses the same abbreviation for standard and
+ * daylight times. So if we see that abbreviation, we don't
+ * know whether it's daylight time.
+ */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = NULL;
+ }
+
+ if (yyparse (&pc) != 0) {
+ goto fail;
+ }
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else {
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen
+ | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen))) {
+ goto fail;
+ }
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
+ tm.tm_mon = pc.month - 1;
+ tm.tm_mday = pc.day;
+ if (pc.times_seen || (pc.rels_seen &&
+ ! pc.dates_seen && ! pc.days_seen)) {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0) {
+ goto fail;
+ }
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds.tv_sec;
+ } else {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ pc.seconds.tv_nsec = 0;
+ }
+
+ /**
+ * Let mktime deduce tm_isdst if we have an absolute timestamp.
+ */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ tm.tm_isdst = -1;
+
+ /**
+ * But if the input explicitly specifies local time with or
+ * without DST, give mktime that information.
+ */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ if (! pc.zones_seen) {
+ goto fail;
+ } else {
+ /** Guard against falsely reporting errors near
+ * the time_t boundaries when parsing times in
+ * other time zones. For example, suppose the
+ * input string "1969-12-31 23:00:00 -0100", the
+ * current time zone is 8 hours ahead of UTC,
+ * and the min time_t value is 1970-01-01
+ * 00:00:00 UTC. Then the min localtime value
+ * is 1970-01-01 08:00:00, and mktime will
+ * therefore fail on 1969-12-31 23:00:00. To
+ * work around the problem, set the time zone to
+ * 1 hour behind UTC temporarily by setting
+ * TZ="XXX1:00" and try mktime again.
+ */
+
+ intmax_t time_zone = pc.time_zone;
+
+ intmax_t abs_time_zone = time_zone < 0
+ ? - time_zone : time_zone;
+
+ intmax_t abs_time_zone_hour
+ = abs_time_zone / 60;
+
+ int abs_time_zone_min = abs_time_zone % 60;
+
+ char tz1buf[sizeof "XXX+0:00"
+ + sizeof pc.time_zone
+ * CHAR_BIT / 3];
+
+ if (!tz_was_altered)
+ tz0 = get_tz (tz0buf);
+ sprintf (tz1buf, "XXX%s%jd:%02d",
+ &"-"[time_zone < 0],
+ abs_time_zone_hour,
+ abs_time_zone_min);
+ if (setenv ("TZ", tz1buf, 1) != 0) {
+ goto fail;
+ }
+ tz_was_altered = 1;
+ tm = tm0;
+ Start = mktime (&tm);
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (pc.days_seen && ! pc.dates_seen) {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + 7
+ * (pc.day_ordinal
+ - (0 < pc.day_ordinal
+ && tm.tm_wday != pc.day_number)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+ /* Add relative date. */
+ if (pc.rel.year | pc.rel.month | pc.rel.day) {
+ int year = tm.tm_year + pc.rel.year;
+ int month = tm.tm_mon + pc.rel.month;
+ int day = tm.tm_mday + pc.rel.day;
+ if (((year < tm.tm_year) ^ (pc.rel.year < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) {
+ goto fail;
+ }
+ tm.tm_year = year;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_hour = tm0.tm_hour;
+ tm.tm_min = tm0.tm_min;
+ tm.tm_sec = tm0.tm_sec;
+ tm.tm_isdst = tm0.tm_isdst;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+
+ /**
+ * The only "output" of this if-block is an updated Start value,
+ * so this block must follow others that clobber Start.
+ */
+ if (pc.zones_seen) {
+ intmax_t delta = pc.time_zone * 60;
+ time_t t1;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ time_t t = Start;
+ struct tm const *gmt = gmtime (&t);
+ if (! gmt) {
+ goto fail;
+ }
+ delta -= tm_diff (&tm, gmt);
+#endif
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0)) {
+ goto fail; /* time_t overflow */
+ }
+ Start = t1;
+ }
+
+ /**
+ * Add relative hours, minutes, and seconds. On hosts that
+ * support leap seconds, ignore the possibility of leap seconds;
+ * e.g., "+ 10 minutes" adds 600 seconds, even if one of them is
+ * a leap second. Typically this is not what the user wants,
+ * but it's too hard to do it the other way, because the time
+ * zone indicator must be applied before relative times, and if
+ * mktime is applied again the time zone will be lost.
+ */
+ intmax_t sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
+ intmax_t normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+ time_t t0 = Start;
+ intmax_t d1 = 60 * 60 * pc.rel.hour;
+ time_t t1 = t0 + d1;
+ intmax_t d2 = 60 * pc.rel.minutes;
+ time_t t2 = t1 + d2;
+ time_t d3 = pc.rel.seconds;
+ time_t t3 = t2 + d3;
+ intmax_t d4 = (sum_ns - normalized_ns) / BILLION;
+ time_t t4 = t3 + d4;
+ time_t t5 = t4;
+
+ if ((d1 / (60 * 60) ^ pc.rel.hour)
+ | (d2 / 60 ^ pc.rel.minutes)
+ | ((t1 < t0) ^ (d1 < 0))
+ | ((t2 < t1) ^ (d2 < 0))
+ | ((t3 < t2) ^ (d3 < 0))
+ | ((t4 < t3) ^ (d4 < 0))
+ | (t5 != t4)) {
+ goto fail;
+ }
+ result->tv_sec = t5;
+ result->tv_nsec = normalized_ns;
+ }
+
+ goto done;
+
+ fail:
+ ok = 0;
+ done:
+ if (tz_was_altered)
+ ok &= (tz0 ? setenv ("TZ", tz0, 1)
+ : unsetenv ("TZ")) == 0;
+ if (tz0 != tz0buf)
+ free (tz0);
+ return ok;
+}
diff --git a/sys-utils/hwclock-parse-date.y b/sys-utils/hwclock-parse-date.y
new file mode 100644
index 0000000..35669fd
--- /dev/null
+++ b/sys-utils/hwclock-parse-date.y
@@ -0,0 +1,1629 @@
+%{
+/**
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Parse a string into an internal timestamp.
+ *
+ * This file is based on gnulib parse-datetime.y-dd7a871 with
+ * the other gnulib dependencies removed for use in util-linux.
+ *
+ * Copyright (C) 1999-2000, 2002-2017 Free Software Foundation, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ *
+ * Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ * the right thing about local DST. Also modified by Paul Eggert
+ * <eggert@cs.ucla.edu> in February 2004 to support
+ * nanosecond-resolution timestamps, and in October 2004 to support
+ * TZ strings in dates.
+ */
+
+/**
+ * FIXME: Check for arithmetic overflow in all cases, not just
+ * some of them.
+ */
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "timeutils.h"
+#include "hwclock.h"
+
+/**
+ * There's no need to extend the stack, so there's no need to involve
+ * alloca.
+ */
+#define YYSTACK_USE_ALLOCA 0
+
+/**
+ * Tell Bison how much stack space is needed. 20 should be plenty for
+ * this grammar, which is not right recursive. Beware setting it too
+ * high, since that might cause problems on machines whose
+ * implementations have lame stack-overflow checking.
+ */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+/**
+ * Since the code of parse-datetime.y is not included in the Emacs executable
+ * itself, there is no need to #define static in this file. Even if
+ * the code were included in the Emacs executable, it probably
+ * wouldn't do any harm to #undef it here; this will only cause
+ * problems if we try to write to a static variable, which I don't
+ * think this code needs to do.
+ */
+#ifdef emacs
+# undef static
+#endif
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <stdarg.h>
+#include "cctype.h"
+#include "nls.h"
+
+/**
+ * Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
+ * use _STDLIB_H_ as witness. Map the latter to the one bison uses.
+ * FIXME: this is temporary. Remove when we have a mechanism to ensure
+ * that the version we're using is fixed, too.
+ */
+#ifdef _STDLIB_H_
+# undef _STDLIB_H
+# define _STDLIB_H 1
+#endif
+
+/**
+ * Shift A right by B bits portably, by dividing A by 2**B and
+ * truncating towards minus infinity. A and B should be free of side
+ * effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ * INT_BITS is the number of useful bits in an int. GNU code can
+ * assume that INT_BITS is at least 32.
+ *
+ * ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ * implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ * right in the usual way when A < 0, so SHR falls back on division if
+ * ordinary A >> B doesn't seem to be the usual signed shift.
+ */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+/**
+ * Convert a possibly-signed character to an unsigned character. This is
+ * a bit safer than casting to unsigned char, since it catches some type
+ * errors that the cast doesn't.
+ */
+static unsigned char to_uchar (char ch) { return ch; }
+
+/**
+ * FIXME: It also assumes that signed integer overflow silently wraps around,
+ * but this is not true any more with recent versions of GCC 4.
+ */
+
+/**
+ * An integer value, and the number of digits in its textual
+ * representation.
+ */
+typedef struct {
+ int negative;
+ intmax_t value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct {
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+typedef struct {
+ intmax_t year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ time_t seconds;
+ int ns;
+} relative_time;
+
+#if HAVE_COMPOUND_LITERALS
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+#else
+static relative_time const RELATIVE_TIME_0;
+#endif
+
+/* Information passed to and from the parser. */
+typedef struct {
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ intmax_t day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and ns. */
+ textint year;
+ intmax_t month;
+ intmax_t day;
+ intmax_t hour;
+ intmax_t minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and ns. */
+ relative_time rel;
+
+ /* Presence or counts of some nonterminals parsed so far. */
+ int timespec_seen;
+ int rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbreviations, null terminated. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static int time_zone_hhmm (parser_control *, textint, textint);
+
+/**
+ * Extract into *PC any date and time info from a string of digits
+ * of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
+ * YYYY, ...).
+ */
+static void digits_to_date_time(parser_control *pc, textint text_int)
+{
+ if (pc->dates_seen && ! pc->year.digits
+ && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits)) {
+ pc->year = text_int;
+ } else {
+ if (4 < text_int.digits) {
+ pc->dates_seen++;
+ pc->day = text_int.value % 100;
+ pc->month = (text_int.value / 100) % 100;
+ pc->year.value = text_int.value / 10000;
+ pc->year.digits = text_int.digits - 4;
+ } else {
+ pc->times_seen++;
+ if (text_int.digits <= 2) {
+ pc->hour = text_int.value;
+ pc->minutes = 0;
+ }
+ else {
+ pc->hour = text_int.value / 100;
+ pc->minutes = text_int.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+/* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
+static void apply_relative_time(parser_control *pc, relative_time rel,
+ int factor)
+{
+ pc->rel.ns += factor * rel.ns;
+ pc->rel.seconds += factor * rel.seconds;
+ pc->rel.minutes += factor * rel.minutes;
+ pc->rel.hour += factor * rel.hour;
+ pc->rel.day += factor * rel.day;
+ pc->rel.month += factor * rel.month;
+ pc->rel.year += factor * rel.year;
+ pc->rels_seen = 1;
+}
+
+/* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
+static void
+set_hhmmss(parser_control *pc, intmax_t hour, intmax_t minutes,
+ time_t sec, int nsec)
+{
+ pc->hour = hour;
+ pc->minutes = minutes;
+ pc->seconds.tv_sec = sec;
+ pc->seconds.tv_nsec = nsec;
+}
+
+%}
+
+/**
+ * We want a reentrant parser, even if the TZ manipulation and the calls to
+ * localtime and gmtime are not reentrant.
+ */
+%define api.pure
+%parse-param { parser_control *pc }
+%lex-param { parser_control *pc }
+
+/* This grammar has 31 shift/reduce conflicts. */
+%expect 31
+
+%union {
+ intmax_t intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+}
+
+%token <intval> tAGO
+%token tDST
+
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT tDAY_SHIFT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
+
+%type <textintval> o_colon_minutes
+%type <timespec> seconds signed_seconds unsigned_seconds
+
+%type <rel> relunit relunit_snumber dayshift
+
+%%
+
+spec:
+ timespec
+ | items
+;
+
+timespec:
+ '@' seconds {
+ pc->seconds = $2;
+ pc->timespec_seen = 1;
+ }
+;
+
+items:
+ /* empty */
+ | items item
+;
+
+item:
+ datetime {
+ pc->times_seen++; pc->dates_seen++;
+ }
+ | time {
+ pc->times_seen++;
+ }
+ | local_zone {
+ pc->local_zones_seen++;
+ }
+ | zone {
+ pc->zones_seen++;
+ }
+ | date {
+ pc->dates_seen++;
+ }
+ | day {
+ pc->days_seen++;
+ }
+ | rel
+ | number
+ | hybrid
+;
+
+datetime:
+ iso_8601_datetime
+;
+
+iso_8601_datetime:
+ iso_8601_date 'T' iso_8601_time
+;
+
+time:
+ tUNUMBER tMERIDIAN {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER tMERIDIAN {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = $6;
+ }
+ | iso_8601_time
+;
+
+iso_8601_time:
+ tUNUMBER zone_offset {
+ set_hhmmss (pc, $1.value, 0, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER o_zone_offset {
+ set_hhmmss (pc, $1.value, $3.value, 0, 0);
+ pc->meridian = MER24;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset {
+ set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
+ pc->meridian = MER24;
+ }
+;
+
+o_zone_offset:
+ /* empty */
+ | zone_offset
+;
+
+zone_offset:
+ tSNUMBER o_colon_minutes {
+ pc->zones_seen++;
+ if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
+ }
+;
+
+/**
+ * Local zone strings only affect DST setting,
+ * and only take affect if the current TZ setting is relevant.
+ *
+ * Example 1:
+ * 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
+ * TZ=Europe/Helsinki date -d '2016-12-30 EEST'
+ *
+ * Example 2:
+ * 'EEST' is parsed as 'zone' (TZ=+03:00):
+ * TZ=Asia/Tokyo ./src/date --debug -d '2011-06-11 EEST'
+ *
+ * This is implemented by probing the next three calendar quarters
+ * of the effective timezone and looking for DST changes -
+ * if found, the timezone name (EEST) is inserted into
+ * the lexical lookup table with type tLOCAL_ZONE.
+ * (Search for 'quarter' comment in 'parse_date').
+ */
+local_zone:
+ tLOCAL_ZONE {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
+ | tLOCAL_ZONE tDST {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
+;
+
+/**
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+zone:
+ tZONE {
+ pc->time_zone = $1;
+ }
+ | 'T' {
+ pc->time_zone = HOUR(7);
+ }
+ | tZONE relunit_snumber {
+ pc->time_zone = $1;
+ apply_relative_time (pc, $2, 1);
+ }
+ | 'T' relunit_snumber {
+ pc->time_zone = HOUR(7);
+ apply_relative_time (pc, $2, 1);
+ }
+ | tZONE tSNUMBER o_colon_minutes {
+ if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
+ pc->time_zone += $1;
+ }
+ | tDAYZONE {
+ pc->time_zone = $1 + 60;
+ }
+ | tZONE tDST {
+ pc->time_zone = $1 + 60;
+ }
+;
+
+day:
+ tDAY {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tDAY ',' {
+ pc->day_ordinal = 0;
+ pc->day_number = $1;
+ }
+ | tORDINAL tDAY {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
+ | tUNUMBER tDAY {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+;
+
+date:
+ tUNUMBER '/' tUNUMBER {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ /**
+ * Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ * otherwise as MM/DD/YY.
+ * The goal in recognizing YYYY/MM/DD is solely to support legacy
+ * machine-generated dates like those in an RCS log listing. If
+ * you want portability, use the ISO 8601 format.
+ */
+ if (4 <= $1.digits) {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ } else {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ pc->year = $5;
+ }
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tSNUMBER tSNUMBER {
+ /* e.g. JUN-17-1992. */
+ pc->month = $1;
+ pc->day = -$2.value;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tUNUMBER {
+ pc->month = $1;
+ pc->day = $2.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ | iso_8601_date
+;
+
+iso_8601_date:
+ tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format.YYYY-MM-DD. */
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
+ }
+;
+
+rel:
+ relunit tAGO
+ { apply_relative_time (pc, $1, $2); }
+ | relunit
+ { apply_relative_time (pc, $1, 1); }
+ | dayshift
+ { apply_relative_time (pc, $1, 1); }
+;
+
+relunit:
+ tORDINAL tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1; }
+ | tUNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = 1; }
+ | tORDINAL tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1; }
+ | tUNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = 1; }
+ | tORDINAL tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
+ | tUNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+ | tORDINAL tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1; }
+ | tUNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = 1; }
+ | tORDINAL tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
+ | tUNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
+ | tORDINAL tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
+ | tUNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+ | tSDECIMAL_NUMBER tSEC_UNIT {
+ $$ = RELATIVE_TIME_0;
+ $$.seconds = $1.tv_sec;
+ $$.ns = $1.tv_nsec;
+ }
+ | tUDECIMAL_NUMBER tSEC_UNIT {
+ $$ = RELATIVE_TIME_0;
+ $$.seconds = $1.tv_sec;
+ $$.ns = $1.tv_nsec;
+ }
+ | tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
+ | relunit_snumber
+;
+
+relunit_snumber:
+ tSNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tSNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tSNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tSNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tSNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tSNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+;
+
+dayshift:
+ tDAY_SHIFT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+;
+
+seconds: signed_seconds | unsigned_seconds;
+
+signed_seconds:
+ tSDECIMAL_NUMBER
+ | tSNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+;
+
+unsigned_seconds:
+ tUDECIMAL_NUMBER
+ | tUNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+;
+
+number:
+ tUNUMBER
+ { digits_to_date_time (pc, $1); }
+;
+
+hybrid:
+ tUNUMBER relunit_snumber {
+ /**
+ * Hybrid all-digit and relative offset, so that we accept e.g.,
+ * "YYYYMMDD +N days" as well as "YYYYMMDD N days".
+ */
+ digits_to_date_time (pc, $1);
+ apply_relative_time (pc, $2, 1);
+ }
+;
+
+o_colon_minutes:
+ /* empty */
+ { $$.value = $$.digits = 0; }
+ | ':' tUNUMBER {
+ $$ = $2;
+ }
+;
+
+%%
+
+static table const meridian_table[] = {
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] = {
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] = {
+ { "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 }
+};
+
+static table const time_units_table[] = {
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "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 relative_time_table[] = {
+ { "TOMORROW", tDAY_SHIFT, 1 },
+ { "YESTERDAY",tDAY_SHIFT, -1 },
+ { "TODAY", tDAY_SHIFT, 0 },
+ { "NOW", tDAY_SHIFT, 0 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+ /*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
+ { "AGO", tAGO, -1 },
+ { "HENCE", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The universal time zone table. These labels can be used even for
+ * timestamps that would not otherwise be valid, e.g., GMT timestamps
+ * in London during summer.
+ */
+static table const universal_time_zone_table[] = {
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/**
+ * The time zone table. This table is necessarily incomplete, as time
+ * zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ * as Eastern time in Australia, not as US Eastern Standard Time.
+ * You cannot rely on parse_date to handle arbitrary time zone
+ * abbreviations; use numeric abbreviations like "-0500" instead.
+ */
+static table const time_zone_table[] = {
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "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 */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/**
+ * Military time zone table.
+ *
+ * Note 'T' is a special case, as it is used as the separator in ISO
+ * 8601 date and time of day representation.
+ */
+static table const military_table[] = {
+ { "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", 'T', 0 },
+ { "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 }
+};
+
+/**
+ * Convert a time offset expressed as HH:MM or HHMM into an integer count of
+ * minutes. If hh is more than 2 digits then it is of the form HHMM and must be
+ * delimited; in that case 'mm' is required to be absent. Otherwise, hh and mm
+ * are used ('mm' contains digits that were prefixed with a colon).
+ *
+ * POSIX TZ and ISO 8601 both define the maximum offset as 24:59. POSIX also
+ * allows seconds, but currently the parser rejects them. Both require minutes
+ * to be zero padded (2 digits). ISO requires hours to be zero padded, POSIX
+ * does not, either is accepted; which means an invalid ISO offset could pass.
+ */
+
+static int time_zone_hhmm(parser_control *pc, textint hh, textint mm)
+{
+ int h, m;
+
+ if (hh.digits > 2 && hh.digits < 5 && mm.digits == 0) {
+ h = hh.value / 100;
+ m = hh.value % 100;
+ } else if (hh.digits < 3 && (mm.digits == 0 || mm.digits == 2)) {
+ h = hh.value;
+ m = hh.negative ? -mm.value : mm.value;
+ } else
+ return 0;
+
+ if (abs(h) > 24 || abs(m) > 59)
+ return 0;
+
+ pc->time_zone = h * 60 + m;
+ return 1;
+}
+
+static int to_hour(intmax_t hours, int meridian)
+{
+ switch (meridian) {
+ default: /* Pacify GCC. */
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ }
+}
+
+static long int to_year(textint textyear)
+{
+ intmax_t year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /**
+ * XPG4 suggests that years 00-68 map to 2000-2068, and
+ * years 69-99 map to 1969-1999.
+ */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const * lookup_zone(parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ /**
+ * Try local zone abbreviations before those in time_zone_table, as
+ * the local ones are more likely to be right.
+ */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+#if ! HAVE_TM_GMTOFF
+/**
+ * Yield the difference between *A and *B,
+ * measured in seconds, ignoring leap seconds.
+ * The body of this function is taken directly from the GNU C Library;
+ * see src/strftime.c.
+ */
+static int tm_diff(struct tm const *a, struct tm const *b)
+{
+ /**
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations.
+ */
+ int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
+ int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = SHR (a100, 2);
+ int b400 = SHR (b100, 2);
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const * lookup_word(parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int period_found;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ *p = c_toupper (to_uchar (*p));
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) :
+ strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S') {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = 1;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int yylex (union YYSTYPE *lvalp, parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;) {
+ while (c = *pc->input, c_isspace (c))
+ pc->input++;
+
+ if (c_isdigit (c) || c == '-' || c == '+') {
+ char const *p;
+ int sign;
+ uintmax_t value;
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ while (c = *++pc->input, c_isspace (c))
+ continue;
+ if (! c_isdigit (c))
+ /* skip the '-' sign */
+ continue;
+ } else
+ sign = 0;
+ p = pc->input;
+ for (value = 0; ; value *= 10) {
+ uintmax_t value1 = value + (c - '0');
+ if (value1 < value)
+ return '?';
+ value = value1;
+ c = *++p;
+ if (! c_isdigit (c))
+ break;
+ if (UINTMAX_MAX / 10 < value)
+ return '?';
+ }
+ if ((c == '.' || c == ',') && c_isdigit (p[1])) {
+ time_t s;
+ int ns;
+ int digits;
+ uintmax_t value1;
+
+ /* Check for overflow when converting value to
+ * time_t.
+ */
+ if (sign < 0) {
+ s = - value;
+ if (0 < s)
+ return '?';
+ value1 = -s;
+ } else {
+ s = value;
+ if (s < 0)
+ return '?';
+ value1 = s;
+ }
+ if (value != value1)
+ return '?';
+
+ /* Accumulate fraction, to ns precision. */
+ p++;
+ ns = *p++ - '0';
+ for (digits = 2;
+ digits <= LOG10_BILLION; digits++) {
+ ns *= 10;
+ if (c_isdigit (*p))
+ ns += *p++ - '0';
+ }
+
+ /* Skip excess digits, truncating toward
+ * -Infinity.
+ */
+ if (sign < 0)
+ for (; c_isdigit (*p); p++)
+ if (*p != '0') {
+ ns++;
+ break;
+ }
+ while (c_isdigit (*p))
+ p++;
+
+ /* Adjust to the timespec convention, which is
+ * that tv_nsec is always a positive offset even
+ * if tv_sec is negative.
+ */
+ if (sign < 0 && ns) {
+ s--;
+ if (! (s < 0))
+ return '?';
+ ns = BILLION - ns;
+ }
+
+ lvalp->timespec.tv_sec = s;
+ lvalp->timespec.tv_nsec = ns;
+ pc->input = p;
+ return
+ sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
+ } else {
+ lvalp->textintval.negative = sign < 0;
+ if (sign < 0) {
+ lvalp->textintval.value = - value;
+ if (0 < lvalp->textintval.value)
+ return '?';
+ } else {
+ lvalp->textintval.value = value;
+ if (lvalp->textintval.value < 0)
+ return '?';
+ }
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ }
+
+ if (c_isalpha (c)) {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (c_isalpha (c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp) {
+ return '?';
+ }
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return to_uchar (*pc->input++);
+
+ count = 0;
+ do {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int yyerror(parser_control const *pc __attribute__((__unused__)),
+ char const *s __attribute__((__unused__)))
+{
+ return 0;
+}
+
+/**
+ * If *TM0 is the old and *TM1 is the new value of a struct tm after
+ * passing it to mktime, return 1 if it's OK that mktime returned T.
+ * It's not OK if *TM0 has out-of-range members.
+ */
+
+static int mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t)
+{
+ if (t == (time_t) -1) {
+ /**
+ * Guard against falsely reporting an error when parsing a
+ * timestamp that happens to equal (time_t) -1, on a host that
+ * supports such a timestamp.
+ */
+ tm1 = localtime (&t);
+ if (!tm1)
+ return 0;
+ }
+
+ return ! ((tm0->tm_sec ^ tm1->tm_sec)
+ | (tm0->tm_min ^ tm1->tm_min)
+ | (tm0->tm_hour ^ tm1->tm_hour)
+ | (tm0->tm_mday ^ tm1->tm_mday)
+ | (tm0->tm_mon ^ tm1->tm_mon)
+ | (tm0->tm_year ^ tm1->tm_year));
+}
+
+/**
+ * A reasonable upper bound for the size of ordinary TZ strings.
+ * Use heap allocation if TZ's length exceeds this.
+ */
+enum { TZBUFSIZE = 100 };
+
+/**
+ * Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
+ * otherwise.
+ */
+static char * get_tz(char tzbuf[TZBUFSIZE])
+{
+ char *tz = getenv ("TZ");
+ if (tz) {
+ size_t tzsize = strlen (tz) + 1;
+ tz = (tzsize <= TZBUFSIZE
+ ? memcpy (tzbuf, tz, tzsize)
+ : strdup (tz));
+ }
+ return tz;
+}
+
+/**
+ * Parse a date/time string, storing the resulting time value into *result.
+ * The string itself is pointed to by *p. Return 1 if successful.
+ * *p can be an incomplete or relative time specification; if so, use
+ * *now as the basis for the returned time.
+ */
+int parse_date(struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ intmax_t Start_ns;
+ struct tm const *tmp;
+ struct tm tm;
+ struct tm tm0;
+ parser_control pc;
+ struct timespec gettime_buffer;
+ unsigned char c;
+ int tz_was_altered = 0;
+ char *tz0 = NULL;
+ char tz0buf[TZBUFSIZE];
+ int ok = 1;
+ struct timeval tv;
+
+ if (! now) {
+ gettimeofday (&tv, NULL);
+ gettime_buffer.tv_sec = tv.tv_sec;
+ gettime_buffer.tv_nsec = tv.tv_usec * 1000;
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = localtime (&now->tv_sec);
+ if (! tmp)
+ return 0;
+
+ while (c = *p, c_isspace (c))
+ p++;
+
+ if (strncmp (p, "TZ=\"", 4) == 0) {
+ char const *tzbase = p + 4;
+ size_t tzsize = 1;
+ char const *s;
+
+ for (s = tzbase; *s; s++, tzsize++)
+ if (*s == '\\') {
+ s++;
+ if (! (*s == '\\' || *s == '"'))
+ break;
+ } else if (*s == '"') {
+ char *z;
+ char *tz1;
+ char tz1buf[TZBUFSIZE];
+ int large_tz = TZBUFSIZE < tzsize;
+ int setenv_ok;
+
+ tz0 = get_tz (tz0buf);
+ if (!tz0)
+ goto fail;
+
+ if (large_tz) {
+ z = tz1 = malloc (tzsize);
+ if (!tz1)
+ goto fail;
+ } else
+ z = tz1 = tz1buf;
+
+ for (s = tzbase; *s != '"'; s++)
+ *z++ = *(s += *s == '\\');
+ *z = '\0';
+ setenv_ok = setenv ("TZ", tz1, 1) == 0;
+ if (large_tz)
+ free (tz1);
+ if (!setenv_ok)
+ goto fail;
+ tz_was_altered = 1;
+
+ p = s + 1;
+ while (c = *p, c_isspace (c))
+ p++;
+
+ break;
+ }
+ }
+
+ /**
+ * As documented, be careful to treat the empty string just like
+ * a date string of "0". Without this, an empty string would be
+ * declared invalid when parsed during a DST transition.
+ */
+ if (*p == '\0')
+ p = "0";
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year;
+ pc.year.value += TM_YEAR_BASE;
+ pc.year.digits = 0;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = 0;
+ pc.rels_seen = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+
+#if HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = NULL;
+
+ /**
+ * Probe the names used in the next three calendar quarters, looking
+ * for a tm_isdst different from the one we already have.
+ */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++) {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm const *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst
+ != pc.local_time_zone_table[0].value) {
+ {
+ pc.local_time_zone_table[1].name
+ = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type
+ = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value
+ = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name
+ = NULL;
+ }
+ break;
+ }
+ }
+ }
+#else
+#if HAVE_TZNAME
+ {
+# if !HAVE_DECL_TZNAME
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++) {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = NULL;
+ }
+#else
+ pc.local_time_zone_table[0].name = NULL;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name)) {
+ /**
+ * This locale uses the same abbreviation for standard and
+ * daylight times. So if we see that abbreviation, we don't
+ * know whether it's daylight time.
+ */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = NULL;
+ }
+
+ if (yyparse (&pc) != 0) {
+ goto fail;
+ }
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else {
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen
+ | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen))) {
+ goto fail;
+ }
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
+ tm.tm_mon = pc.month - 1;
+ tm.tm_mday = pc.day;
+ if (pc.times_seen || (pc.rels_seen &&
+ ! pc.dates_seen && ! pc.days_seen)) {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0) {
+ goto fail;
+ }
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds.tv_sec;
+ } else {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ pc.seconds.tv_nsec = 0;
+ }
+
+ /**
+ * Let mktime deduce tm_isdst if we have an absolute timestamp.
+ */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ tm.tm_isdst = -1;
+
+ /**
+ * But if the input explicitly specifies local time with or
+ * without DST, give mktime that information.
+ */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ if (! pc.zones_seen) {
+ goto fail;
+ } else {
+ /** Guard against falsely reporting errors near
+ * the time_t boundaries when parsing times in
+ * other time zones. For example, suppose the
+ * input string "1969-12-31 23:00:00 -0100", the
+ * current time zone is 8 hours ahead of UTC,
+ * and the min time_t value is 1970-01-01
+ * 00:00:00 UTC. Then the min localtime value
+ * is 1970-01-01 08:00:00, and mktime will
+ * therefore fail on 1969-12-31 23:00:00. To
+ * work around the problem, set the time zone to
+ * 1 hour behind UTC temporarily by setting
+ * TZ="XXX1:00" and try mktime again.
+ */
+
+ intmax_t time_zone = pc.time_zone;
+
+ intmax_t abs_time_zone = time_zone < 0
+ ? - time_zone : time_zone;
+
+ intmax_t abs_time_zone_hour
+ = abs_time_zone / 60;
+
+ int abs_time_zone_min = abs_time_zone % 60;
+
+ char tz1buf[sizeof "XXX+0:00"
+ + sizeof pc.time_zone
+ * CHAR_BIT / 3];
+
+ if (!tz_was_altered)
+ tz0 = get_tz (tz0buf);
+ sprintf (tz1buf, "XXX%s%jd:%02d",
+ &"-"[time_zone < 0],
+ abs_time_zone_hour,
+ abs_time_zone_min);
+ if (setenv ("TZ", tz1buf, 1) != 0) {
+ goto fail;
+ }
+ tz_was_altered = 1;
+ tm = tm0;
+ Start = mktime (&tm);
+ if (! mktime_ok (&tm0, &tm, Start)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (pc.days_seen && ! pc.dates_seen) {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7 + 7
+ * (pc.day_ordinal
+ - (0 < pc.day_ordinal
+ && tm.tm_wday != pc.day_number)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+ /* Add relative date. */
+ if (pc.rel.year | pc.rel.month | pc.rel.day) {
+ int year = tm.tm_year + pc.rel.year;
+ int month = tm.tm_mon + pc.rel.month;
+ int day = tm.tm_mday + pc.rel.day;
+ if (((year < tm.tm_year) ^ (pc.rel.year < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel.day < 0))) {
+ goto fail;
+ }
+ tm.tm_year = year;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_hour = tm0.tm_hour;
+ tm.tm_min = tm0.tm_min;
+ tm.tm_sec = tm0.tm_sec;
+ tm.tm_isdst = tm0.tm_isdst;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1) {
+ goto fail;
+ }
+ }
+
+ /**
+ * The only "output" of this if-block is an updated Start value,
+ * so this block must follow others that clobber Start.
+ */
+ if (pc.zones_seen) {
+ intmax_t delta = pc.time_zone * 60;
+ time_t t1;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ time_t t = Start;
+ struct tm const *gmt = gmtime (&t);
+ if (! gmt) {
+ goto fail;
+ }
+ delta -= tm_diff (&tm, gmt);
+#endif
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0)) {
+ goto fail; /* time_t overflow */
+ }
+ Start = t1;
+ }
+
+ /**
+ * Add relative hours, minutes, and seconds. On hosts that
+ * support leap seconds, ignore the possibility of leap seconds;
+ * e.g., "+ 10 minutes" adds 600 seconds, even if one of them is
+ * a leap second. Typically this is not what the user wants,
+ * but it's too hard to do it the other way, because the time
+ * zone indicator must be applied before relative times, and if
+ * mktime is applied again the time zone will be lost.
+ */
+ intmax_t sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
+ intmax_t normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+ time_t t0 = Start;
+ intmax_t d1 = 60 * 60 * pc.rel.hour;
+ time_t t1 = t0 + d1;
+ intmax_t d2 = 60 * pc.rel.minutes;
+ time_t t2 = t1 + d2;
+ time_t d3 = pc.rel.seconds;
+ time_t t3 = t2 + d3;
+ intmax_t d4 = (sum_ns - normalized_ns) / BILLION;
+ time_t t4 = t3 + d4;
+ time_t t5 = t4;
+
+ if ((d1 / (60 * 60) ^ pc.rel.hour)
+ | (d2 / 60 ^ pc.rel.minutes)
+ | ((t1 < t0) ^ (d1 < 0))
+ | ((t2 < t1) ^ (d2 < 0))
+ | ((t3 < t2) ^ (d3 < 0))
+ | ((t4 < t3) ^ (d4 < 0))
+ | (t5 != t4)) {
+ goto fail;
+ }
+ result->tv_sec = t5;
+ result->tv_nsec = normalized_ns;
+ }
+
+ goto done;
+
+ fail:
+ ok = 0;
+ done:
+ if (tz_was_altered)
+ ok &= (tz0 ? setenv ("TZ", tz0, 1)
+ : unsetenv ("TZ")) == 0;
+ if (tz0 != tz0buf)
+ free (tz0);
+ return ok;
+}
diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c
new file mode 100644
index 0000000..fb94868
--- /dev/null
+++ b/sys-utils/hwclock-rtc.c
@@ -0,0 +1,454 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * rtc.c - Use /dev/rtc for clock access
+ */
+#include <asm/ioctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "monotonic.h"
+#include "nls.h"
+
+#include "hwclock.h"
+
+/*
+ * Get defines for rtc stuff.
+ *
+ * Getting the rtc defines is nontrivial. The obvious way is by including
+ * <linux/mc146818rtc.h> but that again includes <asm/io.h> which again
+ * includes ... and on sparc and alpha this gives compilation errors for
+ * many kernel versions. So, we give the defines ourselves here. Moreover,
+ * some Sparc person decided to be incompatible, and used a struct rtc_time
+ * different from that used in mc146818rtc.h.
+ */
+
+/*
+ * On Sparcs, there is a <asm/rtc.h> that defines different ioctls (that are
+ * required on my machine). However, this include file does not exist on
+ * other architectures.
+ */
+/* One might do:
+#ifdef __sparc__
+# include <asm/rtc.h>
+#endif
+ */
+#ifdef __sparc__
+/* The following is roughly equivalent */
+struct sparc_rtc_time
+{
+ int sec; /* Seconds 0-59 */
+ int min; /* Minutes 0-59 */
+ int hour; /* Hour 0-23 */
+ int dow; /* Day of the week 1-7 */
+ int dom; /* Day of the month 1-31 */
+ int month; /* Month of year 1-12 */
+ int year; /* Year 0-99 */
+};
+#define RTCGET _IOR('p', 20, struct sparc_rtc_time)
+#define RTCSET _IOW('p', 21, struct sparc_rtc_time)
+#endif
+
+/*
+ * struct rtc_time is present since 1.3.99.
+ * Earlier (since 1.3.89), a struct tm was used.
+ */
+struct linux_rtc_time {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+ int tm_wday;
+ int tm_yday;
+ int tm_isdst;
+};
+
+/* RTC_RD_TIME etc have this definition since 1.99.9 (pre2.0-9) */
+#ifndef RTC_RD_TIME
+# define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time)
+# define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time)
+# define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */
+# define RTC_UIE_OFF _IO('p', 0x04) /* Update int. enable off */
+#endif
+
+/* RTC_EPOCH_READ and RTC_EPOCH_SET are present since 2.0.34 and 2.1.89 */
+#ifndef RTC_EPOCH_READ
+# define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */
+# define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */
+#endif
+
+/*
+ * /dev/rtc is conventionally chardev 10/135
+ * ia64 uses /dev/efirtc, chardev 10/136
+ * devfs (obsolete) used /dev/misc/... for miscdev
+ * new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN
+ * ... so we need an overridable default
+ */
+
+/* default or user defined dev (by hwclock --rtc=<path>) */
+static const char *rtc_dev_name;
+static int rtc_dev_fd = -1;
+
+static void close_rtc(void)
+{
+ if (rtc_dev_fd != -1)
+ close(rtc_dev_fd);
+ rtc_dev_fd = -1;
+}
+
+static int open_rtc(const struct hwclock_control *ctl)
+{
+ static const char *fls[] = {
+#ifdef __ia64__
+ "/dev/efirtc",
+ "/dev/misc/efirtc",
+#endif
+ "/dev/rtc0",
+ "/dev/rtc",
+ "/dev/misc/rtc"
+ };
+ size_t i;
+
+ if (rtc_dev_fd != -1)
+ return rtc_dev_fd;
+
+ /* --rtc option has been given */
+ if (ctl->rtc_dev_name) {
+ rtc_dev_name = ctl->rtc_dev_name;
+ rtc_dev_fd = open(rtc_dev_name, O_RDONLY);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fls); i++) {
+ if (ctl->verbose)
+ printf(_("Trying to open: %s\n"), fls[i]);
+ rtc_dev_fd = open(fls[i], O_RDONLY);
+
+ if (rtc_dev_fd < 0) {
+ if (errno == ENOENT || errno == ENODEV)
+ continue;
+ if (ctl->verbose)
+ warn(_("cannot open %s"), fls[i]);
+ }
+ rtc_dev_name = fls[i];
+ break;
+ }
+ if (rtc_dev_fd < 0)
+ rtc_dev_name = *fls; /* default for error messages */
+ }
+ if (rtc_dev_fd != -1)
+ atexit(close_rtc);
+ return rtc_dev_fd;
+}
+
+static int open_rtc_or_exit(const struct hwclock_control *ctl)
+{
+ int rtc_fd = open_rtc(ctl);
+
+ if (rtc_fd < 0) {
+ warn(_("cannot open rtc device"));
+ hwclock_exit(ctl, EXIT_FAILURE);
+ }
+ return rtc_fd;
+}
+
+static int do_rtc_read_ioctl(int rtc_fd, struct tm *tm)
+{
+ int rc = -1;
+ char *ioctlname;
+#ifdef __sparc__
+ /* some but not all sparcs use a different ioctl and struct */
+ struct sparc_rtc_time stm;
+#endif
+
+ ioctlname = "RTC_RD_TIME";
+ rc = ioctl(rtc_fd, RTC_RD_TIME, tm);
+
+#ifdef __sparc__
+ if (rc == -1) { /* sparc sbus */
+ ioctlname = "RTCGET";
+ rc = ioctl(rtc_fd, RTCGET, &stm);
+ if (rc == 0) {
+ tm->tm_sec = stm.sec;
+ tm->tm_min = stm.min;
+ tm->tm_hour = stm.hour;
+ tm->tm_mday = stm.dom;
+ tm->tm_mon = stm.month - 1;
+ tm->tm_year = stm.year - 1900;
+ tm->tm_wday = stm.dow - 1;
+ tm->tm_yday = -1; /* day in the year */
+ }
+ }
+#endif
+
+ if (rc == -1) {
+ warn(_("ioctl(%s) to %s to read the time failed"),
+ ioctlname, rtc_dev_name);
+ return -1;
+ }
+
+ tm->tm_isdst = -1; /* don't know whether it's dst */
+ return 0;
+}
+
+/*
+ * Wait for the top of a clock tick by reading /dev/rtc in a busy loop
+ * until we see it. This function is used for rtc drivers without ioctl
+ * interrupts. This is typical on an Alpha, where the Hardware Clock
+ * interrupts are used by the kernel for the system clock, so aren't at
+ * the user's disposal.
+ */
+static int busywait_for_rtc_clock_tick(const struct hwclock_control *ctl,
+ const int rtc_fd)
+{
+ struct tm start_time;
+ /* The time when we were called (and started waiting) */
+ struct tm nowtime;
+ int rc;
+ struct timeval begin, now;
+
+ if (ctl->verbose) {
+ printf("ioctl(%d, RTC_UIE_ON, 0): %s\n",
+ rtc_fd, strerror(errno));
+ printf(_("Waiting in loop for time from %s to change\n"),
+ rtc_dev_name);
+ }
+
+ if (do_rtc_read_ioctl(rtc_fd, &start_time))
+ return 1;
+
+ /*
+ * Wait for change. Should be within a second, but in case
+ * something weird happens, we have a time limit (1.5s) on this loop
+ * to reduce the impact of this failure.
+ */
+ gettime_monotonic(&begin);
+ do {
+ rc = do_rtc_read_ioctl(rtc_fd, &nowtime);
+ if (rc || start_time.tm_sec != nowtime.tm_sec)
+ break;
+ gettime_monotonic(&now);
+ if (time_diff(now, begin) > 1.5) {
+ warnx(_("Timed out waiting for time change."));
+ return 1;
+ }
+ } while (1);
+
+ if (rc)
+ return 1;
+ return 0;
+}
+
+/*
+ * Same as synchronize_to_clock_tick(), but just for /dev/rtc.
+ */
+static int synchronize_to_clock_tick_rtc(const struct hwclock_control *ctl)
+{
+ int rtc_fd; /* File descriptor of /dev/rtc */
+ int ret = 1;
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd == -1) {
+ warn(_("cannot open rtc device"));
+ return ret;
+ }
+
+ /* Turn on update interrupts (one per second) */
+ int rc = ioctl(rtc_fd, RTC_UIE_ON, 0);
+
+ if (rc != -1) {
+ /*
+ * Just reading rtc_fd fails on broken hardware: no
+ * update interrupt comes and a bootscript with a
+ * hwclock call hangs
+ */
+ fd_set rfds;
+ struct timeval tv;
+
+ /*
+ * Wait up to ten seconds for the next update
+ * interrupt
+ */
+ FD_ZERO(&rfds);
+ FD_SET(rtc_fd, &rfds);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+ rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv);
+ if (0 < rc)
+ ret = 0;
+ else if (rc == 0) {
+ warnx(_("select() to %s to wait for clock tick timed out"),
+ rtc_dev_name);
+ } else
+ warn(_("select() to %s to wait for clock tick failed"),
+ rtc_dev_name);
+ /* Turn off update interrupts */
+ rc = ioctl(rtc_fd, RTC_UIE_OFF, 0);
+ if (rc == -1)
+ warn(_("ioctl() to %s to turn off update interrupts failed"),
+ rtc_dev_name);
+ } else if (errno == ENOTTY || errno == EINVAL) {
+ /* rtc ioctl interrupts are unimplemented */
+ ret = busywait_for_rtc_clock_tick(ctl, rtc_fd);
+ } else
+ warn(_("ioctl(%d, RTC_UIE_ON, 0) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ return ret;
+}
+
+static int read_hardware_clock_rtc(const struct hwclock_control *ctl,
+ struct tm *tm)
+{
+ int rtc_fd, rc;
+
+ rtc_fd = open_rtc_or_exit(ctl);
+
+ /* Read the RTC time/date, return answer via tm */
+ rc = do_rtc_read_ioctl(rtc_fd, tm);
+
+ return rc;
+}
+
+/*
+ * Set the Hardware Clock to the broken down time <new_broken_time>. Use
+ * ioctls to "rtc" device /dev/rtc.
+ */
+static int set_hardware_clock_rtc(const struct hwclock_control *ctl,
+ const struct tm *new_broken_time)
+{
+ int rc = -1;
+ int rtc_fd;
+ char *ioctlname;
+
+ rtc_fd = open_rtc_or_exit(ctl);
+
+ ioctlname = "RTC_SET_TIME";
+ rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time);
+
+#ifdef __sparc__
+ if (rc == -1) { /* sparc sbus */
+ struct sparc_rtc_time stm;
+
+ stm.sec = new_broken_time->tm_sec;
+ stm.min = new_broken_time->tm_min;
+ stm.hour = new_broken_time->tm_hour;
+ stm.dom = new_broken_time->tm_mday;
+ stm.month = new_broken_time->tm_mon + 1;
+ stm.year = new_broken_time->tm_year + 1900;
+ stm.dow = new_broken_time->tm_wday + 1;
+
+ ioctlname = "RTCSET";
+ rc = ioctl(rtc_fd, RTCSET, &stm);
+ }
+#endif
+
+ if (rc == -1) {
+ warn(_("ioctl(%s) to %s to set the time failed"),
+ ioctlname, rtc_dev_name);
+ hwclock_exit(ctl, EXIT_FAILURE);
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%s) was successful.\n"), ioctlname);
+
+ return 0;
+}
+
+static int get_permissions_rtc(void)
+{
+ return 0;
+}
+
+static const char *get_device_path(void)
+{
+ return rtc_dev_name;
+}
+
+static struct clock_ops rtc_interface = {
+ N_("Using the rtc interface to the clock."),
+ get_permissions_rtc,
+ read_hardware_clock_rtc,
+ set_hardware_clock_rtc,
+ synchronize_to_clock_tick_rtc,
+ get_device_path,
+};
+
+/* return &rtc if /dev/rtc can be opened, NULL otherwise */
+struct clock_ops *probe_for_rtc_clock(const struct hwclock_control *ctl)
+{
+ const int rtc_fd = open_rtc(ctl);
+
+ if (rtc_fd < 0)
+ return NULL;
+ return &rtc_interface;
+}
+
+#ifdef __alpha__
+/*
+ * Get the Hardware Clock epoch setting from the kernel.
+ */
+int get_epoch_rtc(const struct hwclock_control *ctl, unsigned long *epoch_p)
+{
+ int rtc_fd;
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warn(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) {
+ warn(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ return 1;
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s succeeded.\n"),
+ rtc_fd, rtc_dev_name);
+
+ return 0;
+}
+
+/*
+ * Set the Hardware Clock epoch in the kernel.
+ */
+int set_epoch_rtc(const struct hwclock_control *ctl)
+{
+ int rtc_fd;
+ unsigned long epoch;
+
+ epoch = strtoul(ctl->epoch_option, NULL, 10);
+
+ /* There were no RTC clocks before 1900. */
+ if (epoch < 1900 || epoch == ULONG_MAX) {
+ warnx(_("invalid epoch '%s'."), ctl->epoch_option);
+ return 1;
+ }
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warn(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) {
+ warn(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s failed"),
+ rtc_fd, epoch, rtc_dev_name);
+ return 1;
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s succeeded.\n"),
+ rtc_fd, epoch, rtc_dev_name);
+
+ return 0;
+}
+#endif /* __alpha__ */
diff --git a/sys-utils/hwclock.8 b/sys-utils/hwclock.8
new file mode 100644
index 0000000..5cd701f
--- /dev/null
+++ b/sys-utils/hwclock.8
@@ -0,0 +1,993 @@
+.\" hwclock.8.in -- man page for util-linux' hwclock
+.\"
+.\" 2015-01-07 J William Piggott
+.\" Authored new section: DATE-TIME CONFIGURATION.
+.\" Subsections: Keeping Time..., LOCAL vs UTC, POSIX vs 'RIGHT'.
+.\"
+.TH HWCLOCK 8 "July 2017" "util-linux" "System Administration"
+.SH NAME
+hwclock \- time clocks utility
+.SH SYNOPSIS
+.B hwclock
+.RI [ function ]
+.RI [ option ...]
+.
+.SH DESCRIPTION
+.B hwclock
+is an administration tool for the time clocks. It can: display the
+Hardware Clock time; set the Hardware Clock to a specified time; set the
+Hardware Clock from the System Clock; set the System Clock from the
+Hardware Clock; compensate for Hardware Clock drift; correct the System
+Clock timescale; set the kernel's timezone, NTP timescale, and epoch
+(Alpha only); and predict future
+Hardware Clock values based on its drift rate.
+.PP
+Since v2.26 important changes were made to the
+.B \-\-hctosys
+function and the
+.B \-\-directisa
+option, and a new option
+.B \-\-update\-drift
+was added. See their respective descriptions below.
+.
+.SH FUNCTIONS
+The following functions are mutually exclusive, only one can be given at
+a time. If none is given, the default is \fB\-\-show\fR.
+.TP
+.B \-a, \-\-adjust
+Add or subtract time from the Hardware Clock to account for systematic
+drift since the last time the clock was set or adjusted. See the
+discussion below, under
+.BR "The Adjust Function" .
+.
+.TP
+.B \-\-getepoch
+.TQ
+.B \-\-setepoch
+These functions are for Alpha machines only, and are only available
+through the Linux kernel RTC driver.
+.sp
+They are used to read and set the kernel's Hardware Clock epoch value.
+Epoch is the number of years into AD to which a zero year value in the
+Hardware Clock refers. For example, if the machine's BIOS sets the year
+counter in the Hardware Clock to contain the number of full years since
+1952, then the kernel's Hardware Clock epoch value must be 1952.
+.sp
+The \fB\%\-\-setepoch\fR function requires using the
+.B \%\-\-epoch
+option to specify the year. For example:
+.RS
+.IP "" 4
+.B hwclock\ \-\-setepoch\ \-\-epoch=1952
+.PP
+The RTC driver attempts to guess the correct epoch value, so setting it
+may not be required.
+.PP
+This epoch value is used whenever
+.B \%hwclock
+reads or sets the Hardware Clock on an Alpha machine. For ISA machines
+the kernel uses the fixed Hardware Clock epoch of 1900.
+.RE
+.
+.TP
+.B \-\-predict
+Predict what the Hardware Clock will read in the future based upon the
+time given by the
+.B \-\-date
+option and the information in
+.IR /etc/adjtime .
+This is useful, for example, to account for drift when setting a
+Hardware Clock wakeup (aka alarm). See
+.BR \%rtcwake (8).
+.sp
+Do not use this function if the Hardware Clock is being modified by
+anything other than the current operating system's
+.B \%hwclock
+command, such as \%'11\ minute\ mode' or from dual-booting another OS.
+.
+.TP
+.BR \-r , \ \-\-show
+.TQ
+.B \-\-get
+.br
+Read the Hardware Clock and print its time to standard output in the
+.B ISO 8601
+format.
+The time shown is always in local time, even if you keep your Hardware Clock
+in UTC. See the
+.B \%\-\-localtime
+option.
+.sp
+Showing the Hardware Clock time is the default when no function is specified.
+.sp
+The
+.B \-\-get
+function also applies drift correction to the time read, based upon the
+information in
+.IR /etc/adjtime .
+Do not use this function if the Hardware Clock is being modified by
+anything other than the current operating system's
+.B \%hwclock
+command, such as \%'11\ minute\ mode' or from dual-booting another OS.
+.
+.TP
+.BR \-s , \ \-\-hctosys
+Set the System Clock from the Hardware Clock. The time read from the Hardware
+Clock is compensated to account for systematic drift before using it to set the
+System Clock. See the discussion below, under
+.BR "The Adjust Function" .
+.sp
+The System Clock must be kept in the UTC timescale for date-time
+applications to work correctly in conjunction with the timezone configured
+for the system. If the Hardware Clock is kept in local time then the time read
+from it must be shifted to the UTC timescale before using it to set the System
+Clock. The
+.B \%\-\-hctosys
+function does this based upon the information in the
+.I /etc/adjtime
+file or the command line arguments
+.BR \%\-\-localtime " and " \-\-utc .
+Note: no daylight saving adjustment is made. See the discussion below, under
+.BR "LOCAL vs UTC" .
+.sp
+The kernel also keeps a timezone value, the
+.B \%\-\-hctosys
+function sets it to the timezone configured for the system. The system
+timezone is configured by the TZ environment variable or the
+.I \%/etc/localtime
+file, as
+.BR \%tzset (3)
+would interpret them.
+The obsolete tz_dsttime field of the kernel's timezone value is set
+to zero. (For details on what this field used to mean, see
+.BR \%settimeofday (2).)
+.sp
+When used in a startup script, making the
+.B \%\-\-hctosys
+function the first caller of
+.BR \%settimeofday (2)
+from boot, it will set the NTP \%'11\ minute\ mode' timescale via the
+.I \%persistent_clock_is_local
+kernel variable. If the Hardware Clock's timescale configuration is
+changed then a reboot is required to inform the kernel. See the
+discussion below, under
+.BR "Automatic Hardware Clock Synchronization by the Kernel" .
+.sp
+This is a good function to use in one of the system startup scripts before the
+file systems are mounted read/write.
+.sp
+This function should never be used on a running system. Jumping system time
+will cause problems, such as corrupted filesystem timestamps. Also, if
+something has changed the Hardware Clock, like NTP's \%'11\ minute\ mode', then
+.B \%\-\-hctosys
+will set the time incorrectly by including drift compensation.
+.sp
+Drift compensation can be inhibited by setting the drift factor in
+.I /etc/adjtime
+to zero. This setting will be persistent as long as the
+.BR \%\-\-update\-drift " option is not used with " \%\-\-systohc
+at shutdown (or anywhere else). Another way to inhibit this is by using the
+.BR \%\-\-noadjfile " option when calling the " \%\-\-hctosys
+function. A third method is to delete the
+.IR /etc/adjtime " file."
+.B Hwclock
+will then default to using the UTC timescale for the Hardware Clock. If
+the Hardware Clock is ticking local time it will need to be defined in
+the file. This can be done by calling
+.BR hwclock\ \-\-localtime\ \-\-adjust ;
+when the file is not present this command will not actually
+adjust the Clock, but it will create the file with local time
+configured, and a drift factor of zero.
+.sp
+A condition under which inhibiting
+.BR hwclock 's
+drift correction may be desired is when dual-booting multiple operating
+systems. If while this instance of Linux is stopped, another OS changes
+the Hardware Clock's value, then when this instance is started again the
+drift correction applied will be incorrect.
+.sp
+.RB "For " hwclock 's
+drift correction to work properly it is imperative that nothing changes
+the Hardware Clock while its Linux instance is not running.
+.
+.TP
+.B \-\-set
+Set the Hardware Clock to the time given by the
+.B \-\-date
+option, and update the timestamps in
+.IR /etc/adjtime .
+With the
+.B \%\-\-update-drift
+option also (re)calculate the drift factor. Try it without the option if
+.BR \%\-\-set " fails. See " \%\-\-update-drift " below."
+.
+.TP
+.B \-\-systz
+This is an alternate to the
+.B \%\-\-hctosys
+function that does not read the Hardware Clock nor set the System Clock;
+consequently there is not any drift correction. It is intended to be
+used in a startup script on systems with kernels above version 2.6 where
+you know the System Clock has been set from the Hardware Clock by the
+kernel during boot.
+.sp
+It does the following things that are detailed above in the
+.BR \%\-\-hctosys " function:"
+.RS
+.IP \(bu 2
+Corrects the System Clock timescale to UTC as needed. Only instead of
+accomplishing this by setting the System Clock,
+.B hwclock
+simply informs the kernel and it handles the change.
+.IP \(bu 2
+Sets the kernel's NTP \%'11\ minute\ mode' timescale.
+.IP \(bu 2
+Sets the kernel's timezone.
+.PP
+The first two are only available on the first call of
+.BR \%settimeofday (2)
+after boot. Consequently this option only makes sense when used in a
+startup script. If the Hardware Clocks timescale configuration is
+changed then a reboot would be required to inform the kernel.
+.RE
+.
+.TP
+.BR \-w , \ \-\-systohc
+Set the Hardware Clock from the System Clock, and update the timestamps in
+.IR /etc/adjtime .
+With the
+.B \%\-\-update-drift
+option also (re)calculate the drift factor. Try it without the option if
+.BR \%\-\-systohc " fails. See " \%\-\-update-drift " below."
+.
+.TP
+.BR \-V , \ \-\-version
+Display version information and exit.
+.
+.TP
+.BR \-h , \ \-\-help
+Display help text and exit.
+.
+.SH OPTIONS
+.
+.TP
+.BI \-\-adjfile= filename
+.RI "Override the default " /etc/adjtime " file path."
+.
+.TP
+.BI \%\-\-date= date_string
+This option must be used with the
+.B \-\-set
+or
+.B \%\-\-predict
+functions, otherwise it is ignored.
+.RS
+.IP "" 4
+.B "hwclock\ \-\-set\ \-\-date='16:45'"
+.IP "" 4
+.B "hwclock\ \-\-predict\ \-\-date='2525-08-14\ 07:11:05'"
+.PP
+The argument must be in local time, even if you keep your Hardware Clock in
+UTC. See the
+.B \%\-\-localtime
+option. Therefore, the argument should not include any timezone information.
+It also should not be a relative time like "+5 minutes", because
+.BR \%hwclock 's
+precision depends upon correlation between the argument's value and when the
+enter key is pressed. Fractional seconds are silently dropped. This option is
+capable of understanding many time and date formats, but the previous
+parameters should be observed.
+.RE
+.
+.TP
+.BI \%\-\-delay= seconds
+This option can be used to overwrite the internally used delay
+when setting the clock time. The
+default is 0.5 (500ms) for rtc_cmos, for another RTC types the delay is 0. If
+RTC type is impossible to determine (from sysfs) then it defaults also to 0.5
+to be backwardly compatible.
+.RS
+.PP
+The 500ms default is based on commonly used MC146818A-compatible (x86) hardware clock. This
+Hardware Clock can only be set to any integer time plus one half second. The
+integer time is required because there is no interface to set or get a
+fractional second. The additional half second delay is because the Hardware
+Clock updates to the following second precisely 500 ms after setting the new
+time. Unfortunately, this behavior is hardware specific and in same cases
+another delay is required.
+.RE
+.
+.TP
+.BR \-D ", " \-\-debug
+.RB Use\ \-\-verbose .
+.RB The\ \%\-\-debug\ option
+has been deprecated and may be repurposed or removed in a future release.
+.
+.TP
+.B \-\-directisa
+This option is meaningful for ISA compatible machines in the x86 and
+x86_64 family. For other machines, it has no effect. This option tells
+.B \%hwclock
+to use explicit I/O instructions to access the Hardware Clock.
+Without this option,
+.B \%hwclock
+will use the rtc device file, which it assumes to be driven by the Linux
+RTC device driver. As of v2.26 it will no longer automatically use
+directisa when the rtc driver is unavailable; this was causing an unsafe
+condition that could allow two processes to access the Hardware Clock at
+the same time. Direct hardware access from userspace should only be
+used for testing, troubleshooting, and as a last resort when all other
+methods fail. See the
+.BR \-\-rtc " option."
+.
+.TP
+.BI \-\-epoch= year
+This option is required when using the
+.BR \%\-\-setepoch \ function.
+.RI "The minimum " year
+value is 1900. The maximum is system dependent
+.RB ( ULONG_MAX\ -\ 1 ).
+.
+.TP
+.BR \-f , \ \-\-rtc=\fIfilename\fR
+.RB "Override " \%hwclock 's
+default rtc device file name. Otherwise it will
+use the first one found in this order:
+.in +4
+.br
+.I /dev/rtc0
+.br
+.I /dev/rtc
+.br
+.I /dev/misc/rtc
+.br
+.in
+.RB "For " IA-64:
+.in +4
+.br
+.I /dev/efirtc
+.br
+.I /dev/misc/efirtc
+.in
+.
+.TP
+.BR \-l , \ \-\-localtime
+.TQ
+.BR \-u ", " \-\-utc
+Indicate which timescale the Hardware Clock is set to.
+.sp
+The Hardware Clock may be configured to use either the UTC or the local
+timescale, but nothing in the clock itself says which alternative is
+being used. The
+.BR \%\-\-localtime " or " \-\-utc
+options give this information to the
+.B \%hwclock
+command. If you specify the wrong one (or specify neither and take a
+wrong default), both setting and reading the Hardware Clock will be
+incorrect.
+.sp
+If you specify neither
+.BR \-\-utc " nor " \%\-\-localtime
+then the one last given with a set function
+.RB ( \-\-set ", " \%\-\-systohc ", or " \%\-\-adjust ),
+as recorded in
+.IR /etc/adjtime ,
+will be used. If the adjtime file doesn't exist, the default is UTC.
+.sp
+Note: daylight saving time changes may be inconsistent when the
+Hardware Clock is kept in local time. See the discussion below, under
+.BR "LOCAL vs UTC" .
+.
+.TP
+.B \-\-noadjfile
+Disable the facilities provided by
+.IR /etc/adjtime .
+.B \%hwclock
+will not read nor write to that file with this option. Either
+.BR \-\-utc " or " \%\-\-localtime
+must be specified when using this option.
+.
+.TP
+.B \-\-test
+Do not actually change anything on the system, that is, the Clocks or
+.I /etc/adjtime
+.RB ( \%\-\-verbose
+is implicit with this option).
+.
+.TP
+.B \-\-update\-drift
+Update the Hardware Clock's drift factor in
+.IR /etc/adjtime .
+It can only be used with
+.BR \-\-set " or " \%\-\-systohc ,
+.sp
+A minimum four hour period between settings is required. This is to
+avoid invalid calculations. The longer the period, the more precise the
+resulting drift factor will be.
+.sp
+This option was added in v2.26, because
+it is typical for systems to call
+.B \%hwclock\ \-\-systohc
+at shutdown; with the old behaviour this would automatically
+(re)calculate the drift factor which caused several problems:
+.RS
+.IP \(bu 2
+When using NTP with an \%'11\ minute\ mode' kernel the drift factor
+would be clobbered to near zero.
+.IP \(bu 2
+It would not allow the use of 'cold' drift correction. With most
+configurations using 'cold' drift will yield favorable results. Cold,
+means when the machine is turned off which can have a significant impact
+on the drift factor.
+.IP \(bu 2
+(Re)calculating drift factor on every shutdown delivers suboptimal
+results. For example, if ephemeral conditions cause the machine to be
+abnormally hot the drift factor calculation would be out of range.
+.IP \(bu 2
+Significantly increased system shutdown times (as of v2.31 when not
+using
+.B \%\-\-update\-drift
+the RTC is not read).
+.PP
+.RB "Having " \%hwclock
+calculate the drift factor is a good starting point, but for optimal
+results it will likely need to be adjusted by directly editing the
+.I /etc/adjtime
+file. For most configurations once a machine's optimal drift factor is
+crafted it should not need to be changed. Therefore, the old behavior to
+automatically (re)calculate drift was changed and now requires this
+option to be used. See the discussion below, under
+.BR "The Adjust Function" .
+.PP
+This option requires reading the Hardware Clock before setting it. If
+it cannot be read, then this option will cause the set functions to fail.
+This can happen, for example, if the Hardware Clock is corrupted by a
+power failure. In that case, the clock must first be set without this
+option. Despite it not working, the resulting drift correction factor
+would be invalid anyway.
+.RE
+.
+.TP
+.BR \-v ", " \-\-verbose
+Display more details about what
+.B \%hwclock
+is doing internally.
+.
+.SH NOTES
+.
+.SS Clocks in a Linux System
+There are two types of date-time clocks:
+.PP
+.B The Hardware Clock:
+This clock is an independent hardware device, with its own power domain
+(battery, capacitor, etc), that operates when the machine is powered off,
+or even unplugged.
+.PP
+On an ISA compatible system, this clock is specified as part of the ISA
+standard. A control program can read or set this clock only to a whole
+second, but it can also detect the edges of the 1 second clock ticks, so
+the clock actually has virtually infinite precision.
+.PP
+This clock is commonly called the hardware clock, the real time clock,
+the RTC, the BIOS clock, and the CMOS clock. Hardware Clock, in its
+capitalized form, was coined for use by
+.BR \%hwclock .
+The Linux kernel also refers to it as the persistent clock.
+.PP
+Some non-ISA systems have a few real time clocks with
+only one of them having its own power domain.
+A very low power external I2C or SPI clock chip might be used with a
+backup battery as the hardware clock to initialize a more functional
+integrated real-time clock which is used for most other purposes.
+.PP
+.B The System Clock:
+This clock is part of the Linux kernel and is driven by
+a timer interrupt. (On an ISA machine, the timer interrupt is part of
+the ISA standard.) It has meaning only while Linux is running on the
+machine. The System Time is the number of seconds since 00:00:00
+January 1, 1970 UTC (or more succinctly, the number of seconds since
+1969 UTC). The System Time is not an integer, though. It has virtually
+infinite precision.
+.PP
+The System Time is the time that matters. The Hardware Clock's basic
+purpose is to keep time when Linux is not running so that the System
+Clock can be initialized from it at boot. Note that in DOS, for which
+ISA was designed, the Hardware Clock is the only real time clock.
+.PP
+It is important that the System Time not have any discontinuities such as
+would happen if you used the
+.BR \%date (1)
+program to set it while the system is running. You can, however, do whatever
+you want to the Hardware Clock while the system is running, and the next
+time Linux starts up, it will do so with the adjusted time from the Hardware
+Clock. Note: currently this is not possible on most systems because
+.B \%hwclock\ \-\-systohc
+is called at shutdown.
+.PP
+The Linux kernel's timezone is set by
+.BR hwclock .
+But don't be misled -- almost nobody cares what timezone the kernel
+thinks it is in. Instead, programs that care about the timezone
+(perhaps because they want to display a local time for you) almost
+always use a more traditional method of determining the timezone: They
+use the TZ environment variable or the
+.I \%/etc/localtime
+file, as explained in the man page for
+.BR \%tzset (3).
+However, some programs and fringe parts of the Linux kernel such as filesystems
+use the kernel's timezone value. An example is the vfat filesystem. If the
+kernel timezone value is wrong, the vfat filesystem will report and set the
+wrong timestamps on files. Another example is the kernel's NTP \%'11\ minute\ mode'.
+If the kernel's timezone value and/or the
+.I \%persistent_clock_is_local
+variable are wrong, then the Hardware Clock will be set incorrectly
+by \%'11\ minute\ mode'. See the discussion below, under
+.BR "Automatic Hardware Clock Synchronization by the Kernel" .
+.PP
+.B \%hwclock
+sets the kernel's timezone to the value indicated by TZ or
+.IR \%/etc/localtime " with the"
+.BR \%\-\-hctosys " or " \%\-\-systz " functions."
+.PP
+The kernel's timezone value actually consists of two parts: 1) a field
+tz_minuteswest indicating how many minutes local time (not adjusted
+for DST) lags behind UTC, and 2) a field tz_dsttime indicating
+the type of Daylight Savings Time (DST) convention that is in effect
+in the locality at the present time.
+This second field is not used under Linux and is always zero.
+See also
+.BR \%settimeofday (2).
+.
+.SS Hardware Clock Access Methods
+.B \%hwclock
+uses many different ways to get and set Hardware Clock values. The most
+normal way is to do I/O to the rtc device special file, which is
+presumed to be driven by the rtc device driver. Also, Linux systems
+using the rtc framework with udev, are capable of supporting multiple
+Hardware Clocks. This may bring about the need to override the default
+rtc device by specifying one with the
+.BR \-\-rtc " option."
+.PP
+However, this method is not always available as older systems do not
+have an rtc driver. On these systems, the method of accessing the
+Hardware Clock depends on the system hardware.
+.PP
+On an ISA compatible system,
+.B \%hwclock
+can directly access the "CMOS memory" registers that
+constitute the clock, by doing I/O to Ports 0x70 and 0x71. It does
+this with actual I/O instructions and consequently can only do it if
+running with superuser effective userid. This method may be used by
+specifying the
+.BR \%\-\-directisa " option."
+.PP
+This is a really poor method of accessing the clock, for all the
+reasons that userspace programs are generally not supposed to do
+direct I/O and disable interrupts.
+.B \%hwclock
+provides it for testing, troubleshooting, and because it may be the
+only method available on ISA systems which do not have a working rtc
+device driver.
+.SS The Adjust Function
+The Hardware Clock is usually not very accurate. However, much of its
+inaccuracy is completely predictable - it gains or loses the same amount
+of time every day. This is called systematic drift.
+.BR \%hwclock "'s " \%\-\-adjust
+function lets you apply systematic drift corrections to the
+Hardware Clock.
+.PP
+It works like this:
+.BR \%hwclock " keeps a file,"
+.IR /etc/adjtime ,
+that keeps some historical information. This is called the adjtime file.
+.PP
+Suppose you start with no adjtime file. You issue a
+.B \%hwclock\ \-\-set
+command to set the Hardware Clock to the true current time.
+.B \%hwclock
+creates the adjtime file and records in it the current time as the
+last time the clock was calibrated.
+Five days later, the clock has gained 10 seconds, so you issue a
+.B \%hwclock\ \-\-set\ \-\-update\-drift
+command to set it back 10 seconds.
+.B \%hwclock
+updates the adjtime file to show the current time as the last time the
+clock was calibrated, and records 2 seconds per day as the systematic
+drift rate. 24 hours go by, and then you issue a
+.B \%hwclock\ \-\-adjust
+command.
+.B \%hwclock
+consults the adjtime file and sees that the clock gains 2 seconds per
+day when left alone and that it has been left alone for exactly one
+day. So it subtracts 2 seconds from the Hardware Clock. It then
+records the current time as the last time the clock was adjusted.
+Another 24 hours go by and you issue another
+.BR \%hwclock\ \-\-adjust .
+.B \%hwclock
+does the same thing: subtracts 2 seconds and updates the adjtime file
+with the current time as the last time the clock was adjusted.
+.PP
+When you use the
+.BR \%\-\-update\-drift " option with " \-\-set " or " \%\-\-systohc ,
+the systematic drift rate is (re)calculated by comparing the fully drift
+corrected current Hardware Clock time with the new set time, from that
+it derives the 24 hour drift rate based on the last calibrated timestamp
+from the adjtime file. This updated drift factor is then saved in
+.IR /etc/adjtime .
+.PP
+A small amount of error creeps in when
+the Hardware Clock is set, so
+.B \%\-\-adjust
+refrains from making any adjustment that is less
+than 1 second. Later on, when you request an adjustment again, the accumulated
+drift will be more than 1 second and
+.B \%\-\-adjust
+will make the adjustment including any fractional amount.
+.PP
+.B \%hwclock\ \-\-hctosys
+also uses the adjtime file data to compensate the value read from the Hardware
+Clock before using it to set the System Clock. It does not share the 1 second
+limitation of
+.BR \%\-\-adjust ,
+and will correct sub-second drift values immediately. It does not
+change the Hardware Clock time nor the adjtime file. This may eliminate
+the need to use
+.BR \%\-\-adjust ,
+unless something else on the system needs the Hardware Clock to be
+compensated.
+.
+.SS The Adjtime File
+While named for its historical purpose of controlling adjustments only,
+it actually contains other information used by
+.B hwclock
+from one invocation to the next.
+.PP
+The format of the adjtime file is, in ASCII:
+.PP
+Line 1: Three numbers, separated by blanks: 1) the systematic drift rate
+in seconds per day, floating point decimal; 2) the resulting number of
+seconds since 1969 UTC of most recent adjustment or calibration,
+decimal integer; 3) zero (for compatibility with
+.BR \%clock (8))
+as a floating point decimal.
+.PP
+Line 2: One number: the resulting number of seconds since 1969 UTC of most
+recent calibration. Zero if there has been no calibration yet or it
+is known that any previous calibration is moot (for example, because
+the Hardware Clock has been found, since that calibration, not to
+contain a valid time). This is a decimal integer.
+.PP
+Line 3: "UTC" or "LOCAL". Tells whether the Hardware Clock is set to
+Coordinated Universal Time or local time. You can always override this
+value with options on the
+.B \%hwclock
+command line.
+.PP
+You can use an adjtime file that was previously used with the
+.BR \%clock "(8) program with " \%hwclock .
+.
+.SS Automatic Hardware Clock Synchronization by the Kernel
+You should be aware of another way that the Hardware Clock is kept
+synchronized in some systems. The Linux kernel has a mode wherein it
+copies the System Time to the Hardware Clock every 11 minutes. This mode
+is a compile time option, so not all kernels will have this capability.
+This is a good mode to use when you are using something sophisticated
+like NTP to keep your System Clock synchronized. (NTP is a way to keep
+your System Time synchronized either to a time server somewhere on the
+network or to a radio clock hooked up to your system. See RFC 1305.)
+.PP
+If the kernel is compiled with the \%'11\ minute\ mode' option it will
+be active when the kernel's clock discipline is in a synchronized state.
+When in this state, bit 6 (the bit that is set in the mask 0x0040)
+of the kernel's
+.I \%time_status
+variable is unset. This value is output as the 'status' line of the
+.BR \%adjtimex\ --print " or " \%ntptime " commands."
+.PP
+It takes an outside influence, like the NTP daemon
+to put the kernel's clock discipline into a synchronized state, and
+therefore turn on \%'11\ minute\ mode'.
+It can be turned off by running anything that sets the System Clock the old
+fashioned way, including
+.BR "\%hwclock\ \-\-hctosys" .
+However, if the NTP daemon is still running, it will turn \%'11\ minute\ mode'
+back on again the next time it synchronizes the System Clock.
+.PP
+If your system runs with \%'11\ minute\ mode' on, it may need to use either
+.BR \%\-\-hctosys " or " \%\-\-systz
+in a startup script, especially if the Hardware Clock is configured to use
+the local timescale. Unless the kernel is informed of what timescale the
+Hardware Clock is using, it may clobber it with the wrong one. The kernel
+uses UTC by default.
+.PP
+The first userspace command to set the System Clock informs the
+kernel what timescale the Hardware Clock is using. This happens via the
+.I \%persistent_clock_is_local
+kernel variable. If
+.BR \%\-\-hctosys " or " \%\-\-systz
+is the first, it will set this variable according to the adjtime file or the
+appropriate command-line argument. Note that when using this capability and the
+Hardware Clock timescale configuration is changed, then a reboot is required to
+notify the kernel.
+.PP
+.B \%hwclock\ \-\-adjust
+should not be used with NTP \%'11\ minute\ mode'.
+.
+.SS ISA Hardware Clock Century value
+There is some sort of standard that defines CMOS memory Byte 50 on an ISA
+machine as an indicator of what century it is.
+.B \%hwclock
+does not use or set that byte because there are some machines that
+don't define the byte that way, and it really isn't necessary anyway,
+since the year-of-century does a good job of implying which century it
+is.
+.PP
+If you have a bona fide use for a CMOS century byte, contact the
+.B \%hwclock
+maintainer; an option may be appropriate.
+.PP
+Note that this section is only relevant when you are using the "direct
+ISA" method of accessing the Hardware Clock.
+ACPI provides a standard way to access century values, when they
+are supported by the hardware.
+.
+.SH DATE-TIME CONFIGURATION
+.in +4
+.SS Keeping Time without External Synchronization
+.in
+.PP
+This discussion is based on the following conditions:
+.IP \(bu 2
+Nothing is running that alters the date-time clocks, such as NTP daemon or a cron job."
+.IP \(bu 2
+The system timezone is configured for the correct local time. See below, under
+.BR "POSIX vs 'RIGHT'" .
+.IP \(bu 2
+Early during startup the following are called, in this order:
+.br
+.BI \%adjtimex\ \-\-tick \ value\ \-\-frequency \ value
+.br
+.B \%hwclock\ \-\-hctosys
+.IP \(bu 2
+During shutdown the following is called:
+.br
+.B \%hwclock\ \-\-systohc
+.PP
+.in +4
+.BR * " Systems without " adjtimex " may use " ntptime .
+.in
+.PP
+Whether maintaining precision time with NTP daemon
+or not, it makes sense to configure the system to keep reasonably good
+date-time on its own.
+.PP
+The first step in making that happen is having a clear understanding of
+the big picture. There are two completely separate hardware devices
+running at their own speed and drifting away from the 'correct' time at
+their own rates. The methods and software for drift correction are
+different for each of them. However, most systems are configured to
+exchange values between these two clocks at startup and shutdown. Now
+the individual device's time keeping errors are transferred back and
+forth between each other. Attempt to configure drift correction for only
+one of them, and the other's drift will be overlaid upon it.
+.PP
+This problem can be avoided when configuring drift correction for the
+System Clock by simply not shutting down the machine. This, plus the
+fact that all of
+.BR \%hwclock 's
+precision (including calculating drift factors) depends upon the System
+Clock's rate being correct, means that configuration of the System Clock
+should be done first.
+.PP
+The System Clock drift is corrected with the
+.BR \%adjtimex "(8) command's " \-\-tick " and " \%\-\-frequency
+options. These two work together: tick is the coarse adjustment and
+frequency is the fine adjustment. (For systems that do not have an
+.BR \%adjtimex " package,"
+.BI \%ntptime\ \-f\ ppm
+may be used instead.)
+.PP
+Some Linux distributions attempt to automatically calculate the System
+Clock drift with
+.BR \%adjtimex 's
+compare operation. Trying to correct one
+drifting clock by using another drifting clock as a reference is akin to
+a dog trying to catch its own tail. Success may happen eventually, but
+great effort and frustration will likely precede it. This automation may
+yield an improvement over no configuration, but expecting optimum
+results would be in error. A better choice for manual configuration
+would be
+.BR \%adjtimex 's " \-\-log " options.
+.PP
+It may be more effective to simply track the System Clock drift with
+.BR \%sntp ", or " \%date\ \-Ins
+and a precision timepiece, and then calculate the correction manually.
+.PP
+After setting the tick and frequency values, continue to test and refine the
+adjustments until the System Clock keeps good time. See
+.BR \%adjtimex (2)
+for more information and the example demonstrating manual drift
+calculations.
+.PP
+Once the System Clock is ticking smoothly, move on to the Hardware Clock.
+.PP
+As a rule, cold drift will work best for most use cases. This should be
+true even for 24/7 machines whose normal downtime consists of a reboot.
+In that case the drift factor value makes little difference. But on the
+rare occasion that the machine is shut down for an extended period, then
+cold drift should yield better results.
+.PP
+.B Steps to calculate cold drift:
+.IP 1 2
+.B "Ensure that NTP daemon will not be launched at startup."
+.IP 2 2
+.RI The " System Clock " "time must be correct at shutdown!"
+.IP 3 2
+Shut down the system.
+.IP 4 2
+Let an extended period pass without changing the Hardware Clock.
+.IP 5 2
+Start the system.
+.IP 6 2
+.RB "Immediately use " hwclock " to set the correct time, adding the"
+.BR \%\-\-update\-drift " option."
+.PP
+Note: if step 6 uses
+.BR \%\-\-systohc ,
+then the System Clock must be set correctly (step 6a) just before doing so.
+.PP
+.RB "Having " hwclock
+calculate the drift factor is a good starting point, but for optimal
+results it will likely need to be adjusted by directly editing the
+.I /etc/adjtime
+file. Continue to test and refine the drift factor until the Hardware
+Clock is corrected properly at startup. To check this, first make sure
+that the System Time is correct before shutdown and then use
+.BR \%sntp ", or " \%date\ \-Ins
+and a precision timepiece, immediately after startup.
+.SS LOCAL vs UTC
+Keeping the Hardware Clock in a local timescale causes inconsistent
+daylight saving time results:
+.IP \(bu 2
+If Linux is running during a daylight saving time change, the time
+written to the Hardware Clock will be adjusted for the change.
+.IP \(bu 2
+If Linux is NOT running during a daylight saving time change, the time
+read from the Hardware Clock will NOT be adjusted for the change.
+.PP
+The Hardware Clock on an ISA compatible system keeps only a date and time,
+it has no concept of timezone nor daylight saving. Therefore, when
+.B hwclock
+is told that it is in local time, it assumes it is in the 'correct'
+local time and makes no adjustments to the time read from it.
+.PP
+Linux handles daylight saving time changes transparently only when the
+Hardware Clock is kept in the UTC timescale. Doing so is made easy for
+system administrators as
+.B \%hwclock
+uses local time for its output and as the argument to the
+.BR \%\-\-date " option."
+.PP
+POSIX systems, like Linux, are designed to have the System Clock operate
+in the UTC timescale. The Hardware Clock's purpose is to initialize the
+System Clock, so also keeping it in UTC makes sense.
+.PP
+Linux does, however, attempt to accommodate the Hardware Clock being in
+the local timescale. This is primarily for dual-booting with older
+versions of MS Windows. From Windows 7 on, the RealTimeIsUniversal
+registry key is supposed to be working properly so that its Hardware
+Clock can be kept in UTC.
+.
+.SS POSIX vs 'RIGHT'
+A discussion on date-time configuration would be incomplete without
+addressing timezones, this is mostly well covered by
+.BR tzset (3).
+One area that seems to have no documentation is the 'right'
+directory of the Time Zone Database, sometimes called tz or zoneinfo.
+.PP
+There are two separate databases in the zoneinfo system, posix
+and 'right'. 'Right' (now named zoneinfo\-leaps) includes leap seconds and posix
+does not. To use the 'right' database the System Clock must be set to
+\%(UTC\ +\ leap seconds), which is equivalent to \%(TAI\ \-\ 10). This
+allows calculating the
+exact number of seconds between two dates that cross a leap second
+epoch. The System Clock is then converted to the correct civil time,
+including UTC, by using the 'right' timezone files which subtract the
+leap seconds. Note: this configuration is considered experimental and is
+known to have issues.
+.PP
+To configure a system to use a particular database all of the files
+located in its directory must be copied to the root of
+.IR \%/usr/share/zoneinfo .
+Files are never used directly from the posix or 'right' subdirectories, e.g.,
+.RI \%TZ=' right/Europe/Dublin '.
+This habit was becoming so common that the upstream zoneinfo project
+restructured the system's file tree by moving the posix and 'right'
+subdirectories out of the zoneinfo directory and into sibling directories:
+.PP
+.in +2
+.I /usr/share/zoneinfo
+.br
+.I /usr/share/zoneinfo\-posix
+.br
+.I /usr/share/zoneinfo\-leaps
+.PP
+Unfortunately, some Linux distributions are changing it back to the old
+tree structure in their packages. So the problem of system
+administrators reaching into the 'right' subdirectory persists. This
+causes the system timezone to be configured to include leap seconds
+while the zoneinfo database is still configured to exclude them. Then
+when an application such as a World Clock needs the South_Pole timezone
+file; or an email MTA, or
+.B hwclock
+needs the UTC timezone file; they fetch it from the root of
+.I \%/usr/share/zoneinfo
+, because that is what they are supposed to do. Those files exclude leap
+seconds, but the System Clock now includes them, causing an incorrect
+time conversion.
+.PP
+Attempting to mix and match files from these separate databases will not
+work, because they each require the System Clock to use a different
+timescale. The zoneinfo database must be configured to use either posix
+or 'right', as described above, or by assigning a database path to the
+.SB TZDIR
+environment variable.
+.SH EXIT STATUS
+One of the following exit values will be returned:
+.TP
+.BR EXIT_SUCCESS " ('0' on POSIX systems)"
+Successful program execution.
+.TP
+.BR EXIT_FAILURE " ('1' on POSIX systems)"
+The operation failed or the command syntax was not valid.
+.SH ENVIRONMENT
+.TP
+.B TZ
+If this variable is set its value takes precedence over the system
+configured timezone.
+.TP
+.B TZDIR
+If this variable is set its value takes precedence over the system
+configured timezone database directory path.
+.SH FILES
+.TP
+.I /etc/adjtime
+The configuration and state file for hwclock.
+.TP
+.I /etc/localtime
+The system timezone file.
+.TP
+.I /usr/share/zoneinfo/
+The system timezone database directory.
+.PP
+Device files
+.B hwclock
+may try for Hardware Clock access:
+.br
+.I /dev/rtc0
+.br
+.I /dev/rtc
+.br
+.I /dev/misc/rtc
+.br
+.I /dev/efirtc
+.br
+.I /dev/misc/efirtc
+.SH "SEE ALSO"
+.BR date (1),
+.BR adjtimex (8),
+.BR gettimeofday (2),
+.BR settimeofday (2),
+.BR crontab (1p),
+.BR tzset (3)
+.
+.SH AUTHORS
+Written by Bryan Henderson, September 1996 (bryanh@giraffe-data.com),
+based on work done on the
+.BR \%clock (8)
+program by Charles Hedrick, Rob Hooft, and Harald Koenig.
+See the source code for complete history and credits.
+.
+.SH AVAILABILITY
+The hwclock command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/hwclock.8.in b/sys-utils/hwclock.8.in
new file mode 100644
index 0000000..2354ddd
--- /dev/null
+++ b/sys-utils/hwclock.8.in
@@ -0,0 +1,993 @@
+.\" hwclock.8.in -- man page for util-linux' hwclock
+.\"
+.\" 2015-01-07 J William Piggott
+.\" Authored new section: DATE-TIME CONFIGURATION.
+.\" Subsections: Keeping Time..., LOCAL vs UTC, POSIX vs 'RIGHT'.
+.\"
+.TH HWCLOCK 8 "July 2017" "util-linux" "System Administration"
+.SH NAME
+hwclock \- time clocks utility
+.SH SYNOPSIS
+.B hwclock
+.RI [ function ]
+.RI [ option ...]
+.
+.SH DESCRIPTION
+.B hwclock
+is an administration tool for the time clocks. It can: display the
+Hardware Clock time; set the Hardware Clock to a specified time; set the
+Hardware Clock from the System Clock; set the System Clock from the
+Hardware Clock; compensate for Hardware Clock drift; correct the System
+Clock timescale; set the kernel's timezone, NTP timescale, and epoch
+(Alpha only); and predict future
+Hardware Clock values based on its drift rate.
+.PP
+Since v2.26 important changes were made to the
+.B \-\-hctosys
+function and the
+.B \-\-directisa
+option, and a new option
+.B \-\-update\-drift
+was added. See their respective descriptions below.
+.
+.SH FUNCTIONS
+The following functions are mutually exclusive, only one can be given at
+a time. If none is given, the default is \fB\-\-show\fR.
+.TP
+.B \-a, \-\-adjust
+Add or subtract time from the Hardware Clock to account for systematic
+drift since the last time the clock was set or adjusted. See the
+discussion below, under
+.BR "The Adjust Function" .
+.
+.TP
+.B \-\-getepoch
+.TQ
+.B \-\-setepoch
+These functions are for Alpha machines only, and are only available
+through the Linux kernel RTC driver.
+.sp
+They are used to read and set the kernel's Hardware Clock epoch value.
+Epoch is the number of years into AD to which a zero year value in the
+Hardware Clock refers. For example, if the machine's BIOS sets the year
+counter in the Hardware Clock to contain the number of full years since
+1952, then the kernel's Hardware Clock epoch value must be 1952.
+.sp
+The \fB\%\-\-setepoch\fR function requires using the
+.B \%\-\-epoch
+option to specify the year. For example:
+.RS
+.IP "" 4
+.B hwclock\ \-\-setepoch\ \-\-epoch=1952
+.PP
+The RTC driver attempts to guess the correct epoch value, so setting it
+may not be required.
+.PP
+This epoch value is used whenever
+.B \%hwclock
+reads or sets the Hardware Clock on an Alpha machine. For ISA machines
+the kernel uses the fixed Hardware Clock epoch of 1900.
+.RE
+.
+.TP
+.B \-\-predict
+Predict what the Hardware Clock will read in the future based upon the
+time given by the
+.B \-\-date
+option and the information in
+.IR @ADJTIME_PATH@ .
+This is useful, for example, to account for drift when setting a
+Hardware Clock wakeup (aka alarm). See
+.BR \%rtcwake (8).
+.sp
+Do not use this function if the Hardware Clock is being modified by
+anything other than the current operating system's
+.B \%hwclock
+command, such as \%'11\ minute\ mode' or from dual-booting another OS.
+.
+.TP
+.BR \-r , \ \-\-show
+.TQ
+.B \-\-get
+.br
+Read the Hardware Clock and print its time to standard output in the
+.B ISO 8601
+format.
+The time shown is always in local time, even if you keep your Hardware Clock
+in UTC. See the
+.B \%\-\-localtime
+option.
+.sp
+Showing the Hardware Clock time is the default when no function is specified.
+.sp
+The
+.B \-\-get
+function also applies drift correction to the time read, based upon the
+information in
+.IR @ADJTIME_PATH@ .
+Do not use this function if the Hardware Clock is being modified by
+anything other than the current operating system's
+.B \%hwclock
+command, such as \%'11\ minute\ mode' or from dual-booting another OS.
+.
+.TP
+.BR \-s , \ \-\-hctosys
+Set the System Clock from the Hardware Clock. The time read from the Hardware
+Clock is compensated to account for systematic drift before using it to set the
+System Clock. See the discussion below, under
+.BR "The Adjust Function" .
+.sp
+The System Clock must be kept in the UTC timescale for date-time
+applications to work correctly in conjunction with the timezone configured
+for the system. If the Hardware Clock is kept in local time then the time read
+from it must be shifted to the UTC timescale before using it to set the System
+Clock. The
+.B \%\-\-hctosys
+function does this based upon the information in the
+.I @ADJTIME_PATH@
+file or the command line arguments
+.BR \%\-\-localtime " and " \-\-utc .
+Note: no daylight saving adjustment is made. See the discussion below, under
+.BR "LOCAL vs UTC" .
+.sp
+The kernel also keeps a timezone value, the
+.B \%\-\-hctosys
+function sets it to the timezone configured for the system. The system
+timezone is configured by the TZ environment variable or the
+.I \%/etc/localtime
+file, as
+.BR \%tzset (3)
+would interpret them.
+The obsolete tz_dsttime field of the kernel's timezone value is set
+to zero. (For details on what this field used to mean, see
+.BR \%settimeofday (2).)
+.sp
+When used in a startup script, making the
+.B \%\-\-hctosys
+function the first caller of
+.BR \%settimeofday (2)
+from boot, it will set the NTP \%'11\ minute\ mode' timescale via the
+.I \%persistent_clock_is_local
+kernel variable. If the Hardware Clock's timescale configuration is
+changed then a reboot is required to inform the kernel. See the
+discussion below, under
+.BR "Automatic Hardware Clock Synchronization by the Kernel" .
+.sp
+This is a good function to use in one of the system startup scripts before the
+file systems are mounted read/write.
+.sp
+This function should never be used on a running system. Jumping system time
+will cause problems, such as corrupted filesystem timestamps. Also, if
+something has changed the Hardware Clock, like NTP's \%'11\ minute\ mode', then
+.B \%\-\-hctosys
+will set the time incorrectly by including drift compensation.
+.sp
+Drift compensation can be inhibited by setting the drift factor in
+.I @ADJTIME_PATH@
+to zero. This setting will be persistent as long as the
+.BR \%\-\-update\-drift " option is not used with " \%\-\-systohc
+at shutdown (or anywhere else). Another way to inhibit this is by using the
+.BR \%\-\-noadjfile " option when calling the " \%\-\-hctosys
+function. A third method is to delete the
+.IR @ADJTIME_PATH@ " file."
+.B Hwclock
+will then default to using the UTC timescale for the Hardware Clock. If
+the Hardware Clock is ticking local time it will need to be defined in
+the file. This can be done by calling
+.BR hwclock\ \-\-localtime\ \-\-adjust ;
+when the file is not present this command will not actually
+adjust the Clock, but it will create the file with local time
+configured, and a drift factor of zero.
+.sp
+A condition under which inhibiting
+.BR hwclock 's
+drift correction may be desired is when dual-booting multiple operating
+systems. If while this instance of Linux is stopped, another OS changes
+the Hardware Clock's value, then when this instance is started again the
+drift correction applied will be incorrect.
+.sp
+.RB "For " hwclock 's
+drift correction to work properly it is imperative that nothing changes
+the Hardware Clock while its Linux instance is not running.
+.
+.TP
+.B \-\-set
+Set the Hardware Clock to the time given by the
+.B \-\-date
+option, and update the timestamps in
+.IR @ADJTIME_PATH@ .
+With the
+.B \%\-\-update-drift
+option also (re)calculate the drift factor. Try it without the option if
+.BR \%\-\-set " fails. See " \%\-\-update-drift " below."
+.
+.TP
+.B \-\-systz
+This is an alternate to the
+.B \%\-\-hctosys
+function that does not read the Hardware Clock nor set the System Clock;
+consequently there is not any drift correction. It is intended to be
+used in a startup script on systems with kernels above version 2.6 where
+you know the System Clock has been set from the Hardware Clock by the
+kernel during boot.
+.sp
+It does the following things that are detailed above in the
+.BR \%\-\-hctosys " function:"
+.RS
+.IP \(bu 2
+Corrects the System Clock timescale to UTC as needed. Only instead of
+accomplishing this by setting the System Clock,
+.B hwclock
+simply informs the kernel and it handles the change.
+.IP \(bu 2
+Sets the kernel's NTP \%'11\ minute\ mode' timescale.
+.IP \(bu 2
+Sets the kernel's timezone.
+.PP
+The first two are only available on the first call of
+.BR \%settimeofday (2)
+after boot. Consequently this option only makes sense when used in a
+startup script. If the Hardware Clocks timescale configuration is
+changed then a reboot would be required to inform the kernel.
+.RE
+.
+.TP
+.BR \-w , \ \-\-systohc
+Set the Hardware Clock from the System Clock, and update the timestamps in
+.IR @ADJTIME_PATH@ .
+With the
+.B \%\-\-update-drift
+option also (re)calculate the drift factor. Try it without the option if
+.BR \%\-\-systohc " fails. See " \%\-\-update-drift " below."
+.
+.TP
+.BR \-V , \ \-\-version
+Display version information and exit.
+.
+.TP
+.BR \-h , \ \-\-help
+Display help text and exit.
+.
+.SH OPTIONS
+.
+.TP
+.BI \-\-adjfile= filename
+.RI "Override the default " @ADJTIME_PATH@ " file path."
+.
+.TP
+.BI \%\-\-date= date_string
+This option must be used with the
+.B \-\-set
+or
+.B \%\-\-predict
+functions, otherwise it is ignored.
+.RS
+.IP "" 4
+.B "hwclock\ \-\-set\ \-\-date='16:45'"
+.IP "" 4
+.B "hwclock\ \-\-predict\ \-\-date='2525-08-14\ 07:11:05'"
+.PP
+The argument must be in local time, even if you keep your Hardware Clock in
+UTC. See the
+.B \%\-\-localtime
+option. Therefore, the argument should not include any timezone information.
+It also should not be a relative time like "+5 minutes", because
+.BR \%hwclock 's
+precision depends upon correlation between the argument's value and when the
+enter key is pressed. Fractional seconds are silently dropped. This option is
+capable of understanding many time and date formats, but the previous
+parameters should be observed.
+.RE
+.
+.TP
+.BI \%\-\-delay= seconds
+This option can be used to overwrite the internally used delay
+when setting the clock time. The
+default is 0.5 (500ms) for rtc_cmos, for another RTC types the delay is 0. If
+RTC type is impossible to determine (from sysfs) then it defaults also to 0.5
+to be backwardly compatible.
+.RS
+.PP
+The 500ms default is based on commonly used MC146818A-compatible (x86) hardware clock. This
+Hardware Clock can only be set to any integer time plus one half second. The
+integer time is required because there is no interface to set or get a
+fractional second. The additional half second delay is because the Hardware
+Clock updates to the following second precisely 500 ms after setting the new
+time. Unfortunately, this behavior is hardware specific and in same cases
+another delay is required.
+.RE
+.
+.TP
+.BR \-D ", " \-\-debug
+.RB Use\ \-\-verbose .
+.RB The\ \%\-\-debug\ option
+has been deprecated and may be repurposed or removed in a future release.
+.
+.TP
+.B \-\-directisa
+This option is meaningful for ISA compatible machines in the x86 and
+x86_64 family. For other machines, it has no effect. This option tells
+.B \%hwclock
+to use explicit I/O instructions to access the Hardware Clock.
+Without this option,
+.B \%hwclock
+will use the rtc device file, which it assumes to be driven by the Linux
+RTC device driver. As of v2.26 it will no longer automatically use
+directisa when the rtc driver is unavailable; this was causing an unsafe
+condition that could allow two processes to access the Hardware Clock at
+the same time. Direct hardware access from userspace should only be
+used for testing, troubleshooting, and as a last resort when all other
+methods fail. See the
+.BR \-\-rtc " option."
+.
+.TP
+.BI \-\-epoch= year
+This option is required when using the
+.BR \%\-\-setepoch \ function.
+.RI "The minimum " year
+value is 1900. The maximum is system dependent
+.RB ( ULONG_MAX\ -\ 1 ).
+.
+.TP
+.BR \-f , \ \-\-rtc=\fIfilename\fR
+.RB "Override " \%hwclock 's
+default rtc device file name. Otherwise it will
+use the first one found in this order:
+.in +4
+.br
+.I /dev/rtc0
+.br
+.I /dev/rtc
+.br
+.I /dev/misc/rtc
+.br
+.in
+.RB "For " IA-64:
+.in +4
+.br
+.I /dev/efirtc
+.br
+.I /dev/misc/efirtc
+.in
+.
+.TP
+.BR \-l , \ \-\-localtime
+.TQ
+.BR \-u ", " \-\-utc
+Indicate which timescale the Hardware Clock is set to.
+.sp
+The Hardware Clock may be configured to use either the UTC or the local
+timescale, but nothing in the clock itself says which alternative is
+being used. The
+.BR \%\-\-localtime " or " \-\-utc
+options give this information to the
+.B \%hwclock
+command. If you specify the wrong one (or specify neither and take a
+wrong default), both setting and reading the Hardware Clock will be
+incorrect.
+.sp
+If you specify neither
+.BR \-\-utc " nor " \%\-\-localtime
+then the one last given with a set function
+.RB ( \-\-set ", " \%\-\-systohc ", or " \%\-\-adjust ),
+as recorded in
+.IR @ADJTIME_PATH@ ,
+will be used. If the adjtime file doesn't exist, the default is UTC.
+.sp
+Note: daylight saving time changes may be inconsistent when the
+Hardware Clock is kept in local time. See the discussion below, under
+.BR "LOCAL vs UTC" .
+.
+.TP
+.B \-\-noadjfile
+Disable the facilities provided by
+.IR @ADJTIME_PATH@ .
+.B \%hwclock
+will not read nor write to that file with this option. Either
+.BR \-\-utc " or " \%\-\-localtime
+must be specified when using this option.
+.
+.TP
+.B \-\-test
+Do not actually change anything on the system, that is, the Clocks or
+.I @ADJTIME_PATH@
+.RB ( \%\-\-verbose
+is implicit with this option).
+.
+.TP
+.B \-\-update\-drift
+Update the Hardware Clock's drift factor in
+.IR @ADJTIME_PATH@ .
+It can only be used with
+.BR \-\-set " or " \%\-\-systohc ,
+.sp
+A minimum four hour period between settings is required. This is to
+avoid invalid calculations. The longer the period, the more precise the
+resulting drift factor will be.
+.sp
+This option was added in v2.26, because
+it is typical for systems to call
+.B \%hwclock\ \-\-systohc
+at shutdown; with the old behaviour this would automatically
+(re)calculate the drift factor which caused several problems:
+.RS
+.IP \(bu 2
+When using NTP with an \%'11\ minute\ mode' kernel the drift factor
+would be clobbered to near zero.
+.IP \(bu 2
+It would not allow the use of 'cold' drift correction. With most
+configurations using 'cold' drift will yield favorable results. Cold,
+means when the machine is turned off which can have a significant impact
+on the drift factor.
+.IP \(bu 2
+(Re)calculating drift factor on every shutdown delivers suboptimal
+results. For example, if ephemeral conditions cause the machine to be
+abnormally hot the drift factor calculation would be out of range.
+.IP \(bu 2
+Significantly increased system shutdown times (as of v2.31 when not
+using
+.B \%\-\-update\-drift
+the RTC is not read).
+.PP
+.RB "Having " \%hwclock
+calculate the drift factor is a good starting point, but for optimal
+results it will likely need to be adjusted by directly editing the
+.I @ADJTIME_PATH@
+file. For most configurations once a machine's optimal drift factor is
+crafted it should not need to be changed. Therefore, the old behavior to
+automatically (re)calculate drift was changed and now requires this
+option to be used. See the discussion below, under
+.BR "The Adjust Function" .
+.PP
+This option requires reading the Hardware Clock before setting it. If
+it cannot be read, then this option will cause the set functions to fail.
+This can happen, for example, if the Hardware Clock is corrupted by a
+power failure. In that case, the clock must first be set without this
+option. Despite it not working, the resulting drift correction factor
+would be invalid anyway.
+.RE
+.
+.TP
+.BR \-v ", " \-\-verbose
+Display more details about what
+.B \%hwclock
+is doing internally.
+.
+.SH NOTES
+.
+.SS Clocks in a Linux System
+There are two types of date-time clocks:
+.PP
+.B The Hardware Clock:
+This clock is an independent hardware device, with its own power domain
+(battery, capacitor, etc), that operates when the machine is powered off,
+or even unplugged.
+.PP
+On an ISA compatible system, this clock is specified as part of the ISA
+standard. A control program can read or set this clock only to a whole
+second, but it can also detect the edges of the 1 second clock ticks, so
+the clock actually has virtually infinite precision.
+.PP
+This clock is commonly called the hardware clock, the real time clock,
+the RTC, the BIOS clock, and the CMOS clock. Hardware Clock, in its
+capitalized form, was coined for use by
+.BR \%hwclock .
+The Linux kernel also refers to it as the persistent clock.
+.PP
+Some non-ISA systems have a few real time clocks with
+only one of them having its own power domain.
+A very low power external I2C or SPI clock chip might be used with a
+backup battery as the hardware clock to initialize a more functional
+integrated real-time clock which is used for most other purposes.
+.PP
+.B The System Clock:
+This clock is part of the Linux kernel and is driven by
+a timer interrupt. (On an ISA machine, the timer interrupt is part of
+the ISA standard.) It has meaning only while Linux is running on the
+machine. The System Time is the number of seconds since 00:00:00
+January 1, 1970 UTC (or more succinctly, the number of seconds since
+1969 UTC). The System Time is not an integer, though. It has virtually
+infinite precision.
+.PP
+The System Time is the time that matters. The Hardware Clock's basic
+purpose is to keep time when Linux is not running so that the System
+Clock can be initialized from it at boot. Note that in DOS, for which
+ISA was designed, the Hardware Clock is the only real time clock.
+.PP
+It is important that the System Time not have any discontinuities such as
+would happen if you used the
+.BR \%date (1)
+program to set it while the system is running. You can, however, do whatever
+you want to the Hardware Clock while the system is running, and the next
+time Linux starts up, it will do so with the adjusted time from the Hardware
+Clock. Note: currently this is not possible on most systems because
+.B \%hwclock\ \-\-systohc
+is called at shutdown.
+.PP
+The Linux kernel's timezone is set by
+.BR hwclock .
+But don't be misled -- almost nobody cares what timezone the kernel
+thinks it is in. Instead, programs that care about the timezone
+(perhaps because they want to display a local time for you) almost
+always use a more traditional method of determining the timezone: They
+use the TZ environment variable or the
+.I \%/etc/localtime
+file, as explained in the man page for
+.BR \%tzset (3).
+However, some programs and fringe parts of the Linux kernel such as filesystems
+use the kernel's timezone value. An example is the vfat filesystem. If the
+kernel timezone value is wrong, the vfat filesystem will report and set the
+wrong timestamps on files. Another example is the kernel's NTP \%'11\ minute\ mode'.
+If the kernel's timezone value and/or the
+.I \%persistent_clock_is_local
+variable are wrong, then the Hardware Clock will be set incorrectly
+by \%'11\ minute\ mode'. See the discussion below, under
+.BR "Automatic Hardware Clock Synchronization by the Kernel" .
+.PP
+.B \%hwclock
+sets the kernel's timezone to the value indicated by TZ or
+.IR \%/etc/localtime " with the"
+.BR \%\-\-hctosys " or " \%\-\-systz " functions."
+.PP
+The kernel's timezone value actually consists of two parts: 1) a field
+tz_minuteswest indicating how many minutes local time (not adjusted
+for DST) lags behind UTC, and 2) a field tz_dsttime indicating
+the type of Daylight Savings Time (DST) convention that is in effect
+in the locality at the present time.
+This second field is not used under Linux and is always zero.
+See also
+.BR \%settimeofday (2).
+.
+.SS Hardware Clock Access Methods
+.B \%hwclock
+uses many different ways to get and set Hardware Clock values. The most
+normal way is to do I/O to the rtc device special file, which is
+presumed to be driven by the rtc device driver. Also, Linux systems
+using the rtc framework with udev, are capable of supporting multiple
+Hardware Clocks. This may bring about the need to override the default
+rtc device by specifying one with the
+.BR \-\-rtc " option."
+.PP
+However, this method is not always available as older systems do not
+have an rtc driver. On these systems, the method of accessing the
+Hardware Clock depends on the system hardware.
+.PP
+On an ISA compatible system,
+.B \%hwclock
+can directly access the "CMOS memory" registers that
+constitute the clock, by doing I/O to Ports 0x70 and 0x71. It does
+this with actual I/O instructions and consequently can only do it if
+running with superuser effective userid. This method may be used by
+specifying the
+.BR \%\-\-directisa " option."
+.PP
+This is a really poor method of accessing the clock, for all the
+reasons that userspace programs are generally not supposed to do
+direct I/O and disable interrupts.
+.B \%hwclock
+provides it for testing, troubleshooting, and because it may be the
+only method available on ISA systems which do not have a working rtc
+device driver.
+.SS The Adjust Function
+The Hardware Clock is usually not very accurate. However, much of its
+inaccuracy is completely predictable - it gains or loses the same amount
+of time every day. This is called systematic drift.
+.BR \%hwclock "'s " \%\-\-adjust
+function lets you apply systematic drift corrections to the
+Hardware Clock.
+.PP
+It works like this:
+.BR \%hwclock " keeps a file,"
+.IR @ADJTIME_PATH@ ,
+that keeps some historical information. This is called the adjtime file.
+.PP
+Suppose you start with no adjtime file. You issue a
+.B \%hwclock\ \-\-set
+command to set the Hardware Clock to the true current time.
+.B \%hwclock
+creates the adjtime file and records in it the current time as the
+last time the clock was calibrated.
+Five days later, the clock has gained 10 seconds, so you issue a
+.B \%hwclock\ \-\-set\ \-\-update\-drift
+command to set it back 10 seconds.
+.B \%hwclock
+updates the adjtime file to show the current time as the last time the
+clock was calibrated, and records 2 seconds per day as the systematic
+drift rate. 24 hours go by, and then you issue a
+.B \%hwclock\ \-\-adjust
+command.
+.B \%hwclock
+consults the adjtime file and sees that the clock gains 2 seconds per
+day when left alone and that it has been left alone for exactly one
+day. So it subtracts 2 seconds from the Hardware Clock. It then
+records the current time as the last time the clock was adjusted.
+Another 24 hours go by and you issue another
+.BR \%hwclock\ \-\-adjust .
+.B \%hwclock
+does the same thing: subtracts 2 seconds and updates the adjtime file
+with the current time as the last time the clock was adjusted.
+.PP
+When you use the
+.BR \%\-\-update\-drift " option with " \-\-set " or " \%\-\-systohc ,
+the systematic drift rate is (re)calculated by comparing the fully drift
+corrected current Hardware Clock time with the new set time, from that
+it derives the 24 hour drift rate based on the last calibrated timestamp
+from the adjtime file. This updated drift factor is then saved in
+.IR @ADJTIME_PATH@ .
+.PP
+A small amount of error creeps in when
+the Hardware Clock is set, so
+.B \%\-\-adjust
+refrains from making any adjustment that is less
+than 1 second. Later on, when you request an adjustment again, the accumulated
+drift will be more than 1 second and
+.B \%\-\-adjust
+will make the adjustment including any fractional amount.
+.PP
+.B \%hwclock\ \-\-hctosys
+also uses the adjtime file data to compensate the value read from the Hardware
+Clock before using it to set the System Clock. It does not share the 1 second
+limitation of
+.BR \%\-\-adjust ,
+and will correct sub-second drift values immediately. It does not
+change the Hardware Clock time nor the adjtime file. This may eliminate
+the need to use
+.BR \%\-\-adjust ,
+unless something else on the system needs the Hardware Clock to be
+compensated.
+.
+.SS The Adjtime File
+While named for its historical purpose of controlling adjustments only,
+it actually contains other information used by
+.B hwclock
+from one invocation to the next.
+.PP
+The format of the adjtime file is, in ASCII:
+.PP
+Line 1: Three numbers, separated by blanks: 1) the systematic drift rate
+in seconds per day, floating point decimal; 2) the resulting number of
+seconds since 1969 UTC of most recent adjustment or calibration,
+decimal integer; 3) zero (for compatibility with
+.BR \%clock (8))
+as a floating point decimal.
+.PP
+Line 2: One number: the resulting number of seconds since 1969 UTC of most
+recent calibration. Zero if there has been no calibration yet or it
+is known that any previous calibration is moot (for example, because
+the Hardware Clock has been found, since that calibration, not to
+contain a valid time). This is a decimal integer.
+.PP
+Line 3: "UTC" or "LOCAL". Tells whether the Hardware Clock is set to
+Coordinated Universal Time or local time. You can always override this
+value with options on the
+.B \%hwclock
+command line.
+.PP
+You can use an adjtime file that was previously used with the
+.BR \%clock "(8) program with " \%hwclock .
+.
+.SS Automatic Hardware Clock Synchronization by the Kernel
+You should be aware of another way that the Hardware Clock is kept
+synchronized in some systems. The Linux kernel has a mode wherein it
+copies the System Time to the Hardware Clock every 11 minutes. This mode
+is a compile time option, so not all kernels will have this capability.
+This is a good mode to use when you are using something sophisticated
+like NTP to keep your System Clock synchronized. (NTP is a way to keep
+your System Time synchronized either to a time server somewhere on the
+network or to a radio clock hooked up to your system. See RFC 1305.)
+.PP
+If the kernel is compiled with the \%'11\ minute\ mode' option it will
+be active when the kernel's clock discipline is in a synchronized state.
+When in this state, bit 6 (the bit that is set in the mask 0x0040)
+of the kernel's
+.I \%time_status
+variable is unset. This value is output as the 'status' line of the
+.BR \%adjtimex\ --print " or " \%ntptime " commands."
+.PP
+It takes an outside influence, like the NTP daemon
+to put the kernel's clock discipline into a synchronized state, and
+therefore turn on \%'11\ minute\ mode'.
+It can be turned off by running anything that sets the System Clock the old
+fashioned way, including
+.BR "\%hwclock\ \-\-hctosys" .
+However, if the NTP daemon is still running, it will turn \%'11\ minute\ mode'
+back on again the next time it synchronizes the System Clock.
+.PP
+If your system runs with \%'11\ minute\ mode' on, it may need to use either
+.BR \%\-\-hctosys " or " \%\-\-systz
+in a startup script, especially if the Hardware Clock is configured to use
+the local timescale. Unless the kernel is informed of what timescale the
+Hardware Clock is using, it may clobber it with the wrong one. The kernel
+uses UTC by default.
+.PP
+The first userspace command to set the System Clock informs the
+kernel what timescale the Hardware Clock is using. This happens via the
+.I \%persistent_clock_is_local
+kernel variable. If
+.BR \%\-\-hctosys " or " \%\-\-systz
+is the first, it will set this variable according to the adjtime file or the
+appropriate command-line argument. Note that when using this capability and the
+Hardware Clock timescale configuration is changed, then a reboot is required to
+notify the kernel.
+.PP
+.B \%hwclock\ \-\-adjust
+should not be used with NTP \%'11\ minute\ mode'.
+.
+.SS ISA Hardware Clock Century value
+There is some sort of standard that defines CMOS memory Byte 50 on an ISA
+machine as an indicator of what century it is.
+.B \%hwclock
+does not use or set that byte because there are some machines that
+don't define the byte that way, and it really isn't necessary anyway,
+since the year-of-century does a good job of implying which century it
+is.
+.PP
+If you have a bona fide use for a CMOS century byte, contact the
+.B \%hwclock
+maintainer; an option may be appropriate.
+.PP
+Note that this section is only relevant when you are using the "direct
+ISA" method of accessing the Hardware Clock.
+ACPI provides a standard way to access century values, when they
+are supported by the hardware.
+.
+.SH DATE-TIME CONFIGURATION
+.in +4
+.SS Keeping Time without External Synchronization
+.in
+.PP
+This discussion is based on the following conditions:
+.IP \(bu 2
+Nothing is running that alters the date-time clocks, such as NTP daemon or a cron job."
+.IP \(bu 2
+The system timezone is configured for the correct local time. See below, under
+.BR "POSIX vs 'RIGHT'" .
+.IP \(bu 2
+Early during startup the following are called, in this order:
+.br
+.BI \%adjtimex\ \-\-tick \ value\ \-\-frequency \ value
+.br
+.B \%hwclock\ \-\-hctosys
+.IP \(bu 2
+During shutdown the following is called:
+.br
+.B \%hwclock\ \-\-systohc
+.PP
+.in +4
+.BR * " Systems without " adjtimex " may use " ntptime .
+.in
+.PP
+Whether maintaining precision time with NTP daemon
+or not, it makes sense to configure the system to keep reasonably good
+date-time on its own.
+.PP
+The first step in making that happen is having a clear understanding of
+the big picture. There are two completely separate hardware devices
+running at their own speed and drifting away from the 'correct' time at
+their own rates. The methods and software for drift correction are
+different for each of them. However, most systems are configured to
+exchange values between these two clocks at startup and shutdown. Now
+the individual device's time keeping errors are transferred back and
+forth between each other. Attempt to configure drift correction for only
+one of them, and the other's drift will be overlaid upon it.
+.PP
+This problem can be avoided when configuring drift correction for the
+System Clock by simply not shutting down the machine. This, plus the
+fact that all of
+.BR \%hwclock 's
+precision (including calculating drift factors) depends upon the System
+Clock's rate being correct, means that configuration of the System Clock
+should be done first.
+.PP
+The System Clock drift is corrected with the
+.BR \%adjtimex "(8) command's " \-\-tick " and " \%\-\-frequency
+options. These two work together: tick is the coarse adjustment and
+frequency is the fine adjustment. (For systems that do not have an
+.BR \%adjtimex " package,"
+.BI \%ntptime\ \-f\ ppm
+may be used instead.)
+.PP
+Some Linux distributions attempt to automatically calculate the System
+Clock drift with
+.BR \%adjtimex 's
+compare operation. Trying to correct one
+drifting clock by using another drifting clock as a reference is akin to
+a dog trying to catch its own tail. Success may happen eventually, but
+great effort and frustration will likely precede it. This automation may
+yield an improvement over no configuration, but expecting optimum
+results would be in error. A better choice for manual configuration
+would be
+.BR \%adjtimex 's " \-\-log " options.
+.PP
+It may be more effective to simply track the System Clock drift with
+.BR \%sntp ", or " \%date\ \-Ins
+and a precision timepiece, and then calculate the correction manually.
+.PP
+After setting the tick and frequency values, continue to test and refine the
+adjustments until the System Clock keeps good time. See
+.BR \%adjtimex (2)
+for more information and the example demonstrating manual drift
+calculations.
+.PP
+Once the System Clock is ticking smoothly, move on to the Hardware Clock.
+.PP
+As a rule, cold drift will work best for most use cases. This should be
+true even for 24/7 machines whose normal downtime consists of a reboot.
+In that case the drift factor value makes little difference. But on the
+rare occasion that the machine is shut down for an extended period, then
+cold drift should yield better results.
+.PP
+.B Steps to calculate cold drift:
+.IP 1 2
+.B "Ensure that NTP daemon will not be launched at startup."
+.IP 2 2
+.RI The " System Clock " "time must be correct at shutdown!"
+.IP 3 2
+Shut down the system.
+.IP 4 2
+Let an extended period pass without changing the Hardware Clock.
+.IP 5 2
+Start the system.
+.IP 6 2
+.RB "Immediately use " hwclock " to set the correct time, adding the"
+.BR \%\-\-update\-drift " option."
+.PP
+Note: if step 6 uses
+.BR \%\-\-systohc ,
+then the System Clock must be set correctly (step 6a) just before doing so.
+.PP
+.RB "Having " hwclock
+calculate the drift factor is a good starting point, but for optimal
+results it will likely need to be adjusted by directly editing the
+.I @ADJTIME_PATH@
+file. Continue to test and refine the drift factor until the Hardware
+Clock is corrected properly at startup. To check this, first make sure
+that the System Time is correct before shutdown and then use
+.BR \%sntp ", or " \%date\ \-Ins
+and a precision timepiece, immediately after startup.
+.SS LOCAL vs UTC
+Keeping the Hardware Clock in a local timescale causes inconsistent
+daylight saving time results:
+.IP \(bu 2
+If Linux is running during a daylight saving time change, the time
+written to the Hardware Clock will be adjusted for the change.
+.IP \(bu 2
+If Linux is NOT running during a daylight saving time change, the time
+read from the Hardware Clock will NOT be adjusted for the change.
+.PP
+The Hardware Clock on an ISA compatible system keeps only a date and time,
+it has no concept of timezone nor daylight saving. Therefore, when
+.B hwclock
+is told that it is in local time, it assumes it is in the 'correct'
+local time and makes no adjustments to the time read from it.
+.PP
+Linux handles daylight saving time changes transparently only when the
+Hardware Clock is kept in the UTC timescale. Doing so is made easy for
+system administrators as
+.B \%hwclock
+uses local time for its output and as the argument to the
+.BR \%\-\-date " option."
+.PP
+POSIX systems, like Linux, are designed to have the System Clock operate
+in the UTC timescale. The Hardware Clock's purpose is to initialize the
+System Clock, so also keeping it in UTC makes sense.
+.PP
+Linux does, however, attempt to accommodate the Hardware Clock being in
+the local timescale. This is primarily for dual-booting with older
+versions of MS Windows. From Windows 7 on, the RealTimeIsUniversal
+registry key is supposed to be working properly so that its Hardware
+Clock can be kept in UTC.
+.
+.SS POSIX vs 'RIGHT'
+A discussion on date-time configuration would be incomplete without
+addressing timezones, this is mostly well covered by
+.BR tzset (3).
+One area that seems to have no documentation is the 'right'
+directory of the Time Zone Database, sometimes called tz or zoneinfo.
+.PP
+There are two separate databases in the zoneinfo system, posix
+and 'right'. 'Right' (now named zoneinfo\-leaps) includes leap seconds and posix
+does not. To use the 'right' database the System Clock must be set to
+\%(UTC\ +\ leap seconds), which is equivalent to \%(TAI\ \-\ 10). This
+allows calculating the
+exact number of seconds between two dates that cross a leap second
+epoch. The System Clock is then converted to the correct civil time,
+including UTC, by using the 'right' timezone files which subtract the
+leap seconds. Note: this configuration is considered experimental and is
+known to have issues.
+.PP
+To configure a system to use a particular database all of the files
+located in its directory must be copied to the root of
+.IR \%/usr/share/zoneinfo .
+Files are never used directly from the posix or 'right' subdirectories, e.g.,
+.RI \%TZ=' right/Europe/Dublin '.
+This habit was becoming so common that the upstream zoneinfo project
+restructured the system's file tree by moving the posix and 'right'
+subdirectories out of the zoneinfo directory and into sibling directories:
+.PP
+.in +2
+.I /usr/share/zoneinfo
+.br
+.I /usr/share/zoneinfo\-posix
+.br
+.I /usr/share/zoneinfo\-leaps
+.PP
+Unfortunately, some Linux distributions are changing it back to the old
+tree structure in their packages. So the problem of system
+administrators reaching into the 'right' subdirectory persists. This
+causes the system timezone to be configured to include leap seconds
+while the zoneinfo database is still configured to exclude them. Then
+when an application such as a World Clock needs the South_Pole timezone
+file; or an email MTA, or
+.B hwclock
+needs the UTC timezone file; they fetch it from the root of
+.I \%/usr/share/zoneinfo
+, because that is what they are supposed to do. Those files exclude leap
+seconds, but the System Clock now includes them, causing an incorrect
+time conversion.
+.PP
+Attempting to mix and match files from these separate databases will not
+work, because they each require the System Clock to use a different
+timescale. The zoneinfo database must be configured to use either posix
+or 'right', as described above, or by assigning a database path to the
+.SB TZDIR
+environment variable.
+.SH EXIT STATUS
+One of the following exit values will be returned:
+.TP
+.BR EXIT_SUCCESS " ('0' on POSIX systems)"
+Successful program execution.
+.TP
+.BR EXIT_FAILURE " ('1' on POSIX systems)"
+The operation failed or the command syntax was not valid.
+.SH ENVIRONMENT
+.TP
+.B TZ
+If this variable is set its value takes precedence over the system
+configured timezone.
+.TP
+.B TZDIR
+If this variable is set its value takes precedence over the system
+configured timezone database directory path.
+.SH FILES
+.TP
+.I @ADJTIME_PATH@
+The configuration and state file for hwclock.
+.TP
+.I /etc/localtime
+The system timezone file.
+.TP
+.I /usr/share/zoneinfo/
+The system timezone database directory.
+.PP
+Device files
+.B hwclock
+may try for Hardware Clock access:
+.br
+.I /dev/rtc0
+.br
+.I /dev/rtc
+.br
+.I /dev/misc/rtc
+.br
+.I /dev/efirtc
+.br
+.I /dev/misc/efirtc
+.SH "SEE ALSO"
+.BR date (1),
+.BR adjtimex (8),
+.BR gettimeofday (2),
+.BR settimeofday (2),
+.BR crontab (1p),
+.BR tzset (3)
+.
+.SH AUTHORS
+Written by Bryan Henderson, September 1996 (bryanh@giraffe-data.com),
+based on work done on the
+.BR \%clock (8)
+program by Charles Hedrick, Rob Hooft, and Harald Koenig.
+See the source code for complete history and credits.
+.
+.SH AVAILABILITY
+The hwclock command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c
new file mode 100644
index 0000000..c1cfbd3
--- /dev/null
+++ b/sys-utils/hwclock.c
@@ -0,0 +1,1602 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Since 7a3000f7ba548cf7d74ac77cc63fe8de228a669e (v2.30) hwclock is linked
+ * with parse_date.y from gnullib. This gnulib code is distributed with GPLv3.
+ * Use --disable-hwclock-gplv3 to exclude this code.
+ *
+ *
+ * clock.c was written by Charles Hedrick, hedrick@cs.rutgers.edu, Apr 1992
+ * Modified for clock adjustments - Rob Hooft <hooft@chem.ruu.nl>, Nov 1992
+ * Improvements by Harald Koenig <koenig@nova.tat.physik.uni-tuebingen.de>
+ * and Alan Modra <alan@spri.levels.unisa.edu.au>.
+ *
+ * Major rewrite by Bryan Henderson <bryanh@giraffe-data.com>, 96.09.19.
+ * The new program is called hwclock. New features:
+ *
+ * - You can set the hardware clock without also modifying the system
+ * clock.
+ * - You can read and set the clock with finer than 1 second precision.
+ * - When you set the clock, hwclock automatically refigures the drift
+ * rate, based on how far off the clock was before you set it.
+ *
+ * Reshuffled things, added sparc code, and re-added alpha stuff
+ * by David Mosberger <davidm@azstarnet.com>
+ * and Jay Estabrook <jestabro@amt.tay1.dec.com>
+ * and Martin Ostermann <ost@coments.rwth-aachen.de>, aeb@cwi.nl, 990212.
+ *
+ * Fix for Award 2094 bug, Dave Coffin (dcoffin@shore.net) 11/12/98
+ * Change of local time handling, Stefan Ring <e9725446@stud3.tuwien.ac.at>
+ * Change of adjtime handling, James P. Rutledge <ao112@rgfn.epcc.edu>.
+ *
+ *
+ */
+/*
+ * Explanation of `adjusting' (Rob Hooft):
+ *
+ * The problem with my machine is that its CMOS clock is 10 seconds
+ * per day slow. With this version of clock.c, and my '/etc/rc.local'
+ * reading '/etc/clock -au' instead of '/etc/clock -u -s', this error
+ * is automatically corrected at every boot.
+ *
+ * To do this job, the program reads and writes the file '/etc/adjtime'
+ * to determine the correction, and to save its data. In this file are
+ * three numbers:
+ *
+ * 1) the correction in seconds per day. (So if your clock runs 5
+ * seconds per day fast, the first number should read -5.0)
+ * 2) the number of seconds since 1/1/1970 the last time the program
+ * was used
+ * 3) the remaining part of a second which was leftover after the last
+ * adjustment
+ *
+ * Installation and use of this program:
+ *
+ * a) create a file '/etc/adjtime' containing as the first and only
+ * line: '0.0 0 0.0'
+ * b) run 'clock -au' or 'clock -a', depending on whether your cmos is
+ * in universal or local time. This updates the second number.
+ * c) set your system time using the 'date' command.
+ * d) update your cmos time using 'clock -wu' or 'clock -w'
+ * e) replace the first number in /etc/adjtime by your correction.
+ * f) put the command 'clock -au' or 'clock -a' in your '/etc/rc.local'
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "pathnames.h"
+#include "hwclock.h"
+#include "timeutils.h"
+#include "env.h"
+#include "xalloc.h"
+#include "path.h"
+#include "strutils.h"
+
+#ifdef HAVE_LIBAUDIT
+#include <libaudit.h>
+static int hwaudit_fd = -1;
+#endif
+
+UL_DEBUG_DEFINE_MASK(hwclock);
+UL_DEBUG_DEFINE_MASKNAMES(hwclock) = UL_DEBUG_EMPTY_MASKNAMES;
+
+/* The struct that holds our hardware access routines */
+static struct clock_ops *ur;
+
+/* Maximal clock adjustment in seconds per day.
+ (adjtime() glibc call has 2145 seconds limit on i386, so it is good enough for us as well,
+ 43219 is a maximal safe value preventing exact_adjustment overflow.) */
+#define MAX_DRIFT 2145.0
+
+struct adjtime {
+ /*
+ * This is information we keep in the adjtime file that tells us how
+ * to do drift corrections. Elements are all straight from the
+ * adjtime file, so see documentation of that file for details.
+ * Exception is <dirty>, which is an indication that what's in this
+ * structure is not what's in the disk file (because it has been
+ * updated since read from the disk file).
+ */
+ int dirty;
+ /* line 1 */
+ double drift_factor;
+ time_t last_adj_time;
+ double not_adjusted;
+ /* line 2 */
+ time_t last_calib_time;
+ /*
+ * The most recent time that we set the clock from an external
+ * authority (as opposed to just doing a drift adjustment)
+ */
+ /* line 3 */
+ enum a_local_utc { UTC = 0, LOCAL, UNKNOWN } local_utc;
+ /*
+ * To which time zone, local or UTC, we most recently set the
+ * hardware clock.
+ */
+};
+
+static void hwclock_init_debug(const char *str)
+{
+ __UL_INIT_DEBUG_FROM_STRING(hwclock, HWCLOCK_DEBUG_, 0, str);
+
+ DBG(INIT, ul_debug("hwclock debug mask: 0x%04x", hwclock_debug_mask));
+ DBG(INIT, ul_debug("hwclock version: %s", PACKAGE_STRING));
+}
+
+/* FOR TESTING ONLY: inject random delays of up to 1000ms */
+static void up_to_1000ms_sleep(void)
+{
+ int usec = random() % 1000000;
+
+ DBG(RANDOM_SLEEP, ul_debug("sleeping ~%d usec", usec));
+ xusleep(usec);
+}
+
+/*
+ * time_t to timeval conversion.
+ */
+static struct timeval t2tv(time_t timet)
+{
+ struct timeval rettimeval;
+
+ rettimeval.tv_sec = timet;
+ rettimeval.tv_usec = 0;
+ return rettimeval;
+}
+
+/*
+ * The difference in seconds between two times in "timeval" format.
+ */
+double time_diff(struct timeval subtrahend, struct timeval subtractor)
+{
+ return (subtrahend.tv_sec - subtractor.tv_sec)
+ + (subtrahend.tv_usec - subtractor.tv_usec) / 1E6;
+}
+
+/*
+ * The time, in "timeval" format, which is <increment> seconds after the
+ * time <addend>. Of course, <increment> may be negative.
+ */
+static struct timeval time_inc(struct timeval addend, double increment)
+{
+ struct timeval newtime;
+
+ newtime.tv_sec = addend.tv_sec + (int)increment;
+ newtime.tv_usec = addend.tv_usec + (increment - (int)increment) * 1E6;
+
+ /*
+ * Now adjust it so that the microsecond value is between 0 and 1
+ * million.
+ */
+ if (newtime.tv_usec < 0) {
+ newtime.tv_usec += 1E6;
+ newtime.tv_sec -= 1;
+ } else if (newtime.tv_usec >= 1E6) {
+ newtime.tv_usec -= 1E6;
+ newtime.tv_sec += 1;
+ }
+ return newtime;
+}
+
+static int
+hw_clock_is_utc(const struct hwclock_control *ctl,
+ const struct adjtime adjtime)
+{
+ int ret;
+
+ if (ctl->utc)
+ ret = 1; /* --utc explicitly given on command line */
+ else if (ctl->local_opt)
+ ret = 0; /* --localtime explicitly given */
+ else
+ /* get info from adjtime file - default is UTC */
+ ret = (adjtime.local_utc != LOCAL);
+ if (ctl->verbose)
+ printf(_("Assuming hardware clock is kept in %s time.\n"),
+ ret ? _("UTC") : _("local"));
+ return ret;
+}
+
+/*
+ * Read the adjustment parameters out of the /etc/adjtime file.
+ *
+ * Return them as the adjtime structure <*adjtime_p>. Its defaults are
+ * initialized in main().
+ */
+static int read_adjtime(const struct hwclock_control *ctl,
+ struct adjtime *adjtime_p)
+{
+ FILE *adjfile;
+ char line1[81]; /* String: first line of adjtime file */
+ char line2[81]; /* String: second line of adjtime file */
+ char line3[81]; /* String: third line of adjtime file */
+
+ if (access(ctl->adj_file_name, R_OK) != 0)
+ return EXIT_SUCCESS;
+
+ adjfile = fopen(ctl->adj_file_name, "r"); /* open file for reading */
+ if (adjfile == NULL) {
+ warn(_("cannot open %s"), ctl->adj_file_name);
+ return EXIT_FAILURE;
+ }
+
+ if (!fgets(line1, sizeof(line1), adjfile))
+ line1[0] = '\0'; /* In case fgets fails */
+ if (!fgets(line2, sizeof(line2), adjfile))
+ line2[0] = '\0'; /* In case fgets fails */
+ if (!fgets(line3, sizeof(line3), adjfile))
+ line3[0] = '\0'; /* In case fgets fails */
+
+ fclose(adjfile);
+
+ sscanf(line1, "%lf %ld %lf",
+ &adjtime_p->drift_factor,
+ &adjtime_p->last_adj_time,
+ &adjtime_p->not_adjusted);
+
+ sscanf(line2, "%ld", &adjtime_p->last_calib_time);
+
+ if (!strcmp(line3, "UTC\n")) {
+ adjtime_p->local_utc = UTC;
+ } else if (!strcmp(line3, "LOCAL\n")) {
+ adjtime_p->local_utc = LOCAL;
+ } else {
+ adjtime_p->local_utc = UNKNOWN;
+ if (line3[0]) {
+ warnx(_("Warning: unrecognized third line in adjtime file\n"
+ "(Expected: `UTC' or `LOCAL' or nothing.)"));
+ }
+ }
+
+ if (ctl->verbose) {
+ printf(_
+ ("Last drift adjustment done at %ld seconds after 1969\n"),
+ (long)adjtime_p->last_adj_time);
+ printf(_("Last calibration done at %ld seconds after 1969\n"),
+ (long)adjtime_p->last_calib_time);
+ printf(_("Hardware clock is on %s time\n"),
+ (adjtime_p->local_utc ==
+ LOCAL) ? _("local") : (adjtime_p->local_utc ==
+ UTC) ? _("UTC") : _("unknown"));
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Wait until the falling edge of the Hardware Clock's update flag so that
+ * any time that is read from the clock immediately after we return will be
+ * exact.
+ *
+ * The clock only has 1 second precision, so it gives the exact time only
+ * once per second, right on the falling edge of the update flag.
+ *
+ * We wait (up to one second) either blocked waiting for an rtc device or in
+ * a CPU spin loop. The former is probably not very accurate.
+ *
+ * Return 0 if it worked, nonzero if it didn't.
+ */
+static int synchronize_to_clock_tick(const struct hwclock_control *ctl)
+{
+ int rc;
+
+ if (ctl->verbose)
+ printf(_("Waiting for clock tick...\n"));
+
+ rc = ur->synchronize_to_clock_tick(ctl);
+
+ if (ctl->verbose) {
+ if (rc)
+ printf(_("...synchronization failed\n"));
+ else
+ printf(_("...got clock tick\n"));
+ }
+
+ return rc;
+}
+
+/*
+ * Convert a time in broken down format (hours, minutes, etc.) into standard
+ * unix time (seconds into epoch). Return it as *systime_p.
+ *
+ * The broken down time is argument <tm>. This broken down time is either
+ * in local time zone or UTC, depending on value of logical argument
+ * "universal". True means it is in UTC.
+ *
+ * If the argument contains values that do not constitute a valid time, and
+ * mktime() recognizes this, return *valid_p == false and *systime_p
+ * undefined. However, mktime() sometimes goes ahead and computes a
+ * fictional time "as if" the input values were valid, e.g. if they indicate
+ * the 31st day of April, mktime() may compute the time of May 1. In such a
+ * case, we return the same fictional value mktime() does as *systime_p and
+ * return *valid_p == true.
+ */
+static int
+mktime_tz(const struct hwclock_control *ctl, struct tm tm,
+ time_t *systime_p)
+{
+ int valid;
+
+ if (ctl->universal)
+ *systime_p = timegm(&tm);
+ else
+ *systime_p = mktime(&tm);
+ if (*systime_p == -1) {
+ /*
+ * This apparently (not specified in mktime() documentation)
+ * means the 'tm' structure does not contain valid values
+ * (however, not containing valid values does _not_ imply
+ * mktime() returns -1).
+ */
+ valid = 0;
+ if (ctl->verbose)
+ printf(_("Invalid values in hardware clock: "
+ "%4d/%.2d/%.2d %.2d:%.2d:%.2d\n"),
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ } else {
+ valid = 1;
+ if (ctl->verbose)
+ printf(_
+ ("Hw clock time : %4d/%.2d/%.2d %.2d:%.2d:%.2d = "
+ "%ld seconds since 1969\n"), tm.tm_year + 1900,
+ tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec, (long)*systime_p);
+ }
+ return valid;
+}
+
+/*
+ * Read the hardware clock and return the current time via <tm> argument.
+ *
+ * Use the method indicated by <method> argument to access the hardware
+ * clock.
+ */
+static int
+read_hardware_clock(const struct hwclock_control *ctl,
+ int *valid_p, time_t *systime_p)
+{
+ struct tm tm;
+ int err;
+
+ err = ur->read_hardware_clock(ctl, &tm);
+ if (err)
+ return err;
+
+ if (ctl->verbose)
+ printf(_
+ ("Time read from Hardware Clock: %4d/%.2d/%.2d %02d:%02d:%02d\n"),
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
+ tm.tm_min, tm.tm_sec);
+ *valid_p = mktime_tz(ctl, tm, systime_p);
+
+ return 0;
+}
+
+/*
+ * Set the Hardware Clock to the time <newtime>, in local time zone or UTC,
+ * according to <universal>.
+ */
+static void
+set_hardware_clock(const struct hwclock_control *ctl, const time_t newtime)
+{
+ struct tm new_broken_time;
+ /*
+ * Time to which we will set Hardware Clock, in broken down format,
+ * in the time zone of caller's choice
+ */
+
+ if (ctl->universal)
+ gmtime_r(&newtime, &new_broken_time);
+ else
+ localtime_r(&newtime, &new_broken_time);
+
+ if (ctl->verbose)
+ printf(_("Setting Hardware Clock to %.2d:%.2d:%.2d "
+ "= %ld seconds since 1969\n"),
+ new_broken_time.tm_hour, new_broken_time.tm_min,
+ new_broken_time.tm_sec, (long)newtime);
+
+ if (!ctl->testing)
+ ur->set_hardware_clock(ctl, &new_broken_time);
+}
+
+static double
+get_hardware_delay(const struct hwclock_control *ctl)
+{
+ const char *devpath, *rtcname;
+ char name[128 + 1];
+ struct path_cxt *pc;
+ int rc;
+
+ devpath = ur->get_device_path();
+ if (!devpath)
+ goto unknown;
+
+ rtcname = strrchr(devpath, '/');
+ if (!rtcname || !*(rtcname + 1))
+ goto unknown;
+ rtcname++;
+
+ pc = ul_new_path("/sys/class/rtc/%s", rtcname);
+ if (!pc)
+ goto unknown;
+ rc = ul_path_scanf(pc, "name", "%128[^\n ]", &name);
+ ul_unref_path(pc);
+
+ if (rc != 1 || !*name)
+ goto unknown;
+
+ if (ctl->verbose)
+ printf(_("RTC type: '%s'\n"), name);
+
+ /* MC146818A-compatible (x86) */
+ if (strcmp(name, "rtc_cmos") == 0)
+ return 0.5;
+
+ /* Another HW */
+ return 0;
+unknown:
+ /* Let's be backwardly compatible */
+ return 0.5;
+}
+
+
+/*
+ * Set the Hardware Clock to the time "sethwtime", in local time zone or
+ * UTC, according to "universal".
+ *
+ * Wait for a fraction of a second so that "sethwtime" is the value of the
+ * Hardware Clock as of system time "refsystime", which is in the past. For
+ * example, if "sethwtime" is 14:03:05 and "refsystime" is 12:10:04.5 and
+ * the current system time is 12:10:06.0: Wait .5 seconds (to make exactly 2
+ * seconds since "refsystime") and then set the Hardware Clock to 14:03:07,
+ * thus getting a precise and retroactive setting of the clock. The .5 delay is
+ * default on x86, see --delay and get_hardware_delay().
+ *
+ * (Don't be confused by the fact that the system clock and the Hardware
+ * Clock differ by two hours in the above example. That's just to remind you
+ * that there are two independent time scales here).
+ *
+ * This function ought to be able to accept set times as fractional times.
+ * Idea for future enhancement.
+ */
+static void
+set_hardware_clock_exact(const struct hwclock_control *ctl,
+ const time_t sethwtime,
+ const struct timeval refsystime)
+{
+ /*
+ * The Hardware Clock can only be set to any integer time plus one
+ * half second. The integer time is required because there is no
+ * interface to set or get a fractional second. The additional half
+ * second is because the Hardware Clock updates to the following
+ * second precisely 500 ms (not 1 second!) after you release the
+ * divider reset (after setting the new time) - see description of
+ * DV2, DV1, DV0 in Register A in the MC146818A data sheet (and note
+ * that although that document doesn't say so, real-world code seems
+ * to expect that the SET bit in Register B functions the same way).
+ * That means that, e.g., when you set the clock to 1:02:03, it
+ * effectively really sets it to 1:02:03.5, because it will update to
+ * 1:02:04 only half a second later. Our caller passes the desired
+ * integer Hardware Clock time in sethwtime, and the corresponding
+ * system time (which may have a fractional part, and which may or may
+ * not be the same!) in refsystime. In an ideal situation, we would
+ * then apply sethwtime to the Hardware Clock at refsystime+500ms, so
+ * that when the Hardware Clock ticks forward to sethwtime+1s half a
+ * second later at refsystime+1000ms, everything is in sync. So we
+ * spin, waiting for gettimeofday() to return a time at or after that
+ * time (refsystime+500ms) up to a tolerance value, initially 1ms. If
+ * we miss that time due to being preempted for some other process,
+ * then we increase the margin a little bit (initially 1ms, doubling
+ * each time), add 1 second (or more, if needed to get a time that is
+ * in the future) to both the time for which we are waiting and the
+ * time that we will apply to the Hardware Clock, and start waiting
+ * again.
+ *
+ * For example, the caller requests that we set the Hardware Clock to
+ * 1:02:03, with reference time (current system time) = 6:07:08.250.
+ * We want the Hardware Clock to update to 1:02:04 at 6:07:09.250 on
+ * the system clock, and the first such update will occur 0.500
+ * seconds after we write to the Hardware Clock, so we spin until the
+ * system clock reads 6:07:08.750. If we get there, great, but let's
+ * imagine the system is so heavily loaded that our process is
+ * preempted and by the time we get to run again, the system clock
+ * reads 6:07:11.990. We now want to wait until the next xx:xx:xx.750
+ * time, which is 6:07:12.750 (4.5 seconds after the reference time),
+ * at which point we will set the Hardware Clock to 1:02:07 (4 seconds
+ * after the originally requested time). If we do that successfully,
+ * then at 6:07:13.250 (5 seconds after the reference time), the
+ * Hardware Clock will update to 1:02:08 (5 seconds after the
+ * originally requested time), and all is well thereafter.
+ */
+
+ time_t newhwtime = sethwtime;
+ double target_time_tolerance_secs = 0.001; /* initial value */
+ double tolerance_incr_secs = 0.001; /* initial value */
+ double delay;
+ struct timeval rtc_set_delay_tv;
+
+ struct timeval targetsystime;
+ struct timeval nowsystime;
+ struct timeval prevsystime = refsystime;
+ double deltavstarget;
+
+ if (ctl->rtc_delay != -1.0) /* --delay specified */
+ delay = ctl->rtc_delay;
+ else
+ delay = get_hardware_delay(ctl);
+
+ if (ctl->verbose)
+ printf(_("Using delay: %.6f seconds\n"), delay);
+
+ rtc_set_delay_tv.tv_sec = 0;
+ rtc_set_delay_tv.tv_usec = delay * 1E6;
+
+ timeradd(&refsystime, &rtc_set_delay_tv, &targetsystime);
+
+ while (1) {
+ double ticksize;
+
+ ON_DBG(RANDOM_SLEEP, up_to_1000ms_sleep());
+
+ gettimeofday(&nowsystime, NULL);
+ deltavstarget = time_diff(nowsystime, targetsystime);
+ ticksize = time_diff(nowsystime, prevsystime);
+ prevsystime = nowsystime;
+
+ if (ticksize < 0) {
+ if (ctl->verbose)
+ printf(_("time jumped backward %.6f seconds "
+ "to %ld.%06ld - retargeting\n"),
+ ticksize, nowsystime.tv_sec,
+ nowsystime.tv_usec);
+ /* The retarget is handled at the end of the loop. */
+ } else if (deltavstarget < 0) {
+ /* deltavstarget < 0 if current time < target time */
+ DBG(DELTA_VS_TARGET,
+ ul_debug("%ld.%06ld < %ld.%06ld (%.6f)",
+ nowsystime.tv_sec, nowsystime.tv_usec,
+ targetsystime.tv_sec,
+ targetsystime.tv_usec, deltavstarget));
+ continue; /* not there yet - keep spinning */
+ } else if (deltavstarget <= target_time_tolerance_secs) {
+ /* Close enough to the target time; done waiting. */
+ break;
+ } else /* (deltavstarget > target_time_tolerance_secs) */ {
+ /*
+ * We missed our window. Increase the tolerance and
+ * aim for the next opportunity.
+ */
+ if (ctl->verbose)
+ printf(_("missed it - %ld.%06ld is too far "
+ "past %ld.%06ld (%.6f > %.6f)\n"),
+ nowsystime.tv_sec,
+ nowsystime.tv_usec,
+ targetsystime.tv_sec,
+ targetsystime.tv_usec,
+ deltavstarget,
+ target_time_tolerance_secs);
+ target_time_tolerance_secs += tolerance_incr_secs;
+ tolerance_incr_secs *= 2;
+ }
+
+ /*
+ * Aim for the same offset (tv_usec) within the second in
+ * either the current second (if that offset hasn't arrived
+ * yet), or the next second.
+ */
+ if (nowsystime.tv_usec < targetsystime.tv_usec)
+ targetsystime.tv_sec = nowsystime.tv_sec;
+ else
+ targetsystime.tv_sec = nowsystime.tv_sec + 1;
+ }
+
+ newhwtime = sethwtime
+ + ceil(time_diff(nowsystime, refsystime)
+ - delay /* don't count this */);
+ if (ctl->verbose)
+ printf(_("%ld.%06ld is close enough to %ld.%06ld (%.6f < %.6f)\n"
+ "Set RTC to %ld (%ld + %d; refsystime = %ld.%06ld)\n"),
+ nowsystime.tv_sec, nowsystime.tv_usec,
+ targetsystime.tv_sec, targetsystime.tv_usec,
+ deltavstarget, target_time_tolerance_secs,
+ newhwtime, sethwtime,
+ (int)(newhwtime - sethwtime),
+ refsystime.tv_sec, refsystime.tv_usec);
+
+ set_hardware_clock(ctl, newhwtime);
+}
+
+static int
+display_time(struct timeval hwctime)
+{
+ char buf[ISO_BUFSIZ];
+
+ if (strtimeval_iso(&hwctime, ISO_TIMESTAMP_DOT, buf, sizeof(buf)))
+ return EXIT_FAILURE;
+
+ printf("%s\n", buf);
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Adjusts System time, sets the kernel's timezone and RTC timescale.
+ *
+ * The kernel warp_clock function adjusts the System time according to the
+ * tz.tz_minuteswest argument and sets PCIL (see below). At boot settimeofday(2)
+ * has one-shot access to this function as shown in the table below.
+ *
+ * +-------------------------------------------------------------------------+
+ * | settimeofday(tv, tz) |
+ * |-------------------------------------------------------------------------|
+ * | Arguments | System Time | TZ | PCIL | | warp_clock |
+ * | tv | tz | set | warped | set | set | firsttime | locked |
+ * |---------|---------|---------------|-----|------|-----------|------------|
+ * | pointer | NULL | yes | no | no | no | 1 | no |
+ * | NULL | ptr2utc | no | no | yes | no | 0 | yes |
+ * | NULL | pointer | no | yes | yes | yes | 0 | yes |
+ * +-------------------------------------------------------------------------+
+ * ptr2utc: tz.tz_minuteswest is zero (UTC).
+ * PCIL: persistent_clock_is_local, sets the "11 minute mode" timescale.
+ * firsttime: locks the warp_clock function (initialized to 1 at boot).
+ *
+ * +---------------------------------------------------------------------------+
+ * | op | RTC scale | settimeofday calls |
+ * |---------|-----------|-----------------------------------------------------|
+ * | systz | Local | 1) warps system time*, sets PCIL* and kernel tz |
+ * | systz | UTC | 1st) locks warp_clock* 2nd) sets kernel tz |
+ * | hctosys | Local | 1st) sets PCIL* & kernel tz 2nd) sets system time |
+ * | hctosys | UTC | 1st) locks warp* 2nd) sets tz 3rd) sets system time |
+ * +---------------------------------------------------------------------------+
+ * * only on first call after boot
+ *
+ * POSIX 2008 marked TZ in settimeofday() as deprecated. Unfortunately,
+ * different C libraries react to this deprecation in a different way. Since
+ * glibc v2.31 settimeofday() will fail if both args are not NULL, Musl-C
+ * ignores TZ at all, etc. We use __set_time() and __set_timezone() to hide
+ * these portability issues and to keep code readable.
+ */
+#define __set_time(_tv) settimeofday(_tv, NULL)
+
+#ifndef SYS_settimeofday
+# ifdef __NR_settimeofday
+# define SYS_settimeofday __NR_settimeofday
+# else
+# define SYS_settimeofday __NR_settimeofday_time32
+# endif
+#endif
+
+static inline int __set_timezone(const struct timezone *tz)
+{
+#ifdef SYS_settimeofday
+ errno = 0;
+ return syscall(SYS_settimeofday, NULL, tz);
+#else
+ return settimeofday(NULL, tz);
+#endif
+}
+
+static int
+set_system_clock(const struct hwclock_control *ctl,
+ const struct timeval newtime)
+{
+ struct tm broken;
+ int minuteswest;
+ int rc = 0;
+
+ localtime_r(&newtime.tv_sec, &broken);
+ minuteswest = -get_gmtoff(&broken) / 60;
+
+ if (ctl->verbose) {
+ if (ctl->universal) {
+ puts(_("Calling settimeofday(NULL, 0) "
+ "to lock the warp_clock function."));
+ if (!( ctl->universal && !minuteswest ))
+ printf(_("Calling settimeofday(NULL, %d) "
+ "to set the kernel timezone.\n"),
+ minuteswest);
+ } else
+ printf(_("Calling settimeofday(NULL, %d) to warp "
+ "System time, set PCIL and the kernel tz.\n"),
+ minuteswest);
+
+ if (ctl->hctosys)
+ printf(_("Calling settimeofday(%ld.%06ld, NULL) "
+ "to set the System time.\n"),
+ newtime.tv_sec, newtime.tv_usec);
+ }
+
+ if (!ctl->testing) {
+ const struct timezone tz_utc = { 0 };
+ const struct timezone tz = { minuteswest };
+
+ /* If UTC RTC: lock warp_clock and PCIL */
+ if (ctl->universal)
+ rc = __set_timezone(&tz_utc);
+
+ /* Set kernel tz; if localtime RTC: warp_clock and set PCIL */
+ if (!rc && !( ctl->universal && !minuteswest ))
+ rc = __set_timezone(&tz);
+
+ /* Set the System Clock */
+ if ((!rc || errno == ENOSYS) && ctl->hctosys)
+ rc = __set_time(&newtime);
+
+ if (rc) {
+ warn(_("settimeofday() failed"));
+ return EXIT_FAILURE;
+ }
+ }
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Refresh the last calibrated and last adjusted timestamps in <*adjtime_p>
+ * to facilitate future drift calculations based on this set point.
+ *
+ * With the --update-drift option:
+ * Update the drift factor in <*adjtime_p> based on the fact that the
+ * Hardware Clock was just calibrated to <nowtime> and before that was
+ * set to the <hclocktime> time scale.
+ */
+static void
+adjust_drift_factor(const struct hwclock_control *ctl,
+ struct adjtime *adjtime_p,
+ const struct timeval nowtime,
+ const struct timeval hclocktime)
+{
+ if (!ctl->update) {
+ if (ctl->verbose)
+ printf(_("Not adjusting drift factor because the "
+ "--update-drift option was not used.\n"));
+ } else if (adjtime_p->last_calib_time == 0) {
+ if (ctl->verbose)
+ printf(_("Not adjusting drift factor because last "
+ "calibration time is zero,\n"
+ "so history is bad and calibration startover "
+ "is necessary.\n"));
+ } else if ((hclocktime.tv_sec - adjtime_p->last_calib_time) < 4 * 60 * 60) {
+ if (ctl->verbose)
+ printf(_("Not adjusting drift factor because it has "
+ "been less than four hours since the last "
+ "calibration.\n"));
+ } else {
+ /*
+ * At adjustment time we drift correct the hardware clock
+ * according to the contents of the adjtime file and refresh
+ * its last adjusted timestamp.
+ *
+ * At calibration time we set the Hardware Clock and refresh
+ * both timestamps in <*adjtime_p>.
+ *
+ * Here, with the --update-drift option, we also update the
+ * drift factor in <*adjtime_p>.
+ *
+ * Let us do computation in doubles. (Floats almost suffice,
+ * but 195 days + 1 second equals 195 days in floats.)
+ */
+ const double sec_per_day = 24.0 * 60.0 * 60.0;
+ double factor_adjust;
+ double drift_factor;
+ struct timeval last_calib;
+
+ last_calib = t2tv(adjtime_p->last_calib_time);
+ /*
+ * Correction to apply to the current drift factor.
+ *
+ * Simplified: uncorrected_drift / days_since_calibration.
+ *
+ * hclocktime is fully corrected with the current drift factor.
+ * Its difference from nowtime is the missed drift correction.
+ */
+ factor_adjust = time_diff(nowtime, hclocktime) /
+ (time_diff(nowtime, last_calib) / sec_per_day);
+
+ drift_factor = adjtime_p->drift_factor + factor_adjust;
+ if (fabs(drift_factor) > MAX_DRIFT) {
+ if (ctl->verbose)
+ printf(_("Clock drift factor was calculated as "
+ "%f seconds/day.\n"
+ "It is far too much. Resetting to zero.\n"),
+ drift_factor);
+ drift_factor = 0;
+ } else {
+ if (ctl->verbose)
+ printf(_("Clock drifted %f seconds in the past "
+ "%f seconds\nin spite of a drift factor of "
+ "%f seconds/day.\n"
+ "Adjusting drift factor by %f seconds/day\n"),
+ time_diff(nowtime, hclocktime),
+ time_diff(nowtime, last_calib),
+ adjtime_p->drift_factor, factor_adjust);
+ }
+
+ adjtime_p->drift_factor = drift_factor;
+ }
+ adjtime_p->last_calib_time = nowtime.tv_sec;
+
+ adjtime_p->last_adj_time = nowtime.tv_sec;
+
+ adjtime_p->not_adjusted = 0;
+
+ adjtime_p->dirty = 1;
+}
+
+/*
+ * Calculate the drift correction currently needed for the
+ * Hardware Clock based on the last time it was adjusted,
+ * and the current drift factor, as stored in the adjtime file.
+ *
+ * The total drift adjustment needed is stored at tdrift_p.
+ *
+ */
+static void
+calculate_adjustment(const struct hwclock_control *ctl,
+ const double factor,
+ const time_t last_time,
+ const double not_adjusted,
+ const time_t systime, struct timeval *tdrift_p)
+{
+ double exact_adjustment;
+
+ exact_adjustment =
+ ((double)(systime - last_time)) * factor / (24 * 60 * 60)
+ + not_adjusted;
+ tdrift_p->tv_sec = (time_t) floor(exact_adjustment);
+ tdrift_p->tv_usec = (exact_adjustment -
+ (double)tdrift_p->tv_sec) * 1E6;
+ if (ctl->verbose) {
+ printf(P_("Time since last adjustment is %ld second\n",
+ "Time since last adjustment is %ld seconds\n",
+ (systime - last_time)),
+ (systime - last_time));
+ printf(_("Calculated Hardware Clock drift is %ld.%06ld seconds\n"),
+ tdrift_p->tv_sec, tdrift_p->tv_usec);
+ }
+}
+
+/*
+ * Write the contents of the <adjtime> structure to its disk file.
+ *
+ * But if the contents are clean (unchanged since read from disk), don't
+ * bother.
+ */
+static int save_adjtime(const struct hwclock_control *ctl,
+ const struct adjtime *adjtime)
+{
+ char *content; /* Stuff to write to disk file */
+ FILE *fp;
+
+ xasprintf(&content, "%f %ld %f\n%ld\n%s\n",
+ adjtime->drift_factor,
+ adjtime->last_adj_time,
+ adjtime->not_adjusted,
+ adjtime->last_calib_time,
+ (adjtime->local_utc == LOCAL) ? "LOCAL" : "UTC");
+
+ if (ctl->verbose){
+ printf(_("New %s data:\n%s"),
+ ctl->adj_file_name, content);
+ }
+
+ if (!ctl->testing) {
+ fp = fopen(ctl->adj_file_name, "w");
+ if (fp == NULL) {
+ warn(_("cannot open %s"), ctl->adj_file_name);
+ return EXIT_FAILURE;
+ }
+
+ if (fputs(content, fp) < 0 || close_stream(fp) != 0) {
+ warn(_("cannot update %s"), ctl->adj_file_name);
+ return EXIT_FAILURE;
+ }
+ }
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Do the adjustment requested, by 1) setting the Hardware Clock (if
+ * necessary), and 2) updating the last-adjusted time in the adjtime
+ * structure.
+ *
+ * Do not update anything if the Hardware Clock does not currently present a
+ * valid time.
+ *
+ * <hclocktime> is the drift corrected time read from the Hardware Clock.
+ *
+ * <read_time> was the system time when the <hclocktime> was read, which due
+ * to computational delay could be a short time ago. It is used to define a
+ * trigger point for setting the Hardware Clock. The fractional part of the
+ * Hardware clock set time is subtracted from read_time to 'refer back', or
+ * delay, the trigger point. Fractional parts must be accounted for in this
+ * way, because the Hardware Clock can only be set to a whole second.
+ *
+ * <universal>: the Hardware Clock is kept in UTC.
+ *
+ * <testing>: We are running in test mode (no updating of clock).
+ *
+ */
+static void
+do_adjustment(const struct hwclock_control *ctl, struct adjtime *adjtime_p,
+ const struct timeval hclocktime,
+ const struct timeval read_time)
+{
+ if (adjtime_p->last_adj_time == 0) {
+ if (ctl->verbose)
+ printf(_("Not setting clock because last adjustment time is zero, "
+ "so history is bad.\n"));
+ } else if (fabs(adjtime_p->drift_factor) > MAX_DRIFT) {
+ if (ctl->verbose)
+ printf(_("Not setting clock because drift factor %f is far too high.\n"),
+ adjtime_p->drift_factor);
+ } else {
+ set_hardware_clock_exact(ctl, hclocktime.tv_sec,
+ time_inc(read_time,
+ -(hclocktime.tv_usec / 1E6)));
+ adjtime_p->last_adj_time = hclocktime.tv_sec;
+ adjtime_p->not_adjusted = 0;
+ adjtime_p->dirty = 1;
+ }
+}
+
+static void determine_clock_access_method(const struct hwclock_control *ctl)
+{
+ ur = NULL;
+
+#ifdef USE_HWCLOCK_CMOS
+ if (ctl->directisa)
+ ur = probe_for_cmos_clock();
+#endif
+#ifdef __linux__
+ if (!ur)
+ ur = probe_for_rtc_clock(ctl);
+#endif
+ if (ur) {
+ if (ctl->verbose)
+ puts(ur->interface_name);
+
+ } else {
+ if (ctl->verbose)
+ printf(_("No usable clock interface found.\n"));
+
+ warnx(_("Cannot access the Hardware Clock via "
+ "any known method."));
+
+ if (!ctl->verbose)
+ warnx(_("Use the --verbose option to see the "
+ "details of our search for an access "
+ "method."));
+ hwclock_exit(ctl, EXIT_FAILURE);
+ }
+}
+
+/* Do all the normal work of hwclock - read, set clock, etc. */
+static int
+manipulate_clock(const struct hwclock_control *ctl, const time_t set_time,
+ const struct timeval startup_time, struct adjtime *adjtime)
+{
+ /* The time at which we read the Hardware Clock */
+ struct timeval read_time = { 0 };
+ /*
+ * The Hardware Clock gives us a valid time, or at
+ * least something close enough to fool mktime().
+ */
+ int hclock_valid = 0;
+ /*
+ * Tick synchronized time read from the Hardware Clock and
+ * then drift corrected for all operations except --show.
+ */
+ struct timeval hclocktime = { 0 };
+ /*
+ * hclocktime correlated to startup_time. That is, what drift
+ * corrected Hardware Clock time would have been at start up.
+ */
+ struct timeval startup_hclocktime = { 0 };
+ /* Total Hardware Clock drift correction needed. */
+ struct timeval tdrift = { 0 };
+
+ if ((ctl->set || ctl->systohc || ctl->adjust) &&
+ (adjtime->local_utc == UTC) != ctl->universal) {
+ adjtime->local_utc = ctl->universal ? UTC : LOCAL;
+ adjtime->dirty = 1;
+ }
+ /*
+ * Negate the drift correction, because we want to 'predict' a
+ * Hardware Clock time that includes drift.
+ */
+ if (ctl->predict) {
+ hclocktime = t2tv(set_time);
+ calculate_adjustment(ctl, adjtime->drift_factor,
+ adjtime->last_adj_time,
+ adjtime->not_adjusted,
+ hclocktime.tv_sec, &tdrift);
+ hclocktime = time_inc(hclocktime, (double)
+ -(tdrift.tv_sec + tdrift.tv_usec / 1E6));
+ if (ctl->verbose) {
+ printf(_ ("Target date: %ld\n"), set_time);
+ printf(_ ("Predicted RTC: %ld\n"), hclocktime.tv_sec);
+ }
+ return display_time(hclocktime);
+ }
+
+ if (ctl->systz)
+ return set_system_clock(ctl, startup_time);
+
+ if (ur->get_permissions())
+ return EXIT_FAILURE;
+
+ /*
+ * Read and drift correct RTC time; except for RTC set functions
+ * without the --update-drift option because: 1) it's not needed;
+ * 2) it enables setting a corrupted RTC without reading it first;
+ * 3) it significantly reduces system shutdown time.
+ */
+ if ( ! ((ctl->set || ctl->systohc) && !ctl->update)) {
+ /*
+ * Timing critical - do not change the order of, or put
+ * anything between the follow three statements.
+ * Synchronization failure MUST exit, because all drift
+ * operations are invalid without it.
+ */
+ if (synchronize_to_clock_tick(ctl))
+ return EXIT_FAILURE;
+ read_hardware_clock(ctl, &hclock_valid, &hclocktime.tv_sec);
+ gettimeofday(&read_time, NULL);
+
+ if (!hclock_valid) {
+ warnx(_("RTC read returned an invalid value."));
+ return EXIT_FAILURE;
+ }
+ /*
+ * Calculate and apply drift correction to the Hardware Clock
+ * time for everything except --show
+ */
+ calculate_adjustment(ctl, adjtime->drift_factor,
+ adjtime->last_adj_time,
+ adjtime->not_adjusted,
+ hclocktime.tv_sec, &tdrift);
+ if (!ctl->show)
+ hclocktime = time_inc(tdrift, hclocktime.tv_sec);
+
+ startup_hclocktime =
+ time_inc(hclocktime, time_diff(startup_time, read_time));
+ }
+ if (ctl->show || ctl->get) {
+ return display_time(startup_hclocktime);
+ }
+
+ if (ctl->set) {
+ set_hardware_clock_exact(ctl, set_time, startup_time);
+ if (!ctl->noadjfile)
+ adjust_drift_factor(ctl, adjtime, t2tv(set_time),
+ startup_hclocktime);
+ } else if (ctl->adjust) {
+ if (tdrift.tv_sec > 0 || tdrift.tv_sec < -1)
+ do_adjustment(ctl, adjtime, hclocktime, read_time);
+ else
+ printf(_("Needed adjustment is less than one second, "
+ "so not setting clock.\n"));
+ } else if (ctl->systohc) {
+ struct timeval nowtime, reftime;
+ /*
+ * We can only set_hardware_clock_exact to a
+ * whole seconds time, so we set it with
+ * reference to the most recent whole
+ * seconds time.
+ */
+ gettimeofday(&nowtime, NULL);
+ reftime.tv_sec = nowtime.tv_sec;
+ reftime.tv_usec = 0;
+ set_hardware_clock_exact(ctl, (time_t) reftime.tv_sec, reftime);
+ if (!ctl->noadjfile)
+ adjust_drift_factor(ctl, adjtime, nowtime,
+ hclocktime);
+ } else if (ctl->hctosys) {
+ return set_system_clock(ctl, hclocktime);
+ }
+ if (!ctl->noadjfile && adjtime->dirty)
+ return save_adjtime(ctl, adjtime);
+ return EXIT_SUCCESS;
+}
+
+/**
+ * Get or set the kernel RTC driver's epoch on Alpha machines.
+ * ISA machines are hard coded for 1900.
+ */
+#if defined(__linux__) && defined(__alpha__)
+static void
+manipulate_epoch(const struct hwclock_control *ctl)
+{
+ if (ctl->getepoch) {
+ unsigned long epoch;
+
+ if (get_epoch_rtc(ctl, &epoch))
+ warnx(_("unable to read the RTC epoch."));
+ else
+ printf(_("The RTC epoch is set to %lu.\n"), epoch);
+ } else if (ctl->setepoch) {
+ if (!ctl->epoch_option)
+ warnx(_("--epoch is required for --setepoch."));
+ else if (!ctl->testing)
+ if (set_epoch_rtc(ctl))
+ warnx(_("unable to set the RTC epoch."));
+ }
+}
+#endif /* __linux__ __alpha__ */
+
+static void out_version(void)
+{
+ printf(UTIL_LINUX_VERSION);
+}
+
+static void __attribute__((__noreturn__))
+usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(_(" %s [function] [option...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ puts(_("Time clocks utility."));
+
+ fputs(USAGE_FUNCTIONS, stdout);
+ puts(_(" -r, --show display the RTC time"));
+ puts(_(" --get display drift corrected RTC time"));
+ puts(_(" --set set the RTC according to --date"));
+ puts(_(" -s, --hctosys set the system time from the RTC"));
+ puts(_(" -w, --systohc set the RTC from the system time"));
+ puts(_(" --systz send timescale configurations to the kernel"));
+ puts(_(" -a, --adjust adjust the RTC to account for systematic drift"));
+#if defined(__linux__) && defined(__alpha__)
+ puts(_(" --getepoch display the RTC epoch"));
+ puts(_(" --setepoch set the RTC epoch according to --epoch"));
+#endif
+ puts(_(" --predict predict the drifted RTC time according to --date"));
+ fputs(USAGE_OPTIONS, stdout);
+ puts(_(" -u, --utc the RTC timescale is UTC"));
+ puts(_(" -l, --localtime the RTC timescale is Local"));
+#ifdef __linux__
+ printf(_(
+ " -f, --rtc <file> use an alternate file to %1$s\n"), _PATH_RTC_DEV);
+#endif
+ printf(_(
+ " --directisa use the ISA bus instead of %1$s access\n"), _PATH_RTC_DEV);
+ puts(_(" --date <time> date/time input for --set and --predict"));
+ puts(_(" --delay <sec> delay used when set new RTC time"));
+#if defined(__linux__) && defined(__alpha__)
+ puts(_(" --epoch <year> epoch input for --setepoch"));
+#endif
+ puts(_(" --update-drift update the RTC drift factor"));
+ printf(_(
+ " --noadjfile do not use %1$s\n"), _PATH_ADJTIME);
+ printf(_(
+ " --adjfile <file> use an alternate file to %1$s\n"), _PATH_ADJTIME);
+ puts(_(" --test dry run; implies --verbose"));
+ puts(_(" -v, --verbose display more details"));
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(22));
+ printf(USAGE_MAN_TAIL("hwclock(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct hwclock_control ctl = {
+ .show = 1, /* default op is show */
+ .rtc_delay = -1.0 /* unspecified */
+ };
+ struct timeval startup_time;
+ struct adjtime adjtime = { 0 };
+ /*
+ * The time we started up, in seconds into the epoch, including
+ * fractions.
+ */
+ time_t set_time = 0; /* Time to which user said to set Hardware Clock */
+ int rc, c;
+
+ /* Long only options. */
+ enum {
+ OPT_ADJFILE = CHAR_MAX + 1,
+ OPT_DATE,
+ OPT_DELAY,
+ OPT_DIRECTISA,
+ OPT_EPOCH,
+ OPT_GET,
+ OPT_GETEPOCH,
+ OPT_NOADJFILE,
+ OPT_PREDICT,
+ OPT_SET,
+ OPT_SETEPOCH,
+ OPT_SYSTZ,
+ OPT_TEST,
+ OPT_UPDATE
+ };
+
+ static const struct option longopts[] = {
+ { "adjust", no_argument, NULL, 'a' },
+ { "help", no_argument, NULL, 'h' },
+ { "localtime", no_argument, NULL, 'l' },
+ { "show", no_argument, NULL, 'r' },
+ { "hctosys", no_argument, NULL, 's' },
+ { "utc", no_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'V' },
+ { "systohc", no_argument, NULL, 'w' },
+ { "debug", no_argument, NULL, 'D' },
+ { "ul-debug", required_argument, NULL, 'd' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "set", no_argument, NULL, OPT_SET },
+#if defined(__linux__) && defined(__alpha__)
+ { "getepoch", no_argument, NULL, OPT_GETEPOCH },
+ { "setepoch", no_argument, NULL, OPT_SETEPOCH },
+ { "epoch", required_argument, NULL, OPT_EPOCH },
+#endif
+ { "noadjfile", no_argument, NULL, OPT_NOADJFILE },
+ { "directisa", no_argument, NULL, OPT_DIRECTISA },
+ { "test", no_argument, NULL, OPT_TEST },
+ { "date", required_argument, NULL, OPT_DATE },
+ { "delay", required_argument, NULL, OPT_DELAY },
+#ifdef __linux__
+ { "rtc", required_argument, NULL, 'f' },
+#endif
+ { "adjfile", required_argument, NULL, OPT_ADJFILE },
+ { "systz", no_argument, NULL, OPT_SYSTZ },
+ { "predict", no_argument, NULL, OPT_PREDICT },
+ { "get", no_argument, NULL, OPT_GET },
+ { "update-drift", no_argument, NULL, OPT_UPDATE },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'a','r','s','w',
+ OPT_GET, OPT_GETEPOCH, OPT_PREDICT,
+ OPT_SET, OPT_SETEPOCH, OPT_SYSTZ },
+ { 'l', 'u' },
+ { OPT_ADJFILE, OPT_NOADJFILE },
+ { OPT_NOADJFILE, OPT_UPDATE },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ /* Remember what time we were invoked */
+ gettimeofday(&startup_time, NULL);
+
+#ifdef HAVE_LIBAUDIT
+ hwaudit_fd = audit_open();
+ if (hwaudit_fd < 0 && !(errno == EINVAL || errno == EPROTONOSUPPORT ||
+ errno == EAFNOSUPPORT)) {
+ /*
+ * You get these error codes only when the kernel doesn't
+ * have audit compiled in.
+ */
+ warnx(_("Unable to connect to audit system"));
+ return EXIT_FAILURE;
+ }
+#endif
+ setlocale(LC_ALL, "");
+#ifdef LC_NUMERIC
+ /*
+ * We need LC_CTYPE and LC_TIME and LC_MESSAGES, but must avoid
+ * LC_NUMERIC since it gives problems when we write to /etc/adjtime.
+ * - gqueri@mail.dotcom.fr
+ */
+ setlocale(LC_NUMERIC, "C");
+#endif
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv,
+ "hvVDd:alrsuwf:", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'D':
+ warnx(_("use --verbose, --debug has been deprecated."));
+ break;
+ case 'v':
+ ctl.verbose = 1;
+ break;
+ case 'd':
+ hwclock_init_debug(optarg);
+ break;
+ case 'a':
+ ctl.adjust = 1;
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+ case 'l':
+ ctl.local_opt = 1; /* --localtime */
+ break;
+ case 'r':
+ ctl.show = 1;
+ break;
+ case 's':
+ ctl.hctosys = 1;
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+ case 'u':
+ ctl.utc = 1;
+ break;
+ case 'w':
+ ctl.systohc = 1;
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+ case OPT_SET:
+ ctl.set = 1;
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+#if defined(__linux__) && defined(__alpha__)
+ case OPT_GETEPOCH:
+ ctl.getepoch = 1;
+ ctl.show = 0;
+ break;
+ case OPT_SETEPOCH:
+ ctl.setepoch = 1;
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+ case OPT_EPOCH:
+ ctl.epoch_option = optarg; /* --epoch */
+ break;
+#endif
+ case OPT_NOADJFILE:
+ ctl.noadjfile = 1;
+ break;
+ case OPT_DIRECTISA:
+ ctl.directisa = 1;
+ break;
+ case OPT_TEST:
+ ctl.testing = 1; /* --test */
+ ctl.verbose = 1;
+ break;
+ case OPT_DATE:
+ ctl.date_opt = optarg; /* --date */
+ break;
+ case OPT_DELAY:
+ ctl.rtc_delay = strtod_or_err(optarg, "invalid --delay argument");
+ break;
+ case OPT_ADJFILE:
+ ctl.adj_file_name = optarg; /* --adjfile */
+ break;
+ case OPT_SYSTZ:
+ ctl.systz = 1; /* --systz */
+ ctl.show = 0;
+ ctl.hwaudit_on = 1;
+ break;
+ case OPT_PREDICT:
+ ctl.predict = 1; /* --predict */
+ ctl.show = 0;
+ break;
+ case OPT_GET:
+ ctl.get = 1; /* --get */
+ ctl.show = 0;
+ break;
+ case OPT_UPDATE:
+ ctl.update = 1; /* --update-drift */
+ break;
+#ifdef __linux__
+ case 'f':
+ ctl.rtc_dev_name = optarg; /* --rtc */
+ break;
+#endif
+
+ case 'V': /* --version */
+ print_version(EXIT_SUCCESS);
+ case 'h': /* --help */
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argc -= optind) {
+ warnx(_("%d too many arguments given"), argc);
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (!ctl.adj_file_name)
+ ctl.adj_file_name = _PATH_ADJTIME;
+
+ if (ctl.update && !ctl.set && !ctl.systohc) {
+ warnx(_("--update-drift requires --set or --systohc"));
+ exit(EXIT_FAILURE);
+ }
+
+ if (ctl.noadjfile && !ctl.utc && !ctl.local_opt) {
+ warnx(_("With --noadjfile, you must specify "
+ "either --utc or --localtime"));
+ exit(EXIT_FAILURE);
+ }
+
+ if (ctl.set || ctl.predict) {
+ if (!ctl.date_opt) {
+ warnx(_("--date is required for --set or --predict"));
+ exit(EXIT_FAILURE);
+ }
+#ifdef USE_HWCLOCK_GPLv3_DATETIME
+ /* date(1) compatible GPLv3 parser */
+ struct timespec when = { 0 };
+
+ if (parse_date(&when, ctl.date_opt, NULL))
+ set_time = when.tv_sec;
+#else
+ /* minimalistic GPLv2 based parser */
+ usec_t usec;
+
+ if (parse_timestamp(ctl.date_opt, &usec) == 0)
+ set_time = (time_t) (usec / 1000000);
+#endif
+ else {
+ warnx(_("invalid date '%s'"), ctl.date_opt);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+#if defined(__linux__) && defined(__alpha__)
+ if (ctl.getepoch || ctl.setepoch) {
+ manipulate_epoch(&ctl);
+ hwclock_exit(&ctl, EXIT_SUCCESS);
+ }
+#endif
+
+ if (ctl.verbose) {
+ out_version();
+ printf(_("System Time: %ld.%06ld\n"),
+ startup_time.tv_sec, startup_time.tv_usec);
+ }
+
+ if (!ctl.systz && !ctl.predict)
+ determine_clock_access_method(&ctl);
+
+ if (!ctl.noadjfile && !(ctl.systz && (ctl.utc || ctl.local_opt))) {
+ if ((rc = read_adjtime(&ctl, &adjtime)) != 0)
+ hwclock_exit(&ctl, rc);
+ } else
+ /* Avoid writing adjtime file if we don't have to. */
+ adjtime.dirty = 0;
+ ctl.universal = hw_clock_is_utc(&ctl, adjtime);
+ rc = manipulate_clock(&ctl, set_time, startup_time, &adjtime);
+ if (ctl.testing)
+ puts(_("Test mode: nothing was changed."));
+ hwclock_exit(&ctl, rc);
+ return rc; /* Not reached */
+}
+
+void
+hwclock_exit(const struct hwclock_control *ctl
+#ifndef HAVE_LIBAUDIT
+ __attribute__((__unused__))
+#endif
+ , int status)
+{
+#ifdef HAVE_LIBAUDIT
+ if (ctl->hwaudit_on && !ctl->testing) {
+ audit_log_user_message(hwaudit_fd, AUDIT_USYS_CONFIG,
+ "op=change-system-time", NULL, NULL, NULL,
+ status == EXIT_SUCCESS ? 1 : 0);
+ }
+ close(hwaudit_fd);
+#endif
+ exit(status);
+}
+
+/*
+ * History of this program:
+ *
+ * 98.08.12 BJH Version 2.4
+ *
+ * Don't use century byte from Hardware Clock. Add comments telling why.
+ *
+ * 98.06.20 BJH Version 2.3.
+ *
+ * Make --hctosys set the kernel timezone from TZ environment variable
+ * and/or /usr/lib/zoneinfo. From Klaus Ripke (klaus@ripke.com).
+ *
+ * 98.03.05 BJH. Version 2.2.
+ *
+ * Add --getepoch and --setepoch.
+ *
+ * Fix some word length things so it works on Alpha.
+ *
+ * Make it work when /dev/rtc doesn't have the interrupt functions. In this
+ * case, busywait for the top of a second instead of blocking and waiting
+ * for the update complete interrupt.
+ *
+ * Fix a bunch of bugs too numerous to mention.
+ *
+ * 97.06.01: BJH. Version 2.1. Read and write the century byte (Byte 50) of
+ * the ISA Hardware Clock when using direct ISA I/O. Problem discovered by
+ * job (jei@iclnl.icl.nl).
+ *
+ * Use the rtc clock access method in preference to the KDGHWCLK method.
+ * Problem discovered by Andreas Schwab <schwab@LS5.informatik.uni-dortmund.de>.
+ *
+ * November 1996: Version 2.0.1. Modifications by Nicolai Langfeldt
+ * (janl@math.uio.no) to make it compile on linux 1.2 machines as well as
+ * more recent versions of the kernel. Introduced the NO_CLOCK access method
+ * and wrote feature test code to detect absence of rtc headers.
+ *
+ ***************************************************************************
+ * Maintenance notes
+ *
+ * To compile this, you must use GNU compiler optimization (-O option) in
+ * order to make the "extern inline" functions from asm/io.h (inb(), etc.)
+ * compile. If you don't optimize, which means the compiler will generate no
+ * inline functions, the references to these functions in this program will
+ * be compiled as external references. Since you probably won't be linking
+ * with any functions by these names, you will have unresolved external
+ * references when you link.
+ *
+ * Here's some info on how we must deal with the time that elapses while
+ * this program runs: There are two major delays as we run:
+ *
+ * 1) Waiting up to 1 second for a transition of the Hardware Clock so
+ * we are synchronized to the Hardware Clock.
+ * 2) Running the "date" program to interpret the value of our --date
+ * option.
+ *
+ * Reading the /etc/adjtime file is the next biggest source of delay and
+ * uncertainty.
+ *
+ * The user wants to know what time it was at the moment he invoked us, not
+ * some arbitrary time later. And in setting the clock, he is giving us the
+ * time at the moment we are invoked, so if we set the clock some time
+ * later, we have to add some time to that.
+ *
+ * So we check the system time as soon as we start up, then run "date" and
+ * do file I/O if necessary, then wait to synchronize with a Hardware Clock
+ * edge, then check the system time again to see how much time we spent. We
+ * immediately read the clock then and (if appropriate) report that time,
+ * and additionally, the delay we measured.
+ *
+ * If we're setting the clock to a time given by the user, we wait some more
+ * so that the total delay is an integral number of seconds, then set the
+ * Hardware Clock to the time the user requested plus that integral number
+ * of seconds. N.B. The Hardware Clock can only be set in integral seconds.
+ *
+ * If we're setting the clock to the system clock value, we wait for the
+ * system clock to reach the top of a second, and then set the Hardware
+ * Clock to the system clock's value.
+ *
+ * Here's an interesting point about setting the Hardware Clock: On my
+ * machine, when you set it, it sets to that precise time. But one can
+ * imagine another clock whose update oscillator marches on a steady one
+ * second period, so updating the clock between any two oscillator ticks is
+ * the same as updating it right at the earlier tick. To avoid any
+ * complications that might cause, we set the clock as soon as possible
+ * after an oscillator tick.
+ *
+ * About synchronizing to the Hardware Clock when reading the time: The
+ * precision of the Hardware Clock counters themselves is one second. You
+ * can't read the counters and find out that is 12:01:02.5. But if you
+ * consider the location in time of the counter's ticks as part of its
+ * value, then its precision is as infinite as time is continuous! What I'm
+ * saying is this: To find out the _exact_ time in the hardware clock, we
+ * wait until the next clock tick (the next time the second counter changes)
+ * and measure how long we had to wait. We then read the value of the clock
+ * counters and subtract the wait time and we know precisely what time it
+ * was when we set out to query the time.
+ *
+ * hwclock uses this method, and considers the Hardware Clock to have
+ * infinite precision.
+ */
diff --git a/sys-utils/hwclock.h b/sys-utils/hwclock.h
new file mode 100644
index 0000000..627cf51
--- /dev/null
+++ b/sys-utils/hwclock.h
@@ -0,0 +1,82 @@
+#ifndef HWCLOCK_CLOCK_H
+#define HWCLOCK_CLOCK_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "debug.h"
+
+#define HWCLOCK_DEBUG_INIT (1 << 0)
+#define HWCLOCK_DEBUG_RANDOM_SLEEP (1 << 1)
+#define HWCLOCK_DEBUG_DELTA_VS_TARGET (1 << 2)
+#define HWCLOCK_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(hwclock);
+#define DBG(m, x) __UL_DBG(hwclock, HWCLOCK_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(hwclock, HWCLOCK_DEBUG_, m, x)
+
+struct hwclock_control {
+ char *date_opt;
+ char *adj_file_name;
+ double rtc_delay; /* --delay <seconds> */
+#if defined(__linux__) && defined(__alpha__)
+ char *epoch_option;
+#endif
+#ifdef __linux__
+ char *rtc_dev_name;
+#endif
+ unsigned int
+ hwaudit_on:1,
+ adjust:1,
+ show:1,
+ hctosys:1,
+ utc:1,
+ systohc:1,
+#if defined(__linux__) && defined(__alpha__)
+ getepoch:1,
+ setepoch:1,
+#endif
+ noadjfile:1,
+ local_opt:1,
+ directisa:1,
+ testing:1,
+ systz:1,
+ predict:1,
+ get:1,
+ set:1,
+ update:1,
+ universal:1, /* will store hw_clock_is_utc() return value */
+ verbose:1;
+};
+
+struct clock_ops {
+ char *interface_name;
+ int (*get_permissions) (void);
+ int (*read_hardware_clock) (const struct hwclock_control *ctl, struct tm * tm);
+ int (*set_hardware_clock) (const struct hwclock_control *ctl, const struct tm * tm);
+ int (*synchronize_to_clock_tick) (const struct hwclock_control *ctl);
+ const char *(*get_device_path) (void);
+};
+
+extern struct clock_ops *probe_for_cmos_clock(void);
+extern struct clock_ops *probe_for_rtc_clock(const struct hwclock_control *ctl);
+
+/* hwclock.c */
+extern double time_diff(struct timeval subtrahend, struct timeval subtractor);
+
+/* rtc.c */
+#if defined(__linux__) && defined(__alpha__)
+extern int get_epoch_rtc(const struct hwclock_control *ctl, unsigned long *epoch);
+extern int set_epoch_rtc(const struct hwclock_control *ctl);
+#endif
+
+extern void __attribute__((__noreturn__))
+hwclock_exit(const struct hwclock_control *ctl, int status);
+
+extern int parse_date(struct timespec *, char const *, struct timespec const *);
+
+#endif /* HWCLOCK_CLOCK_H */
diff --git a/sys-utils/ipcmk.1 b/sys-utils/ipcmk.1
new file mode 100644
index 0000000..a194af4
--- /dev/null
+++ b/sys-utils/ipcmk.1
@@ -0,0 +1,55 @@
+.\" Copyright 2008 Hayden A. James (hayden.james@gmail.com)
+.\" May be distributed under the GNU General Public License
+.TH IPCMK "1" "July 2014" "util-linux" "User Commands"
+.SH NAME
+ipcmk \- make various IPC resources
+.SH SYNOPSIS
+.B ipcmk
+[options]
+.SH DESCRIPTION
+.B ipcmk
+allows you to create System V inter-process communication (IPC) objects:
+shared memory segments, message queues,
+and semaphore arrays.
+.SH OPTIONS
+.TP
+Resources can be specified with these options:
+.TP
+.BR \-M , " \-\-shmem " \fIsize
+Create a shared memory segment of
+.I size
+bytes.
+The \fIsize\fR argument may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, etc. (the
+"iB" is optional, e.g., "K" has the same meaning as "KiB") or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, etc.
+.TP
+.BR \-Q , " \-\-queue"
+Create a message queue.
+.TP
+.BR \-S , " \-\-semaphore " \fInumber
+Create a semaphore array with
+.I number
+of elements.
+.PP
+Other options are:
+.TP
+.BR \-p , " \-\-mode " \fImode
+Access permissions for the resource. Default is 0644.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+.MT hayden.james@gmail.com
+Hayden A. James
+.ME
+.SH SEE ALSO
+.BR ipcrm (1),
+.BR ipcs (1),
+.BR sysvipc (7)
+.SH AVAILABILITY
+The ipcmk command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/ipcmk.c b/sys-utils/ipcmk.c
new file mode 100644
index 0000000..0a84be7
--- /dev/null
+++ b/sys-utils/ipcmk.c
@@ -0,0 +1,166 @@
+/*
+ * ipcmk.c - used to create ad-hoc IPC segments
+ *
+ * Copyright (C) 2008 Hayden A. James (hayden.james@gmail.com)
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "nls.h"
+#include "randutils.h"
+#include "strutils.h"
+#include "closestream.h"
+
+static int create_shm(size_t size, int permission)
+{
+ key_t key;
+
+ ul_random_get_bytes(&key, sizeof(key));
+ return shmget(key, size, permission | IPC_CREAT);
+}
+
+static int create_msg(int permission)
+{
+ key_t key;
+
+ ul_random_get_bytes(&key, sizeof(key));
+ return msgget(key, permission | IPC_CREAT);
+}
+
+static int create_sem(int nsems, int permission)
+{
+ key_t key;
+
+ ul_random_get_bytes(&key, sizeof(key));
+ return semget(key, nsems, permission | IPC_CREAT);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Create various IPC resources.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -M, --shmem <size> create shared memory segment of size <size>\n"), out);
+ fputs(_(" -S, --semaphore <number> create semaphore array with <number> elements\n"), out);
+ fputs(_(" -Q, --queue create message queue\n"), out);
+ fputs(_(" -p, --mode <mode> permission for the resource (default is 0644)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<size>")));
+
+ printf(USAGE_MAN_TAIL("ipcmk(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int permission = 0644;
+ int opt;
+ size_t size = 0;
+ int nsems = 0;
+ int ask_shm = 0, ask_msg = 0, ask_sem = 0;
+ static const struct option longopts[] = {
+ {"shmem", required_argument, NULL, 'M'},
+ {"semaphore", required_argument, NULL, 'S'},
+ {"queue", no_argument, NULL, 'Q'},
+ {"mode", required_argument, NULL, 'p'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while((opt = getopt_long(argc, argv, "hM:QS:p:Vh", longopts, NULL)) != -1) {
+ switch(opt) {
+ case 'M':
+ size = strtosize_or_err(optarg, _("failed to parse size"));
+ ask_shm = 1;
+ break;
+ case 'Q':
+ ask_msg = 1;
+ break;
+ case 'S':
+ nsems = strtos32_or_err(optarg, _("failed to parse elements"));
+ ask_sem = 1;
+ break;
+ case 'p':
+ permission = strtoul(optarg, NULL, 8);
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if(!ask_shm && !ask_msg && !ask_sem) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (ask_shm) {
+ int shmid;
+ if (-1 == (shmid = create_shm(size, permission)))
+ err(EXIT_FAILURE, _("create share memory failed"));
+ else
+ printf(_("Shared memory id: %d\n"), shmid);
+ }
+
+ if (ask_msg) {
+ int msgid;
+ if (-1 == (msgid = create_msg(permission)))
+ err(EXIT_FAILURE, _("create message queue failed"));
+ else
+ printf(_("Message queue id: %d\n"), msgid);
+ }
+
+ if (ask_sem) {
+ int semid;
+ if (-1 == (semid = create_sem(nsems, permission)))
+ err(EXIT_FAILURE, _("create semaphore failed"));
+ else
+ printf(_("Semaphore id: %d\n"), semid);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/ipcrm.1 b/sys-utils/ipcrm.1
new file mode 100644
index 0000000..fc85135
--- /dev/null
+++ b/sys-utils/ipcrm.1
@@ -0,0 +1,118 @@
+.\" Copyright 2002 Andre C. Mazzone (linuxdev@karagee.com)
+.\" May be distributed under the GNU General Public License
+.TH IPCRM "1" "July 2014" "util-linux" "User Commands"
+.SH NAME
+ipcrm \- remove certain IPC resources
+.SH SYNOPSIS
+.B ipcrm
+[options]
+.sp
+.B ipcrm
+.RB { shm | msg | sem }
+.IR id ...
+.SH DESCRIPTION
+.B ipcrm
+removes System V inter-process communication (IPC) objects
+and associated data structures from the system.
+In order to delete such objects, you must be superuser, or
+the creator or owner of the object.
+.PP
+System V IPC objects are of three types: shared memory,
+message queues, and semaphores.
+Deletion of a message queue or semaphore object is immediate
+(regardless of whether any process still holds an IPC
+identifier for the object).
+A shared memory object is only removed
+after all currently attached processes have detached
+.RB ( shmdt (2))
+the object from their virtual address space.
+.PP
+Two syntax styles are supported. The old Linux historical syntax specifies
+a three-letter keyword indicating which class of object is to be deleted,
+followed by one or more IPC identifiers for objects of this type.
+.PP
+The SUS-compliant syntax allows the specification of
+zero or more objects of all three types in a single command line,
+with objects specified either by key or by identifier (see below).
+Both keys and identifiers may be specified in decimal, hexadecimal
+(specified with an initial '0x' or '0X'), or octal (specified with
+an initial '0').
+.PP
+The details of the removes are described in
+.BR shmctl (2),
+.BR msgctl (2),
+and
+.BR semctl (2).
+The identifiers and keys can be found by using
+.BR ipcs (1).
+.SH OPTIONS
+.TP
+\fB\-a\fR, \fB\-\-all\fR [\fBshm\fR] [\fBmsg\fR] [\fBsem\fR]
+Remove all resources. When an option argument is provided, the removal is
+performed only for the specified resource types. \fIWarning!\fR Do not use
+.B \-a
+if you are unsure how the software using the resources might react to missing
+objects. Some programs create these resources at startup and may not have
+any code to deal with an unexpected disappearance.
+.TP
+.BR \-M , " \-\-shmem\-key " \fIshmkey
+Remove the shared memory segment created with
+.I shmkey
+after the last detach is performed.
+.TP
+.BR \-m , " \-\-shmem\-id " \fIshmid
+Remove the shared memory segment identified by
+.I shmid
+after the last detach is performed.
+.TP
+.BR \-Q , " \-\-queue\-key " \fImsgkey
+Remove the message queue created with
+.IR msgkey .
+.TP
+.BR \-q , " \-\-queue\-id " \fImsgid
+Remove the message queue identified by
+.IR msgid .
+.TP
+.BR \-S , " \-\-semaphore\-key " \fIsemkey
+Remove the semaphore created with
+.IR semkey .
+.TP
+.BR \-s , " \-\-semaphore\-id " \fIsemid
+Remove the semaphore identified by
+.IR semid .
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+In its first Linux implementation, \fBipcrm\fR used the deprecated syntax
+shown in the second line of the
+.BR SYNOPSIS .
+Functionality present in other *nix implementations of \fBipcrm\fR has since
+been added, namely the ability to delete resources by key (not just
+identifier), and to respect the same command-line syntax. For backward
+compatibility the previous syntax is still supported.
+.\" .SH AUTHORS
+.\" Andre C. Mazzone (linuxdev@karagee.com)
+.\" .br
+.\" Krishna Balasubramanian (balasub@cis.ohio-state.edu)
+.SH SEE ALSO
+.nh
+.BR ipcmk (1),
+.BR ipcs (1),
+.BR msgctl (2),
+.BR msgget (2),
+.BR semctl (2),
+.BR semget (2),
+.BR shmctl (2),
+.BR shmdt (2),
+.BR shmget (2),
+.BR ftok (3),
+.BR sysvipc (7)
+.SH AVAILABILITY
+The ipcrm command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/ipcrm.c b/sys-utils/ipcrm.c
new file mode 100644
index 0000000..5a27b7d
--- /dev/null
+++ b/sys-utils/ipcrm.c
@@ -0,0 +1,423 @@
+/*
+ * krishna balasubramanian 1993
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 1999-04-02 frank zago
+ * - can now remove several id's in the same call
+ *
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/types.h>
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "closestream.h"
+
+#ifndef HAVE_UNION_SEMUN
+/* according to X/OPEN we have to define it ourselves */
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short int *array;
+ struct seminfo *__buf;
+};
+#endif
+
+typedef enum type_id {
+ SHM,
+ SEM,
+ MSG,
+ ALL
+} type_id;
+
+static int verbose = 0;
+
+/* print the usage */
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %1$s [options]\n"
+ " %1$s shm|msg|sem <id>...\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Remove certain IPC resources.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -m, --shmem-id <id> remove shared memory segment by id\n"), out);
+ fputs(_(" -M, --shmem-key <key> remove shared memory segment by key\n"), out);
+ fputs(_(" -q, --queue-id <id> remove message queue by id\n"), out);
+ fputs(_(" -Q, --queue-key <key> remove message queue by key\n"), out);
+ fputs(_(" -s, --semaphore-id <id> remove semaphore by id\n"), out);
+ fputs(_(" -S, --semaphore-key <key> remove semaphore by key\n"), out);
+ fputs(_(" -a, --all[=shm|msg|sem] remove all (in the specified category)\n"), out);
+ fputs(_(" -v, --verbose explain what is being done\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(28));
+ printf(USAGE_MAN_TAIL("ipcrm(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static int remove_id(int type, int iskey, int id)
+{
+ int ret;
+ char *errmsg;
+ /* needed to delete semaphores */
+ union semun arg;
+ arg.val = 0;
+
+ /* do the removal */
+ switch (type) {
+ case SHM:
+ if (verbose)
+ printf(_("removing shared memory segment id `%d'\n"), id);
+ ret = shmctl(id, IPC_RMID, NULL);
+ break;
+ case MSG:
+ if (verbose)
+ printf(_("removing message queue id `%d'\n"), id);
+ ret = msgctl(id, IPC_RMID, NULL);
+ break;
+ case SEM:
+ if (verbose)
+ printf(_("removing semaphore id `%d'\n"), id);
+ ret = semctl(id, 0, IPC_RMID, arg);
+ break;
+ default:
+ errx(EXIT_FAILURE, "impossible occurred");
+ }
+
+ /* how did the removal go? */
+ if (ret < 0) {
+ switch (errno) {
+ case EACCES:
+ case EPERM:
+ errmsg = iskey ? _("permission denied for key") : _("permission denied for id");
+ break;
+ case EINVAL:
+ errmsg = iskey ? _("invalid key") : _("invalid id");
+ break;
+ case EIDRM:
+ errmsg = iskey ? _("already removed key") : _("already removed id");
+ break;
+ default:
+ err(EXIT_FAILURE, "%s", iskey ? _("key failed") : _("id failed"));
+ }
+ warnx("%s (%d)", errmsg, id);
+ return 1;
+ }
+ return 0;
+}
+
+static int remove_arg_list(type_id type, int argc, char **argv)
+{
+ int id;
+ char *end;
+ int nb_errors = 0;
+
+ do {
+ id = strtoul(argv[0], &end, 10);
+ if (*end != 0) {
+ warnx(_("invalid id: %s"), argv[0]);
+ nb_errors++;
+ } else {
+ if (remove_id(type, 0, id))
+ nb_errors++;
+ }
+ argc--;
+ argv++;
+ } while (argc);
+ return (nb_errors);
+}
+
+static int deprecated_main(int argc, char **argv)
+{
+ type_id type;
+
+ if (!strcmp(argv[1], "shm"))
+ type = SHM;
+ else if (!strcmp(argv[1], "msg"))
+ type = MSG;
+ else if (!strcmp(argv[1], "sem"))
+ type = SEM;
+ else
+ return 0;
+
+ if (argc < 3) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (remove_arg_list(type, argc - 2, &argv[2]))
+ exit(EXIT_FAILURE);
+
+ printf(_("resource(s) deleted\n"));
+ return 1;
+}
+
+static unsigned long strtokey(const char *str, const char *errmesg)
+{
+ unsigned long num;
+ char *end = NULL;
+
+ if (str == NULL || *str == '\0')
+ goto err;
+ errno = 0;
+ /* keys are in hex or decimal */
+ num = strtoul(str, &end, 0);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+ err:
+ if (errno)
+ err(EXIT_FAILURE, "%s: '%s'", errmesg, str);
+ else
+ errx(EXIT_FAILURE, "%s: '%s'", errmesg, str);
+ return 0;
+}
+
+static int key_to_id(type_id type, char *s)
+{
+ int id;
+ /* keys are in hex or decimal */
+ key_t key = strtokey(s, "failed to parse argument");
+ if (key == IPC_PRIVATE) {
+ warnx(_("illegal key (%s)"), s);
+ return -1;
+ }
+ switch (type) {
+ case SHM:
+ id = shmget(key, 0, 0);
+ break;
+ case MSG:
+ id = msgget(key, 0);
+ break;
+ case SEM:
+ id = semget(key, 0, 0);
+ break;
+ case ALL:
+ abort();
+ default:
+ errx(EXIT_FAILURE, "impossible occurred");
+ }
+ if (id < 0) {
+ char *errmsg;
+ switch (errno) {
+ case EACCES:
+ errmsg = _("permission denied for key");
+ break;
+ case EIDRM:
+ errmsg = _("already removed key");
+ break;
+ case ENOENT:
+ errmsg = _("invalid key");
+ break;
+ default:
+ err(EXIT_FAILURE, _("key failed"));
+ }
+ warnx("%s (%s)", errmsg, s);
+ }
+ return id;
+}
+
+static int remove_all(type_id type)
+{
+ int ret = 0;
+ int id, rm_me, maxid;
+
+ struct shmid_ds shmseg;
+
+ struct semid_ds semary;
+ struct seminfo seminfo;
+ union semun arg;
+
+ struct msqid_ds msgque;
+ struct msginfo msginfo;
+
+ if (type == SHM || type == ALL) {
+ maxid = shmctl(0, SHM_INFO, &shmseg);
+ if (maxid < 0)
+ errx(EXIT_FAILURE,
+ _("kernel not configured for shared memory"));
+ for (id = 0; id <= maxid; id++) {
+ rm_me = shmctl(id, SHM_STAT, &shmseg);
+ if (rm_me < 0)
+ continue;
+ ret |= remove_id(SHM, 0, rm_me);
+ }
+ }
+ if (type == SEM || type == ALL) {
+ arg.array = (ushort *) (void *)&seminfo;
+ maxid = semctl(0, 0, SEM_INFO, arg);
+ if (maxid < 0)
+ errx(EXIT_FAILURE,
+ _("kernel not configured for semaphores"));
+ for (id = 0; id <= maxid; id++) {
+ arg.buf = (struct semid_ds *)&semary;
+ rm_me = semctl(id, 0, SEM_STAT, arg);
+ if (rm_me < 0)
+ continue;
+ ret |= remove_id(SEM, 0, rm_me);
+ }
+ }
+/* kFreeBSD hackery -- ah 20140723 */
+#ifndef MSG_STAT
+#define MSG_STAT 11
+#endif
+#ifndef MSG_INFO
+#define MSG_INFO 12
+#endif
+ if (type == MSG || type == ALL) {
+ maxid =
+ msgctl(0, MSG_INFO, (struct msqid_ds *)(void *)&msginfo);
+ if (maxid < 0)
+ errx(EXIT_FAILURE,
+ _("kernel not configured for message queues"));
+ for (id = 0; id <= maxid; id++) {
+ rm_me = msgctl(id, MSG_STAT, &msgque);
+ if (rm_me < 0)
+ continue;
+ ret |= remove_id(MSG, 0, rm_me);
+ }
+ }
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ int ret = 0;
+ int id = -1;
+ int iskey;
+ int rm_all = 0;
+ type_id what_all = ALL;
+
+ static const struct option longopts[] = {
+ {"shmem-id", required_argument, NULL, 'm'},
+ {"shmem-key", required_argument, NULL, 'M'},
+ {"queue-id", required_argument, NULL, 'q'},
+ {"queue-key", required_argument, NULL, 'Q'},
+ {"semaphore-id", required_argument, NULL, 's'},
+ {"semaphore-key", required_argument, NULL, 'S'},
+ {"all", optional_argument, NULL, 'a'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ /* if the command is executed without parameters, do nothing */
+ if (argc == 1)
+ return 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ /* check to see if the command is being invoked in the old way if so
+ * then remove argument list */
+ if (deprecated_main(argc, argv))
+ return EXIT_SUCCESS;
+
+ /* process new syntax to conform with SYSV ipcrm */
+ while((c = getopt_long(argc, argv, "q:m:s:Q:M:S:a::vhV", longopts, NULL)) != -1) {
+ iskey = 0;
+ switch (c) {
+ case 'M':
+ iskey = 1;
+ id = key_to_id(SHM, optarg);
+ if (id < 0) {
+ ret++;
+ break;
+ }
+ /* fallthrough */
+ case 'm':
+ if (!iskey)
+ id = strtos32_or_err(optarg, _("failed to parse argument"));
+ if (remove_id(SHM, iskey, id))
+ ret++;
+ break;
+ case 'Q':
+ iskey = 1;
+ id = key_to_id(MSG, optarg);
+ if (id < 0) {
+ ret++;
+ break;
+ }
+ /* fallthrough */
+ case 'q':
+ if (!iskey)
+ id = strtos32_or_err(optarg, _("failed to parse argument"));
+ if (remove_id(MSG, iskey, id))
+ ret++;
+ break;
+ case 'S':
+ iskey = 1;
+ id = key_to_id(SEM, optarg);
+ if (id < 0) {
+ ret++;
+ break;
+ }
+ /* fallthrough */
+ case 's':
+ if (!iskey)
+ id = strtos32_or_err(optarg, _("failed to parse argument"));
+ if (remove_id(SEM, iskey, id))
+ ret++;
+ break;
+ case 'a':
+ rm_all = 1;
+ if (optarg) {
+ if (!strcmp(optarg, "shm"))
+ what_all = SHM;
+ else if (!strcmp(optarg, "msg"))
+ what_all = MSG;
+ else if (!strcmp(optarg, "sem"))
+ what_all = SEM;
+ else
+ errx(EXIT_FAILURE,
+ _("unknown argument: %s"), optarg);
+ } else {
+ what_all = ALL;
+ }
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (rm_all && remove_all(what_all))
+ ret++;
+
+ /* print usage if we still have some arguments left over */
+ if (optind < argc) {
+ warnx(_("unknown argument: %s"), argv[optind]);
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/sys-utils/ipcs.1 b/sys-utils/ipcs.1
new file mode 100644
index 0000000..4d13884
--- /dev/null
+++ b/sys-utils/ipcs.1
@@ -0,0 +1,135 @@
+.\" Copyright 1993 Rickard E. Faith (faith@cs.unc.edu)
+.\" May be distributed under the GNU General Public License
+.TH IPCS "1" "July 2014" "util-linux" "User Commands"
+.SH NAME
+ipcs \- show information on IPC facilities
+.SH SYNOPSIS
+.B ipcs
+[options]
+.SH DESCRIPTION
+.B ipcs
+shows information on System V inter-process communication facilities.
+By default it shows information about all three resources:
+shared memory segments, message queues, and semaphore arrays.
+.SH OPTIONS
+.TP
+\fB\-i\fR, \fB\-\-id\fR \fIid\fR
+Show full details on just the one resource element identified by
+.IR id .
+This option needs to be combined with one of the three resource options:
+.BR \-m ,
+.BR \-q " or"
+.BR \-s .
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SS "Resource options"
+.TP
+\fB\-m\fR, \fB\-\-shmems\fR
+Write information about active shared memory segments.
+.TP
+\fB\-q\fR, \fB\-\-queues\fR
+Write information about active message queues.
+.TP
+\fB\-s\fR, \fB\-\-semaphores\fR
+Write information about active semaphore sets.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Write information about all three resources (default).
+.SS "Output formats"
+Of these options only one takes effect: the last one specified.
+.TP
+\fB\-c\fR, \fB\-\-creator\fR
+Show creator and owner.
+.TP
+\fB\-l\fR, \fB\-\-limits\fR
+Show resource limits.
+.TP
+\fB\-p\fR, \fB\-\-pid\fR
+Show PIDs of creator and last operator.
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+Write time information. The time of the last control operation that changed
+the access permissions for all facilities, the time of the last
+.BR msgsnd (2)
+and
+.BR msgrcv (2)
+operations on message queues, the time of the last
+.BR shmat (2)
+and
+.BR shmdt (2)
+operations on shared memory, and the time of the last
+.BR semop (2)
+operation on semaphores.
+.TP
+\fB\-u\fR, \fB\-\-summary\fR
+Show status summary.
+.SS "Representation"
+These affect only the \fB\-l\fR (\fB\-\-limits\fR) option.
+.TP
+\fB\-b\fR, \fB\-\-bytes\fR
+Print sizes in bytes.
+.TP
+.B \-\-human
+Print sizes in human-readable format.
+.SH CONFORMING TO
+The Linux ipcs utility is not fully compatible to the POSIX ipcs utility.
+The Linux version does not support the POSIX
+.BR \-a ,
+.B \-b
+and
+.B \-o
+options, but does support the
+.B \-l
+and
+.B \-u
+options not defined by POSIX. A portable application shall not use the
+.BR \-a ,
+.BR \-b ,
+.BR \-o ,
+.BR \-l ,
+and
+.B \-u
+options.
+.SH NOTES
+The current implementation of
+.B ipcs
+obtains information about available IPC resources by parsing the files in
+.IR /proc/sysvipc .
+Before util-linux version v2.23, an alternate mechanism was used: the
+.B IPC_STAT
+command of
+.BR msgctl (2),
+.BR semctl (2),
+and
+.BR shmctl (2).
+This mechanism is also used in later util-linux versions in the case where
+.I /proc
+is unavailable.
+A limitation of the
+.B IPC_STAT
+mechanism is that it can only be used to retrieve information about
+IPC resources for which the user has read permission.
+.SH AUTHORS
+.UR balasub@cis.ohio-state.edu
+Krishna Balasubramanian
+.UE
+.SH SEE ALSO
+.BR ipcmk (1),
+.BR ipcrm (1),
+.BR msgrcv (2),
+.BR msgsnd (2),
+.BR semget (2),
+.BR semop (2),
+.BR shmat (2),
+.BR shmdt (2),
+.BR shmget (2),
+.BR sysvipc (7)
+.SH AVAILABILITY
+The ipcs command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/ipcs.c b/sys-utils/ipcs.c
new file mode 100644
index 0000000..fc6fba4
--- /dev/null
+++ b/sys-utils/ipcs.c
@@ -0,0 +1,672 @@
+/* Original author unknown, may be "krishna balasub@cis.ohio-state.edu" */
+/*
+ * Modified Sat Oct 9 10:55:28 1993 for 0.99.13
+ *
+ * Patches from Mike Jagdis (jaggy@purplet.demon.co.uk) applied Wed Feb 8
+ * 12:12:21 1995 by faith@cs.unc.edu to print numeric uids if no passwd file
+ * entry.
+ *
+ * Patch from arnolds@ifns.de (Heinz-Ado Arnolds) applied Mon Jul 1 19:30:41
+ * 1996 by janl@math.uio.no to add code missing in case PID: clauses.
+ *
+ * Patched to display the key field -- hy@picksys.com 12/18/96
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <errno.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+#include "timeutils.h"
+
+#include "ipcutils.h"
+
+enum output_formats {
+ NOTSPECIFIED,
+ LIMITS,
+ STATUS,
+ CREATOR,
+ TIME,
+ PID
+};
+enum {
+ OPT_HUMAN = CHAR_MAX + 1
+};
+
+static void do_shm (char format, int unit);
+static void print_shm (int id, int unit);
+static void do_sem (char format);
+static void print_sem (int id);
+static void do_msg (char format, int unit);
+static void print_msg (int id, int unit);
+
+static inline char *ctime64(int64_t *t)
+{
+ static char buf[CTIME_BUFSIZ];
+
+ /* we read time as int64_t from /proc, so cast... */
+ ctime_r((time_t *)t, buf);
+ return buf;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %1$s [resource-option...] [output-option]\n"
+ " %1$s -m|-q|-s -i <id>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Show information on IPC facilities.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -i, --id <id> print details on resource identified by <id>\n"), out);
+ printf(USAGE_HELP_OPTIONS(16));
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Resource options:\n"), out);
+ fputs(_(" -m, --shmems shared memory segments\n"), out);
+ fputs(_(" -q, --queues message queues\n"), out);
+ fputs(_(" -s, --semaphores semaphores\n"), out);
+ fputs(_(" -a, --all all (default)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Output options:\n"), out);
+ fputs(_(" -t, --time show attach, detach and change times\n"), out);
+ fputs(_(" -p, --pid show PIDs of creator and last operator\n"), out);
+ fputs(_(" -c, --creator show creator and owner\n"), out);
+ fputs(_(" -l, --limits show resource limits\n"), out);
+ fputs(_(" -u, --summary show status summary\n"), out);
+ fputs(_(" --human show sizes in human-readable format\n"), out);
+ fputs(_(" -b, --bytes show sizes in bytes\n"), out);
+ printf(USAGE_MAN_TAIL("ipcs(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main (int argc, char **argv)
+{
+ int opt, msg = 0, shm = 0, sem = 0, id = 0, specific = 0;
+ char format = NOTSPECIFIED;
+ int unit = IPC_UNIT_DEFAULT;
+ static const struct option longopts[] = {
+ {"id", required_argument, NULL, 'i'},
+ {"queues", no_argument, NULL, 'q'},
+ {"shmems", no_argument, NULL, 'm'},
+ {"semaphores", no_argument, NULL, 's'},
+ {"all", no_argument, NULL, 'a'},
+ {"time", no_argument, NULL, 't'},
+ {"pid", no_argument, NULL, 'p'},
+ {"creator", no_argument, NULL, 'c'},
+ {"limits", no_argument, NULL, 'l'},
+ {"summary", no_argument, NULL, 'u'},
+ {"human", no_argument, NULL, OPT_HUMAN},
+ {"bytes", no_argument, NULL, 'b'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ char options[] = "i:qmsatpclubVh";
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((opt = getopt_long(argc, argv, options, longopts, NULL)) != -1) {
+ switch (opt) {
+ case 'i':
+ id = atoi (optarg);
+ specific = 1;
+ break;
+ case 'a':
+ msg = shm = sem = 1;
+ break;
+ case 'q':
+ msg = 1;
+ break;
+ case 'm':
+ shm = 1;
+ break;
+ case 's':
+ sem = 1;
+ break;
+ case 't':
+ format = TIME;
+ break;
+ case 'c':
+ format = CREATOR;
+ break;
+ case 'p':
+ format = PID;
+ break;
+ case 'l':
+ format = LIMITS;
+ break;
+ case 'u':
+ format = STATUS;
+ break;
+ case OPT_HUMAN:
+ unit = IPC_UNIT_HUMAN;
+ break;
+ case 'b':
+ unit = IPC_UNIT_BYTES;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (specific && (msg + shm + sem != 1))
+ errx (EXIT_FAILURE,
+ _("when using an ID, a single resource must be specified"));
+ if (specific) {
+ if (msg)
+ print_msg (id, unit);
+ if (shm)
+ print_shm (id, unit);
+ if (sem)
+ print_sem (id);
+ } else {
+ if (!msg && !shm && !sem)
+ msg = shm = sem = 1;
+ printf ("\n");
+ if (msg) {
+ do_msg (format, unit);
+ printf ("\n");
+ }
+ if (shm) {
+ do_shm (format, unit);
+ printf ("\n");
+ }
+ if (sem) {
+ do_sem (format);
+ printf ("\n");
+ }
+ }
+ return EXIT_SUCCESS;
+}
+
+static void do_shm (char format, int unit)
+{
+ struct passwd *pw;
+ struct shm_data *shmds, *shmdsp;
+
+ switch (format) {
+ case LIMITS:
+ {
+ struct ipc_limits lim;
+ uint64_t tmp, pgsz = getpagesize();
+
+ if (ipc_shm_get_limits(&lim)) {
+ printf (_("unable to fetch shared memory limits\n"));
+ return;
+ }
+ printf (_("------ Shared Memory Limits --------\n"));
+ printf (_("max number of segments = %ju\n"), lim.shmmni);
+ ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_KB : unit,
+ _("max seg size"), lim.shmmax, "\n", 0);
+
+ tmp = (uint64_t) lim.shmall * pgsz;
+ /* overflow handling, at least we don't print ridiculous small values */
+ if (lim.shmall != 0 && tmp / lim.shmall != pgsz) {
+ tmp = UINT64_MAX - (UINT64_MAX % pgsz);
+ }
+ ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_KB : unit,
+ _("max total shared memory"), tmp, "\n", 0);
+ ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_BYTES : unit,
+ _("min seg size"), lim.shmmin, "\n", 0);
+ return;
+ }
+ case STATUS:
+ {
+ int maxid;
+ struct shmid_ds shmbuf;
+ struct shm_info *shm_info;
+
+ maxid = shmctl (0, SHM_INFO, &shmbuf);
+ shm_info = (struct shm_info *) &shmbuf;
+ if (maxid < 0) {
+ printf (_("kernel not configured for shared memory\n"));
+ return;
+ }
+
+ printf (_("------ Shared Memory Status --------\n"));
+ /*
+ * TRANSLATORS: This output format is maintained for backward
+ * compatibility as ipcs is used in scripts. For consistency
+ * with the rest, the translated form can follow this model:
+ *
+ * "segments allocated = %d\n"
+ * "pages allocated = %ld\n"
+ * "pages resident = %ld\n"
+ * "pages swapped = %ld\n"
+ * "swap performance = %ld attempts, %ld successes\n"
+ */
+ printf (_("segments allocated %d\n"
+ "pages allocated %ld\n"
+ "pages resident %ld\n"
+ "pages swapped %ld\n"
+ "Swap performance: %ld attempts\t %ld successes\n"),
+ shm_info->used_ids,
+ shm_info->shm_tot,
+ shm_info->shm_rss,
+ shm_info->shm_swp,
+ shm_info->swap_attempts, shm_info->swap_successes);
+ return;
+ }
+
+ /*
+ * Headers only
+ */
+ case CREATOR:
+ printf (_("------ Shared Memory Segment Creators/Owners --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-10s %-10s\n",
+ _("shmid"),_("perms"),_("cuid"),_("cgid"),_("uid"),_("gid"));
+ break;
+
+ case TIME:
+ printf (_("------ Shared Memory Attach/Detach/Change Times --------\n"));
+ printf ("%-10s %-10s %-20s %-20s %-20s\n",
+ _("shmid"),_("owner"),_("attached"),_("detached"),
+ _("changed"));
+ break;
+
+ case PID:
+ printf (_("------ Shared Memory Creator/Last-op PIDs --------\n"));
+ printf ("%-10s %-10s %-10s %-10s\n",
+ _("shmid"),_("owner"),_("cpid"),_("lpid"));
+ break;
+
+ default:
+ printf (_("------ Shared Memory Segments --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-10s %-10s %-12s\n",
+ _("key"),_("shmid"),_("owner"),_("perms"),
+ unit == IPC_UNIT_HUMAN ? _("size") : _("bytes"),
+ _("nattch"),_("status"));
+ break;
+ }
+
+ /*
+ * Print data
+ */
+ if (ipc_shm_get_info(-1, &shmds) < 1)
+ return;
+
+ for (shmdsp = shmds; shmdsp->next != NULL; shmdsp = shmdsp->next) {
+ if (format == CREATOR) {
+ ipc_print_perms(stdout, &shmdsp->shm_perm);
+ continue;
+ }
+ pw = getpwuid(shmdsp->shm_perm.uid);
+ switch (format) {
+ case TIME:
+ if (pw)
+ printf ("%-10d %-10.10s", shmdsp->shm_perm.id, pw->pw_name);
+ else
+ printf ("%-10d %-10u", shmdsp->shm_perm.id, shmdsp->shm_perm.uid);
+ /* ctime uses static buffer: use separate calls */
+ printf(" %-20.16s", shmdsp->shm_atim
+ ? ctime64(&shmdsp->shm_atim) + 4 : _("Not set"));
+ printf(" %-20.16s", shmdsp->shm_dtim
+ ? ctime64(&shmdsp->shm_dtim) + 4 : _("Not set"));
+ printf(" %-20.16s\n", shmdsp->shm_ctim
+ ? ctime64(&shmdsp->shm_ctim) + 4 : _("Not set"));
+ break;
+ case PID:
+ if (pw)
+ printf ("%-10d %-10.10s", shmdsp->shm_perm.id, pw->pw_name);
+ else
+ printf ("%-10d %-10u", shmdsp->shm_perm.id, shmdsp->shm_perm.uid);
+ printf (" %-10u %-10u\n",
+ shmdsp->shm_cprid, shmdsp->shm_lprid);
+ break;
+
+ default:
+ printf("0x%08x ", shmdsp->shm_perm.key);
+ if (pw)
+ printf ("%-10d %-10.10s", shmdsp->shm_perm.id, pw->pw_name);
+ else
+ printf ("%-10d %-10u", shmdsp->shm_perm.id, shmdsp->shm_perm.uid);
+ printf (" %-10o ", shmdsp->shm_perm.mode & 0777);
+
+ if (unit == IPC_UNIT_HUMAN)
+ ipc_print_size(unit, NULL, shmdsp->shm_segsz, " ", 6);
+ else
+ ipc_print_size(unit, NULL, shmdsp->shm_segsz, NULL, -10);
+
+ printf (" %-10ju %-6s %-6s\n",
+ shmdsp->shm_nattch,
+ shmdsp->shm_perm.mode & SHM_DEST ? _("dest") : " ",
+ shmdsp->shm_perm.mode & SHM_LOCKED ? _("locked") : " ");
+ break;
+ }
+ }
+
+ ipc_shm_free_info(shmds);
+}
+
+static void do_sem (char format)
+{
+ struct passwd *pw;
+ struct sem_data *semds, *semdsp;
+
+ switch (format) {
+ case LIMITS:
+ {
+ struct ipc_limits lim;
+
+ if (ipc_sem_get_limits(&lim)) {
+ printf (_("unable to fetch semaphore limits\n"));
+ return;
+ }
+ printf (_("------ Semaphore Limits --------\n"));
+ printf (_("max number of arrays = %d\n"), lim.semmni);
+ printf (_("max semaphores per array = %d\n"), lim.semmsl);
+ printf (_("max semaphores system wide = %d\n"), lim.semmns);
+ printf (_("max ops per semop call = %d\n"), lim.semopm);
+ printf (_("semaphore max value = %u\n"), lim.semvmx);
+ return;
+ }
+ case STATUS:
+ {
+ struct seminfo seminfo;
+ union semun arg;
+ arg.array = (ushort *) (void *) &seminfo;
+ if (semctl (0, 0, SEM_INFO, arg) < 0) {
+ printf (_("kernel not configured for semaphores\n"));
+ return;
+ }
+ printf (_("------ Semaphore Status --------\n"));
+ printf (_("used arrays = %d\n"), seminfo.semusz);
+ printf (_("allocated semaphores = %d\n"), seminfo.semaem);
+ return;
+ }
+
+ case CREATOR:
+ printf (_("------ Semaphore Arrays Creators/Owners --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-10s %-10s\n",
+ _("semid"),_("perms"),_("cuid"),_("cgid"),_("uid"),_("gid"));
+ break;
+
+ case TIME:
+ printf (_("------ Semaphore Operation/Change Times --------\n"));
+ printf ("%-8s %-10s %-26.24s %-26.24s\n",
+ _("semid"),_("owner"),_("last-op"),_("last-changed"));
+ break;
+
+ case PID:
+ break;
+
+ default:
+ printf (_("------ Semaphore Arrays --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-10s\n",
+ _("key"),_("semid"),_("owner"),_("perms"),_("nsems"));
+ break;
+ }
+
+ /*
+ * Print data
+ */
+ if (ipc_sem_get_info(-1, &semds) < 1)
+ return;
+
+ for (semdsp = semds; semdsp->next != NULL; semdsp = semdsp->next) {
+ if (format == CREATOR) {
+ ipc_print_perms(stdout, &semdsp->sem_perm);
+ continue;
+ }
+ pw = getpwuid(semdsp->sem_perm.uid);
+ switch (format) {
+ case TIME:
+ if (pw)
+ printf ("%-8d %-10.10s", semdsp->sem_perm.id, pw->pw_name);
+ else
+ printf ("%-8d %-10u", semdsp->sem_perm.id, semdsp->sem_perm.uid);
+ printf (" %-26.24s", semdsp->sem_otime
+ ? ctime64(&semdsp->sem_otime) : _("Not set"));
+ printf (" %-26.24s\n", semdsp->sem_ctime
+ ? ctime64( &semdsp->sem_ctime) : _("Not set"));
+ break;
+ case PID:
+ break;
+
+ default:
+ printf("0x%08x ", semdsp->sem_perm.key);
+ if (pw)
+ printf ("%-10d %-10.10s", semdsp->sem_perm.id, pw->pw_name);
+ else
+ printf ("%-10d %-10u", semdsp->sem_perm.id, semdsp->sem_perm.uid);
+ printf (" %-10o %-10ju\n",
+ semdsp->sem_perm.mode & 0777,
+ semdsp->sem_nsems);
+ break;
+ }
+ }
+
+ ipc_sem_free_info(semds);
+}
+
+static void do_msg (char format, int unit)
+{
+ struct passwd *pw;
+ struct msg_data *msgds, *msgdsp;
+
+ switch (format) {
+ case LIMITS:
+ {
+ struct ipc_limits lim;
+
+ if (ipc_msg_get_limits(&lim)) {
+ printf (_("unable to fetch message limits\n"));
+ return;
+ }
+ printf (_("------ Messages Limits --------\n"));
+ printf (_("max queues system wide = %d\n"), lim.msgmni);
+ ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_BYTES : unit,
+ _("max size of message"), lim.msgmax, "\n", 0);
+ ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_BYTES : unit,
+ _("default max size of queue"), lim.msgmnb, "\n", 0);
+ return;
+ }
+ case STATUS:
+ {
+ struct msginfo msginfo;
+ if (msgctl (0, MSG_INFO, (struct msqid_ds *) (void *) &msginfo) < 0) {
+ printf (_("kernel not configured for message queues\n"));
+ return;
+ }
+ printf (_("------ Messages Status --------\n"));
+#ifndef __FreeBSD_kernel__
+ printf (_("allocated queues = %d\n"), msginfo.msgpool);
+ printf (_("used headers = %d\n"), msginfo.msgmap);
+#endif
+ ipc_print_size(unit, _("used space"), msginfo.msgtql,
+ unit == IPC_UNIT_DEFAULT ? _(" bytes\n") : "\n", 0);
+ return;
+ }
+ case CREATOR:
+ printf (_("------ Message Queues Creators/Owners --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-10s %-10s\n",
+ _("msqid"),_("perms"),_("cuid"),_("cgid"),_("uid"),_("gid"));
+ break;
+
+ case TIME:
+ printf (_("------ Message Queues Send/Recv/Change Times --------\n"));
+ printf ("%-8s %-10s %-20s %-20s %-20s\n",
+ _("msqid"),_("owner"),_("send"),_("recv"),_("change"));
+ break;
+
+ case PID:
+ printf (_("------ Message Queues PIDs --------\n"));
+ printf ("%-10s %-10s %-10s %-10s\n",
+ _("msqid"),_("owner"),_("lspid"),_("lrpid"));
+ break;
+
+ default:
+ printf (_("------ Message Queues --------\n"));
+ printf ("%-10s %-10s %-10s %-10s %-12s %-12s\n",
+ _("key"), _("msqid"), _("owner"), _("perms"),
+ unit == IPC_UNIT_HUMAN ? _("size") : _("used-bytes"),
+ _("messages"));
+ break;
+ }
+
+ /*
+ * Print data
+ */
+ if (ipc_msg_get_info(-1, &msgds) < 1)
+ return;
+
+ for (msgdsp = msgds; msgdsp->next != NULL; msgdsp = msgdsp->next) {
+ if (format == CREATOR) {
+ ipc_print_perms(stdout, &msgdsp->msg_perm);
+ continue;
+ }
+ pw = getpwuid(msgdsp->msg_perm.uid);
+ switch (format) {
+ case TIME:
+ if (pw)
+ printf ("%-8d %-10.10s", msgdsp->msg_perm.id, pw->pw_name);
+ else
+ printf ("%-8d %-10u", msgdsp->msg_perm.id, msgdsp->msg_perm.uid);
+ printf (" %-20.16s", msgdsp->q_stime
+ ? ctime64(&msgdsp->q_stime) + 4 : _("Not set"));
+ printf (" %-20.16s", msgdsp->q_rtime
+ ? ctime64(&msgdsp->q_rtime) + 4 : _("Not set"));
+ printf (" %-20.16s\n", msgdsp->q_ctime
+ ? ctime64(&msgdsp->q_ctime) + 4 : _("Not set"));
+ break;
+ case PID:
+ if (pw)
+ printf ("%-8d %-10.10s", msgdsp->msg_perm.id, pw->pw_name);
+ else
+ printf ("%-8d %-10u", msgdsp->msg_perm.id, msgdsp->msg_perm.uid);
+ printf (" %5d %5d\n",
+ msgdsp->q_lspid, msgdsp->q_lrpid);
+ break;
+
+ default:
+ printf( "0x%08x ",msgdsp->msg_perm.key );
+ if (pw)
+ printf ("%-10d %-10.10s", msgdsp->msg_perm.id, pw->pw_name);
+ else
+ printf ("%-10d %-10u", msgdsp->msg_perm.id, msgdsp->msg_perm.uid);
+ printf (" %-10o ", msgdsp->msg_perm.mode & 0777);
+
+ if (unit == IPC_UNIT_HUMAN)
+ ipc_print_size(unit, NULL, msgdsp->q_cbytes, " ", 6);
+ else
+ ipc_print_size(unit, NULL, msgdsp->q_cbytes, NULL, -12);
+
+ printf (" %-12ju\n", msgdsp->q_qnum);
+ break;
+ }
+ }
+
+ ipc_msg_free_info(msgds);
+}
+
+static void print_shm(int shmid, int unit)
+{
+ struct shm_data *shmdata;
+
+ if (ipc_shm_get_info(shmid, &shmdata) < 1) {
+ warnx(_("id %d not found"), shmid);
+ return;
+ }
+
+ printf(_("\nShared memory Segment shmid=%d\n"), shmid);
+ printf(_("uid=%u\tgid=%u\tcuid=%u\tcgid=%u\n"),
+ shmdata->shm_perm.uid, shmdata->shm_perm.gid,
+ shmdata->shm_perm.cuid, shmdata->shm_perm.cgid);
+ printf(_("mode=%#o\taccess_perms=%#o\n"), shmdata->shm_perm.mode,
+ shmdata->shm_perm.mode & 0777);
+ ipc_print_size(unit, unit == IPC_UNIT_HUMAN ? _("size=") : _("bytes="),
+ shmdata->shm_segsz, "\t", 0);
+ printf(_("lpid=%u\tcpid=%u\tnattch=%jd\n"),
+ shmdata->shm_lprid, shmdata->shm_cprid,
+ shmdata->shm_nattch);
+ printf(_("att_time=%-26.24s\n"),
+ shmdata->shm_atim ? ctime64(&(shmdata->shm_atim)) : _("Not set"));
+ printf(_("det_time=%-26.24s\n"),
+ shmdata->shm_dtim ? ctime64(&shmdata->shm_dtim) : _("Not set"));
+ printf(_("change_time=%-26.24s\n"), ctime64(&shmdata->shm_ctim));
+ printf("\n");
+
+ ipc_shm_free_info(shmdata);
+}
+
+static void print_msg(int msgid, int unit)
+{
+ struct msg_data *msgdata;
+
+ if (ipc_msg_get_info(msgid, &msgdata) < 1) {
+ warnx(_("id %d not found"), msgid);
+ return;
+ }
+
+ printf(_("\nMessage Queue msqid=%d\n"), msgid);
+ printf(_("uid=%u\tgid=%u\tcuid=%u\tcgid=%u\tmode=%#o\n"),
+ msgdata->msg_perm.uid, msgdata->msg_perm.gid,
+ msgdata->msg_perm.cuid, msgdata->msg_perm.cgid,
+ msgdata->msg_perm.mode);
+ ipc_print_size(unit, unit == IPC_UNIT_HUMAN ? _("csize=") : _("cbytes="),
+ msgdata->q_cbytes, "\t", 0);
+ ipc_print_size(unit, unit == IPC_UNIT_HUMAN ? _("qsize=") : _("qbytes="),
+ msgdata->q_qbytes, "\t", 0);
+ printf("qnum=%jd\tlspid=%d\tlrpid=%d\n",
+ msgdata->q_qnum,
+ msgdata->q_lspid, msgdata->q_lrpid);
+ printf(_("send_time=%-26.24s\n"),
+ msgdata->q_stime ? ctime64(&msgdata->q_stime) : _("Not set"));
+ printf(_("rcv_time=%-26.24s\n"),
+ msgdata->q_rtime ? ctime64(&msgdata->q_rtime) : _("Not set"));
+ printf(_("change_time=%-26.24s\n"),
+ msgdata->q_ctime ? ctime64(&msgdata->q_ctime) : _("Not set"));
+ printf("\n");
+
+ ipc_msg_free_info(msgdata);
+}
+
+static void print_sem(int semid)
+{
+ struct sem_data *semdata;
+ size_t i;
+
+ if (ipc_sem_get_info(semid, &semdata) < 1) {
+ warnx(_("id %d not found"), semid);
+ return;
+ }
+
+ printf(_("\nSemaphore Array semid=%d\n"), semid);
+ printf(_("uid=%u\t gid=%u\t cuid=%u\t cgid=%u\n"),
+ semdata->sem_perm.uid, semdata->sem_perm.gid,
+ semdata->sem_perm.cuid, semdata->sem_perm.cgid);
+ printf(_("mode=%#o, access_perms=%#o\n"),
+ semdata->sem_perm.mode, semdata->sem_perm.mode & 0777);
+ printf(_("nsems = %ju\n"), semdata->sem_nsems);
+ printf(_("otime = %-26.24s\n"),
+ semdata->sem_otime ? ctime64(&semdata->sem_otime) : _("Not set"));
+ printf(_("ctime = %-26.24s\n"), ctime64(&semdata->sem_ctime));
+
+ printf("%-10s %-10s %-10s %-10s %-10s\n",
+ _("semnum"), _("value"), _("ncount"), _("zcount"), _("pid"));
+
+ for (i = 0; i < semdata->sem_nsems; i++) {
+ struct sem_elem *e = &semdata->elements[i];
+ printf("%-10zu %-10d %-10d %-10d %-10d\n",
+ i, e->semval, e->ncount, e->zcount, e->pid);
+ }
+ printf("\n");
+ ipc_sem_free_info(semdata);
+}
diff --git a/sys-utils/ipcutils.c b/sys-utils/ipcutils.c
new file mode 100644
index 0000000..674b612
--- /dev/null
+++ b/sys-utils/ipcutils.c
@@ -0,0 +1,536 @@
+#include <inttypes.h>
+
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "path.h"
+#include "pathnames.h"
+#include "ipcutils.h"
+#include "strutils.h"
+
+#ifndef SEMVMX
+# define SEMVMX 32767 /* <= 32767 semaphore maximum value */
+#endif
+#ifndef SHMMIN
+# define SHMMIN 1 /* min shared segment size in bytes */
+#endif
+
+
+int ipc_msg_get_limits(struct ipc_limits *lim)
+{
+ if (access(_PATH_PROC_IPC_MSGMNI, F_OK) == 0 &&
+ access(_PATH_PROC_IPC_MSGMNB, F_OK) == 0 &&
+ access(_PATH_PROC_IPC_MSGMAX, F_OK) == 0) {
+
+ if (ul_path_read_s32(NULL, &lim->msgmni, _PATH_PROC_IPC_MSGMNI) != 0)
+ return 1;
+ if (ul_path_read_s32(NULL, &lim->msgmnb, _PATH_PROC_IPC_MSGMNB) != 0)
+ return 1;
+ if (ul_path_read_u64(NULL, &lim->msgmax, _PATH_PROC_IPC_MSGMAX) != 0)
+ return 1;
+ } else {
+ struct msginfo msginfo;
+
+ if (msgctl(0, IPC_INFO, (struct msqid_ds *) &msginfo) < 0)
+ return 1;
+ lim->msgmni = msginfo.msgmni;
+ lim->msgmnb = msginfo.msgmnb;
+ lim->msgmax = msginfo.msgmax;
+ }
+
+ return 0;
+}
+
+int ipc_sem_get_limits(struct ipc_limits *lim)
+{
+ FILE *f;
+ int rc = 0;
+
+ lim->semvmx = SEMVMX;
+
+ f = fopen(_PATH_PROC_IPC_SEM, "r");
+ if (f) {
+ rc = fscanf(f, "%d\t%d\t%d\t%d",
+ &lim->semmsl, &lim->semmns, &lim->semopm, &lim->semmni);
+ fclose(f);
+ }
+
+ if (rc != 4) {
+ struct seminfo seminfo = { .semmni = 0 };
+ union semun arg = { .array = (ushort *) &seminfo };
+
+ if (semctl(0, 0, IPC_INFO, arg) < 0)
+ return 1;
+ lim->semmni = seminfo.semmni;
+ lim->semmsl = seminfo.semmsl;
+ lim->semmns = seminfo.semmns;
+ lim->semopm = seminfo.semopm;
+ }
+
+ return 0;
+}
+
+int ipc_shm_get_limits(struct ipc_limits *lim)
+{
+ lim->shmmin = SHMMIN;
+
+ if (access(_PATH_PROC_IPC_SHMALL, F_OK) == 0 &&
+ access(_PATH_PROC_IPC_SHMMAX, F_OK) == 0 &&
+ access(_PATH_PROC_IPC_SHMMNI, F_OK) == 0) {
+
+ ul_path_read_u64(NULL, &lim->shmall, _PATH_PROC_IPC_SHMALL);
+ ul_path_read_u64(NULL, &lim->shmmax, _PATH_PROC_IPC_SHMMAX);
+ ul_path_read_u64(NULL, &lim->shmmni, _PATH_PROC_IPC_SHMMNI);
+
+ } else {
+ struct shminfo *shminfo;
+ struct shmid_ds shmbuf;
+
+ if (shmctl(0, IPC_INFO, &shmbuf) < 0)
+ return 1;
+ shminfo = (struct shminfo *) &shmbuf;
+ lim->shmmni = shminfo->shmmni;
+ lim->shmall = shminfo->shmall;
+ lim->shmmax = shminfo->shmmax;
+ }
+
+ return 0;
+}
+
+int ipc_shm_get_info(int id, struct shm_data **shmds)
+{
+ FILE *f;
+ int i = 0, maxid;
+ char buf[BUFSIZ];
+ struct shm_data *p;
+ struct shmid_ds dummy;
+
+ p = *shmds = xcalloc(1, sizeof(struct shm_data));
+ p->next = NULL;
+
+ f = fopen(_PATH_PROC_SYSV_SHM, "r");
+ if (!f)
+ goto shm_fallback;
+
+ while (fgetc(f) != '\n'); /* skip header */
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ /* scan for the first 14-16 columns (e.g. Linux 2.6.32 has 14) */
+ p->shm_rss = 0xdead;
+ p->shm_swp = 0xdead;
+ if (sscanf(buf,
+ "%d %d %o %"SCNu64 " %u %u "
+ "%"SCNu64 " %u %u %u %u %"SCNi64 " %"SCNi64 " %"SCNi64
+ " %"SCNu64 " %"SCNu64 "\n",
+ &p->shm_perm.key,
+ &p->shm_perm.id,
+ &p->shm_perm.mode,
+ &p->shm_segsz,
+ &p->shm_cprid,
+ &p->shm_lprid,
+ &p->shm_nattch,
+ &p->shm_perm.uid,
+ &p->shm_perm.gid,
+ &p->shm_perm.cuid,
+ &p->shm_perm.cgid,
+ &p->shm_atim,
+ &p->shm_dtim,
+ &p->shm_ctim,
+ &p->shm_rss,
+ &p->shm_swp) < 14)
+ continue; /* invalid line, skipped */
+
+ if (id > -1) {
+ /* ID specified */
+ if (id == p->shm_perm.id) {
+ i = 1;
+ break;
+ }
+ continue;
+ }
+
+ p->next = xcalloc(1, sizeof(struct shm_data));
+ p = p->next;
+ p->next = NULL;
+ i++;
+ }
+
+ if (i == 0)
+ free(*shmds);
+ fclose(f);
+ return i;
+
+ /* Fallback; /proc or /sys file(s) missing. */
+shm_fallback:
+ maxid = shmctl(0, SHM_INFO, &dummy);
+
+ for (int j = 0; j <= maxid; j++) {
+ int shmid;
+ struct shmid_ds shmseg;
+ struct ipc_perm *ipcp = &shmseg.shm_perm;
+
+ shmid = shmctl(j, SHM_STAT, &shmseg);
+ if (shmid < 0 || (id > -1 && shmid != id)) {
+ continue;
+ }
+
+ i++;
+ p->shm_perm.key = ipcp->KEY;
+ p->shm_perm.id = shmid;
+ p->shm_perm.mode = ipcp->mode;
+ p->shm_segsz = shmseg.shm_segsz;
+ p->shm_cprid = shmseg.shm_cpid;
+ p->shm_lprid = shmseg.shm_lpid;
+ p->shm_nattch = shmseg.shm_nattch;
+ p->shm_perm.uid = ipcp->uid;
+ p->shm_perm.gid = ipcp->gid;
+ p->shm_perm.cuid = ipcp->cuid;
+ p->shm_perm.cgid = ipcp->cuid;
+ p->shm_atim = shmseg.shm_atime;
+ p->shm_dtim = shmseg.shm_dtime;
+ p->shm_ctim = shmseg.shm_ctime;
+ p->shm_rss = 0xdead;
+ p->shm_swp = 0xdead;
+
+ if (id < 0) {
+ p->next = xcalloc(1, sizeof(struct shm_data));
+ p = p->next;
+ p->next = NULL;
+ } else
+ break;
+ }
+
+ if (i == 0)
+ free(*shmds);
+ return i;
+}
+
+void ipc_shm_free_info(struct shm_data *shmds)
+{
+ while (shmds) {
+ struct shm_data *next = shmds->next;
+ free(shmds);
+ shmds = next;
+ }
+}
+
+static void get_sem_elements(struct sem_data *p)
+{
+ size_t i;
+
+ if (!p || !p->sem_nsems || p->sem_perm.id < 0)
+ return;
+
+ p->elements = xcalloc(p->sem_nsems, sizeof(struct sem_elem));
+
+ for (i = 0; i < p->sem_nsems; i++) {
+ struct sem_elem *e = &p->elements[i];
+ union semun arg = { .val = 0 };
+
+ e->semval = semctl(p->sem_perm.id, i, GETVAL, arg);
+ if (e->semval < 0)
+ err(EXIT_FAILURE, _("%s failed"), "semctl(GETVAL)");
+
+ e->ncount = semctl(p->sem_perm.id, i, GETNCNT, arg);
+ if (e->ncount < 0)
+ err(EXIT_FAILURE, _("%s failed"), "semctl(GETNCNT)");
+
+ e->zcount = semctl(p->sem_perm.id, i, GETZCNT, arg);
+ if (e->zcount < 0)
+ err(EXIT_FAILURE, _("%s failed"), "semctl(GETZCNT)");
+
+ e->pid = semctl(p->sem_perm.id, i, GETPID, arg);
+ if (e->pid < 0)
+ err(EXIT_FAILURE, _("%s failed"), "semctl(GETPID)");
+ }
+}
+
+int ipc_sem_get_info(int id, struct sem_data **semds)
+{
+ FILE *f;
+ int i = 0, maxid;
+ struct sem_data *p;
+ struct seminfo dummy;
+ union semun arg;
+
+ p = *semds = xcalloc(1, sizeof(struct sem_data));
+ p->next = NULL;
+
+ f = fopen(_PATH_PROC_SYSV_SEM, "r");
+ if (!f)
+ goto sem_fallback;
+
+ while (fgetc(f) != '\n') ; /* skip header */
+
+ while (feof(f) == 0) {
+ if (fscanf(f,
+ "%d %d %o %" SCNu64 " %u %u %u %u %"
+ SCNi64 " %" SCNi64 "\n",
+ &p->sem_perm.key,
+ &p->sem_perm.id,
+ &p->sem_perm.mode,
+ &p->sem_nsems,
+ &p->sem_perm.uid,
+ &p->sem_perm.gid,
+ &p->sem_perm.cuid,
+ &p->sem_perm.cgid,
+ &p->sem_otime,
+ &p->sem_ctime) != 10)
+ continue;
+
+ if (id > -1) {
+ /* ID specified */
+ if (id == p->sem_perm.id) {
+ get_sem_elements(p);
+ i = 1;
+ break;
+ }
+ continue;
+ }
+
+ p->next = xcalloc(1, sizeof(struct sem_data));
+ p = p->next;
+ p->next = NULL;
+ i++;
+ }
+
+ if (i == 0)
+ free(*semds);
+ fclose(f);
+ return i;
+
+ /* Fallback; /proc or /sys file(s) missing. */
+sem_fallback:
+ arg.array = (ushort *) (void *)&dummy;
+ maxid = semctl(0, 0, SEM_INFO, arg);
+
+ for (int j = 0; j <= maxid; j++) {
+ int semid;
+ struct semid_ds semseg;
+ struct ipc_perm *ipcp = &semseg.sem_perm;
+ arg.buf = (struct semid_ds *)&semseg;
+
+ semid = semctl(j, 0, SEM_STAT, arg);
+ if (semid < 0 || (id > -1 && semid != id)) {
+ continue;
+ }
+
+ i++;
+ p->sem_perm.key = ipcp->KEY;
+ p->sem_perm.id = semid;
+ p->sem_perm.mode = ipcp->mode;
+ p->sem_nsems = semseg.sem_nsems;
+ p->sem_perm.uid = ipcp->uid;
+ p->sem_perm.gid = ipcp->gid;
+ p->sem_perm.cuid = ipcp->cuid;
+ p->sem_perm.cgid = ipcp->cuid;
+ p->sem_otime = semseg.sem_otime;
+ p->sem_ctime = semseg.sem_ctime;
+
+ if (id < 0) {
+ p->next = xcalloc(1, sizeof(struct sem_data));
+ p = p->next;
+ p->next = NULL;
+ i++;
+ } else {
+ get_sem_elements(p);
+ break;
+ }
+ }
+
+ if (i == 0)
+ free(*semds);
+ return i;
+}
+
+void ipc_sem_free_info(struct sem_data *semds)
+{
+ while (semds) {
+ struct sem_data *next = semds->next;
+ free(semds->elements);
+ free(semds);
+ semds = next;
+ }
+}
+
+int ipc_msg_get_info(int id, struct msg_data **msgds)
+{
+ FILE *f;
+ int i = 0, maxid;
+ struct msg_data *p;
+ struct msqid_ds dummy;
+ struct msqid_ds msgseg;
+
+ p = *msgds = xcalloc(1, sizeof(struct msg_data));
+ p->next = NULL;
+
+ f = fopen(_PATH_PROC_SYSV_MSG, "r");
+ if (!f)
+ goto msg_fallback;
+
+ while (fgetc(f) != '\n') ; /* skip header */
+
+ while (feof(f) == 0) {
+ if (fscanf(f,
+ "%d %d %o %" SCNu64 " %" SCNu64
+ " %u %u %u %u %u %u %" SCNi64 " %" SCNi64 " %" SCNi64 "\n",
+ &p->msg_perm.key,
+ &p->msg_perm.id,
+ &p->msg_perm.mode,
+ &p->q_cbytes,
+ &p->q_qnum,
+ &p->q_lspid,
+ &p->q_lrpid,
+ &p->msg_perm.uid,
+ &p->msg_perm.gid,
+ &p->msg_perm.cuid,
+ &p->msg_perm.cgid,
+ &p->q_stime,
+ &p->q_rtime,
+ &p->q_ctime) != 14)
+ continue;
+
+ if (id > -1) {
+ /* ID specified */
+ if (id == p->msg_perm.id) {
+ if (msgctl(id, IPC_STAT, &msgseg) != -1)
+ p->q_qbytes = msgseg.msg_qbytes;
+ i = 1;
+ break;
+ }
+ continue;
+ }
+
+ p->next = xcalloc(1, sizeof(struct msg_data));
+ p = p->next;
+ p->next = NULL;
+ i++;
+ }
+
+ if (i == 0)
+ free(*msgds);
+ fclose(f);
+ return i;
+
+ /* Fallback; /proc or /sys file(s) missing. */
+msg_fallback:
+ maxid = msgctl(0, MSG_INFO, &dummy);
+
+ for (int j = 0; j <= maxid; j++) {
+ int msgid;
+ struct ipc_perm *ipcp = &msgseg.msg_perm;
+
+ msgid = msgctl(j, MSG_STAT, &msgseg);
+ if (msgid < 0 || (id > -1 && msgid != id)) {
+ continue;
+ }
+
+ i++;
+ p->msg_perm.key = ipcp->KEY;
+ p->msg_perm.id = msgid;
+ p->msg_perm.mode = ipcp->mode;
+ p->q_cbytes = msgseg.msg_cbytes;
+ p->q_qnum = msgseg.msg_qnum;
+ p->q_lspid = msgseg.msg_lspid;
+ p->q_lrpid = msgseg.msg_lrpid;
+ p->msg_perm.uid = ipcp->uid;
+ p->msg_perm.gid = ipcp->gid;
+ p->msg_perm.cuid = ipcp->cuid;
+ p->msg_perm.cgid = ipcp->cgid;
+ p->q_stime = msgseg.msg_stime;
+ p->q_rtime = msgseg.msg_rtime;
+ p->q_ctime = msgseg.msg_ctime;
+ p->q_qbytes = msgseg.msg_qbytes;
+
+ if (id < 0) {
+ p->next = xcalloc(1, sizeof(struct msg_data));
+ p = p->next;
+ p->next = NULL;
+ } else
+ break;
+ }
+
+ if (i == 0)
+ free(*msgds);
+ return i;
+}
+
+void ipc_msg_free_info(struct msg_data *msgds)
+{
+ while (msgds) {
+ struct msg_data *next = msgds->next;
+ free(msgds);
+ msgds = next;
+ }
+}
+
+void ipc_print_perms(FILE *f, struct ipc_stat *is)
+{
+ struct passwd *pw;
+ struct group *gr;
+
+ fprintf(f, "%-10d %-10o", is->id, is->mode & 0777);
+
+ if ((pw = getpwuid(is->cuid)))
+ fprintf(f, " %-10s", pw->pw_name);
+ else
+ fprintf(f, " %-10u", is->cuid);
+
+ if ((gr = getgrgid(is->cgid)))
+ fprintf(f, " %-10s", gr->gr_name);
+ else
+ fprintf(f, " %-10u", is->cgid);
+
+ if ((pw = getpwuid(is->uid)))
+ fprintf(f, " %-10s", pw->pw_name);
+ else
+ fprintf(f, " %-10u", is->uid);
+
+ if ((gr = getgrgid(is->gid)))
+ fprintf(f, " %-10s\n", gr->gr_name);
+ else
+ fprintf(f, " %-10u\n", is->gid);
+}
+
+void ipc_print_size(int unit, char *msg, uint64_t size, const char *end,
+ int width)
+{
+ char format[32];
+
+ if (!msg)
+ /* NULL */ ;
+ else if (msg[strlen(msg) - 1] == '=')
+ printf("%s", msg);
+ else if (unit == IPC_UNIT_BYTES)
+ printf(_("%s (bytes) = "), msg);
+ else if (unit == IPC_UNIT_KB)
+ printf(_("%s (kbytes) = "), msg);
+ else
+ printf("%s = ", msg);
+
+ switch (unit) {
+ case IPC_UNIT_DEFAULT:
+ case IPC_UNIT_BYTES:
+ sprintf(format, "%%%dju", width);
+ printf(format, size);
+ break;
+ case IPC_UNIT_KB:
+ sprintf(format, "%%%dju", width);
+ printf(format, size / 1024);
+ break;
+ case IPC_UNIT_HUMAN:
+ {
+ char *tmp;
+ sprintf(format, "%%%ds", width);
+ printf(format, (tmp = size_to_human_string(SIZE_SUFFIX_1LETTER, size)));
+ free(tmp);
+ break;
+ }
+ default:
+ /* impossible occurred */
+ abort();
+ }
+
+ if (end)
+ printf("%s", end);
+}
diff --git a/sys-utils/ipcutils.h b/sys-utils/ipcutils.h
new file mode 100644
index 0000000..db85f57
--- /dev/null
+++ b/sys-utils/ipcutils.h
@@ -0,0 +1,187 @@
+#ifndef UTIL_LINUX_IPCUTILS_H
+#define UTIL_LINUX_IPCUTILS_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdint.h>
+
+/*
+ * SHM_DEST and SHM_LOCKED are defined in kernel headers, but inside
+ * #ifdef __KERNEL__ ... #endif
+ */
+#ifndef SHM_DEST
+ /* shm_mode upper byte flags */
+# define SHM_DEST 01000 /* segment will be destroyed on last detach */
+# define SHM_LOCKED 02000 /* segment will not be swapped */
+#endif
+
+/* For older kernels the same holds for the defines below */
+#ifndef MSG_STAT
+# define MSG_STAT 11
+# define MSG_INFO 12
+#endif
+
+#ifndef SHM_STAT
+# define SHM_STAT 13
+# define SHM_INFO 14
+struct shm_info {
+ int used_ids;
+ unsigned long shm_tot; /* total allocated shm */
+ unsigned long shm_rss; /* total resident shm */
+ unsigned long shm_swp; /* total swapped shm */
+ unsigned long swap_attempts;
+ unsigned long swap_successes;
+};
+#endif
+
+#ifndef SEM_STAT
+# define SEM_STAT 18
+# define SEM_INFO 19
+#endif
+
+/* Some versions of libc only define IPC_INFO when __USE_GNU is defined. */
+#ifndef IPC_INFO
+# define IPC_INFO 3
+#endif
+
+/*
+ * * The last arg of semctl is a union semun, but where is it defined? X/OPEN
+ * * tells us to define it ourselves, but until recently Linux include files
+ * * would also define it.
+ * */
+#ifndef HAVE_UNION_SEMUN
+/* according to X/OPEN we have to define it ourselves */
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short int *array;
+ struct seminfo *__buf;
+};
+#endif
+
+/*
+ * X/OPEN (Jan 1987) does not define fields key, seq in struct ipc_perm;
+ * glibc-1.09 has no support for sysv ipc.
+ * glibc 2 uses __key, __seq
+ */
+#if defined (__GLIBC__) && __GLIBC__ >= 2
+# define KEY __key
+#else
+# define KEY key
+#endif
+
+/* Size printing in ipcs is using these. */
+enum {
+ IPC_UNIT_DEFAULT,
+ IPC_UNIT_BYTES,
+ IPC_UNIT_KB,
+ IPC_UNIT_HUMAN
+};
+
+struct ipc_limits {
+ uint64_t shmmni; /* max number of segments */
+ uint64_t shmmax; /* max segment size */
+ uint64_t shmall; /* max total shared memory */
+ uint64_t shmmin; /* min segment size */
+
+ int semmni; /* max number of arrays */
+ int semmsl; /* max semaphores per array */
+ int semmns; /* max semaphores system wide */
+ int semopm; /* max ops per semop call */
+ unsigned int semvmx; /* semaphore max value (constant) */
+
+ int msgmni; /* max queues system wide */
+ uint64_t msgmax; /* max size of message */
+ int msgmnb; /* default max size of queue */
+};
+
+extern int ipc_msg_get_limits(struct ipc_limits *lim);
+extern int ipc_sem_get_limits(struct ipc_limits *lim);
+extern int ipc_shm_get_limits(struct ipc_limits *lim);
+
+struct ipc_stat {
+ int id;
+ key_t key;
+ uid_t uid; /* current uid */
+ gid_t gid; /* current gid */
+ uid_t cuid; /* creator uid */
+ gid_t cgid; /* creator gid */
+ unsigned int mode;
+};
+
+extern void ipc_print_perms(FILE *f, struct ipc_stat *is);
+extern void ipc_print_size(int unit, char *msg, uint64_t size, const char *end, int width);
+
+/* See 'struct shmid_kernel' in kernel sources
+ */
+struct shm_data {
+ struct ipc_stat shm_perm;
+
+ uint64_t shm_nattch;
+ uint64_t shm_segsz;
+ int64_t shm_atim; /* __kernel_time_t is signed long */
+ int64_t shm_dtim;
+ int64_t shm_ctim;
+ pid_t shm_cprid;
+ pid_t shm_lprid;
+ uint64_t shm_rss;
+ uint64_t shm_swp;
+
+ struct shm_data *next;
+};
+
+extern int ipc_shm_get_info(int id, struct shm_data **shmds);
+extern void ipc_shm_free_info(struct shm_data *shmds);
+
+/* See 'struct sem_array' in kernel sources
+ */
+struct sem_elem {
+ int semval;
+ int ncount; /* processes waiting on increase semval */
+ int zcount; /* processes waiting on semval set to zero */
+ pid_t pid; /* process last executed semop(2) call */
+};
+struct sem_data {
+ struct ipc_stat sem_perm;
+
+ int64_t sem_ctime;
+ int64_t sem_otime;
+ uint64_t sem_nsems;
+
+ struct sem_elem *elements;
+ struct sem_data *next;
+};
+
+extern int ipc_sem_get_info(int id, struct sem_data **semds);
+extern void ipc_sem_free_info(struct sem_data *semds);
+
+/* See 'struct msg_queue' in kernel sources
+ */
+struct msg_data {
+ struct ipc_stat msg_perm;
+
+ int64_t q_stime;
+ int64_t q_rtime;
+ int64_t q_ctime;
+ uint64_t q_cbytes;
+ uint64_t q_qnum;
+ uint64_t q_qbytes;
+ pid_t q_lspid;
+ pid_t q_lrpid;
+
+ struct msg_data *next;
+};
+
+extern int ipc_msg_get_info(int id, struct msg_data **msgds);
+extern void ipc_msg_free_info(struct msg_data *msgds);
+
+#endif /* UTIL_LINUX_IPCUTILS_H */
diff --git a/sys-utils/irq-common.c b/sys-utils/irq-common.c
new file mode 100644
index 0000000..81e8b1d
--- /dev/null
+++ b/sys-utils/irq-common.c
@@ -0,0 +1,415 @@
+/*
+ * irq-common.c - functions to display kernel interrupt information.
+ *
+ * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com>
+ * Copyright (C) 2020 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "irq-common.h"
+
+#define IRQ_INFO_LEN 64
+
+struct colinfo {
+ const char *name;
+ double whint;
+ int flags;
+ const char *help;
+ int json_type;
+};
+
+static const struct colinfo infos[] = {
+ [COL_IRQ] = {"IRQ", 0.10, SCOLS_FL_RIGHT, N_("interrupts"), SCOLS_JSON_STRING},
+ [COL_TOTAL] = {"TOTAL", 0.10, SCOLS_FL_RIGHT, N_("total count"), SCOLS_JSON_NUMBER},
+ [COL_DELTA] = {"DELTA", 0.10, SCOLS_FL_RIGHT, N_("delta count"), SCOLS_JSON_NUMBER},
+ [COL_NAME] = {"NAME", 0.70, SCOLS_FL_TRUNC, N_("name"), SCOLS_JSON_STRING},
+};
+
+int irq_column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ assert(name);
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static inline int get_column_id(struct irq_output *out, size_t const num)
+{
+ assert(num < out->ncolumns);
+ assert(out->columns[num] < (int)ARRAY_SIZE(infos));
+
+ return out->columns[num];
+}
+
+static inline const struct colinfo *get_column_info(
+ struct irq_output *out, unsigned num)
+{
+ return &infos[get_column_id(out, num)];
+}
+
+void irq_print_columns(FILE *f, int nodelta)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ if (nodelta && i == COL_DELTA)
+ continue;
+ fprintf(f, " %-5s %s\n", infos[i].name, _(infos[i].help));
+ }
+}
+
+static struct libscols_table *new_scols_table(struct irq_output *out)
+{
+ size_t i;
+ struct libscols_table *table;
+
+ table = scols_new_table();
+ if (!table) {
+ warn(_("failed to initialize output table"));
+ return NULL;
+ }
+ scols_table_enable_json(table, out->json);
+ scols_table_enable_noheadings(table, out->no_headings);
+ scols_table_enable_export(table, out->pairs);
+
+ if (out->json)
+ scols_table_set_name(table, _("interrupts"));
+
+ for (i = 0; i < out->ncolumns; i++) {
+ const struct colinfo *col = get_column_info(out, i);
+ int flags = col->flags;
+ struct libscols_column *cl;
+
+ cl = scols_table_new_column(table, col->name, col->whint, flags);
+ if (cl == NULL) {
+ warnx(_("failed to initialize output column"));
+ goto err;
+ }
+ if (out->json)
+ scols_column_set_json_type(cl, col->json_type);
+ }
+
+ return table;
+ err:
+ scols_unref_table(table);
+ return NULL;
+}
+
+static void add_scols_line(struct irq_output *out,
+ struct irq_info *info,
+ struct libscols_table *table)
+{
+ size_t i;
+ struct libscols_line *line;
+
+ line = scols_table_new_line(table, NULL);
+ if (!line) {
+ warn(_("failed to add line to output"));
+ return;
+ }
+
+ for (i = 0; i < out->ncolumns; i++) {
+ char *str = NULL;
+
+ switch (get_column_id(out, i)) {
+ case COL_IRQ:
+ xasprintf(&str, "%s", info->irq);
+ break;
+ case COL_TOTAL:
+ xasprintf(&str, "%ld", info->total);
+ break;
+ case COL_DELTA:
+ xasprintf(&str, "%ld", info->delta);
+ break;
+ case COL_NAME:
+ xasprintf(&str, "%s", info->name);
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, i, str) != 0)
+ err_oom();
+ }
+}
+
+static char *remove_repeated_spaces(char *str)
+{
+ char *inp = str, *outp = str;
+ uint8_t prev_space = 0;
+
+ while (*inp) {
+ if (isspace(*inp)) {
+ if (!prev_space) {
+ *outp++ = ' ';
+ prev_space = 1;
+ }
+ } else {
+ *outp++ = *inp;
+ prev_space = 0;
+ }
+ ++inp;
+ }
+ *outp = '\0';
+ return str;
+}
+
+/*
+ * irqinfo - parse the system's interrupts
+ */
+static struct irq_stat *get_irqinfo(void)
+{
+ FILE *irqfile;
+ char *line = NULL, *tmp;
+ size_t len = 0;
+ struct irq_stat *stat;
+ struct irq_info *curr;
+
+ /* NAME + ':' + 11 bytes/cpu + IRQ_NAME_LEN */
+ stat = xcalloc(1, sizeof(*stat));
+
+ stat->irq_info = xmalloc(sizeof(*stat->irq_info) * IRQ_INFO_LEN);
+ stat->nr_irq_info = IRQ_INFO_LEN;
+
+ irqfile = fopen(_PATH_PROC_INTERRUPTS, "r");
+ if (!irqfile) {
+ warn(_("cannot open %s"), _PATH_PROC_INTERRUPTS);
+ goto free_stat;
+ }
+
+ /* read header firstly */
+ if (getline(&line, &len, irqfile) < 0) {
+ warn(_("cannot read %s"), _PATH_PROC_INTERRUPTS);
+ goto close_file;
+ }
+
+ tmp = line;
+ while ((tmp = strstr(tmp, "CPU")) != NULL) {
+ tmp += 3; /* skip this "CPU", find next */
+ stat->nr_active_cpu++;
+ }
+
+ /* parse each line of _PATH_PROC_INTERRUPTS */
+ while (getline(&line, &len, irqfile) >= 0) {
+ unsigned long count;
+ int index, length;
+
+ tmp = strchr(line, ':');
+ if (!tmp)
+ continue;
+
+ length = strlen(line);
+
+ curr = stat->irq_info + stat->nr_irq++;
+ memset(curr, 0, sizeof(*curr));
+ *tmp = '\0';
+ curr->irq = xstrdup(line);
+ ltrim_whitespace((unsigned char *)curr->irq);
+
+ tmp += 1;
+ for (index = 0; (index < stat->nr_active_cpu) && (tmp - line < length); index++) {
+ sscanf(tmp, " %10lu", &count);
+ curr->total += count;
+ stat->total_irq += count;
+ tmp += 11;
+ }
+
+ if (tmp - line < length) {
+ /* strip all space before desc */
+ while (isspace(*tmp))
+ tmp++;
+ tmp = remove_repeated_spaces(tmp);
+ rtrim_whitespace((unsigned char *)tmp);
+ curr->name = xstrdup(tmp);
+ } else /* no irq name string, we have to set '\0' here */
+ curr->name = xstrdup("");
+
+ if (stat->nr_irq == stat->nr_irq_info) {
+ stat->nr_irq_info *= 2;
+ stat->irq_info = xrealloc(stat->irq_info,
+ sizeof(*stat->irq_info) * stat->nr_irq_info);
+ }
+ }
+ fclose(irqfile);
+ free(line);
+ return stat;
+
+ close_file:
+ fclose(irqfile);
+ free_stat:
+ free(stat->irq_info);
+ free(stat);
+ free(line);
+ return NULL;
+}
+
+void free_irqstat(struct irq_stat *stat)
+{
+ size_t i;
+
+ if (!stat)
+ return;
+
+ for (i = 0; i < stat->nr_irq; i++) {
+ free(stat->irq_info[i].name);
+ free(stat->irq_info[i].irq);
+ }
+
+ free(stat->irq_info);
+ free(stat);
+}
+
+static inline int cmp_name(const struct irq_info *a,
+ const struct irq_info *b)
+{
+ return (strcmp(a->name, b->name) > 0) ? 1 : 0;
+}
+
+static inline int cmp_total(const struct irq_info *a,
+ const struct irq_info *b)
+{
+ return a->total < b->total;
+}
+
+static inline int cmp_delta(const struct irq_info *a,
+ const struct irq_info *b)
+{
+ return a->delta < b->delta;
+}
+
+static inline int cmp_interrupts(const struct irq_info *a,
+ const struct irq_info *b)
+{
+ return (strcmp(a->irq, b->irq) > 0) ? 1 : 0;
+}
+
+static void sort_result(struct irq_output *out,
+ struct irq_info *result,
+ size_t nmemb)
+{
+ irq_cmp_t *func = cmp_total; /* default */
+
+ if (out->sort_cmp_func)
+ func = out->sort_cmp_func;
+
+ qsort(result, nmemb, sizeof(*result),
+ (int (*)(const void *, const void *)) func);
+}
+
+void set_sort_func_by_name(struct irq_output *out, const char *name)
+{
+ if (strcasecmp(name, "IRQ") == 0)
+ out->sort_cmp_func = cmp_interrupts;
+ else if (strcasecmp(name, "TOTAL") == 0)
+ out->sort_cmp_func = cmp_total;
+ else if (strcasecmp(name, "DELTA") == 0)
+ out->sort_cmp_func = cmp_delta;
+ else if (strcasecmp(name, "NAME") == 0)
+ out->sort_cmp_func = cmp_name;
+ else
+ errx(EXIT_FAILURE, _("unsupported column name to sort output"));
+}
+
+void set_sort_func_by_key(struct irq_output *out, char c)
+{
+ switch (c) {
+ case 'i':
+ out->sort_cmp_func = cmp_interrupts;
+ break;
+ case 't':
+ out->sort_cmp_func = cmp_total;
+ break;
+ case 'd':
+ out->sort_cmp_func = cmp_delta;
+ break;
+ case 'n':
+ out->sort_cmp_func = cmp_name;
+ break;
+ }
+}
+
+struct libscols_table *get_scols_table(struct irq_output *out,
+ struct irq_stat *prev,
+ struct irq_stat **xstat)
+{
+ struct libscols_table *table;
+ struct irq_info *result;
+ struct irq_stat *stat;
+ size_t size;
+ size_t i;
+
+ /* the stats */
+ stat = get_irqinfo();
+ if (!stat)
+ return NULL;
+
+ size = sizeof(*stat->irq_info) * stat->nr_irq;
+ result = xmalloc(size);
+ memcpy(result, stat->irq_info, size);
+
+ if (prev) {
+ stat->delta_irq = 0;
+ for (i = 0; i < stat->nr_irq; i++) {
+ struct irq_info *cur = &result[i];
+ struct irq_info *pre = &prev->irq_info[i];
+
+ cur->delta = cur->total - pre->total;
+ stat->delta_irq += cur->delta;
+ }
+ }
+ sort_result(out, result, stat->nr_irq);
+
+ table = new_scols_table(out);
+ if (!table)
+ return NULL;
+
+ for (i = 0; i < stat->nr_irq; i++)
+ add_scols_line(out, &result[i], table);
+
+ free(result);
+
+ if (xstat)
+ *xstat = stat;
+ else
+ free_irqstat(stat);
+
+ return table;
+}
diff --git a/sys-utils/irq-common.h b/sys-utils/irq-common.h
new file mode 100644
index 0000000..9515daf
--- /dev/null
+++ b/sys-utils/irq-common.h
@@ -0,0 +1,61 @@
+#ifndef UTIL_LINUX_H_IRQ_COMMON
+#define UTIL_LINUX_H_IRQ_COMMON
+
+#include "c.h"
+#include "nls.h"
+
+/* supported columns */
+enum {
+ COL_IRQ = 0,
+ COL_TOTAL,
+ COL_DELTA,
+ COL_NAME,
+
+ __COL_COUNT
+};
+
+struct irq_info {
+ char *irq; /* short name of this irq */
+ char *name; /* descriptive name of this irq */
+ unsigned long total; /* total count since system start up */
+ unsigned long delta; /* delta count since previous update */
+};
+
+
+struct irq_stat {
+ unsigned int nr_irq; /* number of irq vector */
+ unsigned int nr_irq_info; /* number of irq info */
+ struct irq_info *irq_info; /* array of irq_info */
+ long nr_active_cpu; /* number of active cpu */
+ unsigned long total_irq; /* total irqs */
+ unsigned long delta_irq; /* delta irqs */
+};
+
+typedef int (irq_cmp_t)(const struct irq_info *, const struct irq_info *);
+
+/* output definition */
+struct irq_output {
+ int columns[__COL_COUNT * 2];
+ size_t ncolumns;
+
+ irq_cmp_t *sort_cmp_func;
+
+ unsigned int
+ json:1, /* JSON output */
+ pairs:1, /* export, NAME="value" aoutput */
+ no_headings:1; /* don't print header */
+};
+
+int irq_column_name_to_id(char const *const name, size_t const namesz);
+void free_irqstat(struct irq_stat *stat);
+
+void irq_print_columns(FILE *f, int nodelta);
+
+void set_sort_func_by_name(struct irq_output *out, const char *name);
+void set_sort_func_by_key(struct irq_output *out, const char c);
+
+struct libscols_table *get_scols_table(struct irq_output *out,
+ struct irq_stat *prev,
+ struct irq_stat **xstat);
+
+#endif /* UTIL_LINUX_H_IRQ_COMMON */
diff --git a/sys-utils/irqtop.1 b/sys-utils/irqtop.1
new file mode 100644
index 0000000..96b053f
--- /dev/null
+++ b/sys-utils/irqtop.1
@@ -0,0 +1,75 @@
+.TH IRQTOP "1" "February 2020" "util-linux" "User Commands"
+.SH NAME
+irqtop \- utility to display kernel interrupt information
+.SH SYNOPSIS
+.B irqtop
+[options]
+.SH DESCRIPTION
+Display kernel interrupt counter information in
+.BR top (1)
+style view.
+.PP
+The default output is subject to change. So whenever possible, you should
+avoid using default outputs in your scripts. Always explicitly define
+expected columns by using
+.BR \-\-output .
+.SH OPTIONS
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use
+.B \-\-help
+to get a list of all supported columns. The default list of columns may be
+extended if list is specified in the format
+.IR +list .
+.TP
+.BR \-d , " \-\-delay " \fIseconds\fP
+Update interrupt output every
+.I seconds
+intervals.
+.TP
+.BR \-s , " \-\-sort " \fIcolumn\fP
+Specify sort criteria by column name. See
+.B \-\-help
+output to get column names. The sort criteria may be changes in interactive
+mode.
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+.TP
+.BR \-h ,\ \-\-help
+Display help text and exit.
+.SH INTERACTIVE MODE KEY COMMANDS
+.PD 0
+.TP
+.B i
+sort by short irq name or number field
+.TP
+.B t
+sort by total count of interrupts (the default)
+.TP
+.B d
+sort by delta count of interrupts
+.TP
+.B n
+sort by long descriptive name field
+.TP
+.B q Q
+stop updates and exit program
+.PD 1
+.SH AUTHORS
+.MT pizhenwei@\:bytedance.com
+Zhenwei Pi
+.ME
+.br
+.MT kerolasa@\:iki.fi
+Sami Kerola
+.ME
+.br
+.MT kzak@\:redhat.com
+Karel Zak
+.ME
+.SH AVAILABILITY
+The irqtop command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/irqtop.c b/sys-utils/irqtop.c
new file mode 100644
index 0000000..4363fff
--- /dev/null
+++ b/sys-utils/irqtop.c
@@ -0,0 +1,341 @@
+/*
+ * irqtop.c - utility to display kernel interrupt information.
+ *
+ * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com>
+ * Copyright (C) 2020 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/signalfd.h>
+#include <sys/time.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifdef HAVE_SLCURSES_H
+# include <slcurses.h>
+#elif defined(HAVE_SLANG_SLCURSES_H)
+# include <slang/slcurses.h>
+#elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR)
+# include <ncursesw/ncurses.h>
+#elif defined(HAVE_NCURSES_H)
+# include <ncurses.h>
+#elif defined(HAVE_NCURSES_NCURSES_H)
+# include <ncurses/ncurses.h>
+#endif
+
+#ifdef HAVE_WIDECHAR
+# include <wctype.h>
+# include <wchar.h>
+#endif
+
+#include <libsmartcols.h>
+
+#include "closestream.h"
+#include "monotonic.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "timeutils.h"
+#include "ttyutils.h"
+#include "xalloc.h"
+
+#include "irq-common.h"
+
+#define MAX_EVENTS 3
+
+/* top control struct */
+struct irqtop_ctl {
+ WINDOW *win;
+ int cols;
+ int rows;
+ char *hostname;
+
+ struct itimerspec timer;
+ struct irq_stat *prev_stat;
+
+ unsigned int request_exit:1;
+};
+
+/* user's input parser */
+static void parse_input(struct irqtop_ctl *ctl, struct irq_output *out, char c)
+{
+ switch (c) {
+ case 'q':
+ case 'Q':
+ ctl->request_exit = 1;
+ break;
+ default:
+ set_sort_func_by_key(out, c);
+ break;
+ }
+}
+
+static int update_screen(struct irqtop_ctl *ctl, struct irq_output *out)
+{
+ struct libscols_table *table;
+ struct irq_stat *stat;
+ time_t now = time(NULL);
+ char timestr[64], *data;
+
+ table = get_scols_table(out, ctl->prev_stat, &stat);
+ if (!table) {
+ ctl->request_exit = 1;
+ return 1;
+ }
+
+ /* header in interactive mode */
+ move(0, 0);
+ strtime_iso(&now, ISO_TIMESTAMP, timestr, sizeof(timestr));
+ wprintw(ctl->win, _("irqtop | total: %ld delta: %ld | %s | %s\n\n"),
+ stat->total_irq, stat->delta_irq, ctl->hostname, timestr);
+
+ scols_print_table_to_string(table, &data);
+ wprintw(ctl->win, "%s", data);
+ free(data);
+
+ /* clean up */
+ scols_unref_table(table);
+ if (ctl->prev_stat)
+ free_irqstat(ctl->prev_stat);
+ ctl->prev_stat = stat;
+ return 0;
+}
+
+static int event_loop(struct irqtop_ctl *ctl, struct irq_output *out)
+{
+ int efd, sfd, tfd;
+ sigset_t sigmask;
+ struct signalfd_siginfo siginfo;
+ struct epoll_event ev, events[MAX_EVENTS];
+ long int nr;
+ uint64_t unused;
+ int retval = 0;
+
+ efd = epoll_create1(0);
+
+ if ((tfd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0)
+ err(EXIT_FAILURE, _("cannot not create timerfd"));
+ if (timerfd_settime(tfd, 0, &ctl->timer, NULL) != 0)
+ err(EXIT_FAILURE, _("cannot set timerfd"));
+
+ ev.events = EPOLLIN;
+ ev.data.fd = tfd;
+ if (epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev) != 0)
+ err(EXIT_FAILURE, _("epoll_ctl failed"));
+
+ if (sigfillset(&sigmask) != 0)
+ err(EXIT_FAILURE, _("sigfillset failed"));
+ if (sigprocmask(SIG_BLOCK, &sigmask, NULL) != 0)
+ err(EXIT_FAILURE, _("sigprocmask failed"));
+
+ sigaddset(&sigmask, SIGWINCH);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGQUIT);
+
+ if ((sfd = signalfd(-1, &sigmask, SFD_CLOEXEC)) < 0)
+ err(EXIT_FAILURE, _("cannot not create signalfd"));
+
+ ev.events = EPOLLIN;
+ ev.data.fd = sfd;
+ if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &ev) != 0)
+ err(EXIT_FAILURE, _("epoll_ctl failed"));
+
+ ev.events = EPOLLIN;
+ ev.data.fd = STDIN_FILENO;
+ if (epoll_ctl(efd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) != 0)
+ err(EXIT_FAILURE, _("epoll_ctl failed"));
+
+ retval |= update_screen(ctl, out);
+ refresh();
+
+ while (!ctl->request_exit) {
+ const ssize_t nr_events = epoll_wait(efd, events, MAX_EVENTS, -1);
+
+ for (nr = 0; nr < nr_events; nr++) {
+ if (events[nr].data.fd == tfd) {
+ if (read(tfd, &unused, sizeof(unused)) < 0)
+ warn(_("read failed"));
+ } else if (events[nr].data.fd == sfd) {
+ if (read(sfd, &siginfo, sizeof(siginfo)) < 0) {
+ warn(_("read failed"));
+ continue;
+ }
+ if (siginfo.ssi_signo == SIGWINCH) {
+ get_terminal_dimension(&ctl->cols, &ctl->rows);
+#if HAVE_RESIZETERM
+ resizeterm(ctl->rows, ctl->cols);
+#endif
+ }
+ else {
+ ctl->request_exit = 1;
+ break;
+ }
+ } else if (events[nr].data.fd == STDIN_FILENO) {
+ char c;
+
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ warn(_("read failed"));
+ parse_input(ctl, out, c);
+ } else
+ abort();
+ retval |= update_screen(ctl, out);
+ refresh();
+ }
+ }
+ return retval;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(_(" %s [options]\n"), program_invocation_short_name);
+ fputs(USAGE_SEPARATOR, stdout);
+
+ puts(_("Interactive utility to display kernel interrupt information."));
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(" -d, --delay <secs> delay updates\n"), stdout);
+ fputs(_(" -o, --output <list> define which output columns to use\n"), stdout);
+ fputs(_(" -s, --sort <column> specify sort column\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ fputs(_("\nThe following interactive key commands are valid:\n"), stdout);
+ fputs(_(" i sort by IRQ\n"), stdout);
+ fputs(_(" t sort by TOTAL\n"), stdout);
+ fputs(_(" d sort by DELTA\n"), stdout);
+ fputs(_(" n sort by NAME\n"), stdout);
+ fputs(_(" q Q quit program\n"), stdout);
+
+ fputs(USAGE_COLUMNS, stdout);
+ irq_print_columns(stdout, 0);
+
+ printf(USAGE_MAN_TAIL("irqtop(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static void parse_args( struct irqtop_ctl *ctl,
+ struct irq_output *out,
+ int argc,
+ char **argv)
+{
+ const char *outarg = NULL;
+ static const struct option longopts[] = {
+ {"delay", required_argument, NULL, 'd'},
+ {"sort", required_argument, NULL, 's'},
+ {"output", required_argument, NULL, 'o'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ int o;
+
+ while ((o = getopt_long(argc, argv, "d:o:s:hV", longopts, NULL)) != -1) {
+ switch (o) {
+ case 'd':
+ {
+ struct timeval delay;
+
+ strtotimeval_or_err(optarg, &delay,
+ _("failed to parse delay argument"));
+ TIMEVAL_TO_TIMESPEC(&delay, &ctl->timer.it_interval);
+ ctl->timer.it_value = ctl->timer.it_interval;
+ }
+ break;
+ case 's':
+ set_sort_func_by_name(out, optarg);
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ /* default */
+ if (!out->ncolumns) {
+ out->columns[out->ncolumns++] = COL_IRQ;
+ out->columns[out->ncolumns++] = COL_TOTAL;
+ out->columns[out->ncolumns++] = COL_DELTA;
+ out->columns[out->ncolumns++] = COL_NAME;
+ }
+
+ /* add -o [+]<list> to putput */
+ if (outarg && string_add_to_idarray(outarg, out->columns,
+ ARRAY_SIZE(out->columns),
+ &out->ncolumns,
+ irq_column_name_to_id) < 0)
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv)
+{
+ int is_tty = 0;
+ struct termios saved_tty;
+ struct irq_output out = {
+ .ncolumns = 0
+ };
+ struct irqtop_ctl ctl = {
+ .timer.it_interval = {3, 0},
+ .timer.it_value = {3, 0}
+ };
+
+ setlocale(LC_ALL, "");
+
+ parse_args(&ctl, &out, argc, argv);
+
+ is_tty = isatty(STDIN_FILENO);
+ if (is_tty && tcgetattr(STDIN_FILENO, &saved_tty) == -1)
+ fputs(_("terminal setting retrieval"), stdout);
+
+ ctl.win = initscr();
+ get_terminal_dimension(&ctl.cols, &ctl.rows);
+#if HAVE_RESIZETERM
+ resizeterm(ctl.rows, ctl.cols);
+#endif
+ curs_set(0);
+
+ ctl.hostname = xgethostname();
+ event_loop(&ctl, &out);
+
+ free_irqstat(ctl.prev_stat);
+ free(ctl.hostname);
+
+ if (is_tty)
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tty);
+ delwin(ctl.win);
+ endwin();
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/ldattach.8 b/sys-utils/ldattach.8
new file mode 100644
index 0000000..eb3b0b6
--- /dev/null
+++ b/sys-utils/ldattach.8
@@ -0,0 +1,155 @@
+.\" Copyright 2008 Tilman Schmidt (tilman@imap.cc)
+.\" May be distributed under the GNU General Public License version 2 or later
+.TH LDATTACH 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+ldattach \- attach a line discipline to a serial line
+.SH SYNOPSIS
+.B ldattach
+.RB [ \-1278denoVh ]
+.RB [ \-i
+.IR iflag ]
+.RB [ \-s
+.IR speed ]
+.I ldisc device
+.SH DESCRIPTION
+The
+.B ldattach
+daemon opens the specified
+.I device
+file
+(which should refer to a serial device)
+and attaches the line discipline
+.I ldisc
+to it for processing of the sent and/or received data.
+It then goes into the background keeping the device open so that the
+line discipline stays loaded.
+.sp
+The line discipline
+.I ldisc
+may be specified either by name
+or by number.
+.sp
+In order to detach the line discipline,
+.BR kill (1)
+the
+.B ldattach
+process.
+.sp
+With no arguments,
+.B ldattach
+prints usage information.
+.SH LINE DISCIPLINES
+Depending on the kernel release, the following line disciplines are supported:
+.TP
+.BR TTY ( 0 )
+The default line discipline,
+providing transparent operation (raw mode)
+as well as the habitual terminal line editing capabilities (cooked mode).
+.TP
+.BR SLIP ( 1 )
+Serial Line IP (SLIP) protocol processor
+for transmitting TCP/IP packets over serial lines.
+.TP
+.BR MOUSE ( 2 )
+Device driver for RS232 connected pointing devices (serial mice).
+.TP
+.BR PPP ( 3 )
+Point to Point Protocol (PPP) processor
+for transmitting network packets over serial lines.
+.TP
+.BR STRIP ( 4 )
+.TP
+.BR AX25 ( 5 )
+.TP
+.BR X25 ( 6 )
+Line driver for transmitting X.25 packets over asynchronous serial lines.
+.TP
+.BR 6PACK ( 7 )
+.TP
+.BR R3964 ( 9 )
+Driver for Simatic R3964 module.
+.TP
+.BR IRDA ( 11 )
+Linux IrDa (infrared data transmission) driver -
+see http://irda.sourceforge.net/
+.TP
+.BR HDLC ( 13 )
+Synchronous HDLC driver.
+.TP
+.BR SYNC_PPP ( 14 )
+Synchronous PPP driver.
+.TP
+.BR HCI ( 15 )
+Bluetooth HCI UART driver.
+.TP
+.BR GIGASET_M101 ( 16 )
+Driver for Siemens Gigaset M101 serial DECT adapter.
+.TP
+.BR PPS ( 18 )
+Driver for serial line Pulse Per Second (PPS) source.
+.TP
+.BR GSM0710 ( 21 )
+Driver for GSM 07.10 multiplexing protocol modem (CMUX).
+.SH OPTIONS
+.TP
+.BR \-1 , " \-\-onestopbit"
+Set the number of stop bits of the serial line to one.
+.TP
+.BR \-2 , " \-\-twostopbits"
+Set the number of stop bits of the serial line to two.
+.TP
+.BR \-7 , " \-\-sevenbits"
+Set the character size of the serial line to 7 bits.
+.TP
+.BR \-8 , " \-\-eightbits"
+Set the character size of the serial line to 8 bits.
+.TP
+.BR \-d , " \-\-debug"
+Keep
+.B ldattach
+in the foreground so that it can be interrupted or debugged,
+and to print verbose messages about its progress to standard error output.
+.TP
+.BR \-e , " \-\-evenparity"
+Set the parity of the serial line to even.
+.TP
+.BR \-i , " \-\-iflag " [ \- ] \fIvalue\fR...
+Set the specified bits in the c_iflag word of the serial line.
+The given \fIvalue\fP may be a number or a symbolic name.
+If \fIvalue\fP is prefixed by a minus sign, the specified bits are cleared
+instead. Several comma-separated values may be given in order to
+set and clear multiple bits.
+.TP
+.BR \-n , " \-\-noparity"
+Set the parity of the serial line to none.
+.TP
+.BR \-o , " \-\-oddparity"
+Set the parity of the serial line to odd.
+.TP
+.BR \-s , " \-\-speed " \fIvalue
+Set the speed (the baud rate) of the serial line to the specified \fIvalue\fR.
+.TP
+.BR \-c , " \-\-intro\-command " \fIstring
+Define an intro command that is sent through the serial line before the invocation
+of ldattach. E.g. in conjunction with line discipline GSM0710, the command
+\'AT+CMUX=0\\r\' is commonly suitable to switch the modem into the CMUX mode.
+.TP
+.BR \-p , " \-\-pause " \fIvalue
+Sleep for \fIvalue\fR seconds before the invocation of ldattach. Default is one second.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+.nf
+Tilman Schmidt (tilman@imap.cc)
+.fi
+.SH SEE ALSO
+.BR inputattach (1),
+.BR ttys (4)
+.SH AVAILABILITY
+The ldattach command is part of the util-linux package
+and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/ldattach.c b/sys-utils/ldattach.c
new file mode 100644
index 0000000..baf7cd5
--- /dev/null
+++ b/sys-utils/ldattach.c
@@ -0,0 +1,488 @@
+/* line discipline loading daemon
+ * open a serial device and attach a line discipline on it
+ *
+ * Usage:
+ * ldattach GIGASET_M101 /dev/ttyS0
+ *
+ * =====================================================================
+ * 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.
+ * =====================================================================
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "all-io.h"
+#include "nls.h"
+#include "strutils.h"
+#include "closestream.h"
+
+#include <signal.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+
+#include <linux/tty.h> /* for N_GSM0710 */
+
+#ifdef LINUX_GSMMUX_H
+# include <linux/gsmmux.h> /* Add by guowenxue */
+#else
+struct gsm_config
+{
+ unsigned int adaption;
+ unsigned int encapsulation;
+ unsigned int initiator;
+ unsigned int t1;
+ unsigned int t2;
+ unsigned int t3;
+ unsigned int n2;
+ unsigned int mru;
+ unsigned int mtu;
+ unsigned int k;
+ unsigned int i;
+ unsigned int unused[8]; /* Padding for expansion without
+ breaking stuff */
+};
+# define GSMIOC_GETCONF _IOR('G', 0, struct gsm_config)
+# define GSMIOC_SETCONF _IOW('G', 1, struct gsm_config)
+#endif
+
+#ifndef N_GIGASET_M101
+# define N_GIGASET_M101 16
+#endif
+
+#ifndef N_PPS
+# define N_PPS 18
+#endif
+
+#ifndef N_GSM0710
+# define N_GSM0710 21
+#endif
+
+#define MAXINTROPARMLEN 32
+
+/* attach a line discipline ioctl */
+#ifndef TIOCSETD
+# define TIOCSETD 0x5423
+#endif
+
+static int debug = 0;
+
+struct ld_table {
+ const char *name;
+ int value;
+};
+
+/* currently supported line disciplines, plus some aliases */
+static const struct ld_table ld_discs[] = {
+ { "TTY", N_TTY },
+ { "SLIP", N_SLIP },
+ { "MOUSE", N_MOUSE },
+ { "PPP", N_PPP },
+ { "STRIP", N_STRIP },
+ { "AX25", N_AX25 },
+ { "X25", N_X25 },
+ { "6PACK", N_6PACK },
+ { "R3964", N_R3964 },
+ { "IRDA", N_IRDA },
+ { "HDLC", N_HDLC },
+ { "SYNC_PPP", N_SYNC_PPP },
+ { "SYNCPPP", N_SYNC_PPP },
+ { "HCI", N_HCI },
+ { "GIGASET_M101", N_GIGASET_M101 },
+ { "M101", N_GIGASET_M101 },
+ { "GIGASET", N_GIGASET_M101 },
+ { "PPS", N_PPS },
+ { "GSM0710", N_GSM0710},
+ { NULL, 0 }
+};
+
+/* known c_iflag names */
+static const struct ld_table ld_iflags[] =
+{
+ { "IGNBRK", IGNBRK },
+ { "BRKINT", BRKINT },
+ { "IGNPAR", IGNPAR },
+ { "PARMRK", PARMRK },
+ { "INPCK", INPCK },
+ { "ISTRIP", ISTRIP },
+ { "INLCR", INLCR },
+ { "IGNCR", IGNCR },
+ { "ICRNL", ICRNL },
+ { "IUCLC", IUCLC },
+ { "IXON", IXON },
+ { "IXANY", IXANY },
+ { "IXOFF", IXOFF },
+ { "IMAXBEL", IMAXBEL },
+ { "IUTF8", IUTF8 },
+ { NULL, 0 }
+};
+
+static void dbg(char *fmt, ...)
+{
+ va_list args;
+
+ if (debug == 0)
+ return;
+ fflush(NULL);
+ va_start(args, fmt);
+#ifdef HAVE_VWARNX
+ vwarnx(fmt, args);
+#else
+ fprintf(stderr, "%s: ", program_invocation_short_name);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+#endif
+ va_end(args);
+ fflush(NULL);
+}
+
+static int lookup_table(const struct ld_table *tab, const char *str)
+{
+ const struct ld_table *t;
+
+ for (t = tab; t && t->name; t++)
+ if (!strcasecmp(t->name, str))
+ return t->value;
+ return -1;
+}
+
+static void print_table(FILE * out, const struct ld_table *tab)
+{
+ const struct ld_table *t;
+ int i;
+
+ for (t = tab, i = 1; t && t->name; t++, i++) {
+ fprintf(out, " %-12s", t->name);
+ if (!(i % 5))
+ fputc('\n', out);
+ }
+}
+
+static int parse_iflag(char *str, int *set_iflag, int *clr_iflag)
+{
+ int iflag;
+ char *s;
+
+ for (s = strtok(str, ","); s != NULL; s = strtok(NULL, ",")) {
+ if (*s == '-')
+ s++;
+ if ((iflag = lookup_table(ld_iflags, s)) < 0)
+ iflag = strtos32_or_err(s, _("invalid iflag"));
+ if (s > str && *(s - 1) == '-')
+ *clr_iflag |= iflag;
+ else
+ *set_iflag |= iflag;
+ }
+ dbg("iflag (set/clear): %d/%d", *set_iflag, *clr_iflag);
+ return 0;
+}
+
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] <ldisc> <device>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Attach a line discipline to a serial line.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -d, --debug print verbose messages to stderr\n"), out);
+ fputs(_(" -s, --speed <value> set serial line speed\n"), out);
+ fputs(_(" -c, --intro-command <string> intro sent before ldattach\n"), out);
+ fputs(_(" -p, --pause <seconds> pause between intro and ldattach\n"), out);
+ fputs(_(" -7, --sevenbits set character size to 7 bits\n"), out);
+ fputs(_(" -8, --eightbits set character size to 8 bits\n"), out);
+ fputs(_(" -n, --noparity set parity to none\n"), out);
+ fputs(_(" -e, --evenparity set parity to even\n"), out);
+ fputs(_(" -o, --oddparity set parity to odd\n"), out);
+ fputs(_(" -1, --onestopbit set stop bits to one\n"), out);
+ fputs(_(" -2, --twostopbits set stop bits to two\n"), out);
+ fputs(_(" -i, --iflag [-]<iflag> set input mode flag\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ fputs(_("\nKnown <ldisc> names:\n"), out);
+ print_table(out, ld_discs);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_("\nKnown <iflag> names:\n"), out);
+ print_table(out, ld_iflags);
+
+ printf(USAGE_MAN_TAIL("ldattach(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int my_cfsetspeed(struct termios *ts, int speed)
+{
+ /* Standard speeds
+ * -- cfsetspeed() is able to translate number to Bxxx constants
+ */
+ if (cfsetspeed(ts, speed) == 0)
+ return 0;
+
+ /* Nonstandard speeds
+ * -- we have to bypass glibc and set the speed manually (because glibc
+ * checks for speed and supports Bxxx bit rates only)...
+ */
+#if _HAVE_STRUCT_TERMIOS_C_ISPEED
+# define BOTHER 0010000 /* non standard rate */
+ dbg("using non-standard speeds");
+ ts->c_ospeed = ts->c_ispeed = speed;
+ ts->c_cflag &= ~CBAUD;
+ ts->c_cflag |= BOTHER;
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static void handler(int s)
+{
+ dbg("got SIG %i -> exiting", s);
+ exit(EXIT_SUCCESS);
+}
+
+static void gsm0710_set_conf(int tty_fd)
+{
+ struct gsm_config c;
+
+ /* Add by guowenxue */
+ /* get n_gsm configuration */
+ ioctl(tty_fd, GSMIOC_GETCONF, &c);
+ /* we are initiator and need encoding 0 (basic) */
+ c.initiator = 1;
+ c.encapsulation = 0;
+ /* our modem defaults to a maximum size of 127 bytes */
+ c.mru = 127;
+ c.mtu = 127;
+ /* set the new configuration */
+ ioctl(tty_fd, GSMIOC_SETCONF, &c);
+ /* Add by guowenxue end*/
+}
+
+int main(int argc, char **argv)
+{
+ int tty_fd;
+ struct termios ts;
+ int speed = 0, bits = '-', parity = '-', stop = '-';
+ int set_iflag = 0, clr_iflag = 0;
+ int ldisc;
+ int optc;
+ char *dev;
+ int intropause = 1;
+ char *introparm = NULL;
+
+ static const struct option opttbl[] = {
+ {"speed", required_argument, NULL, 's'},
+ {"sevenbits", no_argument, NULL, '7'},
+ {"eightbits", no_argument, NULL, '8'},
+ {"noparity", no_argument, NULL, 'n'},
+ {"evenparity", no_argument, NULL, 'e'},
+ {"oddparity", no_argument, NULL, 'o'},
+ {"onestopbit", no_argument, NULL, '1'},
+ {"twostopbits", no_argument, NULL, '2'},
+ {"iflag", required_argument, NULL, 'i'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {"debug", no_argument, NULL, 'd'},
+ {"intro-command", no_argument, NULL, 'c'},
+ {"pause", no_argument, NULL, 'p'},
+ {NULL, 0, NULL, 0}
+ };
+
+ signal(SIGKILL, handler);
+ signal(SIGINT, handler);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ /* parse options */
+ if (argc == 0)
+ errx(EXIT_FAILURE, _("bad usage"));
+
+ while ((optc =
+ getopt_long(argc, argv, "dhV78neo12s:i:c:p:", opttbl,
+ NULL)) >= 0) {
+ switch (optc) {
+ case 'd':
+ debug = 1;
+ break;
+ case '1':
+ case '2':
+ stop = optc;
+ break;
+ case '7':
+ case '8':
+ bits = optc;
+ break;
+ case 'n':
+ case 'e':
+ case 'o':
+ parity = optc;
+ break;
+ case 's':
+ speed = strtos32_or_err(optarg, _("invalid speed argument"));
+ break;
+ case 'p':
+ intropause = strtou32_or_err(optarg, _("invalid pause argument"));
+ if (intropause > 10)
+ errx(EXIT_FAILURE, "invalid pause: %s", optarg);
+ break;
+ case 'c':
+ introparm = optarg;
+ break;
+ case 'i':
+ parse_iflag(optarg, &set_iflag, &clr_iflag);
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argc - optind != 2) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ /* parse line discipline specification */
+ ldisc = lookup_table(ld_discs, argv[optind]);
+ if (ldisc < 0)
+ ldisc = strtos32_or_err(argv[optind], _("invalid line discipline argument"));
+
+ /* ldisc specific option settings */
+ if (ldisc == N_GIGASET_M101) {
+ /* device specific defaults for line speed and data format */
+ if (speed == 0)
+ speed = 115200;
+ if (bits == '-')
+ bits = '8';
+ if (parity == '-')
+ parity = 'n';
+ if (stop == '-')
+ stop = '1';
+ }
+
+ /* open device */
+ dev = argv[optind + 1];
+ if ((tty_fd = open(dev, O_RDWR | O_NOCTTY)) < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), dev);
+ if (!isatty(tty_fd))
+ errx(EXIT_FAILURE, _("%s is not a serial line"), dev);
+
+ dbg("opened %s", dev);
+
+ /* set line speed and format */
+ if (tcgetattr(tty_fd, &ts) < 0)
+ err(EXIT_FAILURE,
+ _("cannot get terminal attributes for %s"), dev);
+ cfmakeraw(&ts);
+ if (speed && my_cfsetspeed(&ts, speed) < 0)
+ errx(EXIT_FAILURE, _("speed %d unsupported"), speed);
+
+ switch (stop) {
+ case '1':
+ ts.c_cflag &= ~CSTOPB;
+ break;
+ case '2':
+ ts.c_cflag |= CSTOPB;
+ break;
+ case '-':
+ break;
+ default:
+ abort();
+ }
+ switch (bits) {
+ case '7':
+ ts.c_cflag = (ts.c_cflag & ~CSIZE) | CS7;
+ break;
+ case '8':
+ ts.c_cflag = (ts.c_cflag & ~CSIZE) | CS8;
+ break;
+ case '-':
+ break;
+ default:
+ abort();
+ }
+ switch (parity) {
+ case 'n':
+ ts.c_cflag &= ~(PARENB | PARODD);
+ break;
+ case 'e':
+ ts.c_cflag |= PARENB;
+ ts.c_cflag &= ~PARODD;
+ break;
+ case 'o':
+ ts.c_cflag |= (PARENB | PARODD);
+ break;
+ case '-':
+ break;
+ default:
+ abort();
+ }
+
+ ts.c_cflag |= CREAD; /* just to be on the safe side */
+ ts.c_iflag |= set_iflag;
+ ts.c_iflag &= ~clr_iflag;
+
+ if (tcsetattr(tty_fd, TCSAFLUSH, &ts) < 0)
+ err(EXIT_FAILURE,
+ _("cannot set terminal attributes for %s"), dev);
+
+ dbg("set to raw %d %c%c%c: cflag=0x%x",
+ speed, bits, parity, stop, ts.c_cflag);
+
+ if (introparm && *introparm)
+ {
+ dbg("intro command is '%s'", introparm);
+ if (write_all(tty_fd, introparm, strlen(introparm)) != 0)
+ err(EXIT_FAILURE,
+ _("cannot write intro command to %s"), dev);
+
+ if (intropause) {
+ dbg("waiting for %d seconds", intropause);
+ sleep(intropause);
+ }
+ }
+
+ /* Attach the line discipline. */
+ if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0)
+ err(EXIT_FAILURE, _("cannot set line discipline"));
+
+ dbg("line discipline set to %d", ldisc);
+
+ /* ldisc specific post-attach actions */
+ if (ldisc == N_GSM0710)
+ gsm0710_set_conf(tty_fd);
+
+ /* Go into background if not in debug mode. */
+ if (!debug && daemon(0, 0) < 0)
+ err(EXIT_FAILURE, _("cannot daemonize"));
+
+ /* Sleep to keep the line discipline active. */
+ pause();
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/sys-utils/losetup.8 b/sys-utils/losetup.8
new file mode 100644
index 0000000..23e2dea
--- /dev/null
+++ b/sys-utils/losetup.8
@@ -0,0 +1,217 @@
+.TH LOSETUP 8 "November 2015" "util-linux" "System Administration"
+.SH NAME
+losetup \- set up and control loop devices
+.SH SYNOPSIS
+.ad l
+Get info:
+.sp
+.in +5
+.B losetup
+[\fIloopdev\fP]
+.sp
+.B losetup \-l
+.RB [ \-a ]
+.sp
+.B losetup \-j
+.I file
+.RB [ \-o
+.IR offset ]
+.sp
+.in -5
+Detach a loop device:
+.sp
+.in +5
+.B "losetup \-d"
+.IR loopdev ...
+.sp
+.in -5
+Detach all associated loop devices:
+.sp
+.in +5
+.B "losetup \-D"
+.sp
+.in -5
+Set up a loop device:
+.sp
+.in +5
+.B losetup
+.RB [ \-o
+.IR offset ]
+.RB [ \-\-sizelimit
+.IR size ]
+.RB [ \-\-sector\-size
+.IR size ]
+.in +8
+.RB [ \-Pr ]
+.RB [ \-\-show ] " \-f" | \fIloopdev\fP
+.I file
+.sp
+.in -13
+Resize a loop device:
+.sp
+.in +5
+.B "losetup \-c"
+.I loopdev
+.in -5
+.ad b
+.SH DESCRIPTION
+.B losetup
+is used to associate loop devices with regular files or block devices,
+to detach loop devices, and to query the status of a loop device. If only the
+\fIloopdev\fP argument is given, the status of the corresponding loop
+device is shown. If no option is given, all loop devices are shown.
+.sp
+Note that the old output format (i.e., \fBlosetup \-a\fR) with comma-delimited
+strings is deprecated in favour of the \fB\-\-list\fR output format.
+.sp
+It's possible to create more independent loop devices for the same backing
+file.
+.B This setup may be dangerous, can cause data loss, corruption and overwrites.
+Use \fB\-\-nooverlap\fR with \fB\-\-find\fR during setup to avoid this problem.
+.sp
+The loop device setup is not an atomic operation when used with \fB\-\-find\fP, and
+.B losetup
+does not protect this operation by any lock. The number of attempts is
+internally restricted to a maximum of 16. It is recommended to use for example
+.BR flock (1)
+to avoid a collision in heavily parallel use cases.
+
+.SH OPTIONS
+The \fIsize\fR and \fIoffset\fR
+arguments may be followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" is
+optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+
+.TP
+.BR \-a , " \-\-all"
+Show the status of all loop devices. Note that not all information is accessible
+for non-root users. See also \fB\-\-list\fR. The old output format (as printed
+without \fB\-\-list)\fR is deprecated.
+.TP
+.BR \-d , " \-\-detach " \fIloopdev\fR...
+Detach the file or device associated with the specified loop device(s). Note
+that since Linux v3.7 kernel uses "lazy device destruction". The detach
+operation does not return EBUSY error anymore if device is actively used by
+system, but it is marked by autoclear flag and destroyed later.
+.TP
+.BR \-D , " \-\-detach\-all"
+Detach all associated loop devices.
+.TP
+.BR \-f , " \-\-find " "\fR[\fIfile\fR]"
+Find the first unused loop device. If a \fIfile\fR argument is present, use
+the found device as loop device. Otherwise, just print its name.
+.IP "\fB\-\-show\fP"
+Display the name of the assigned loop device if the \fB\-f\fP option and a
+\fIfile\fP argument are present.
+.TP
+.BR \-L , " \-\-nooverlap"
+Check for conflicts between loop devices to avoid situation when the same
+backing file is shared between more loop devices. If the file is already used
+by another device then re-use the device rather than a new one. The option
+makes sense only with \fB\-\-find\fP.
+.TP
+.BR \-j , " \-\-associated " \fIfile\fR " \fR[\fB\-o \fIoffset\fR]"
+Show the status of all loop devices associated with the given \fIfile\fR.
+.TP
+.BR \-o , " \-\-offset " \fIoffset
+The data start is moved \fIoffset\fP bytes into the specified file or device. The \fIoffset\fP
+may be followed by the multiplicative suffixes; see above.
+.IP "\fB\-\-sizelimit \fIsize\fP"
+The data end is set to no more than \fIsize\fP bytes after the data start. The \fIsize\fP
+may be followed by the multiplicative suffixes; see above.
+.TP
+.BR \-b , " \-\-sector-size " \fIsize
+Set the logical sector size of the loop device in bytes (since Linux 4.14). The
+option may be used when create a new loop device as well as stand-alone command
+to modify sector size of the already existing loop device.
+.TP
+.BR \-c , " \-\-set\-capacity " \fIloopdev
+Force the loop driver to reread the size of the file associated with the
+specified loop device.
+.TP
+.BR \-P , " \-\-partscan"
+Force the kernel to scan the partition table on a newly created loop device. Note that the
+partition table parsing depends on sector sizes. The default is sector size is 512 bytes,
+otherwise you need to use the option \fB\-\-sector\-size\fR together with \fB\-\-partscan\fR.
+.TP
+.BR \-r , " \-\-read\-only"
+Set up a read-only loop device.
+.TP
+.BR \-\-direct\-io [ =on | off ]
+Enable or disable direct I/O for the backing file. The optional argument
+can be either \fBon\fR or \fBoff\fR. If the argument is omitted, it defaults
+to \fBoff\fR.
+.TP
+.BR \-v , " \-\-verbose"
+Verbose mode.
+.TP
+.BR \-l , " \-\-list"
+If a loop device or the \fB\-a\fR option is specified, print the default columns
+for either the specified loop device or all loop devices; the default is to
+print info about all devices. See also \fB\-\-output\fP, \fB\-\-noheadings\fP,
+\fB\-\-raw\fP, and \fB\-\-json\fP.
+.TP
+.BR \-O , " \-\-output " \fIcolumn\fR[,\fIcolumn\fR]...
+Specify the columns that are to be printed for the \fB\-\-list\fP output.
+Use \fB\-\-help\fR to get a list of all supported columns.
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.BR \-n , " \-\-noheadings"
+Don't print headings for \fB\-\-list\fP output format.
+.IP "\fB\-\-raw\fP"
+Use the raw \fB\-\-list\fP output format.
+.TP
+.BR \-J , " \-\-json"
+Use JSON format for \fB\-\-list\fP output.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH ENCRYPTION
+.B Cryptoloop is no longer supported in favor of dm-crypt.
+.B For more details see cryptsetup(8).
+
+.SH EXIT STATUS
+.B losetup
+returns 0 on success, nonzero on failure. When
+.B losetup
+displays the status of a loop device, it returns 1 if the device
+is not configured and 2 if an error occurred which prevented
+determining the status of the device.
+
+.SH ENVIRONMENT
+.IP LOOPDEV_DEBUG=all
+enables debug output.
+
+.SH FILES
+.TP
+.I /dev/loop[0..N]
+loop block devices
+.TP
+.I /dev/loop-control
+loop control device
+.SH EXAMPLE
+The following commands can be used as an example of using the loop device.
+.nf
+.IP
+# dd if=/dev/zero of=~/file.img bs=1024k count=10
+# losetup \-\-find \-\-show ~/file.img
+/dev/loop0
+# mkfs \-t ext2 /dev/loop0
+# mount /dev/loop0 /mnt
+ ...
+# umount /dev/loop0
+# losetup \-\-detach /dev/loop0
+.fi
+.SH AUTHORS
+Karel Zak <kzak@redhat.com>, based on the original version from
+Theodore Ts'o <tytso@athena.mit.edu>
+.SH AVAILABILITY
+The losetup command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/losetup.c b/sys-utils/losetup.c
new file mode 100644
index 0000000..7aadc9f
--- /dev/null
+++ b/sys-utils/losetup.c
@@ -0,0 +1,925 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ * Originally from Ted's losetup.c
+ *
+ * losetup.c - setup and control loop devices
+ */
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <getopt.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "loopdev.h"
+#include "closestream.h"
+#include "optutils.h"
+#include "xalloc.h"
+#include "canonicalize.h"
+#include "pathnames.h"
+
+enum {
+ A_CREATE = 1, /* setup a new device */
+ A_DELETE, /* delete given device(s) */
+ A_DELETE_ALL, /* delete all devices */
+ A_SHOW, /* list devices */
+ A_SHOW_ONE, /* print info about one device */
+ A_FIND_FREE, /* find first unused */
+ A_SET_CAPACITY, /* set device capacity */
+ A_SET_DIRECT_IO, /* set accessing backing file by direct io */
+ A_SET_BLOCKSIZE, /* set logical block size of the loop device */
+};
+
+enum {
+ COL_NAME = 0,
+ COL_AUTOCLR,
+ COL_BACK_FILE,
+ COL_BACK_INO,
+ COL_BACK_MAJMIN,
+ COL_MAJMIN,
+ COL_OFFSET,
+ COL_PARTSCAN,
+ COL_RO,
+ COL_SIZELIMIT,
+ COL_DIO,
+ COL_LOGSEC,
+};
+
+/* basic output flags */
+static int no_headings;
+static int raw;
+static int json;
+
+struct colinfo {
+ const char *name;
+ double whint;
+ int flags;
+ const char *help;
+
+ int json_type; /* default is string */
+};
+
+static struct colinfo infos[] = {
+ [COL_AUTOCLR] = { "AUTOCLEAR", 1, SCOLS_FL_RIGHT, N_("autoclear flag set"), SCOLS_JSON_BOOLEAN},
+ [COL_BACK_FILE] = { "BACK-FILE", 0.3, 0, N_("device backing file")},
+ [COL_BACK_INO] = { "BACK-INO", 4, SCOLS_FL_RIGHT, N_("backing file inode number"), SCOLS_JSON_NUMBER},
+ [COL_BACK_MAJMIN] = { "BACK-MAJ:MIN", 6, 0, N_("backing file major:minor device number")},
+ [COL_NAME] = { "NAME", 0.25, 0, N_("loop device name")},
+ [COL_OFFSET] = { "OFFSET", 5, SCOLS_FL_RIGHT, N_("offset from the beginning"), SCOLS_JSON_NUMBER},
+ [COL_PARTSCAN] = { "PARTSCAN", 1, SCOLS_FL_RIGHT, N_("partscan flag set"), SCOLS_JSON_BOOLEAN},
+ [COL_RO] = { "RO", 1, SCOLS_FL_RIGHT, N_("read-only device"), SCOLS_JSON_BOOLEAN},
+ [COL_SIZELIMIT] = { "SIZELIMIT", 5, SCOLS_FL_RIGHT, N_("size limit of the file in bytes"), SCOLS_JSON_NUMBER},
+ [COL_MAJMIN] = { "MAJ:MIN", 3, 0, N_("loop device major:minor number")},
+ [COL_DIO] = { "DIO", 1, SCOLS_FL_RIGHT, N_("access backing file with direct-io"), SCOLS_JSON_BOOLEAN},
+ [COL_LOGSEC] = { "LOG-SEC", 4, SCOLS_FL_RIGHT, N_("logical sector size in bytes"), SCOLS_JSON_NUMBER},
+};
+
+static int columns[ARRAY_SIZE(infos) * 2] = {-1};
+static size_t ncolumns;
+
+static int get_column_id(int num)
+{
+ assert(num >= 0);
+ assert((size_t) num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+ return columns[num];
+}
+
+static struct colinfo *get_column_info(int num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int printf_loopdev(struct loopdev_cxt *lc)
+{
+ uint64_t x;
+ dev_t dev = 0;
+ ino_t ino = 0;
+ char *fname;
+ uint32_t type;
+
+ fname = loopcxt_get_backing_file(lc);
+ if (!fname)
+ return -EINVAL;
+
+ if (loopcxt_get_backing_devno(lc, &dev) == 0)
+ loopcxt_get_backing_inode(lc, &ino);
+
+ if (!dev && !ino) {
+ /*
+ * Probably non-root user (no permissions to
+ * call LOOP_GET_STATUS ioctls).
+ */
+ printf("%s: []: (%s)",
+ loopcxt_get_device(lc), fname);
+
+ if (loopcxt_get_offset(lc, &x) == 0 && x)
+ printf(_(", offset %ju"), x);
+
+ if (loopcxt_get_sizelimit(lc, &x) == 0 && x)
+ printf(_(", sizelimit %ju"), x);
+ goto done;
+ }
+
+ printf("%s: [%04d]:%" PRIu64 " (%s)",
+ loopcxt_get_device(lc), (int) dev, ino, fname);
+
+ if (loopcxt_get_offset(lc, &x) == 0 && x)
+ printf(_(", offset %ju"), x);
+
+ if (loopcxt_get_sizelimit(lc, &x) == 0 && x)
+ printf(_(", sizelimit %ju"), x);
+
+ if (loopcxt_get_encrypt_type(lc, &type) == 0) {
+ const char *e = loopcxt_get_crypt_name(lc);
+
+ if ((!e || !*e) && type == 1)
+ e = "XOR";
+ if (e && *e)
+ printf(_(", encryption %s (type %u)"), e, type);
+ }
+
+done:
+ free(fname);
+ printf("\n");
+ return 0;
+}
+
+static int show_all_loops(struct loopdev_cxt *lc, const char *file,
+ uint64_t offset, int flags)
+{
+ struct stat sbuf, *st = &sbuf;
+ char *cn_file = NULL;
+
+ if (loopcxt_init_iterator(lc, LOOPITER_FL_USED))
+ return -1;
+
+ if (!file || stat(file, st))
+ st = NULL;
+
+ while (loopcxt_next(lc) == 0) {
+ if (file) {
+ int used;
+ const char *bf = cn_file ? cn_file : file;
+
+ used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
+ if (!used && !cn_file) {
+ bf = cn_file = canonicalize_path(file);
+ used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
+ }
+ if (!used)
+ continue;
+ }
+ printf_loopdev(lc);
+ }
+ loopcxt_deinit_iterator(lc);
+ free(cn_file);
+ return 0;
+}
+
+static int delete_loop(struct loopdev_cxt *lc)
+{
+ if (loopcxt_delete_device(lc))
+ warn(_("%s: detach failed"), loopcxt_get_device(lc));
+ else
+ return 0;
+
+ return -1;
+}
+
+static int delete_all_loops(struct loopdev_cxt *lc)
+{
+ int res = 0;
+
+ if (loopcxt_init_iterator(lc, LOOPITER_FL_USED))
+ return -1;
+
+ while (loopcxt_next(lc) == 0)
+ res += delete_loop(lc);
+
+ loopcxt_deinit_iterator(lc);
+ return res;
+}
+
+static int set_scols_data(struct loopdev_cxt *lc, struct libscols_line *ln)
+{
+ size_t i;
+
+ for (i = 0; i < ncolumns; i++) {
+ const char *p = NULL; /* external data */
+ char *np = NULL; /* allocated here */
+ uint64_t x = 0;
+ int rc = 0;
+
+ switch(get_column_id(i)) {
+ case COL_NAME:
+ p = loopcxt_get_device(lc);
+ break;
+ case COL_BACK_FILE:
+ p = loopcxt_get_backing_file(lc);
+ break;
+ case COL_OFFSET:
+ if (loopcxt_get_offset(lc, &x) == 0)
+ xasprintf(&np, "%jd", x);
+ break;
+ case COL_SIZELIMIT:
+ if (loopcxt_get_sizelimit(lc, &x) == 0)
+ xasprintf(&np, "%jd", x);
+ break;
+ case COL_BACK_MAJMIN:
+ {
+ dev_t dev = 0;
+ if (loopcxt_get_backing_devno(lc, &dev) == 0 && dev)
+ xasprintf(&np, "%8u:%-3u", major(dev), minor(dev));
+ break;
+ }
+ case COL_MAJMIN:
+ {
+ struct stat st;
+
+ if (loopcxt_get_device(lc)
+ && stat(loopcxt_get_device(lc), &st) == 0
+ && S_ISBLK(st.st_mode)
+ && major(st.st_rdev) == LOOPDEV_MAJOR)
+ xasprintf(&np, "%3u:%-3u", major(st.st_rdev),
+ minor(st.st_rdev));
+ break;
+ }
+ case COL_BACK_INO:
+ {
+ ino_t ino = 0;
+ if (loopcxt_get_backing_inode(lc, &ino) == 0 && ino)
+ xasprintf(&np, "%ju", ino);
+ break;
+ }
+ case COL_AUTOCLR:
+ p = loopcxt_is_autoclear(lc) ? "1" : "0";
+ break;
+ case COL_RO:
+ p = loopcxt_is_readonly(lc) ? "1" : "0";
+ break;
+ case COL_DIO:
+ p = loopcxt_is_dio(lc) ? "1" : "0";
+ break;
+ case COL_PARTSCAN:
+ p = loopcxt_is_partscan(lc) ? "1" : "0";
+ break;
+ case COL_LOGSEC:
+ if (loopcxt_get_blocksize(lc, &x) == 0)
+ xasprintf(&np, "%jd", x);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ if (p)
+ rc = scols_line_set_data(ln, i, p); /* calls strdup() */
+ else if (np)
+ rc = scols_line_refer_data(ln, i, np); /* only refers */
+
+ if (rc)
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+
+ return 0;
+}
+
+static int show_table(struct loopdev_cxt *lc,
+ const char *file,
+ uint64_t offset,
+ int flags)
+{
+ struct stat sbuf, *st = &sbuf;
+ struct libscols_table *tb;
+ struct libscols_line *ln;
+ int rc = 0;
+ size_t i;
+
+ scols_init_debug(0);
+
+ if (!(tb = scols_new_table()))
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+ scols_table_enable_raw(tb, raw);
+ scols_table_enable_json(tb, json);
+ scols_table_enable_noheadings(tb, no_headings);
+
+ if (json)
+ scols_table_set_name(tb, "loopdevices");
+
+ for (i = 0; i < ncolumns; i++) {
+ struct colinfo *ci = get_column_info(i);
+ struct libscols_column *cl;
+
+ cl = scols_table_new_column(tb, ci->name, ci->whint, ci->flags);
+ if (!cl)
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ if (json)
+ scols_column_set_json_type(cl, ci->json_type);
+ }
+
+ /* only one loopdev requested (already assigned to loopdev_cxt) */
+ if (loopcxt_get_device(lc)) {
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+ rc = set_scols_data(lc, ln);
+
+ /* list all loopdevs */
+ } else {
+ char *cn_file = NULL;
+
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+ if (rc)
+ goto done;
+ if (!file || stat(file, st))
+ st = NULL;
+
+ while (loopcxt_next(lc) == 0) {
+ if (file) {
+ int used;
+ const char *bf = cn_file ? cn_file : file;
+
+ used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
+ if (!used && !cn_file) {
+ bf = cn_file = canonicalize_path(file);
+ used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
+ }
+ if (!used)
+ continue;
+ }
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+ rc = set_scols_data(lc, ln);
+ if (rc)
+ break;
+ }
+
+ loopcxt_deinit_iterator(lc);
+ free(cn_file);
+ }
+done:
+ if (rc == 0)
+ rc = scols_print_table(tb);
+ scols_unref_table(tb);
+ return rc;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+
+ fprintf(out,
+ _(" %1$s [options] [<loopdev>]\n"
+ " %1$s [options] -f | <loopdev> <file>\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Set up and control loop devices.\n"), out);
+
+ /* commands */
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all list all used devices\n"), out);
+ fputs(_(" -d, --detach <loopdev>... detach one or more devices\n"), out);
+ fputs(_(" -D, --detach-all detach all used devices\n"), out);
+ fputs(_(" -f, --find find first unused device\n"), out);
+ fputs(_(" -c, --set-capacity <loopdev> resize the device\n"), out);
+ fputs(_(" -j, --associated <file> list all devices associated with <file>\n"), out);
+ fputs(_(" -L, --nooverlap avoid possible conflict between devices\n"), out);
+
+ /* commands options */
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -o, --offset <num> start at offset <num> into file\n"), out);
+ fputs(_(" --sizelimit <num> device is limited to <num> bytes of the file\n"), out);
+ fputs(_(" -b, --sector-size <num> set the logical sector size to <num>\n"), out);
+ fputs(_(" -P, --partscan create a partitioned loop device\n"), out);
+ fputs(_(" -r, --read-only set up a read-only loop device\n"), out);
+ fputs(_(" --direct-io[=<on|off>] open backing file with O_DIRECT\n"), out);
+ fputs(_(" --show print device name after setup (with -f)\n"), out);
+ fputs(_(" -v, --verbose verbose mode\n"), out);
+
+ /* output options */
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -J, --json use JSON --list output format\n"), out);
+ fputs(_(" -l, --list list info about all or specified (default)\n"), out);
+ fputs(_(" -n, --noheadings don't print headings for --list output\n"), out);
+ fputs(_(" -O, --output <cols> specify columns to output for --list\n"), out);
+ fputs(_(" --output-all output all columns\n"), out);
+ fputs(_(" --raw use raw --list output format\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(31));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %12s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("losetup(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static void warn_size(const char *filename, uint64_t size, uint64_t offset, int flags)
+{
+ struct stat st;
+
+ if (!size) {
+ if (stat(filename, &st) || S_ISBLK(st.st_mode))
+ return;
+ size = st.st_size;
+
+ if (flags & LOOPDEV_FL_OFFSET)
+ size -= offset;
+ }
+
+ if (size < 512)
+ warnx(_("%s: Warning: file is smaller than 512 bytes; the loop device "
+ "may be useless or invisible for system tools."),
+ filename);
+ else if (size % 512)
+ warnx(_("%s: Warning: file does not fit into a 512-byte sector; "
+ "the end of the file will be ignored."),
+ filename);
+}
+
+static int create_loop(struct loopdev_cxt *lc,
+ int nooverlap, int lo_flags, int flags,
+ const char *file, uint64_t offset, uint64_t sizelimit,
+ uint64_t blocksize)
+{
+ int hasdev = loopcxt_has_device(lc);
+ int rc = 0, ntries = 0;
+
+ /* losetup --find --noverlap file.img */
+ if (!hasdev && nooverlap) {
+ rc = loopcxt_find_overlap(lc, file, offset, sizelimit);
+ switch (rc) {
+ case 0: /* not found */
+ break;
+
+ case 1: /* overlap */
+ loopcxt_deinit(lc);
+ errx(EXIT_FAILURE, _("%s: overlapping loop device exists"), file);
+
+ case 2: /* overlap -- full size and offset match (reuse) */
+ {
+ uint32_t lc_encrypt_type;
+
+ /* Once a loop is initialized RO, there is no
+ * way to change its parameters. */
+ if (loopcxt_is_readonly(lc)
+ && !(lo_flags & LO_FLAGS_READ_ONLY)) {
+ loopcxt_deinit(lc);
+ errx(EXIT_FAILURE, _("%s: overlapping read-only loop device exists"), file);
+ }
+
+ /* This is no more supported, but check to be safe. */
+ if (loopcxt_get_encrypt_type(lc, &lc_encrypt_type) == 0
+ && lc_encrypt_type != LO_CRYPT_NONE) {
+ loopcxt_deinit(lc);
+ errx(EXIT_FAILURE, _("%s: overlapping encrypted loop device exists"), file);
+ }
+
+ lc->info.lo_flags &= ~LO_FLAGS_AUTOCLEAR;
+ if (loopcxt_ioctl_status(lc)) {
+ loopcxt_deinit(lc);
+ errx(EXIT_FAILURE, _("%s: failed to re-use loop device"), file);
+ }
+ return 0; /* success, re-use */
+ }
+ default: /* error */
+ loopcxt_deinit(lc);
+ errx(EXIT_FAILURE, _("failed to inspect loop devices"));
+ return -errno;
+ }
+ }
+
+ if (hasdev && !is_loopdev(loopcxt_get_device(lc)))
+ loopcxt_add_device(lc);
+
+ /* losetup --noverlap /dev/loopN file.img */
+ if (hasdev && nooverlap) {
+ struct loopdev_cxt lc2;
+
+ if (loopcxt_init(&lc2, 0)) {
+ loopcxt_deinit(lc);
+ err(EXIT_FAILURE, _("failed to initialize loopcxt"));
+ }
+ rc = loopcxt_find_overlap(&lc2, file, offset, sizelimit);
+ loopcxt_deinit(&lc2);
+
+ if (rc) {
+ loopcxt_deinit(lc);
+ if (rc > 0)
+ errx(EXIT_FAILURE, _("%s: overlapping loop device exists"), file);
+ err(EXIT_FAILURE, _("%s: failed to check for conflicting loop devices"), file);
+ }
+ }
+
+ /* Create a new device */
+ do {
+ const char *errpre;
+
+ /* Note that loopcxt_{find_unused,set_device}() resets
+ * loopcxt struct.
+ */
+ if (!hasdev && (rc = loopcxt_find_unused(lc))) {
+ warnx(_("cannot find an unused loop device"));
+ break;
+ }
+ if (flags & LOOPDEV_FL_OFFSET)
+ loopcxt_set_offset(lc, offset);
+ if (flags & LOOPDEV_FL_SIZELIMIT)
+ loopcxt_set_sizelimit(lc, sizelimit);
+ if (lo_flags)
+ loopcxt_set_flags(lc, lo_flags);
+ if (blocksize > 0)
+ loopcxt_set_blocksize(lc, blocksize);
+
+ if ((rc = loopcxt_set_backing_file(lc, file))) {
+ warn(_("%s: failed to use backing file"), file);
+ break;
+ }
+ errno = 0;
+ rc = loopcxt_setup_device(lc);
+ if (rc == 0)
+ break; /* success */
+
+ if (errno == EBUSY && !hasdev && ntries < 64) {
+ xusleep(200000);
+ ntries++;
+ continue;
+ }
+
+ /* errors */
+ errpre = hasdev && loopcxt_get_fd(lc) < 0 ?
+ loopcxt_get_device(lc) : file;
+ warn(_("%s: failed to set up loop device"), errpre);
+ break;
+ } while (hasdev == 0);
+
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ struct loopdev_cxt lc;
+ int act = 0, flags = 0, no_overlap = 0, c;
+ char *file = NULL;
+ uint64_t offset = 0, sizelimit = 0, blocksize = 0;
+ int res = 0, showdev = 0, lo_flags = 0;
+ char *outarg = NULL;
+ int list = 0;
+ unsigned long use_dio = 0, set_dio = 0, set_blocksize = 0;
+
+ enum {
+ OPT_SIZELIMIT = CHAR_MAX + 1,
+ OPT_SHOW,
+ OPT_RAW,
+ OPT_DIO,
+ OPT_OUTPUT_ALL
+ };
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "set-capacity", required_argument, NULL, 'c' },
+ { "detach", required_argument, NULL, 'd' },
+ { "detach-all", no_argument, NULL, 'D' },
+ { "find", no_argument, NULL, 'f' },
+ { "nooverlap", no_argument, NULL, 'L' },
+ { "help", no_argument, NULL, 'h' },
+ { "associated", required_argument, NULL, 'j' },
+ { "json", no_argument, NULL, 'J' },
+ { "list", no_argument, NULL, 'l' },
+ { "sector-size", required_argument, NULL, 'b' },
+ { "noheadings", no_argument, NULL, 'n' },
+ { "offset", required_argument, NULL, 'o' },
+ { "output", required_argument, NULL, 'O' },
+ { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
+ { "sizelimit", required_argument, NULL, OPT_SIZELIMIT },
+ { "partscan", no_argument, NULL, 'P' },
+ { "read-only", no_argument, NULL, 'r' },
+ { "direct-io", optional_argument, NULL, OPT_DIO },
+ { "raw", no_argument, NULL, OPT_RAW },
+ { "show", no_argument, NULL, OPT_SHOW },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'D','a','c','d','f','j' },
+ { 'D','c','d','f','l' },
+ { 'D','c','d','f','O' },
+ { 'J',OPT_RAW },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ if (loopcxt_init(&lc, 0))
+ err(EXIT_FAILURE, _("failed to initialize loopcxt"));
+
+ while ((c = getopt_long(argc, argv, "ab:c:d:Dfhj:JlLno:O:PrvV",
+ longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'a':
+ act = A_SHOW;
+ break;
+ case 'b':
+ set_blocksize = 1;
+ blocksize = strtosize_or_err(optarg, _("failed to parse logical block size"));
+ break;
+ case 'c':
+ act = A_SET_CAPACITY;
+ if (!is_loopdev(optarg) ||
+ loopcxt_set_device(&lc, optarg))
+ err(EXIT_FAILURE, _("%s: failed to use device"),
+ optarg);
+ break;
+ case 'r':
+ lo_flags |= LO_FLAGS_READ_ONLY;
+ break;
+ case 'd':
+ act = A_DELETE;
+ if (!is_loopdev(optarg) ||
+ loopcxt_set_device(&lc, optarg))
+ err(EXIT_FAILURE, _("%s: failed to use device"),
+ optarg);
+ break;
+ case 'D':
+ act = A_DELETE_ALL;
+ break;
+ case 'f':
+ act = A_FIND_FREE;
+ break;
+ case 'J':
+ json = 1;
+ break;
+ case 'j':
+ act = A_SHOW;
+ file = optarg;
+ break;
+ case 'l':
+ list = 1;
+ break;
+ case 'L':
+ no_overlap = 1;
+ break;
+ case 'n':
+ no_headings = 1;
+ break;
+ case OPT_RAW:
+ raw = 1;
+ break;
+ case 'o':
+ offset = strtosize_or_err(optarg, _("failed to parse offset"));
+ flags |= LOOPDEV_FL_OFFSET;
+ break;
+ case 'O':
+ outarg = optarg;
+ list = 1;
+ break;
+ case OPT_OUTPUT_ALL:
+ for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ break;
+ case 'P':
+ lo_flags |= LO_FLAGS_PARTSCAN;
+ break;
+ case OPT_SHOW:
+ showdev = 1;
+ break;
+ case OPT_DIO:
+ use_dio = set_dio = 1;
+ if (optarg)
+ use_dio = parse_switch(optarg, _("argument error"), "on", "off", NULL);
+ break;
+ case 'v':
+ break;
+ case OPT_SIZELIMIT: /* --sizelimit */
+ sizelimit = strtosize_or_err(optarg, _("failed to parse size"));
+ flags |= LOOPDEV_FL_SIZELIMIT;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ ul_path_init_debug();
+ ul_sysfs_init_debug();
+
+ /* default is --list --all */
+ if (argc == 1) {
+ act = A_SHOW;
+ list = 1;
+ }
+
+ if (!act && argc == 2 && (raw || json)) {
+ act = A_SHOW;
+ list = 1;
+ }
+
+ /* default --list output columns */
+ if (list && !ncolumns) {
+ columns[ncolumns++] = COL_NAME;
+ columns[ncolumns++] = COL_SIZELIMIT;
+ columns[ncolumns++] = COL_OFFSET;
+ columns[ncolumns++] = COL_AUTOCLR;
+ columns[ncolumns++] = COL_RO;
+ columns[ncolumns++] = COL_BACK_FILE;
+ columns[ncolumns++] = COL_DIO;
+ columns[ncolumns++] = COL_LOGSEC;
+ }
+
+ if (act == A_FIND_FREE && optind < argc) {
+ /*
+ * losetup -f <backing_file>
+ */
+ act = A_CREATE;
+ file = argv[optind++];
+
+ if (optind < argc)
+ errx(EXIT_FAILURE, _("unexpected arguments"));
+ }
+
+ if (list && !act && optind == argc)
+ /*
+ * losetup --list defaults to --all
+ */
+ act = A_SHOW;
+
+ if (!act && optind + 1 == argc) {
+ /*
+ * losetup [--list] <device>
+ * OR
+ * losetup {--direct-io[=off]|--logical-blocksize=size}... <device>
+ */
+ if (!(set_dio || set_blocksize))
+ act = A_SHOW_ONE;
+ if (set_dio)
+ act = A_SET_DIRECT_IO;
+ if (set_blocksize)
+ act = A_SET_BLOCKSIZE;
+ if (!is_loopdev(argv[optind]) ||
+ loopcxt_set_device(&lc, argv[optind]))
+ err(EXIT_FAILURE, _("%s: failed to use device"),
+ argv[optind]);
+ optind++;
+ }
+ if (!act) {
+ /*
+ * losetup <loopdev> <backing_file>
+ */
+ act = A_CREATE;
+
+ if (optind >= argc)
+ errx(EXIT_FAILURE, _("no loop device specified"));
+ /* don't use is_loopdev() here, the device does not have exist yet */
+ if (loopcxt_set_device(&lc, argv[optind]))
+ err(EXIT_FAILURE, _("%s: failed to use device"),
+ argv[optind]);
+ optind++;
+
+ if (optind >= argc)
+ errx(EXIT_FAILURE, _("no file specified"));
+ file = argv[optind++];
+ }
+
+ if (act != A_CREATE &&
+ (sizelimit || lo_flags || showdev))
+ errx(EXIT_FAILURE,
+ _("the options %s are allowed during loop device setup only"),
+ "--{sizelimit,partscan,read-only,show}");
+
+ if ((flags & LOOPDEV_FL_OFFSET) &&
+ act != A_CREATE && (act != A_SHOW || !file))
+ errx(EXIT_FAILURE, _("the option --offset is not allowed in this context"));
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ switch (act) {
+ case A_CREATE:
+ res = create_loop(&lc, no_overlap, lo_flags, flags, file,
+ offset, sizelimit, blocksize);
+ if (res == 0) {
+ if (showdev)
+ printf("%s\n", loopcxt_get_device(&lc));
+ warn_size(file, sizelimit, offset, flags);
+ if (set_dio)
+ goto lo_set_dio;
+ }
+ break;
+ case A_DELETE:
+ res = delete_loop(&lc);
+ while (optind < argc) {
+ if (!is_loopdev(argv[optind]) ||
+ loopcxt_set_device(&lc, argv[optind]))
+ warn(_("%s: failed to use device"),
+ argv[optind]);
+ optind++;
+ res += delete_loop(&lc);
+ }
+ break;
+ case A_DELETE_ALL:
+ res = delete_all_loops(&lc);
+ break;
+ case A_FIND_FREE:
+ res = loopcxt_find_unused(&lc);
+ if (res) {
+ int errsv = errno;
+
+ if (access(_PATH_DEV_LOOPCTL, F_OK) == 0 &&
+ access(_PATH_DEV_LOOPCTL, W_OK) != 0)
+ ;
+ else
+ errno = errsv;
+
+ warn(_("cannot find an unused loop device"));
+ } else
+ printf("%s\n", loopcxt_get_device(&lc));
+ break;
+ case A_SHOW:
+ if (list)
+ res = show_table(&lc, file, offset, flags);
+ else
+ res = show_all_loops(&lc, file, offset, flags);
+ break;
+ case A_SHOW_ONE:
+ if (list)
+ res = show_table(&lc, NULL, 0, 0);
+ else
+ res = printf_loopdev(&lc);
+ if (res)
+ warn("%s", loopcxt_get_device(&lc));
+ break;
+ case A_SET_CAPACITY:
+ res = loopcxt_ioctl_capacity(&lc);
+ if (res)
+ warn(_("%s: set capacity failed"),
+ loopcxt_get_device(&lc));
+ break;
+ case A_SET_DIRECT_IO:
+lo_set_dio:
+ res = loopcxt_ioctl_dio(&lc, use_dio);
+ if (res)
+ warn(_("%s: set direct io failed"),
+ loopcxt_get_device(&lc));
+ break;
+ case A_SET_BLOCKSIZE:
+ res = loopcxt_ioctl_blocksize(&lc, blocksize);
+ if (res)
+ warn(_("%s: set logical block size failed"),
+ loopcxt_get_device(&lc));
+ break;
+ default:
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ break;
+ }
+
+ loopcxt_deinit(&lc);
+ return res ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
diff --git a/sys-utils/lscpu-arm.c b/sys-utils/lscpu-arm.c
new file mode 100644
index 0000000..5569992
--- /dev/null
+++ b/sys-utils/lscpu-arm.c
@@ -0,0 +1,274 @@
+/*
+ * lscpu-arm.c - ARM CPU identification tables
+ *
+ * Copyright (C) 2018 Riku Voipio <riku.voipio@iki.fi>
+ *
+ * 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 would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * The information here is gathered from
+ * - ARM manuals
+ * - Linux kernel: arch/armX/include/asm/cputype.h
+ * - GCC sources: config/arch/arch-cores.def
+ * - Ancient wisdom
+ */
+#include "lscpu.h"
+
+struct id_part {
+ const int id;
+ const char* name;
+};
+
+static const struct id_part arm_part[] = {
+ { 0x810, "ARM810" },
+ { 0x920, "ARM920" },
+ { 0x922, "ARM922" },
+ { 0x926, "ARM926" },
+ { 0x940, "ARM940" },
+ { 0x946, "ARM946" },
+ { 0x966, "ARM966" },
+ { 0xa20, "ARM1020" },
+ { 0xa22, "ARM1022" },
+ { 0xa26, "ARM1026" },
+ { 0xb02, "ARM11 MPCore" },
+ { 0xb36, "ARM1136" },
+ { 0xb56, "ARM1156" },
+ { 0xb76, "ARM1176" },
+ { 0xc05, "Cortex-A5" },
+ { 0xc07, "Cortex-A7" },
+ { 0xc08, "Cortex-A8" },
+ { 0xc09, "Cortex-A9" },
+ { 0xc0d, "Cortex-A17" }, /* Originally A12 */
+ { 0xc0f, "Cortex-A15" },
+ { 0xc0e, "Cortex-A17" },
+ { 0xc14, "Cortex-R4" },
+ { 0xc15, "Cortex-R5" },
+ { 0xc17, "Cortex-R7" },
+ { 0xc18, "Cortex-R8" },
+ { 0xc20, "Cortex-M0" },
+ { 0xc21, "Cortex-M1" },
+ { 0xc23, "Cortex-M3" },
+ { 0xc24, "Cortex-M4" },
+ { 0xc27, "Cortex-M7" },
+ { 0xc60, "Cortex-M0+" },
+ { 0xd01, "Cortex-A32" },
+ { 0xd03, "Cortex-A53" },
+ { 0xd04, "Cortex-A35" },
+ { 0xd05, "Cortex-A55" },
+ { 0xd06, "Cortex-A65" },
+ { 0xd07, "Cortex-A57" },
+ { 0xd08, "Cortex-A72" },
+ { 0xd09, "Cortex-A73" },
+ { 0xd0a, "Cortex-A75" },
+ { 0xd0b, "Cortex-A76" },
+ { 0xd0c, "Neoverse-N1" },
+ { 0xd0d, "Cortex-A77" },
+ { 0xd0e, "Cortex-A76AE" },
+ { 0xd13, "Cortex-R52" },
+ { 0xd20, "Cortex-M23" },
+ { 0xd21, "Cortex-M33" },
+ { 0xd41, "Cortex-A78" },
+ { 0xd42, "Cortex-A78AE" },
+ { 0xd4a, "Neoverse-E1" },
+ { 0xd4b, "Cortex-A78C" },
+ { -1, "unknown" },
+};
+
+static const struct id_part brcm_part[] = {
+ { 0x0f, "Brahma B15" },
+ { 0x100, "Brahma B53" },
+ { 0x516, "ThunderX2" },
+ { -1, "unknown" },
+};
+
+static const struct id_part dec_part[] = {
+ { 0xa10, "SA110" },
+ { 0xa11, "SA1100" },
+ { -1, "unknown" },
+};
+
+static const struct id_part cavium_part[] = {
+ { 0x0a0, "ThunderX" },
+ { 0x0a1, "ThunderX 88XX" },
+ { 0x0a2, "ThunderX 81XX" },
+ { 0x0a3, "ThunderX 83XX" },
+ { 0x0af, "ThunderX2 99xx" },
+ { -1, "unknown" },
+};
+
+static const struct id_part apm_part[] = {
+ { 0x000, "X-Gene" },
+ { -1, "unknown" },
+};
+
+static const struct id_part qcom_part[] = {
+ { 0x00f, "Scorpion" },
+ { 0x02d, "Scorpion" },
+ { 0x04d, "Krait" },
+ { 0x06f, "Krait" },
+ { 0x201, "Kryo" },
+ { 0x205, "Kryo" },
+ { 0x211, "Kryo" },
+ { 0x800, "Falkor V1/Kryo" },
+ { 0x801, "Kryo V2" },
+ { 0xc00, "Falkor" },
+ { 0xc01, "Saphira" },
+ { -1, "unknown" },
+};
+
+static const struct id_part samsung_part[] = {
+ { 0x001, "exynos-m1" },
+ { -1, "unknown" },
+};
+
+static const struct id_part nvidia_part[] = {
+ { 0x000, "Denver" },
+ { 0x003, "Denver 2" },
+ { 0x004, "Carmel" },
+ { -1, "unknown" },
+};
+
+static const struct id_part marvell_part[] = {
+ { 0x131, "Feroceon 88FR131" },
+ { 0x581, "PJ4/PJ4b" },
+ { 0x584, "PJ4B-MP" },
+ { -1, "unknown" },
+};
+
+static const struct id_part faraday_part[] = {
+ { 0x526, "FA526" },
+ { 0x626, "FA626" },
+ { -1, "unknown" },
+};
+
+static const struct id_part intel_part[] = {
+ { 0x200, "i80200" },
+ { 0x210, "PXA250A" },
+ { 0x212, "PXA210A" },
+ { 0x242, "i80321-400" },
+ { 0x243, "i80321-600" },
+ { 0x290, "PXA250B/PXA26x" },
+ { 0x292, "PXA210B" },
+ { 0x2c2, "i80321-400-B0" },
+ { 0x2c3, "i80321-600-B0" },
+ { 0x2d0, "PXA250C/PXA255/PXA26x" },
+ { 0x2d2, "PXA210C" },
+ { 0x411, "PXA27x" },
+ { 0x41c, "IPX425-533" },
+ { 0x41d, "IPX425-400" },
+ { 0x41f, "IPX425-266" },
+ { 0x682, "PXA32x" },
+ { 0x683, "PXA930/PXA935" },
+ { 0x688, "PXA30x" },
+ { 0x689, "PXA31x" },
+ { 0xb11, "SA1110" },
+ { 0xc12, "IPX1200" },
+ { -1, "unknown" },
+};
+
+static const struct id_part fujitsu_part[] = {
+ { 0x001, "A64FX" },
+ { -1, "unknown" },
+};
+
+static const struct id_part hisi_part[] = {
+ { 0xd01, "Kunpeng-920" }, /* aka tsv110 */
+ { -1, "unknown" },
+};
+
+static const struct id_part unknown_part[] = {
+ { -1, "unknown" },
+};
+
+struct hw_impl {
+ const int id;
+ const struct id_part *parts;
+ const char *name;
+};
+
+static const struct hw_impl hw_implementer[] = {
+ { 0x41, arm_part, "ARM" },
+ { 0x42, brcm_part, "Broadcom" },
+ { 0x43, cavium_part, "Cavium" },
+ { 0x44, dec_part, "DEC" },
+ { 0x46, fujitsu_part, "FUJITSU" },
+ { 0x48, hisi_part, "HiSilicon" },
+ { 0x4e, nvidia_part, "Nvidia" },
+ { 0x50, apm_part, "APM" },
+ { 0x51, qcom_part, "Qualcomm" },
+ { 0x53, samsung_part, "Samsung" },
+ { 0x56, marvell_part, "Marvell" },
+ { 0x66, faraday_part, "Faraday" },
+ { 0x69, intel_part, "Intel" },
+ { -1, unknown_part, "unknown" },
+};
+
+void arm_cpu_decode(struct lscpu_desc *desc)
+{
+ int j, impl, part;
+ const struct id_part *parts = NULL;
+ char *end;
+
+ if (desc->vendor == NULL || desc->model == NULL)
+ return;
+ if ((strncmp(desc->vendor,"0x",2) != 0 || strncmp(desc->model,"0x",2) ))
+ return;
+
+ errno = 0;
+ impl = (int) strtol(desc->vendor, &end, 0);
+ if (errno || desc->vendor == end)
+ return;
+
+ errno = 0;
+ part = (int) strtol(desc->model, &end, 0);
+ if (errno || desc->model == end)
+ return;
+
+ for (j = 0; hw_implementer[j].id != -1; j++) {
+ if (hw_implementer[j].id == impl) {
+ parts = hw_implementer[j].parts;
+ desc->vendor = (char *) hw_implementer[j].name;
+ break;
+ }
+ }
+
+ if (parts == NULL)
+ return;
+
+ for (j = 0; parts[j].id != -1; j++) {
+ if (parts[j].id == part) {
+ desc->modelname = (char *) parts[j].name;
+ break;
+ }
+ }
+
+ /* Print out the rXpY string for ARM cores */
+ if (impl == 0x41 && desc->revision && desc->stepping) {
+ int revision, variant;
+ char buf[8];
+
+ errno = 0;
+ revision = (int) strtol(desc->revision, &end, 10);
+ if (errno || desc->revision == end)
+ return;
+
+ errno = 0;
+ variant = (int) strtol(desc->stepping, &end, 0);
+ if (errno || desc->stepping == end)
+ return;
+
+ snprintf(buf, sizeof(buf), "r%dp%d", variant, revision);
+ desc->stepping = xstrdup(buf);
+ }
+}
diff --git a/sys-utils/lscpu-dmi.c b/sys-utils/lscpu-dmi.c
new file mode 100644
index 0000000..edf0f31
--- /dev/null
+++ b/sys-utils/lscpu-dmi.c
@@ -0,0 +1,312 @@
+/*
+ * lscpu-dmi - Module to parse SMBIOS information
+ *
+ * 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 would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Code originally taken from the dmidecode utility and slightly rewritten
+ * to suite the needs of lscpu
+ */
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "lscpu.h"
+
+#define _PATH_SYS_DMI "/sys/firmware/dmi/tables/DMI"
+
+#define WORD(x) (uint16_t)(*(const uint16_t *)(x))
+#define DWORD(x) (uint32_t)(*(const uint32_t *)(x))
+
+struct dmi_header
+{
+ uint8_t type;
+ uint8_t length;
+ uint16_t handle;
+ uint8_t *data;
+};
+
+static void *get_mem_chunk(size_t base, size_t len, const char *devmem)
+{
+ void *p = NULL;
+ int fd;
+
+ if ((fd = open(devmem, O_RDONLY)) < 0)
+ return NULL;
+
+ if (!(p = malloc(len)))
+ goto nothing;
+ if (lseek(fd, base, SEEK_SET) == -1)
+ goto nothing;
+ if (read_all(fd, p, len) == -1)
+ goto nothing;
+
+ close(fd);
+ return p;
+
+nothing:
+ free(p);
+ close(fd);
+ return NULL;
+}
+
+static void to_dmi_header(struct dmi_header *h, uint8_t *data)
+{
+ h->type = data[0];
+ h->length = data[1];
+ memcpy(&h->handle, data + 2, sizeof(h->handle));
+ h->data = data;
+}
+
+static char *dmi_string(const struct dmi_header *dm, uint8_t s)
+{
+ char *bp = (char *)dm->data;
+
+ if (s == 0)
+ return NULL;
+
+ bp += dm->length;
+ while (s > 1 && *bp)
+ {
+ bp += strlen(bp);
+ bp++;
+ s--;
+ }
+
+ if (!*bp)
+ return NULL;
+
+ return bp;
+}
+
+static int hypervisor_from_dmi_table(uint32_t base, uint16_t len,
+ uint16_t num, const char *devmem)
+{
+ uint8_t *buf;
+ uint8_t *data;
+ int i = 0;
+ char *vendor = NULL;
+ char *product = NULL;
+ char *manufacturer = NULL;
+ int rc = HYPER_NONE;
+
+ data = buf = get_mem_chunk(base, len, devmem);
+ if (!buf)
+ goto done;
+
+ /* 4 is the length of an SMBIOS structure header */
+ while (i < num && data + 4 <= buf + len) {
+ uint8_t *next;
+ struct dmi_header h;
+
+ to_dmi_header(&h, data);
+
+ /*
+ * If a short entry is found (less than 4 bytes), not only it
+ * is invalid, but we cannot reliably locate the next entry.
+ * Better stop at this point.
+ */
+ if (h.length < 4)
+ goto done;
+
+ /* look for the next handle */
+ next = data + h.length;
+ while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0))
+ next++;
+ next += 2;
+ switch (h.type) {
+ case 0:
+ vendor = dmi_string(&h, data[0x04]);
+ break;
+ case 1:
+ manufacturer = dmi_string(&h, data[0x04]);
+ product = dmi_string(&h, data[0x05]);
+ break;
+ default:
+ break;
+ }
+
+ data = next;
+ i++;
+ }
+ if (manufacturer && !strcmp(manufacturer, "innotek GmbH"))
+ rc = HYPER_INNOTEK;
+ else if (manufacturer && strstr(manufacturer, "HITACHI") &&
+ product && strstr(product, "LPAR"))
+ rc = HYPER_HITACHI;
+ else if (vendor && !strcmp(vendor, "Parallels"))
+ rc = HYPER_PARALLELS;
+done:
+ free(buf);
+ return rc;
+}
+
+static int checksum(const uint8_t *buf, size_t len)
+{
+ uint8_t sum = 0;
+ size_t a;
+
+ for (a = 0; a < len; a++)
+ sum += buf[a];
+ return (sum == 0);
+}
+
+#if defined(__x86_64__) || defined(__i386__)
+static int hypervisor_decode_legacy(uint8_t *buf, const char *devmem)
+{
+ if (!checksum(buf, 0x0F))
+ return -1;
+
+ return hypervisor_from_dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06),
+ WORD(buf + 0x0C),
+ devmem);
+}
+#endif
+
+static int hypervisor_decode_smbios(uint8_t *buf, const char *devmem)
+{
+ if (!checksum(buf, buf[0x05])
+ || memcmp(buf + 0x10, "_DMI_", 5) != 0
+ || !checksum(buf + 0x10, 0x0F))
+ return -1;
+
+ return hypervisor_from_dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16),
+ WORD(buf + 0x1C),
+ devmem);
+}
+
+/*
+ * Probe for EFI interface
+ */
+#define EFI_NOT_FOUND (-1)
+#define EFI_NO_SMBIOS (-2)
+static int address_from_efi(size_t *address)
+{
+ FILE *tab;
+ char linebuf[64];
+ int ret;
+
+ *address = 0; /* Prevent compiler warning */
+
+ /*
+ * Linux up to 2.6.6: /proc/efi/systab
+ * Linux 2.6.7 and up: /sys/firmware/efi/systab
+ */
+ if (!(tab = fopen("/sys/firmware/efi/systab", "r")) &&
+ !(tab = fopen("/proc/efi/systab", "r")))
+ return EFI_NOT_FOUND; /* No EFI interface */
+
+ ret = EFI_NO_SMBIOS;
+ while ((fgets(linebuf, sizeof(linebuf) - 1, tab)) != NULL) {
+ char *addrp = strchr(linebuf, '=');
+ if (!addrp)
+ continue;
+ *(addrp++) = '\0';
+ if (strcmp(linebuf, "SMBIOS") == 0) {
+ *address = strtoul(addrp, NULL, 0);
+ ret = 0;
+ break;
+ }
+ }
+
+ fclose(tab);
+ return ret;
+}
+
+static int read_hypervisor_dmi_from_devmem(void)
+{
+ int rc = HYPER_NONE;
+ uint8_t *buf = NULL;
+ size_t fp = 0;
+
+ /* First try EFI (ia64, Intel-based Mac) */
+ switch (address_from_efi(&fp)) {
+ case EFI_NOT_FOUND:
+ goto memory_scan;
+ case EFI_NO_SMBIOS:
+ goto done;
+ }
+
+ buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM);
+ if (!buf)
+ goto done;
+
+ rc = hypervisor_decode_smbios(buf, _PATH_DEV_MEM);
+ if (rc >= HYPER_NONE)
+ goto done;
+
+ free(buf);
+ buf = NULL;
+memory_scan:
+#if defined(__x86_64__) || defined(__i386__)
+ /* Fallback to memory scan (x86, x86_64) */
+ buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM);
+ if (!buf)
+ goto done;
+
+ for (fp = 0; fp <= 0xFFF0; fp += 16) {
+ if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) {
+ rc = hypervisor_decode_smbios(buf + fp, _PATH_DEV_MEM);
+ if (rc < 0)
+ fp += 16;
+
+ } else if (memcmp(buf + fp, "_DMI_", 5) == 0)
+ rc = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM);
+
+ if (rc >= HYPER_NONE)
+ break;
+ }
+#endif
+done:
+ free(buf);
+ return rc;
+}
+
+static int read_hypervisor_dmi_from_sysfw(void)
+{
+ static char const sys_fw_dmi_tables[] = _PATH_SYS_DMI;
+ struct stat st;
+
+ if (stat(sys_fw_dmi_tables, &st))
+ return -1;
+
+ return hypervisor_from_dmi_table(0, st.st_size, st.st_size / 4,
+ sys_fw_dmi_tables);
+}
+
+int read_hypervisor_dmi(void)
+{
+ int rc;
+
+ if (sizeof(uint8_t) != 1
+ || sizeof(uint16_t) != 2
+ || sizeof(uint32_t) != 4
+ || '\0' != 0)
+ return HYPER_NONE;
+
+ /* -1 : no DMI in /sys,
+ * 0 : DMI exist, nothing detected (HYPER_NONE)
+ * >0 : hypervisor detected
+ */
+ rc = read_hypervisor_dmi_from_sysfw();
+ if (rc < 0)
+ rc = read_hypervisor_dmi_from_devmem();
+
+ return rc < 0 ? HYPER_NONE : rc;
+}
diff --git a/sys-utils/lscpu.1 b/sys-utils/lscpu.1
new file mode 100644
index 0000000..1ef6ce0
--- /dev/null
+++ b/sys-utils/lscpu.1
@@ -0,0 +1,204 @@
+.TH LSCPU 1 "March 2019" "util-linux" "User Commands"
+.SH NAME
+lscpu \- display information about the CPU architecture
+.SH SYNOPSIS
+.B lscpu
+[options]
+.SH DESCRIPTION
+.B lscpu
+gathers CPU architecture information from sysfs, /proc/cpuinfo and any
+applicable architecture-specific libraries (e.g.\& librtas on Powerpc). The
+command output can be optimized for parsing or for easy readability by humans.
+The information includes, for example, the number of CPUs, threads, cores,
+sockets, and Non-Uniform Memory Access (NUMA) nodes. There is also information
+about the CPU caches and cache sharing, family, model, bogoMIPS, byte order,
+and stepping.
+.sp
+In virtualized environments, the CPU architecture information displayed
+reflects the configuration of the guest operating system which is
+typically different from the physical (host) system. On architectures that
+support retrieving physical topology information,
+.B lscpu
+also displays the number of physical sockets, chips, cores in the host system.
+.sp
+Options that result in an output table have a \fIlist\fP argument. Use this
+argument to customize the command output. Specify a comma-separated list of
+column labels to limit the output table to only the specified columns, arranged
+in the specified order. See \fBCOLUMNS\fP for a list of valid column labels. The
+column labels are not case sensitive.
+.sp
+Not all columns are supported on all architectures. If an unsupported column is
+specified, \fBlscpu\fP prints the column but does not provide any data for it.
+.sp
+The default output formatting on terminal maybe optimized for better
+readability. The output for non-terminals (e.g., pipes) is never affected by
+this optimization and it is always in "Field: data\\n" format.
+.sp
+The cache sizes are reported as summary from all CPUs. The versions before
+v2.34 reported per-core sizes, but this output was confusing due to complicated
+CPUs topology and the way how caches are shared between CPUs. For more details
+about caches see \fB\-\-cache\fP.
+.SS COLUMNS
+Note that topology elements (core, socket, etc.) use a sequential unique ID
+starting from zero, but CPU logical numbers follow the kernel where there is
+no guarantee of sequential numbering.
+.TP
+.B CPU
+The logical CPU number of a CPU as used by the Linux kernel.
+.TP
+.B CORE
+The logical core number. A core can contain several CPUs.
+.TP
+.B SOCKET
+The logical socket number. A socket can contain several cores.
+.TP
+.B BOOK
+The logical book number. A book can contain several sockets.
+.TP
+.B DRAWER
+The logical drawer number. A drawer can contain several books.
+.TP
+.B NODE
+The logical NUMA node number. A node can contain several drawers.
+.TP
+.B CACHE
+Information about how caches are shared between CPUs.
+.TP
+.B ADDRESS
+The physical address of a CPU.
+.TP
+.B ONLINE
+Indicator that shows whether the Linux instance currently makes use of the CPU.
+.TP
+.B CONFIGURED
+Indicator that shows if the hypervisor has allocated the CPU to the virtual
+hardware on which the Linux instance runs. CPUs that are configured can be set
+online by the Linux instance.
+This column contains data only if your hardware system and hypervisor support
+dynamic CPU resource allocation.
+.TP
+.B POLARIZATION
+This column contains data for Linux instances that run on virtual hardware with
+a hypervisor that can switch the CPU dispatching mode (polarization). The
+polarization can be:
+.RS
+.TP 12
+.B horizontal\fP
+The workload is spread across all available CPUs.
+.TP 12
+.B vertical
+The workload is concentrated on few CPUs.
+.P
+For vertical polarization, the column also shows the degree of concentration,
+high, medium, or low. This column contains data only if your hardware system
+and hypervisor support CPU polarization.
+.RE
+.TP
+.B MAXMHZ
+Maximum megahertz value for the CPU. Useful when \fBlscpu\fP is used as hardware
+inventory information gathering tool. Notice that the megahertz value is
+dynamic, and driven by CPU governor depending on current resource need.
+.TP
+.B MINMHZ
+Minimum megahertz value for the CPU.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-all"
+Include lines for online and offline CPUs in the output (default for \fB\-e\fR).
+This option may only be specified together with option \fB\-e\fR or \fB\-p\fR.
+.TP
+.BR \-B , " \-\-bytes"
+Print the sizes in bytes rather than in a human-readable format.
+.TP
+.BR \-b , " \-\-online"
+Limit the output to online CPUs (default for \fB\-p\fR).
+This option may only be specified together with option \fB\-e\fR or \fB\-p\fR.
+.TP
+.BR \-C , " \-\-caches" [=\fIlist\fP]
+Display details about CPU caches. For details about available information see \fB\-\-help\fR
+output.
+
+If the \fIlist\fP argument is omitted, all columns for which data is available
+are included in the command output.
+
+When specifying the \fIlist\fP argument, the string of option, equal sign (=), and
+\fIlist\fP must not contain any blanks or other whitespace.
+Examples: '\fB\-C=NAME,ONE-SIZE\fP' or '\fB\-\-caches=NAME,ONE-SIZE\fP'.
+.TP
+.BR \-c , " \-\-offline"
+Limit the output to offline CPUs.
+This option may only be specified together with option \fB\-e\fR or \fB\-p\fR.
+.TP
+.BR \-e , " \-\-extended" [=\fIlist\fP]
+Display the CPU information in human-readable format.
+
+If the \fIlist\fP argument is omitted, all columns for which data is available
+are included in the command output.
+
+When specifying the \fIlist\fP argument, the string of option, equal sign (=), and
+\fIlist\fP must not contain any blanks or other whitespace.
+Examples: '\fB\-e=cpu,node\fP' or '\fB\-\-extended=cpu,node\fP'.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format for the default summary or extended output (see \fB\-\-extended\fP).
+.TP
+.BR \-p , " \-\-parse" [=\fIlist\fP]
+Optimize the command output for easy parsing.
+
+If the \fIlist\fP argument is omitted, the command output is compatible with earlier
+versions of \fBlscpu\fP. In this compatible format, two commas are used to separate
+CPU cache columns. If no CPU caches are identified the cache column is omitted.
+.br
+If the \fIlist\fP argument is used, cache columns are separated with a colon (:).
+
+When specifying the \fIlist\fP argument, the string of option, equal sign (=), and
+\fIlist\fP must not contain any blanks or other whitespace.
+Examples: '\fB\-p=cpu,node\fP' or '\fB\-\-parse=cpu,node\fP'.
+.TP
+.BR \-s , " \-\-sysroot " \fIdirectory\fP
+Gather CPU data for a Linux instance other than the instance from which the
+\fBlscpu\fP command is issued. The specified \fIdirectory\fP is the system root
+of the Linux instance to be inspected.
+.TP
+.BR \-x , " \-\-hex"
+Use hexadecimal masks for CPU sets (for example "ff"). The default is to print
+the sets in list format (for example 0,1). Note that before version 2.30 the mask
+has been printed with 0x prefix.
+.TP
+.BR \-y , " \-\-physical"
+Display physical IDs for all columns with topology elements (core, socket, etc.).
+Other than logical IDs, which are assigned by \fBlscpu\fP, physical IDs are
+platform-specific values that are provided by the kernel. Physical IDs are not
+necessarily unique and they might not be arranged sequentially.
+If the kernel could not retrieve a physical ID for an element \fBlscpu\fP prints
+the dash (-) character.
+
+The CPU logical numbers are not affected by this option.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.B \-\-output\-all
+Output all available columns. This option must be combined with either
+.BR \-\-extended ", " \-\-parse " or " \-\-caches .
+.SH BUGS
+The basic overview of CPU family, model, etc. is always based on the first
+CPU only.
+
+Sometimes in Xen Dom0 the kernel reports wrong data.
+
+On virtual hardware the number of cores per socket, etc. can be wrong.
+.SH AUTHORS
+.nf
+Cai Qian <qcai@redhat.com>
+Karel Zak <kzak@redhat.com>
+Heiko Carstens <heiko.carstens@de.ibm.com>
+.fi
+.SH SEE ALSO
+.BR chcpu (8)
+.SH AVAILABILITY
+The lscpu command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/lscpu.c b/sys-utils/lscpu.c
new file mode 100644
index 0000000..90c475a
--- /dev/null
+++ b/sys-utils/lscpu.c
@@ -0,0 +1,2545 @@
+/*
+ * lscpu - CPU architecture information helper
+ *
+ * Copyright (C) 2008 Cai Qian <qcai@redhat.com>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/personality.h>
+
+#if (defined(__x86_64__) || defined(__i386__))
+# define INCLUDE_VMWARE_BDOOR
+#endif
+
+#ifdef INCLUDE_VMWARE_BDOOR
+# include <stdint.h>
+# include <signal.h>
+# include <strings.h>
+# include <setjmp.h>
+# ifdef HAVE_SYS_IO_H
+# include <sys/io.h>
+# endif
+#endif
+
+#if defined(HAVE_LIBRTAS)
+#include <librtas.h>
+#endif
+
+#include <libsmartcols.h>
+
+#include "closestream.h"
+#include "optutils.h"
+#include "fileutils.h"
+
+#include "lscpu.h"
+
+#define CACHE_MAX 100
+
+/* /sys paths */
+#define _PATH_SYS_SYSTEM "/sys/devices/system"
+#define _PATH_SYS_HYP_FEATURES "/sys/hypervisor/properties/features"
+#define _PATH_SYS_CPU _PATH_SYS_SYSTEM "/cpu"
+#define _PATH_SYS_NODE _PATH_SYS_SYSTEM "/node"
+
+/* Xen Domain feature flag used for /sys/hypervisor/properties/features */
+#define XENFEAT_supervisor_mode_kernel 3
+#define XENFEAT_mmu_pt_update_preserve_ad 5
+#define XENFEAT_hvm_callback_vector 8
+
+#define XEN_FEATURES_PV_MASK (1U << XENFEAT_mmu_pt_update_preserve_ad)
+#define XEN_FEATURES_PVH_MASK ( (1U << XENFEAT_supervisor_mode_kernel) \
+ | (1U << XENFEAT_hvm_callback_vector) )
+
+static const char *virt_types[] = {
+ [VIRT_NONE] = N_("none"),
+ [VIRT_PARA] = N_("para"),
+ [VIRT_FULL] = N_("full"),
+ [VIRT_CONT] = N_("container"),
+};
+
+static const char *hv_vendors[] = {
+ [HYPER_NONE] = NULL,
+ [HYPER_XEN] = "Xen",
+ [HYPER_KVM] = "KVM",
+ [HYPER_MSHV] = "Microsoft",
+ [HYPER_VMWARE] = "VMware",
+ [HYPER_IBM] = "IBM",
+ [HYPER_VSERVER] = "Linux-VServer",
+ [HYPER_UML] = "User-mode Linux",
+ [HYPER_INNOTEK] = "Innotek GmbH",
+ [HYPER_HITACHI] = "Hitachi",
+ [HYPER_PARALLELS] = "Parallels",
+ [HYPER_VBOX] = "Oracle",
+ [HYPER_OS400] = "OS/400",
+ [HYPER_PHYP] = "pHyp",
+ [HYPER_SPAR] = "Unisys s-Par",
+ [HYPER_WSL] = "Windows Subsystem for Linux"
+};
+
+static const int hv_vendor_pci[] = {
+ [HYPER_NONE] = 0x0000,
+ [HYPER_XEN] = 0x5853,
+ [HYPER_KVM] = 0x0000,
+ [HYPER_MSHV] = 0x1414,
+ [HYPER_VMWARE] = 0x15ad,
+ [HYPER_VBOX] = 0x80ee,
+};
+
+static const int hv_graphics_pci[] = {
+ [HYPER_NONE] = 0x0000,
+ [HYPER_XEN] = 0x0001,
+ [HYPER_KVM] = 0x0000,
+ [HYPER_MSHV] = 0x5353,
+ [HYPER_VMWARE] = 0x0710,
+ [HYPER_VBOX] = 0xbeef,
+};
+
+
+/* dispatching modes */
+static const char *disp_modes[] = {
+ [DISP_HORIZONTAL] = N_("horizontal"),
+ [DISP_VERTICAL] = N_("vertical")
+};
+
+static struct polarization_modes polar_modes[] = {
+ [POLAR_UNKNOWN] = {"U", "-"},
+ [POLAR_VLOW] = {"VL", "vert-low"},
+ [POLAR_VMEDIUM] = {"VM", "vert-medium"},
+ [POLAR_VHIGH] = {"VH", "vert-high"},
+ [POLAR_HORIZONTAL] = {"H", "horizontal"},
+};
+
+static int maxcpus; /* size in bits of kernel cpu mask */
+
+#define is_cpu_online(_d, _cpu) \
+ ((_d) && (_d)->online ? \
+ CPU_ISSET_S((_cpu), CPU_ALLOC_SIZE(maxcpus), (_d)->online) : 0)
+#define is_cpu_present(_d, _cpu) \
+ ((_d) && (_d)->present ? \
+ CPU_ISSET_S((_cpu), CPU_ALLOC_SIZE(maxcpus), (_d)->present) : 0)
+
+#define real_cpu_num(_d, _i) ((_d)->idx2cpunum[(_i)])
+
+/*
+ * IDs
+ */
+enum {
+ COL_CPU_CPU,
+ COL_CPU_CORE,
+ COL_CPU_SOCKET,
+ COL_CPU_NODE,
+ COL_CPU_BOOK,
+ COL_CPU_DRAWER,
+ COL_CPU_CACHE,
+ COL_CPU_POLARIZATION,
+ COL_CPU_ADDRESS,
+ COL_CPU_CONFIGURED,
+ COL_CPU_ONLINE,
+ COL_CPU_MAXMHZ,
+ COL_CPU_MINMHZ,
+};
+
+enum {
+ COL_CACHE_ALLSIZE,
+ COL_CACHE_LEVEL,
+ COL_CACHE_NAME,
+ COL_CACHE_ONESIZE,
+ COL_CACHE_TYPE,
+ COL_CACHE_WAYS,
+ COL_CACHE_ALLOCPOL,
+ COL_CACHE_WRITEPOL,
+ COL_CACHE_PHYLINE,
+ COL_CACHE_SETS,
+ COL_CACHE_COHERENCYSIZE
+};
+
+
+/* column description
+ */
+struct lscpu_coldesc {
+ const char *name;
+ const char *help;
+
+ int flags;
+ unsigned int is_abbr:1; /* name is abbreviation */
+};
+
+static struct lscpu_coldesc coldescs_cpu[] =
+{
+ [COL_CPU_CPU] = { "CPU", N_("logical CPU number"), SCOLS_FL_RIGHT, 1 },
+ [COL_CPU_CORE] = { "CORE", N_("logical core number"), SCOLS_FL_RIGHT },
+ [COL_CPU_SOCKET] = { "SOCKET", N_("logical socket number"), SCOLS_FL_RIGHT },
+ [COL_CPU_NODE] = { "NODE", N_("logical NUMA node number"), SCOLS_FL_RIGHT },
+ [COL_CPU_BOOK] = { "BOOK", N_("logical book number"), SCOLS_FL_RIGHT },
+ [COL_CPU_DRAWER] = { "DRAWER", N_("logical drawer number"), SCOLS_FL_RIGHT },
+ [COL_CPU_CACHE] = { "CACHE", N_("shows how caches are shared between CPUs") },
+ [COL_CPU_POLARIZATION] = { "POLARIZATION", N_("CPU dispatching mode on virtual hardware") },
+ [COL_CPU_ADDRESS] = { "ADDRESS", N_("physical address of a CPU") },
+ [COL_CPU_CONFIGURED] = { "CONFIGURED", N_("shows if the hypervisor has allocated the CPU") },
+ [COL_CPU_ONLINE] = { "ONLINE", N_("shows if Linux currently makes use of the CPU"), SCOLS_FL_RIGHT },
+ [COL_CPU_MAXMHZ] = { "MAXMHZ", N_("shows the maximum MHz of the CPU"), SCOLS_FL_RIGHT },
+ [COL_CPU_MINMHZ] = { "MINMHZ", N_("shows the minimum MHz of the CPU"), SCOLS_FL_RIGHT }
+};
+
+static struct lscpu_coldesc coldescs_cache[] =
+{
+ [COL_CACHE_ALLSIZE] = { "ALL-SIZE", N_("size of all system caches"), SCOLS_FL_RIGHT },
+ [COL_CACHE_LEVEL] = { "LEVEL", N_("cache level"), SCOLS_FL_RIGHT },
+ [COL_CACHE_NAME] = { "NAME", N_("cache name") },
+ [COL_CACHE_ONESIZE] = { "ONE-SIZE", N_("size of one cache"), SCOLS_FL_RIGHT },
+ [COL_CACHE_TYPE] = { "TYPE", N_("cache type") },
+ [COL_CACHE_WAYS] = { "WAYS", N_("ways of associativity"), SCOLS_FL_RIGHT },
+ [COL_CACHE_ALLOCPOL] = { "ALLOC-POLICY", N_("allocation policy") },
+ [COL_CACHE_WRITEPOL] = { "WRITE-POLICY", N_("write policy") },
+ [COL_CACHE_PHYLINE] = { "PHY-LINE", N_("number of physical cache line per cache t"), SCOLS_FL_RIGHT },
+ [COL_CACHE_SETS] = { "SETS", N_("number of sets in the cache; set lines has the same cache index"), SCOLS_FL_RIGHT },
+ [COL_CACHE_COHERENCYSIZE] = { "COHERENCY-SIZE", N_("minimum amount of data in bytes transferred from memory to cache"), SCOLS_FL_RIGHT }
+};
+
+
+static int get_cache_full_size(struct lscpu_desc *desc, struct cpu_cache *ca, uint64_t *res);
+
+static int
+cpu_column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(coldescs_cpu); i++) {
+ const char *cn = coldescs_cpu[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int
+cache_column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(coldescs_cache); i++) {
+ const char *cn = coldescs_cache[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+/* Lookup a pattern and get the value from cpuinfo.
+ * Format is:
+ *
+ * "<pattern> : <key>"
+ */
+static int
+lookup(char *line, char *pattern, char **value)
+{
+ char *p, *v;
+ int len = strlen(pattern);
+
+ /* don't re-fill already found tags, first one wins */
+ if (!*line || *value)
+ return 0;
+
+ /* pattern */
+ if (strncmp(line, pattern, len) != 0)
+ return 0;
+
+ /* white spaces */
+ for (p = line + len; isspace(*p); p++);
+
+ /* separator */
+ if (*p != ':')
+ return 0;
+
+ /* white spaces */
+ for (++p; isspace(*p); p++);
+
+ /* value */
+ if (!*p)
+ return 0;
+ v = p;
+
+ /* end of value */
+ len = strlen(line) - 1;
+ for (p = line + len; isspace(*(p-1)); p--);
+ *p = '\0';
+
+ *value = xstrdup(v);
+ return 1;
+}
+
+/* Parse extra cache lines contained within /proc/cpuinfo but which are not
+ * part of the cache topology information within the sysfs filesystem.
+ * This is true for all shared caches on e.g. s390. When there are layers of
+ * hypervisors in between it is not knows which CPUs share which caches.
+ * Therefore information about shared caches is only available in
+ * /proc/cpuinfo.
+ * Format is:
+ * "cache<nr> : level=<lvl> type=<type> scope=<scope> size=<size> line_size=<lsz> associativity=<as>"
+ */
+static int
+lookup_cache(char *line, struct lscpu_desc *desc)
+{
+ struct cpu_cache *cache;
+ long long size;
+ char *p, type;
+ int level, line_size, associativity;
+
+ /* Make sure line starts with "cache<nr> :" */
+ if (strncmp(line, "cache", 5) != 0)
+ return 0;
+ for (p = line + 5; isdigit(*p); p++);
+ for (; isspace(*p); p++);
+ if (*p != ':')
+ return 0;
+
+ p = strstr(line, "scope=") + 6;
+ /* Skip private caches, also present in sysfs */
+ if (!p || strncmp(p, "Private", 7) == 0)
+ return 0;
+ p = strstr(line, "level=");
+ if (!p || sscanf(p, "level=%d", &level) != 1)
+ return 0;
+ p = strstr(line, "type=") + 5;
+ if (!p || !*p)
+ return 0;
+ type = 0;
+ if (strncmp(p, "Data", 4) == 0)
+ type = 'd';
+ else if (strncmp(p, "Instruction", 11) == 0)
+ type = 'i';
+ else if (strncmp(p, "Unified", 7) == 0)
+ type = 'u';
+ p = strstr(line, "size=");
+ if (!p || sscanf(p, "size=%lld", &size) != 1)
+ return 0;
+
+ p = strstr(line, "line_size=");
+ if (!p || sscanf(p, "line_size=%u", &line_size) != 1)
+ return 0;
+
+ p = strstr(line, "associativity=");
+ if (!p || sscanf(p, "associativity=%u", &associativity) != 1)
+ return 0;
+
+ desc->necaches++;
+ desc->ecaches = xrealloc(desc->ecaches,
+ desc->necaches * sizeof(struct cpu_cache));
+ cache = &desc->ecaches[desc->necaches - 1];
+ memset(cache, 0 , sizeof(*cache));
+
+ if (type == 'i' || type == 'd')
+ xasprintf(&cache->name, "L%d%c", level, type);
+ else
+ xasprintf(&cache->name, "L%d", level);
+
+ cache->level = level;
+ cache->size = size * 1024;
+ cache->ways_of_associativity = associativity;
+ cache->coherency_line_size = line_size;
+ /* Number of sets for s390. For safety, just check divide by zero */
+ cache->number_of_sets = line_size ? (cache->size / line_size): 0;
+ cache->number_of_sets = associativity ? (cache->number_of_sets / associativity) : 0;
+
+ cache->type = type == 'i' ? xstrdup("Instruction") :
+ type == 'd' ? xstrdup("Data") :
+ type == 'u' ? xstrdup("Unified") : NULL;
+ return 1;
+}
+
+/* Don't init the mode for platforms where we are not able to
+ * detect that CPU supports 64-bit mode.
+ */
+static int
+init_mode(struct lscpu_modifier *mod)
+{
+ int m = 0;
+
+ if (mod->system == SYSTEM_SNAPSHOT)
+ /* reading info from any /{sys,proc} dump, don't mix it with
+ * information about our real CPU */
+ return 0;
+
+#if defined(__alpha__) || defined(__ia64__)
+ m |= MODE_64BIT; /* 64bit platforms only */
+#endif
+ /* platforms with 64bit flag in /proc/cpuinfo, define
+ * 32bit default here */
+#if defined(__i386__) || defined(__x86_64__) || \
+ defined(__s390x__) || defined(__s390__) || defined(__sparc_v9__)
+ m |= MODE_32BIT;
+#endif
+
+#if defined(__aarch64__)
+ {
+ /* personality() is the most reliable way (since 4.7)
+ * to determine aarch32 support */
+ int pers = personality(PER_LINUX32);
+ if (pers != -1) {
+ personality(pers);
+ m |= MODE_32BIT;
+ }
+ m |= MODE_64BIT;
+ }
+#endif
+ return m;
+}
+
+#if defined(HAVE_LIBRTAS)
+#define PROCESSOR_MODULE_INFO 43
+static int strbe16toh(const char *buf, int offset)
+{
+ return (buf[offset] << 8) + buf[offset+1];
+}
+
+static void read_physical_info_powerpc(struct lscpu_desc *desc)
+{
+ char buf[BUFSIZ];
+ int rc, len, ntypes;
+
+ desc->physsockets = desc->physchips = desc->physcoresperchip = 0;
+
+ rc = rtas_get_sysparm(PROCESSOR_MODULE_INFO, sizeof(buf), buf);
+ if (rc < 0)
+ return;
+
+ len = strbe16toh(buf, 0);
+ if (len < 8)
+ return;
+
+ ntypes = strbe16toh(buf, 2);
+ if (!ntypes)
+ return;
+
+ desc->physsockets = strbe16toh(buf, 4);
+ desc->physchips = strbe16toh(buf, 6);
+ desc->physcoresperchip = strbe16toh(buf, 8);
+}
+#else
+static void read_physical_info_powerpc(
+ struct lscpu_desc *desc __attribute__((__unused__)))
+{
+}
+#endif
+
+static int cmp_vulnerability_name(const void *a0, const void *b0)
+{
+ const struct cpu_vulnerability *a = (const struct cpu_vulnerability *) a0,
+ *b = (const struct cpu_vulnerability *) b0;
+ return strcmp(a->name, b->name);
+}
+
+static void read_vulnerabilities(struct lscpu_desc *desc)
+{
+ struct dirent *d;
+ DIR *dir = ul_path_opendir(desc->syscpu, "vulnerabilities");
+ int n = 0;
+
+ if (!dir)
+ return;
+
+ desc->nvuls = n = 0;
+
+ while (xreaddir(dir))
+ n++;
+ if (!n)
+ return;
+
+ rewinddir(dir);
+ desc->vuls = xcalloc(n, sizeof(struct cpu_vulnerability));
+
+ while (desc->nvuls < n && (d = xreaddir(dir))) {
+ char *str, *p;
+ struct cpu_vulnerability *vu;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN)
+ continue;
+#endif
+ if (ul_path_readf_string(desc->syscpu, &str,
+ "vulnerabilities/%s", d->d_name) <= 0)
+ continue;
+
+ vu = &desc->vuls[desc->nvuls++];
+
+ /* Name */
+ vu->name = xstrdup(d->d_name);
+ *vu->name = toupper(*vu->name);
+ strrep(vu->name, '_', ' ');
+
+ /* Description */
+ vu->text = str;
+ p = (char *) startswith(vu->text, "Mitigation");
+ if (p) {
+ *p = ';';
+ strrem(vu->text, ':');
+ }
+ }
+ closedir(dir);
+
+ qsort(desc->vuls, desc->nvuls,
+ sizeof(struct cpu_vulnerability), cmp_vulnerability_name);
+}
+
+
+
+
+static void
+read_basicinfo(struct lscpu_desc *desc, struct lscpu_modifier *mod)
+{
+ FILE *fp;
+ char buf[BUFSIZ];
+ struct utsname utsbuf;
+ size_t setsize;
+ cpu_set_t *cpuset = NULL;
+
+ /* architecture */
+ if (uname(&utsbuf) == -1)
+ err(EXIT_FAILURE, _("error: uname failed"));
+
+ fp = ul_path_fopen(desc->procfs, "r", "cpuinfo");
+ if (!fp)
+ err(EXIT_FAILURE, _("cannot open %s"), "/proc/cpuinfo");
+ desc->arch = xstrdup(utsbuf.machine);
+
+ /* details */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (lookup(buf, "vendor", &desc->vendor)) ;
+ else if (lookup(buf, "vendor_id", &desc->vendor)) ;
+ else if (lookup(buf, "CPU implementer", &desc->vendor)) ; /* ARM and aarch64 */
+ else if (lookup(buf, "family", &desc->family)) ;
+ else if (lookup(buf, "cpu family", &desc->family)) ;
+ else if (lookup(buf, "model", &desc->model)) ;
+ else if (lookup(buf, "CPU part", &desc->model)) ; /* ARM and aarch64 */
+ else if (lookup(buf, "cpu model", &desc->model)) ; /* mips */
+ else if (lookup(buf, "model name", &desc->modelname)) ;
+ else if (lookup(buf, "stepping", &desc->stepping)) ;
+ else if (lookup(buf, "CPU variant", &desc->stepping)) ; /* aarch64 */
+ else if (lookup(buf, "cpu MHz", &desc->mhz)) ;
+ else if (lookup(buf, "cpu MHz dynamic", &desc->dynamic_mhz)) ; /* s390 */
+ else if (lookup(buf, "cpu MHz static", &desc->static_mhz)) ; /* s390 */
+ else if (lookup(buf, "flags", &desc->flags)) ; /* x86 */
+ else if (lookup(buf, "features", &desc->flags)) ; /* s390 */
+ else if (lookup(buf, "Features", &desc->flags)) ; /* aarch64 */
+ else if (lookup(buf, "ASEs implemented", &desc->flags)) ; /* mips */
+ else if (lookup(buf, "type", &desc->flags)) ; /* sparc64 */
+ else if (lookup(buf, "bogomips", &desc->bogomips)) ;
+ else if (lookup(buf, "BogoMIPS", &desc->bogomips)) ; /* aarch64 */
+ else if (lookup(buf, "bogomips per cpu", &desc->bogomips)) ; /* s390 */
+ else if (lookup(buf, "cpu", &desc->cpu)) ;
+ else if (lookup(buf, "revision", &desc->revision)) ;
+ else if (lookup(buf, "CPU revision", &desc->revision)) ; /* aarch64 */
+ else if (lookup(buf, "max thread id", &desc->mtid)) ; /* s390 */
+ else if (lookup(buf, "address sizes", &desc->addrsz)) ; /* x86 */
+ else if (lookup_cache(buf, desc)) ;
+ else
+ continue;
+ }
+
+ desc->mode = init_mode(mod);
+
+ if (desc->flags) {
+ snprintf(buf, sizeof(buf), " %s ", desc->flags);
+ if (strstr(buf, " svm "))
+ desc->virtflag = xstrdup("svm");
+ else if (strstr(buf, " vmx "))
+ desc->virtflag = xstrdup("vmx");
+ if (strstr(buf, " lm "))
+ desc->mode |= MODE_32BIT | MODE_64BIT; /* x86_64 */
+ if (strstr(buf, " zarch "))
+ desc->mode |= MODE_32BIT | MODE_64BIT; /* s390x */
+ if (strstr(buf, " sun4v ") || strstr(buf, " sun4u "))
+ desc->mode |= MODE_32BIT | MODE_64BIT; /* sparc64 */
+ }
+
+ if (desc->arch && mod->system != SYSTEM_SNAPSHOT) {
+ if (strcmp(desc->arch, "ppc64") == 0)
+ desc->mode |= MODE_32BIT | MODE_64BIT;
+ else if (strcmp(desc->arch, "ppc") == 0)
+ desc->mode |= MODE_32BIT;
+ }
+
+ fclose(fp);
+
+ if (ul_path_read_s32(desc->syscpu, &maxcpus, "kernel_max") == 0)
+ /* note that kernel_max is maximum index [NR_CPUS-1] */
+ maxcpus += 1;
+
+ else if (mod->system == SYSTEM_LIVE)
+ /* the root is '/' so we are working with data from the current kernel */
+ maxcpus = get_max_number_of_cpus();
+
+ if (maxcpus <= 0)
+ /* error or we are reading some /sys snapshot instead of the
+ * real /sys, let's use any crazy number... */
+ maxcpus = 2048;
+
+ setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ if (ul_path_readf_cpulist(desc->syscpu, &cpuset, maxcpus, "possible") == 0) {
+ int num, idx;
+
+ desc->ncpuspos = CPU_COUNT_S(setsize, cpuset);
+ desc->idx2cpunum = xcalloc(desc->ncpuspos, sizeof(int));
+
+ for (num = 0, idx = 0; num < maxcpus; num++) {
+ if (CPU_ISSET_S(num, setsize, cpuset))
+ desc->idx2cpunum[idx++] = num;
+ }
+ cpuset_free(cpuset);
+ cpuset = NULL;
+ } else
+ err(EXIT_FAILURE, _("failed to determine number of CPUs: %s"),
+ _PATH_SYS_CPU "/possible");
+
+
+ /* get mask for present CPUs */
+ if (ul_path_readf_cpulist(desc->syscpu, &desc->present, maxcpus, "present") == 0)
+ desc->ncpus = CPU_COUNT_S(setsize, desc->present);
+
+ /* get mask for online CPUs */
+ if (ul_path_readf_cpulist(desc->syscpu, &desc->online, maxcpus, "online") == 0)
+ desc->nthreads = CPU_COUNT_S(setsize, desc->online);
+
+ /* get dispatching mode */
+ if (ul_path_read_s32(desc->syscpu, &desc->dispatching, "dispatching") != 0)
+ desc->dispatching = -1;
+
+ /* get cpufreq boost mode */
+ if (ul_path_read_s32(desc->syscpu, &desc->freqboost, "cpufreq/boost") != 0)
+ desc->freqboost = -1;
+
+ if (mod->system == SYSTEM_LIVE)
+ read_physical_info_powerpc(desc);
+
+ if ((fp = ul_path_fopen(desc->procfs, "r", "sysinfo"))) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (lookup(buf, "Type", &desc->machinetype))
+ break;
+ }
+ fclose(fp);
+ }
+
+ /* vulnerabilities */
+ if (ul_path_access(desc->syscpu, F_OK, "vulnerabilities") == 0)
+ read_vulnerabilities(desc);
+}
+
+static int
+has_pci_device(struct lscpu_desc *desc, unsigned int vendor, unsigned int device)
+{
+ FILE *f;
+ unsigned int num, fn, ven, dev;
+ int res = 1;
+
+ f = ul_path_fopen(desc->procfs, "r", "bus/pci/devices");
+ if (!f)
+ return 0;
+
+ /* for more details about bus/pci/devices format see
+ * drivers/pci/proc.c in linux kernel
+ */
+ while(fscanf(f, "%02x%02x\t%04x%04x\t%*[^\n]",
+ &num, &fn, &ven, &dev) == 4) {
+
+ if (ven == vendor && dev == device)
+ goto found;
+ }
+
+ res = 0;
+found:
+ fclose(f);
+ return res;
+}
+
+#if defined(__x86_64__) || defined(__i386__)
+
+/*
+ * This CPUID leaf returns the information about the hypervisor.
+ * EAX : maximum input value for CPUID supported by the hypervisor.
+ * EBX, ECX, EDX : Hypervisor vendor ID signature. E.g. VMwareVMware.
+ */
+#define HYPERVISOR_INFO_LEAF 0x40000000
+
+static inline void
+cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx,
+ unsigned int *ecx, unsigned int *edx)
+{
+ __asm__(
+#if defined(__PIC__) && defined(__i386__)
+ /* x86 PIC cannot clobber ebx -- gcc bitches */
+ "xchg %%ebx, %%esi;"
+ "cpuid;"
+ "xchg %%esi, %%ebx;"
+ : "=S" (*ebx),
+#else
+ "cpuid;"
+ : "=b" (*ebx),
+#endif
+ "=a" (*eax),
+ "=c" (*ecx),
+ "=d" (*edx)
+ : "1" (op), "c"(0));
+}
+
+static void
+read_hypervisor_cpuid(struct lscpu_desc *desc)
+{
+ unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0;
+ char hyper_vendor_id[13];
+
+ memset(hyper_vendor_id, 0, sizeof(hyper_vendor_id));
+
+ cpuid(HYPERVISOR_INFO_LEAF, &eax, &ebx, &ecx, &edx);
+ memcpy(hyper_vendor_id + 0, &ebx, 4);
+ memcpy(hyper_vendor_id + 4, &ecx, 4);
+ memcpy(hyper_vendor_id + 8, &edx, 4);
+ hyper_vendor_id[12] = '\0';
+
+ if (!hyper_vendor_id[0])
+ return;
+
+ if (!strncmp("XenVMMXenVMM", hyper_vendor_id, 12))
+ desc->hyper = HYPER_XEN;
+ else if (!strncmp("KVMKVMKVM", hyper_vendor_id, 9))
+ desc->hyper = HYPER_KVM;
+ else if (!strncmp("Microsoft Hv", hyper_vendor_id, 12))
+ desc->hyper = HYPER_MSHV;
+ else if (!strncmp("VMwareVMware", hyper_vendor_id, 12))
+ desc->hyper = HYPER_VMWARE;
+ else if (!strncmp("UnisysSpar64", hyper_vendor_id, 12))
+ desc->hyper = HYPER_SPAR;
+}
+
+#else /* ! (__x86_64__ || __i386__) */
+static void
+read_hypervisor_cpuid(struct lscpu_desc *desc __attribute__((__unused__)))
+{
+}
+#endif
+
+static int is_devtree_compatible(struct lscpu_desc *desc, const char *str)
+{
+ FILE *fd = ul_path_fopen(desc->procfs, "r", "device-tree/compatible");
+
+ if (fd) {
+ char buf[256];
+ size_t i, len;
+
+ memset(buf, 0, sizeof(buf));
+ len = fread(buf, 1, sizeof(buf) - 1, fd);
+ fclose(fd);
+
+ for (i = 0; i < len;) {
+ if (!strcmp(&buf[i], str))
+ return 1;
+ i += strlen(&buf[i]);
+ i++;
+ }
+ }
+
+ return 0;
+}
+
+static int
+read_hypervisor_powerpc(struct lscpu_desc *desc)
+{
+ assert(!desc->hyper);
+
+ /* IBM iSeries: legacy, para-virtualized on top of OS/400 */
+ if (ul_path_access(desc->procfs, F_OK, "iSeries") == 0) {
+ desc->hyper = HYPER_OS400;
+ desc->virtype = VIRT_PARA;
+
+ /* PowerNV (POWER Non-Virtualized, bare-metal) */
+ } else if (is_devtree_compatible(desc, "ibm,powernv")) {
+ desc->hyper = HYPER_NONE;
+ desc->virtype = VIRT_NONE;
+
+ /* PowerVM (IBM's proprietary hypervisor, aka pHyp) */
+ } else if (ul_path_access(desc->procfs, F_OK, "device-tree/ibm,partition-name") == 0
+ && ul_path_access(desc->procfs, F_OK, "device-tree/hmc-managed?") == 0
+ && ul_path_access(desc->procfs, F_OK, "device-tree/chosen/qemu,graphic-width") != 0) {
+
+ FILE *fd;
+ desc->hyper = HYPER_PHYP;
+ desc->virtype = VIRT_PARA;
+
+ fd = ul_path_fopen(desc->procfs, "r", "device-tree/ibm,partition-name");
+ if (fd) {
+ char buf[256];
+ if (fscanf(fd, "%255s", buf) == 1 && !strcmp(buf, "full"))
+ desc->virtype = VIRT_NONE;
+ fclose(fd);
+ }
+
+ /* Qemu */
+ } else if (is_devtree_compatible(desc, "qemu,pseries")) {
+ desc->hyper = HYPER_KVM;
+ desc->virtype = VIRT_PARA;
+ }
+ return desc->hyper;
+}
+
+#ifdef INCLUDE_VMWARE_BDOOR
+
+#define VMWARE_BDOOR_MAGIC 0x564D5868
+#define VMWARE_BDOOR_PORT 0x5658
+#define VMWARE_BDOOR_CMD_GETVERSION 10
+
+static UL_ASAN_BLACKLIST
+void vmware_bdoor(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx)
+{
+ __asm__(
+#if defined(__PIC__) && defined(__i386__)
+ /* x86 PIC cannot clobber ebx -- gcc bitches */
+ "xchg %%ebx, %%esi;"
+ "inl (%%dx), %%eax;"
+ "xchg %%esi, %%ebx;"
+ : "=S" (*ebx),
+#else
+ "inl (%%dx), %%eax;"
+ : "=b" (*ebx),
+#endif
+ "=a" (*eax),
+ "=c" (*ecx),
+ "=d" (*edx)
+ : "0" (VMWARE_BDOOR_MAGIC),
+ "1" (VMWARE_BDOOR_CMD_GETVERSION),
+ "2" (VMWARE_BDOOR_PORT),
+ "3" (0)
+ : "memory");
+}
+
+static jmp_buf segv_handler_env;
+
+static void
+segv_handler(__attribute__((__unused__)) int sig,
+ __attribute__((__unused__)) siginfo_t *info,
+ __attribute__((__unused__)) void *ignored)
+{
+ siglongjmp(segv_handler_env, 1);
+}
+
+static int
+is_vmware_platform(void)
+{
+ uint32_t eax, ebx, ecx, edx;
+ struct sigaction act, oact;
+
+ /*
+ * FIXME: Not reliable for non-root users. Note it works as expected if
+ * vmware_bdoor() is not optimized for PIE, but then it fails to build
+ * on 32bit x86 systems. See lscpu git log for more details (commit
+ * 7845b91dbc7690064a2be6df690e4aaba728fb04). kzak [3-Nov-2016]
+ */
+ if (getuid() != 0)
+ return 0;
+
+ /*
+ * The assembly routine for vmware detection works
+ * fine under vmware, even if ran as regular user. But
+ * on real HW or under other hypervisors, it segfaults (which is
+ * expected). So we temporarily install SIGSEGV handler to catch
+ * the signal. All this magic is needed because lscpu
+ * isn't supposed to require root privileges.
+ */
+ if (sigsetjmp(segv_handler_env, 1))
+ return 0;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_sigaction = segv_handler;
+ act.sa_flags = SA_SIGINFO;
+
+ if (sigaction(SIGSEGV, &act, &oact))
+ err(EXIT_FAILURE, _("cannot set signal handler"));
+
+ vmware_bdoor(&eax, &ebx, &ecx, &edx);
+
+ if (sigaction(SIGSEGV, &oact, NULL))
+ err(EXIT_FAILURE, _("cannot restore signal handler"));
+
+ return eax != (uint32_t)-1 && ebx == VMWARE_BDOOR_MAGIC;
+}
+
+#else /* ! INCLUDE_VMWARE_BDOOR */
+
+static int
+is_vmware_platform(void)
+{
+ return 0;
+}
+
+#endif /* INCLUDE_VMWARE_BDOOR */
+
+static void
+read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod)
+{
+ FILE *fd;
+
+ /* We have to detect WSL first. is_vmware_platform() crashes on Windows 10. */
+
+ if ((fd = ul_path_fopen(desc->procfs, "r", "sys/kernel/osrelease"))) {
+ char buf[256];
+
+ if (fgets(buf, sizeof(buf), fd) != NULL) {
+ if (strstr(buf, "Microsoft")) {
+ desc->hyper = HYPER_WSL;
+ desc->virtype = VIRT_CONT;
+ }
+ }
+ fclose(fd);
+ if (desc->virtype)
+ return;
+ }
+
+ if (mod->system != SYSTEM_SNAPSHOT) {
+ read_hypervisor_cpuid(desc);
+ if (!desc->hyper)
+ desc->hyper = read_hypervisor_dmi();
+ if (!desc->hyper && is_vmware_platform())
+ desc->hyper = HYPER_VMWARE;
+ }
+
+ if (desc->hyper) {
+ desc->virtype = VIRT_FULL;
+
+ if (desc->hyper == HYPER_XEN) {
+ uint32_t features;
+
+ fd = ul_prefix_fopen(desc->prefix, "r", _PATH_SYS_HYP_FEATURES);
+
+ if (fd && fscanf(fd, "%x", &features) == 1) {
+ /* Xen PV domain */
+ if (features & XEN_FEATURES_PV_MASK)
+ desc->virtype = VIRT_PARA;
+ /* Xen PVH domain */
+ else if ((features & XEN_FEATURES_PVH_MASK)
+ == XEN_FEATURES_PVH_MASK)
+ desc->virtype = VIRT_PARA;
+ }
+ if (fd)
+ fclose(fd);
+ }
+ } else if (read_hypervisor_powerpc(desc) > 0) {
+ /* read_hypervisor_powerpc() sets all necessary stuff to @desc */
+ ;
+ /* Xen para-virt or dom0 */
+ } else if (ul_path_access(desc->procfs, F_OK, "xen") == 0) {
+ int dom0 = 0;
+
+ fd = ul_path_fopen(desc->procfs, "r", "xen/capabilities");
+ if (fd) {
+ char buf[256];
+
+ if (fscanf(fd, "%255s", buf) == 1 &&
+ !strcmp(buf, "control_d"))
+ dom0 = 1;
+ fclose(fd);
+ }
+ desc->virtype = dom0 ? VIRT_NONE : VIRT_PARA;
+ desc->hyper = HYPER_XEN;
+
+ /* Xen full-virt on non-x86_64 */
+ } else if (has_pci_device(desc, hv_vendor_pci[HYPER_XEN], hv_graphics_pci[HYPER_XEN])) {
+ desc->hyper = HYPER_XEN;
+ desc->virtype = VIRT_FULL;
+ } else if (has_pci_device(desc, hv_vendor_pci[HYPER_VMWARE], hv_graphics_pci[HYPER_VMWARE])) {
+ desc->hyper = HYPER_VMWARE;
+ desc->virtype = VIRT_FULL;
+ } else if (has_pci_device(desc, hv_vendor_pci[HYPER_VBOX], hv_graphics_pci[HYPER_VBOX])) {
+ desc->hyper = HYPER_VBOX;
+ desc->virtype = VIRT_FULL;
+
+ /* IBM PR/SM */
+ } else if ((fd = ul_path_fopen(desc->procfs, "r", "sysinfo"))) {
+ char buf[BUFSIZ];
+
+ desc->hyper = HYPER_IBM;
+ desc->hypervisor = "PR/SM";
+ desc->virtype = VIRT_FULL;
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ char *str, *p;
+
+ if (!strstr(buf, "Control Program:"))
+ continue;
+ if (!strstr(buf, "KVM"))
+ desc->hyper = HYPER_IBM;
+ else
+ desc->hyper = HYPER_KVM;
+ p = strchr(buf, ':');
+ if (!p)
+ continue;
+ xasprintf(&str, "%s", p + 1);
+
+ /* remove leading, trailing and repeating whitespace */
+ while (*str == ' ')
+ str++;
+ desc->hypervisor = str;
+ str += strlen(str) - 1;
+ while ((*str == '\n') || (*str == ' '))
+ *(str--) = '\0';
+ while ((str = strstr(desc->hypervisor, " ")))
+ memmove(str, str + 1, strlen(str));
+ break;
+ }
+ fclose(fd);
+ }
+
+ /* OpenVZ/Virtuozzo - /proc/vz dir should exist
+ * /proc/bc should not */
+ else if (ul_path_access(desc->procfs, F_OK, "vz") == 0 &&
+ ul_path_access(desc->procfs, F_OK, "bc") != 0) {
+ desc->hyper = HYPER_PARALLELS;
+ desc->virtype = VIRT_CONT;
+
+ /* IBM */
+ } else if (desc->vendor &&
+ (strcmp(desc->vendor, "PowerVM Lx86") == 0 ||
+ strcmp(desc->vendor, "IBM/S390") == 0)) {
+ desc->hyper = HYPER_IBM;
+ desc->virtype = VIRT_FULL;
+
+ /* User-mode-linux */
+ } else if (desc->modelname && strstr(desc->modelname, "UML")) {
+ desc->hyper = HYPER_UML;
+ desc->virtype = VIRT_PARA;
+
+ /* Linux-VServer */
+ } else if ((fd = ul_path_fopen(desc->procfs, "r", "self/status"))) {
+ char buf[BUFSIZ];
+ char *val = NULL;
+
+ while (fgets(buf, sizeof(buf), fd) != NULL) {
+ if (lookup(buf, "VxID", &val))
+ break;
+ }
+ fclose(fd);
+
+ if (val) {
+ char *org = val;
+
+ while (isdigit(*val))
+ ++val;
+ if (!*val) {
+ desc->hyper = HYPER_VSERVER;
+ desc->virtype = VIRT_CONT;
+ }
+ free(org);
+ }
+ }
+}
+
+/* add @set to the @ary, unnecessary set is deallocated. */
+static int add_cpuset_to_array(cpu_set_t **ary, int *items, cpu_set_t *set)
+{
+ int i;
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ if (!ary)
+ return -1;
+
+ for (i = 0; i < *items; i++) {
+ if (CPU_EQUAL_S(setsize, set, ary[i]))
+ break;
+ }
+ if (i == *items) {
+ ary[*items] = set;
+ ++*items;
+ return 0;
+ }
+ CPU_FREE(set);
+ return 1;
+}
+
+static void
+read_topology(struct lscpu_desc *desc, int idx)
+{
+ cpu_set_t *thread_siblings, *core_siblings;
+ cpu_set_t *book_siblings, *drawer_siblings;
+ int coreid, socketid, bookid, drawerid;
+ int i, num = real_cpu_num(desc, idx);
+
+ if (ul_path_accessf(desc->syscpu, F_OK, "cpu%d/topology/thread_siblings", num) != 0)
+ return;
+
+ ul_path_readf_cpuset(desc->syscpu, &thread_siblings, maxcpus,
+ "cpu%d/topology/thread_siblings", num);
+ ul_path_readf_cpuset(desc->syscpu, &core_siblings, maxcpus,
+ "cpu%d/topology/core_siblings", num);
+ ul_path_readf_cpuset(desc->syscpu, &book_siblings, maxcpus,
+ "cpu%d/topology/book_siblings", num);
+ ul_path_readf_cpuset(desc->syscpu, &drawer_siblings, maxcpus,
+ "cpu%d/topology/drawer_siblings", num);
+
+ if (ul_path_readf_s32(desc->syscpu, &coreid, "cpu%d/topology/core_id", num) != 0)
+ coreid = -1;
+
+ if (ul_path_readf_s32(desc->syscpu, &socketid, "cpu%d/topology/physical_package_id", num) != 0)
+ socketid = -1;
+
+ if (ul_path_readf_s32(desc->syscpu, &bookid, "cpu%d/topology/book_id", num) != 0)
+ bookid = -1;
+
+ if (ul_path_readf_s32(desc->syscpu, &drawerid, "cpu%d/topology/drawer_id", num) != 0)
+ drawerid = -1;
+
+ if (!desc->coremaps) {
+ int ndrawers, nbooks, nsockets, ncores, nthreads;
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ /* threads within one core */
+ nthreads = CPU_COUNT_S(setsize, thread_siblings);
+ if (!nthreads)
+ nthreads = 1;
+
+ /* cores within one socket */
+ ncores = CPU_COUNT_S(setsize, core_siblings) / nthreads;
+ if (!ncores)
+ ncores = 1;
+
+ /* number of sockets within one book. Because of odd /
+ * non-present cpu maps and to keep calculation easy we make
+ * sure that nsockets and nbooks is at least 1.
+ */
+ nsockets = desc->ncpus / nthreads / ncores;
+ if (!nsockets)
+ nsockets = 1;
+
+ /* number of books */
+ nbooks = desc->ncpus / nthreads / ncores / nsockets;
+ if (!nbooks)
+ nbooks = 1;
+
+ /* number of drawers */
+ ndrawers = desc->ncpus / nbooks / nthreads / ncores / nsockets;
+ if (!ndrawers)
+ ndrawers = 1;
+
+ /* all threads, see also read_basicinfo()
+ * -- fallback for kernels without
+ * /sys/devices/system/cpu/online.
+ */
+ if (!desc->nthreads)
+ desc->nthreads = ndrawers * nbooks * nsockets * ncores * nthreads;
+
+ /* For each map we make sure that it can have up to ncpuspos
+ * entries. This is because we cannot reliably calculate the
+ * number of cores, sockets and books on all architectures.
+ * E.g. completely virtualized architectures like s390 may
+ * have multiple sockets of different sizes.
+ */
+ desc->coremaps = xcalloc(desc->ncpuspos, sizeof(cpu_set_t *));
+ desc->socketmaps = xcalloc(desc->ncpuspos, sizeof(cpu_set_t *));
+ desc->coreids = xcalloc(desc->ncpuspos, sizeof(*desc->coreids));
+ desc->socketids = xcalloc(desc->ncpuspos, sizeof(*desc->socketids));
+
+ for (i = 0; i < desc->ncpuspos; i++)
+ desc->coreids[i] = desc->socketids[i] = -1;
+
+ if (book_siblings) {
+ desc->bookmaps = xcalloc(desc->ncpuspos, sizeof(cpu_set_t *));
+ desc->bookids = xcalloc(desc->ncpuspos, sizeof(*desc->bookids));
+ for (i = 0; i < desc->ncpuspos; i++)
+ desc->bookids[i] = -1;
+ }
+ if (drawer_siblings) {
+ desc->drawermaps = xcalloc(desc->ncpuspos, sizeof(cpu_set_t *));
+ desc->drawerids = xcalloc(desc->ncpuspos, sizeof(*desc->drawerids));
+ for (i = 0; i < desc->ncpuspos; i++)
+ desc->drawerids[i] = -1;
+ }
+ }
+
+ add_cpuset_to_array(desc->socketmaps, &desc->nsockets, core_siblings);
+ desc->coreids[idx] = coreid;
+ add_cpuset_to_array(desc->coremaps, &desc->ncores, thread_siblings);
+ desc->socketids[idx] = socketid;
+
+ if (book_siblings && desc->bookmaps && desc->bookids) {
+ add_cpuset_to_array(desc->bookmaps, &desc->nbooks, book_siblings);
+ desc->bookids[idx] = bookid;
+ }
+ if (drawer_siblings && desc->drawermaps && desc->drawerids) {
+ add_cpuset_to_array(desc->drawermaps, &desc->ndrawers, drawer_siblings);
+ desc->drawerids[idx] = drawerid;
+ }
+}
+
+static void
+read_polarization(struct lscpu_desc *desc, int idx)
+{
+ char mode[64];
+ int num = real_cpu_num(desc, idx);
+
+ if (desc->dispatching < 0)
+ return;
+ if (ul_path_accessf(desc->syscpu, F_OK, "cpu%d/polarization", num) != 0)
+ return;
+ if (!desc->polarization)
+ desc->polarization = xcalloc(desc->ncpuspos, sizeof(int));
+
+ ul_path_readf_buffer(desc->syscpu, mode, sizeof(mode), "cpu%d/polarization", num);
+
+ if (strncmp(mode, "vertical:low", sizeof(mode)) == 0)
+ desc->polarization[idx] = POLAR_VLOW;
+ else if (strncmp(mode, "vertical:medium", sizeof(mode)) == 0)
+ desc->polarization[idx] = POLAR_VMEDIUM;
+ else if (strncmp(mode, "vertical:high", sizeof(mode)) == 0)
+ desc->polarization[idx] = POLAR_VHIGH;
+ else if (strncmp(mode, "horizontal", sizeof(mode)) == 0)
+ desc->polarization[idx] = POLAR_HORIZONTAL;
+ else
+ desc->polarization[idx] = POLAR_UNKNOWN;
+}
+
+static void
+read_address(struct lscpu_desc *desc, int idx)
+{
+ int num = real_cpu_num(desc, idx);
+
+ if (ul_path_accessf(desc->syscpu, F_OK, "cpu%d/address", num) != 0)
+ return;
+ if (!desc->addresses)
+ desc->addresses = xcalloc(desc->ncpuspos, sizeof(int));
+ ul_path_readf_s32(desc->syscpu, &desc->addresses[idx], "cpu%d/address", num);
+}
+
+static void
+read_configured(struct lscpu_desc *desc, int idx)
+{
+ int num = real_cpu_num(desc, idx);
+
+ if (ul_path_accessf(desc->syscpu, F_OK, "cpu%d/configure", num) != 0)
+ return;
+ if (!desc->configured)
+ desc->configured = xcalloc(desc->ncpuspos, sizeof(int));
+ ul_path_readf_s32(desc->syscpu, &desc->configured[idx], "cpu%d/configure", num);
+}
+
+/* Read overall maximum frequency of cpu */
+static char *
+cpu_max_mhz(struct lscpu_desc *desc, char *buf, size_t bufsz)
+{
+ int i;
+ float cpu_freq = 0.0;
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ if (desc->present) {
+ for (i = 0; i < desc->ncpuspos; i++) {
+ if (CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present)
+ && desc->maxmhz[i]) {
+ float freq = atof(desc->maxmhz[i]);
+
+ if (freq > cpu_freq)
+ cpu_freq = freq;
+ }
+ }
+ }
+ snprintf(buf, bufsz, "%.4f", cpu_freq);
+ return buf;
+}
+
+/* Read overall minimum frequency of cpu */
+static char *
+cpu_min_mhz(struct lscpu_desc *desc, char *buf, size_t bufsz)
+{
+ int i;
+ float cpu_freq = -1.0;
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ if (desc->present) {
+ for (i = 0; i < desc->ncpuspos; i++) {
+ if (CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present)
+ && desc->minmhz[i]) {
+ float freq = atof(desc->minmhz[i]);
+
+ if (cpu_freq < 0.0 || freq < cpu_freq)
+ cpu_freq = freq;
+ }
+ }
+ }
+ snprintf(buf, bufsz, "%.4f", cpu_freq);
+ return buf;
+}
+
+
+static void
+read_max_mhz(struct lscpu_desc *desc, int idx)
+{
+ int num = real_cpu_num(desc, idx);
+ int mhz;
+
+ if (ul_path_readf_s32(desc->syscpu, &mhz, "cpu%d/cpufreq/cpuinfo_max_freq", num) != 0)
+ return;
+ if (!desc->maxmhz)
+ desc->maxmhz = xcalloc(desc->ncpuspos, sizeof(char *));
+ xasprintf(&desc->maxmhz[idx], "%.4f", (float) mhz / 1000);
+}
+
+static void
+read_min_mhz(struct lscpu_desc *desc, int idx)
+{
+ int num = real_cpu_num(desc, idx);
+ int mhz;
+
+ if (ul_path_readf_s32(desc->syscpu, &mhz, "cpu%d/cpufreq/cpuinfo_min_freq", num) != 0)
+ return;
+ if (!desc->minmhz)
+ desc->minmhz = xcalloc(desc->ncpuspos, sizeof(char *));
+ xasprintf(&desc->minmhz[idx], "%.4f", (float) mhz / 1000);
+}
+
+static int
+cachecmp(const void *a, const void *b)
+{
+ struct cpu_cache *c1 = (struct cpu_cache *) a;
+ struct cpu_cache *c2 = (struct cpu_cache *) b;
+
+ return strcmp(c2->name, c1->name);
+}
+
+static void
+read_cache(struct lscpu_desc *desc, int idx)
+{
+ char buf[256];
+ int i;
+ int num = real_cpu_num(desc, idx);
+
+ if (!desc->ncaches) {
+ while (ul_path_accessf(desc->syscpu, F_OK,
+ "cpu%d/cache/index%d",
+ num, desc->ncaches) == 0)
+ desc->ncaches++;
+
+ if (!desc->ncaches)
+ return;
+ desc->caches = xcalloc(desc->ncaches, sizeof(*desc->caches));
+ }
+ for (i = 0; i < desc->ncaches; i++) {
+ struct cpu_cache *ca = &desc->caches[i];
+ cpu_set_t *map;
+
+ if (ul_path_accessf(desc->syscpu, F_OK,
+ "cpu%d/cache/index%d", num, i) != 0)
+ continue;
+ if (!ca->name) {
+ int type = 0;
+
+ /* cache type */
+ if (ul_path_readf_string(desc->syscpu, &ca->type,
+ "cpu%d/cache/index%d/type", num, i) > 0) {
+ if (!strcmp(ca->type, "Data"))
+ type = 'd';
+ else if (!strcmp(ca->type, "Instruction"))
+ type = 'i';
+ }
+
+ /* cache level */
+ ul_path_readf_s32(desc->syscpu, &ca->level,
+ "cpu%d/cache/index%d/level", num, i);
+ if (type)
+ snprintf(buf, sizeof(buf), "L%d%c", ca->level, type);
+ else
+ snprintf(buf, sizeof(buf), "L%d", ca->level);
+
+ ca->name = xstrdup(buf);
+
+ ul_path_readf_u32(desc->syscpu, &ca->ways_of_associativity,
+ "cpu%d/cache/index%d/ways_of_associativity", num, i);
+ ul_path_readf_u32(desc->syscpu, &ca->physical_line_partition,
+ "cpu%d/cache/index%d/physical_line_partition", num, i);
+ ul_path_readf_u32(desc->syscpu, &ca->number_of_sets,
+ "cpu%d/cache/index%d/number_of_sets", num, i);
+ ul_path_readf_u32(desc->syscpu, &ca->coherency_line_size,
+ "cpu%d/cache/index%d/coherency_line_size", num, i);
+
+ ul_path_readf_string(desc->syscpu, &ca->allocation_policy,
+ "cpu%d/cache/index%d/allocation_policy", num, i);
+ ul_path_readf_string(desc->syscpu, &ca->write_policy,
+ "cpu%d/cache/index%d/write_policy", num, i);
+
+ /* cache size */
+ if (ul_path_readf_buffer(desc->syscpu, buf, sizeof(buf),
+ "cpu%d/cache/index%d/size", num, i) > 0)
+ parse_size(buf, &ca->size, NULL);
+ else
+ ca->size = 0;
+ }
+
+ /* information about how CPUs share different caches */
+ ul_path_readf_cpuset(desc->syscpu, &map, maxcpus,
+ "cpu%d/cache/index%d/shared_cpu_map", num, i);
+
+ if (!ca->sharedmaps)
+ ca->sharedmaps = xcalloc(desc->ncpuspos, sizeof(cpu_set_t *));
+ add_cpuset_to_array(ca->sharedmaps, &ca->nsharedmaps, map);
+ }
+}
+
+static inline int is_node_dirent(struct dirent *d)
+{
+ return
+ d &&
+#ifdef _DIRENT_HAVE_D_TYPE
+ (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN) &&
+#endif
+ strncmp(d->d_name, "node", 4) == 0 &&
+ isdigit_string(d->d_name + 4);
+}
+
+static int
+nodecmp(const void *ap, const void *bp)
+{
+ int *a = (int *) ap, *b = (int *) bp;
+ return *a - *b;
+}
+
+static void
+read_nodes(struct lscpu_desc *desc)
+{
+ int i = 0;
+ DIR *dir;
+ struct dirent *d;
+ struct path_cxt *sysnode;
+
+ desc->nnodes = 0;
+
+ sysnode = ul_new_path(_PATH_SYS_NODE);
+ if (!sysnode)
+ err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_NODE);
+ ul_path_set_prefix(sysnode, desc->prefix);
+
+ dir = ul_path_opendir(sysnode, NULL);
+ if (!dir)
+ goto done;
+
+ while ((d = readdir(dir))) {
+ if (is_node_dirent(d))
+ desc->nnodes++;
+ }
+
+ if (!desc->nnodes) {
+ closedir(dir);
+ goto done;
+ }
+
+ desc->nodemaps = xcalloc(desc->nnodes, sizeof(cpu_set_t *));
+ desc->idx2nodenum = xmalloc(desc->nnodes * sizeof(int));
+
+ rewinddir(dir);
+ while ((d = readdir(dir)) && i < desc->nnodes) {
+ if (is_node_dirent(d))
+ desc->idx2nodenum[i++] = strtol_or_err(((d->d_name) + 4),
+ _("Failed to extract the node number"));
+ }
+ closedir(dir);
+ qsort(desc->idx2nodenum, desc->nnodes, sizeof(int), nodecmp);
+
+ /* information about how nodes share different CPUs */
+ for (i = 0; i < desc->nnodes; i++)
+ ul_path_readf_cpuset(sysnode, &desc->nodemaps[i], maxcpus,
+ "node%d/cpumap", desc->idx2nodenum[i]);
+done:
+ ul_unref_path(sysnode);
+}
+
+static char *
+get_cell_data(struct lscpu_desc *desc, int idx, int col,
+ struct lscpu_modifier *mod,
+ char *buf, size_t bufsz)
+{
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+ size_t i;
+ int cpu = real_cpu_num(desc, idx);
+
+ *buf = '\0';
+
+ switch (col) {
+ case COL_CPU_CPU:
+ snprintf(buf, bufsz, "%d", cpu);
+ break;
+ case COL_CPU_CORE:
+ if (mod->physical) {
+ if (desc->coreids[idx] == -1)
+ snprintf(buf, bufsz, "-");
+ else
+ snprintf(buf, bufsz, "%d", desc->coreids[idx]);
+ } else {
+ if (cpuset_ary_isset(cpu, desc->coremaps,
+ desc->ncores, setsize, &i) == 0)
+ snprintf(buf, bufsz, "%zu", i);
+ }
+ break;
+ case COL_CPU_SOCKET:
+ if (mod->physical) {
+ if (desc->socketids[idx] == -1)
+ snprintf(buf, bufsz, "-");
+ else
+ snprintf(buf, bufsz, "%d", desc->socketids[idx]);
+ } else {
+ if (cpuset_ary_isset(cpu, desc->socketmaps,
+ desc->nsockets, setsize, &i) == 0)
+ snprintf(buf, bufsz, "%zu", i);
+ }
+ break;
+ case COL_CPU_NODE:
+ if (cpuset_ary_isset(cpu, desc->nodemaps,
+ desc->nnodes, setsize, &i) == 0)
+ snprintf(buf, bufsz, "%d", desc->idx2nodenum[i]);
+ break;
+ case COL_CPU_DRAWER:
+ if (!desc->drawerids || !desc->drawermaps)
+ break;
+ if (mod->physical) {
+ if (desc->drawerids[idx] == -1)
+ snprintf(buf, bufsz, "-");
+ else
+ snprintf(buf, bufsz, "%d", desc->drawerids[idx]);
+ } else {
+ if (cpuset_ary_isset(cpu, desc->drawermaps,
+ desc->ndrawers, setsize, &i) == 0)
+ snprintf(buf, bufsz, "%zu", i);
+ }
+ break;
+ case COL_CPU_BOOK:
+ if (!desc->bookids || !desc->bookmaps)
+ break;
+ if (mod->physical) {
+ if (desc->bookids[idx] == -1)
+ snprintf(buf, bufsz, "-");
+ else
+ snprintf(buf, bufsz, "%d", desc->bookids[idx]);
+ } else {
+ if (cpuset_ary_isset(cpu, desc->bookmaps,
+ desc->nbooks, setsize, &i) == 0)
+ snprintf(buf, bufsz, "%zu", i);
+ }
+ break;
+ case COL_CPU_CACHE:
+ {
+ char *p = buf;
+ size_t sz = bufsz;
+ int j;
+
+ for (j = desc->ncaches - 1; j >= 0; j--) {
+ struct cpu_cache *ca = &desc->caches[j];
+
+ if (cpuset_ary_isset(cpu, ca->sharedmaps,
+ ca->nsharedmaps, setsize, &i) == 0) {
+ int x = snprintf(p, sz, "%zu", i);
+ if (x < 0 || (size_t) x >= sz)
+ return NULL;
+ p += x;
+ sz -= x;
+ }
+ if (j != 0) {
+ if (sz < 2)
+ return NULL;
+ *p++ = mod->compat ? ',' : ':';
+ *p = '\0';
+ sz--;
+ }
+ }
+ break;
+ }
+ case COL_CPU_POLARIZATION:
+ if (desc->polarization) {
+ int x = desc->polarization[idx];
+
+ snprintf(buf, bufsz, "%s",
+ mod->mode == OUTPUT_PARSABLE ?
+ polar_modes[x].parsable :
+ polar_modes[x].readable);
+ }
+ break;
+ case COL_CPU_ADDRESS:
+ if (desc->addresses)
+ snprintf(buf, bufsz, "%d", desc->addresses[idx]);
+ break;
+ case COL_CPU_CONFIGURED:
+ if (!desc->configured)
+ break;
+ if (mod->mode == OUTPUT_PARSABLE)
+ snprintf(buf, bufsz, "%s",
+ desc->configured[idx] ? _("Y") : _("N"));
+ else
+ snprintf(buf, bufsz, "%s",
+ desc->configured[idx] ? _("yes") : _("no"));
+ break;
+ case COL_CPU_ONLINE:
+ if (!desc->online)
+ break;
+ if (mod->mode == OUTPUT_PARSABLE)
+ snprintf(buf, bufsz, "%s",
+ is_cpu_online(desc, cpu) ? _("Y") : _("N"));
+ else
+ snprintf(buf, bufsz, "%s",
+ is_cpu_online(desc, cpu) ? _("yes") : _("no"));
+ break;
+ case COL_CPU_MAXMHZ:
+ if (desc->maxmhz && desc->maxmhz[idx])
+ xstrncpy(buf, desc->maxmhz[idx], bufsz);
+ break;
+ case COL_CPU_MINMHZ:
+ if (desc->minmhz && desc->minmhz[idx])
+ xstrncpy(buf, desc->minmhz[idx], bufsz);
+ break;
+ }
+ return buf;
+}
+
+static char *
+get_cell_header(struct lscpu_desc *desc, int col,
+ struct lscpu_modifier *mod,
+ char *buf, size_t bufsz)
+{
+ *buf = '\0';
+
+ if (col == COL_CPU_CACHE) {
+ char *p = buf;
+ size_t sz = bufsz;
+ int i;
+
+ for (i = desc->ncaches - 1; i >= 0; i--) {
+ int x = snprintf(p, sz, "%s", desc->caches[i].name);
+ if (x < 0 || (size_t) x >= sz)
+ return NULL;
+ sz -= x;
+ p += x;
+ if (i > 0) {
+ if (sz < 2)
+ return NULL;
+ *p++ = mod->compat ? ',' : ':';
+ *p = '\0';
+ sz--;
+ }
+ }
+ if (desc->ncaches)
+ return buf;
+ }
+ snprintf(buf, bufsz, "%s", coldescs_cpu[col].name);
+ return buf;
+}
+
+/*
+ * [-C] backend
+ */
+static void
+print_caches_readable(struct lscpu_desc *desc, int cols[], int ncols,
+ struct lscpu_modifier *mod)
+{
+ struct libscols_table *table;
+ struct cpu_cache *cachesrc;
+ int i, end, j, shared_allsize;
+
+ scols_init_debug(0);
+
+ table = scols_new_table();
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+ if (mod->json) {
+ scols_table_enable_json(table, 1);
+ scols_table_set_name(table, "caches");
+ }
+
+ for (i = 0; i < ncols; i++) {
+ struct lscpu_coldesc *cd = &coldescs_cache[cols[i]];
+ if (!scols_table_new_column(table, cd->name, 0, cd->flags))
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ }
+
+ for (j = 0; j < 2; j++) {
+ /* First check the caches from /sys/devices */
+ if (j == 0) {
+ cachesrc = desc->caches;
+ end = desc->ncaches - 1;
+ shared_allsize = 0;
+ } else {
+ /* Check shared caches from /proc/cpuinfo s390 */
+ cachesrc = desc->ecaches;
+ end = desc->necaches - 1;
+ /* Dont use get_cache_full_size */
+ shared_allsize = 1;
+ }
+
+ for (i = end; i >= 0; i--) {
+ struct libscols_line *line;
+ struct cpu_cache *ca = &cachesrc[i];
+ int c;
+
+ line = scols_table_new_line(table, NULL);
+ if (!line)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (c = 0; c < ncols; c++) {
+ char *data = NULL;
+
+ switch (cols[c]) {
+ case COL_CACHE_NAME:
+ if (ca->name)
+ data = xstrdup(ca->name);
+ break;
+ case COL_CACHE_ONESIZE:
+ if (!ca->size)
+ break;
+ if (mod->bytes)
+ xasprintf(&data, "%" PRIu64, ca->size);
+ else
+ data = size_to_human_string(SIZE_SUFFIX_1LETTER, ca->size);
+ break;
+ case COL_CACHE_ALLSIZE:
+ {
+ uint64_t sz = 0;
+ if (shared_allsize)
+ break;
+ if (get_cache_full_size(desc, ca, &sz) != 0)
+ break;
+ if (mod->bytes)
+ xasprintf(&data, "%" PRIu64, sz);
+ else
+ data = size_to_human_string(SIZE_SUFFIX_1LETTER, sz);
+ break;
+ }
+ case COL_CACHE_WAYS:
+ if (ca->ways_of_associativity)
+ xasprintf(&data, "%u", ca->ways_of_associativity);
+ break;
+ case COL_CACHE_TYPE:
+ if (ca->type)
+ data = xstrdup(ca->type);
+ break;
+ case COL_CACHE_LEVEL:
+ if (ca->level)
+ xasprintf(&data, "%d", ca->level);
+ break;
+ case COL_CACHE_ALLOCPOL:
+ if (ca->allocation_policy)
+ data = xstrdup(ca->allocation_policy);
+ break;
+ case COL_CACHE_WRITEPOL:
+ if (ca->write_policy)
+ data = xstrdup(ca->write_policy);
+ break;
+ case COL_CACHE_PHYLINE:
+ if (ca->physical_line_partition)
+ xasprintf(&data, "%u", ca->physical_line_partition);
+ break;
+ case COL_CACHE_SETS:
+ if (ca->number_of_sets)
+ xasprintf(&data, "%u", ca->number_of_sets);
+ break;
+ case COL_CACHE_COHERENCYSIZE:
+ if (ca->coherency_line_size)
+ xasprintf(&data, "%u", ca->coherency_line_size);
+ break;
+ }
+
+ if (data && scols_line_refer_data(line, c, data))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+ }
+
+ }
+
+ scols_print_table(table);
+ scols_unref_table(table);
+}
+
+/*
+ * [-p] backend, we support two parsable formats:
+ *
+ * 1) "compatible" -- this format is compatible with the original lscpu(1)
+ * output and it contains fixed set of the columns. The CACHE columns are at
+ * the end of the line and the CACHE is not printed if the number of the caches
+ * is zero. The CACHE columns are separated by two commas, for example:
+ *
+ * $ lscpu --parse
+ * # CPU,Core,Socket,Node,,L1d,L1i,L2
+ * 0,0,0,0,,0,0,0
+ * 1,1,0,0,,1,1,0
+ *
+ * 2) "user defined output" -- this format prints always all columns without
+ * special prefix for CACHE column. If there are not CACHEs then the column is
+ * empty and the header "Cache" is printed rather than a real name of the cache.
+ * The CACHE columns are separated by ':'.
+ *
+ * $ lscpu --parse=CPU,CORE,SOCKET,NODE,CACHE
+ * # CPU,Core,Socket,Node,L1d:L1i:L2
+ * 0,0,0,0,0:0:0
+ * 1,1,0,0,1:1:0
+ */
+static void
+print_cpus_parsable(struct lscpu_desc *desc, int cols[], int ncols,
+ struct lscpu_modifier *mod)
+{
+ char buf[BUFSIZ], *data;
+ int i;
+
+ /*
+ * Header
+ */
+ printf(_(
+ "# The following is the parsable format, which can be fed to other\n"
+ "# programs. Each different item in every column has an unique ID\n"
+ "# starting from zero.\n"));
+
+ fputs("# ", stdout);
+ for (i = 0; i < ncols; i++) {
+ int col = cols[i];
+
+ if (col == COL_CPU_CACHE) {
+ if (mod->compat && !desc->ncaches)
+ continue;
+ if (mod->compat && i != 0)
+ putchar(',');
+ }
+ if (i > 0)
+ putchar(',');
+
+ data = get_cell_header(desc, col, mod, buf, sizeof(buf));
+
+ if (data && * data && col != COL_CPU_CACHE &&
+ !coldescs_cpu[col].is_abbr) {
+ /*
+ * For normal column names use mixed case (e.g. "Socket")
+ */
+ char *p = data + 1;
+
+ while (p && *p != '\0') {
+ *p = tolower((unsigned int) *p);
+ p++;
+ }
+ }
+ fputs(data && *data ? data : "", stdout);
+ }
+ putchar('\n');
+
+ /*
+ * Data
+ */
+ for (i = 0; i < desc->ncpuspos; i++) {
+ int c;
+ int cpu = real_cpu_num(desc, i);
+
+ if (desc->online) {
+ if (!mod->offline && !is_cpu_online(desc, cpu))
+ continue;
+ if (!mod->online && is_cpu_online(desc, cpu))
+ continue;
+ }
+ if (desc->present && !is_cpu_present(desc, cpu))
+ continue;
+ for (c = 0; c < ncols; c++) {
+ if (mod->compat && cols[c] == COL_CPU_CACHE) {
+ if (!desc->ncaches)
+ continue;
+ if (c > 0)
+ putchar(',');
+ }
+ if (c > 0)
+ putchar(',');
+
+ data = get_cell_data(desc, i, cols[c], mod,
+ buf, sizeof(buf));
+ fputs(data && *data ? data : "", stdout);
+ *buf = '\0';
+ }
+ putchar('\n');
+ }
+}
+
+/*
+ * [-e] backend
+ */
+static void
+print_cpus_readable(struct lscpu_desc *desc, int cols[], int ncols,
+ struct lscpu_modifier *mod)
+{
+ int i;
+ char buf[BUFSIZ];
+ const char *data;
+ struct libscols_table *table;
+
+ scols_init_debug(0);
+
+ table = scols_new_table();
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+ if (mod->json) {
+ scols_table_enable_json(table, 1);
+ scols_table_set_name(table, "cpus");
+ }
+
+ for (i = 0; i < ncols; i++) {
+ data = get_cell_header(desc, cols[i], mod, buf, sizeof(buf));
+ if (!scols_table_new_column(table, data, 0, coldescs_cpu[cols[i]].flags))
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ }
+
+ for (i = 0; i < desc->ncpuspos; i++) {
+ int c;
+ struct libscols_line *line;
+ int cpu = real_cpu_num(desc, i);
+
+ if (desc->online) {
+ if (!mod->offline && !is_cpu_online(desc, cpu))
+ continue;
+ if (!mod->online && is_cpu_online(desc, cpu))
+ continue;
+ }
+ if (desc->present && !is_cpu_present(desc, cpu))
+ continue;
+
+ line = scols_table_new_line(table, NULL);
+ if (!line)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (c = 0; c < ncols; c++) {
+ data = get_cell_data(desc, i, cols[c], mod,
+ buf, sizeof(buf));
+ if (!data || !*data)
+ data = "-";
+ if (scols_line_set_data(line, c, data))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+ }
+
+ scols_print_table(table);
+ scols_unref_table(table);
+}
+
+
+static void __attribute__ ((__format__(printf, 3, 4)))
+ add_summary_sprint(struct libscols_table *tb,
+ const char *txt,
+ const char *fmt,
+ ...)
+{
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+ char *data;
+ va_list args;
+
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ /* description column */
+ if (txt && scols_line_set_data(ln, 0, txt))
+ err(EXIT_FAILURE, _("failed to add output data"));
+
+ /* data column */
+ va_start(args, fmt);
+ xvasprintf(&data, fmt, args);
+ va_end(args);
+
+ if (data && scols_line_refer_data(ln, 1, data))
+ err(EXIT_FAILURE, _("failed to add output data"));
+}
+
+#define add_summary_n(tb, txt, num) add_summary_sprint(tb, txt, "%d", num)
+#define add_summary_s(tb, txt, str) add_summary_sprint(tb, txt, "%s", str)
+
+static void
+print_cpuset(struct libscols_table *tb,
+ const char *key, cpu_set_t *set, int hex)
+{
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+ size_t setbuflen = 7 * maxcpus;
+ char setbuf[setbuflen], *p;
+
+ if (hex) {
+ p = cpumask_create(setbuf, setbuflen, set, setsize);
+ add_summary_s(tb, key, p);
+ } else {
+ p = cpulist_create(setbuf, setbuflen, set, setsize);
+ add_summary_s(tb, key, p);
+ }
+}
+
+static int get_cache_full_size(struct lscpu_desc *desc,
+ struct cpu_cache *ca, uint64_t *res)
+{
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+ int i, nshares = 0;
+
+ /* Count number of CPUs which shares the cache */
+ for (i = 0; i < desc->ncpuspos; i++) {
+ int cpu = real_cpu_num(desc, i);
+
+ if (desc->present && !is_cpu_present(desc, cpu))
+ continue;
+ if (CPU_ISSET_S(cpu, setsize, ca->sharedmaps[0]))
+ nshares++;
+ }
+
+ /* Correction for CPU threads */
+ if (desc->nthreads > desc->ncores)
+ nshares /= (desc->nthreads / desc->ncores);
+ if (nshares < 1)
+ nshares = 1;
+
+ *res = (desc->ncores / nshares) * ca->size;
+ return 0;
+}
+
+/*
+ * default output
+ */
+static void
+print_summary(struct lscpu_desc *desc, struct lscpu_modifier *mod)
+{
+ char buf[BUFSIZ];
+ int i = 0;
+ size_t setsize = CPU_ALLOC_SIZE(maxcpus);
+ struct libscols_table *tb;
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_noheadings(tb, 1);
+ if (mod->json) {
+ scols_table_enable_json(tb, 1);
+ scols_table_set_name(tb, "lscpu");
+ }
+
+ if (scols_table_new_column(tb, "field", 0, 0) == NULL ||
+ scols_table_new_column(tb, "data", 0, SCOLS_FL_NOEXTREMES | SCOLS_FL_WRAP) == NULL)
+ err(EXIT_FAILURE, _("failed to initialize output column"));
+
+ add_summary_s(tb, _("Architecture:"), desc->arch);
+ if (desc->mode) {
+ char *p = buf;
+
+ if (desc->mode & MODE_32BIT) {
+ strcpy(p, "32-bit, ");
+ p += 8;
+ }
+ if (desc->mode & MODE_64BIT) {
+ strcpy(p, "64-bit, ");
+ p += 8;
+ }
+ *(p - 2) = '\0';
+ add_summary_s(tb, _("CPU op-mode(s):"), buf);
+ }
+#if !defined(WORDS_BIGENDIAN)
+ add_summary_s(tb, _("Byte Order:"), "Little Endian");
+#else
+ add_summary_s(tb, _("Byte Order:"), "Big Endian");
+#endif
+
+ if (desc->addrsz)
+ add_summary_s(tb, _("Address sizes:"), desc->addrsz);
+
+ add_summary_n(tb, _("CPU(s):"), desc->ncpus);
+
+ if (desc->online)
+ print_cpuset(tb, mod->hex ? _("On-line CPU(s) mask:") :
+ _("On-line CPU(s) list:"),
+ desc->online, mod->hex);
+
+ if (desc->online && CPU_COUNT_S(setsize, desc->online) != desc->ncpus) {
+ cpu_set_t *set;
+
+ /* Linux kernel provides cpuset of off-line CPUs that contains
+ * all configured CPUs (see /sys/devices/system/cpu/offline),
+ * but want to print real (present in system) off-line CPUs only.
+ */
+ set = cpuset_alloc(maxcpus, NULL, NULL);
+ if (!set)
+ err(EXIT_FAILURE, _("failed to callocate cpu set"));
+ CPU_ZERO_S(setsize, set);
+ for (i = 0; i < desc->ncpuspos; i++) {
+ int cpu = real_cpu_num(desc, i);
+ if (!is_cpu_online(desc, cpu) && is_cpu_present(desc, cpu))
+ CPU_SET_S(cpu, setsize, set);
+ }
+ print_cpuset(tb, mod->hex ? _("Off-line CPU(s) mask:") :
+ _("Off-line CPU(s) list:"),
+ set, mod->hex);
+ cpuset_free(set);
+ }
+
+ if (desc->nsockets) {
+ int threads_per_core, cores_per_socket, sockets_per_book;
+ int books_per_drawer, drawers;
+ FILE *fd;
+
+ threads_per_core = cores_per_socket = sockets_per_book = 0;
+ books_per_drawer = drawers = 0;
+ /* s390 detects its cpu topology via /proc/sysinfo, if present.
+ * Using simply the cpu topology masks in sysfs will not give
+ * usable results since everything is virtualized. E.g.
+ * virtual core 0 may have only 1 cpu, but virtual core 2 may
+ * five cpus.
+ * If the cpu topology is not exported (e.g. 2nd level guest)
+ * fall back to old calculation scheme.
+ */
+ if ((fd = ul_path_fopen(desc->procfs, "r", "sysinfo"))) {
+ int t0, t1;
+
+ while (fd && fgets(buf, sizeof(buf), fd) != NULL) {
+ if (sscanf(buf, "CPU Topology SW:%d%d%d%d%d%d",
+ &t0, &t1, &drawers, &books_per_drawer,
+ &sockets_per_book,
+ &cores_per_socket) == 6)
+ break;
+ }
+ if (fd)
+ fclose(fd);
+ }
+ if (desc->mtid)
+ threads_per_core = atoi(desc->mtid) + 1;
+ add_summary_n(tb, _("Thread(s) per core:"),
+ threads_per_core ?: desc->nthreads / desc->ncores);
+ add_summary_n(tb, _("Core(s) per socket:"),
+ cores_per_socket ?: desc->ncores / desc->nsockets);
+ if (desc->nbooks) {
+ add_summary_n(tb, _("Socket(s) per book:"),
+ sockets_per_book ?: desc->nsockets / desc->nbooks);
+ if (desc->ndrawers) {
+ add_summary_n(tb, _("Book(s) per drawer:"),
+ books_per_drawer ?: desc->nbooks / desc->ndrawers);
+ add_summary_n(tb, _("Drawer(s):"), drawers ?: desc->ndrawers);
+ } else {
+ add_summary_n(tb, _("Book(s):"), books_per_drawer ?: desc->nbooks);
+ }
+ } else {
+ add_summary_n(tb, _("Socket(s):"), sockets_per_book ?: desc->nsockets);
+ }
+ }
+ if (desc->nnodes)
+ add_summary_n(tb, _("NUMA node(s):"), desc->nnodes);
+ if (desc->vendor)
+ add_summary_s(tb, _("Vendor ID:"), desc->vendor);
+ if (desc->machinetype)
+ add_summary_s(tb, _("Machine type:"), desc->machinetype);
+ if (desc->family)
+ add_summary_s(tb, _("CPU family:"), desc->family);
+ if (desc->model || desc->revision)
+ add_summary_s(tb, _("Model:"), desc->revision ? desc->revision : desc->model);
+ if (desc->modelname || desc->cpu)
+ add_summary_s(tb, _("Model name:"), desc->cpu ? desc->cpu : desc->modelname);
+ if (desc->stepping)
+ add_summary_s(tb, _("Stepping:"), desc->stepping);
+ if (desc->freqboost >= 0)
+ add_summary_s(tb, _("Frequency boost:"), desc->freqboost ?
+ _("enabled") : _("disabled"));
+ if (desc->mhz)
+ add_summary_s(tb, _("CPU MHz:"), desc->mhz);
+ if (desc->dynamic_mhz)
+ add_summary_s(tb, _("CPU dynamic MHz:"), desc->dynamic_mhz);
+ if (desc->static_mhz)
+ add_summary_s(tb, _("CPU static MHz:"), desc->static_mhz);
+ if (desc->maxmhz)
+ add_summary_s(tb, _("CPU max MHz:"), cpu_max_mhz(desc, buf, sizeof(buf)));
+ if (desc->minmhz)
+ add_summary_s(tb, _("CPU min MHz:"), cpu_min_mhz(desc, buf, sizeof(buf)));
+ if (desc->bogomips)
+ add_summary_s(tb, _("BogoMIPS:"), desc->bogomips);
+ if (desc->virtflag) {
+ if (!strcmp(desc->virtflag, "svm"))
+ add_summary_s(tb, _("Virtualization:"), "AMD-V");
+ else if (!strcmp(desc->virtflag, "vmx"))
+ add_summary_s(tb, _("Virtualization:"), "VT-x");
+ }
+ if (desc->hypervisor)
+ add_summary_s(tb, _("Hypervisor:"), desc->hypervisor);
+ if (desc->hyper) {
+ add_summary_s(tb, _("Hypervisor vendor:"), hv_vendors[desc->hyper]);
+ add_summary_s(tb, _("Virtualization type:"), _(virt_types[desc->virtype]));
+ }
+ if (desc->dispatching >= 0)
+ add_summary_s(tb, _("Dispatching mode:"), _(disp_modes[desc->dispatching]));
+ if (desc->ncaches) {
+ for (i = desc->ncaches - 1; i >= 0; i--) {
+ uint64_t sz = 0;
+ char *tmp;
+ struct cpu_cache *ca = &desc->caches[i];
+
+ if (ca->size == 0)
+ continue;
+ if (get_cache_full_size(desc, ca, &sz) != 0 || sz == 0)
+ continue;
+ if (mod->bytes)
+ xasprintf(&tmp, "%" PRIu64, sz);
+ else
+ tmp = size_to_human_string(
+ SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+ sz);
+ snprintf(buf, sizeof(buf), _("%s cache:"), ca->name);
+ add_summary_s(tb, buf, tmp);
+ free(tmp);
+ }
+ }
+ if (desc->necaches) {
+ for (i = desc->necaches - 1; i >= 0; i--) {
+ char *tmp;
+ struct cpu_cache *ca = &desc->ecaches[i];
+
+ if (ca->size == 0)
+ continue;
+ if (mod->bytes)
+ xasprintf(&tmp, "%" PRIu64, ca->size);
+ else
+ tmp = size_to_human_string(
+ SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+ ca->size);
+ snprintf(buf, sizeof(buf), _("%s cache:"), ca->name);
+ add_summary_s(tb, buf, tmp);
+ free(tmp);
+ }
+ }
+
+ for (i = 0; i < desc->nnodes; i++) {
+ snprintf(buf, sizeof(buf), _("NUMA node%d CPU(s):"), desc->idx2nodenum[i]);
+ print_cpuset(tb, buf, desc->nodemaps[i], mod->hex);
+ }
+
+ if (desc->physsockets) {
+ add_summary_n(tb, _("Physical sockets:"), desc->physsockets);
+ add_summary_n(tb, _("Physical chips:"), desc->physchips);
+ add_summary_n(tb, _("Physical cores/chip:"), desc->physcoresperchip);
+ }
+
+ if (desc->vuls) {
+ for (i = 0; i < desc->nvuls; i++) {
+ snprintf(buf, sizeof(buf), ("Vulnerability %s:"), desc->vuls[i].name);
+ add_summary_s(tb, buf, desc->vuls[i].text);
+ }
+ }
+
+ if (desc->flags)
+ add_summary_s(tb, _("Flags:"), desc->flags);
+
+ scols_print_table(tb);
+ scols_unref_table(tb);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display information about the CPU architecture.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all print both online and offline CPUs (default for -e)\n"), out);
+ fputs(_(" -b, --online print online CPUs only (default for -p)\n"), out);
+ fputs(_(" -B, --bytes print sizes in bytes rather than in human readable format\n"), out);
+ fputs(_(" -C, --caches[=<list>] info about caches in extended readable format\n"), out);
+ fputs(_(" -c, --offline print offline CPUs only\n"), out);
+ fputs(_(" -J, --json use JSON for default or extended format\n"), out);
+ fputs(_(" -e, --extended[=<list>] print out an extended readable format\n"), out);
+ fputs(_(" -p, --parse[=<list>] print out a parsable format\n"), out);
+ fputs(_(" -s, --sysroot <dir> use specified directory as system root\n"), out);
+ fputs(_(" -x, --hex print hexadecimal masks rather than lists of CPUs\n"), out);
+ fputs(_(" -y, --physical print physical instead of logical IDs\n"), out);
+ fputs(_(" --output-all print all available columns for -e, -p or -C\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ fputs(_("\nAvailable output columns for -e or -p:\n"), out);
+ for (i = 0; i < ARRAY_SIZE(coldescs_cpu); i++)
+ fprintf(out, " %13s %s\n", coldescs_cpu[i].name, _(coldescs_cpu[i].help));
+
+ fputs(_("\nAvailable output columns for -C:\n"), out);
+ for (i = 0; i < ARRAY_SIZE(coldescs_cache); i++)
+ fprintf(out, " %13s %s\n", coldescs_cache[i].name, _(coldescs_cache[i].help));
+
+ printf(USAGE_MAN_TAIL("lscpu(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct lscpu_modifier _mod = { .mode = OUTPUT_SUMMARY }, *mod = &_mod;
+ struct lscpu_desc _desc = { .flags = NULL }, *desc = &_desc;
+ int c, i, all = 0;
+ int columns[ARRAY_SIZE(coldescs_cpu)], ncolumns = 0;
+ int cpu_modifier_specified = 0;
+ size_t setsize;
+
+ enum {
+ OPT_OUTPUT_ALL = CHAR_MAX + 1,
+ };
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "online", no_argument, NULL, 'b' },
+ { "bytes", no_argument, NULL, 'B' },
+ { "caches", optional_argument, NULL, 'C' },
+ { "offline", no_argument, NULL, 'c' },
+ { "help", no_argument, NULL, 'h' },
+ { "extended", optional_argument, NULL, 'e' },
+ { "json", no_argument, NULL, 'J' },
+ { "parse", optional_argument, NULL, 'p' },
+ { "sysroot", required_argument, NULL, 's' },
+ { "physical", no_argument, NULL, 'y' },
+ { "hex", no_argument, NULL, 'x' },
+ { "version", no_argument, NULL, 'V' },
+ { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'C','e','p' },
+ { 'a','b','c' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "aBbC::ce::hJp::s:xyV", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'a':
+ mod->online = mod->offline = 1;
+ cpu_modifier_specified = 1;
+ break;
+ case 'B':
+ mod->bytes = 1;
+ break;
+ case 'b':
+ mod->online = 1;
+ cpu_modifier_specified = 1;
+ break;
+ case 'c':
+ mod->offline = 1;
+ cpu_modifier_specified = 1;
+ break;
+ case 'C':
+ if (optarg) {
+ if (*optarg == '=')
+ optarg++;
+ ncolumns = string_to_idarray(optarg,
+ columns, ARRAY_SIZE(columns),
+ cache_column_name_to_id);
+ if (ncolumns < 0)
+ return EXIT_FAILURE;
+ }
+ mod->mode = OUTPUT_CACHES;
+ break;
+ case 'J':
+ mod->json = 1;
+ break;
+ case 'p':
+ case 'e':
+ if (optarg) {
+ if (*optarg == '=')
+ optarg++;
+ ncolumns = string_to_idarray(optarg,
+ columns, ARRAY_SIZE(columns),
+ cpu_column_name_to_id);
+ if (ncolumns < 0)
+ return EXIT_FAILURE;
+ }
+ mod->mode = c == 'p' ? OUTPUT_PARSABLE : OUTPUT_READABLE;
+ break;
+ case 's':
+ desc->prefix = optarg;
+ mod->system = SYSTEM_SNAPSHOT;
+ break;
+ case 'x':
+ mod->hex = 1;
+ break;
+ case 'y':
+ mod->physical = 1;
+ break;
+ case OPT_OUTPUT_ALL:
+ all = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (all && ncolumns == 0) {
+ size_t sz, maxsz = mod->mode == OUTPUT_CACHES ?
+ ARRAY_SIZE(coldescs_cache) :
+ ARRAY_SIZE(coldescs_cpu);
+
+ for (sz = 0; sz < maxsz; sz++)
+ columns[ncolumns++] = sz;
+ }
+
+ if (cpu_modifier_specified && mod->mode == OUTPUT_SUMMARY) {
+ fprintf(stderr,
+ _("%s: options --all, --online and --offline may only "
+ "be used with options --extended or --parse.\n"),
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (argc != optind) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ /* set default cpu display mode if none was specified */
+ if (!mod->online && !mod->offline) {
+ mod->online = 1;
+ mod->offline = mod->mode == OUTPUT_READABLE ? 1 : 0;
+ }
+
+ ul_path_init_debug();
+
+ /* /sys/devices/system/cpu */
+ desc->syscpu = ul_new_path(_PATH_SYS_CPU);
+ if (!desc->syscpu)
+ err(EXIT_FAILURE, _("failed to initialize CPUs sysfs handler"));
+ if (desc->prefix)
+ ul_path_set_prefix(desc->syscpu, desc->prefix);
+
+ /* /proc */
+ desc->procfs = ul_new_path("/proc");
+ if (!desc->procfs)
+ err(EXIT_FAILURE, _("failed to initialize procfs handler"));
+ if (desc->prefix)
+ ul_path_set_prefix(desc->procfs, desc->prefix);
+
+ read_basicinfo(desc, mod);
+
+ setsize = CPU_ALLOC_SIZE(maxcpus);
+
+ for (i = 0; i < desc->ncpuspos; i++) {
+ /* only consider present CPUs */
+ if (desc->present &&
+ !CPU_ISSET_S(real_cpu_num(desc, i), setsize, desc->present))
+ continue;
+ read_topology(desc, i);
+ read_cache(desc, i);
+ read_polarization(desc, i);
+ read_address(desc, i);
+ read_configured(desc, i);
+ read_max_mhz(desc, i);
+ read_min_mhz(desc, i);
+ }
+
+ if (desc->caches)
+ qsort(desc->caches, desc->ncaches,
+ sizeof(struct cpu_cache), cachecmp);
+
+ if (desc->ecaches)
+ qsort(desc->ecaches, desc->necaches,
+ sizeof(struct cpu_cache), cachecmp);
+
+ read_nodes(desc);
+ read_hypervisor(desc, mod);
+ arm_cpu_decode(desc);
+
+ switch(mod->mode) {
+ case OUTPUT_SUMMARY:
+ print_summary(desc, mod);
+ break;
+ case OUTPUT_CACHES:
+ if (!ncolumns) {
+ columns[ncolumns++] = COL_CACHE_NAME;
+ columns[ncolumns++] = COL_CACHE_ONESIZE;
+ columns[ncolumns++] = COL_CACHE_ALLSIZE;
+ columns[ncolumns++] = COL_CACHE_WAYS;
+ columns[ncolumns++] = COL_CACHE_TYPE;
+ columns[ncolumns++] = COL_CACHE_LEVEL;
+ columns[ncolumns++] = COL_CACHE_SETS;
+ columns[ncolumns++] = COL_CACHE_PHYLINE;
+ columns[ncolumns++] = COL_CACHE_COHERENCYSIZE;
+ }
+ print_caches_readable(desc, columns, ncolumns, mod);
+ break;
+ case OUTPUT_PARSABLE:
+ if (!ncolumns) {
+ columns[ncolumns++] = COL_CPU_CPU;
+ columns[ncolumns++] = COL_CPU_CORE;
+ columns[ncolumns++] = COL_CPU_SOCKET;
+ columns[ncolumns++] = COL_CPU_NODE;
+ columns[ncolumns++] = COL_CPU_CACHE;
+ mod->compat = 1;
+ }
+ print_cpus_parsable(desc, columns, ncolumns, mod);
+ break;
+ case OUTPUT_READABLE:
+ if (!ncolumns) {
+ /* No list was given. Just print whatever is there. */
+ columns[ncolumns++] = COL_CPU_CPU;
+ if (desc->nodemaps)
+ columns[ncolumns++] = COL_CPU_NODE;
+ if (desc->drawermaps)
+ columns[ncolumns++] = COL_CPU_DRAWER;
+ if (desc->bookmaps)
+ columns[ncolumns++] = COL_CPU_BOOK;
+ if (desc->socketmaps)
+ columns[ncolumns++] = COL_CPU_SOCKET;
+ if (desc->coremaps)
+ columns[ncolumns++] = COL_CPU_CORE;
+ if (desc->caches)
+ columns[ncolumns++] = COL_CPU_CACHE;
+ if (desc->online)
+ columns[ncolumns++] = COL_CPU_ONLINE;
+ if (desc->configured)
+ columns[ncolumns++] = COL_CPU_CONFIGURED;
+ if (desc->polarization)
+ columns[ncolumns++] = COL_CPU_POLARIZATION;
+ if (desc->addresses)
+ columns[ncolumns++] = COL_CPU_ADDRESS;
+ if (desc->maxmhz)
+ columns[ncolumns++] = COL_CPU_MAXMHZ;
+ if (desc->minmhz)
+ columns[ncolumns++] = COL_CPU_MINMHZ;
+ }
+ print_cpus_readable(desc, columns, ncolumns, mod);
+ break;
+ }
+
+ ul_unref_path(desc->syscpu);
+ ul_unref_path(desc->procfs);
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/lscpu.h b/sys-utils/lscpu.h
new file mode 100644
index 0000000..13af2ad
--- /dev/null
+++ b/sys-utils/lscpu.h
@@ -0,0 +1,214 @@
+#ifndef LSCPU_H
+#define LSCPU_H
+
+#include "c.h"
+#include "nls.h"
+#include "cpuset.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "bitops.h"
+#include "path.h"
+#include "pathnames.h"
+#include "all-io.h"
+
+/* virtualization types */
+enum {
+ VIRT_NONE = 0,
+ VIRT_PARA,
+ VIRT_FULL,
+ VIRT_CONT
+};
+
+/* hypervisor vendors */
+enum {
+ HYPER_NONE = 0,
+ HYPER_XEN,
+ HYPER_KVM,
+ HYPER_MSHV,
+ HYPER_VMWARE,
+ HYPER_IBM, /* sys-z powervm */
+ HYPER_VSERVER,
+ HYPER_UML,
+ HYPER_INNOTEK, /* VBOX */
+ HYPER_HITACHI,
+ HYPER_PARALLELS, /* OpenVZ/VIrtuozzo */
+ HYPER_VBOX,
+ HYPER_OS400,
+ HYPER_PHYP,
+ HYPER_SPAR,
+ HYPER_WSL,
+};
+
+/* CPU modes */
+enum {
+ MODE_32BIT = (1 << 1),
+ MODE_64BIT = (1 << 2)
+};
+
+/* cache(s) description */
+struct cpu_cache {
+ char *name;
+ char *type;
+ char *allocation_policy;
+ char *write_policy;
+
+ int level;
+ uint64_t size;
+
+ unsigned int ways_of_associativity;
+ unsigned int physical_line_partition;
+ unsigned int number_of_sets;
+ unsigned int coherency_line_size;
+
+ int nsharedmaps;
+ cpu_set_t **sharedmaps;
+};
+
+/* dispatching modes */
+enum {
+ DISP_HORIZONTAL = 0,
+ DISP_VERTICAL = 1
+};
+
+/* cpu polarization */
+enum {
+ POLAR_UNKNOWN = 0,
+ POLAR_VLOW,
+ POLAR_VMEDIUM,
+ POLAR_VHIGH,
+ POLAR_HORIZONTAL
+};
+
+struct polarization_modes {
+ char *parsable;
+ char *readable;
+};
+
+struct cpu_vulnerability {
+ char *name;
+ char *text;
+};
+
+/* global description */
+struct lscpu_desc {
+ const char *prefix; /* path to /sys and /proc snapshot or NULL */
+
+ struct path_cxt *syscpu; /* _PATH_SYS_CPU path handler */
+ struct path_cxt *procfs; /* /proc path handler */
+
+ char *arch;
+ char *vendor;
+ char *machinetype; /* s390 */
+ char *family;
+ char *model;
+ char *modelname;
+ char *revision; /* alternative for model (ppc) */
+ char *cpu; /* alternative for modelname (ppc, sparc) */
+ char *virtflag; /* virtualization flag (vmx, svm) */
+ char *hypervisor; /* hypervisor software */
+ int hyper; /* hypervisor vendor ID */
+ int virtype; /* VIRT_PARA|FULL|NONE ? */
+ char *mhz;
+ char *dynamic_mhz; /* dynamic mega hertz (s390) */
+ char *static_mhz; /* static mega hertz (s390) */
+ char **maxmhz; /* maximum mega hertz */
+ char **minmhz; /* minimum mega hertz */
+ char *stepping;
+ char *bogomips;
+ char *flags;
+ char *mtid; /* maximum thread id (s390) */
+ char *addrsz; /* address sizes */
+ int dispatching; /* none, horizontal or vertical */
+ int freqboost; /* -1 if not available */
+ int mode; /* rm, lm or/and tm */
+
+ int ncpuspos; /* maximal possible CPUs */
+ int ncpus; /* number of present CPUs */
+ cpu_set_t *present; /* mask with present CPUs */
+ cpu_set_t *online; /* mask with online CPUs */
+
+ int nthreads; /* number of online threads */
+
+ int ncaches;
+ struct cpu_cache *caches;
+
+ int necaches; /* extra caches (s390) */
+ struct cpu_cache *ecaches;
+
+ struct cpu_vulnerability *vuls; /* array of CPU vulnerabilities */
+ int nvuls; /* number of CPU vulnerabilities */
+
+ /*
+ * All maps are sequentially indexed (0..ncpuspos), the array index
+ * does not have match with cpuX number as presented by kernel. You
+ * have to use real_cpu_num() to get the real cpuX number.
+ *
+ * For example, the possible system CPUs are: 1,3,5, it means that
+ * ncpuspos=3, so all arrays are in range 0..3.
+ */
+ int *idx2cpunum; /* mapping index to CPU num */
+
+ int nnodes; /* number of NUMA modes */
+ int *idx2nodenum; /* Support for discontinuous nodes */
+ cpu_set_t **nodemaps; /* array with NUMA nodes */
+
+ /* drawers -- based on drawer_siblings (internal kernel map of cpuX's
+ * hardware threads within the same drawer */
+ int ndrawers; /* number of all online drawers */
+ cpu_set_t **drawermaps; /* unique drawer_siblings */
+ int *drawerids; /* physical drawer ids */
+
+ /* books -- based on book_siblings (internal kernel map of cpuX's
+ * hardware threads within the same book */
+ int nbooks; /* number of all online books */
+ cpu_set_t **bookmaps; /* unique book_siblings */
+ int *bookids; /* physical book ids */
+
+ /* sockets -- based on core_siblings (internal kernel map of cpuX's
+ * hardware threads within the same physical_package_id (socket)) */
+ int nsockets; /* number of all online sockets */
+ cpu_set_t **socketmaps; /* unique core_siblings */
+ int *socketids; /* physical socket ids */
+
+ /* cores -- based on thread_siblings (internal kernel map of cpuX's
+ * hardware threads within the same core as cpuX) */
+ int ncores; /* number of all online cores */
+ cpu_set_t **coremaps; /* unique thread_siblings */
+ int *coreids; /* physical core ids */
+
+ int *polarization; /* cpu polarization */
+ int *addresses; /* physical cpu addresses */
+ int *configured; /* cpu configured */
+ int physsockets; /* Physical sockets (modules) */
+ int physchips; /* Physical chips */
+ int physcoresperchip; /* Physical cores per chip */
+};
+
+enum {
+ OUTPUT_SUMMARY = 0, /* default */
+ OUTPUT_PARSABLE, /* -p */
+ OUTPUT_READABLE, /* -e */
+ OUTPUT_CACHES /* -C */
+};
+
+enum {
+ SYSTEM_LIVE = 0, /* analyzing a live system */
+ SYSTEM_SNAPSHOT, /* analyzing a snapshot of a different system */
+};
+
+struct lscpu_modifier {
+ int mode; /* OUTPUT_* */
+ int system; /* SYSTEM_* */
+ unsigned int hex:1, /* print CPU masks rather than CPU lists */
+ compat:1, /* use backwardly compatible format */
+ online:1, /* print online CPUs */
+ offline:1, /* print offline CPUs */
+ json:1, /* JSON output format */
+ bytes:1, /* output sizes in bytes */
+ physical:1; /* use physical numbers */
+};
+
+extern int read_hypervisor_dmi(void);
+extern void arm_cpu_decode(struct lscpu_desc *desc);
+
+#endif /* LSCPU_H */
diff --git a/sys-utils/lsipc.1 b/sys-utils/lsipc.1
new file mode 100644
index 0000000..b58b4cd
--- /dev/null
+++ b/sys-utils/lsipc.1
@@ -0,0 +1,140 @@
+.\" Copyright 2015 Ondrej Oprala(ooprala@redhat.com)
+.\" May be distributed under the GNU General Public License
+.TH LSIPC "1" "November 2015" "util-linux" "User Commands"
+.SH NAME
+lsipc \- show information on IPC facilities currently employed in the system
+.SH SYNOPSIS
+.B lsipc
+[options]
+.SH DESCRIPTION
+.B lsipc
+shows information on the System V inter-process communication facilities
+for which the calling process has read access.
+.SH OPTIONS
+.TP
+\fB\-i\fR, \fB\-\-id\fR \fIid\fR
+Show full details on just the one resource element identified by
+.IR id .
+This option needs to be combined with one of the three resource options:
+.BR \-m ,
+.BR \-q " or"
+.BR \-s .
+It is possible to override the default output format for this option with the
+\fB\-\-list\fR, \fB\-\-raw\fR, \fB\-\-json\fR or \fB\-\-export\fR option.
+.TP
+\fB\-g\fR, \fB\-\-global\fR
+Show system-wide usage and limits of IPC resources.
+This option may be combined with one of the three resource options:
+.BR \-m ,
+.BR \-q " or"
+.BR \-s .
+The default is to show information about all resources.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SS "Resource options"
+.TP
+\fB\-m\fR, \fB\-\-shmems\fR
+Write information about active shared memory segments.
+.TP
+\fB\-q\fR, \fB\-\-queues\fR
+Write information about active message queues.
+.TP
+\fB\-s\fR, \fB\-\-semaphores\fR
+Write information about active semaphore sets.
+.SS "Output formatting"
+.TP
+\fB\-c\fR, \fB\-\-creator\fR
+Show creator and owner.
+.TP
+\fB\-e\fR, \fB\-\-export\fR
+Output data in the format of NAME=VALUE.
+.TP
+\fB\-J\fR, \fB\-\-json\fR
+Use the JSON output format.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+Use the list output format. This is the default, except when \fB\-\-id\fR
+is used.
+.TP
+\fB\-n\fR, \fB\-\-newline\fR
+Display each piece of information on a separate line.
+.TP
+\fB\-\-noheadings\fR
+Do not print a header line.
+.TP
+\fB\-\-notruncate\fR
+Don't truncate output.
+.TP
+\fB\-o\fR, \fB\-\-output \fIlist\fP
+Specify which output columns to print. Use
+.B \-\-help
+to get a list of all supported columns.
+.TP
+\fB\-b\fR, \fB\-\-bytes\fR
+Print size in bytes rather than in human readable format.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+Raw output (no columnation).
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+Write time information. The time of the last control operation that changed
+the access permissions for all facilities, the time of the last
+.BR msgsnd (2)
+and
+.BR msgrcv (2)
+operations on message queues, the time of the last
+.BR shmat (2)
+and
+.BR shmdt (2)
+operations on shared memory, and the time of the last
+.BR semop (2)
+operation on semaphores.
+.TP
+\fB\-\-time\-format\fR \fItype\fP
+Display dates in short, full or iso format. The default is short, this time
+format is designed to be space efficient and human readable.
+.TP
+\fB\-P\fR, \fB\-\-numeric\-perms\fR
+Print numeric permissions in PERMS column.
+
+.SH EXIT STATUS
+.TP
+0
+if OK,
+.TP
+1
+if incorrect arguments specified,
+.TP
+2
+if a serious error occurs.
+.SH HISTORY
+The \fBlsipc\fP utility is inspired by the \fBipcs\fP utility.
+.SH AUTHORS
+.MT ooprala@redhat.com
+Ondrej Oprala
+.ME
+.br
+.MT kzak@redhat.com
+Karel Zak
+.ME
+
+.SH SEE ALSO
+.BR ipcmk (1),
+.BR ipcrm (1),
+.BR msgrcv (2),
+.BR msgsnd (2),
+.BR semget (2),
+.BR semop (2),
+.BR shmat (2),
+.BR shmdt (2),
+.BR shmget (2),
+.BR sysvipc (7)
+.SH AVAILABILITY
+The lsipc command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/lsipc.c b/sys-utils/lsipc.c
new file mode 100644
index 0000000..debd9a9
--- /dev/null
+++ b/sys-utils/lsipc.c
@@ -0,0 +1,1338 @@
+/*
+ * lsipc - List information about IPC instances employed in the system
+ *
+ * Copyright (C) 2015 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2015 Karel Zak <ooprala@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *
+ * lsipc is inspired by the ipcs utility. The aim is to create
+ * a utility unencumbered by a standard to provide more flexible
+ * means of controlling the output.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+#include "strutils.h"
+#include "optutils.h"
+#include "xalloc.h"
+#include "procutils.h"
+#include "ipcutils.h"
+#include "timeutils.h"
+
+/*
+ * time modes
+ * */
+enum {
+ TIME_INVALID = 0,
+ TIME_SHORT,
+ TIME_FULL,
+ TIME_ISO
+};
+
+/*
+ * IDs
+ */
+enum {
+ /* generic */
+ COLDESC_IDX_GEN_FIRST = 0,
+ COL_KEY = COLDESC_IDX_GEN_FIRST,
+ COL_ID,
+ COL_OWNER,
+ COL_PERMS,
+ COL_CUID,
+ COL_CUSER,
+ COL_CGID,
+ COL_CGROUP,
+ COL_UID,
+ COL_USER,
+ COL_GID,
+ COL_GROUP,
+ COL_CTIME,
+ COLDESC_IDX_GEN_LAST = COL_CTIME,
+
+ /* msgq-specific */
+ COLDESC_IDX_MSG_FIRST,
+ COL_USEDBYTES = COLDESC_IDX_MSG_FIRST,
+ COL_MSGS,
+ COL_SEND,
+ COL_RECV,
+ COL_LSPID,
+ COL_LRPID,
+ COLDESC_IDX_MSG_LAST = COL_LRPID,
+
+ /* shm-specific */
+ COLDESC_IDX_SHM_FIRST,
+ COL_SIZE = COLDESC_IDX_SHM_FIRST,
+ COL_NATTCH,
+ COL_STATUS,
+ COL_ATTACH,
+ COL_DETACH,
+ COL_COMMAND,
+ COL_CPID,
+ COL_LPID,
+ COLDESC_IDX_SHM_LAST = COL_LPID,
+
+ /* sem-specific */
+ COLDESC_IDX_SEM_FIRST,
+ COL_NSEMS = COLDESC_IDX_SEM_FIRST,
+ COL_OTIME,
+ COLDESC_IDX_SEM_LAST = COL_OTIME,
+
+ /* summary (--global) */
+ COLDESC_IDX_SUM_FIRST,
+ COL_RESOURCE = COLDESC_IDX_SUM_FIRST,
+ COL_DESC,
+ COL_LIMIT,
+ COL_USED,
+ COL_USEPERC,
+ COLDESC_IDX_SUM_LAST = COL_USEPERC
+};
+
+/* not all columns apply to all options, so we specify a legal range for each */
+static size_t LOWER, UPPER;
+
+/*
+ * output modes
+ */
+enum {
+ OUT_EXPORT = 1,
+ OUT_NEWLINE,
+ OUT_RAW,
+ OUT_JSON,
+ OUT_PRETTY,
+ OUT_LIST
+};
+
+struct lsipc_control {
+ int outmode;
+ unsigned int noheadings : 1, /* don't print header line */
+ notrunc : 1, /* don't truncate columns */
+ bytes : 1, /* SIZE in bytes */
+ numperms : 1, /* numeric permissions */
+ time_mode : 2;
+};
+
+struct lsipc_coldesc {
+ const char *name;
+ const char *help;
+ const char *pretty_name;
+
+ double whint; /* width hint */
+ long flag;
+};
+
+static const struct lsipc_coldesc coldescs[] =
+{
+ /* common */
+ [COL_KEY] = { "KEY", N_("Resource key"), N_("Key"), 1},
+ [COL_ID] = { "ID", N_("Resource ID"), N_("ID"), 1},
+ [COL_OWNER] = { "OWNER", N_("Owner's username or UID"), N_("Owner"), 1, SCOLS_FL_RIGHT},
+ [COL_PERMS] = { "PERMS", N_("Permissions"), N_("Permissions"), 1, SCOLS_FL_RIGHT},
+ [COL_CUID] = { "CUID", N_("Creator UID"), N_("Creator UID"), 1, SCOLS_FL_RIGHT},
+ [COL_CUSER] = { "CUSER", N_("Creator user"), N_("Creator user"), 1 },
+ [COL_CGID] = { "CGID", N_("Creator GID"), N_("Creator GID"), 1, SCOLS_FL_RIGHT},
+ [COL_CGROUP] = { "CGROUP", N_("Creator group"), N_("Creator group"), 1 },
+ [COL_UID] = { "UID", N_("User ID"), N_("UID"), 1, SCOLS_FL_RIGHT},
+ [COL_USER] = { "USER", N_("User name"), N_("User name"), 1},
+ [COL_GID] = { "GID", N_("Group ID"), N_("GID"), 1, SCOLS_FL_RIGHT},
+ [COL_GROUP] = { "GROUP", N_("Group name"), N_("Group name"), 1},
+ [COL_CTIME] = { "CTIME", N_("Time of the last change"), N_("Last change"), 1, SCOLS_FL_RIGHT},
+
+ /* msgq-specific */
+ [COL_USEDBYTES] = { "USEDBYTES",N_("Bytes used"), N_("Bytes used"), 1, SCOLS_FL_RIGHT},
+ [COL_MSGS] = { "MSGS", N_("Number of messages"), N_("Messages"), 1},
+ [COL_SEND] = { "SEND", N_("Time of last msg sent"), N_("Msg sent"), 1, SCOLS_FL_RIGHT},
+ [COL_RECV] = { "RECV", N_("Time of last msg received"), N_("Msg received"), 1, SCOLS_FL_RIGHT},
+ [COL_LSPID] = { "LSPID", N_("PID of the last msg sender"), N_("Msg sender"), 1, SCOLS_FL_RIGHT},
+ [COL_LRPID] = { "LRPID", N_("PID of the last msg receiver"), N_("Msg receiver"), 1, SCOLS_FL_RIGHT},
+
+ /* shm-specific */
+ [COL_SIZE] = { "SIZE", N_("Segment size"), N_("Segment size"), 1, SCOLS_FL_RIGHT},
+ [COL_NATTCH] = { "NATTCH", N_("Number of attached processes"), N_("Attached processes"), 1, SCOLS_FL_RIGHT},
+ [COL_STATUS] = { "STATUS", N_("Status"), N_("Status"), 1, SCOLS_FL_NOEXTREMES},
+ [COL_ATTACH] = { "ATTACH", N_("Attach time"), N_("Attach time"), 1, SCOLS_FL_RIGHT},
+ [COL_DETACH] = { "DETACH", N_("Detach time"), N_("Detach time"), 1, SCOLS_FL_RIGHT},
+ [COL_COMMAND] = { "COMMAND", N_("Creator command line"), N_("Creator command"), 0, SCOLS_FL_TRUNC},
+ [COL_CPID] = { "CPID", N_("PID of the creator"), N_("Creator PID"), 1, SCOLS_FL_RIGHT},
+ [COL_LPID] = { "LPID", N_("PID of last user"), N_("Last user PID"), 1, SCOLS_FL_RIGHT},
+
+ /* sem-specific */
+ [COL_NSEMS] = { "NSEMS", N_("Number of semaphores"), N_("Semaphores"), 1, SCOLS_FL_RIGHT},
+ [COL_OTIME] = { "OTIME", N_("Time of the last operation"), N_("Last operation"), 1, SCOLS_FL_RIGHT},
+
+ /* cols for summarized information */
+ [COL_RESOURCE] = { "RESOURCE", N_("Resource name"), N_("Resource"), 1 },
+ [COL_DESC] = { "DESCRIPTION",N_("Resource description"), N_("Description"), 1 },
+ [COL_USED] = { "USED", N_("Currently used"), N_("Used"), 1, SCOLS_FL_RIGHT },
+ [COL_USEPERC] = { "USE%", N_("Currently use percentage"), N_("Use"), 1, SCOLS_FL_RIGHT },
+ [COL_LIMIT] = { "LIMIT", N_("System-wide limit"), N_("Limit"), 1, SCOLS_FL_RIGHT },
+};
+
+
+/* columns[] array specifies all currently wanted output column. The columns
+ * are defined by coldescs[] array and you can specify (on command line) each
+ * column twice. That's enough, dynamically allocated array of the columns is
+ * unnecessary overkill and over-engineering in this case */
+static int columns[ARRAY_SIZE(coldescs) * 2];
+static size_t ncolumns;
+
+static inline size_t err_columns_index(size_t arysz, size_t idx)
+{
+ if (idx >= arysz)
+ errx(EXIT_FAILURE, _("too many columns specified, "
+ "the limit is %zu columns"),
+ arysz - 1);
+ return idx;
+}
+
+#define add_column(ary, n, id) \
+ ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
+ const char *cn = coldescs[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) {
+ if (i > COL_CTIME) {
+ if (i >= LOWER && i <= UPPER)
+ return i;
+
+ warnx(_("column %s does not apply to the specified IPC"), name);
+ return -1;
+ }
+
+ return i;
+ }
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int get_column_id(int num)
+{
+ assert(num >= 0);
+ assert((size_t) num < ncolumns);
+ assert((size_t) columns[num] < ARRAY_SIZE(coldescs));
+ return columns[num];
+}
+
+static const struct lsipc_coldesc *get_column_desc(int num)
+{
+ return &coldescs[ get_column_id(num) ];
+}
+
+static char *get_username(struct passwd **pw, uid_t id)
+{
+ if (!*pw || (*pw)->pw_uid != id)
+ *pw = getpwuid(id);
+
+ return *pw ? xstrdup((*pw)->pw_name) : NULL;
+}
+
+static char *get_groupname(struct group **gr, gid_t id)
+{
+ if (!*gr || (*gr)->gr_gid != id)
+ *gr = getgrgid(id);
+
+ return *gr ? xstrdup((*gr)->gr_name) : NULL;
+}
+
+static int parse_time_mode(const char *s)
+{
+ struct lsipc_timefmt {
+ const char *name;
+ const int val;
+ };
+ static const struct lsipc_timefmt timefmts[] = {
+ {"iso", TIME_ISO},
+ {"full", TIME_FULL},
+ {"short", TIME_SHORT},
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+ if (strcmp(timefmts[i].name, s) == 0)
+ return timefmts[i].val;
+ }
+ errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Show information on IPC facilities.\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Resource options:\n"), out);
+ fputs(_(" -m, --shmems shared memory segments\n"), out);
+ fputs(_(" -q, --queues message queues\n"), out);
+ fputs(_(" -s, --semaphores semaphores\n"), out);
+ fputs(_(" -g, --global info about system-wide usage (may be used with -m, -q and -s)\n"), out);
+ fputs(_(" -i, --id <id> print details on resource identified by <id>\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" --noheadings don't print headings\n"), out);
+ fputs(_(" --notruncate don't truncate output\n"), out);
+ fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out);
+ fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out);
+ fputs(_(" -c, --creator show creator and owner\n"), out);
+ fputs(_(" -e, --export display in an export-able output format\n"), out);
+ fputs(_(" -J, --json use the JSON output format\n"), out);
+ fputs(_(" -n, --newline display each piece of information on a new line\n"), out);
+ fputs(_(" -l, --list force list output format (for example with --id)\n"), out);
+ fputs(_(" -o, --output[=<list>] define the columns to output\n"), out);
+ fputs(_(" -P, --numeric-perms print numeric permissions (PERMS column)\n"), out);
+ fputs(_(" -r, --raw display in raw mode\n"), out);
+ fputs(_(" -t, --time show attach, detach and change times\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+
+ fprintf(out, _("\nGeneric columns:\n"));
+ for (i = COLDESC_IDX_GEN_FIRST; i <= COLDESC_IDX_GEN_LAST; i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ fprintf(out, _("\nShared-memory columns (--shmems):\n"));
+ for (i = COLDESC_IDX_SHM_FIRST; i <= COLDESC_IDX_SHM_LAST; i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ fprintf(out, _("\nMessage-queue columns (--queues):\n"));
+ for (i = COLDESC_IDX_MSG_FIRST; i <= COLDESC_IDX_MSG_LAST; i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ fprintf(out, _("\nSemaphore columns (--semaphores):\n"));
+ for (i = COLDESC_IDX_SEM_FIRST; i <= COLDESC_IDX_SEM_LAST; i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ fprintf(out, _("\nSummary columns (--global):\n"));
+ for (i = COLDESC_IDX_SUM_FIRST; i <= COLDESC_IDX_SUM_LAST; i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ printf(USAGE_MAN_TAIL("lsipc(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static struct libscols_table *new_table(struct lsipc_control *ctl)
+{
+ struct libscols_table *table = scols_new_table();
+
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ if (ctl->noheadings)
+ scols_table_enable_noheadings(table, 1);
+
+ switch(ctl->outmode) {
+ case OUT_NEWLINE:
+ scols_table_set_column_separator(table, "\n");
+ /* fallthrough */
+ case OUT_EXPORT:
+ scols_table_enable_export(table, 1);
+ break;
+ case OUT_RAW:
+ scols_table_enable_raw(table, 1);
+ break;
+ case OUT_PRETTY:
+ scols_table_enable_noheadings(table, 1);
+ break;
+ case OUT_JSON:
+ scols_table_enable_json(table, 1);
+ break;
+ default:
+ break;
+ }
+ return table;
+}
+
+static struct libscols_table *setup_table(struct lsipc_control *ctl)
+{
+ struct libscols_table *table = new_table(ctl);
+ size_t n;
+
+ for (n = 0; n < ncolumns; n++) {
+ const struct lsipc_coldesc *desc = get_column_desc(n);
+ int flags = desc->flag;
+
+ if (ctl->notrunc)
+ flags &= ~SCOLS_FL_TRUNC;
+ if (!scols_table_new_column(table, desc->name, desc->whint, flags))
+ goto fail;
+ }
+ return table;
+fail:
+ scols_unref_table(table);
+ return NULL;
+}
+
+static int print_pretty(struct libscols_table *table)
+{
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ struct libscols_column *col;
+ struct libscols_cell *data;
+ struct libscols_line *ln;
+ const char *hstr, *dstr;
+ int n = 0;
+
+ ln = scols_table_get_line(table, 0);
+ while (!scols_table_next_column(table, itr, &col)) {
+
+ data = scols_line_get_cell(ln, n);
+
+ hstr = N_(get_column_desc(n)->pretty_name);
+ dstr = scols_cell_get_data(data);
+
+ if (dstr)
+ printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
+ ++n;
+ }
+
+ /* this is used to pretty-print detailed info about a semaphore array */
+ if (ln) {
+ struct libscols_table *subtab = scols_line_get_userdata(ln);
+ if (subtab) {
+ printf(_("Elements:\n\n"));
+ scols_print_table(subtab);
+ }
+ }
+
+ scols_free_iter(itr);
+ return 0;
+
+}
+
+static int print_table(struct lsipc_control *ctl, struct libscols_table *tb)
+{
+ if (ctl->outmode == OUT_PRETTY)
+ print_pretty(tb);
+ else
+ scols_print_table(tb);
+ return 0;
+}
+static struct timeval now;
+
+static char *make_time(int mode, time_t time)
+{
+ char buf[64] = {0};
+
+ switch(mode) {
+ case TIME_FULL:
+ {
+ struct tm tm;
+ char *s;
+
+ localtime_r(&time, &tm);
+ asctime_r(&tm, buf);
+ if (*(s = buf + strlen(buf) - 1) == '\n')
+ *s = '\0';
+ break;
+ }
+ case TIME_SHORT:
+ strtime_short(&time, &now, 0, buf, sizeof(buf));
+ break;
+ case TIME_ISO:
+ strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
+ break;
+ default:
+ errx(EXIT_FAILURE, _("unsupported time type"));
+ }
+ return xstrdup(buf);
+}
+
+static void global_set_data(struct libscols_table *tb, const char *resource,
+ const char *desc, uintmax_t used, uintmax_t limit, int usage)
+{
+ struct libscols_line *ln;
+ size_t n;
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (n = 0; n < ncolumns; n++) {
+ int rc = 0;
+ char *arg = NULL;
+
+ switch (get_column_id(n)) {
+ case COL_RESOURCE:
+ rc = scols_line_set_data(ln, n, resource);
+ break;
+ case COL_DESC:
+ rc = scols_line_set_data(ln, n, desc);
+ break;
+ case COL_USED:
+ if (usage) {
+ xasprintf(&arg, "%ju", used);
+ rc = scols_line_refer_data(ln, n, arg);
+ } else
+ rc = scols_line_set_data(ln, n, "-");
+ break;
+ case COL_USEPERC:
+ if (usage) {
+ xasprintf(&arg, "%2.2f%%", (double) used / limit * 100);
+ rc = scols_line_refer_data(ln, n, arg);
+ } else
+ rc = scols_line_set_data(ln, n, "-");
+ break;
+ case COL_LIMIT:
+ xasprintf(&arg, "%ju", limit);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ }
+
+ if (rc != 0)
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+}
+
+static void setup_sem_elements_columns(struct libscols_table *tb)
+{
+ scols_table_set_name(tb, "elements");
+ if (!scols_table_new_column(tb, "SEMNUM", 0, SCOLS_FL_RIGHT))
+ err_oom();
+ if (!scols_table_new_column(tb, "VALUE", 0, SCOLS_FL_RIGHT))
+ err_oom();
+ if (!scols_table_new_column(tb, "NCOUNT", 0, SCOLS_FL_RIGHT))
+ err_oom();
+ if (!scols_table_new_column(tb, "ZCOUNT", 0, SCOLS_FL_RIGHT))
+ err_oom();
+ if (!scols_table_new_column(tb, "PID", 0, SCOLS_FL_RIGHT))
+ err_oom();
+ if (!scols_table_new_column(tb, "COMMAND", 0, SCOLS_FL_RIGHT))
+ err_oom();
+}
+
+static void do_sem(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+ struct libscols_line *ln;
+ struct passwd *pw = NULL, *cpw = NULL;
+ struct group *gr = NULL, *cgr = NULL;
+ struct sem_data *semds, *semdsp;
+ char *arg = NULL;
+
+ scols_table_set_name(tb, "semaphores");
+
+ if (ipc_sem_get_info(id, &semds) < 1) {
+ if (id > -1)
+ warnx(_("id %d not found"), id);
+ return;
+ }
+ for (semdsp = semds; semdsp->next != NULL || id > -1; semdsp = semdsp->next) {
+ size_t n;
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (n = 0; n < ncolumns; n++) {
+ int rc = 0;
+ switch (get_column_id(n)) {
+ case COL_KEY:
+ xasprintf(&arg, "0x%08x",semdsp->sem_perm.key);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_ID:
+ xasprintf(&arg, "%d",semdsp->sem_perm.id);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_OWNER:
+ arg = get_username(&pw, semdsp->sem_perm.uid);
+ if (!arg)
+ xasprintf(&arg, "%u", semdsp->sem_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_PERMS:
+ if (ctl->numperms)
+ xasprintf(&arg, "%#o", semdsp->sem_perm.mode & 0777);
+ else {
+ arg = xmalloc(11);
+ xstrmode(semdsp->sem_perm.mode & 0777, arg);
+ }
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CUID:
+ xasprintf(&arg, "%u", semdsp->sem_perm.cuid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CUSER:
+ arg = get_username(&cpw, semdsp->sem_perm.cuid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGID:
+ xasprintf(&arg, "%u", semdsp->sem_perm.cgid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGROUP:
+ arg = get_groupname(&cgr, semdsp->sem_perm.cgid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_UID:
+ xasprintf(&arg, "%u", semdsp->sem_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_USER:
+ arg = get_username(&pw, semdsp->sem_perm.uid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GID:
+ xasprintf(&arg, "%u", semdsp->sem_perm.gid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GROUP:
+ arg = get_groupname(&gr, semdsp->sem_perm.gid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CTIME:
+ if (semdsp->sem_ctime != 0) {
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)semdsp->sem_ctime));
+ }
+ break;
+ case COL_NSEMS:
+ xasprintf(&arg, "%ju", semdsp->sem_nsems);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_OTIME:
+ if (semdsp->sem_otime != 0) {
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)semdsp->sem_otime));
+ }
+ break;
+ }
+ if (rc != 0)
+ err(EXIT_FAILURE, _("failed to add output data"));
+ arg = NULL;
+ }
+
+ if (id > -1 && semds->sem_nsems) {
+ /* Create extra table with ID specific semaphore elements */
+ struct libscols_table *sub = new_table(ctl);
+ size_t i;
+ int rc = 0;
+
+ scols_table_enable_noheadings(sub, 0);
+ setup_sem_elements_columns(sub);
+
+ for (i = 0; i < semds->sem_nsems; i++) {
+ struct sem_elem *e = &semds->elements[i];
+ struct libscols_line *sln = scols_table_new_line(sub, NULL);
+
+ if (!sln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ /* SEMNUM */
+ xasprintf(&arg, "%zu", i);
+ rc = scols_line_refer_data(sln, 0, arg);
+ if (rc)
+ break;
+
+ /* VALUE */
+ xasprintf(&arg, "%d", e->semval);
+ rc = scols_line_refer_data(sln, 1, arg);
+ if (rc)
+ break;
+
+ /* NCOUNT */
+ xasprintf(&arg, "%d", e->ncount);
+ rc = scols_line_refer_data(sln, 2, arg);
+ if (rc)
+ break;
+
+ /* ZCOUNT */
+ xasprintf(&arg, "%d", e->zcount);
+ rc = scols_line_refer_data(sln, 3, arg);
+ if (rc)
+ break;
+
+ /* PID */
+ xasprintf(&arg, "%d", e->pid);
+ rc = scols_line_refer_data(sln, 4, arg);
+ if (rc)
+ break;
+
+ /* COMMAND */
+ arg = proc_get_command(e->pid);
+ rc = scols_line_refer_data(sln, 5, arg);
+ if (rc)
+ break;
+ }
+
+ if (rc != 0)
+ err(EXIT_FAILURE, _("failed to set data"));
+
+ scols_line_set_userdata(ln, (void *)sub);
+ break;
+ }
+ }
+ ipc_sem_free_info(semds);
+}
+
+static void do_sem_global(struct libscols_table *tb)
+{
+ struct sem_data *semds, *semdsp;
+ struct ipc_limits lim;
+ int nsems = 0, nsets = 0;
+
+ ipc_sem_get_limits(&lim);
+
+ if (ipc_sem_get_info(-1, &semds) > 0) {
+ for (semdsp = semds; semdsp->next != NULL; semdsp = semdsp->next) {
+ ++nsets;
+ nsems += semds->sem_nsems;
+ }
+ ipc_sem_free_info(semds);
+ }
+
+ global_set_data(tb, "SEMMNI", _("Number of semaphore identifiers"), nsets, lim.semmni, 1);
+ global_set_data(tb, "SEMMNS", _("Total number of semaphores"), nsems, lim.semmns, 1);
+ global_set_data(tb, "SEMMSL", _("Max semaphores per semaphore set."), 0, lim.semmsl, 0);
+ global_set_data(tb, "SEMOPM", _("Max number of operations per semop(2)"), 0, lim.semopm, 0);
+ global_set_data(tb, "SEMVMX", _("Semaphore max value"), 0, lim.semvmx, 0);
+}
+
+static void do_msg(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+ struct libscols_line *ln;
+ struct passwd *pw = NULL;
+ struct group *gr = NULL;
+ struct msg_data *msgds, *msgdsp;
+ char *arg = NULL;
+
+ if (ipc_msg_get_info(id, &msgds) < 1) {
+ if (id > -1)
+ warnx(_("id %d not found"), id);
+ return;
+ }
+ scols_table_set_name(tb, "messages");
+
+ for (msgdsp = msgds; msgdsp->next != NULL || id > -1 ; msgdsp = msgdsp->next) {
+ size_t n;
+ ln = scols_table_new_line(tb, NULL);
+
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ /* no need to call getpwuid() for the same user */
+ if (!(pw && pw->pw_uid == msgdsp->msg_perm.uid))
+ pw = getpwuid(msgdsp->msg_perm.uid);
+
+ /* no need to call getgrgid() for the same user */
+ if (!(gr && gr->gr_gid == msgdsp->msg_perm.gid))
+ gr = getgrgid(msgdsp->msg_perm.gid);
+
+ for (n = 0; n < ncolumns; n++) {
+ int rc = 0;
+
+ switch (get_column_id(n)) {
+ case COL_KEY:
+ xasprintf(&arg, "0x%08x",msgdsp->msg_perm.key);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_ID:
+ xasprintf(&arg, "%d",msgdsp->msg_perm.id);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_OWNER:
+ arg = get_username(&pw, msgdsp->msg_perm.uid);
+ if (!arg)
+ xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_PERMS:
+ if (ctl->numperms)
+ xasprintf(&arg, "%#o", msgdsp->msg_perm.mode & 0777);
+ else {
+ arg = xmalloc(11);
+ xstrmode(msgdsp->msg_perm.mode & 0777, arg);
+ rc = scols_line_refer_data(ln, n, arg);
+ }
+ break;
+ case COL_CUID:
+ xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CUSER:
+ arg = get_username(&pw, msgdsp->msg_perm.cuid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGID:
+ xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGROUP:
+ arg = get_groupname(&gr, msgdsp->msg_perm.cgid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_UID:
+ xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_USER:
+ arg = get_username(&pw, msgdsp->msg_perm.uid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GID:
+ xasprintf(&arg, "%u", msgdsp->msg_perm.gid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GROUP:
+ arg = get_groupname(&gr,msgdsp->msg_perm.gid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CTIME:
+ if (msgdsp->q_ctime != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)msgdsp->q_ctime));
+ break;
+ case COL_USEDBYTES:
+ xasprintf(&arg, "%ju", msgdsp->q_cbytes);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_MSGS:
+ xasprintf(&arg, "%ju", msgdsp->q_qnum);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_SEND:
+ if (msgdsp->q_stime != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)msgdsp->q_stime));
+ break;
+ case COL_RECV:
+ if (msgdsp->q_rtime != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)msgdsp->q_rtime));
+ break;
+ case COL_LSPID:
+ xasprintf(&arg, "%u", msgdsp->q_lspid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_LRPID:
+ xasprintf(&arg, "%u", msgdsp->q_lrpid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ }
+ if (rc != 0)
+ err(EXIT_FAILURE, _("failed to set data"));
+ arg = NULL;
+ }
+ if (id > -1)
+ break;
+ }
+ ipc_msg_free_info(msgds);
+}
+
+
+static void do_msg_global(struct libscols_table *tb)
+{
+ struct msg_data *msgds, *msgdsp;
+ struct ipc_limits lim;
+ int msgqs = 0;
+
+ ipc_msg_get_limits(&lim);
+
+ /* count number of used queues */
+ if (ipc_msg_get_info(-1, &msgds) > 0) {
+ for (msgdsp = msgds; msgdsp->next != NULL; msgdsp = msgdsp->next)
+ ++msgqs;
+ ipc_msg_free_info(msgds);
+ }
+
+ global_set_data(tb, "MSGMNI", _("Number of message queues"), msgqs, lim.msgmni, 1);
+ global_set_data(tb, "MSGMAX", _("Max size of message (bytes)"), 0, lim.msgmax, 0);
+ global_set_data(tb, "MSGMNB", _("Default max size of queue (bytes)"), 0, lim.msgmnb, 0);
+}
+
+
+static void do_shm(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+ struct libscols_line *ln;
+ struct passwd *pw = NULL;
+ struct group *gr = NULL;
+ struct shm_data *shmds, *shmdsp;
+ char *arg = NULL;
+
+ if (ipc_shm_get_info(id, &shmds) < 1) {
+ if (id > -1)
+ warnx(_("id %d not found"), id);
+ return;
+ }
+
+ scols_table_set_name(tb, "sharedmemory");
+
+ for (shmdsp = shmds; shmdsp->next != NULL || id > -1 ; shmdsp = shmdsp->next) {
+ size_t n;
+ ln = scols_table_new_line(tb, NULL);
+
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (n = 0; n < ncolumns; n++) {
+ int rc = 0;
+
+ switch (get_column_id(n)) {
+ case COL_KEY:
+ xasprintf(&arg, "0x%08x",shmdsp->shm_perm.key);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_ID:
+ xasprintf(&arg, "%d",shmdsp->shm_perm.id);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_OWNER:
+ arg = get_username(&pw, shmdsp->shm_perm.uid);
+ if (!arg)
+ xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_PERMS:
+ if (ctl->numperms)
+ xasprintf(&arg, "%#o", shmdsp->shm_perm.mode & 0777);
+ else {
+ arg = xmalloc(11);
+ xstrmode(shmdsp->shm_perm.mode & 0777, arg);
+ }
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CUID:
+ xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CUSER:
+ arg = get_username(&pw, shmdsp->shm_perm.cuid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGID:
+ xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CGROUP:
+ arg = get_groupname(&gr, shmdsp->shm_perm.cgid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_UID:
+ xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_USER:
+ arg = get_username(&pw, shmdsp->shm_perm.uid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GID:
+ xasprintf(&arg, "%u", shmdsp->shm_perm.gid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_GROUP:
+ arg = get_groupname(&gr, shmdsp->shm_perm.gid);
+ if (arg)
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_CTIME:
+ if (shmdsp->shm_ctim != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)shmdsp->shm_ctim));
+ break;
+ case COL_SIZE:
+ if (ctl->bytes)
+ xasprintf(&arg, "%ju", shmdsp->shm_segsz);
+ else
+ arg = size_to_human_string(SIZE_SUFFIX_1LETTER, shmdsp->shm_segsz);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_NATTCH:
+ xasprintf(&arg, "%ju", shmdsp->shm_nattch);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_STATUS: {
+ int comma = 0;
+ size_t offt = 0;
+
+ free(arg);
+ arg = xcalloc(1, sizeof(char) * strlen(_("dest"))
+ + strlen(_("locked"))
+ + strlen(_("hugetlb"))
+ + strlen(_("noreserve")) + 4);
+#ifdef SHM_DEST
+ if (shmdsp->shm_perm.mode & SHM_DEST) {
+ offt += sprintf(arg, "%s", _("dest"));
+ comma++;
+ }
+#endif
+#ifdef SHM_LOCKED
+ if (shmdsp->shm_perm.mode & SHM_LOCKED) {
+ if (comma)
+ arg[offt++] = ',';
+ offt += sprintf(arg + offt, "%s", _("locked"));
+ }
+#endif
+#ifdef SHM_HUGETLB
+ if (shmdsp->shm_perm.mode & SHM_HUGETLB) {
+ if (comma)
+ arg[offt++] = ',';
+ offt += sprintf(arg + offt, "%s", _("hugetlb"));
+ }
+#endif
+#ifdef SHM_NORESERVE
+ if (shmdsp->shm_perm.mode & SHM_NORESERVE) {
+ if (comma)
+ arg[offt++] = ',';
+ sprintf(arg + offt, "%s", _("noreserve"));
+ }
+#endif
+ rc = scols_line_refer_data(ln, n, arg);
+ }
+ break;
+ case COL_ATTACH:
+ if (shmdsp->shm_atim != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)shmdsp->shm_atim));
+ break;
+ case COL_DETACH:
+ if (shmdsp->shm_dtim != 0)
+ rc = scols_line_refer_data(ln, n,
+ make_time(ctl->time_mode,
+ (time_t)shmdsp->shm_dtim));
+ break;
+ case COL_CPID:
+ xasprintf(&arg, "%u", shmdsp->shm_cprid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_LPID:
+ xasprintf(&arg, "%u", shmdsp->shm_lprid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ case COL_COMMAND:
+ arg = proc_get_command(shmdsp->shm_cprid);
+ rc = scols_line_refer_data(ln, n, arg);
+ break;
+ }
+ if (rc != 0)
+ err(EXIT_FAILURE, _("failed to set data"));
+ arg = NULL;
+ }
+ if (id > -1)
+ break;
+ }
+ ipc_shm_free_info(shmds);
+}
+
+static void do_shm_global(struct libscols_table *tb)
+{
+ struct shm_data *shmds, *shmdsp;
+ uint64_t nsegs = 0, sum_segsz = 0;
+ struct ipc_limits lim;
+
+ ipc_shm_get_limits(&lim);
+
+ if (ipc_shm_get_info(-1, &shmds) > 0) {
+ for (shmdsp = shmds; shmdsp->next != NULL; shmdsp = shmdsp->next) {
+ ++nsegs;
+ sum_segsz += shmdsp->shm_segsz;
+ }
+ ipc_shm_free_info(shmds);
+ }
+
+ global_set_data(tb, "SHMMNI", _("Shared memory segments"), nsegs, lim.shmmni, 1);
+ global_set_data(tb, "SHMALL", _("Shared memory pages"), sum_segsz / getpagesize(), lim.shmall, 1);
+ global_set_data(tb, "SHMMAX", _("Max size of shared memory segment (bytes)"), 0, lim.shmmax, 0);
+ global_set_data(tb, "SHMMIN", _("Min size of shared memory segment (bytes)"), 0, lim.shmmin, 0);
+}
+
+int main(int argc, char *argv[])
+{
+ int opt, msg = 0, sem = 0, shm = 0, id = -1;
+ int show_time = 0, show_creat = 0, global = 0;
+ size_t i;
+ struct lsipc_control *ctl = xcalloc(1, sizeof(struct lsipc_control));
+ static struct libscols_table *tb;
+ char *outarg = NULL;
+
+ /* long only options. */
+ enum {
+ OPT_NOTRUNC = CHAR_MAX + 1,
+ OPT_NOHEAD,
+ OPT_TIME_FMT
+ };
+
+ static const struct option longopts[] = {
+ { "bytes", no_argument, NULL, 'b' },
+ { "creator", no_argument, NULL, 'c' },
+ { "export", no_argument, NULL, 'e' },
+ { "global", no_argument, NULL, 'g' },
+ { "help", no_argument, NULL, 'h' },
+ { "id", required_argument, NULL, 'i' },
+ { "json", no_argument, NULL, 'J' },
+ { "list", no_argument, NULL, 'l' },
+ { "newline", no_argument, NULL, 'n' },
+ { "noheadings", no_argument, NULL, OPT_NOHEAD },
+ { "notruncate", no_argument, NULL, OPT_NOTRUNC },
+ { "numeric-perms", no_argument, NULL, 'P' },
+ { "output", required_argument, NULL, 'o' },
+ { "queues", no_argument, NULL, 'q' },
+ { "raw", no_argument, NULL, 'r' },
+ { "semaphores", no_argument, NULL, 's' },
+ { "shmems", no_argument, NULL, 'm' },
+ { "time", no_argument, NULL, 't' },
+ { "time-format", required_argument, NULL, OPT_TIME_FMT },
+ { "version", no_argument, NULL, 'V' },
+ {NULL, 0, NULL, 0}
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'J', 'e', 'l', 'n', 'r' },
+ { 'g', 'i' },
+ { 'c', 'o', 't' },
+ { 'm', 'q', 's' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ ctl->time_mode = 0;
+
+ scols_init_debug(0);
+
+ while ((opt = getopt_long(argc, argv, "bceghi:Jlmno:PqrstV", longopts, NULL)) != -1) {
+
+ err_exclusive_options(opt, longopts, excl, excl_st);
+
+ switch (opt) {
+ case 'b':
+ ctl->bytes = 1;
+ break;
+ case 'i':
+ id = strtos32_or_err(optarg, _("failed to parse IPC identifier"));
+ break;
+ case 'e':
+ ctl->outmode = OUT_EXPORT;
+ break;
+ case 'r':
+ ctl->outmode = OUT_RAW;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case 'g':
+ global = 1;
+ break;
+ case 'q':
+ msg = 1;
+ add_column(columns, ncolumns++, COL_KEY);
+ add_column(columns, ncolumns++, COL_ID);
+ add_column(columns, ncolumns++, COL_PERMS);
+ add_column(columns, ncolumns++, COL_OWNER);
+ add_column(columns, ncolumns++, COL_USEDBYTES);
+ add_column(columns, ncolumns++, COL_MSGS);
+ add_column(columns, ncolumns++, COL_LSPID);
+ add_column(columns, ncolumns++, COL_LRPID);
+ LOWER = COLDESC_IDX_MSG_FIRST;
+ UPPER = COLDESC_IDX_MSG_LAST;
+ break;
+ case 'l':
+ ctl->outmode = OUT_LIST;
+ break;
+ case 'm':
+ shm = 1;
+ add_column(columns, ncolumns++, COL_KEY);
+ add_column(columns, ncolumns++, COL_ID);
+ add_column(columns, ncolumns++, COL_PERMS);
+ add_column(columns, ncolumns++, COL_OWNER);
+ add_column(columns, ncolumns++, COL_SIZE);
+ add_column(columns, ncolumns++, COL_NATTCH);
+ add_column(columns, ncolumns++, COL_STATUS);
+ add_column(columns, ncolumns++, COL_CTIME);
+ add_column(columns, ncolumns++, COL_CPID);
+ add_column(columns, ncolumns++, COL_LPID);
+ add_column(columns, ncolumns++, COL_COMMAND);
+ LOWER = COLDESC_IDX_SHM_FIRST;
+ UPPER = COLDESC_IDX_SHM_LAST;
+ break;
+ case 'n':
+ ctl->outmode = OUT_NEWLINE;
+ break;
+ case 'P':
+ ctl->numperms = 1;
+ break;
+ case 's':
+ sem = 1;
+ add_column(columns, ncolumns++, COL_KEY);
+ add_column(columns, ncolumns++, COL_ID);
+ add_column(columns, ncolumns++, COL_PERMS);
+ add_column(columns, ncolumns++, COL_OWNER);
+ add_column(columns, ncolumns++, COL_NSEMS);
+ LOWER = COLDESC_IDX_SEM_FIRST;
+ UPPER = COLDESC_IDX_SEM_LAST;
+ break;
+ case OPT_NOTRUNC:
+ ctl->notrunc = 1;
+ break;
+ case OPT_NOHEAD:
+ ctl->noheadings = 1;
+ break;
+ case OPT_TIME_FMT:
+ ctl->time_mode = parse_time_mode(optarg);
+ break;
+ case 'J':
+ ctl->outmode = OUT_JSON;
+ break;
+ case 't':
+ show_time = 1;
+ break;
+ case 'c':
+ show_creat = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ /* default is global */
+ if (msg + shm + sem == 0) {
+ msg = shm = sem = global = 1;
+ if (show_time || show_creat || id != -1)
+ errx(EXIT_FAILURE, _("--global is mutually exclusive with --creator, --id and --time"));
+ }
+ if (global) {
+ add_column(columns, ncolumns++, COL_RESOURCE);
+ add_column(columns, ncolumns++, COL_DESC);
+ add_column(columns, ncolumns++, COL_LIMIT);
+ add_column(columns, ncolumns++, COL_USED);
+ add_column(columns, ncolumns++, COL_USEPERC);
+ LOWER = COLDESC_IDX_SUM_FIRST;
+ UPPER = COLDESC_IDX_SUM_LAST;
+ }
+
+ /* default to pretty-print if --id specified */
+ if (id != -1 && !ctl->outmode)
+ ctl->outmode = OUT_PRETTY;
+
+ if (!ctl->time_mode)
+ ctl->time_mode = ctl->outmode == OUT_PRETTY ? TIME_FULL : TIME_SHORT;
+
+ if (ctl->outmode == OUT_PRETTY && !(optarg || show_creat || show_time)) {
+ /* all columns for lsipc --<RESOURCE> --id <ID> */
+ for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
+ columns[ncolumns++] = i;
+ } else {
+ if (show_creat) {
+ add_column(columns, ncolumns++, COL_CUID);
+ add_column(columns, ncolumns++, COL_CGID);
+ add_column(columns, ncolumns++, COL_UID);
+ add_column(columns, ncolumns++, COL_GID);
+ }
+ if (msg && show_time) {
+ add_column(columns, ncolumns++, COL_SEND);
+ add_column(columns, ncolumns++, COL_RECV);
+ add_column(columns, ncolumns++, COL_CTIME);
+ }
+ if (shm && show_time) {
+ /* keep "COMMAND" as last column */
+ size_t cmd = columns[ncolumns - 1] == COL_COMMAND;
+
+ if (cmd)
+ ncolumns--;
+ add_column(columns, ncolumns++, COL_ATTACH);
+ add_column(columns, ncolumns++, COL_DETACH);
+ if (cmd)
+ add_column(columns, ncolumns++, COL_COMMAND);
+ }
+ if (sem && show_time) {
+ add_column(columns, ncolumns++, COL_OTIME);
+ add_column(columns, ncolumns++, COL_CTIME);
+ }
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ tb = setup_table(ctl);
+ if (!tb)
+ return EXIT_FAILURE;
+
+ if (global)
+ scols_table_set_name(tb, "ipclimits");
+
+ if (msg) {
+ if (global)
+ do_msg_global(tb);
+ else
+ do_msg(id, ctl, tb);
+ }
+ if (shm) {
+ if (global)
+ do_shm_global(tb);
+ else
+ do_shm(id, ctl, tb);
+ }
+ if (sem) {
+ if (global)
+ do_sem_global(tb);
+ else
+ do_sem(id, ctl, tb);
+ }
+
+ print_table(ctl, tb);
+
+ scols_unref_table(tb);
+ free(ctl);
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/sys-utils/lsirq.1 b/sys-utils/lsirq.1
new file mode 100644
index 0000000..3249b40
--- /dev/null
+++ b/sys-utils/lsirq.1
@@ -0,0 +1,59 @@
+.TH IRQTOP "1" "February 2020" "util-linux" "User Commands"
+.SH NAME
+lsirq \- utility to display kernel interrupt information
+.SH SYNOPSIS
+.B lsirq
+[options]
+.SH DESCRIPTION
+Display kernel interrupt counter information.
+.PP
+The default output is subject to change. So whenever possible, you should
+avoid using default outputs in your scripts. Always explicitly define
+expected columns by using
+.BR \-\-output .
+.SH OPTIONS
+.TP
+.BR \-n ", " \-\-noheadings
+Don't print headings.
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use
+.B \-\-help
+to get a list of all supported columns. The default list of columns may be
+extended if list is specified in the format
+.IR +list .
+.TP
+.BR \-s , " \-\-sort " \fIcolumn\fP
+Specify sort criteria by column name. See
+.B \-\-help
+output to get column names.
+.TP
+.BR \-J ", " \-\-json
+Use JSON output format.
+.TP
+.BR \-P ", " \-\-pairs
+Produce output in the form of key="value" pairs. All potentially unsafe characters
+are hex-escaped (\\x<code>).
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+.TP
+.BR \-h ,\ \-\-help
+Display help text and exit.
+.SH AUTHORS
+.MT pizhenwei@\:bytedance.com
+Zhenwei Pi
+.ME
+.br
+.MT kerolasa@\:iki.fi
+Sami Kerola
+.ME
+.br
+.MT kzak@\:redhat.com
+Karel Zak
+.ME
+.SH AVAILABILITY
+The lsirq command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/lsirq.c b/sys-utils/lsirq.c
new file mode 100644
index 0000000..33fe48d
--- /dev/null
+++ b/sys-utils/lsirq.c
@@ -0,0 +1,146 @@
+/*
+ * lsirq - utility to display kernel interrupt information.
+ *
+ * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com>
+ * Copyright (C) 2020 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libsmartcols.h>
+
+#include "closestream.h"
+#include "optutils.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "irq-common.h"
+
+static int print_irq_data(struct irq_output *out)
+{
+ struct libscols_table *table;
+
+ table = get_scols_table(out, NULL, NULL);
+ if (!table)
+ return -1;
+
+ scols_print_table(table);
+ scols_unref_table(table);
+ return 0;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(_(" %s [options]\n"), program_invocation_short_name);
+ fputs(USAGE_SEPARATOR, stdout);
+
+ puts(_("Utility to display kernel interrupt information."));
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(" -J, --json use JSON output format\n"), stdout);
+ fputs(_(" -P, --pairs use key=\"value\" output format\n"), stdout);
+ fputs(_(" -n, --noheadings don't print headings\n"), stdout);
+ fputs(_(" -o, --output <list> define which output columns to use\n"), stdout);
+ fputs(_(" -s, --sort <column> specify sort column\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ fputs(USAGE_COLUMNS, stdout);
+ irq_print_columns(stdout, 1);
+
+ printf(USAGE_MAN_TAIL("lsirq(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct irq_output out = {
+ .ncolumns = 0
+ };
+ static const struct option longopts[] = {
+ {"sort", required_argument, NULL, 's'},
+ {"noheadings", no_argument, NULL, 'n'},
+ {"output", required_argument, NULL, 'o'},
+ {"json", no_argument, NULL, 'J'},
+ {"pairs", no_argument, NULL, 'P'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ int c;
+ const char *outarg = NULL;
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ {'J', 'P'},
+ {0}
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+
+ while ((c = getopt_long(argc, argv, "no:s:hJPV", longopts, NULL)) != -1) {
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'J':
+ out.json = 1;
+ break;
+ case 'P':
+ out.pairs = 1;
+ break;
+ case 'n':
+ out.no_headings = 1;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case 's':
+ set_sort_func_by_name(&out, optarg);
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ /* default */
+ if (!out.ncolumns) {
+ out.columns[out.ncolumns++] = COL_IRQ;
+ out.columns[out.ncolumns++] = COL_TOTAL;
+ out.columns[out.ncolumns++] = COL_NAME;
+ }
+
+ /* add -o [+]<list> to putput */
+ if (outarg && string_add_to_idarray(outarg, out.columns,
+ ARRAY_SIZE(out.columns),
+ &out.ncolumns,
+ irq_column_name_to_id) < 0)
+ exit(EXIT_FAILURE);
+
+ return print_irq_data(&out) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/sys-utils/lsmem.1 b/sys-utils/lsmem.1
new file mode 100644
index 0000000..2fe78b4
--- /dev/null
+++ b/sys-utils/lsmem.1
@@ -0,0 +1,100 @@
+.TH LSMEM 1 "October 2016" "util-linux" "User Commands"
+.SH NAME
+lsmem \- list the ranges of available memory with their online status
+.SH SYNOPSIS
+.B lsmem
+[options]
+.SH DESCRIPTION
+The \fBlsmem\fP command lists the ranges of available memory with their online
+status. The listed memory blocks correspond to the memory block representation
+in sysfs. The command also shows the memory block size and the amount of memory
+in online and offline state.
+
+The default output compatible with original implementation from s390-tools, but
+it's strongly recommended to avoid using default outputs in your scripts.
+Always explicitly define expected columns by using the \fB\-\-output\fR option
+together with a columns list in environments where a stable output is required.
+
+The \fBlsmem\fP command lists a new memory range always when the current memory
+block distinguish from the previous block by some output column. This default
+behavior is possible to override by the \fB\-\-split\fR option (e.g., \fBlsmem
+\-\-split=ZONES\fR). The special word "none" may be used to ignore all
+differences between memory blocks and to create as large as possible continuous
+ranges. The opposite semantic is \fB\-\-all\fR to list individual memory
+blocks.
+
+Note that some output columns may provide inaccurate information if a split policy
+forces \fBlsmem\fP to ignore differences in some attributes. For example if you
+merge removable and non-removable memory blocks to the one range than all
+the range will be marked as non-removable on \fBlsmem\fP output.
+
+Not all columns are supported on all systems. If an unsupported column is
+specified, \fBlsmem\fP prints the column but does not provide any data for it.
+
+Use the \fB\-\-help\fR option to see the columns description.
+
+.SH OPTIONS
+.TP
+.BR \-a ", " \-\-all
+List each individual memory block, instead of combining memory blocks with
+similar attributes.
+.TP
+.BR \-b , " \-\-bytes"
+Print the SIZE column in bytes rather than in a human-readable format.
+.TP
+.BR \-h ", " \-\-help
+Display help text and exit.
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use \fB\-\-help\fR
+to get a list of all supported columns.
+The default list of columns may be extended if \fIlist\fP is
+specified in the format \fB+\fIlist\fP (e.g., \fBlsmem \-o +NODE\fP).
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.BR \-P , " \-\-pairs"
+Produce output in the form of key="value" pairs.
+All potentially unsafe characters are hex-escaped (\\x<code>).
+.TP
+.BR \-r , " \-\-raw"
+Produce output in raw format. All potentially unsafe characters are hex-escaped
+(\\x<code>).
+.TP
+.BR \-S , " \-\-split " \fIlist\fP
+Specify which columns (attributes) use to split memory blocks to ranges. The
+supported columns are STATE, REMOVABLE, NODE and ZONES, or "none".
+The other columns are
+silently ignored. For more details see DESCRIPTION above.
+.TP
+.BR \-s , " \-\-sysroot " \fIdirectory\fP
+Gather memory data for a Linux instance other than the instance from which the
+\fBlsmem\fP command is issued. The specified \fIdirectory\fP is the system
+root of the Linux instance to be inspected.
+.TP
+.BR \-V ", " \-\-version
+Display version information and exit.
+.TP
+\fB\-\-summary\fR[=\fIwhen\fR]
+This option controls summary lines output. The optional argument \fIwhen\fP can be
+\fBnever\fR, \fBalways\fR or \fBonly\fR. If the \fIwhen\fR argument is
+omitted, it defaults to \fB"only"\fR. The summary output is suppressed for
+\fB\-\-raw\fR, \fB\-\-pairs\fR and \fB\-\-json\fR.
+.SH AUTHORS
+.B lsmem
+was originally written by Gerald Schaefer for s390-tools in Perl. The C version
+for util-linux was written by Clemens von Mann, Heiko Carstens and Karel Zak.
+.SH SEE ALSO
+.BR chmem (8)
+.SH AVAILABILITY
+The \fBlsmem\fP command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/lsmem.c b/sys-utils/lsmem.c
new file mode 100644
index 0000000..00f9d76
--- /dev/null
+++ b/sys-utils/lsmem.c
@@ -0,0 +1,761 @@
+/*
+ * lsmem - Show memory configuration
+ *
+ * Copyright IBM Corp. 2016
+ * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <c.h>
+#include <nls.h>
+#include <path.h>
+#include <strutils.h>
+#include <closestream.h>
+#include <xalloc.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <optutils.h>
+#include <libsmartcols.h>
+
+#define _PATH_SYS_MEMORY "/sys/devices/system/memory"
+
+#define MEMORY_STATE_ONLINE 0
+#define MEMORY_STATE_OFFLINE 1
+#define MEMORY_STATE_GOING_OFFLINE 2
+#define MEMORY_STATE_UNKNOWN 3
+
+enum zone_id {
+ ZONE_DMA = 0,
+ ZONE_DMA32,
+ ZONE_NORMAL,
+ ZONE_HIGHMEM,
+ ZONE_MOVABLE,
+ ZONE_DEVICE,
+ ZONE_NONE,
+ ZONE_UNKNOWN,
+ MAX_NR_ZONES,
+};
+
+struct memory_block {
+ uint64_t index;
+ uint64_t count;
+ int state;
+ int node;
+ int nr_zones;
+ int zones[MAX_NR_ZONES];
+ unsigned int removable:1;
+};
+
+struct lsmem {
+ struct path_cxt *sysmem; /* _PATH_SYS_MEMORY directory handler */
+ struct dirent **dirs;
+ int ndirs;
+ struct memory_block *blocks;
+ int nblocks;
+ uint64_t block_size;
+ uint64_t mem_online;
+ uint64_t mem_offline;
+
+ struct libscols_table *table;
+ unsigned int have_nodes : 1,
+ raw : 1,
+ export : 1,
+ json : 1,
+ noheadings : 1,
+ summary : 1,
+ list_all : 1,
+ bytes : 1,
+ want_summary : 1,
+ want_table : 1,
+ split_by_node : 1,
+ split_by_state : 1,
+ split_by_removable : 1,
+ split_by_zones : 1,
+ have_zones : 1;
+};
+
+
+enum {
+ COL_RANGE,
+ COL_SIZE,
+ COL_STATE,
+ COL_REMOVABLE,
+ COL_BLOCK,
+ COL_NODE,
+ COL_ZONES,
+};
+
+static char *zone_names[] = {
+ [ZONE_DMA] = "DMA",
+ [ZONE_DMA32] = "DMA32",
+ [ZONE_NORMAL] = "Normal",
+ [ZONE_HIGHMEM] = "Highmem",
+ [ZONE_MOVABLE] = "Movable",
+ [ZONE_DEVICE] = "Device",
+ [ZONE_NONE] = "None", /* block contains more than one zone, can't be offlined */
+ [ZONE_UNKNOWN] = "Unknown",
+};
+
+/* column names */
+struct coldesc {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+};
+
+/* columns descriptions */
+static struct coldesc coldescs[] = {
+ [COL_RANGE] = { "RANGE", 0, 0, N_("start and end address of the memory range")},
+ [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the memory range")},
+ [COL_STATE] = { "STATE", 0, SCOLS_FL_RIGHT, N_("online status of the memory range")},
+ [COL_REMOVABLE] = { "REMOVABLE", 0, SCOLS_FL_RIGHT, N_("memory is removable")},
+ [COL_BLOCK] = { "BLOCK", 0, SCOLS_FL_RIGHT, N_("memory block number or blocks range")},
+ [COL_NODE] = { "NODE", 0, SCOLS_FL_RIGHT, N_("numa node of memory")},
+ [COL_ZONES] = { "ZONES", 0, SCOLS_FL_RIGHT, N_("valid zones for the memory range")},
+};
+
+/* columns[] array specifies all currently wanted output column. The columns
+ * are defined by coldescs[] array and you can specify (on command line) each
+ * column twice. That's enough, dynamically allocated array of the columns is
+ * unnecessary overkill and over-engineering in this case */
+static int columns[ARRAY_SIZE(coldescs) * 2];
+static size_t ncolumns;
+
+static inline size_t err_columns_index(size_t arysz, size_t idx)
+{
+ if (idx >= arysz)
+ errx(EXIT_FAILURE, _("too many columns specified, "
+ "the limit is %zu columns"),
+ arysz - 1);
+ return idx;
+}
+
+/*
+ * name must be null-terminated
+ */
+static int zone_name_to_id(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
+ if (!strcasecmp(name, zone_names[i]))
+ return i;
+ }
+ return ZONE_UNKNOWN;
+}
+
+#define add_column(ary, n, id) \
+ ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
+ const char *cn = coldescs[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static inline int get_column_id(int num)
+{
+ assert(num >= 0);
+ assert((size_t) num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(coldescs));
+
+ return columns[num];
+}
+
+static inline struct coldesc *get_column_desc(int num)
+{
+ return &coldescs[ get_column_id(num) ];
+}
+
+static inline void reset_split_policy(struct lsmem *l, int enable)
+{
+ l->split_by_state = enable;
+ l->split_by_node = enable;
+ l->split_by_removable = enable;
+ l->split_by_zones = enable;
+}
+
+static void set_split_policy(struct lsmem *l, int cols[], size_t ncols)
+{
+ size_t i;
+
+ reset_split_policy(l, 0);
+
+ for (i = 0; i < ncols; i++) {
+ switch (cols[i]) {
+ case COL_STATE:
+ l->split_by_state = 1;
+ break;
+ case COL_NODE:
+ l->split_by_node = 1;
+ break;
+ case COL_REMOVABLE:
+ l->split_by_removable = 1;
+ break;
+ case COL_ZONES:
+ l->split_by_zones = 1;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void add_scols_line(struct lsmem *lsmem, struct memory_block *blk)
+{
+ size_t i;
+ struct libscols_line *line;
+
+ line = scols_table_new_line(lsmem->table, NULL);
+ if (!line)
+ err_oom();
+
+ for (i = 0; i < ncolumns; i++) {
+ char *str = NULL;
+
+ switch (get_column_id(i)) {
+ case COL_RANGE:
+ {
+ uint64_t start = blk->index * lsmem->block_size;
+ uint64_t size = blk->count * lsmem->block_size;
+ xasprintf(&str, "0x%016"PRIx64"-0x%016"PRIx64, start, start + size - 1);
+ break;
+ }
+ case COL_SIZE:
+ if (lsmem->bytes)
+ xasprintf(&str, "%"PRId64, (uint64_t) blk->count * lsmem->block_size);
+ else
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER,
+ (uint64_t) blk->count * lsmem->block_size);
+ break;
+ case COL_STATE:
+ str = xstrdup(
+ blk->state == MEMORY_STATE_ONLINE ? _("online") :
+ blk->state == MEMORY_STATE_OFFLINE ? _("offline") :
+ blk->state == MEMORY_STATE_GOING_OFFLINE ? _("on->off") :
+ "?");
+ break;
+ case COL_REMOVABLE:
+ if (blk->state == MEMORY_STATE_ONLINE)
+ str = xstrdup(blk->removable ? _("yes") : _("no"));
+ break;
+ case COL_BLOCK:
+ if (blk->count == 1)
+ xasprintf(&str, "%"PRId64, blk->index);
+ else
+ xasprintf(&str, "%"PRId64"-%"PRId64,
+ blk->index, blk->index + blk->count - 1);
+ break;
+ case COL_NODE:
+ if (lsmem->have_nodes)
+ xasprintf(&str, "%d", blk->node);
+ break;
+ case COL_ZONES:
+ if (lsmem->have_zones) {
+ char valid_zones[BUFSIZ];
+ int j, zone_id;
+
+ valid_zones[0] = '\0';
+ for (j = 0; j < blk->nr_zones; j++) {
+ zone_id = blk->zones[j];
+ if (strlen(valid_zones) +
+ strlen(zone_names[zone_id]) > BUFSIZ - 2)
+ break;
+ strcat(valid_zones, zone_names[zone_id]);
+ if (j + 1 < blk->nr_zones)
+ strcat(valid_zones, "/");
+ }
+ str = xstrdup(valid_zones);
+ }
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, i, str) != 0)
+ err_oom();
+ }
+}
+
+static void fill_scols_table(struct lsmem *lsmem)
+{
+ int i;
+
+ for (i = 0; i < lsmem->nblocks; i++)
+ add_scols_line(lsmem, &lsmem->blocks[i]);
+}
+
+static void print_summary(struct lsmem *lsmem)
+{
+ if (lsmem->bytes) {
+ printf("%-23s %15"PRId64"\n",_("Memory block size:"), lsmem->block_size);
+ printf("%-23s %15"PRId64"\n",_("Total online memory:"), lsmem->mem_online);
+ printf("%-23s %15"PRId64"\n",_("Total offline memory:"), lsmem->mem_offline);
+ } else {
+ char *p;
+
+ if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->block_size)))
+ printf("%-23s %5s\n",_("Memory block size:"), p);
+ free(p);
+
+ if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_online)))
+ printf("%-23s %5s\n",_("Total online memory:"), p);
+ free(p);
+
+ if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_offline)))
+ printf("%-23s %5s\n",_("Total offline memory:"), p);
+ free(p);
+ }
+}
+
+static int memory_block_get_node(struct lsmem *lsmem, char *name)
+{
+ struct dirent *de;
+ DIR *dir;
+ int node;
+
+ dir = ul_path_opendir(lsmem->sysmem, name);
+ if (!dir)
+ err(EXIT_FAILURE, _("Failed to open %s"), name);
+
+ node = -1;
+ while ((de = readdir(dir)) != NULL) {
+ if (strncmp("node", de->d_name, 4) != 0)
+ continue;
+ if (!isdigit_string(de->d_name + 4))
+ continue;
+ node = strtol(de->d_name + 4, NULL, 10);
+ break;
+ }
+ closedir(dir);
+ return node;
+}
+
+static void memory_block_read_attrs(struct lsmem *lsmem, char *name,
+ struct memory_block *blk)
+{
+ char *line = NULL;
+ int i, x = 0;
+
+ memset(blk, 0, sizeof(*blk));
+
+ blk->count = 1;
+ blk->state = MEMORY_STATE_UNKNOWN;
+ blk->index = strtoumax(name + 6, NULL, 10); /* get <num> of "memory<num>" */
+
+ if (ul_path_readf_s32(lsmem->sysmem, &x, "%s/removable", name) == 0)
+ blk->removable = x == 1;
+
+ if (ul_path_readf_string(lsmem->sysmem, &line, "%s/state", name) > 0) {
+ if (strcmp(line, "offline") == 0)
+ blk->state = MEMORY_STATE_OFFLINE;
+ else if (strcmp(line, "online") == 0)
+ blk->state = MEMORY_STATE_ONLINE;
+ else if (strcmp(line, "going-offline") == 0)
+ blk->state = MEMORY_STATE_GOING_OFFLINE;
+ free(line);
+ }
+
+ if (lsmem->have_nodes)
+ blk->node = memory_block_get_node(lsmem, name);
+
+ blk->nr_zones = 0;
+ if (lsmem->have_zones &&
+ ul_path_readf_string(lsmem->sysmem, &line, "%s/valid_zones", name) > 0) {
+
+ char *token = strtok(line, " ");
+
+ for (i = 0; token && i < MAX_NR_ZONES; i++) {
+ blk->zones[i] = zone_name_to_id(token);
+ blk->nr_zones++;
+ token = strtok(NULL, " ");
+ }
+
+ free(line);
+ }
+}
+
+static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk)
+{
+ struct memory_block *curr;
+ int i;
+
+ if (!lsmem->nblocks)
+ return 0;
+ curr = &lsmem->blocks[lsmem->nblocks - 1];
+ if (lsmem->list_all)
+ return 0;
+ if (curr->index + curr->count != blk->index)
+ return 0;
+ if (lsmem->split_by_state && curr->state != blk->state)
+ return 0;
+ if (lsmem->split_by_removable && curr->removable != blk->removable)
+ return 0;
+ if (lsmem->split_by_node && lsmem->have_nodes) {
+ if (curr->node != blk->node)
+ return 0;
+ }
+ if (lsmem->split_by_zones && lsmem->have_zones) {
+ if (curr->nr_zones != blk->nr_zones)
+ return 0;
+ for (i = 0; i < curr->nr_zones; i++) {
+ if (curr->zones[i] == ZONE_UNKNOWN ||
+ curr->zones[i] != blk->zones[i])
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void free_info(struct lsmem *lsmem)
+{
+ int i;
+
+ if (!lsmem)
+ return;
+ free(lsmem->blocks);
+ for (i = 0; i < lsmem->ndirs; i++)
+ free(lsmem->dirs[i]);
+ free(lsmem->dirs);
+}
+
+static void read_info(struct lsmem *lsmem)
+{
+ struct memory_block blk;
+ char buf[128];
+ int i;
+
+ if (ul_path_read_buffer(lsmem->sysmem, buf, sizeof(buf), "block_size_bytes") <= 0)
+ err(EXIT_FAILURE, _("failed to read memory block size"));
+ lsmem->block_size = strtoumax(buf, NULL, 16);
+
+ for (i = 0; i < lsmem->ndirs; i++) {
+ memory_block_read_attrs(lsmem, lsmem->dirs[i]->d_name, &blk);
+ if (blk.state == MEMORY_STATE_ONLINE)
+ lsmem->mem_online += lsmem->block_size;
+ else
+ lsmem->mem_offline += lsmem->block_size;
+ if (is_mergeable(lsmem, &blk)) {
+ lsmem->blocks[lsmem->nblocks - 1].count++;
+ continue;
+ }
+ lsmem->nblocks++;
+ lsmem->blocks = xrealloc(lsmem->blocks, lsmem->nblocks * sizeof(blk));
+ *&lsmem->blocks[lsmem->nblocks - 1] = blk;
+ }
+}
+
+static int memory_block_filter(const struct dirent *de)
+{
+ if (strncmp("memory", de->d_name, 6) != 0)
+ return 0;
+ return isdigit_string(de->d_name + 6);
+}
+
+static void read_basic_info(struct lsmem *lsmem)
+{
+ char dir[PATH_MAX];
+
+ if (ul_path_access(lsmem->sysmem, F_OK, "block_size_bytes") != 0)
+ errx(EXIT_FAILURE, _("This system does not support memory blocks"));
+
+ ul_path_get_abspath(lsmem->sysmem, dir, sizeof(dir), NULL);
+
+ lsmem->ndirs = scandir(dir, &lsmem->dirs, memory_block_filter, versionsort);
+ if (lsmem->ndirs <= 0)
+ err(EXIT_FAILURE, _("Failed to read %s"), dir);
+
+ if (memory_block_get_node(lsmem, lsmem->dirs[0]->d_name) != -1)
+ lsmem->have_nodes = 1;
+
+ /* The valid_zones sysmem attribute was introduced with kernel 3.18 */
+ if (ul_path_access(lsmem->sysmem, F_OK, "memory0/valid_zones") == 0)
+ lsmem->have_zones = 1;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("List the ranges of available memory with their online status.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -J, --json use JSON output format\n"), out);
+ fputs(_(" -P, --pairs use key=\"value\" output format\n"), out);
+ fputs(_(" -a, --all list each individual memory block\n"), out);
+ fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> output columns\n"), out);
+ fputs(_(" --output-all output all columns\n"), out);
+ fputs(_(" -r, --raw use raw output format\n"), out);
+ fputs(_(" -S, --split <list> split ranges by specified columns\n"), out);
+ fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out);
+ fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++)
+ fprintf(out, " %10s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ printf(USAGE_MAN_TAIL("lsmem(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct lsmem _lsmem = {
+ .want_table = 1,
+ .want_summary = 1
+ }, *lsmem = &_lsmem;
+
+ const char *outarg = NULL, *splitarg = NULL, *prefix = NULL;
+ int c;
+ size_t i;
+
+ enum {
+ LSMEM_OPT_SUMARRY = CHAR_MAX + 1,
+ OPT_OUTPUT_ALL
+ };
+
+ static const struct option longopts[] = {
+ {"all", no_argument, NULL, 'a'},
+ {"bytes", no_argument, NULL, 'b'},
+ {"help", no_argument, NULL, 'h'},
+ {"json", no_argument, NULL, 'J'},
+ {"noheadings", no_argument, NULL, 'n'},
+ {"output", required_argument, NULL, 'o'},
+ {"output-all", no_argument, NULL, OPT_OUTPUT_ALL},
+ {"pairs", no_argument, NULL, 'P'},
+ {"raw", no_argument, NULL, 'r'},
+ {"sysroot", required_argument, NULL, 's'},
+ {"split", required_argument, NULL, 'S'},
+ {"version", no_argument, NULL, 'V'},
+ {"summary", optional_argument, NULL, LSMEM_OPT_SUMARRY },
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'J', 'P', 'r' },
+ { 'S', 'a' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "abhJno:PrS:s:V", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'a':
+ lsmem->list_all = 1;
+ break;
+ case 'b':
+ lsmem->bytes = 1;
+ break;
+ case 'J':
+ lsmem->json = 1;
+ lsmem->want_summary = 0;
+ break;
+ case 'n':
+ lsmem->noheadings = 1;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case OPT_OUTPUT_ALL:
+ for (ncolumns = 0; (size_t)ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ break;
+ case 'P':
+ lsmem->export = 1;
+ lsmem->want_summary = 0;
+ break;
+ case 'r':
+ lsmem->raw = 1;
+ lsmem->want_summary = 0;
+ break;
+ case 's':
+ prefix = optarg;
+ break;
+ case 'S':
+ splitarg = optarg;
+ break;
+ case LSMEM_OPT_SUMARRY:
+ if (optarg) {
+ if (strcmp(optarg, "never") == 0)
+ lsmem->want_summary = 0;
+ else if (strcmp(optarg, "only") == 0)
+ lsmem->want_table = 0;
+ else if (strcmp(optarg, "always") == 0)
+ lsmem->want_summary = 1;
+ else
+ errx(EXIT_FAILURE, _("unsupported --summary argument"));
+ } else
+ lsmem->want_table = 0;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argc != optind) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (lsmem->want_table + lsmem->want_summary == 0)
+ errx(EXIT_FAILURE, _("options --{raw,json,pairs} and --summary=only are mutually exclusive"));
+
+ ul_path_init_debug();
+
+ lsmem->sysmem = ul_new_path(_PATH_SYS_MEMORY);
+ if (!lsmem->sysmem)
+ err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_MEMORY);
+ if (prefix && ul_path_set_prefix(lsmem->sysmem, prefix) != 0)
+ err(EXIT_FAILURE, _("invalid argument to --sysroot"));
+ if (!ul_path_is_accessible(lsmem->sysmem))
+ err(EXIT_FAILURE, _("cannot open %s"), _PATH_SYS_MEMORY);
+
+ /* Shortcut to avoid scols machinery on --summary=only */
+ if (lsmem->want_table == 0 && lsmem->want_summary) {
+ read_basic_info(lsmem);
+ read_info(lsmem);
+ print_summary(lsmem);
+ return EXIT_SUCCESS;
+ }
+
+ /*
+ * Default columns
+ */
+ if (!ncolumns) {
+ add_column(columns, ncolumns++, COL_RANGE);
+ add_column(columns, ncolumns++, COL_SIZE);
+ add_column(columns, ncolumns++, COL_STATE);
+ add_column(columns, ncolumns++, COL_REMOVABLE);
+ add_column(columns, ncolumns++, COL_BLOCK);
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ /*
+ * Initialize output
+ */
+ scols_init_debug(0);
+
+ if (!(lsmem->table = scols_new_table()))
+ errx(EXIT_FAILURE, _("failed to initialize output table"));
+ scols_table_enable_raw(lsmem->table, lsmem->raw);
+ scols_table_enable_export(lsmem->table, lsmem->export);
+ scols_table_enable_json(lsmem->table, lsmem->json);
+ scols_table_enable_noheadings(lsmem->table, lsmem->noheadings);
+
+ if (lsmem->json)
+ scols_table_set_name(lsmem->table, "memory");
+
+ for (i = 0; i < ncolumns; i++) {
+ struct coldesc *ci = get_column_desc(i);
+ struct libscols_column *cl;
+
+ cl = scols_table_new_column(lsmem->table, ci->name, ci->whint, ci->flags);
+ if (!cl)
+ err(EXIT_FAILURE, _("Failed to initialize output column"));
+
+ if (lsmem->json) {
+ int id = get_column_id(i);
+
+ switch (id) {
+ case COL_SIZE:
+ if (!lsmem->bytes)
+ break;
+ /* fallthrough */
+ case COL_NODE:
+ scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
+ break;
+ case COL_REMOVABLE:
+ scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN);
+ break;
+ }
+ }
+ }
+
+ if (splitarg) {
+ int split[ARRAY_SIZE(coldescs)] = { 0 };
+ static size_t nsplits = 0;
+
+ if (strcasecmp(splitarg, "none") == 0)
+ ;
+ else if (string_add_to_idarray(splitarg, split, ARRAY_SIZE(split),
+ &nsplits, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ set_split_policy(lsmem, split, nsplits);
+
+ } else
+ /* follow output columns */
+ set_split_policy(lsmem, columns, ncolumns);
+
+ /*
+ * Read data and print output
+ */
+ read_basic_info(lsmem);
+ read_info(lsmem);
+
+ if (lsmem->want_table) {
+ fill_scols_table(lsmem);
+ scols_print_table(lsmem->table);
+
+ if (lsmem->want_summary)
+ fputc('\n', stdout);
+ }
+
+ if (lsmem->want_summary)
+ print_summary(lsmem);
+
+ scols_unref_table(lsmem->table);
+ ul_unref_path(lsmem->sysmem);
+ free_info(lsmem);
+ return 0;
+}
diff --git a/sys-utils/lsns.8 b/sys-utils/lsns.8
new file mode 100644
index 0000000..30ee204
--- /dev/null
+++ b/sys-utils/lsns.8
@@ -0,0 +1,92 @@
+.\" Man page for the lsns command.
+.\" Copyright 2015 Karel Zak <kzak@redhat.com>
+.\" May be distributed under the GNU General Public License
+
+.TH LSNS 8 "December 2015" "util-linux" "System Administration"
+.SH NAME
+lsns \- list namespaces
+.SH SYNOPSIS
+.B lsns
+[options]
+.RI [ namespace ]
+
+.SH DESCRIPTION
+.B lsns
+lists information about all the currently accessible namespaces or about the
+given \fInamespace\fP. The \fInamespace\fP identifier is an inode number.
+
+The default output is subject to change. So whenever possible, you should
+avoid using default outputs in your scripts. Always explicitly define expected
+columns by using the \fB\-\-output\fR option together with a columns list in
+environments where a stable output is required.
+
+The \fBNSFS\fP column, printed when \fBnet\fP is specified for the
+\fB\-\-type\fP option, is special; it uses multi-line cells. Use the option
+\fB\-\-nowrap\fR to switch to ","-separated single-line representation.
+
+Note that \fBlsns\fR reads information directly from the /proc filesystem and
+for non-root users it may return incomplete information. The current /proc
+filesystem may be unshared and affected by a PID namespace
+(see \fBunshare \-\-mount\-proc\fP for more details).
+.B lsns
+is not able to see persistent namespaces without processes where the namespace
+instance is held by a bind mount to /proc/\fIpid\fR/ns/\fItype\fR.
+
+.SH OPTIONS
+.TP
+.BR \-J , " \-\-json"
+Use JSON output format.
+.TP
+.BR \-l , " \-\-list"
+Use list output format.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line.
+.TP
+.BR \-o , " \-\-output " \fIlist\fP
+Specify which output columns to print. Use \fB\-\-help\fR
+to get a list of all supported columns.
+
+The default list of columns may be extended if \fIlist\fP is
+specified in the format \fB+\fIlist\fP (e.g., \fBlsns \-o +PATH\fP).
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.BR \-p , " \-\-task " \fIpid\fP
+Display only the namespaces held by the process with this \fIpid\fR.
+.TP
+.BR \-r , " \-\-raw"
+Use the raw output format.
+.TP
+.BR \-t , " \-\-type " \fItype\fP
+Display the specified \fItype\fP of namespaces only. The supported types are
+\fBmnt\fP, \fBnet\fP, \fBipc\fP, \fBuser\fP, \fBpid\fP, \fButs\fP,
+\fBcgroup\fP and \fBtime\fP. This option may be given more than once.
+.TP
+.BR \-u , " \-\-notruncate"
+Do not truncate text in columns.
+.TP
+.BR \-W , " \-\-nowrap"
+Do not use multi-line text in columns.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH AUTHORS
+.nf
+Karel Zak <kzak@redhat.com>
+.fi
+
+.SH SEE ALSO
+.BR nsenter (1),
+.BR unshare (1),
+.BR clone (2),
+.BR namespaces (7)
+
+.SH AVAILABILITY
+The lsns command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c
new file mode 100644
index 0000000..eec8e27
--- /dev/null
+++ b/sys-utils/lsns.c
@@ -0,0 +1,1101 @@
+/*
+ * lsns(8) - list system namespaces
+ *
+ * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wchar.h>
+#include <libsmartcols.h>
+#include <libmount.h>
+
+#ifdef HAVE_LINUX_NET_NAMESPACE_H
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/net_namespace.h>
+#endif
+
+#include "pathnames.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "c.h"
+#include "list.h"
+#include "closestream.h"
+#include "optutils.h"
+#include "procutils.h"
+#include "strutils.h"
+#include "namespace.h"
+#include "idcache.h"
+
+#include "debug.h"
+
+static UL_DEBUG_DEFINE_MASK(lsns);
+UL_DEBUG_DEFINE_MASKNAMES(lsns) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define LSNS_DEBUG_INIT (1 << 1)
+#define LSNS_DEBUG_PROC (1 << 2)
+#define LSNS_DEBUG_NS (1 << 3)
+#define LSNS_DEBUG_ALL 0xFFFF
+
+#define LSNS_NETNS_UNUSABLE -2
+
+#define DBG(m, x) __UL_DBG(lsns, LSNS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(lsns, LSNS_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(lsns)
+#include "debugobj.h"
+
+static struct idcache *uid_cache = NULL;
+
+/* column IDs */
+enum {
+ COL_NS = 0,
+ COL_TYPE,
+ COL_PATH,
+ COL_NPROCS,
+ COL_PID,
+ COL_PPID,
+ COL_COMMAND,
+ COL_UID,
+ COL_USER,
+ COL_NETNSID,
+ COL_NSFS,
+};
+
+/* column names */
+struct colinfo {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+ int json_type;
+};
+
+/* columns descriptions */
+static const struct colinfo infos[] = {
+ [COL_NS] = { "NS", 10, SCOLS_FL_RIGHT, N_("namespace identifier (inode number)"), SCOLS_JSON_NUMBER },
+ [COL_TYPE] = { "TYPE", 5, 0, N_("kind of namespace") },
+ [COL_PATH] = { "PATH", 0, 0, N_("path to the namespace")},
+ [COL_NPROCS] = { "NPROCS", 5, SCOLS_FL_RIGHT, N_("number of processes in the namespace"), SCOLS_JSON_NUMBER },
+ [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("lowest PID in the namespace"), SCOLS_JSON_NUMBER },
+ [COL_PPID] = { "PPID", 5, SCOLS_FL_RIGHT, N_("PPID of the PID"), SCOLS_JSON_NUMBER },
+ [COL_COMMAND] = { "COMMAND", 0, SCOLS_FL_TRUNC, N_("command line of the PID")},
+ [COL_UID] = { "UID", 0, SCOLS_FL_RIGHT, N_("UID of the PID"), SCOLS_JSON_NUMBER},
+ [COL_USER] = { "USER", 0, 0, N_("username of the PID")},
+ [COL_NETNSID] = { "NETNSID", 0, SCOLS_FL_RIGHT, N_("namespace ID as used by network subsystem")},
+ [COL_NSFS] = { "NSFS", 0, SCOLS_FL_WRAP, N_("nsfs mountpoint (usually used network subsystem)")}
+};
+
+static int columns[ARRAY_SIZE(infos) * 2];
+static size_t ncolumns;
+
+enum {
+ LSNS_ID_MNT = 0,
+ LSNS_ID_NET,
+ LSNS_ID_PID,
+ LSNS_ID_UTS,
+ LSNS_ID_IPC,
+ LSNS_ID_USER,
+ LSNS_ID_CGROUP,
+ LSNS_ID_TIME
+};
+
+static char *ns_names[] = {
+ [LSNS_ID_MNT] = "mnt",
+ [LSNS_ID_NET] = "net",
+ [LSNS_ID_PID] = "pid",
+ [LSNS_ID_UTS] = "uts",
+ [LSNS_ID_IPC] = "ipc",
+ [LSNS_ID_USER] = "user",
+ [LSNS_ID_CGROUP] = "cgroup",
+ [LSNS_ID_TIME] = "time"
+};
+
+struct lsns_namespace {
+ ino_t id;
+ int type; /* LSNS_* */
+ int nprocs;
+ int netnsid;
+
+ struct lsns_process *proc;
+
+ struct list_head namespaces; /* lsns->processes member */
+ struct list_head processes; /* head of lsns_process *siblings */
+};
+
+struct lsns_process {
+ pid_t pid; /* process PID */
+ pid_t ppid; /* parent's PID */
+ pid_t tpid; /* thread group */
+ char state;
+ uid_t uid;
+
+ ino_t ns_ids[ARRAY_SIZE(ns_names)];
+ struct list_head ns_siblings[ARRAY_SIZE(ns_names)];
+
+ struct list_head processes; /* list of processes */
+
+ struct libscols_line *outline;
+ struct lsns_process *parent;
+
+ int netnsid;
+};
+
+struct lsns {
+ struct list_head processes;
+ struct list_head namespaces;
+
+ pid_t fltr_pid; /* filter out by PID */
+ ino_t fltr_ns; /* filter out by namespace */
+ int fltr_types[ARRAY_SIZE(ns_names)];
+ int fltr_ntypes;
+
+ unsigned int raw : 1,
+ json : 1,
+ tree : 1,
+ list : 1,
+ no_trunc : 1,
+ no_headings: 1,
+ no_wrap : 1;
+
+ struct libmnt_table *tab;
+};
+
+struct netnsid_cache {
+ ino_t ino;
+ int id;
+ struct list_head netnsids;
+};
+
+static struct list_head netnsids_cache;
+
+static int netlink_fd = -1;
+
+static void lsns_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(lsns, LSNS_DEBUG_, 0, LSNS_DEBUG);
+}
+
+static int ns_name2type(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ns_names); i++) {
+ if (strcmp(ns_names[i], name) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ assert(name);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int has_column(int id)
+{
+ size_t i;
+
+ for (i = 0; i < ncolumns; i++) {
+ if (columns[i] == id)
+ return 1;
+ }
+ return 0;
+}
+
+static inline int get_column_id(int num)
+{
+ assert(num >= 0);
+ assert((size_t) num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+
+ return columns[num];
+}
+
+static inline const struct colinfo *get_column_info(unsigned num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+static int get_ns_ino(int dir, const char *nsname, ino_t *ino)
+{
+ struct stat st;
+ char path[16];
+
+ snprintf(path, sizeof(path), "ns/%s", nsname);
+
+ if (fstatat(dir, path, &st, 0) != 0)
+ return -errno;
+ *ino = st.st_ino;
+ return 0;
+}
+
+static int parse_proc_stat(FILE *fp, pid_t *pid, char *state, pid_t *ppid)
+{
+ char *line = NULL, *p;
+ size_t len = 0;
+ int rc;
+
+ if (getline(&line, &len, fp) < 0) {
+ rc = -errno;
+ goto error;
+ }
+
+ p = strrchr(line, ')');
+ if (p == NULL ||
+ sscanf(line, "%d (", pid) != 1 ||
+ sscanf(p, ") %c %d*[^\n]", state, ppid) != 2) {
+ rc = -EINVAL;
+ goto error;
+ }
+ rc = 0;
+
+error:
+ free(line);
+ return rc;
+}
+
+#ifdef HAVE_LINUX_NET_NAMESPACE_H
+static int netnsid_cache_find(ino_t netino, int *netnsid)
+{
+ struct list_head *p;
+
+ list_for_each(p, &netnsids_cache) {
+ struct netnsid_cache *e = list_entry(p,
+ struct netnsid_cache,
+ netnsids);
+ if (e->ino == netino) {
+ *netnsid = e->id;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void netnsid_cache_add(ino_t netino, int netnsid)
+{
+ struct netnsid_cache *e;
+
+ e = xcalloc(1, sizeof(*e));
+ e->ino = netino;
+ e->id = netnsid;
+ INIT_LIST_HEAD(&e->netnsids);
+ list_add(&e->netnsids, &netnsids_cache);
+}
+
+static int get_netnsid_via_netlink_send_request(int target_fd)
+{
+ unsigned char req[NLMSG_SPACE(sizeof(struct rtgenmsg))
+ + RTA_SPACE(sizeof(int32_t))];
+
+ struct nlmsghdr *nlh = (struct nlmsghdr *)req;
+ struct rtgenmsg *rt = NLMSG_DATA(req);
+ struct rtattr *rta = (struct rtattr *)
+ (req + NLMSG_SPACE(sizeof(struct rtgenmsg)));
+ int32_t *fd = RTA_DATA(rta);
+
+ nlh->nlmsg_len = sizeof(req);
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+ nlh->nlmsg_type = RTM_GETNSID;
+ rt->rtgen_family = AF_UNSPEC;
+ rta->rta_type = NETNSA_FD;
+ rta->rta_len = RTA_SPACE(sizeof(int32_t));
+ *fd = target_fd;
+
+ if (send(netlink_fd, req, sizeof(req), 0) < 0)
+ return -1;
+ return 0;
+}
+
+static int get_netnsid_via_netlink_recv_response(int *netnsid)
+{
+ unsigned char res[NLMSG_SPACE(sizeof(struct rtgenmsg))
+ + ((RTA_SPACE(sizeof(int32_t))
+ < RTA_SPACE(sizeof(struct nlmsgerr)))
+ ? RTA_SPACE(sizeof(struct nlmsgerr))
+ : RTA_SPACE(sizeof(int32_t)))];
+ int rtalen;
+ ssize_t reslen;
+
+ struct nlmsghdr *nlh;
+ struct rtattr *rta;
+
+ reslen = recv(netlink_fd, res, sizeof(res), 0);
+ if (reslen < 0)
+ return -1;
+
+ nlh = (struct nlmsghdr *)res;
+ if (!(NLMSG_OK(nlh, (size_t)reslen)
+ && nlh->nlmsg_type == RTM_NEWNSID))
+ return -1;
+
+ rtalen = NLMSG_PAYLOAD(nlh, sizeof(struct rtgenmsg));
+ rta = (struct rtattr *)(res + NLMSG_SPACE(sizeof(struct rtgenmsg)));
+ if (!(RTA_OK(rta, rtalen)
+ && rta->rta_type == NETNSA_NSID))
+ return -1;
+
+ *netnsid = *(int *)RTA_DATA(rta);
+
+ return 0;
+}
+
+static int get_netnsid_via_netlink(int dir, const char *path)
+{
+ int netnsid;
+ int target_fd;
+
+ if (netlink_fd < 0)
+ return LSNS_NETNS_UNUSABLE;
+
+ target_fd = openat(dir, path, O_RDONLY);
+ if (target_fd < 0)
+ return LSNS_NETNS_UNUSABLE;
+
+ if (get_netnsid_via_netlink_send_request(target_fd) < 0) {
+ netnsid = LSNS_NETNS_UNUSABLE;
+ goto out;
+ }
+
+ if (get_netnsid_via_netlink_recv_response(&netnsid) < 0) {
+ netnsid = LSNS_NETNS_UNUSABLE;
+ goto out;
+ }
+
+ out:
+ close(target_fd);
+ return netnsid;
+}
+
+static int get_netnsid(int dir, ino_t netino)
+{
+ int netnsid;
+
+ if (!netnsid_cache_find(netino, &netnsid)) {
+ netnsid = get_netnsid_via_netlink(dir, "ns/net");
+ netnsid_cache_add(netino, netnsid);
+ }
+
+ return netnsid;
+}
+#else
+static int get_netnsid(int dir __attribute__((__unused__)),
+ ino_t netino __attribute__((__unused__)))
+{
+ return LSNS_NETNS_UNUSABLE;
+}
+#endif /* HAVE_LINUX_NET_NAMESPACE_H */
+
+static int read_process(struct lsns *ls, pid_t pid)
+{
+ struct lsns_process *p = NULL;
+ char buf[BUFSIZ];
+ DIR *dir;
+ int rc = 0, fd;
+ FILE *f = NULL;
+ size_t i;
+ struct stat st;
+
+ DBG(PROC, ul_debug("reading %d", (int) pid));
+
+ snprintf(buf, sizeof(buf), "/proc/%d", pid);
+ dir = opendir(buf);
+ if (!dir)
+ return -errno;
+
+ p = xcalloc(1, sizeof(*p));
+ p->netnsid = LSNS_NETNS_UNUSABLE;
+
+ if (fstat(dirfd(dir), &st) == 0) {
+ p->uid = st.st_uid;
+ add_uid(uid_cache, st.st_uid);
+ }
+
+ fd = openat(dirfd(dir), "stat", O_RDONLY);
+ if (fd < 0) {
+ rc = -errno;
+ goto done;
+ }
+ if (!(f = fdopen(fd, "r"))) {
+ rc = -errno;
+ goto done;
+ }
+ rc = parse_proc_stat(f, &p->pid, &p->state, &p->ppid);
+ if (rc < 0)
+ goto done;
+ rc = 0;
+
+ for (i = 0; i < ARRAY_SIZE(p->ns_ids); i++) {
+ INIT_LIST_HEAD(&p->ns_siblings[i]);
+
+ if (!ls->fltr_types[i])
+ continue;
+
+ rc = get_ns_ino(dirfd(dir), ns_names[i], &p->ns_ids[i]);
+ if (rc && rc != -EACCES && rc != -ENOENT)
+ goto done;
+ if (i == LSNS_ID_NET)
+ p->netnsid = get_netnsid(dirfd(dir), p->ns_ids[i]);
+ rc = 0;
+ }
+
+ INIT_LIST_HEAD(&p->processes);
+
+ DBG(PROC, ul_debugobj(p, "new pid=%d", p->pid));
+ list_add_tail(&p->processes, &ls->processes);
+done:
+ if (f)
+ fclose(f);
+ closedir(dir);
+ if (rc)
+ free(p);
+ return rc;
+}
+
+static int read_processes(struct lsns *ls)
+{
+ struct proc_processes *proc = NULL;
+ pid_t pid;
+ int rc = 0;
+
+ DBG(PROC, ul_debug("opening /proc"));
+
+ if (!(proc = proc_open_processes())) {
+ rc = -errno;
+ goto done;
+ }
+
+ while (proc_next_pid(proc, &pid) == 0) {
+ rc = read_process(ls, pid);
+ if (rc && rc != -EACCES && rc != -ENOENT)
+ break;
+ rc = 0;
+ }
+done:
+ DBG(PROC, ul_debug("closing /proc"));
+ proc_close_processes(proc);
+ return rc;
+}
+
+static struct lsns_namespace *get_namespace(struct lsns *ls, ino_t ino)
+{
+ struct list_head *p;
+
+ list_for_each(p, &ls->namespaces) {
+ struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces);
+
+ if (ns->id == ino)
+ return ns;
+ }
+ return NULL;
+}
+
+static int namespace_has_process(struct lsns_namespace *ns, pid_t pid)
+{
+ struct list_head *p;
+
+ list_for_each(p, &ns->processes) {
+ struct lsns_process *proc = list_entry(p, struct lsns_process, ns_siblings[ns->type]);
+
+ if (proc->pid == pid)
+ return 1;
+ }
+ return 0;
+}
+
+static struct lsns_namespace *add_namespace(struct lsns *ls, int type, ino_t ino)
+{
+ struct lsns_namespace *ns = xcalloc(1, sizeof(*ns));
+
+ if (!ns)
+ return NULL;
+
+ DBG(NS, ul_debugobj(ns, "new %s[%ju]", ns_names[type], (uintmax_t)ino));
+
+ INIT_LIST_HEAD(&ns->processes);
+ INIT_LIST_HEAD(&ns->namespaces);
+
+ ns->type = type;
+ ns->id = ino;
+
+ list_add_tail(&ns->namespaces, &ls->namespaces);
+ return ns;
+}
+
+static int add_process_to_namespace(struct lsns *ls, struct lsns_namespace *ns, struct lsns_process *proc)
+{
+ struct list_head *p;
+
+ DBG(NS, ul_debugobj(ns, "add process [%p] pid=%d to %s[%ju]",
+ proc, proc->pid, ns_names[ns->type], (uintmax_t)ns->id));
+
+ list_for_each(p, &ls->processes) {
+ struct lsns_process *xproc = list_entry(p, struct lsns_process, processes);
+
+ if (xproc->pid == proc->ppid) /* my parent */
+ proc->parent = xproc;
+ else if (xproc->ppid == proc->pid) /* my child */
+ xproc->parent = proc;
+ }
+
+ list_add_tail(&proc->ns_siblings[ns->type], &ns->processes);
+ ns->nprocs++;
+
+ if (!ns->proc || ns->proc->pid > proc->pid)
+ ns->proc = proc;
+
+ return 0;
+}
+
+static int cmp_namespaces(struct list_head *a, struct list_head *b,
+ __attribute__((__unused__)) void *data)
+{
+ struct lsns_namespace *xa = list_entry(a, struct lsns_namespace, namespaces),
+ *xb = list_entry(b, struct lsns_namespace, namespaces);
+
+ return cmp_numbers(xa->id, xb->id);
+}
+
+static int netnsid_xasputs(char **str, int netnsid)
+{
+ if (netnsid >= 0)
+ return xasprintf(str, "%d", netnsid);
+#ifdef NETNSA_NSID_NOT_ASSIGNED
+ if (netnsid == NETNSA_NSID_NOT_ASSIGNED)
+ return xasprintf(str, "%s", "unassigned");
+#endif
+ return 0;
+}
+
+static int read_namespaces(struct lsns *ls)
+{
+ struct list_head *p;
+
+ DBG(NS, ul_debug("reading namespace"));
+
+ list_for_each(p, &ls->processes) {
+ size_t i;
+ struct lsns_namespace *ns;
+ struct lsns_process *proc = list_entry(p, struct lsns_process, processes);
+
+ for (i = 0; i < ARRAY_SIZE(proc->ns_ids); i++) {
+ if (proc->ns_ids[i] == 0)
+ continue;
+ if (!(ns = get_namespace(ls, proc->ns_ids[i]))) {
+ ns = add_namespace(ls, i, proc->ns_ids[i]);
+ if (!ns)
+ return -ENOMEM;
+ }
+ add_process_to_namespace(ls, ns, proc);
+ }
+ }
+
+ list_sort(&ls->namespaces, cmp_namespaces, NULL);
+
+ return 0;
+}
+
+static int is_nsfs_root(struct libmnt_fs *fs, void *data)
+{
+ if (!mnt_fs_match_fstype(fs, "nsfs") || !mnt_fs_get_root(fs))
+ return 0;
+
+ return (strcmp(mnt_fs_get_root(fs), (char *)data) == 0);
+}
+
+static int is_path_included(const char *path_set, const char *elt,
+ const char sep)
+{
+ size_t elt_len;
+ size_t path_set_len;
+ char *tmp;
+
+
+ tmp = strstr(path_set, elt);
+ if (!tmp)
+ return 0;
+
+ elt_len = strlen(elt);
+ path_set_len = strlen(path_set);
+
+ /* path_set includes only elt or
+ * path_set includes elt as the first element.
+ */
+ if (tmp == path_set
+ && ((path_set_len == elt_len)
+ || (path_set[elt_len] == sep)))
+ return 1;
+
+ /* path_set includes elt at the middle
+ * or as the last element.
+ */
+ if ((*(tmp - 1) == sep)
+ && ((*(tmp + elt_len) == sep)
+ || (*(tmp + elt_len) == '\0')))
+ return 1;
+
+ return 0;
+}
+
+static int nsfs_xasputs(char **str,
+ struct lsns_namespace *ns,
+ struct libmnt_table *tab,
+ char sep)
+{
+ struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
+ char *expected_root;
+ struct libmnt_fs *fs = NULL;
+
+ xasprintf(&expected_root, "%s:[%ju]", ns_names[ns->type], (uintmax_t)ns->id);
+ *str = NULL;
+
+ while (mnt_table_find_next_fs(tab, itr, is_nsfs_root,
+ expected_root, &fs) == 0) {
+
+ const char *tgt = mnt_fs_get_target(fs);
+
+ if (!*str)
+ xasprintf(str, "%s", tgt);
+
+ else if (!is_path_included(*str, tgt, sep)) {
+ char *tmp = NULL;
+
+ xasprintf(&tmp, "%s%c%s", *str, sep, tgt);
+ free(*str);
+ *str = tmp;
+ }
+ }
+ free(expected_root);
+ mnt_free_iter(itr);
+
+ return 1;
+}
+static void add_scols_line(struct lsns *ls, struct libscols_table *table,
+ struct lsns_namespace *ns, struct lsns_process *proc)
+{
+ size_t i;
+ struct libscols_line *line;
+
+ assert(ns);
+ assert(table);
+
+ line = scols_table_new_line(table,
+ ls->tree && proc->parent ? proc->parent->outline : NULL);
+ if (!line) {
+ warn(_("failed to add line to output"));
+ return;
+ }
+
+ for (i = 0; i < ncolumns; i++) {
+ char *str = NULL;
+
+ switch (get_column_id(i)) {
+ case COL_NS:
+ xasprintf(&str, "%ju", (uintmax_t)ns->id);
+ break;
+ case COL_PID:
+ xasprintf(&str, "%d", (int) proc->pid);
+ break;
+ case COL_PPID:
+ xasprintf(&str, "%d", (int) proc->ppid);
+ break;
+ case COL_TYPE:
+ xasprintf(&str, "%s", ns_names[ns->type]);
+ break;
+ case COL_NPROCS:
+ xasprintf(&str, "%d", ns->nprocs);
+ break;
+ case COL_COMMAND:
+ str = proc_get_command(proc->pid);
+ if (!str)
+ str = proc_get_command_name(proc->pid);
+ break;
+ case COL_PATH:
+ xasprintf(&str, "/proc/%d/ns/%s", (int) proc->pid, ns_names[ns->type]);
+ break;
+ case COL_UID:
+ xasprintf(&str, "%d", (int) proc->uid);
+ break;
+ case COL_USER:
+ xasprintf(&str, "%s", get_id(uid_cache, proc->uid)->name);
+ break;
+ case COL_NETNSID:
+ if (ns->type == LSNS_ID_NET)
+ netnsid_xasputs(&str, proc->netnsid);
+ break;
+ case COL_NSFS:
+ nsfs_xasputs(&str, ns, ls->tab, ls->no_wrap ? ',' : '\n');
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, i, str) != 0)
+ err_oom();
+ }
+
+ proc->outline = line;
+}
+
+static struct libscols_table *init_scols_table(struct lsns *ls)
+{
+ struct libscols_table *tab;
+ size_t i;
+
+ tab = scols_new_table();
+ if (!tab) {
+ warn(_("failed to initialize output table"));
+ return NULL;
+ }
+
+ scols_table_enable_raw(tab, ls->raw);
+ scols_table_enable_json(tab, ls->json);
+ scols_table_enable_noheadings(tab, ls->no_headings);
+
+ if (ls->json)
+ scols_table_set_name(tab, "namespaces");
+
+ for (i = 0; i < ncolumns; i++) {
+ const struct colinfo *col = get_column_info(i);
+ int flags = col->flags;
+ struct libscols_column *cl;
+
+ if (ls->no_trunc)
+ flags &= ~SCOLS_FL_TRUNC;
+ if (ls->tree && get_column_id(i) == COL_COMMAND)
+ flags |= SCOLS_FL_TREE;
+ if (ls->no_wrap)
+ flags &= ~SCOLS_FL_WRAP;
+
+ cl = scols_table_new_column(tab, col->name, col->whint, flags);
+ if (cl == NULL) {
+ warnx(_("failed to initialize output column"));
+ goto err;
+ }
+ if (ls->json)
+ scols_column_set_json_type(cl, col->json_type);
+
+ if (!ls->no_wrap && get_column_id(i) == COL_NSFS) {
+ scols_column_set_wrapfunc(cl,
+ scols_wrapnl_chunksize,
+ scols_wrapnl_nextchunk,
+ NULL);
+ scols_column_set_safechars(cl, "\n");
+ }
+ }
+
+ return tab;
+err:
+ scols_unref_table(tab);
+ return NULL;
+}
+
+static int show_namespaces(struct lsns *ls)
+{
+ struct libscols_table *tab;
+ struct list_head *p;
+ int rc = 0;
+
+ tab = init_scols_table(ls);
+ if (!tab)
+ return -ENOMEM;
+
+ list_for_each(p, &ls->namespaces) {
+ struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces);
+
+ if (ls->fltr_pid != 0 && !namespace_has_process(ns, ls->fltr_pid))
+ continue;
+
+ add_scols_line(ls, tab, ns, ns->proc);
+ }
+
+ scols_print_table(tab);
+ scols_unref_table(tab);
+ return rc;
+}
+
+static void show_process(struct lsns *ls, struct libscols_table *tab,
+ struct lsns_process *proc, struct lsns_namespace *ns)
+{
+ /*
+ * create a tree from parent->child relation, but only if the parent is
+ * within the same namespace
+ */
+ if (ls->tree
+ && proc->parent
+ && !proc->parent->outline
+ && proc->parent->ns_ids[ns->type] == proc->ns_ids[ns->type])
+ show_process(ls, tab, proc->parent, ns);
+
+ add_scols_line(ls, tab, ns, proc);
+}
+
+
+static int show_namespace_processes(struct lsns *ls, struct lsns_namespace *ns)
+{
+ struct libscols_table *tab;
+ struct list_head *p;
+
+ tab = init_scols_table(ls);
+ if (!tab)
+ return -ENOMEM;
+
+ list_for_each(p, &ns->processes) {
+ struct lsns_process *proc = list_entry(p, struct lsns_process, ns_siblings[ns->type]);
+
+ if (!proc->outline)
+ show_process(ls, tab, proc, ns);
+ }
+
+
+ scols_print_table(tab);
+ scols_unref_table(tab);
+ return 0;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+
+ fprintf(out,
+ _(" %s [options] [<namespace>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("List system namespaces.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -J, --json use JSON output format\n"), out);
+ fputs(_(" -l, --list use list format output\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> define which output columns to use\n"), out);
+ fputs(_(" --output-all output all columns\n"), out);
+ fputs(_(" -p, --task <pid> print process namespaces\n"), out);
+ fputs(_(" -r, --raw use the raw output format\n"), out);
+ fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
+ fputs(_(" -W, --nowrap don't use multi-line representation\n"), out);
+ fputs(_(" -t, --type <name> namespace type (mnt, net, ipc, user, pid, uts, cgroup, time)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("lsns(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct lsns ls;
+ int c;
+ int r = 0;
+ char *outarg = NULL;
+ enum {
+ OPT_OUTPUT_ALL = CHAR_MAX + 1
+ };
+ static const struct option long_opts[] = {
+ { "json", no_argument, NULL, 'J' },
+ { "task", required_argument, NULL, 'p' },
+ { "help", no_argument, NULL, 'h' },
+ { "output", required_argument, NULL, 'o' },
+ { "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
+ { "notruncate", no_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'V' },
+ { "noheadings", no_argument, NULL, 'n' },
+ { "nowrap", no_argument, NULL, 'W' },
+ { "list", no_argument, NULL, 'l' },
+ { "raw", no_argument, NULL, 'r' },
+ { "type", required_argument, NULL, 't' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'J','r' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ int is_net = 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ lsns_init_debug();
+ memset(&ls, 0, sizeof(ls));
+
+ INIT_LIST_HEAD(&ls.processes);
+ INIT_LIST_HEAD(&ls.namespaces);
+ INIT_LIST_HEAD(&netnsids_cache);
+
+ while ((c = getopt_long(argc, argv,
+ "Jlp:o:nruhVt:W", long_opts, NULL)) != -1) {
+
+ err_exclusive_options(c, long_opts, excl, excl_st);
+
+ switch(c) {
+ case 'J':
+ ls.json = 1;
+ break;
+ case 'l':
+ ls.list = 1;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case OPT_OUTPUT_ALL:
+ for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ break;
+ case 'p':
+ ls.fltr_pid = strtos32_or_err(optarg, _("invalid PID argument"));
+ break;
+ case 'n':
+ ls.no_headings = 1;
+ break;
+ case 'r':
+ ls.no_wrap = ls.raw = 1;
+ break;
+ case 'u':
+ ls.no_trunc = 1;
+ break;
+ case 't':
+ {
+ int type = ns_name2type(optarg);
+ if (type < 0)
+ errx(EXIT_FAILURE, _("unknown namespace type: %s"), optarg);
+ ls.fltr_types[type] = 1;
+ ls.fltr_ntypes++;
+ if (type == LSNS_ID_NET)
+ is_net = 1;
+ break;
+ }
+ case 'W':
+ ls.no_wrap = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (!ls.fltr_ntypes) {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ns_names); i++)
+ ls.fltr_types[i] = 1;
+ }
+
+ if (optind < argc) {
+ if (ls.fltr_pid)
+ errx(EXIT_FAILURE, _("--task is mutually exclusive with <namespace>"));
+ ls.fltr_ns = strtou64_or_err(argv[optind], _("invalid namespace argument"));
+ ls.tree = ls.list ? 0 : 1;
+
+ if (!ncolumns) {
+ columns[ncolumns++] = COL_PID;
+ columns[ncolumns++] = COL_PPID;
+ columns[ncolumns++] = COL_USER;
+ columns[ncolumns++] = COL_COMMAND;
+ }
+ }
+
+ if (!ncolumns) {
+ columns[ncolumns++] = COL_NS;
+ columns[ncolumns++] = COL_TYPE;
+ columns[ncolumns++] = COL_NPROCS;
+ columns[ncolumns++] = COL_PID;
+ columns[ncolumns++] = COL_USER;
+ if (is_net) {
+ columns[ncolumns++] = COL_NETNSID;
+ columns[ncolumns++] = COL_NSFS;
+ }
+ columns[ncolumns++] = COL_COMMAND;
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ scols_init_debug(0);
+
+ uid_cache = new_idcache();
+ if (!uid_cache)
+ err(EXIT_FAILURE, _("failed to allocate UID cache"));
+
+#ifdef HAVE_LINUX_NET_NAMESPACE_H
+ if (has_column(COL_NETNSID))
+ netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+#endif
+ if (has_column(COL_NSFS)) {
+ ls.tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+ if (!ls.tab)
+ err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO);
+ }
+
+ r = read_processes(&ls);
+ if (!r)
+ r = read_namespaces(&ls);
+ if (!r) {
+ if (ls.fltr_ns) {
+ struct lsns_namespace *ns = get_namespace(&ls, ls.fltr_ns);
+
+ if (!ns)
+ errx(EXIT_FAILURE, _("not found namespace: %ju"), (uintmax_t) ls.fltr_ns);
+ r = show_namespace_processes(&ls, ns);
+ } else
+ r = show_namespaces(&ls);
+ }
+
+ mnt_free_table(ls.tab);
+ if (netlink_fd >= 0)
+ close(netlink_fd);
+ free_idcache(uid_cache);
+ return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/sys-utils/mount.8 b/sys-utils/mount.8
new file mode 100644
index 0000000..d5fa55f
--- /dev/null
+++ b/sys-utils/mount.8
@@ -0,0 +1,2903 @@
+.\" Copyright (c) 1996-2004 Andries Brouwer
+.\" Copyright (C) 2006-2012 Karel Zak <kzak@redhat.com>
+.\"
+.\" This page is somewhat derived from a page that was
+.\" (c) 1980, 1989, 1991 The Regents of the University of California
+.\" and had been heavily modified by Rik Faith and myself.
+.\" (Probably no BSD text remains.)
+.\" Fragments of text were written by Werner Almesberger, Remy Card,
+.\" Stephen Tweedie and Eric Youngdale.
+.\"
+.\" This is free documentation; 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.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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.,
+.\" 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+.\"
+.TH MOUNT 8 "August 2015" "util-linux" "System Administration"
+.SH NAME
+mount \- mount a filesystem
+.SH SYNOPSIS
+.B mount
+.RB [ \-h | \-V ]
+.LP
+.B mount
+.RB [ \-l ]
+.RB [ \-t
+.IR fstype ]
+.LP
+.B mount \-a
+.RB [ \-fFnrsvw ]
+.RB [ \-t
+.IR fstype ]
+.RB [ \-O
+.IR optlist ]
+.LP
+.B mount
+.RB [ \-fnrsvw ]
+.RB [ \-o
+.IR options ]
+.IR device | mountpoint
+.LP
+.B mount
+.RB [ \-fnrsvw ]
+.RB [ \-t
+.IB fstype ]
+.RB [ \-o
+.IR options ]
+.I device mountpoint
+.LP
+.B mount
+.BR \-\-bind | \-\-rbind | \-\-move
+.I olddir newdir
+.LP
+.B mount
+.BR \-\-make\- { shared | slave | private | unbindable | rshared | \
+rslave | rprivate | runbindable }
+.I mountpoint
+.SH DESCRIPTION
+All files accessible in a Unix system are arranged in one big
+tree, the file hierarchy, rooted at
+.IR / .
+These files can be spread out over several devices. The
+.B mount
+command serves to attach the filesystem found on some device
+to the big file tree. Conversely, the
+.BR umount (8)
+command will detach it again. The filesystem is used to control how data is
+stored on the device or provided in a virtual way by network or other services.
+
+The standard form of the
+.B mount
+command is:
+.RS
+
+.BI "mount \-t" " type device dir"
+
+.RE
+This tells the kernel to attach the filesystem found on
+.I device
+(which is of type
+.IR type )
+at the directory
+.IR dir .
+The option \fB\-t \fItype\fR is optional. The
+.B mount
+command is usually able to detect a filesystem. The root permissions are necessary
+to mount a filesystem by default. See section "Non-superuser mounts" below for more details.
+The previous contents (if any) and owner and mode of
+.I dir
+become invisible, and as long as this filesystem remains mounted,
+the pathname
+.I dir
+refers to the root of the filesystem on
+.IR device .
+
+If only the directory or the device is given, for example:
+.RS
+.sp
+.B mount /dir
+.sp
+.RE
+then \fBmount\fR looks for a mountpoint (and if not found then for a device) in the
+.I /etc/fstab
+file. It's possible to use the
+.B \-\-target
+or
+.B \-\-source
+options to avoid ambiguous interpretation of the given argument.
+For example:
+.RS
+.sp
+.B mount \-\-target /mountpoint
+.sp
+.RE
+
+The same filesystem may be mounted more than once, and in some cases (e.g.,
+network filesystems) the same filesystem may be mounted on the same
+mountpoint multiple times. The
+.B mount
+command does not implement any policy to
+control this behavior. All behavior is controlled by the kernel and it is usually
+specific to the filesystem driver. The exception is \fB\-\-all\fR, in this case
+already mounted filesystems are ignored (see \fB\-\-all\fR below for more details).
+
+.SS Listing the mounts
+The listing mode is maintained for backward compatibility only.
+
+For more robust and customizable output use
+.BR findmnt (8),
+\fBespecially in your scripts\fP. Note that control characters in the
+mountpoint name are replaced with '?'.
+
+The following command lists all mounted filesystems (of type
+.IR type ):
+.RS
+.sp
+.BR "mount " [ \-l "] [" "\-t \fItype\/\fP" ]
+.sp
+.RE
+The option \fB\-l\fR adds labels to this listing. See below.
+
+.SS Indicating the device and filesystem
+Most devices are indicated by a filename (of a block special device), like
+.IR /dev/sda1 ,
+but there are other possibilities. For example, in the case of an NFS mount,
+.I device
+may look like
+.IR knuth.cwi.nl:/dir .
+.PP
+The device names of disk partitions are unstable; hardware reconfiguration,
+and adding or removing a device can cause changes in names.
+This is the reason why it's
+strongly recommended to use filesystem or partition identifiers like UUID or
+LABEL. Currently supported identifiers (tags):
+.RS
+.TP
+LABEL=\fIlabel\fR
+Human readable filesystem identifier. See also \fB\-L\fR.
+.TP
+UUID=\fIuuid\fR
+Filesystem universally unique identifier. The format of the UUID is usually a
+series of hex digits separated by hyphens. See also \fB\-U\fR.
+.sp
+Note that
+.BR mount (8)
+uses UUIDs as strings. The UUIDs from the command line or from
+.BR fstab (5)
+are not converted to internal binary representation. The string representation
+of the UUID should be based on lower case characters.
+.TP
+PARTLABEL=\fIlabel\fR
+Human readable partition identifier. This identifier is independent on
+filesystem and does not change by mkfs or mkswap operations It's supported
+for example for GUID Partition Tables (GPT).
+.TP
+PARTUUID=\fIuuid\fR
+Partition universally unique identifier. This identifier is independent on
+filesystem and does not change by mkfs or mkswap operations It's supported
+for example for GUID Partition Tables (GPT).
+.TP
+ID=\fIid\fR
+Hardware block device ID as generated by udevd. This identifier is usually
+based on WWN (unique storage identifier) and assigned by the hardware
+manufacturer. See \fBls /dev/disk/by-id\fR for more details, this directory
+and running udevd is required. This identifier is not recommended for generic
+use as the identifier is not strictly defined and it depends on udev, udev rules
+and hardware.
+.RE
+.sp
+The command \fBlsblk \-\-fs\fR provides an overview of filesystems, LABELs and UUIDs
+on available block devices. The command \fBblkid \-p <device>\fR provides details about
+a filesystem on the specified device.
+
+Don't forget that there is no guarantee that UUIDs and labels are really
+unique, especially if you move, share or copy the device. Use
+.B "lsblk \-o +UUID,PARTUUID"
+to verify that the UUIDs are really unique in your system.
+
+The recommended setup is to use tags (e.g.\& \fBUUID=\fIuuid\fR) rather than
+.I /dev/disk/by-{label,uuid,id,partuuid,partlabel}
+udev symlinks in the
+.I /etc/fstab
+file. Tags are
+more readable, robust and portable. The
+.BR mount (8)
+command internally uses udev
+symlinks, so the use of symlinks in
+.I /etc/fstab
+has no advantage over tags.
+For more details see
+.BR libblkid (3).
+
+The
+.I proc
+filesystem is not associated with a special device, and when
+mounting it, an arbitrary keyword\(emfor example,
+.IR proc \(emcan
+be used instead of a device specification.
+(The customary choice
+.I none
+is less fortunate: the error message `none already mounted' from
+.B mount
+can be confusing.)
+
+.SS The files /etc/fstab, /etc/mtab and /proc/mounts
+The file
+.I /etc/fstab
+(see
+.BR fstab (5)),
+may contain lines describing what devices are usually
+mounted where, using which options. The default location of the
+.BR fstab (5)
+file can be overridden with the
+.BI \-\-fstab " path"
+command-line option (see below for more details).
+.LP
+The command
+.RS
+.sp
+.B mount \-a
+.RB [ \-t
+.IR type ]
+.RB [ \-O
+.IR optlist ]
+.sp
+.RE
+(usually given in a bootscript) causes all filesystems mentioned in
+.I fstab
+(of the proper type and/or having or not having the proper options)
+to be mounted as indicated, except for those whose line contains the
+.B noauto
+keyword. Adding the
+.B \-F
+option will make \fBmount\fR fork, so that the
+filesystems are mounted in parallel.
+.LP
+When mounting a filesystem mentioned in
+.I fstab
+or
+.IR mtab ,
+it suffices to specify on the command line only the device, or only the mount point.
+.sp
+The programs
+.B mount
+and
+.B umount
+traditionally maintained a list of currently mounted filesystems in the file
+.IR /etc/mtab .
+The support for regular classic
+.I /etc/mtab
+is completely disabled at compile time by default, because on current Linux
+systems it is better to make
+.I /etc/mtab
+a symlink to
+.I /proc/mounts
+instead. The regular
+.I mtab
+file maintained in userspace cannot reliably
+work with namespaces, containers and other advanced Linux features.
+If the regular
+.I mtab
+support is enabled, then it's possible to
+use the file as well as the symlink.
+.sp
+If no arguments are given to
+.BR mount ,
+the list of mounted filesystems is printed.
+.sp
+If you want to override mount options from
+.IR /etc/fstab ,
+you have to use the \fB\-o\fR option:
+.RS
+.sp
+.BI mount " device" \fR| "dir " \-o " options"
+.sp
+.RE
+and then the mount options from the command line will be appended to
+the list of options from
+.IR /etc/fstab .
+This default behaviour can be changed using the
+\fB\-\-options\-mode\fR
+command-line option.
+The usual behavior is that the last option wins if there are conflicting
+ones.
+.sp
+The
+.B mount
+program does not read the
+.I /etc/fstab
+file if both
+.I device
+(or LABEL, UUID, ID, PARTUUID or PARTLABEL) and
+.I dir
+are specified. For example, to mount device
+.BR foo " at " /dir :
+.RS
+.sp
+.B "mount /dev/foo /dir"
+.sp
+.RE
+This default behaviour can be changed by using the
+\fB\-\-options\-source\-force\fR command-line option
+to always read configuration from
+.IR fstab .
+For non-root users
+.B mount
+always reads the
+.I fstab
+configuration.
+
+.SS Non-superuser mounts
+Normally, only the superuser can mount filesystems.
+However, when
+.I fstab
+contains the
+.B user
+option on a line, anybody can mount the corresponding filesystem.
+.LP
+Thus, given a line
+.RS
+.sp
+.B "/dev/cdrom /cd iso9660 ro,user,noauto,unhide"
+.sp
+.RE
+any user can mount the iso9660 filesystem found on an inserted CDROM
+using the command:
+.PP
+.RS
+.B "mount /cd"
+.sp
+.RE
+Note that \fBmount\fR is very strict about non-root users and all paths
+specified on command line are verified before
+.I fstab
+is parsed or a helper
+program is executed. It's strongly recommended to use a valid mountpoint to
+specify filesystem, otherwise \fBmount\fR may fail. For example it's a bad idea
+to use NFS or CIFS source on command line.
+.PP
+Since util-linux 2.35, \fBmount\fR does not exit when user permissions are
+inadequate according to libmount's internal security rules.
+Instead, it drops suid permissions
+and continues as regular non-root user. This behavior supports use-cases where
+root permissions are not necessary (e.g., fuse filesystems, user namespaces,
+etc).
+.PP
+For more details, see
+.BR fstab (5).
+Only the user that mounted a filesystem can unmount it again.
+If any user should be able to unmount it, then use
+.B users
+instead of
+.B user
+in the
+.I fstab
+line.
+The
+.B owner
+option is similar to the
+.B user
+option, with the restriction that the user must be the owner
+of the special file. This may be useful e.g.\& for
+.I /dev/fd
+if a login script makes the console user owner of this device.
+The
+.B group
+option is similar, with the restriction that the user must be a
+member of the group of the special file.
+
+.SS Bind mount operation
+Remount part of the file hierarchy somewhere else. The call is:
+
+.RS
+.br
+.B mount \-\-bind
+.I olddir newdir
+.RE
+
+or by using this
+.I fstab
+entry:
+
+.RS
+.br
+.BI / olddir
+.BI / newdir
+.B none bind
+.RE
+
+After this call the same contents are accessible in two places.
+
+It is important to understand that "bind" does not create any second-class
+or special node in the kernel VFS. The "bind" is just another operation to
+attach a filesystem. There is nowhere stored information that the filesystem
+has been attached by a "bind" operation. The \fIolddir\fR and \fInewdir\fR are
+independent and the \fIolddir\fR may be unmounted.
+
+One can also remount a single file (on a single file). It's also
+possible to use a bind mount to create a mountpoint from a regular
+directory, for example:
+
+.RS
+.br
+.B mount \-\-bind foo foo
+.RE
+
+The bind mount call attaches only (part of) a single filesystem, not possible
+submounts. The entire file hierarchy including submounts can be attached
+a second place by using:
+
+.RS
+.br
+.B mount \-\-rbind
+.I olddir newdir
+.RE
+
+Note that the filesystem mount options maintained by the kernel will remain the same as those
+on the original mount point. The userspace mount options (e.g., _netdev) will not be copied
+by
+.B mount
+and it's necessary to explicitly specify the options on the
+.B mount
+command line.
+
+Since util-linux 2.27
+.BR mount (8)
+permits changing the mount options by passing the
+relevant options along with
+.BR \-\-bind .
+For example:
+
+.RS
+.br
+.B mount \-o bind,ro foo foo
+.RE
+
+This feature is not supported by the Linux kernel; it is implemented in userspace
+by an additional \fBmount\fR(2) remounting system call.
+This solution is not atomic.
+
+The alternative (classic) way to create a read-only bind mount is to use the remount
+operation, for example:
+
+.RS
+.br
+.B mount \-\-bind
+.I olddir newdir
+.br
+.B mount \-o remount,bind,ro
+.I olddir newdir
+.RE
+
+Note that a read-only bind will create a read-only mountpoint (VFS entry),
+but the original filesystem superblock will still be writable, meaning that the
+.I olddir
+will be writable, but the
+.I newdir
+will be read-only.
+
+It's also possible to change nosuid, nodev, noexec, noatime, nodiratime and
+relatime VFS entry flags via a "remount,bind" operation.
+The other flags (for example
+filesystem-specific flags) are silently ignored. It's impossible to change mount
+options recursively (for example with \fB\-o rbind,ro\fR).
+
+Since util-linux 2.31,
+.B mount
+ignores the \fBbind\fR flag from
+.I /etc/fstab
+on a
+.B remount
+operation
+(if "\-o remount" is specified on command line).
+This is necessary to fully control
+mount options on remount by command line. In previous versions the bind
+flag has been always applied and it was impossible to re-define mount options
+without interaction with the bind semantic. This
+.BR mount (8)
+behavior does not affect situations when "remount,bind" is specified in the
+.I /etc/fstab
+file.
+
+.SS The move operation
+Move a
+.B mounted tree
+to another place (atomically). The call is:
+
+.RS
+.br
+.B mount \-\-move
+.I olddir newdir
+.RE
+
+This will cause the contents which previously appeared under
+.I olddir
+to now be accessible under
+.IR newdir .
+The physical location of the files is not changed.
+Note that
+.I olddir
+has to be a mountpoint.
+
+Note also that moving a mount residing under a shared mount is invalid and
+unsupported. Use
+.B findmnt \-o TARGET,PROPAGATION
+to see the current propagation flags.
+
+.SS Shared subtree operations
+Since Linux 2.6.15 it is possible to mark a mount and its submounts as shared,
+private, slave or unbindable. A shared mount provides the ability to create mirrors
+of that mount such that mounts and unmounts within any of the mirrors propagate
+to the other mirror. A slave mount receives propagation from its master, but
+not vice versa. A private mount carries no propagation abilities. An
+unbindable mount is a private mount which cannot be cloned through a bind
+operation. The detailed semantics are documented in
+.I Documentation/filesystems/sharedsubtree.txt
+file in the kernel source tree; see also
+.BR mount_namespaces (7).
+
+Supported operations are:
+
+.RS
+.nf
+.BI "mount \-\-make\-shared " mountpoint
+.BI "mount \-\-make\-slave " mountpoint
+.BI "mount \-\-make\-private " mountpoint
+.BI "mount \-\-make\-unbindable " mountpoint
+.fi
+.RE
+
+The following commands allow one to recursively change the type of all the
+mounts under a given mountpoint.
+
+.RS
+.nf
+.BI "mount \-\-make\-rshared " mountpoint
+.BI "mount \-\-make\-rslave " mountpoint
+.BI "mount \-\-make\-rprivate " mountpoint
+.BI "mount \-\-make\-runbindable " mountpoint
+.fi
+.RE
+
+.BR mount (8)
+.B does not read
+.BR fstab (5)
+when a \fB\-\-make-\fR* operation is requested. All necessary information has to be
+specified on the command line.
+
+Note that the Linux kernel does not allow changing multiple propagation flags
+with a single
+.BR mount (2)
+system call, and the flags cannot be mixed with other mount options and operations.
+
+Since util-linux 2.23 the \fBmount\fR command can be used to do more propagation
+(topology) changes by one mount(8) call and do it also together with other
+mount operations. This feature is EXPERIMENTAL. The propagation flags are applied
+by additional \fBmount\fR(2) system calls when the preceding mount operations
+were successful. Note that this use case is not atomic. It is possible to
+specify the propagation flags in
+.BR fstab (5)
+as mount options
+.RB ( private ,
+.BR slave ,
+.BR shared ,
+.BR unbindable ,
+.BR rprivate ,
+.BR rslave ,
+.BR rshared ,
+.BR runbindable ).
+
+For example:
+
+.RS
+.nf
+.B mount \-\-make\-private \-\-make\-unbindable /dev/sda1 /foo
+.fi
+.RE
+
+is the same as:
+
+.RS
+.nf
+.B mount /dev/sda1 /foo
+.B mount \-\-make\-private /foo
+.B mount \-\-make\-unbindable /foo
+.fi
+.RE
+
+.SH COMMAND-LINE OPTIONS
+The full set of mount options used by an invocation of
+.B mount
+is determined by first extracting the
+mount options for the filesystem from the
+.I fstab
+table, then applying any options specified by the
+.B \-o
+argument, and finally applying a
+.BR \-r " or " \-w
+option, when present.
+
+The \fBmount\fR command does not pass all command-line options to the
+\fB/sbin/mount.\fIsuffix\fR mount helpers. The interface between \fBmount\fR
+and the mount helpers is described below in the section \fBEXTERNAL HELPERS\fR.
+.sp
+Command-line options available for the
+.B mount
+command are:
+.TP
+.BR \-a , " \-\-all"
+Mount all filesystems (of the given types) mentioned in
+.I fstab
+(except for those whose line contains the
+.B noauto
+keyword). The filesystems are mounted following their order in
+.IR fstab .
+The
+.B mount
+command compares filesystem source, target (and fs root for bind
+mount or btrfs) to detect already mounted filesystems. The kernel table with
+already mounted filesystems is cached during \fBmount \-\-all\fR. This means
+that all duplicated
+.I fstab
+entries will be mounted.
+.sp
+The option \fB\-\-all\fR is possible to use for remount operation too. In this
+case all filters (\fB\-t\fR and \fB\-O\fR) are applied to the table of already
+mounted filesystems.
+.sp
+Since version 2.35 is possible to use the command line option \fB\-o\fR to
+alter mount options from
+.I fstab
+(see also \fB\-\-options\-mode\fR).
+.sp
+Note that it is a bad practice to use \fBmount \-a\fR for
+.I fstab
+checking. The recommended solution is \fBfindmnt \-\-verify\fR.
+.TP
+.BR \-B , " \-\-bind"
+Remount a subtree somewhere else (so that its contents are available
+in both places). See above, under \fBBind mounts\fR.
+.TP
+.BR \-c , " \-\-no\-canonicalize"
+Don't canonicalize paths. The
+.B mount
+command canonicalizes all paths
+(from the command line or
+.IR fstab )
+by default. This option can be used
+together with the
+.B \-f
+flag for already canonicalized absolute paths. The option is designed for mount
+helpers which call \fBmount \-i\fR. It is strongly recommended to not use this
+command-line option for normal mount operations.
+.sp
+Note that \fBmount\fR(8) does not pass this option to the
+\fB/sbin/mount.\fItype\fR helpers.
+.TP
+.BR \-F , " \-\-fork"
+(Used in conjunction with
+.BR \-a .)
+Fork off a new incarnation of \fBmount\fR for each device.
+This will do the mounts on different devices or different NFS servers
+in parallel.
+This has the advantage that it is faster; also NFS timeouts proceed in
+parallel.
+A disadvantage is that the order of the mount operations is undefined.
+Thus, you cannot use this option if you want to mount both
+.I /usr
+and
+.IR /usr/spool .
+.IP "\fB\-f, \-\-fake\fP"
+Causes everything to be done except for the actual system call; if it's not
+obvious, this ``fakes'' mounting the filesystem. This option is useful in
+conjunction with the
+.B \-v
+flag to determine what the
+.B mount
+command is trying to do. It can also be used to add entries for devices
+that were mounted earlier with the \fB\-n\fR option. The \fB\-f\fR option
+checks for an existing record in
+.I /etc/mtab
+and fails when the record already
+exists (with a regular non-fake mount, this check is done by the kernel).
+.IP "\fB\-i, \-\-internal\-only\fP"
+Don't call the \fB/sbin/mount.\fIfilesystem\fR helper even if it exists.
+.TP
+.BR \-L , " \-\-label " \fIlabel
+Mount the partition that has the specified
+.IR label .
+.TP
+.BR \-l , " \-\-show\-labels"
+Add the labels in the mount output. \fBmount\fR must have
+permission to read the disk device (e.g.\& be set-user-ID root) for this to work.
+One can set such a label for ext2, ext3 or ext4 using the
+.BR e2label (8)
+utility, or for XFS using
+.BR xfs_admin (8),
+or for reiserfs using
+.BR reiserfstune (8).
+.TP
+.BR \-M , " \-\-move"
+Move a subtree to some other place. See above, the subsection
+\fBThe move operation\fR.
+.TP
+.BR \-n , " \-\-no\-mtab"
+Mount without writing in
+.IR /etc/mtab .
+This is necessary for example when
+.I /etc
+is on a read-only filesystem.
+.TP
+.BR \-N , " \-\-namespace " \fIns
+Perform the mount operation in the mount namespace specified by \fIns\fR.
+\fIns\fR is either PID of process running in that namespace
+or special file representing that namespace.
+.sp
+.BR mount (8)
+switches to the mount namespace when it reads
+.IR /etc/fstab ,
+writes
+.I /etc/mtab
+(or writes to
+.IR /run/mount )
+and calls the
+.BR mount (2)
+system call, otherwise it runs in the original mount namespace.
+This means that the target namespace does not have
+to contain any libraries or other requirements necessary to execute the
+.BR mount (2)
+call.
+.sp
+See \fBmount_namespaces\fR(7) for more information.
+.TP
+.BR \-O , " \-\-test\-opts " \fIopts
+Limit the set of filesystems to which the
+.B \-a
+option applies. In this regard it is like the
+.B \-t
+option except that
+.B \-O
+is useless without
+.BR \-a .
+For example, the command:
+.RS
+.RS
+.sp
+.B "mount \-a \-O no_netdev"
+.sp
+.RE
+mounts all filesystems except those which have the option
+.I _netdev
+specified in the options field in the
+.I /etc/fstab
+file.
+
+It is different from
+.B \-t
+in that each option is matched exactly; a leading
+.B no
+at the beginning of one option does not negate the rest.
+
+The
+.B \-t
+and
+.B \-O
+options are cumulative in effect; that is, the command
+.RS
+.sp
+.B "mount \-a \-t ext2 \-O _netdev"
+.sp
+.RE
+mounts all ext2 filesystems with the _netdev option, not all filesystems
+that are either ext2 or have the _netdev option specified.
+.RE
+.TP
+.BR \-o , " \-\-options " \fIopts
+Use the specified mount options. The \fIopts\fR argument is
+a comma-separated list. For example:
+.RS
+.RS
+.sp
+.B "mount LABEL=mydisk \-o noatime,nodev,nosuid"
+.sp
+.RE
+
+For more details, see the
+.B FILESYSTEM-INDEPENDENT MOUNT OPTIONS
+and
+.B FILESYSTEM-SPECIFIC MOUNT OPTIONS
+sections.
+.RE
+
+.TP
+.BR "\-\-options\-mode " \fImode
+Controls how to combine options from
+.IR fstab / mtab
+with options from the command line.
+\fImode\fR can be one of
+.BR ignore ", " append ", " prepend " or " replace .
+For example, \fBappend\fR means that options from
+.I fstab
+are appended to options from the command line.
+The default value is \fBprepend\fR -- it means command line options are evaluated after
+.I fstab
+options.
+Note that the last option wins if there are conflicting ones.
+
+.TP
+.BR "\-\-options\-source " \fIsource
+Source of default options.
+\fIsource\fR is a comma-separated list of
+.BR fstab ", " mtab " and " disable .
+\fBdisable\fR disables
+.BR fstab " and " mtab
+and disables \fB\-\-options\-source\-force\fR.
+The default value is \fBfstab,mtab\fR.
+
+.TP
+.B \-\-options\-source\-force
+Use options from
+.IR fstab / mtab
+even if both \fIdevice\fR and \fIdir\fR are specified.
+
+.TP
+.BR \-R , " \-\-rbind"
+Remount a subtree and all possible submounts somewhere else (so that its
+contents are available in both places). See above, the subsection
+\fBBind mounts\fR.
+.TP
+.BR \-r , " \-\-read\-only"
+Mount the filesystem read-only. A synonym is
+.BR "\-o ro" .
+.sp
+Note that, depending on the filesystem type, state and kernel behavior, the
+system may still write to the device. For example, ext3 and ext4 will replay the
+journal if the filesystem is dirty. To prevent this kind of write access, you
+may want to mount an ext3 or ext4 filesystem with the \fBro,noload\fR mount
+options or set the block device itself to read-only mode, see the
+.BR blockdev (8)
+command.
+.TP
+.B \-s
+Tolerate sloppy mount options rather than failing. This will ignore mount
+options not supported by a filesystem type. Not all filesystems support this
+option. Currently it's supported by the \fBmount.nfs\fR mount helper only.
+.TP
+.BI \-\-source " device"
+If only one argument for the mount command is given, then the argument might be
+interpreted as the target (mountpoint) or source (device).
+This option allows you to
+explicitly define that the argument is the mount source.
+.TP
+.BI \-\-target " directory"
+If only one argument for the mount command is given, then the argument might be
+interpreted as the target (mountpoint) or source (device).
+This option allows you to
+explicitly define that the argument is the mount target.
+.TP
+.BI \-\-target\-prefix " directory"
+Prepend the specified directory to all mount targets.
+This option can be used to follow
+.IR fstab ,
+but mount operations are done in another place, for example:
+.RS
+.RS
+.sp
+.B "mount \-\-all \-\-target\-prefix /chroot \-o X\-mount.mkdir"
+.sp
+.RE
+mounts all from system
+.I fstab
+to
+.IR /chroot ,
+all missing mountpoint are created
+(due to X-mount.mkdir). See also \fB\-\-fstab\fP to use an alternative
+.IR fstab .
+.RE
+.TP
+.BR \-T , " \-\-fstab " \fIpath
+Specifies an alternative
+.I fstab
+file.
+If \fIpath\fP is a directory, then the files
+in the directory are sorted by
+.BR strverscmp (3);
+files that start with "."\& or without an
+.I \&.fstab
+extension are ignored. The option
+can be specified more than once. This option is mostly designed for initramfs
+or chroot scripts where additional configuration is specified beyond standard
+system configuration.
+.sp
+Note that \fBmount\fR(8) does not pass the option \fB\-\-fstab\fP to the
+\fB/sbin/mount.\fItype\fR helpers, meaning that the alternative
+.I fstab
+files will be
+invisible for the helpers. This is no problem for normal mounts, but user
+(non-root) mounts always require
+.I fstab
+to verify the user's rights.
+.TP
+.BR \-t , " \-\-types " \fIfstype
+The argument following the
+.B \-t
+is used to indicate the filesystem type. The filesystem types which are
+currently supported depend on the running kernel. See
+.I /proc/filesystems
+and
+.I /lib/modules/$(uname \-r)/kernel/fs
+for a complete list of the filesystems. The most common are ext2, ext3, ext4,
+xfs, btrfs, vfat, sysfs, proc, nfs and cifs.
+.sp
+The programs
+.B mount
+and
+.B umount
+support filesystem subtypes. The subtype is defined by a '.subtype' suffix. For
+example 'fuse.sshfs'. It's recommended to use subtype notation rather than add
+any prefix to the mount source (for example 'sshfs#example.com' is
+deprecated).
+
+If no
+.B \-t
+option is given, or if the
+.B auto
+type is specified,
+.B mount
+will try to guess the desired type.
+Mount uses the blkid library for guessing the filesystem
+type; if that does not turn up anything that looks familiar,
+.B mount
+will try to read the file
+.IR /etc/filesystems ,
+or, if that does not exist,
+.IR /proc/filesystems .
+All of the filesystem types listed there will be tried,
+except for those that are labeled "nodev" (e.g.\&
+.IR devpts ,
+.I proc
+and
+.IR nfs ).
+If
+.I /etc/filesystems
+ends in a line with a single *, mount will read
+.I /proc/filesystems
+afterwards. While trying, all filesystem types will be
+mounted with the mount option \fBsilent\fR.
+.sp
+The
+.B auto
+type may be useful for user-mounted floppies.
+Creating a file
+.I /etc/filesystems
+can be useful to change the probe order (e.g., to try vfat before msdos
+or ext3 before ext2) or if you use a kernel module autoloader.
+.sp
+More than one type may be specified in a comma-separated
+list, for the
+.B \-t
+option as well as in an
+.I /etc/fstab
+entry. The list of filesystem types for the
+.B \-t
+option can be prefixed with
+.B no
+to specify the filesystem types on which no action should be taken.
+The prefix
+.B no
+has no effect when specified in an
+.I /etc/fstab
+entry.
+.sp
+The prefix
+.B no
+can be meaningful with the
+.B \-a
+option. For example, the command
+.RS
+.RS
+.sp
+.B "mount \-a \-t nomsdos,smbfs"
+.sp
+.RE
+mounts all filesystems except those of type
+.I msdos
+and
+.IR smbfs .
+.sp
+For most types all the
+.B mount
+program has to do is issue a simple
+.BR mount (2)
+system call, and no detailed knowledge of the filesystem type is required.
+For a few types however (like nfs, nfs4, cifs, smbfs, ncpfs) an ad hoc code is
+necessary. The nfs, nfs4, cifs, smbfs, and ncpfs filesystems
+have a separate mount program. In order to make it possible to
+treat all types in a uniform way, \fBmount\fR will execute the program
+.BI /sbin/mount. type
+(if that exists) when called with type
+.IR type .
+Since different versions of the
+.B smbmount
+program have different calling conventions,
+.B /sbin/mount.smbfs
+may have to be a shell script that sets up the desired call.
+.RE
+.TP
+.BR \-U , " \-\-uuid " \fIuuid
+Mount the partition that has the specified
+.IR uuid .
+.TP
+.BR \-v , " \-\-verbose"
+Verbose mode.
+.TP
+.BR \-w , " \-\-rw" , " \-\-read\-write"
+Mount the filesystem read/write. Read-write is the kernel default and the
+.B mount
+default is to try read-only if the previous mount syscall with read-write flags
+on write-protected devices of filesystems failed.
+.sp
+A synonym is
+.BR "\-o rw" .
+
+Note that specifying \fB\-w\fR on the command line forces \fBmount\fR to never
+try read-only mount on write-protected devices or already mounted read-only
+filesystems.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH FILESYSTEM-INDEPENDENT MOUNT OPTIONS
+Some of these options are only useful when they appear in the
+.I /etc/fstab
+file.
+
+Some of these options could be enabled or disabled by default
+in the system kernel. To check the current setting see the options
+in
+.IR /proc/mounts .
+Note that filesystems also have per-filesystem
+specific default mount options (see for example \fBtune2fs \-l\fP
+output for extN filesystems).
+
+The following options apply to any filesystem that is being
+mounted (but not every filesystem actually honors them \(en e.g.\&, the
+.B sync
+option today has an effect only for ext2, ext3, ext4, fat, vfat, ufs and xfs):
+
+.TP
+.B async
+All I/O to the filesystem should be done asynchronously. (See also the
+.B sync
+option.)
+.TP
+.B atime
+Do not use the \fBnoatime\fR feature, so the inode access time is controlled
+by kernel defaults. See also the descriptions of the \fB\%relatime\fR and
+.B strictatime
+mount options.
+.TP
+.B noatime
+Do not update inode access times on this filesystem (e.g.\& for faster
+access on the news spool to speed up news servers). This works for all
+inode types (directories too), so it implies \fB\%nodiratime\fR.
+.TP
+.B auto
+Can be mounted with the
+.B \-a
+option.
+.TP
+.B noauto
+Can only be mounted explicitly (i.e., the
+.B \-a
+option will not cause the filesystem to be mounted).
+.TP
+.na
+.BR context=\fIcontext ", " fscontext=\fIcontext ", " defcontext=\fIcontext ", and " \%rootcontext=\fIcontext
+.ad
+The
+.B context=
+option is useful when mounting filesystems that do not support
+extended attributes, such as a floppy or hard disk formatted with VFAT, or
+systems that are not normally running under SELinux, such as an ext3 or ext4 formatted
+disk from a non-SELinux workstation. You can also use
+.B context=
+on filesystems you do not trust, such as a floppy. It also helps in compatibility with
+xattr-supporting filesystems on earlier 2.4.<x> kernel versions. Even where
+xattrs are supported, you can save time not having to label every file by
+assigning the entire disk one security context.
+
+A commonly used option for removable media is
+.BR \%context="system_u:object_r:removable_t" .
+
+Two other options are
+.B fscontext=
+and
+.BR defcontext= ,
+both of which are mutually exclusive of the
+.B context=
+option. This means you
+can use fscontext and defcontext with each other, but neither can be used with
+context.
+
+The
+.B fscontext=
+option works for all filesystems, regardless of their xattr
+support. The fscontext option sets the overarching filesystem label to a
+specific security context. This filesystem label is separate from the
+individual labels on the files. It represents the entire filesystem for
+certain kinds of permission checks, such as during mount or file creation.
+Individual file labels are still obtained from the xattrs on the files
+themselves. The context option actually sets the aggregate context that
+fscontext provides, in addition to supplying the same label for individual
+files.
+
+You can set the default security context for unlabeled files using
+.B defcontext=
+option. This overrides the value set for unlabeled files in the policy and requires a
+filesystem that supports xattr labeling.
+
+The
+.B rootcontext=
+option allows you to explicitly label the root inode of a FS being mounted
+before that FS or inode becomes visible to userspace. This was found to be
+useful for things like stateless Linux.
+
+Note that the kernel rejects any remount request that includes the context
+option, \fBeven\fP when unchanged from the current context.
+
+.BR "Warning: the \fIcontext\fP value might contain commas" ,
+in which case the value has to be properly quoted, otherwise
+.BR mount (8)
+will interpret the comma as a separator between mount options. Don't forget that
+the shell strips off quotes and thus
+.BR "double quoting is required" .
+For example:
+.RS
+.RS
+.sp
+.nf
+.B mount \-t tmpfs none /mnt \-o \e
+.B \ \ 'context="system_u:object_r:tmp_t:s0:c127,c456",noexec'
+.fi
+.sp
+.RE
+For more details, see
+.BR selinux (8).
+.RE
+
+.TP
+.B defaults
+Use the default options:
+.BR rw ", " suid ", " dev ", " exec ", " auto ", " nouser ", and " async .
+
+Note that the real set of all default mount options depends on the kernel
+and filesystem type. See the beginning of this section for more details.
+.TP
+.B dev
+Interpret character or block special devices on the filesystem.
+.TP
+.B nodev
+Do not interpret character or block special devices on the filesystem.
+.TP
+.B diratime
+Update directory inode access times on this filesystem. This is the default.
+(This option is ignored when \fBnoatime\fR is set.)
+.TP
+.B nodiratime
+Do not update directory inode access times on this filesystem.
+(This option is implied when \fBnoatime\fR is set.)
+.TP
+.B dirsync
+All directory updates within the filesystem should be done synchronously.
+This affects the following system calls: creat, link, unlink, symlink,
+mkdir, rmdir, mknod and rename.
+.TP
+.B exec
+Permit execution of binaries.
+.TP
+.B noexec
+Do not permit direct execution of any binaries on the mounted filesystem.
+.TP
+.B group
+Allow an ordinary user to mount the filesystem if one
+of that user's groups matches the group of the device.
+This option implies the options
+.BR nosuid " and " nodev
+(unless overridden by subsequent options, as in the option line
+.BR group,dev,suid ).
+.TP
+.B iversion
+Every time the inode is modified, the i_version field will be incremented.
+.TP
+.B noiversion
+Do not increment the i_version inode field.
+.TP
+.B mand
+Allow mandatory locks on this filesystem. See
+.BR fcntl (2).
+.TP
+.B nomand
+Do not allow mandatory locks on this filesystem.
+.TP
+.B _netdev
+The filesystem resides on a device that requires network access
+(used to prevent the system from attempting to mount these filesystems
+until the network has been enabled on the system).
+.TP
+.B nofail
+Do not report errors for this device if it does not exist.
+.TP
+.B relatime
+Update inode access times relative to modify or change time. Access
+time is only updated if the previous access time was earlier than the
+current modify or change time. (Similar to \fB\%noatime\fR, but it doesn't
+break \fBmutt\fR or other applications that need to know if a file has been
+read since the last time it was modified.)
+
+Since Linux 2.6.30, the kernel defaults to the behavior provided by this
+option (unless
+.B \%noatime
+was specified), and the
+.B \%strictatime
+option is required to obtain traditional semantics. In addition, since Linux
+2.6.30, the file's last access time is always updated if it is more than 1
+day old.
+.TP
+.B norelatime
+Do not use the
+.B relatime
+feature. See also the
+.B strictatime
+mount option.
+.TP
+.B strictatime
+Allows to explicitly request full atime updates. This makes it
+possible for the kernel to default to
+.B \%relatime
+or
+.B \%noatime
+but still allow userspace to override it. For more details about the default
+system mount options see
+.IR /proc/mounts .
+.TP
+.B nostrictatime
+Use the kernel's default behavior for inode access time updates.
+.TP
+.B lazytime
+Only update times (atime, mtime, ctime) on the in-memory version of the file inode.
+
+This mount option significantly reduces writes to the inode table for
+workloads that perform frequent random writes to preallocated files.
+
+The on-disk timestamps are updated only when:
+.RS
+.IP - 2
+the inode needs to be updated for some change unrelated to file timestamps
+.IP -
+the application employs
+.BR fsync (2),
+.BR syncfs (2),
+or
+.BR sync (2)
+.IP -
+an undeleted inode is evicted from memory
+.IP -
+more than 24 hours have passed since the i-node was written to disk.
+.RE
+.sp
+.TP
+.B nolazytime
+Do not use the lazytime feature.
+.TP
+.B suid
+Honor set-user-ID and set-group-ID bits or file capabilities when
+executing programs from this filesystem.
+.TP
+.B nosuid
+Do not honor set-user-ID and set-group-ID bits or file capabilities when
+executing programs from this filesystem.
+.TP
+.B silent
+Turn on the silent flag.
+.TP
+.B loud
+Turn off the silent flag.
+.TP
+.B owner
+Allow an ordinary user to mount the filesystem if that
+user is the owner of the device.
+This option implies the options
+.BR nosuid " and " nodev
+(unless overridden by subsequent options, as in the option line
+.BR owner,dev,suid ).
+.TP
+.B remount
+Attempt to remount an already-mounted filesystem. This is commonly
+used to change the mount flags for a filesystem, especially to make a
+readonly filesystem writable. It does not change device or mount point.
+
+The remount operation together with the
+.B bind
+flag has special semantics. See above, the subsection \fBBind mounts\fR.
+
+The remount functionality follows the standard way the
+.B mount
+command works
+with options from
+.IR fstab .
+This means that \fBmount\fR does not read
+.I fstab
+(or
+.IR mtab )
+only when both
+.I device
+and
+.I dir
+are specified.
+.sp
+.in +4
+.B "mount \-o remount,rw /dev/foo /dir"
+.in
+.sp
+After this call all old mount options are replaced and arbitrary stuff from
+.I fstab
+(or
+.IR mtab )
+is ignored, except the loop= option which is internally
+generated and maintained by the mount command.
+.sp
+.in +4
+.B "mount \-o remount,rw /dir"
+.in
+.sp
+After this call, mount reads
+.I fstab
+and merges these options with
+the options from the command line (\fB\-o\fR).
+If no mountpoint is found in
+.IR fstab ,
+then a remount with unspecified source is
+allowed.
+.sp
+.B mount
+allows the use of \fB\-\-all\fR to remount all already mounted filesystems
+which match a specified filter (\fB\-O\fR and \fB\-t\fR). For example:
+.sp
+.in +4
+.B "mount \-\-all \-o remount,ro \-t vfat"
+.in
+.sp
+remounts all already mounted vfat filesystems in read-only mode. Each of the
+filesystems is remounted by "mount \-o remount,ro /dir" semantic.
+This means the
+.B mount
+command reads
+.I fstab
+or
+.I mtab
+and merges these options with the options
+from the command line.
+.TP
+.B ro
+Mount the filesystem read-only.
+.TP
+.B rw
+Mount the filesystem read-write.
+.TP
+.B sync
+All I/O to the filesystem should be done synchronously. In the case of
+media with a limited number of write cycles
+(e.g.\& some flash drives), \fBsync\fR may cause life-cycle shortening.
+.TP
+.B user
+Allow an ordinary user to mount the filesystem.
+The name of the mounting user is written to the
+.I mtab
+file (or to the private
+libmount file in
+.I /run/mount
+on systems without a regular
+.IR mtab )
+so that this
+same user can unmount the filesystem again.
+This option implies the options
+.BR noexec ", " nosuid ", and " nodev
+(unless overridden by subsequent options, as in the option line
+.BR user,exec,dev,suid ).
+.TP
+.B nouser
+Forbid an ordinary user to mount the filesystem.
+This is the default; it does not imply any other options.
+.TP
+.B users
+Allow any user to mount and to unmount the filesystem, even
+when some other ordinary user mounted it.
+This option implies the options
+.BR noexec ", " nosuid ", and " nodev
+(unless overridden by subsequent options, as in the option line
+.BR users,exec,dev,suid ).
+.TP
+.B X-*
+All options prefixed with "X-" are interpreted as comments or as userspace
+application-specific options.
+These options are not stored in user space (e.g.,
+.I mtab
+file),
+nor sent to the mount.\fItype\fR helpers nor to the
+.BR mount (2)
+system call. The suggested format is \fBX-\fIappname\fR.\fIoption\fR.
+.TP
+.B x-*
+The same as \fBX-*\fR options, but stored permanently in user space.
+This means the options are also available for
+.B umount
+or other operations. Note
+that maintaining mount options in user space is tricky,
+because it's necessary use
+libmount-based tools and there is no guarantee that the options will be always
+available (for example after a move mount operation or in unshared namespace).
+
+Note that before util-linux v2.30 the x-* options have not been maintained by
+libmount and stored in user space (functionality was the same as for X-* now),
+but due to the growing number of use-cases (in initrd, systemd etc.) the
+functionality has been extended to keep existing
+.I fstab
+configurations usable
+without a change.
+.TP
+.BR X-mount.mkdir [ = \fImode\fR ]
+Allow to make a target directory (mountpoint) if it does not exit yet.
+The optional argument
+.I mode
+specifies the filesystem access mode used for
+.BR mkdir (2)
+in octal notation. The default mode is 0755. This functionality is supported
+only for root users or when mount executed without suid permissions. The option
+is also supported as x-mount.mkdir, this notation is deprecated since v2.30.
+.TP
+.B nosymfollow
+Do not follow symlinks when resolving paths. Symlinks can still be created,
+and
+.BR readlink (1),
+.BR readlink (2),
+.BR realpath (1)
+and
+.BR realpath (3)
+all still work properly.
+
+.SH FILESYSTEM-SPECIFIC MOUNT OPTIONS
+This section lists options that are specific to particular filesystems.
+Where possible, you should first consult filesystem-specific manual pages
+for details.
+Some of those pages are listed in the following table.
+.TS
+lb lb
+l l.
+Filesystem(s) Manual page
+btrfs \fBbtrfs\fP(5)
+cifs \fBmount.cifs\fP(8)
+ext2, ext3, ext4 \fBext4\fP(5)
+fuse \fBfuse\fP(8)
+nfs \fBnfs\fP(5)
+tmpfs \fBtmpfs\fP(5)
+xfs \fBxfs\fP(5)
+.TE
+.PP
+Note that some of the pages listed above might be available only
+after you install the respective userland tools.
+.PP
+The following options apply only to certain filesystems.
+We sort them by filesystem.
+All options follow the
+.B \-o
+flag.
+.PP
+What options are supported depends a bit on the running kernel.
+Further information may be available in filesystem-specific
+files in the kernel source subdirectory
+.IR Documentation/filesystems .
+.\"
+.SS "Mount options for adfs"
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+Set the owner and group of the files in the filesystem (default: uid=gid=0).
+.TP
+\fBownmask=\fP\,\fIvalue\fP and \fBothmask=\fP\,\fIvalue\fP
+Set the permission mask for ADFS 'owner' permissions and 'other' permissions,
+respectively (default: 0700 and 0077, respectively).
+See also
+.IR /usr/src/linux/Documentation/filesystems/adfs.rst .
+
+.SS "Mount options for affs"
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+Set the owner and group of the root of the filesystem (default: uid=gid=0,
+but with option
+.B uid
+or
+.B gid
+without specified value, the UID and GID of the current process are taken).
+.TP
+\fBsetuid=\fP\,\fIvalue\fP and \fBsetgid=\fP\,\fIvalue\fP
+Set the owner and group of all files.
+.TP
+.BI mode= value
+Set the mode of all files to
+.IR value " & 0777"
+disregarding the original permissions.
+Add search permission to directories that have read permission.
+The value is given in octal.
+.TP
+.B protect
+Do not allow any changes to the protection bits on the filesystem.
+.TP
+.B usemp
+Set UID and GID of the root of the filesystem to the UID and GID
+of the mount point upon the first sync or umount, and then
+clear this option. Strange...
+.TP
+.B verbose
+Print an informational message for each successful mount.
+.TP
+.BI prefix= string
+Prefix used before volume name, when following a link.
+.TP
+.BI volume= string
+Prefix (of length at most 30) used before '/' when following a symbolic link.
+.TP
+.BI reserved= value
+(Default: 2.) Number of unused blocks at the start of the device.
+.TP
+.BI root= value
+Give explicitly the location of the root block.
+.TP
+.BI bs= value
+Give blocksize. Allowed values are 512, 1024, 2048, 4096.
+.TP
+.BR grpquota | noquota | quota | usrquota
+These options are accepted but ignored.
+(However, quota utilities may react to such strings in
+.IR /etc/fstab .)
+
+.SS "Mount options for debugfs"
+The debugfs filesystem is a pseudo filesystem, traditionally mounted on
+.IR /sys/kernel/debug .
+.\" or just /debug
+.\" present since 2.6.11
+As of kernel version 3.4, debugfs has the following options:
+.TP
+.BI uid= n ", gid=" n
+Set the owner and group of the mountpoint.
+.TP
+.BI mode= value
+Sets the mode of the mountpoint.
+
+.SS "Mount options for devpts"
+The devpts filesystem is a pseudo filesystem, traditionally mounted on
+.IR /dev/pts .
+In order to acquire a pseudo terminal, a process opens
+.IR /dev/ptmx ;
+the number of the pseudo terminal is then made available to the process
+and the pseudo terminal slave can be accessed as
+.IR /dev/pts/ <number>.
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+This sets the owner or the group of newly created pseudo terminals to
+the specified values. When nothing is specified, they will
+be set to the UID and GID of the creating process.
+For example, if there is a tty group with GID 5, then
+.B gid=5
+will cause newly created pseudo terminals to belong to the tty group.
+.TP
+.BI mode= value
+Set the mode of newly created pseudo terminals to the specified value.
+The default is 0600.
+A value of
+.B mode=620
+and
+.B gid=5
+makes "mesg y" the default on newly created pseudo terminals.
+.TP
+\fBnewinstance
+Create a private instance of the devpts filesystem, such that
+indices of pseudo terminals allocated in this new instance are
+independent of indices created in other instances of devpts.
+
+All mounts of devpts without this
+.B newinstance
+option share the same set of pseudo terminal indices (i.e., legacy mode).
+Each mount of devpts with the
+.B newinstance
+option has a private set of pseudo terminal indices.
+
+This option is mainly used to support containers in the
+Linux kernel. It is implemented in Linux kernel versions
+starting with 2.6.29. Further, this mount option is valid
+only if CONFIG_DEVPTS_MULTIPLE_INSTANCES is enabled in the
+kernel configuration.
+
+To use this option effectively,
+.I /dev/ptmx
+must be a symbolic link to
+.IR pts/ptmx .
+See
+.I Documentation/filesystems/devpts.txt
+in the Linux kernel source tree for details.
+.TP
+.BI ptmxmode= value
+
+Set the mode for the new
+.I ptmx
+device node in the devpts filesystem.
+
+With the support for multiple instances of devpts (see
+.B newinstance
+option above), each instance has a private
+.I ptmx
+node in the root of the devpts filesystem (typically
+.IR /dev/pts/ptmx ).
+
+For compatibility with older versions of the kernel, the
+default mode of the new
+.I ptmx
+node is 0000.
+.BI ptmxmode= value
+specifies a more useful mode for the
+.I ptmx
+node and is highly recommended when the
+.B newinstance
+option is specified.
+
+This option is only implemented in Linux kernel versions
+starting with 2.6.29. Further, this option is valid only if
+CONFIG_DEVPTS_MULTIPLE_INSTANCES is enabled in the kernel
+configuration.
+
+.SS "Mount options for fat"
+(Note:
+.I fat
+is not a separate filesystem, but a common part of the
+.IR msdos ,
+.I umsdos
+and
+.I vfat
+filesystems.)
+.TP
+.BR blocksize= { 512 | 1024 | 2048 }
+Set blocksize (default 512). This option is obsolete.
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+Set the owner and group of all files.
+(Default: the UID and GID of the current process.)
+.TP
+.BI umask= value
+Set the umask (the bitmask of the permissions that are
+.B not
+present). The default is the umask of the current process.
+The value is given in octal.
+.TP
+.BI dmask= value
+Set the umask applied to directories only.
+The default is the umask of the current process.
+The value is given in octal.
+.TP
+.BI fmask= value
+Set the umask applied to regular files only.
+The default is the umask of the current process.
+The value is given in octal.
+.TP
+.BI allow_utime= value
+This option controls the permission check of mtime/atime.
+.RS
+.TP
+.B 20
+If current process is in group of file's group ID, you can change timestamp.
+.TP
+.B 2
+Other users can change timestamp.
+.PP
+The default is set from `dmask' option. (If the directory is writable,
+.BR utime (2)
+is also allowed. I.e.\& \s+3~\s0dmask & 022)
+
+Normally
+.BR utime (2)
+checks that the current process is owner of the file, or that it has the
+.B CAP_FOWNER
+capability. But FAT filesystems don't have UID/GID on disk, so the
+normal check is too inflexible. With this option you can relax it.
+.RE
+.TP
+.BI check= value
+Three different levels of pickiness can be chosen:
+.RS
+.TP
+.BR r [ elaxed ]
+Upper and lower case are accepted and equivalent, long name parts are
+truncated (e.g.\&
+.I verylongname.foobar
+becomes
+.IR verylong.foo ),
+leading and embedded spaces are accepted in each name part (name and extension).
+.TP
+.BR n [ ormal ]
+Like "relaxed", but many special characters (*, ?, <, spaces, etc.) are
+rejected. This is the default.
+.TP
+.BR s [ trict ]
+Like "normal", but names that contain long parts or special characters
+that are sometimes used on Linux but are not accepted by MS-DOS
+(+, =, etc.) are rejected.
+.RE
+.TP
+.BI codepage= value
+Sets the codepage for converting to shortname characters on FAT
+and VFAT filesystems. By default, codepage 437 is used.
+.TP
+.BI conv= mode
+This option is obsolete and may fail or be ignored.
+.TP
+.BI cvf_format= module
+Forces the driver to use the CVF (Compressed Volume File) module
+.RI cvf_ module
+instead of auto-detection. If the kernel supports kmod, the
+cvf_format=xxx option also controls on-demand CVF module loading.
+This option is obsolete.
+.TP
+.BI cvf_option= option
+Option passed to the CVF module. This option is obsolete.
+.TP
+.B debug
+Turn on the
+.I debug
+flag. A version string and a list of filesystem parameters will be
+printed (these data are also printed if the parameters appear to be
+inconsistent).
+.TP
+.B discard
+If set, causes discard/TRIM commands to be issued to the block device
+when blocks are freed. This is useful for SSD devices and
+sparse/thinly-provisioned LUNs.
+.TP
+.B dos1xfloppy
+If set, use a fallback default BIOS Parameter Block configuration, determined
+by backing device size. These static parameters match defaults assumed by DOS
+1.x for 160 kiB, 180 kiB, 320 kiB, and 360 kiB floppies and floppy images.
+.TP
+.BR errors= { panic | continue | remount-ro }
+Specify FAT behavior on critical errors: panic, continue without doing
+anything, or remount the partition in read-only mode (default behavior).
+.TP
+.BR fat= { 12 | 16 | 32 }
+Specify a 12, 16 or 32 bit fat. This overrides
+the automatic FAT type detection routine. Use with caution!
+.TP
+.BI iocharset= value
+Character set to use for converting between 8 bit characters
+and 16 bit Unicode characters. The default is iso8859-1.
+Long filenames are stored on disk in Unicode format.
+.TP
+.BR nfs= { stale_rw | nostale_ro }
+Enable this only if you want to export the FAT filesystem over NFS.
+
+.BR stale_rw :
+This option maintains an index (cache) of directory inodes which is used by the
+nfs-related code to improve look-ups. Full file operations (read/write) over
+NFS are supported but with cache eviction at NFS server, this could result in
+spurious
+.B ESTALE
+errors.
+
+.BR nostale_ro :
+This option bases the inode number and file handle
+on the on-disk location of a file in the FAT directory entry.
+This ensures that
+.B ESTALE
+will not be returned after a file is
+evicted from the inode cache. However, it means that operations
+such as rename, create and unlink could cause file handles that
+previously pointed at one file to point at a different file,
+potentially causing data corruption. For this reason, this
+option also mounts the filesystem readonly.
+
+To maintain backward compatibility, '\-o nfs' is also accepted,
+defaulting to
+.BR stale_rw .
+.TP
+.B tz=UTC
+This option disables the conversion of timestamps
+between local time (as used by Windows on FAT) and UTC
+(which Linux uses internally). This is particularly
+useful when mounting devices (like digital cameras)
+that are set to UTC in order to avoid the pitfalls of
+local time.
+.TP
+.BI time_offset= minutes
+Set offset for conversion of timestamps from local time used by FAT to UTC.
+I.e.,
+.I minutes
+will be subtracted from each timestamp to convert it to UTC used
+internally by Linux. This is useful when the time zone set in the kernel via
+.BR settimeofday (2)
+is not the time zone used by the filesystem. Note
+that this option still does not provide correct time stamps in all cases in
+presence of DST - time stamps in a different DST setting will be off by one
+hour.
+.TP
+.B quiet
+Turn on the
+.I quiet
+flag. Attempts to chown or chmod files do not return errors,
+although they fail. Use with caution!
+.TP
+.B rodir
+FAT has the ATTR_RO (read-only) attribute. On Windows, the ATTR_RO of the
+directory will just be ignored, and is used only by applications as a flag
+(e.g.\& it's set for the customized folder).
+
+If you want to use ATTR_RO as read-only flag even for the directory, set this
+option.
+.TP
+.B showexec
+If set, the execute permission bits of the file will be allowed only if
+the extension part of the name is \&.EXE, \&.COM, or \&.BAT. Not set by default.
+.TP
+.B sys_immutable
+If set, ATTR_SYS attribute on FAT is handled as IMMUTABLE flag on Linux.
+Not set by default.
+.TP
+.B flush
+If set, the filesystem will try to flush to disk more early than normal.
+Not set by default.
+.TP
+.B usefree
+Use the "free clusters" value stored on FSINFO. It'll
+be used to determine number of free clusters without
+scanning disk. But it's not used by default, because
+recent Windows don't update it correctly in some
+case. If you are sure the "free clusters" on FSINFO is
+correct, by this option you can avoid scanning disk.
+.TP
+.BR dots ", " nodots ", " dotsOK= [ yes | no ]
+Various misguided attempts to force Unix or DOS conventions
+onto a FAT filesystem.
+
+.SS "Mount options for hfs"
+.TP
+.BI creator= cccc ", type=" cccc
+Set the creator/type values as shown by the MacOS finder
+used for creating new files. Default values: '????'.
+.TP
+.BI uid= n ", gid=" n
+Set the owner and group of all files.
+(Default: the UID and GID of the current process.)
+.TP
+.BI dir_umask= n ", file_umask=" n ", umask=" n
+Set the umask used for all directories, all regular files, or all
+files and directories. Defaults to the umask of the current process.
+.TP
+.BI session= n
+Select the CDROM session to mount.
+Defaults to leaving that decision to the CDROM driver.
+This option will fail with anything but a CDROM as underlying device.
+.TP
+.BI part= n
+Select partition number n from the device.
+Only makes sense for CDROMs.
+Defaults to not parsing the partition table at all.
+.TP
+.B quiet
+Don't complain about invalid mount options.
+
+.SS "Mount options for hpfs"
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+Set the owner and group of all files. (Default: the UID and GID
+of the current process.)
+.TP
+.BI umask= value
+Set the umask (the bitmask of the permissions that are
+.B not
+present). The default is the umask of the current process.
+The value is given in octal.
+.TP
+.BR case= { lower | asis }
+Convert all files names to lower case, or leave them.
+(Default:
+.BR case=lower .)
+.TP
+.BI conv= mode
+This option is obsolete and may fail or being ignored.
+.TP
+.B nocheck
+Do not abort mounting when certain consistency checks fail.
+
+.SS "Mount options for iso9660"
+ISO 9660 is a standard describing a filesystem structure to be used
+on CD-ROMs. (This filesystem type is also seen on some DVDs. See also the
+.I udf
+filesystem.)
+
+Normal
+.I iso9660
+filenames appear in an 8.3 format (i.e., DOS-like restrictions on filename
+length), and in addition all characters are in upper case. Also there is
+no field for file ownership, protection, number of links, provision for
+block/character devices, etc.
+
+Rock Ridge is an extension to iso9660 that provides all of these UNIX-like
+features. Basically there are extensions to each directory record that
+supply all of the additional information, and when Rock Ridge is in use,
+the filesystem is indistinguishable from a normal UNIX filesystem (except
+that it is read-only, of course).
+.TP
+.B norock
+Disable the use of Rock Ridge extensions, even if available. Cf.\&
+.BR map .
+.TP
+.B nojoliet
+Disable the use of Microsoft Joliet extensions, even if available. Cf.\&
+.BR map .
+.TP
+.BR check= { r [ elaxed ]| s [ trict ]}
+With
+.BR check=relaxed ,
+a filename is first converted to lower case before doing the lookup.
+This is probably only meaningful together with
+.B norock
+and
+.BR map=normal .
+(Default:
+.BR check=strict .)
+.TP
+\fBuid=\fP\,\fIvalue\fP and \fBgid=\fP\,\fIvalue\fP
+Give all files in the filesystem the indicated user or group id,
+possibly overriding the information found in the Rock Ridge extensions.
+(Default:
+.BR uid=0,gid=0 .)
+.TP
+.BR map= { n [ ormal ]| o [ ff ]| a [ corn ]}
+For non-Rock Ridge volumes, normal name translation maps upper
+to lower case ASCII, drops a trailing `;1', and converts `;' to `.'.
+With
+.B map=off
+no name translation is done. See
+.BR norock .
+(Default:
+.BR map=normal .)
+.B map=acorn
+is like
+.B map=normal
+but also apply Acorn extensions if present.
+.TP
+.BI mode= value
+For non-Rock Ridge volumes, give all files the indicated mode.
+(Default: read and execute permission for everybody.)
+Octal mode values require a leading 0.
+.TP
+.B unhide
+Also show hidden and associated files.
+(If the ordinary files and the associated or hidden files have
+the same filenames, this may make the ordinary files inaccessible.)
+.TP
+.BR block= { 512 | 1024 | 2048 }
+Set the block size to the indicated value.
+(Default:
+.BR block=1024 .)
+.TP
+.BI conv= mode
+This option is obsolete and may fail or being ignored.
+.TP
+.B cruft
+If the high byte of the file length contains other garbage,
+set this mount option to ignore the high order bits of the file length.
+This implies that a file cannot be larger than 16\ MB.
+.TP
+.BI session= x
+Select number of session on multisession CD.
+.TP
+.BI sbsector= xxx
+Session begins from sector xxx.
+.LP
+The following options are the same as for vfat and specifying them only makes
+sense when using discs encoded using Microsoft's Joliet extensions.
+.TP
+.BI iocharset= value
+Character set to use for converting 16 bit Unicode characters on CD
+to 8 bit characters. The default is iso8859-1.
+.TP
+.B utf8
+Convert 16 bit Unicode characters on CD to UTF-8.
+
+.SS "Mount options for jfs"
+.TP
+.BI iocharset= name
+Character set to use for converting from Unicode to ASCII. The default is
+to do no conversion. Use
+.B iocharset=utf8
+for UTF8 translations. This requires CONFIG_NLS_UTF8 to be set in
+the kernel
+.I ".config"
+file.
+.TP
+.BI resize= value
+Resize the volume to
+.I value
+blocks. JFS only supports growing a volume, not shrinking it. This option
+is only valid during a remount, when the volume is mounted read-write. The
+.B resize
+keyword with no value will grow the volume to the full size of the partition.
+.TP
+.B nointegrity
+Do not write to the journal. The primary use of this option is to allow
+for higher performance when restoring a volume from backup media. The
+integrity of the volume is not guaranteed if the system abnormally ends.
+.TP
+.B integrity
+Default. Commit metadata changes to the journal. Use this option to remount
+a volume where the
+.B nointegrity
+option was previously specified in order to restore normal behavior.
+.TP
+.BR errors= { continue | remount-ro | panic }
+Define the behavior when an error is encountered.
+(Either ignore errors and just mark the filesystem erroneous and continue,
+or remount the filesystem read-only, or panic and halt the system.)
+.TP
+.BR noquota | quota | usrquota | grpquota
+These options are accepted but ignored.
+
+.SS "Mount options for msdos"
+See mount options for fat.
+If the
+.I msdos
+filesystem detects an inconsistency, it reports an error and sets the file
+system read-only. The filesystem can be made writable again by remounting
+it.
+
+.SS "Mount options for ncpfs"
+Just like
+.IR nfs ", the " ncpfs
+implementation expects a binary argument (a
+.IR "struct ncp_mount_data" )
+to the mount system call. This argument is constructed by
+.BR ncpmount (8)
+and the current version of
+.B mount
+(2.12) does not know anything about ncpfs.
+
+.SS "Mount options for ntfs"
+.TP
+.BI iocharset= name
+Character set to use when returning file names.
+Unlike VFAT, NTFS suppresses names that contain
+nonconvertible characters. Deprecated.
+.TP
+.BI nls= name
+New name for the option earlier called
+.IR iocharset .
+.TP
+.B utf8
+Use UTF-8 for converting file names.
+.TP
+.BR uni_xlate= { 0 | 1 | 2 }
+For 0 (or `no' or `false'), do not use escape sequences
+for unknown Unicode characters.
+For 1 (or `yes' or `true') or 2, use vfat-style 4-byte escape sequences
+starting with ":". Here 2 give a little-endian encoding
+and 1 a byteswapped bigendian encoding.
+.TP
+.B posix=[0|1]
+If enabled (posix=1), the filesystem distinguishes between
+upper and lower case. The 8.3 alias names are presented as
+hard links instead of being suppressed. This option is obsolete.
+.TP
+\fBuid=\fP\,\fIvalue\fP, \fBgid=\fP\,\fIvalue\fP and \fBumask=\fP\,\fIvalue\fP
+Set the file permission on the filesystem.
+The umask value is given in octal.
+By default, the files are owned by root and not readable by somebody else.
+
+.SS "Mount options for overlay"
+Since Linux 3.18 the overlay pseudo filesystem implements a union mount for
+other filesystems.
+
+An overlay filesystem combines two filesystems - an \fBupper\fR filesystem and
+a \fBlower\fR filesystem. When a name exists in both filesystems, the object
+in the upper filesystem is visible while the object in the lower filesystem is
+either hidden or, in the case of directories, merged with the upper object.
+
+The lower filesystem can be any filesystem supported by Linux and does not need
+to be writable. The lower filesystem can even be another overlayfs. The upper
+filesystem will normally be writable and if it is it must support the creation
+of trusted.* extended attributes, and must provide a valid d_type in readdir
+responses, so NFS is not suitable.
+
+A read-only overlay of two read-only filesystems may use any filesystem type.
+The options \fBlowerdir\fR and \fBupperdir\fR are combined into a merged
+directory by using:
+
+.RS
+.br
+.nf
+.B "mount \-t overlay overlay \e"
+.B " \-olowerdir=/lower,upperdir=/upper,workdir=/work /merged"
+.fi
+.br
+.RE
+
+.TP
+.BI lowerdir= directory
+Any filesystem, does not need to be on a writable filesystem.
+.TP
+.BI upperdir= directory
+The upperdir is normally on a writable filesystem.
+.TP
+.BI workdir= directory
+The workdir needs to be an empty directory on the same filesystem as upperdir.
+
+.SS "Mount options for reiserfs"
+Reiserfs is a journaling filesystem.
+.TP
+.B conv
+Instructs version 3.6 reiserfs software to mount a version 3.5 filesystem,
+using the 3.6 format for newly created objects. This filesystem will no
+longer be compatible with reiserfs 3.5 tools.
+.TP
+.BR hash= { rupasov | tea | r5 | detect }
+Choose which hash function reiserfs will use to find files within directories.
+.RS
+.TP
+.B rupasov
+A hash invented by Yury Yu.\& Rupasov. It is fast and preserves locality,
+mapping lexicographically close file names to close hash values.
+This option should not be used, as it causes a high probability of hash
+collisions.
+.TP
+.B tea
+A Davis-Meyer function implemented by Jeremy Fitzhardinge.
+It uses hash permuting bits in the name. It gets high randomness
+and, therefore, low probability of hash collisions at some CPU cost.
+This may be used if EHASHCOLLISION errors are experienced with the r5 hash.
+.TP
+.B r5
+A modified version of the rupasov hash. It is used by default and is
+the best choice unless the filesystem has huge directories and
+unusual file-name patterns.
+.TP
+.B detect
+Instructs
+.I mount
+to detect which hash function is in use by examining
+the filesystem being mounted, and to write this information into
+the reiserfs superblock. This is only useful on the first mount of
+an old format filesystem.
+.RE
+.TP
+.B hashed_relocation
+Tunes the block allocator. This may provide performance improvements
+in some situations.
+.TP
+.B no_unhashed_relocation
+Tunes the block allocator. This may provide performance improvements
+in some situations.
+.TP
+.B noborder
+Disable the border allocator algorithm invented by Yury Yu.\& Rupasov.
+This may provide performance improvements in some situations.
+.TP
+.B nolog
+Disable journaling. This will provide slight performance improvements in
+some situations at the cost of losing reiserfs's fast recovery from crashes.
+Even with this option turned on, reiserfs still performs all journaling
+operations, save for actual writes into its journaling area. Implementation
+of
+.I nolog
+is a work in progress.
+.TP
+.B notail
+By default, reiserfs stores small files and `file tails' directly into its
+tree. This confuses some utilities such as
+.BR LILO (8).
+This option is used to disable packing of files into the tree.
+.TP
+.B replayonly
+Replay the transactions which are in the journal, but do not actually
+mount the filesystem. Mainly used by
+.IR reiserfsck .
+.TP
+.BI resize= number
+A remount option which permits online expansion of reiserfs partitions.
+Instructs reiserfs to assume that the device has
+.I number
+blocks.
+This option is designed for use with devices which are under logical
+volume management (LVM).
+There is a special
+.I resizer
+utility which can be obtained from
+.IR ftp://ftp.namesys.com/pub/reiserfsprogs .
+.TP
+.B user_xattr
+Enable Extended User Attributes. See the
+.BR attr (1)
+manual page.
+.TP
+.B acl
+Enable POSIX Access Control Lists. See the
+.BR acl (5)
+manual page.
+.TP
+.BR barrier=none " / " barrier=flush
+This disables / enables the use of write barriers in the journaling code.
+barrier=none disables, barrier=flush enables (default). This also requires an
+IO stack which can support barriers, and if reiserfs gets an error on a barrier
+write, it will disable barriers again with a warning. Write barriers enforce
+proper on-disk ordering of journal commits, making volatile disk write caches
+safe to use, at some performance penalty. If your disks are battery-backed in
+one way or another, disabling barriers may safely improve performance.
+
+.SS "Mount options for ubifs"
+UBIFS is a flash filesystem which works on top of UBI volumes. Note that
+\fBatime\fR is not supported and is always turned off.
+.TP
+The device name may be specified as
+.PP
+.RS
+.B ubiX_Y
+UBI device number
+.BR X ,
+volume number
+.B Y
+.TP
+.B ubiY
+UBI device number
+.BR 0 ,
+volume number
+.B Y
+.TP
+.B ubiX:NAME
+UBI device number
+.BR X ,
+volume with name
+.B NAME
+.TP
+.B ubi:NAME
+UBI device number
+.BR 0 ,
+volume with name
+.B NAME
+.RE
+.PP
+Alternative
+.B !
+separator may be used instead of
+.BR : .
+.TP
+The following mount options are available:
+.TP
+.B bulk_read
+Enable bulk-read. VFS read-ahead is disabled because it slows down the filesystem. Bulk-Read is an internal optimization. Some flashes may read faster if
+the data are read at one go, rather than at several read requests. For
+example, OneNAND can do "read-while-load" if it reads more than one NAND page.
+.TP
+.B no_bulk_read
+Do not bulk-read. This is the default.
+.TP
+.B chk_data_crc
+Check data CRC-32 checksums. This is the default.
+.TP
+.BR no_chk_data_crc .
+Do not check data CRC-32 checksums. With this option, the filesystem does not
+check CRC-32 checksum for data, but it does check it for the internal indexing
+information. This option only affects reading, not writing. CRC-32 is always
+calculated when writing the data.
+.TP
+.BR compr= { none | lzo | zlib }
+Select the default compressor which is used when new files are written. It is
+still possible to read compressed files if mounted with the
+.B none
+option.
+
+.SS "Mount options for udf"
+UDF is the "Universal Disk Format" filesystem defined by OSTA, the Optical
+Storage Technology Association, and is often used for DVD-ROM, frequently
+in the form of a hybrid UDF/ISO-9660 filesystem. It is, however,
+perfectly usable by itself on disk drives, flash drives and other block devices.
+See also
+.IR iso9660 .
+.TP
+.B uid=
+Make all files in the filesystem belong to the given user.
+uid=forget can be specified independently of (or usually in
+addition to) uid=<user> and results in UDF
+not storing uids to the media. In fact the recorded uid
+is the 32-bit overflow uid \-1 as defined by the UDF standard.
+The value is given as either <user> which is a valid user name or the corresponding
+decimal user id, or the special string "forget".
+.TP
+.B gid=
+Make all files in the filesystem belong to the given group.
+gid=forget can be specified independently of (or usually in
+addition to) gid=<group> and results in UDF
+not storing gids to the media. In fact the recorded gid
+is the 32-bit overflow gid \-1 as defined by the UDF standard.
+The value is given as either <group> which is a valid group name or the corresponding
+decimal group id, or the special string "forget".
+.TP
+.B umask=
+Mask out the given permissions from all inodes read from the filesystem.
+The value is given in octal.
+.TP
+.B mode=
+If mode= is set the permissions of all non-directory inodes read from the
+filesystem will be set to the given mode. The value is given in octal.
+.TP
+.B dmode=
+If dmode= is set the permissions of all directory inodes read from the
+filesystem will be set to the given dmode. The value is given in octal.
+.TP
+.B bs=
+Set the block size. Default value prior to kernel version 2.6.30 was
+2048. Since 2.6.30 and prior to 4.11 it was logical device block size with
+fallback to 2048. Since 4.11 it is logical block size with fallback to
+any valid block size between logical device block size and 4096.
+
+For other details see the \fBmkudffs\fP(8) 2.0+ manpage, sections
+\fBCOMPATIBILITY\fP and \fBBLOCK SIZE\fP.
+.TP
+.B unhide
+Show otherwise hidden files.
+.TP
+.B undelete
+Show deleted files in lists.
+.TP
+.B adinicb
+Embed data in the inode. (default)
+.TP
+.B noadinicb
+Don't embed data in the inode.
+.TP
+.B shortad
+Use short UDF address descriptors.
+.TP
+.B longad
+Use long UDF address descriptors. (default)
+.TP
+.B nostrict
+Unset strict conformance.
+.TP
+.B iocharset=
+Set the NLS character set. This requires kernel compiled with CONFIG_UDF_NLS option.
+.TP
+.B utf8
+Set the UTF-8 character set.
+.SS Mount options for debugging and disaster recovery
+.TP
+.B novrs
+Ignore the Volume Recognition Sequence and attempt to mount anyway.
+.TP
+.B session=
+Select the session number for multi-session recorded optical media. (default= last session)
+.TP
+.B anchor=
+Override standard anchor location. (default= 256)
+.TP
+.B lastblock=
+Set the last block of the filesystem.
+.SS Unused historical mount options that may be encountered and should be removed
+.TP
+.B uid=ignore
+Ignored, use uid=<user> instead.
+.TP
+.B gid=ignore
+Ignored, use gid=<group> instead.
+.TP
+.B volume=
+Unimplemented and ignored.
+.TP
+.B partition=
+Unimplemented and ignored.
+.TP
+.B fileset=
+Unimplemented and ignored.
+.TP
+.B rootdir=
+Unimplemented and ignored.
+
+.SS "Mount options for ufs"
+.TP
+.BI ufstype= value
+UFS is a filesystem widely used in different operating systems.
+The problem are differences among implementations. Features of some
+implementations are undocumented, so its hard to recognize the
+type of ufs automatically.
+That's why the user must specify the type of ufs by mount option.
+Possible values are:
+.RS
+.TP
+.B old
+Old format of ufs, this is the default, read only.
+(Don't forget to give the \-r option.)
+.TP
+.B 44bsd
+For filesystems created by a BSD-like system (NetBSD, FreeBSD, OpenBSD).
+.TP
+.B ufs2
+Used in FreeBSD 5.x supported as read-write.
+.TP
+.B 5xbsd
+Synonym for ufs2.
+.TP
+.B sun
+For filesystems created by SunOS or Solaris on Sparc.
+.TP
+.B sunx86
+For filesystems created by Solaris on x86.
+.TP
+.B hp
+For filesystems created by HP-UX, read-only.
+.TP
+.B nextstep
+For filesystems created by NeXTStep (on NeXT station) (currently read only).
+.TP
+.B nextstep-cd
+For NextStep CDROMs (block_size == 2048), read-only.
+.TP
+.B openstep
+For filesystems created by OpenStep (currently read only).
+The same filesystem type is also used by Mac OS X.
+.RE
+
+.TP
+.BI onerror= value
+Set behavior on error:
+.RS
+.TP
+.B panic
+If an error is encountered, cause a kernel panic.
+.TP
+.RB [ lock | umount | repair ]
+These mount options don't do anything at present;
+when an error is encountered only a console message is printed.
+.RE
+
+.SS "Mount options for umsdos"
+See mount options for msdos.
+The
+.B dotsOK
+option is explicitly killed by
+.IR umsdos .
+
+.SS "Mount options for vfat"
+First of all, the mount options for
+.I fat
+are recognized.
+The
+.B dotsOK
+option is explicitly killed by
+.IR vfat .
+Furthermore, there are
+.TP
+.B uni_xlate
+Translate unhandled Unicode characters to special escaped sequences.
+This lets you backup and restore filenames that are created with any
+Unicode characters. Without this option, a '?' is used when no
+translation is possible. The escape character is ':' because it is
+otherwise invalid on the vfat filesystem. The escape sequence
+that gets used, where u is the Unicode character,
+is: ':', (u & 0x3f), ((u>>6) & 0x3f), (u>>12).
+.TP
+.B posix
+Allow two files with names that only differ in case.
+This option is obsolete.
+.TP
+.B nonumtail
+First try to make a short name without sequence number,
+before trying
+.IR name\s+3~\s0num.ext .
+.TP
+.B utf8
+UTF8 is the filesystem safe 8-bit encoding of Unicode that is used by the
+console. It can be enabled for the filesystem with this option or disabled
+with utf8=0, utf8=no or utf8=false. If `uni_xlate' gets set, UTF8 gets
+disabled.
+.TP
+.BI shortname= mode
+Defines the behavior for creation and display of filenames which fit into
+8.3 characters. If a long name for a file exists, it will always be the
+preferred one for display. There are four \fImode\fRs:
+.RS
+.TP
+.B lower
+Force the short name to lower case upon display; store a long name when
+the short name is not all upper case.
+.TP
+.B win95
+Force the short name to upper case upon display; store a long name when
+the short name is not all upper case.
+.TP
+.B winnt
+Display the short name as is; store a long name when the short name is
+not all lower case or all upper case.
+.TP
+.B mixed
+Display the short name as is; store a long name when the short name is not
+all upper case. This mode is the default since Linux 2.6.32.
+.RE
+
+.SS "Mount options for usbfs"
+.TP
+\fBdevuid=\fP\,\fIuid\fP and \fBdevgid=\fP\,\fIgid\fP and \fBdevmode=\fP\,\fImode\fP
+Set the owner and group and mode of the device files in the usbfs filesystem
+(default: uid=gid=0, mode=0644). The mode is given in octal.
+.TP
+\fBbusuid=\fP\,\fIuid\fP and \fBbusgid=\fP\,\fIgid\fP and \fBbusmode=\fP\,\fImode\fP
+Set the owner and group and mode of the bus directories in the usbfs
+filesystem (default: uid=gid=0, mode=0555). The mode is given in octal.
+.TP
+\fBlistuid=\fP\,\fIuid\fP and \fBlistgid=\fP\,\fIgid\fP and \fBlistmode=\fP\,\fImode\fP
+Set the owner and group and mode of the file
+.I devices
+(default: uid=gid=0, mode=0444). The mode is given in octal.
+
+.SH DM-VERITY SUPPORT (experimental)
+The device-mapper verity target provides read-only transparent integrity
+checking of block devices using kernel crypto API. The
+.B mount
+command can open
+the dm-verity device and do the integrity verification before on the device
+filesystem is mounted. Requires libcryptsetup with in libmount (optionally via dlopen).
+If libcryptsetup supports extracting the root hash of an already mounted device,
+existing devices will be automatically reused in case of a match.
+Mount options for dm-verity:
+.TP
+\fBverity.hashdevice=\fP\,\fIpath\fP
+Path to the hash tree device associated with the source volume to pass to dm-verity.
+.TP
+\fBverity.roothash=\fP\,\fIhex\fP
+Hex-encoded hash of the root of
+.I verity.hashdevice
+Mutually exclusive with
+.I verity.roothashfile.
+.TP
+\fBverity.roothashfile=\fP\,\fIpath\fP
+Path to file containing the hex-encoded hash of the root of
+.I verity.hashdevice.
+Mutually exclusive with
+.I verity.roothash.
+.TP
+\fBverity.hashoffset=\fP\,\fIoffset\fP
+If the hash tree device is embedded in the source volume,
+.I offset
+(default: 0) is used by dm-verity to get to the tree.
+.TP
+\fBverity.fecdevice=\fP\,\fIpath\fP
+Path to the Forward Error Correction (FEC) device associated with the source volume to pass to dm-verity.
+Optional. Requires kernel built with CONFIG_DM_VERITY_FEC.
+.TP
+\fBverity.fecoffset=\fP\,\fIoffset\fP
+If the FEC device is embedded in the source volume,
+.I offset
+(default: 0) is used by dm-verity to get to the FEC area. Optional.
+.TP
+\fBverity.fecroots=\fP\,\fIvalue\fP
+Parity bytes for FEC (default: 2). Optional.
+.TP
+\fBverity.roothashsig=\fP\,\fIpath\fP
+Path to pkcs7 signature of root hash hex string. Requires crypt_activate_by_signed_key() from cryptsetup and
+kernel built with CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG. For device reuse, signatures have to be either used by all
+mounts of a device or by none. Optional.
+.PP
+Supported since util-linux v2.35.
+.PP
+For example commands:
+.sp
+.RS
+.nf
+.B mksquashfs /etc /tmp/etc.squashfs
+.B dd if=/dev/zero of=/tmp/etc.hash bs=1M count=10
+.B veritysetup format /tmp/etc.squashfs /tmp/etc.hash
+.B openssl smime \-sign \-in <hash> \-nocerts \-inkey private.key \e
+.B \-signer private.crt \-noattr \-binary \-outform der \-out /tmp/etc.p7
+.B mount \-o verity.hashdevice=/tmp/etc.hash,verity.roothash=<hash>,\e
+.B verity.roothashsig=/tmp/etc.p7 /tmp/etc.squashfs /mnt
+.fi
+.RE
+.sp
+create squashfs image from /etc directory, verity hash device
+and mount verified filesystem image to /mnt.
+The kernel will verify that the root hash is signed by a key from the kernel keyring if roothashsig is used.
+
+.SH LOOP-DEVICE SUPPORT
+One further possible type is a mount via the loop device. For example,
+the command
+.RS
+.sp
+.B "mount /tmp/disk.img /mnt \-t vfat \-o loop=/dev/loop3"
+.sp
+.RE
+will set up the loop device
+.I /dev/loop3
+to correspond to the file
+.IR /tmp/disk.img ,
+and then mount this device on
+.IR /mnt .
+
+If no explicit loop device is mentioned
+(but just an option `\fB\-o loop\fP' is given), then
+.B mount
+will try to find some unused loop device and use that, for example
+.RS
+.sp
+.B "mount /tmp/disk.img /mnt \-o loop"
+.sp
+.RE
+The
+.B mount
+command
+.B automatically
+creates a loop device from a regular file if a filesystem type is
+not specified or the filesystem is known for libblkid, for example:
+.RS
+.sp
+.B "mount /tmp/disk.img /mnt"
+.sp
+.B "mount \-t ext4 /tmp/disk.img /mnt"
+.sp
+.RE
+This type of mount knows about three options, namely
+.BR loop ", " offset " and " sizelimit ,
+that are really options to
+.BR \%losetup (8).
+(These options can be used in addition to those specific
+to the filesystem type.)
+
+Since Linux 2.6.25 auto-destruction of loop devices is supported,
+meaning that any loop device allocated by
+.B mount
+will be freed by
+.B umount
+independently of
+.IR /etc/mtab .
+
+You can also free a loop device by hand, using
+.BR "losetup \-d " or " umount \-d" .
+
+Since util-linux v2.29,
+.B mount
+re-uses the loop device rather than
+initializing a new device if the same backing file is already used for some loop
+device with the same offset and sizelimit. This is necessary to avoid
+a filesystem corruption.
+
+.SH EXIT STATUS
+.B mount
+has the following exit status values (the bits can be ORed):
+.TP
+.B 0
+success
+.TP
+.B 1
+incorrect invocation or permissions
+.TP
+.B 2
+system error (out of memory, cannot fork, no more loop devices)
+.TP
+.B 4
+internal
+.B mount
+bug
+.TP
+.B 8
+user interrupt
+.TP
+.B 16
+problems writing or locking /etc/mtab
+.TP
+.B 32
+mount failure
+.TP
+.B 64
+some mount succeeded
+
+The command \fBmount \-a\fR returns 0 (all succeeded), 32 (all failed), or 64 (some
+failed, some succeeded).
+
+.SH EXTERNAL HELPERS
+The syntax of external mount helpers is:
+.sp
+.in +4
+.BI /sbin/mount. suffix
+.I spec dir
+.RB [ \-sfnv ]
+.RB [ \-N
+.IR namespace ]
+.RB [ \-o
+.IR options ]
+.RB [ \-t
+.IR type \fB. subtype ]
+.in
+.sp
+where the \fIsuffix\fR is the filesystem type and the \fB\-sfnvoN\fR options have
+the same meaning as the normal mount options. The \fB\-t\fR option is used for
+filesystems with subtypes support (for example
+.BR "/sbin/mount.fuse \-t fuse.sshfs" ).
+
+The command \fBmount\fR does not pass the mount options
+.BR unbindable ,
+.BR runbindable ,
+.BR private ,
+.BR rprivate ,
+.BR slave ,
+.BR rslave ,
+.BR shared ,
+.BR rshared ,
+.BR auto ,
+.BR noauto ,
+.BR comment ,
+.BR x-* ,
+.BR loop ,
+.B offset
+and
+.B sizelimit
+to the mount.<suffix> helpers. All other options are used in a
+comma-separated list as an argument to the \fB\-o\fR option.
+
+.SH ENVIRONMENT
+.IP LIBMOUNT_FSTAB=<path>
+overrides the default location of the
+.I fstab
+file (ignored for suid)
+.IP LIBMOUNT_MTAB=<path>
+overrides the default location of the
+.I mtab
+file (ignored for suid)
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output
+.IP LIBBLKID_DEBUG=all
+enables libblkid debug output
+.IP LOOPDEV_DEBUG=all
+enables loop device setup debug output
+.SH FILES
+See also "\fBThe files /etc/fstab, /etc/mtab and /proc/mounts\fR" section above.
+.TP 18n
+.I /etc/fstab
+filesystem table
+.TP
+.I /run/mount
+libmount private runtime directory
+.TP
+.I /etc/mtab
+table of mounted filesystems or symlink to
+.I /proc/mounts
+.TP
+.I /etc/mtab\s+3~\s0
+lock file (unused on systems with
+.I mtab
+symlink)
+.TP
+.I /etc/mtab.tmp
+temporary file (unused on systems with
+.I mtab
+symlink)
+.TP
+.I /etc/filesystems
+a list of filesystem types to try
+.SH HISTORY
+A
+.B mount
+command existed in Version 5 AT&T UNIX.
+.SH BUGS
+It is possible for a corrupted filesystem to cause a crash.
+.PP
+Some Linux filesystems don't support
+.BR "\-o sync " and " \-o dirsync"
+(the ext2, ext3, ext4, fat and vfat filesystems
+.I do
+support synchronous updates (a la BSD) when mounted with the
+.B sync
+option).
+.PP
+The
+.B "\-o remount"
+may not be able to change mount parameters (all
+.IR ext2fs -specific
+parameters, except
+.BR sb ,
+are changeable with a remount, for example, but you can't change
+.B gid
+or
+.B umask
+for the
+.IR fatfs ).
+.PP
+It is possible that the files
+.I /etc/mtab
+and
+.I /proc/mounts
+don't match on systems with a regular
+.I mtab
+file. The first file is based only on
+the mount command options, but the content of the second file also depends on
+the kernel and others settings (e.g.\& on a remote NFS server -- in certain cases
+the mount command may report unreliable information about an NFS mount point
+and the
+.I /proc/mount
+file usually contains more reliable information.) This is
+another reason to replace the
+.I mtab
+file with a symlink to the
+.I /proc/mounts
+file.
+.PP
+Checking files on NFS filesystems referenced by file descriptors (i.e.\& the
+.B fcntl
+and
+.B ioctl
+families of functions) may lead to inconsistent results due to the lack of
+a consistency check in the kernel even if the
+.B noac
+mount option is used.
+.PP
+The
+.B loop
+option with the
+.B offset
+or
+.B sizelimit
+options used may fail when using older kernels if the
+.B mount
+command can't confirm that the size of the block device has been configured
+as requested. This situation can be worked around by using
+the
+.B losetup
+command manually before calling
+.B mount
+with the configured loop device.
+.SH AUTHORS
+.nf
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.na
+.BR lsblk (1),
+.BR mount (2),
+.BR umount (2),
+.BR filesystems (5),
+.BR fstab (5),
+.BR nfs (5),
+.BR xfs (5),
+.BR mount_namespaces (7)
+.BR xattr (7)
+.BR e2label (8),
+.BR findmnt (8),
+.BR losetup (8),
+.BR mke2fs (8),
+.BR mountd (8),
+.BR nfsd (8),
+.BR swapon (8),
+.BR tune2fs (8),
+.BR umount (8),
+.BR xfs_admin (8)
+.ad
+.SH AVAILABILITY
+The mount command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/mount.c b/sys-utils/mount.c
new file mode 100644
index 0000000..a87833e
--- /dev/null
+++ b/sys-utils/mount.c
@@ -0,0 +1,996 @@
+/*
+ * mount(8) -- mount a filesystem
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <libmount.h>
+#include <ctype.h>
+
+#include "nls.h"
+#include "c.h"
+#include "env.h"
+#include "strutils.h"
+#include "closestream.h"
+#include "canonicalize.h"
+
+#define XALLOC_EXIT_CODE MNT_EX_SYSERR
+#include "xalloc.h"
+
+#define OPTUTILS_EXIT_CODE MNT_EX_USAGE
+#include "optutils.h"
+
+static int mk_exit_code(struct libmnt_context *cxt, int rc);
+
+static void suid_drop(struct libmnt_context *cxt)
+{
+ const uid_t ruid = getuid();
+ const uid_t euid = geteuid();
+
+ if (ruid != 0 && euid == 0) {
+ if (setgid(getgid()) < 0)
+ err(MNT_EX_FAIL, _("setgid() failed"));
+
+ if (setuid(getuid()) < 0)
+ err(MNT_EX_FAIL, _("setuid() failed"));
+ }
+
+ /* be paranoid and check it, setuid(0) has to fail */
+ if (ruid != 0 && setuid(0) == 0)
+ errx(MNT_EX_FAIL, _("drop permissions failed."));
+
+ mnt_context_force_unrestricted(cxt);
+}
+
+static void __attribute__((__noreturn__)) mount_print_version(void)
+{
+ const char *ver = NULL;
+ const char **features = NULL, **p;
+
+ mnt_get_library_version(&ver);
+ mnt_get_library_features(&features);
+
+ printf(_("%s from %s (libmount %s"),
+ program_invocation_short_name,
+ PACKAGE_STRING,
+ ver);
+ p = features;
+ while (p && *p) {
+ fputs(p == features ? ": " : ", ", stdout);
+ fputs(*p++, stdout);
+ }
+ fputs(")\n", stdout);
+ exit(MNT_EX_SUCCESS);
+}
+
+static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)),
+ const char *filename, int line)
+{
+ if (filename)
+ warnx(_("%s: parse error at line %d -- ignored"), filename, line);
+ return 1;
+}
+
+/*
+ * Replace control chars with '?' to be compatible with coreutils. For more
+ * robust solution use findmnt(1) where we use \x?? hex encoding.
+ */
+static void safe_fputs(const char *data)
+{
+ const char *p;
+
+ for (p = data; p && *p; p++) {
+ if (iscntrl((unsigned char) *p))
+ fputc('?', stdout);
+ else
+ fputc(*p, stdout);
+ }
+}
+
+static void print_all(struct libmnt_context *cxt, char *pattern, int show_label)
+{
+ struct libmnt_table *tb;
+ struct libmnt_iter *itr = NULL;
+ struct libmnt_fs *fs;
+ struct libmnt_cache *cache = NULL;
+
+ if (mnt_context_get_mtab(cxt, &tb))
+ err(MNT_EX_SYSERR, _("failed to read mtab"));
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ err(MNT_EX_SYSERR, _("failed to initialize libmount iterator"));
+ if (show_label)
+ cache = mnt_new_cache();
+
+ while (mnt_table_next_fs(tb, itr, &fs) == 0) {
+ const char *type = mnt_fs_get_fstype(fs);
+ const char *src = mnt_fs_get_source(fs);
+ const char *optstr = mnt_fs_get_options(fs);
+ char *xsrc = NULL;
+
+ if (type && pattern && !mnt_match_fstype(type, pattern))
+ continue;
+
+ if (!mnt_fs_is_pseudofs(fs) && !mnt_fs_is_netfs(fs))
+ xsrc = mnt_pretty_path(src, cache);
+ printf ("%s on ", xsrc ? xsrc : src);
+ safe_fputs(mnt_fs_get_target(fs));
+
+ if (type)
+ printf (" type %s", type);
+ if (optstr)
+ printf (" (%s)", optstr);
+ if (show_label && src) {
+ char *lb = mnt_cache_find_tag_value(cache, src, "LABEL");
+ if (lb)
+ printf (" [%s]", lb);
+ }
+ fputc('\n', stdout);
+ free(xsrc);
+ }
+
+ mnt_unref_cache(cache);
+ mnt_free_iter(itr);
+}
+
+/*
+ * mount -a [-F]
+ */
+static int mount_all(struct libmnt_context *cxt)
+{
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+ int mntrc, ignored, rc = MNT_EX_SUCCESS;
+
+ int nsucc = 0, nerrs = 0;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr) {
+ warn(_("failed to initialize libmount iterator"));
+ return MNT_EX_SYSERR;
+ }
+
+ while (mnt_context_next_mount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
+
+ const char *tgt = mnt_fs_get_target(fs);
+
+ if (ignored) {
+ if (mnt_context_is_verbose(cxt))
+ printf(ignored == 1 ? _("%-25s: ignored\n") :
+ _("%-25s: already mounted\n"),
+ tgt);
+ } else if (mnt_context_is_fork(cxt)) {
+ if (mnt_context_is_verbose(cxt))
+ printf("%-25s: mount successfully forked\n", tgt);
+ } else {
+ if (mk_exit_code(cxt, mntrc) == MNT_EX_SUCCESS) {
+ nsucc++;
+
+ /* Note that MNT_EX_SUCCESS return code does
+ * not mean that FS has been really mounted
+ * (e.g. nofail option) */
+ if (mnt_context_get_status(cxt)
+ && mnt_context_is_verbose(cxt))
+ printf("%-25s: successfully mounted\n", tgt);
+ } else
+ nerrs++;
+ }
+ }
+
+ if (mnt_context_is_parent(cxt)) {
+ /* wait for mount --fork children */
+ int nchildren = 0;
+
+ nerrs = 0, nsucc = 0;
+
+ rc = mnt_context_wait_for_children(cxt, &nchildren, &nerrs);
+ if (!rc && nchildren)
+ nsucc = nchildren - nerrs;
+ }
+
+ if (nerrs == 0)
+ rc = MNT_EX_SUCCESS; /* all success */
+ else if (nsucc == 0)
+ rc = MNT_EX_FAIL; /* all failed */
+ else
+ rc = MNT_EX_SOMEOK; /* some success, some failed */
+
+ mnt_free_iter(itr);
+ return rc;
+}
+
+
+/*
+ * mount -a -o remount
+ */
+static int remount_all(struct libmnt_context *cxt)
+{
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+ int mntrc, ignored, rc = MNT_EX_SUCCESS;
+
+ int nsucc = 0, nerrs = 0;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr) {
+ warn(_("failed to initialize libmount iterator"));
+ return MNT_EX_SYSERR;
+ }
+
+ while (mnt_context_next_remount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
+
+ const char *tgt = mnt_fs_get_target(fs);
+
+ if (ignored) {
+ if (mnt_context_is_verbose(cxt))
+ printf(_("%-25s: ignored\n"), tgt);
+ } else {
+ if (mk_exit_code(cxt, mntrc) == MNT_EX_SUCCESS) {
+ nsucc++;
+
+ /* Note that MNT_EX_SUCCESS return code does
+ * not mean that FS has been really mounted
+ * (e.g. nofail option) */
+ if (mnt_context_get_status(cxt)
+ && mnt_context_is_verbose(cxt))
+ printf("%-25s: successfully remounted\n", tgt);
+ } else
+ nerrs++;
+ }
+ }
+
+ if (nerrs == 0)
+ rc = MNT_EX_SUCCESS; /* all success */
+ else if (nsucc == 0)
+ rc = MNT_EX_FAIL; /* all failed */
+ else
+ rc = MNT_EX_SOMEOK; /* some success, some failed */
+
+ mnt_free_iter(itr);
+ return rc;
+}
+
+static void success_message(struct libmnt_context *cxt)
+{
+ unsigned long mflags = 0;
+ const char *tgt, *src, *pr = program_invocation_short_name;
+
+ if (mnt_context_helper_executed(cxt)
+ || mnt_context_get_status(cxt) != 1)
+ return;
+
+ mnt_context_get_mflags(cxt, &mflags);
+ tgt = mnt_context_get_target(cxt);
+ src = mnt_context_get_source(cxt);
+
+ if (mflags & MS_MOVE)
+ printf(_("%s: %s moved to %s.\n"), pr, src, tgt);
+ else if (mflags & MS_BIND)
+ printf(_("%s: %s bound on %s.\n"), pr, src, tgt);
+ else if (mflags & MS_PROPAGATION) {
+ if (src && strcmp(src, "none") != 0 && tgt)
+ printf(_("%s: %s mounted on %s.\n"), pr, src, tgt);
+
+ printf(_("%s: %s propagation flags changed.\n"), pr, tgt);
+ } else
+ printf(_("%s: %s mounted on %s.\n"), pr, src, tgt);
+}
+
+#if defined(HAVE_LIBSELINUX) && defined(HAVE_SECURITY_GET_INITIAL_CONTEXT)
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+
+static void selinux_warning(struct libmnt_context *cxt, const char *tgt)
+{
+
+ if (tgt && mnt_context_is_verbose(cxt) && is_selinux_enabled() > 0) {
+ security_context_t raw = NULL, def = NULL;
+
+ if (getfilecon(tgt, &raw) > 0
+ && security_get_initial_context("file", &def) == 0) {
+
+ if (!selinux_file_context_cmp(raw, def))
+ printf(_(
+ "mount: %s does not contain SELinux labels.\n"
+ " You just mounted an file system that supports labels which does not\n"
+ " contain labels, onto an SELinux box. It is likely that confined\n"
+ " applications will generate AVC messages and not be allowed access to\n"
+ " this file system. For more details see restorecon(8) and mount(8).\n"),
+ tgt);
+ }
+ freecon(raw);
+ freecon(def);
+ }
+}
+#else
+# define selinux_warning(_x, _y)
+#endif
+
+/*
+ * Returns exit status (MNT_EX_*) and/or prints error message.
+ */
+static int mk_exit_code(struct libmnt_context *cxt, int rc)
+{
+ const char *tgt;
+ char buf[BUFSIZ] = { 0 };
+
+ rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf));
+ tgt = mnt_context_get_target(cxt);
+
+ if (*buf) {
+ const char *spec = tgt;
+ if (!spec)
+ spec = mnt_context_get_source(cxt);
+ if (!spec)
+ spec = "???";
+ warnx("%s: %s.", spec, buf);
+ }
+
+ if (rc == MNT_EX_SUCCESS && mnt_context_get_status(cxt) == 1) {
+ selinux_warning(cxt, tgt);
+ }
+ return rc;
+}
+
+static struct libmnt_table *append_fstab(struct libmnt_context *cxt,
+ struct libmnt_table *fstab,
+ const char *path)
+{
+
+ if (!fstab) {
+ fstab = mnt_new_table();
+ if (!fstab)
+ err(MNT_EX_SYSERR, _("failed to initialize libmount table"));
+
+ mnt_table_set_parser_errcb(fstab, table_parser_errcb);
+ mnt_context_set_fstab(cxt, fstab);
+
+ mnt_unref_table(fstab); /* reference is handled by @cxt now */
+ }
+
+ if (mnt_table_parse_fstab(fstab, path))
+ errx(MNT_EX_USAGE,_("%s: failed to parse"), path);
+
+ return fstab;
+}
+
+/*
+ * Check source and target paths -- non-root user should not be able to
+ * resolve paths which are unreadable for him.
+ */
+static int sanitize_paths(struct libmnt_context *cxt)
+{
+ const char *p;
+ struct libmnt_fs *fs = mnt_context_get_fs(cxt);
+
+ if (!fs)
+ return 0;
+
+ p = mnt_fs_get_target(fs);
+ if (p) {
+ char *np = canonicalize_path_restricted(p);
+ if (!np)
+ return -EPERM;
+ mnt_fs_set_target(fs, np);
+ free(np);
+ }
+
+ p = mnt_fs_get_srcpath(fs);
+ if (p) {
+ char *np = canonicalize_path_restricted(p);
+ if (!np)
+ return -EPERM;
+ mnt_fs_set_source(fs, np);
+ free(np);
+ }
+ return 0;
+}
+
+static void append_option(struct libmnt_context *cxt, const char *opt)
+{
+ if (opt && (*opt == '=' || *opt == '\'' || *opt == '\"' || isblank(*opt)))
+ errx(MNT_EX_USAGE, _("unsupported option format: %s"), opt);
+ if (mnt_context_append_options(cxt, opt))
+ err(MNT_EX_SYSERR, _("failed to append option '%s'"), opt);
+}
+
+static int has_remount_flag(struct libmnt_context *cxt)
+{
+ unsigned long mflags = 0;
+
+ if (mnt_context_get_mflags(cxt, &mflags))
+ return 0;
+
+ return mflags & MS_REMOUNT;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %1$s [-lhV]\n"
+ " %1$s -a [options]\n"
+ " %1$s [options] [--source] <source> | [--target] <directory>\n"
+ " %1$s [options] <source> <directory>\n"
+ " %1$s <operation> <mountpoint> [<target>]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Mount a filesystem.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fprintf(out, _(
+ " -a, --all mount all filesystems mentioned in fstab\n"
+ " -c, --no-canonicalize don't canonicalize paths\n"
+ " -f, --fake dry run; skip the mount(2) syscall\n"
+ " -F, --fork fork off for each device (use with -a)\n"
+ " -T, --fstab <path> alternative file to /etc/fstab\n"));
+ fprintf(out, _(
+ " -i, --internal-only don't call the mount.<type> helpers\n"));
+ fprintf(out, _(
+ " -l, --show-labels show also filesystem labels\n"));
+ fprintf(out, _(
+ " -n, --no-mtab don't write to /etc/mtab\n"));
+ fprintf(out, _(
+ " --options-mode <mode>\n"
+ " what to do with options loaded from fstab\n"
+ " --options-source <source>\n"
+ " mount options source\n"
+ " --options-source-force\n"
+ " force use of options from fstab/mtab\n"));
+ fprintf(out, _(
+ " -o, --options <list> comma-separated list of mount options\n"
+ " -O, --test-opts <list> limit the set of filesystems (use with -a)\n"
+ " -r, --read-only mount the filesystem read-only (same as -o ro)\n"
+ " -t, --types <list> limit the set of filesystem types\n"));
+ fprintf(out, _(
+ " --source <src> explicitly specifies source (path, label, uuid)\n"
+ " --target <target> explicitly specifies mountpoint\n"));
+ fprintf(out, _(
+ " --target-prefix <path>\n"
+ " specifies path use for all mountpoints\n"));
+ fprintf(out, _(
+ " -v, --verbose say what is being done\n"));
+ fprintf(out, _(
+ " -w, --rw, --read-write mount the filesystem read-write (default)\n"));
+ fprintf(out, _(
+ " -N, --namespace <ns> perform mount in another namespace\n"));
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ fprintf(out, _(
+ "\nSource:\n"
+ " -L, --label <label> synonym for LABEL=<label>\n"
+ " -U, --uuid <uuid> synonym for UUID=<uuid>\n"
+ " LABEL=<label> specifies device by filesystem label\n"
+ " UUID=<uuid> specifies device by filesystem UUID\n"
+ " PARTLABEL=<label> specifies device by partition label\n"
+ " PARTUUID=<uuid> specifies device by partition UUID\n"
+ " ID=<id> specifies device by udev hardware ID\n"));
+
+ fprintf(out, _(
+ " <device> specifies device by path\n"
+ " <directory> mountpoint for bind mounts (see --bind/rbind)\n"
+ " <file> regular file for loopdev setup\n"));
+
+ fprintf(out, _(
+ "\nOperations:\n"
+ " -B, --bind mount a subtree somewhere else (same as -o bind)\n"
+ " -M, --move move a subtree to some other place\n"
+ " -R, --rbind mount a subtree and all submounts somewhere else\n"));
+ fprintf(out, _(
+ " --make-shared mark a subtree as shared\n"
+ " --make-slave mark a subtree as slave\n"
+ " --make-private mark a subtree as private\n"
+ " --make-unbindable mark a subtree as unbindable\n"));
+ fprintf(out, _(
+ " --make-rshared recursively mark a whole subtree as shared\n"
+ " --make-rslave recursively mark a whole subtree as slave\n"
+ " --make-rprivate recursively mark a whole subtree as private\n"
+ " --make-runbindable recursively mark a whole subtree as unbindable\n"));
+
+ printf(USAGE_MAN_TAIL("mount(8)"));
+
+ exit(MNT_EX_SUCCESS);
+}
+
+struct flag_str {
+ int value;
+ char *str;
+};
+
+static int omode2mask(const char *str)
+{
+ size_t i;
+
+ static const struct flag_str flags[] = {
+ { MNT_OMODE_IGNORE, "ignore" },
+ { MNT_OMODE_APPEND, "append" },
+ { MNT_OMODE_PREPEND, "prepend" },
+ { MNT_OMODE_REPLACE, "replace" },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ if (!strcmp(str, flags[i].str))
+ return flags[i].value;
+ }
+ return -EINVAL;
+}
+
+static long osrc2mask(const char *str, size_t len)
+{
+ size_t i;
+
+ static const struct flag_str flags[] = {
+ { MNT_OMODE_FSTAB, "fstab" },
+ { MNT_OMODE_MTAB, "mtab" },
+ { MNT_OMODE_NOTAB, "disable" },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ if (!strncmp(str, flags[i].str, len) && !flags[i].str[len])
+ return flags[i].value;
+ }
+ return -EINVAL;
+}
+
+static pid_t parse_pid(const char *str)
+{
+ char *end;
+ pid_t ret;
+
+ errno = 0;
+ ret = strtoul(str, &end, 10);
+
+ if (ret < 0 || errno || end == str || (end && *end))
+ return 0;
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int c, rc = MNT_EX_SUCCESS, all = 0, show_labels = 0;
+ struct libmnt_context *cxt;
+ struct libmnt_table *fstab = NULL;
+ char *srcbuf = NULL;
+ char *types = NULL;
+ int oper = 0, is_move = 0;
+ int propa = 0;
+ int optmode = 0, optmode_mode = 0, optmode_src = 0;
+
+ enum {
+ MOUNT_OPT_SHARED = CHAR_MAX + 1,
+ MOUNT_OPT_SLAVE,
+ MOUNT_OPT_PRIVATE,
+ MOUNT_OPT_UNBINDABLE,
+ MOUNT_OPT_RSHARED,
+ MOUNT_OPT_RSLAVE,
+ MOUNT_OPT_RPRIVATE,
+ MOUNT_OPT_RUNBINDABLE,
+ MOUNT_OPT_TARGET,
+ MOUNT_OPT_TARGET_PREFIX,
+ MOUNT_OPT_SOURCE,
+ MOUNT_OPT_OPTMODE,
+ MOUNT_OPT_OPTSRC,
+ MOUNT_OPT_OPTSRC_FORCE
+ };
+
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "fake", no_argument, NULL, 'f' },
+ { "fstab", required_argument, NULL, 'T' },
+ { "fork", no_argument, NULL, 'F' },
+ { "help", no_argument, NULL, 'h' },
+ { "no-mtab", no_argument, NULL, 'n' },
+ { "read-only", no_argument, NULL, 'r' },
+ { "ro", no_argument, NULL, 'r' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { "read-write", no_argument, NULL, 'w' },
+ { "rw", no_argument, NULL, 'w' },
+ { "options", required_argument, NULL, 'o' },
+ { "test-opts", required_argument, NULL, 'O' },
+ { "types", required_argument, NULL, 't' },
+ { "uuid", required_argument, NULL, 'U' },
+ { "label", required_argument, NULL, 'L' },
+ { "bind", no_argument, NULL, 'B' },
+ { "move", no_argument, NULL, 'M' },
+ { "rbind", no_argument, NULL, 'R' },
+ { "make-shared", no_argument, NULL, MOUNT_OPT_SHARED },
+ { "make-slave", no_argument, NULL, MOUNT_OPT_SLAVE },
+ { "make-private", no_argument, NULL, MOUNT_OPT_PRIVATE },
+ { "make-unbindable", no_argument, NULL, MOUNT_OPT_UNBINDABLE },
+ { "make-rshared", no_argument, NULL, MOUNT_OPT_RSHARED },
+ { "make-rslave", no_argument, NULL, MOUNT_OPT_RSLAVE },
+ { "make-rprivate", no_argument, NULL, MOUNT_OPT_RPRIVATE },
+ { "make-runbindable", no_argument, NULL, MOUNT_OPT_RUNBINDABLE },
+ { "no-canonicalize", no_argument, NULL, 'c' },
+ { "internal-only", no_argument, NULL, 'i' },
+ { "show-labels", no_argument, NULL, 'l' },
+ { "target", required_argument, NULL, MOUNT_OPT_TARGET },
+ { "target-prefix", required_argument, NULL, MOUNT_OPT_TARGET_PREFIX },
+ { "source", required_argument, NULL, MOUNT_OPT_SOURCE },
+ { "options-mode", required_argument, NULL, MOUNT_OPT_OPTMODE },
+ { "options-source", required_argument, NULL, MOUNT_OPT_OPTSRC },
+ { "options-source-force", no_argument, NULL, MOUNT_OPT_OPTSRC_FORCE},
+ { "namespace", required_argument, NULL, 'N' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'B','M','R' }, /* bind,move,rbind */
+ { 'L','U', MOUNT_OPT_SOURCE }, /* label,uuid,source */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ sanitize_env();
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ strutils_set_exitcode(MNT_EX_USAGE);
+
+ mnt_init_debug(0);
+ cxt = mnt_new_context();
+ if (!cxt)
+ err(MNT_EX_SYSERR, _("libmount context allocation failed"));
+
+ mnt_context_set_tables_errcb(cxt, table_parser_errcb);
+
+ while ((c = getopt_long(argc, argv, "aBcfFhilL:Mno:O:rRsU:vVwt:T:N:",
+ longopts, NULL)) != -1) {
+
+ /* only few options are allowed for non-root users */
+ if (mnt_context_is_restricted(cxt) &&
+ !strchr("hlLUVvrist", c) &&
+ c != MOUNT_OPT_TARGET &&
+ c != MOUNT_OPT_SOURCE)
+ suid_drop(cxt);
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'a':
+ all = 1;
+ break;
+ case 'c':
+ mnt_context_disable_canonicalize(cxt, TRUE);
+ break;
+ case 'f':
+ mnt_context_enable_fake(cxt, TRUE);
+ break;
+ case 'F':
+ mnt_context_enable_fork(cxt, TRUE);
+ break;
+ case 'i':
+ mnt_context_disable_helpers(cxt, TRUE);
+ break;
+ case 'n':
+ mnt_context_disable_mtab(cxt, TRUE);
+ break;
+ case 'r':
+ append_option(cxt, "ro");
+ mnt_context_enable_rwonly_mount(cxt, FALSE);
+ break;
+ case 'v':
+ mnt_context_enable_verbose(cxt, TRUE);
+ break;
+ case 'w':
+ append_option(cxt, "rw");
+ mnt_context_enable_rwonly_mount(cxt, TRUE);
+ break;
+ case 'o':
+ /* "move" is not supported as option string in libmount
+ * to avoid use in fstab */
+ if (mnt_optstr_get_option(optarg, "move", NULL, 0) == 0) {
+ char *o = xstrdup(optarg);
+
+ mnt_optstr_remove_option(&o, "move");
+ if (o && *o)
+ append_option(cxt, o);
+ oper = is_move = 1;
+ free(o);
+ } else
+ append_option(cxt, optarg);
+ break;
+ case 'O':
+ if (mnt_context_set_options_pattern(cxt, optarg))
+ err(MNT_EX_SYSERR, _("failed to set options pattern"));
+ break;
+ case 'L':
+ xasprintf(&srcbuf, "LABEL=\"%s\"", optarg);
+ mnt_context_disable_swapmatch(cxt, 1);
+ mnt_context_set_source(cxt, srcbuf);
+ free(srcbuf);
+ break;
+ case 'U':
+ xasprintf(&srcbuf, "UUID=\"%s\"", optarg);
+ mnt_context_disable_swapmatch(cxt, 1);
+ mnt_context_set_source(cxt, srcbuf);
+ free(srcbuf);
+ break;
+ case 'l':
+ show_labels = 1;
+ break;
+ case 't':
+ types = optarg;
+ break;
+ case 'T':
+ fstab = append_fstab(cxt, fstab, optarg);
+ break;
+ case 's':
+ mnt_context_enable_sloppy(cxt, TRUE);
+ break;
+ case 'B':
+ oper = 1;
+ append_option(cxt, "bind");
+ break;
+ case 'M':
+ oper = 1;
+ is_move = 1;
+ break;
+ case 'R':
+ oper = 1;
+ append_option(cxt, "rbind");
+ break;
+ case 'N':
+ {
+ char path[PATH_MAX];
+ pid_t pid = parse_pid(optarg);
+
+ if (pid)
+ snprintf(path, sizeof(path), "/proc/%i/ns/mnt", pid);
+
+ if (mnt_context_set_target_ns(cxt, pid ? path : optarg))
+ err(MNT_EX_SYSERR, _("failed to set target namespace to %s"), pid ? path : optarg);
+ break;
+ }
+ case MOUNT_OPT_SHARED:
+ append_option(cxt, "shared");
+ propa = 1;
+ break;
+ case MOUNT_OPT_SLAVE:
+ append_option(cxt, "slave");
+ propa = 1;
+ break;
+ case MOUNT_OPT_PRIVATE:
+ append_option(cxt, "private");
+ propa = 1;
+ break;
+ case MOUNT_OPT_UNBINDABLE:
+ append_option(cxt, "unbindable");
+ propa = 1;
+ break;
+ case MOUNT_OPT_RSHARED:
+ append_option(cxt, "rshared");
+ propa = 1;
+ break;
+ case MOUNT_OPT_RSLAVE:
+ append_option(cxt, "rslave");
+ propa = 1;
+ break;
+ case MOUNT_OPT_RPRIVATE:
+ append_option(cxt, "rprivate");
+ propa = 1;
+ break;
+ case MOUNT_OPT_RUNBINDABLE:
+ append_option(cxt, "runbindable");
+ propa = 1;
+ break;
+ case MOUNT_OPT_TARGET:
+ mnt_context_disable_swapmatch(cxt, 1);
+ mnt_context_set_target(cxt, optarg);
+ break;
+ case MOUNT_OPT_TARGET_PREFIX:
+ mnt_context_set_target_prefix(cxt, optarg);
+ break;
+ case MOUNT_OPT_SOURCE:
+ mnt_context_disable_swapmatch(cxt, 1);
+ mnt_context_set_source(cxt, optarg);
+ break;
+ case MOUNT_OPT_OPTMODE:
+ optmode_mode = omode2mask(optarg);
+ if (optmode_mode == -EINVAL) {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+ break;
+ case MOUNT_OPT_OPTSRC:
+ {
+ unsigned long tmp = 0;
+ if (string_to_bitmask(optarg, &tmp, osrc2mask)) {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+ optmode_src = tmp;
+ break;
+ }
+ case MOUNT_OPT_OPTSRC_FORCE:
+ optmode |= MNT_OMODE_FORCE;
+ break;
+
+ case 'h':
+ mnt_free_context(cxt);
+ usage();
+ case 'V':
+ mnt_free_context(cxt);
+ mount_print_version();
+ default:
+ errtryhelp(MNT_EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ optmode |= optmode_mode | optmode_src;
+ if (optmode) {
+ if (!optmode_mode)
+ optmode |= MNT_OMODE_PREPEND;
+ if (!optmode_src)
+ optmode |= MNT_OMODE_FSTAB | MNT_OMODE_MTAB;
+ mnt_context_set_optsmode(cxt, optmode);
+ }
+
+ if (fstab && !mnt_context_is_nocanonicalize(cxt)) {
+ /*
+ * We have external (context independent) fstab instance, let's
+ * make a connection between the fstab and the canonicalization
+ * cache.
+ */
+ mnt_table_set_cache(fstab, mnt_context_get_cache(cxt));
+ }
+
+ if (!mnt_context_get_source(cxt) &&
+ !mnt_context_get_target(cxt) &&
+ !argc &&
+ !all) {
+ if (oper || mnt_context_get_options(cxt)) {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+ print_all(cxt, types, show_labels);
+ goto done;
+ }
+
+ /* Non-root users are allowed to use -t to print_all(),
+ but not to mount */
+ if (mnt_context_is_restricted(cxt) && types)
+ suid_drop(cxt);
+
+ if (oper && (types || all || mnt_context_get_source(cxt))) {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+
+ if (types && (all || strchr(types, ',') ||
+ strncmp(types, "no", 2) == 0))
+ mnt_context_set_fstype_pattern(cxt, types);
+ else if (types)
+ mnt_context_set_fstype(cxt, types);
+
+ if (all) {
+ /*
+ * A) Mount all
+ */
+ if (has_remount_flag(cxt))
+ rc = remount_all(cxt);
+ else
+ rc = mount_all(cxt);
+ goto done;
+
+ } else if (argc == 0 && (mnt_context_get_source(cxt) ||
+ mnt_context_get_target(cxt))) {
+ /*
+ * B) mount -L|-U|--source|--target
+ *
+ * non-root may specify source *or* target, but not both
+ */
+ if (mnt_context_is_restricted(cxt) &&
+ mnt_context_get_source(cxt) &&
+ mnt_context_get_target(cxt))
+ suid_drop(cxt);
+
+ } else if (argc == 1 && (!mnt_context_get_source(cxt) ||
+ !mnt_context_get_target(cxt))) {
+ /*
+ * C) mount [-L|-U|--source] <target>
+ * mount [--target <dir>] <source>
+ * mount <source|target>
+ *
+ * non-root may specify source *or* target, but not both
+ *
+ * It does not matter for libmount if we set source or target
+ * here (the library is able to swap it), but it matters for
+ * sanitize_paths().
+ */
+ int istag = mnt_tag_is_valid(argv[0]);
+
+ if (istag && mnt_context_get_source(cxt))
+ /* -L, -U or --source together with LABEL= or UUID= */
+ errx(MNT_EX_USAGE, _("source specified more than once"));
+ else if (istag || mnt_context_get_target(cxt))
+ mnt_context_set_source(cxt, argv[0]);
+ else
+ mnt_context_set_target(cxt, argv[0]);
+
+ if (mnt_context_is_restricted(cxt) &&
+ mnt_context_get_source(cxt) &&
+ mnt_context_get_target(cxt))
+ suid_drop(cxt);
+
+ } else if (argc == 2 && !mnt_context_get_source(cxt)
+ && !mnt_context_get_target(cxt)) {
+ /*
+ * D) mount <source> <target>
+ */
+ if (mnt_context_is_restricted(cxt))
+ suid_drop(cxt);
+
+ mnt_context_set_source(cxt, argv[0]);
+ mnt_context_set_target(cxt, argv[1]);
+
+ } else {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+
+ if (mnt_context_is_restricted(cxt) && sanitize_paths(cxt) != 0)
+ suid_drop(cxt);
+
+ if (is_move)
+ /* "move" as option string is not supported by libmount */
+ mnt_context_set_mflags(cxt, MS_MOVE);
+
+ if ((oper && !has_remount_flag(cxt)) || propa)
+ /* For --make-* or --bind is fstab/mtab unnecessary */
+ mnt_context_set_optsmode(cxt, MNT_OMODE_NOTAB);
+
+ rc = mnt_context_mount(cxt);
+
+ if (rc == -EPERM
+ && mnt_context_is_restricted(cxt)
+ && !mnt_context_syscall_called(cxt)) {
+ /* Try it again without permissions */
+ suid_drop(cxt);
+ rc = mnt_context_mount(cxt);
+ }
+ rc = mk_exit_code(cxt, rc);
+
+ if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt))
+ success_message(cxt);
+done:
+ mnt_free_context(cxt);
+ return rc;
+}
+
diff --git a/sys-utils/mountpoint.1 b/sys-utils/mountpoint.1
new file mode 100644
index 0000000..a100721
--- /dev/null
+++ b/sys-utils/mountpoint.1
@@ -0,0 +1,61 @@
+.TH MOUNTPOINT 1 "August 2019" "util-linux" "User Commands"
+.SH NAME
+mountpoint \- see if a directory or file is a mountpoint
+.SH SYNOPSIS
+.B mountpoint
+.RB [ \-d | \-q ]
+.I directory
+|
+.I file
+.sp
+.B mountpoint
+.B \-x
+.I device
+
+.SH DESCRIPTION
+.B mountpoint
+checks whether the given
+.I directory
+or
+.I file
+is mentioned in the /proc/self/mountinfo file.
+.SH OPTIONS
+.TP
+.BR \-d , " \-\-fs\-devno"
+Show the major/minor numbers of the device that is mounted on the given
+directory.
+.TP
+.BR \-q , " \-\-quiet"
+Be quiet - don't print anything.
+.TP
+.B "\-\-nofollow"
+Do not follow symbolic link if it the last element of the
+.I directory
+path.
+.TP
+.BR \-x , " \-\-devno"
+Show the major/minor numbers of the given blockdevice on standard output.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXIT STATUS
+Zero if the directory or file is a mountpoint, non-zero if not.
+.SH ENVIRONMENT
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output.
+.SH NOTES
+The util-linux
+.B mountpoint
+implementation was written from scratch for libmount. The original version
+for sysvinit suite was written by Miquel van Smoorenburg.
+
+.SH AUTHORS
+Karel Zak <kzak@redhat.com>
+.SH SEE ALSO
+.BR mount (8)
+.SH AVAILABILITY
+The mountpoint command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/mountpoint.c b/sys-utils/mountpoint.c
new file mode 100644
index 0000000..052440b
--- /dev/null
+++ b/sys-utils/mountpoint.c
@@ -0,0 +1,215 @@
+/*
+ * mountpoint(1) - see if a directory is a mountpoint
+ *
+ * This is libmount based reimplementation of the mountpoint(1)
+ * from sysvinit project.
+ *
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <libmount.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "c.h"
+#include "closestream.h"
+#include "pathnames.h"
+
+struct mountpoint_control {
+ char *path;
+ dev_t dev;
+ struct stat st;
+ unsigned int
+ dev_devno:1,
+ fs_devno:1,
+ nofollow:1,
+ quiet:1;
+};
+
+static int dir_to_device(struct mountpoint_control *ctl)
+{
+ struct libmnt_table *tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+ struct libmnt_fs *fs;
+ struct libmnt_cache *cache;
+ int rc = -1;
+
+ if (!tb) {
+ /*
+ * Fallback. Traditional way to detect mountpoints. This way
+ * is independent on /proc, but not able to detect bind mounts.
+ */
+ struct stat pst;
+ char buf[PATH_MAX], *cn;
+ int len;
+
+ cn = mnt_resolve_path(ctl->path, NULL); /* canonicalize */
+
+ len = snprintf(buf, sizeof(buf), "%s/..", cn ? cn : ctl->path);
+ free(cn);
+
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ return -1;
+ if (stat(buf, &pst) !=0)
+ return -1;
+
+ if (ctl->st.st_dev != pst.st_dev || ctl->st.st_ino == pst.st_ino) {
+ ctl->dev = ctl->st.st_dev;
+ return 0;
+ }
+
+ return -1;
+ }
+
+ /* to canonicalize all necessary paths */
+ cache = mnt_new_cache();
+ mnt_table_set_cache(tb, cache);
+ mnt_unref_cache(cache);
+
+ fs = mnt_table_find_target(tb, ctl->path, MNT_ITER_BACKWARD);
+ if (fs && mnt_fs_get_target(fs)) {
+ ctl->dev = mnt_fs_get_devno(fs);
+ rc = 0;
+ }
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int print_devno(const struct mountpoint_control *ctl)
+{
+ if (!S_ISBLK(ctl->st.st_mode)) {
+ if (!ctl->quiet)
+ warnx(_("%s: not a block device"), ctl->path);
+ return -1;
+ }
+ printf("%u:%u\n", major(ctl->st.st_rdev), minor(ctl->st.st_rdev));
+ return 0;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %1$s [-qd] /path/to/directory\n"
+ " %1$s -x /dev/device\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Check whether a directory or file is a mountpoint.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -q, --quiet quiet mode - don't print anything\n"
+ " --nofollow do not follow symlink\n"
+ " -d, --fs-devno print maj:min device number of the filesystem\n"
+ " -x, --devno print maj:min device number of the block device\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(20));
+ printf(USAGE_MAN_TAIL("mountpoint(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ struct mountpoint_control ctl = { NULL };
+
+ enum {
+ OPT_NOFOLLOW = CHAR_MAX + 1
+ };
+
+ static const struct option longopts[] = {
+ { "quiet", no_argument, NULL, 'q' },
+ { "nofollow", no_argument, NULL, OPT_NOFOLLOW },
+ { "fs-devno", no_argument, NULL, 'd' },
+ { "devno", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ mnt_init_debug(0);
+
+ while ((c = getopt_long(argc, argv, "qdxhV", longopts, NULL)) != -1) {
+
+ switch(c) {
+ case 'q':
+ ctl.quiet = 1;
+ break;
+ case OPT_NOFOLLOW:
+ ctl.nofollow = 1;
+ break;
+ case 'd':
+ ctl.fs_devno = 1;
+ break;
+ case 'x':
+ ctl.dev_devno = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind + 1 != argc) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (ctl.nofollow && ctl.dev_devno)
+ errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"),
+ "--devno", "--nofollow");
+
+ ctl.path = argv[optind];
+ c = ctl.nofollow ? lstat(ctl.path, &ctl.st) : stat(ctl.path, &ctl.st);
+ if (c) {
+ if (!ctl.quiet)
+ err(EXIT_FAILURE, "%s", ctl.path);
+ return EXIT_FAILURE;
+ }
+ if (ctl.dev_devno)
+ return print_devno(&ctl) ? EXIT_FAILURE : EXIT_SUCCESS;
+ if ((ctl.nofollow && S_ISLNK(ctl.st.st_mode)) || dir_to_device(&ctl)) {
+ if (!ctl.quiet)
+ printf(_("%s is not a mountpoint\n"), ctl.path);
+ return EXIT_FAILURE;
+ }
+ if (ctl.fs_devno)
+ printf("%u:%u\n", major(ctl.dev), minor(ctl.dev));
+ else if (!ctl.quiet)
+ printf(_("%s is a mountpoint\n"), ctl.path);
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/nsenter.1 b/sys-utils/nsenter.1
new file mode 100644
index 0000000..5674f8d
--- /dev/null
+++ b/sys-utils/nsenter.1
@@ -0,0 +1,269 @@
+.TH NSENTER 1 "June 2013" "util-linux" "User Commands"
+.SH NAME
+nsenter \- run program in different namespaces
+.SH SYNOPSIS
+.B nsenter
+[options]
+.RI [ program
+.RI [ arguments ]]
+.SH DESCRIPTION
+The
+.B nsenter
+command executes
+.I program
+in the namespace(s) that are specified in the command-line options
+(described below).
+If \fIprogram\fP is not given, then ``${SHELL}'' is run (default: /bin\:/sh).
+.PP
+Enterable namespaces are:
+.TP
+.B mount namespace
+Mounting and unmounting filesystems will not affect the rest of the system,
+except for filesystems which are explicitly marked as shared (with
+\fBmount --make-\:shared\fP; see \fI/proc\:/self\:/mountinfo\fP for the
+\fBshared\fP flag).
+For further details, see
+.BR mount_namespaces (7)
+and the discussion of the
+.B CLONE_NEWNS
+flag in
+.BR clone (2).
+.TP
+.B UTS namespace
+Setting hostname or domainname will not affect the rest of the system.
+For further details, see
+.BR uts_namespaces (7).
+.TP
+.B IPC namespace
+The process will have an independent namespace for POSIX message queues
+as well as System V message queues,
+semaphore sets and shared memory segments.
+For further details, see
+.BR ipc_namespaces (7).
+.TP
+.B network namespace
+The process will have independent IPv4 and IPv6 stacks, IP routing tables,
+firewall rules, the
+.I /proc\:/net
+and
+.I /sys\:/class\:/net
+directory trees, sockets, etc.
+For further details, see
+.BR network_namespaces (7).
+.TP
+.B PID namespace
+Children will have a set of PID to process mappings separate from the
+.B nsenter
+process.
+.B nsenter
+will fork by default if changing the PID namespace, so that the new program
+and its children share the same PID namespace and are visible to each other.
+If \fB\-\-no\-fork\fP is used, the new program will be exec'ed without forking.
+For further details, see
+.BR pid_namespaces (7).
+.TP
+.B user namespace
+The process will have a distinct set of UIDs, GIDs and capabilities.
+For further details, see
+.BR user_namespaces (7).
+.TP
+.B cgroup namespace
+The process will have a virtualized view of \fI/proc\:/self\:/cgroup\fP, and new
+cgroup mounts will be rooted at the namespace cgroup root.
+For further details, see
+.BR cgroup_namespaces (7).
+.TP
+.B time namespace
+The process can have a distinct view of
+.B CLOCK_MONOTONIC
+and/or
+.B CLOCK_BOOTTIME
+which can be changed using \fI/proc/self/timens_offsets\fP.
+For further details, see
+.BR time_namespaces (7).
+.SH OPTIONS
+Various of the options below that relate to namespaces take an optional
+.I file
+argument.
+This should be one of the
+.I /proc/[pid]/ns/*
+files described in
+.BR namespaces (7),
+or the pathname of a bind mount that was created on one of those files.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Enter all namespaces of the target process by the default
+.I /proc/[pid]/ns/*
+namespace paths. The default paths to the target process namespaces may be
+overwritten by namespace specific options (e.g., --all --mount=[path]).
+
+The user namespace will be ignored if the same as the caller's current user
+namespace. It prevents a caller that has dropped capabilities from regaining
+those capabilities via a call to setns(). See
+.BR setns (2)
+for more details.
+.TP
+\fB\-t\fR, \fB\-\-target\fR \fIpid\fP
+Specify a target process to get contexts from. The paths to the contexts
+specified by
+.I pid
+are:
+.RS
+.PD 0
+.IP "" 20
+.TP
+/proc/\fIpid\fR/ns/mnt
+the mount namespace
+.TP
+/proc/\fIpid\fR/ns/uts
+the UTS namespace
+.TP
+/proc/\fIpid\fR/ns/ipc
+the IPC namespace
+.TP
+/proc/\fIpid\fR/ns/net
+the network namespace
+.TP
+/proc/\fIpid\fR/ns/pid
+the PID namespace
+.TP
+/proc/\fIpid\fR/ns/user
+the user namespace
+.TP
+/proc/\fIpid\fR/ns/cgroup
+the cgroup namespace
+.TP
+/proc/\fIpid\fR/ns/time
+the time namespace
+.TP
+/proc/\fIpid\fR/root
+the root directory
+.TP
+/proc/\fIpid\fR/cwd
+the working directory respectively
+.PD
+.RE
+.TP
+\fB\-m\fR, \fB\-\-mount\fR[=\fIfile\fR]
+Enter the mount namespace. If no file is specified, enter the mount namespace
+of the target process.
+If
+.I file
+is specified, enter the mount namespace
+specified by
+.IR file .
+.TP
+\fB\-u\fR, \fB\-\-uts\fR[=\fIfile\fR]
+Enter the UTS namespace. If no file is specified, enter the UTS namespace of
+the target process.
+If
+.I file
+is specified, enter the UTS namespace specified by
+.IR file .
+.TP
+\fB\-i\fR, \fB\-\-ipc\fR[=\fIfile\fR]
+Enter the IPC namespace. If no file is specified, enter the IPC namespace of
+the target process.
+If
+.I file
+is specified, enter the IPC namespace specified by
+.IR file .
+.TP
+\fB\-n\fR, \fB\-\-net\fR[=\fIfile\fR]
+Enter the network namespace. If no file is specified, enter the network
+namespace of the target process.
+If
+.I file
+is specified, enter the network namespace specified by
+.IR file .
+.TP
+\fB\-p\fR, \fB\-\-pid\fR[=\fIfile\fR]
+Enter the PID namespace. If no file is specified, enter the PID namespace of
+the target process.
+If
+.I file
+is specified, enter the PID namespace specified by
+.IR file .
+.TP
+\fB\-U\fR, \fB\-\-user\fR[=\fIfile\fR]
+Enter the user namespace. If no file is specified, enter the user namespace of
+the target process.
+If
+.I file
+is specified, enter the user namespace specified by
+.IR file .
+See also the \fB\-\-setuid\fR and \fB\-\-setgid\fR options.
+.TP
+\fB\-C\fR, \fB\-\-cgroup\fR[=\fIfile\fR]
+Enter the cgroup namespace. If no file is specified, enter the cgroup namespace of
+the target process.
+If
+.I file
+is specified, enter the cgroup namespace specified by
+.IR file .
+.TP
+\fB\-T\fR, \fB\-\-time\fR[=\fIfile\fR]
+Enter the time namespace. If no file is specified, enter the time namespace of
+the target process.
+If
+.I file
+is specified, enter the time namespace specified by
+.IR file .
+.TP
+\fB\-G\fR, \fB\-\-setgid\fR \fIgid\fR
+Set the group ID which will be used in the entered namespace and drop
+supplementary groups.
+.BR nsenter (1)
+always sets GID for user namespaces, the default is 0.
+.TP
+\fB\-S\fR, \fB\-\-setuid\fR \fIuid\fR
+Set the user ID which will be used in the entered namespace.
+.BR nsenter (1)
+always sets UID for user namespaces, the default is 0.
+.TP
+\fB\-\-preserve\-credentials\fR
+Don't modify UID and GID when enter user namespace. The default is to
+drops supplementary groups and sets GID and UID to 0.
+.TP
+\fB\-r\fR, \fB\-\-root\fR[=\fIdirectory\fR]
+Set the root directory. If no directory is specified, set the root directory to
+the root directory of the target process. If directory is specified, set the
+root directory to the specified directory.
+.TP
+\fB\-w\fR, \fB\-\-wd\fR[=\fIdirectory\fR]
+Set the working directory. If no directory is specified, set the working
+directory to the working directory of the target process. If directory is
+specified, set the working directory to the specified directory.
+.TP
+\fB\-F\fR, \fB\-\-no\-fork\fR
+Do not fork before exec'ing the specified program. By default, when entering a
+PID namespace, \fBnsenter\fP calls \fBfork\fP before calling \fBexec\fP so that
+any children will also be in the newly entered PID namespace.
+.TP
+\fB\-Z\fR, \fB\-\-follow\-context\fR
+Set the SELinux security context used for executing a new process according to
+already running process specified by \fB\-\-target\fR PID. (The util-linux has
+to be compiled with SELinux support otherwise the option is unavailable.)
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH AUTHORS
+.UR biederm@xmission.com
+Eric Biederman
+.UE
+.br
+.UR kzak@redhat.com
+Karel Zak
+.UE
+.SH SEE ALSO
+.BR clone (2),
+.BR setns (2),
+.BR namespaces (7)
+.SH AVAILABILITY
+The nsenter command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/nsenter.c b/sys-utils/nsenter.c
new file mode 100644
index 0000000..4432cd3
--- /dev/null
+++ b/sys-utils/nsenter.c
@@ -0,0 +1,492 @@
+/*
+ * nsenter(1) - command-line interface for setns(2)
+ *
+ * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xmission.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; version 2.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <grp.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+#endif
+
+#include "strutils.h"
+#include "nls.h"
+#include "c.h"
+#include "closestream.h"
+#include "namespace.h"
+#include "exec_shell.h"
+
+static struct namespace_file {
+ int nstype;
+ const char *name;
+ int fd;
+} namespace_files[] = {
+ /* Careful the order is significant in this array.
+ *
+ * The user namespace comes either first or last: first if
+ * you're using it to increase your privilege and last if
+ * you're using it to decrease. We enter the namespaces in
+ * two passes starting initially from offset 1 and then offset
+ * 0 if that fails.
+ */
+ { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 },
+ { .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 },
+ { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 },
+ { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 },
+ { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 },
+ { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 },
+ { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 },
+ { .nstype = CLONE_NEWTIME, .name = "ns/time", .fd = -1 },
+ { .nstype = 0, .name = NULL, .fd = -1 }
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Run a program with namespaces of other processes.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all enter all namespaces\n"), out);
+ fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out);
+ fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out);
+ fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out);
+ fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out);
+ fputs(_(" -n, --net[=<file>] enter network namespace\n"), out);
+ fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out);
+ fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out);
+ fputs(_(" -U, --user[=<file>] enter user namespace\n"), out);
+ fputs(_(" -T, --time[=<file>] enter time namespace\n"), out);
+ fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out);
+ fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out);
+ fputs(_(" --preserve-credentials do not touch uids or gids\n"), out);
+ fputs(_(" -r, --root[=<dir>] set the root directory\n"), out);
+ fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out);
+ fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out);
+#ifdef HAVE_LIBSELINUX
+ fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out);
+#endif
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+ printf(USAGE_MAN_TAIL("nsenter(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static pid_t namespace_target_pid = 0;
+static int root_fd = -1;
+static int wd_fd = -1;
+
+static void open_target_fd(int *fd, const char *type, const char *path)
+{
+ char pathbuf[PATH_MAX];
+
+ if (!path && namespace_target_pid) {
+ snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s",
+ namespace_target_pid, type);
+ path = pathbuf;
+ }
+ if (!path)
+ errx(EXIT_FAILURE,
+ _("neither filename nor target pid supplied for %s"),
+ type);
+
+ if (*fd >= 0)
+ close(*fd);
+
+ *fd = open(path, O_RDONLY);
+ if (*fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), path);
+}
+
+static void open_namespace_fd(int nstype, const char *path)
+{
+ struct namespace_file *nsfile;
+
+ for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
+ if (nstype != nsfile->nstype)
+ continue;
+
+ open_target_fd(&nsfile->fd, nsfile->name, path);
+ return;
+ }
+ /* This should never happen */
+ assert(nsfile->nstype);
+}
+
+static int get_ns_ino(const char *path, ino_t *ino)
+{
+ struct stat st;
+
+ if (stat(path, &st) != 0)
+ return -errno;
+ *ino = st.st_ino;
+ return 0;
+}
+
+static int is_same_namespace(pid_t a, pid_t b, const char *type)
+{
+ char path[PATH_MAX];
+ ino_t a_ino = 0, b_ino = 0;
+
+ snprintf(path, sizeof(path), "/proc/%u/%s", a, type);
+ if (get_ns_ino(path, &a_ino) != 0)
+ err(EXIT_FAILURE, _("stat of %s failed"), path);
+
+ snprintf(path, sizeof(path), "/proc/%u/%s", b, type);
+ if (get_ns_ino(path, &b_ino) != 0)
+ err(EXIT_FAILURE, _("stat of %s failed"), path);
+
+ return a_ino == b_ino;
+}
+
+static void continue_as_child(void)
+{
+ pid_t child = fork();
+ int status;
+ pid_t ret;
+
+ if (child < 0)
+ err(EXIT_FAILURE, _("fork failed"));
+
+ /* Only the child returns */
+ if (child == 0)
+ return;
+
+ for (;;) {
+ ret = waitpid(child, &status, WUNTRACED);
+ if ((ret == child) && (WIFSTOPPED(status))) {
+ /* The child suspended so suspend us as well */
+ kill(getpid(), SIGSTOP);
+ kill(child, SIGCONT);
+ } else {
+ break;
+ }
+ }
+ /* Return the child's exit code if possible */
+ if (WIFEXITED(status)) {
+ exit(WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ kill(getpid(), WTERMSIG(status));
+ }
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ enum {
+ OPT_PRESERVE_CRED = CHAR_MAX + 1
+ };
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V'},
+ { "target", required_argument, NULL, 't' },
+ { "mount", optional_argument, NULL, 'm' },
+ { "uts", optional_argument, NULL, 'u' },
+ { "ipc", optional_argument, NULL, 'i' },
+ { "net", optional_argument, NULL, 'n' },
+ { "pid", optional_argument, NULL, 'p' },
+ { "user", optional_argument, NULL, 'U' },
+ { "cgroup", optional_argument, NULL, 'C' },
+ { "time", optional_argument, NULL, 'T' },
+ { "setuid", required_argument, NULL, 'S' },
+ { "setgid", required_argument, NULL, 'G' },
+ { "root", optional_argument, NULL, 'r' },
+ { "wd", optional_argument, NULL, 'w' },
+ { "no-fork", no_argument, NULL, 'F' },
+ { "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED },
+#ifdef HAVE_LIBSELINUX
+ { "follow-context", no_argument, NULL, 'Z' },
+#endif
+ { NULL, 0, NULL, 0 }
+ };
+
+ struct namespace_file *nsfile;
+ int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0;
+ bool do_rd = false, do_wd = false, force_uid = false, force_gid = false;
+ bool do_all = false;
+ int do_fork = -1; /* unknown yet */
+ uid_t uid = 0;
+ gid_t gid = 0;
+#ifdef HAVE_LIBSELINUX
+ bool selinux = 0;
+#endif
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c =
+ getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::FZ",
+ longopts, NULL)) != -1) {
+ switch (c) {
+ case 'a':
+ do_all = true;
+ break;
+ case 't':
+ namespace_target_pid =
+ strtoul_or_err(optarg, _("failed to parse pid"));
+ break;
+ case 'm':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWNS, optarg);
+ else
+ namespaces |= CLONE_NEWNS;
+ break;
+ case 'u':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWUTS, optarg);
+ else
+ namespaces |= CLONE_NEWUTS;
+ break;
+ case 'i':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWIPC, optarg);
+ else
+ namespaces |= CLONE_NEWIPC;
+ break;
+ case 'n':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWNET, optarg);
+ else
+ namespaces |= CLONE_NEWNET;
+ break;
+ case 'p':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWPID, optarg);
+ else
+ namespaces |= CLONE_NEWPID;
+ break;
+ case 'C':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWCGROUP, optarg);
+ else
+ namespaces |= CLONE_NEWCGROUP;
+ break;
+ case 'U':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWUSER, optarg);
+ else
+ namespaces |= CLONE_NEWUSER;
+ break;
+ case 'T':
+ if (optarg)
+ open_namespace_fd(CLONE_NEWTIME, optarg);
+ else
+ namespaces |= CLONE_NEWTIME;
+ break;
+ case 'S':
+ uid = strtoul_or_err(optarg, _("failed to parse uid"));
+ force_uid = true;
+ break;
+ case 'G':
+ gid = strtoul_or_err(optarg, _("failed to parse gid"));
+ force_gid = true;
+ break;
+ case 'F':
+ do_fork = 0;
+ break;
+ case 'r':
+ if (optarg)
+ open_target_fd(&root_fd, "root", optarg);
+ else
+ do_rd = true;
+ break;
+ case 'w':
+ if (optarg)
+ open_target_fd(&wd_fd, "cwd", optarg);
+ else
+ do_wd = true;
+ break;
+ case OPT_PRESERVE_CRED:
+ preserve_cred = 1;
+ break;
+#ifdef HAVE_LIBSELINUX
+ case 'Z':
+ selinux = 1;
+ break;
+#endif
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+#ifdef HAVE_LIBSELINUX
+ if (selinux && is_selinux_enabled() > 0) {
+ char *scon = NULL;
+
+ if (!namespace_target_pid)
+ errx(EXIT_FAILURE, _("no target PID specified for --follow-context"));
+ if (getpidcon(namespace_target_pid, &scon) < 0)
+ errx(EXIT_FAILURE, _("failed to get %d SELinux context"),
+ (int) namespace_target_pid);
+ if (setexeccon(scon) < 0)
+ errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon);
+ freecon(scon);
+ }
+#endif
+
+ if (do_all) {
+ if (!namespace_target_pid)
+ errx(EXIT_FAILURE, _("no target PID specified for --all"));
+ for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
+ if (nsfile->fd >= 0)
+ continue; /* namespace already specified */
+
+ /* It is not permitted to use setns(2) to reenter the caller's
+ * current user namespace; see setns(2) man page for more details.
+ */
+ if (nsfile->nstype & CLONE_NEWUSER
+ && is_same_namespace(getpid(), namespace_target_pid, nsfile->name))
+ continue;
+
+ namespaces |= nsfile->nstype;
+ }
+ }
+
+ /*
+ * Open remaining namespace and directory descriptors.
+ */
+ for (nsfile = namespace_files; nsfile->nstype; nsfile++)
+ if (nsfile->nstype & namespaces)
+ open_namespace_fd(nsfile->nstype, NULL);
+ if (do_rd)
+ open_target_fd(&root_fd, "root", NULL);
+ if (do_wd)
+ open_target_fd(&wd_fd, "cwd", NULL);
+
+ /*
+ * Update namespaces variable to contain all requested namespaces
+ */
+ for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
+ if (nsfile->fd < 0)
+ continue;
+ namespaces |= nsfile->nstype;
+ }
+
+ /* for user namespaces we always set UID and GID (default is 0)
+ * and clear root's groups if --preserve-credentials is no specified */
+ if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {
+ force_uid = true, force_gid = true;
+
+ /* We call setgroups() before and after we enter user namespace,
+ * let's complain only if both fail */
+ if (setgroups(0, NULL) != 0)
+ setgroups_nerrs++;
+ }
+
+ /*
+ * Now that we know which namespaces we want to enter, enter
+ * them. Do this in two passes, not entering the user
+ * namespace on the first pass. So if we're deprivileging the
+ * container we'll enter the user namespace last and if we're
+ * privileging it then we enter the user namespace first
+ * (because the initial setns will fail).
+ */
+ for (pass = 0; pass < 2; pass ++) {
+ for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) {
+ if (nsfile->fd < 0)
+ continue;
+ if (nsfile->nstype == CLONE_NEWPID && do_fork == -1)
+ do_fork = 1;
+ if (setns(nsfile->fd, nsfile->nstype)) {
+ if (pass != 0)
+ err(EXIT_FAILURE,
+ _("reassociate to namespace '%s' failed"),
+ nsfile->name);
+ else
+ continue;
+ }
+
+ close(nsfile->fd);
+ nsfile->fd = -1;
+ }
+ }
+
+ /* Remember the current working directory if I'm not changing it */
+ if (root_fd >= 0 && wd_fd < 0) {
+ wd_fd = open(".", O_RDONLY);
+ if (wd_fd < 0)
+ err(EXIT_FAILURE,
+ _("cannot open current working directory"));
+ }
+
+ /* Change the root directory */
+ if (root_fd >= 0) {
+ if (fchdir(root_fd) < 0)
+ err(EXIT_FAILURE,
+ _("change directory by root file descriptor failed"));
+
+ if (chroot(".") < 0)
+ err(EXIT_FAILURE, _("chroot failed"));
+
+ close(root_fd);
+ root_fd = -1;
+ }
+
+ /* Change the working directory */
+ if (wd_fd >= 0) {
+ if (fchdir(wd_fd) < 0)
+ err(EXIT_FAILURE,
+ _("change directory by working directory file descriptor failed"));
+
+ close(wd_fd);
+ wd_fd = -1;
+ }
+
+ if (do_fork == 1)
+ continue_as_child();
+
+ if (force_uid || force_gid) {
+ if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs) /* drop supplementary groups */
+ err(EXIT_FAILURE, _("setgroups failed"));
+ if (force_gid && setgid(gid) < 0) /* change GID */
+ err(EXIT_FAILURE, _("setgid failed"));
+ if (force_uid && setuid(uid) < 0) /* change UID */
+ err(EXIT_FAILURE, _("setuid failed"));
+ }
+
+ if (optind < argc) {
+ execvp(argv[optind], argv + optind);
+ errexec(argv[optind]);
+ }
+ exec_shell();
+}
diff --git a/sys-utils/pivot_root.8 b/sys-utils/pivot_root.8
new file mode 100644
index 0000000..ea98f3b
--- /dev/null
+++ b/sys-utils/pivot_root.8
@@ -0,0 +1,75 @@
+.TH PIVOT_ROOT 8 "August 2011" "util-linux" "System Administration"
+.SH NAME
+pivot_root \- change the root filesystem
+.SH SYNOPSIS
+.B pivot_root
+.I new_root put_old
+.SH DESCRIPTION
+\fBpivot_root\fP moves the root file system of the current process to the
+directory \fIput_old\fP and makes \fInew_root\fP the new root file system.
+Since \fBpivot_root\fP(8) simply calls \fBpivot_root\fP(2), we refer to
+the man page of the latter for further details.
+
+Note that, depending on the implementation of \fBpivot_root\fP, root and
+cwd of the caller may or may not change. The following is a sequence for
+invoking \fBpivot_root\fP that works in either case, assuming that
+\fBpivot_root\fP and \fBchroot\fP are in the current \fBPATH\fP:
+.sp
+cd \fInew_root\fP
+.br
+pivot_root . \fIput_old\fP
+.br
+exec chroot . \fIcommand\fP
+.sp
+Note that \fBchroot\fP must be available under the old root and under the new
+root, because \fBpivot_root\fP may or may not have implicitly changed the
+root directory of the shell.
+
+Note that \fBexec chroot\fP changes the running executable, which is
+necessary if the old root directory should be unmounted afterwards.
+Also note that standard input, output, and error may still point to a
+device on the old root file system, keeping it busy. They can easily be
+changed when invoking \fBchroot\fP (see below; note the absence of
+leading slashes to make it work whether \fBpivot_root\fP has changed the
+shell's root or not).
+.SH OPTIONS
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH EXAMPLE
+Change the root file system to /dev/hda1 from an interactive shell:
+.sp
+.nf
+mount /dev/hda1 /new-root
+cd /new-root
+pivot_root . old-root
+exec chroot . sh <dev/console >dev/console 2>&1
+umount /old-root
+.fi
+.sp
+Mount the new root file system over NFS from 10.0.0.1:/my_root and run
+\fBinit\fP:
+.sp
+.nf
+ifconfig lo 127.0.0.1 up # for portmap
+# configure Ethernet or such
+portmap # for lockd (implicitly started by mount)
+mount \-o ro 10.0.0.1:/my_root /mnt
+killall portmap # portmap keeps old root busy
+cd /mnt
+pivot_root . old_root
+exec chroot . sh \-c 'umount /old_root; exec /sbin/init' \\
+ <dev/console >dev/console 2>&1
+.fi
+.SH SEE ALSO
+.BR chroot (1),
+.BR pivot_root (2),
+.BR mount (8),
+.BR switch_root (8),
+.BR umount (8)
+.SH AVAILABILITY
+The pivot_root command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/pivot_root.c b/sys-utils/pivot_root.c
new file mode 100644
index 0000000..aef1b12
--- /dev/null
+++ b/sys-utils/pivot_root.c
@@ -0,0 +1,79 @@
+/*
+ * pivot_root.c - Change the root file system
+ *
+ * Copyright (C) 2000 Werner Almesberger
+ *
+ * This file 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 file 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.
+ */
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+
+#define pivot_root(new_root,put_old) syscall(SYS_pivot_root,new_root,put_old)
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] new_root put_old\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Change the root filesystem.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("pivot_root(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (argc != 3) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (pivot_root(argv[1], argv[2]) < 0)
+ err(EXIT_FAILURE, _("failed to change root from `%s' to `%s'"),
+ argv[1], argv[2]);
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/prlimit.1 b/sys-utils/prlimit.1
new file mode 100644
index 0000000..0bf2db0
--- /dev/null
+++ b/sys-utils/prlimit.1
@@ -0,0 +1,120 @@
+.\" prlimit.1 --
+.\" Copyright 2011 Davidlohr Bueso <dave@gnu.org>
+.\" May be distributed under the GNU General Public License
+
+.TH PRLIMIT 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+prlimit \- get and set process resource limits
+.SH SYNOPSIS
+.BR prlimit " [options]"
+.RB [ \-\-\fIresource\fR [ =\fIlimits\fR]
+.RB [ \-\-pid\ \fIPID\fR]
+
+.BR prlimit " [options]"
+.RB [ \-\-\fIresource\fR [ =\fIlimits\fR]
+.IR "command " [ argument ...]
+
+.SH DESCRIPTION
+Given a process ID and one or more resources, \fBprlimit\fP tries to retrieve
+and/or modify the limits.
+
+When \fIcommand\fR is given,
+.B prlimit
+will run this command with the given arguments.
+
+The \fIlimits\fP parameter is composed of a soft and a hard value, separated
+by a colon (:), in order to modify the existing values. If no \fIlimits\fR are
+given, \fBprlimit\fP will display the current values. If one of the values
+is not given, then the existing one will be used. To specify the unlimited or
+infinity limit (RLIM_INFINITY), the \-1 or 'unlimited' string can be passed.
+
+Because of the nature of limits, the soft limit must be lower or equal to the
+high limit (also called the ceiling). To see all available resource limits,
+refer to the RESOURCE OPTIONS section.
+
+.IP "\fIsoft\fP:\fIhard\fP Specify both limits."
+.IP "\fIsoft\fP: Specify only the soft limit."
+.IP ":\fIhard\fP Specify only the hard limit."
+.IP "\fIvalue\fP Specify both limits to the same value."
+
+.SH GENERAL OPTIONS
+.IP "\fB\-h, \-\-help\fP"
+Display help text and exit.
+.IP "\fB\-\-noheadings\fP"
+Do not print a header line.
+.IP "\fB\-o, \-\-output \fIlist\fP"
+Define the output columns to use. If no output arrangement is specified,
+then a default set is used.
+Use \fB\-\-help\fP to get a list of all supported columns.
+.IP "\fB\-p, \-\-pid\fP"
+Specify the process id; if none is given, the running process will be used.
+.IP "\fB\-\-raw\fP"
+Use the raw output format.
+.IP "\fB\-\-verbose\fP"
+Verbose mode.
+.IP "\fB\-V, \-\-version\fP"
+Display version information and exit.
+
+.SH RESOURCE OPTIONS
+.IP "\fB\-c, \-\-core\fP[=\fIlimits\fR]"
+Maximum size of a core file.
+.IP "\fB\-d, \-\-data\fP[=\fIlimits\fR]"
+Maximum data size.
+.IP "\fB\-e, \-\-nice\fP[=\fIlimits\fR]"
+Maximum nice priority allowed to raise.
+.IP "\fB\-f, \-\-fsize\fP[=\fIlimits\fR]"
+Maximum file size.
+.IP "\fB\-i, \-\-sigpending\fP[=\fIlimits\fR]"
+Maximum number of pending signals.
+.IP "\fB\-l, \-\-memlock\fP[=\fIlimits\fR]"
+Maximum locked-in-memory address space.
+.IP "\fB\-m, \-\-rss\fP[=\fIlimits\fR]"
+Maximum Resident Set Size (RSS).
+.IP "\fB\-n, \-\-nofile\fP[=\fIlimits\fR]"
+Maximum number of open files.
+.IP "\fB\-q, \-\-msgqueue\fP[=\fIlimits\fR]"
+Maximum number of bytes in POSIX message queues.
+.IP "\fB\-r, \-\-rtprio\fP[=\fIlimits\fR]"
+Maximum real-time priority.
+.IP "\fB\-s, \-\-stack\fP[=\fIlimits\fR]"
+Maximum size of the stack.
+.IP "\fB\-t, \-\-cpu\fP[=\fIlimits\fR]"
+CPU time, in seconds.
+.IP "\fB\-u, \-\-nproc\fP[=\fIlimits\fR]"
+Maximum number of processes.
+.IP "\fB\-v, \-\-as\fP[=\fIlimits\fR]"
+Address space limit.
+.IP "\fB\-x, \-\-locks\fP[=\fIlimits\fR]"
+Maximum number of file locks held.
+.IP "\fB\-y, \-\-rttime\fP[=\fIlimits\fR]"
+Timeout for real-time tasks.
+
+.SH NOTES
+The prlimit system call is supported since Linux 2.6.36, older kernels will
+break this program.
+
+.SH EXAMPLES
+.IP "\fBprlimit \-\-pid 13134\fP"
+Display limit values for all current resources.
+.IP "\fBprlimit \-\-pid 13134 \-\-rss \-\-nofile=1024:4095\fP"
+Display the limits of the RSS, and set the soft and hard limits for the number
+of open files to 1024 and 4095, respectively.
+.IP "\fBprlimit \-\-pid 13134 \-\-nproc=512:\fP"
+Modify only the soft limit for the number of processes.
+.IP "\fBprlimit \-\-pid $$ \-\-nproc=unlimited\fP"
+Set for the current process both the soft and ceiling values for the number of
+processes to unlimited.
+.IP "\fBprlimit \-\-cpu=10 sort \-u hugefile\fP"
+Set both the soft and hard CPU time limit to ten seconds and run 'sort'.
+
+.SH AUTHORS
+.nf
+Davidlohr Bueso <dave@gnu.org> - In memory of Dennis M. Ritchie.
+.fi
+.SH SEE ALSO
+.BR ulimit (1p),
+.BR prlimit (2)
+
+.SH AVAILABILITY
+The prlimit command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/prlimit.c b/sys-utils/prlimit.c
new file mode 100644
index 0000000..c8c7d5c
--- /dev/null
+++ b/sys-utils/prlimit.c
@@ -0,0 +1,647 @@
+/*
+ * prlimit - get/set process resource limits.
+ *
+ * Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/resource.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "list.h"
+#include "closestream.h"
+
+#ifndef RLIMIT_RTTIME
+# define RLIMIT_RTTIME 15
+#endif
+
+enum {
+ AS,
+ CORE,
+ CPU,
+ DATA,
+ FSIZE,
+ LOCKS,
+ MEMLOCK,
+ MSGQUEUE,
+ NICE,
+ NOFILE,
+ NPROC,
+ RSS,
+ RTPRIO,
+ RTTIME,
+ SIGPENDING,
+ STACK
+};
+
+/* basic output flags */
+static int no_headings;
+static int raw;
+
+struct prlimit_desc {
+ const char *name;
+ const char *help;
+ const char *unit;
+ int resource;
+};
+
+static struct prlimit_desc prlimit_desc[] =
+{
+ [AS] = { "AS", N_("address space limit"), N_("bytes"), RLIMIT_AS },
+ [CORE] = { "CORE", N_("max core file size"), N_("bytes"), RLIMIT_CORE },
+ [CPU] = { "CPU", N_("CPU time"), N_("seconds"), RLIMIT_CPU },
+ [DATA] = { "DATA", N_("max data size"), N_("bytes"), RLIMIT_DATA },
+ [FSIZE] = { "FSIZE", N_("max file size"), N_("bytes"), RLIMIT_FSIZE },
+ [LOCKS] = { "LOCKS", N_("max number of file locks held"), N_("locks"), RLIMIT_LOCKS },
+ [MEMLOCK] = { "MEMLOCK", N_("max locked-in-memory address space"), N_("bytes"), RLIMIT_MEMLOCK },
+ [MSGQUEUE] = { "MSGQUEUE", N_("max bytes in POSIX mqueues"), N_("bytes"), RLIMIT_MSGQUEUE },
+ [NICE] = { "NICE", N_("max nice prio allowed to raise"), NULL, RLIMIT_NICE },
+ [NOFILE] = { "NOFILE", N_("max number of open files"), N_("files"), RLIMIT_NOFILE },
+ [NPROC] = { "NPROC", N_("max number of processes"), N_("processes"), RLIMIT_NPROC },
+ [RSS] = { "RSS", N_("max resident set size"), N_("bytes"), RLIMIT_RSS },
+ [RTPRIO] = { "RTPRIO", N_("max real-time priority"), NULL, RLIMIT_RTPRIO },
+ [RTTIME] = { "RTTIME", N_("timeout for real-time tasks"), N_("microsecs"), RLIMIT_RTTIME },
+ [SIGPENDING] = { "SIGPENDING", N_("max number of pending signals"), N_("signals"), RLIMIT_SIGPENDING },
+ [STACK] = { "STACK", N_("max stack size"), N_("bytes"), RLIMIT_STACK }
+};
+
+#define MAX_RESOURCES ARRAY_SIZE(prlimit_desc)
+
+struct prlimit {
+ struct list_head lims;
+
+ struct rlimit rlim;
+ struct prlimit_desc *desc;
+ int modify; /* PRLIMIT_{SOFT,HARD} mask */
+};
+
+#define PRLIMIT_EMPTY_LIMIT {{ 0, 0, }, NULL, 0 }
+
+enum {
+ COL_HELP,
+ COL_RES,
+ COL_SOFT,
+ COL_HARD,
+ COL_UNITS,
+};
+
+/* column names */
+struct colinfo {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+};
+
+/* columns descriptions */
+static struct colinfo infos[] = {
+ [COL_RES] = { "RESOURCE", 0.25, SCOLS_FL_TRUNC, N_("resource name") },
+ [COL_HELP] = { "DESCRIPTION", 0.1, SCOLS_FL_TRUNC, N_("resource description")},
+ [COL_SOFT] = { "SOFT", 0.1, SCOLS_FL_RIGHT, N_("soft limit")},
+ [COL_HARD] = { "HARD", 1, SCOLS_FL_RIGHT, N_("hard limit (ceiling)")},
+ [COL_UNITS] = { "UNITS", 0.1, SCOLS_FL_TRUNC, N_("units")},
+};
+
+static int columns[ARRAY_SIZE(infos) * 2];
+static int ncolumns;
+
+
+
+#define INFINITY_STR "unlimited"
+#define INFINITY_STRLEN (sizeof(INFINITY_STR) - 1)
+
+#define PRLIMIT_SOFT (1 << 1)
+#define PRLIMIT_HARD (1 << 2)
+
+static pid_t pid; /* calling process (default) */
+static int verbose;
+
+#ifndef HAVE_PRLIMIT
+# include <sys/syscall.h>
+static int prlimit(pid_t p, int resource,
+ const struct rlimit *new_limit,
+ struct rlimit *old_limit)
+{
+ return syscall(SYS_prlimit64, p, resource, new_limit, old_limit);
+}
+#endif
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+
+ fprintf(out,
+ _(" %s [options] [-p PID]\n"), program_invocation_short_name);
+ fprintf(out,
+ _(" %s [options] COMMAND\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Show or change the resource limits of a process.\n"), out);
+
+ fputs(_("\nGeneral Options:\n"), out);
+ fputs(_(" -p, --pid <pid> process id\n"
+ " -o, --output <list> define which output columns to use\n"
+ " --noheadings don't print headings\n"
+ " --raw use the raw output format\n"
+ " --verbose verbose output\n"
+ ), out);
+ printf(USAGE_HELP_OPTIONS(24));
+
+ fputs(_("\nResources Options:\n"), out);
+ fputs(_(" -c, --core maximum size of core files created\n"
+ " -d, --data maximum size of a process's data segment\n"
+ " -e, --nice maximum nice priority allowed to raise\n"
+ " -f, --fsize maximum size of files written by the process\n"
+ " -i, --sigpending maximum number of pending signals\n"
+ " -l, --memlock maximum size a process may lock into memory\n"
+ " -m, --rss maximum resident set size\n"
+ " -n, --nofile maximum number of open files\n"
+ " -q, --msgqueue maximum bytes in POSIX message queues\n"
+ " -r, --rtprio maximum real-time scheduling priority\n"
+ " -s, --stack maximum stack size\n"
+ " -t, --cpu maximum amount of CPU time in seconds\n"
+ " -u, --nproc maximum number of user processes\n"
+ " -v, --as size of virtual memory\n"
+ " -x, --locks maximum number of file locks\n"
+ " -y, --rttime CPU time in microseconds a process scheduled\n"
+ " under real-time scheduling\n"), out);
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("prlimit(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static inline int get_column_id(int num)
+{
+ assert(num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+
+ return columns[num];
+}
+
+static inline struct colinfo *get_column_info(unsigned num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+static void add_scols_line(struct libscols_table *table, struct prlimit *l)
+{
+ int i;
+ struct libscols_line *line;
+
+ assert(table);
+ assert(l);
+
+ line = scols_table_new_line(table, NULL);
+ if (!line)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (i = 0; i < ncolumns; i++) {
+ char *str = NULL;
+
+ switch (get_column_id(i)) {
+ case COL_RES:
+ str = xstrdup(l->desc->name);
+ break;
+ case COL_HELP:
+ str = xstrdup(l->desc->help);
+ break;
+ case COL_SOFT:
+ if (l->rlim.rlim_cur == RLIM_INFINITY)
+ str = xstrdup(_("unlimited"));
+ else
+ xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_cur);
+ break;
+ case COL_HARD:
+ if (l->rlim.rlim_max == RLIM_INFINITY)
+ str = xstrdup(_("unlimited"));
+ else
+ xasprintf(&str, "%llu", (unsigned long long) l->rlim.rlim_max);
+ break;
+ case COL_UNITS:
+ str = l->desc->unit ? xstrdup(_(l->desc->unit)) : NULL;
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, i, str))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ assert(name);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static void rem_prlim(struct prlimit *lim)
+{
+ if (!lim)
+ return;
+ list_del(&lim->lims);
+ free(lim);
+}
+
+static int show_limits(struct list_head *lims)
+{
+ int i;
+ struct list_head *p, *pnext;
+ struct libscols_table *table;
+
+ table = scols_new_table();
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_raw(table, raw);
+ scols_table_enable_noheadings(table, no_headings);
+
+ for (i = 0; i < ncolumns; i++) {
+ struct colinfo *col = get_column_info(i);
+
+ if (!scols_table_new_column(table, col->name, col->whint, col->flags))
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ }
+
+ list_for_each_safe(p, pnext, lims) {
+ struct prlimit *lim = list_entry(p, struct prlimit, lims);
+
+ add_scols_line(table, lim);
+ rem_prlim(lim);
+ }
+
+ scols_print_table(table);
+ scols_unref_table(table);
+ return 0;
+}
+
+/*
+ * If one of the limits is unknown (default value for not being passed), we
+ * need to get the current limit and use it. I see no other way other than
+ * using prlimit(2).
+ */
+static void get_unknown_hardsoft(struct prlimit *lim)
+{
+ struct rlimit old;
+
+ if (prlimit(pid, lim->desc->resource, NULL, &old) == -1)
+ err(EXIT_FAILURE, _("failed to get old %s limit"),
+ lim->desc->name);
+
+ if (!(lim->modify & PRLIMIT_SOFT))
+ lim->rlim.rlim_cur = old.rlim_cur;
+ else if (!(lim->modify & PRLIMIT_HARD))
+ lim->rlim.rlim_max = old.rlim_max;
+}
+
+static void do_prlimit(struct list_head *lims)
+{
+ struct list_head *p, *pnext;
+
+ list_for_each_safe(p, pnext, lims) {
+ struct rlimit *new = NULL, *old = NULL;
+ struct prlimit *lim = list_entry(p, struct prlimit, lims);
+
+ if (lim->modify) {
+ if (lim->modify != (PRLIMIT_HARD | PRLIMIT_SOFT))
+ get_unknown_hardsoft(lim);
+
+ if ((lim->rlim.rlim_cur > lim->rlim.rlim_max) &&
+ (lim->rlim.rlim_cur != RLIM_INFINITY ||
+ lim->rlim.rlim_max != RLIM_INFINITY))
+ errx(EXIT_FAILURE, _("the soft limit %s cannot exceed the hard limit"),
+ lim->desc->name);
+ new = &lim->rlim;
+ } else
+ old = &lim->rlim;
+
+ if (verbose && new) {
+ printf(_("New %s limit for pid %d: "), lim->desc->name,
+ pid ? pid : getpid());
+ if (new->rlim_cur == RLIM_INFINITY)
+ printf("<%s", _("unlimited"));
+ else
+ printf("<%ju", (uintmax_t)new->rlim_cur);
+
+ if (new->rlim_max == RLIM_INFINITY)
+ printf(":%s>\n", _("unlimited"));
+ else
+ printf(":%ju>\n", (uintmax_t)new->rlim_max);
+ }
+
+ if (prlimit(pid, lim->desc->resource, new, old) == -1)
+ err(EXIT_FAILURE, lim->modify ?
+ _("failed to set the %s resource limit") :
+ _("failed to get the %s resource limit"),
+ lim->desc->name);
+
+ if (lim->modify)
+ rem_prlim(lim); /* modify only; don't show */
+ }
+}
+
+static int get_range(char *str, rlim_t *soft, rlim_t *hard, int *found)
+{
+ char *end = NULL;
+
+ if (!str)
+ return 0;
+
+ *found = errno = 0;
+ *soft = *hard = RLIM_INFINITY;
+
+ if (!strcmp(str, INFINITY_STR)) { /* <unlimited> */
+ *found |= PRLIMIT_SOFT | PRLIMIT_HARD;
+ return 0;
+
+ }
+
+ if (*str == ':') { /* <:hard> */
+ str++;
+
+ if (strcmp(str, INFINITY_STR) != 0) {
+ *hard = strtoull(str, &end, 10);
+
+ if (errno || !end || *end || end == str)
+ return -1;
+ }
+ *found |= PRLIMIT_HARD;
+ return 0;
+
+ }
+
+ if (strncmp(str, INFINITY_STR, INFINITY_STRLEN) == 0) {
+ /* <unlimited> or <unlimited:> */
+ end = str + INFINITY_STRLEN;
+ } else {
+ /* <value> or <soft:> */
+ *hard = *soft = strtoull(str, &end, 10);
+ if (errno || !end || end == str)
+ return -1;
+ }
+
+ if (*end == ':' && !*(end + 1)) /* <soft:> */
+ *found |= PRLIMIT_SOFT;
+
+ else if (*end == ':') { /* <soft:hard> */
+ str = end + 1;
+
+ if (!strcmp(str, INFINITY_STR))
+ *hard = RLIM_INFINITY;
+ else {
+ end = NULL;
+ errno = 0;
+ *hard = strtoull(str, &end, 10);
+
+ if (errno || !end || *end || end == str)
+ return -1;
+ }
+ *found |= PRLIMIT_SOFT | PRLIMIT_HARD;
+
+ } else /* <value> */
+ *found |= PRLIMIT_SOFT | PRLIMIT_HARD;
+
+ return 0;
+}
+
+
+static int parse_prlim(struct rlimit *lim, char *ops, size_t id)
+{
+ rlim_t soft, hard;
+ int found = 0;
+
+ if (get_range(ops, &soft, &hard, &found))
+ errx(EXIT_FAILURE, _("failed to parse %s limit"),
+ prlimit_desc[id].name);
+
+ lim->rlim_cur = soft;
+ lim->rlim_max = hard;
+
+ return found;
+}
+
+static int add_prlim(char *ops, struct list_head *lims, size_t id)
+{
+ struct prlimit *lim = xcalloc(1, sizeof(*lim));
+
+ INIT_LIST_HEAD(&lim->lims);
+ lim->desc = &prlimit_desc[id];
+
+ if (ops)
+ lim->modify = parse_prlim(&lim->rlim, ops, id);
+
+ list_add_tail(&lim->lims, lims);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ struct list_head lims;
+
+ enum {
+ VERBOSE_OPTION = CHAR_MAX + 1,
+ RAW_OPTION,
+ NOHEADINGS_OPTION
+ };
+
+ static const struct option longopts[] = {
+ { "pid", required_argument, NULL, 'p' },
+ { "output", required_argument, NULL, 'o' },
+ { "as", optional_argument, NULL, 'v' },
+ { "core", optional_argument, NULL, 'c' },
+ { "cpu", optional_argument, NULL, 't' },
+ { "data", optional_argument, NULL, 'd' },
+ { "fsize", optional_argument, NULL, 'f' },
+ { "locks", optional_argument, NULL, 'x' },
+ { "memlock", optional_argument, NULL, 'l' },
+ { "msgqueue", optional_argument, NULL, 'q' },
+ { "nice", optional_argument, NULL, 'e' },
+ { "nofile", optional_argument, NULL, 'n' },
+ { "nproc", optional_argument, NULL, 'u' },
+ { "rss", optional_argument, NULL, 'm' },
+ { "rtprio", optional_argument, NULL, 'r' },
+ { "rttime", optional_argument, NULL, 'y' },
+ { "sigpending", optional_argument, NULL, 'i' },
+ { "stack", optional_argument, NULL, 's' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { "noheadings", no_argument, NULL, NOHEADINGS_OPTION },
+ { "raw", no_argument, NULL, RAW_OPTION },
+ { "verbose", no_argument, NULL, VERBOSE_OPTION },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ INIT_LIST_HEAD(&lims);
+
+ /*
+ * Something is very wrong if this doesn't succeed,
+ * assuming STACK is the last resource, of course.
+ */
+ assert(MAX_RESOURCES == STACK + 1);
+
+ while((opt = getopt_long(argc, argv,
+ "+c::d::e::f::i::l::m::n::q::r::s::t::u::v::x::y::p:o:vVh",
+ longopts, NULL)) != -1) {
+ switch(opt) {
+ case 'c':
+ add_prlim(optarg, &lims, CORE);
+ break;
+ case 'd':
+ add_prlim(optarg, &lims, DATA);
+ break;
+ case 'e':
+ add_prlim(optarg, &lims, NICE);
+ break;
+ case 'f':
+ add_prlim(optarg, &lims, FSIZE);
+ break;
+ case 'i':
+ add_prlim(optarg, &lims, SIGPENDING);
+ break;
+ case 'l':
+ add_prlim(optarg, &lims, MEMLOCK);
+ break;
+ case 'm':
+ add_prlim(optarg, &lims, RSS);
+ break;
+ case 'n':
+ add_prlim(optarg, &lims, NOFILE);
+ break;
+ case 'q':
+ add_prlim(optarg, &lims, MSGQUEUE);
+ break;
+ case 'r':
+ add_prlim(optarg, &lims, RTPRIO);
+ break;
+ case 's':
+ add_prlim(optarg, &lims, STACK);
+ break;
+ case 't':
+ add_prlim(optarg, &lims, CPU);
+ break;
+ case 'u':
+ add_prlim(optarg, &lims, NPROC);
+ break;
+ case 'v':
+ add_prlim(optarg, &lims, AS);
+ break;
+ case 'x':
+ add_prlim(optarg, &lims, LOCKS);
+ break;
+ case 'y':
+ add_prlim(optarg, &lims, RTTIME);
+ break;
+
+ case 'p':
+ if (pid)
+ errx(EXIT_FAILURE, _("option --pid may be specified only once"));
+ pid = strtos32_or_err(optarg, _("invalid PID argument"));
+ break;
+ case 'o':
+ ncolumns = string_to_idarray(optarg,
+ columns, ARRAY_SIZE(columns),
+ column_name_to_id);
+ if (ncolumns < 0)
+ return EXIT_FAILURE;
+ break;
+ case NOHEADINGS_OPTION:
+ no_headings = 1;
+ break;
+ case VERBOSE_OPTION:
+ verbose++;
+ break;
+ case RAW_OPTION:
+ raw = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ if (argc > optind && pid)
+ errx(EXIT_FAILURE, _("options --pid and COMMAND are mutually exclusive"));
+ if (!ncolumns) {
+ /* default columns */
+ columns[ncolumns++] = COL_RES;
+ columns[ncolumns++] = COL_HELP;
+ columns[ncolumns++] = COL_SOFT;
+ columns[ncolumns++] = COL_HARD;
+ columns[ncolumns++] = COL_UNITS;
+ }
+
+ scols_init_debug(0);
+
+ if (list_empty(&lims)) {
+ /* default is to print all resources */
+ size_t n;
+
+ for (n = 0; n < MAX_RESOURCES; n++)
+ add_prlim(NULL, &lims, n);
+ }
+
+ do_prlimit(&lims);
+
+ if (!list_empty(&lims))
+ show_limits(&lims);
+
+ if (argc > optind) {
+ /* prlimit [options] COMMAND */
+ execvp(argv[optind], &argv[optind]);
+ errexec(argv[optind]);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/readprofile.8 b/sys-utils/readprofile.8
new file mode 100644
index 0000000..b987abb
--- /dev/null
+++ b/sys-utils/readprofile.8
@@ -0,0 +1,151 @@
+.TH READPROFILE "8" "October 2011" "util-linux" "System Administration"
+.SH NAME
+readprofile \- read kernel profiling information
+.SH SYNOPSIS
+.B readprofile
+[options]
+.SH VERSION
+This manpage documents version 2.0 of the program.
+.SH DESCRIPTION
+The
+.B readprofile
+command uses the
+.I /proc/profile
+information to print ascii data on standard output. The output is
+organized in three columns: the first is the number of clock ticks,
+the second is the name of the C function in the kernel where those
+many ticks occurred, and the third is the normalized `load' of the
+procedure, calculated as a ratio between the number of ticks and the
+length of the procedure. The output is filled with blanks to ease
+readability.
+.SH OPTIONS
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Print all symbols in the mapfile. By default the procedures with
+reported ticks are not printed.
+.TP
+\fB\-b\fR, \fB\-\-histbin\fR
+Print individual histogram-bin counts.
+.TP
+\fB\-i\fR, \fB\-\-info\fR
+Info. This makes
+.B readprofile
+only print the profiling step used by the kernel. The profiling step
+is the resolution of the profiling buffer, and is chosen during
+kernel configuration (through `make config'), or in the kernel's
+command line. If the
+.B \-t
+(terse) switch is used together with
+.B \-i
+only the decimal number is printed.
+.TP
+\fB\-m\fR, \fB\-\-mapfile\fR \fImapfile\fR
+Specify a mapfile, which by default is
+.IR /usr/src/linux/System.map .
+You should specify the map file on cmdline if your current kernel
+isn't the last one you compiled, or if you keep System.map elsewhere.
+If the name of the map file ends with `.gz' it is decompressed on the
+fly.
+.TP
+\fB\-M\fR, \fB\-\-multiplier\fR \fImultiplier\fR
+On some architectures it is possible to alter the frequency at which
+the kernel delivers profiling interrupts to each CPU. This option
+allows you to set the frequency, as a multiplier of the system clock
+frequency, HZ. Linux 2.6.16 dropped multiplier support for most systems.
+This option also resets the profiling buffer, and requires superuser
+privileges.
+.TP
+\fB\-p\fR, \fB\-\-profile\fR \fIpro-file\fR
+Specify a different profiling buffer, which by default is
+.IR /proc/profile .
+Using a different pro-file is useful if you want to `freeze' the
+kernel profiling at some time and read it later. The
+.I /proc/profile
+file can be copied using `cat' or `cp'. There is no more support for
+compressed profile buffers, like in
+.B readprofile-1.1,
+because the program needs to know the size of the buffer in advance.
+.TP
+\fB\-r\fR, \fB\-\-reset\fR
+Reset the profiling buffer. This can only be invoked by root,
+because
+.I /proc/profile
+is readable by everybody but writable only by the superuser.
+However, you can make
+.B readprofile
+set-user-ID 0, in order to reset the buffer without gaining privileges.
+.TP
+\fB\-s, \fB\-\-counters\fR
+Print individual counters within functions.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Verbose. The output is organized in four columns and filled with
+blanks. The first column is the RAM address of a kernel function,
+the second is the name of the function, the third is the number of
+clock ticks and the last is the normalized load.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH FILES
+.nf
+/proc/profile A binary snapshot of the profiling buffer.
+/usr/src/linux/System.map The symbol table for the kernel.
+/usr/src/linux/* The program being profiled :-)
+.fi
+.SH BUGS
+.B readprofile
+only works with a 1.3.x or newer kernel, because
+.I /proc/profile
+changed in the step from 1.2 to 1.3
+.LP
+This program only works with ELF kernels. The change for a.out
+kernels is trivial, and left as an exercise to the a.out user.
+.LP
+To enable profiling, the kernel must be rebooted, because no
+profiling module is available, and it wouldn't be easy to build. To
+enable profiling, you can specify "profile=2" (or another number) on
+the kernel commandline. The number you specify is the two-exponent
+used as profiling step.
+.LP
+Profiling is disabled when interrupts are inhibited. This means that
+many profiling ticks happen when interrupts are re-enabled. Watch
+out for misleading information.
+.SH EXAMPLE
+Browse the profiling buffer ordering by clock ticks:
+.nf
+ readprofile | sort \-nr | less
+
+.fi
+Print the 20 most loaded procedures:
+.nf
+ readprofile | sort \-nr +2 | head \-20
+
+.fi
+Print only filesystem profile:
+.nf
+ readprofile | grep _ext2
+
+.fi
+Look at all the kernel information, with ram addresses:
+.nf
+ readprofile \-av | less
+
+.fi
+Browse a `frozen' profile buffer for a non current kernel:
+.nf
+ readprofile \-p ~/profile.freeze \-m /zImage.map.gz
+
+.fi
+Request profiling at 2kHz per CPU, and reset the profiling buffer:
+.nf
+ sudo readprofile \-M 20
+.fi
+.SH AVAILABILITY
+The readprofile command is part of the util-linux package and is
+available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/readprofile.c b/sys-utils/readprofile.c
new file mode 100644
index 0000000..1323864
--- /dev/null
+++ b/sys-utils/readprofile.c
@@ -0,0 +1,409 @@
+/*
+ * readprofile.c - used to read /proc/profile
+ *
+ * Copyright (C) 1994,1996 Alessandro Rubini (rubini@ipvvis.unipv.it)
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ * 1999-09-01 Stephane Eranian <eranian@cello.hpl.hp.com>
+ * - 64bit clean patch
+ * 3Feb2001 Andrew Morton <andrewm@uow.edu.au>
+ * - -M option to write profile multiplier.
+ * 2001-11-07 Werner Almesberger <wa@almesberger.net>
+ * - byte order auto-detection and -n option
+ * 2001-11-09 Werner Almesberger <wa@almesberger.net>
+ * - skip step size (index 0)
+ * 2002-03-09 John Levon <moz@compsoc.man.ac.uk>
+ * - make maplineno do something
+ * 2002-11-28 Mads Martin Joergensen +
+ * - also try /boot/System.map-`uname -r`
+ * 2003-04-09 Werner Almesberger <wa@almesberger.net>
+ * - fixed off-by eight error and improved heuristics in byte order detection
+ * 2003-08-12 Nikita Danilov <Nikita@Namesys.COM>
+ * - added -s option; example of use:
+ * "readprofile -s -m /boot/System.map-test | grep __d_lookup | sort -n -k3"
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "closestream.h"
+
+#define S_LEN 128
+
+/* These are the defaults */
+static char defaultmap[]="/boot/System.map";
+static char defaultpro[]="/proc/profile";
+
+static FILE *myopen(char *name, char *mode, int *flag)
+{
+ int len = strlen(name);
+
+ if (!strcmp(name + len - 3, ".gz")) {
+ FILE *res;
+ char *cmdline = xmalloc(len + 6);
+ sprintf(cmdline, "zcat %s", name);
+ res = popen(cmdline, mode);
+ free(cmdline);
+ *flag = 1;
+ return res;
+ }
+ *flag = 0;
+ return fopen(name, mode);
+}
+
+#ifndef BOOT_SYSTEM_MAP
+#define BOOT_SYSTEM_MAP "/boot/System.map-"
+#endif
+
+static char *boot_uname_r_str(void)
+{
+ struct utsname uname_info;
+ char *s;
+ size_t len;
+
+ if (uname(&uname_info))
+ return "";
+ len = strlen(BOOT_SYSTEM_MAP) + strlen(uname_info.release) + 1;
+ s = xmalloc(len);
+ strcpy(s, BOOT_SYSTEM_MAP);
+ strcat(s, uname_info.release);
+ return s;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display kernel profiling information.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fprintf(out,
+ _(" -m, --mapfile <mapfile> (defaults: \"%s\" and\n"), defaultmap);
+ fprintf(out,
+ _(" \"%s\")\n"), boot_uname_r_str());
+ fprintf(out,
+ _(" -p, --profile <pro-file> (default: \"%s\")\n"), defaultpro);
+ fputs(_(" -M, --multiplier <mult> set the profiling multiplier to <mult>\n"), out);
+ fputs(_(" -i, --info print only info about the sampling step\n"), out);
+ fputs(_(" -v, --verbose print verbose data\n"), out);
+ fputs(_(" -a, --all print all symbols, even if count is 0\n"), out);
+ fputs(_(" -b, --histbin print individual histogram-bin counts\n"), out);
+ fputs(_(" -s, --counters print individual counters within functions\n"), out);
+ fputs(_(" -r, --reset reset all the counters (root only)\n"), out);
+ fputs(_(" -n, --no-auto disable byte order auto-detection\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(27));
+ printf(USAGE_MAN_TAIL("readprofile(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ FILE *map;
+ int proFd;
+ char *mapFile, *proFile, *mult = NULL;
+ size_t len = 0, indx = 1;
+ unsigned long long add0 = 0;
+ unsigned int step;
+ unsigned int *buf, total, fn_len;
+ unsigned long long fn_add = 0, next_add; /* current and next address */
+ char fn_name[S_LEN], next_name[S_LEN]; /* current and next name */
+ char mode[8];
+ int c;
+ ssize_t rc;
+ int optAll = 0, optInfo = 0, optReset = 0, optVerbose = 0, optNative = 0;
+ int optBins = 0, optSub = 0;
+ char mapline[S_LEN];
+ int maplineno = 1;
+ int popenMap; /* flag to tell if popen() has been used */
+ int header_printed;
+
+ static const struct option longopts[] = {
+ {"mapfile", required_argument, NULL, 'm'},
+ {"profile", required_argument, NULL, 'p'},
+ {"multiplier", required_argument, NULL, 'M'},
+ {"info", no_argument, NULL, 'i'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"all", no_argument, NULL, 'a'},
+ {"histbin", no_argument, NULL, 'b'},
+ {"counters", no_argument, NULL, 's'},
+ {"reset", no_argument, NULL, 'r'},
+ {"no-auto", no_argument, NULL, 'n'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+#define next (current^1)
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ proFile = defaultpro;
+ mapFile = defaultmap;
+
+ while ((c = getopt_long(argc, argv, "m:p:M:ivabsrnVh", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'm':
+ mapFile = optarg;
+ break;
+ case 'n':
+ optNative++;
+ break;
+ case 'p':
+ proFile = optarg;
+ break;
+ case 'a':
+ optAll++;
+ break;
+ case 'b':
+ optBins++;
+ break;
+ case 's':
+ optSub++;
+ break;
+ case 'i':
+ optInfo++;
+ break;
+ case 'M':
+ mult = optarg;
+ break;
+ case 'r':
+ optReset++;
+ break;
+ case 'v':
+ optVerbose++;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optReset || mult) {
+ int multiplier, fd, to_write;
+
+ /* When writing the multiplier, if the length of the
+ * write is not sizeof(int), the multiplier is not
+ * changed. */
+ if (mult) {
+ multiplier = strtoul(mult, NULL, 10);
+ to_write = sizeof(int);
+ } else {
+ multiplier = 0;
+ /* sth different from sizeof(int) */
+ to_write = 1;
+ }
+ /* try to become root, just in case */
+ ignore_result( setuid(0) );
+ fd = open(defaultpro, O_WRONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, "%s", defaultpro);
+ if (write(fd, &multiplier, to_write) != to_write)
+ err(EXIT_FAILURE, _("error writing %s"), defaultpro);
+ close(fd);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Use an fd for the profiling buffer, to skip stdio overhead */
+ if (((proFd = open(proFile, O_RDONLY)) < 0)
+ || ((int)(len = lseek(proFd, 0, SEEK_END)) < 0)
+ || (lseek(proFd, 0, SEEK_SET) < 0))
+ err(EXIT_FAILURE, "%s", proFile);
+ if (!len)
+ errx(EXIT_FAILURE, "%s: %s", proFile, _("input file is empty"));
+
+ buf = xmalloc(len);
+
+ rc = read(proFd, buf, len);
+ if (rc < 0 || (size_t) rc != len)
+ err(EXIT_FAILURE, "%s", proFile);
+ close(proFd);
+
+ if (!optNative) {
+ int entries = len / sizeof(*buf);
+ int big = 0, small = 0;
+ unsigned *p;
+ size_t i;
+
+ for (p = buf + 1; p < buf + entries; p++) {
+ if (*p & ~0U << ((unsigned) sizeof(*buf) * 4U))
+ big++;
+ if (*p & ((1U << ((unsigned) sizeof(*buf) * 4U)) - 1U))
+ small++;
+ }
+ if (big > small) {
+ warnx(_("Assuming reversed byte order. "
+ "Use -n to force native byte order."));
+ for (p = buf; p < buf + entries; p++)
+ for (i = 0; i < sizeof(*buf) / 2; i++) {
+ unsigned char *b = (unsigned char *)p;
+ unsigned char tmp;
+ tmp = b[i];
+ b[i] = b[sizeof(*buf) - i - 1];
+ b[sizeof(*buf) - i - 1] = tmp;
+ }
+ }
+ }
+
+ step = buf[0];
+ if (optInfo) {
+ printf(_("Sampling_step: %u\n"), step);
+ exit(EXIT_SUCCESS);
+ }
+
+ total = 0;
+
+ map = myopen(mapFile, "r", &popenMap);
+ if (map == NULL && mapFile == defaultmap) {
+ mapFile = boot_uname_r_str();
+ map = myopen(mapFile, "r", &popenMap);
+ }
+ if (map == NULL)
+ err(EXIT_FAILURE, "%s", mapFile);
+
+ while (fgets(mapline, S_LEN, map)) {
+ if (sscanf(mapline, "%llx %7[^\n ] %127[^\n ]", &fn_add, mode, fn_name) != 3)
+ errx(EXIT_FAILURE, _("%s(%i): wrong map line"), mapFile,
+ maplineno);
+ /* only elf works like this */
+ if (!strcmp(fn_name, "_stext") || !strcmp(fn_name, "__stext")) {
+ add0 = fn_add;
+ break;
+ }
+ maplineno++;
+ }
+
+ if (!add0)
+ errx(EXIT_FAILURE, _("can't find \"_stext\" in %s"), mapFile);
+
+ /*
+ * Main loop.
+ */
+ while (fgets(mapline, S_LEN, map)) {
+ unsigned int this = 0;
+ int done = 0;
+
+ if (sscanf(mapline, "%llx %7[^\n ] %127[^\n ]", &next_add, mode, next_name) != 3)
+ errx(EXIT_FAILURE, _("%s(%i): wrong map line"), mapFile,
+ maplineno);
+ header_printed = 0;
+
+ /* the kernel only profiles up to _etext */
+ if (!strcmp(next_name, "_etext") ||
+ !strcmp(next_name, "__etext"))
+ done = 1;
+ else {
+ /* ignore any LEADING (before a '[tT]' symbol
+ * is found) Absolute symbols and __init_end
+ * because some architectures place it before
+ * .text section */
+ if ((*mode == 'A' || *mode == '?')
+ && (total == 0 || !strcmp(next_name, "__init_end")))
+ continue;
+ if (*mode != 'T' && *mode != 't' &&
+ *mode != 'W' && *mode != 'w')
+ break; /* only text is profiled */
+ }
+
+ if (indx >= len / sizeof(*buf))
+ errx(EXIT_FAILURE,
+ _("profile address out of range. Wrong map file?"));
+
+ while (indx < (next_add - add0) / step) {
+ if (optBins && (buf[indx] || optAll)) {
+ if (!header_printed) {
+ printf("%s:\n", fn_name);
+ header_printed = 1;
+ }
+ printf("\t%llx\t%u\n", (indx - 1) * step + add0,
+ buf[indx]);
+ }
+ this += buf[indx++];
+ }
+ total += this;
+
+ if (optBins) {
+ if (optVerbose || this > 0)
+ printf(" total\t\t\t\t%u\n", this);
+ } else if ((this || optAll) &&
+ (fn_len = next_add - fn_add) != 0) {
+ if (optVerbose)
+ printf("%016llx %-40s %6u %8.4f\n", fn_add,
+ fn_name, this, this / (double)fn_len);
+ else
+ printf("%6u %-40s %8.4f\n",
+ this, fn_name, this / (double)fn_len);
+ if (optSub) {
+ unsigned long long scan;
+
+ for (scan = (fn_add - add0) / step + 1;
+ scan < (next_add - add0) / step;
+ scan++) {
+ unsigned long long addr;
+ addr = (scan - 1) * step + add0;
+ printf("\t%#llx\t%s+%#llx\t%u\n",
+ addr, fn_name, addr - fn_add,
+ buf[scan]);
+ }
+ }
+ }
+
+ fn_add = next_add;
+ strcpy(fn_name, next_name);
+
+ maplineno++;
+ if (done)
+ break;
+ }
+
+ /* clock ticks, out of kernel text - probably modules */
+ printf("%6u %s\n", buf[len / sizeof(*buf) - 1], "*unknown*");
+
+ /* trailer */
+ if (optVerbose)
+ printf("%016x %-40s %6u %8.4f\n",
+ 0, "total", total, total / (double)(fn_add - add0));
+ else
+ printf("%6u %-40s %8.4f\n",
+ total, _("total"), total / (double)(fn_add - add0));
+
+ popenMap ? pclose(map) : fclose(map);
+ exit(EXIT_SUCCESS);
+}
diff --git a/sys-utils/renice.1 b/sys-utils/renice.1
new file mode 100644
index 0000000..c39aa1f
--- /dev/null
+++ b/sys-utils/renice.1
@@ -0,0 +1,119 @@
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)renice.8 8.1 (Berkeley) 6/9/93
+.\"
+.TH RENICE "1" "July 2014" "util-linux" "User Commands"
+.SH NAME
+renice \- alter priority of running processes
+.SH SYNOPSIS
+.B renice
+.RB [ \-n ]
+.I priority
+.RB [ \-g | \-p | \-u ]
+.IR identifier ...
+.SH DESCRIPTION
+.B renice
+alters the scheduling priority of one or more running processes. The
+first argument is the \fIpriority\fR value to be used.
+The other arguments are interpreted as process IDs (by default),
+process group IDs, user IDs, or user names.
+.BR renice 'ing
+a process group causes all processes in the process group to have their
+scheduling priority altered.
+.BR renice 'ing
+a user causes all processes owned by the user to have their scheduling
+priority altered.
+.SH OPTIONS
+.TP
+.BR \-n , " \-\-priority " \fIpriority\fR
+Specify the scheduling
+.I priority
+to be used for the process, process group, or user. Use of the option
+.BR \-n " or " \-\-priority
+is optional, but when used it must be the first argument.
+.TP
+.BR \-g ", " \-\-pgrp
+Interpret the succeeding arguments as process group IDs.
+.TP
+.BR \-p ", " \-\-pid
+Interpret the succeeding arguments as process IDs
+(the default).
+.TP
+.BR \-u ", " \-\-user
+Interpret the succeeding arguments as usernames or UIDs.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH FILES
+.TP
+.I /etc/passwd
+to map user names to user IDs
+.SH NOTES
+Users other than the superuser may only alter the priority of processes they
+own. Furthermore, an unprivileged user can only
+.I increase
+the ``nice value'' (i.e., choose a lower priority)
+and such changes are irreversible unless (since Linux 2.6.12)
+the user has a suitable ``nice'' resource limit (see
+.BR ulimit (1p)
+and
+.BR getrlimit (2)).
+
+The superuser may alter the priority of any process and set the priority to any
+value in the range \-20 to 19.
+Useful priorities are: 19 (the affected processes will run only when nothing
+else in the system wants to), 0 (the ``base'' scheduling priority), anything
+negative (to make things go very fast).
+.SH HISTORY
+The
+.B renice
+command appeared in 4.0BSD.
+.SH EXAMPLES
+The following command would change the priority of the processes with
+PIDs 987 and 32, plus all processes owned by the users daemon and root:
+.TP
+.B " renice" +1 987 \-u daemon root \-p 32
+.SH SEE ALSO
+.BR nice (1),
+.BR chrt (1),
+.BR getpriority (2),
+.BR setpriority (2),
+.BR credentials (7),
+.BR sched (7)
+.SH AVAILABILITY
+The renice command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/renice.c b/sys-utils/renice.c
new file mode 100644
index 0000000..080b86e
--- /dev/null
+++ b/sys-utils/renice.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 1983, 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. 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.
+ */
+
+ /* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "nls.h"
+#include "c.h"
+#include "closestream.h"
+
+static const char *idtype[] = {
+ [PRIO_PROCESS] = N_("process ID"),
+ [PRIO_PGRP] = N_("process group ID"),
+ [PRIO_USER] = N_("user ID"),
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %1$s [-n] <priority> [-p|--pid] <pid>...\n"
+ " %1$s [-n] <priority> -g|--pgrp <pgid>...\n"
+ " %1$s [-n] <priority> -u|--user <user>...\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Alter the priority of running processes.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -n, --priority <num> specify the nice value\n"), out);
+ fputs(_(" -p, --pid interpret arguments as process ID (default)\n"), out);
+ fputs(_(" -g, --pgrp interpret arguments as process group ID\n"), out);
+ fputs(_(" -u, --user interpret arguments as username or user ID\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+ printf(USAGE_MAN_TAIL("renice(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int getprio(const int which, const int who, int *prio)
+{
+ errno = 0;
+ *prio = getpriority(which, who);
+ if (*prio == -1 && errno) {
+ warn(_("failed to get priority for %d (%s)"), who, idtype[which]);
+ return -errno;
+ }
+ return 0;
+}
+
+static int donice(const int which, const int who, const int prio)
+{
+ int oldprio, newprio;
+
+ if (getprio(which, who, &oldprio) != 0)
+ return 1;
+ if (setpriority(which, who, prio) < 0) {
+ warn(_("failed to set priority for %d (%s)"), who, idtype[which]);
+ return 1;
+ }
+ if (getprio(which, who, &newprio) != 0)
+ return 1;
+ printf(_("%d (%s) old priority %d, new priority %d\n"),
+ who, idtype[which], oldprio, newprio);
+ return 0;
+}
+
+/*
+ * Change the priority (the nice value) of processes
+ * or groups of processes which are already running.
+ */
+int main(int argc, char **argv)
+{
+ int which = PRIO_PROCESS;
+ int who = 0, prio, errs = 0;
+ char *endptr = NULL;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ argc--;
+ argv++;
+
+ if (argc == 1) {
+ if (strcmp(*argv, "-h") == 0 ||
+ strcmp(*argv, "--help") == 0)
+ usage();
+
+ if (strcmp(*argv, "-v") == 0 ||
+ strcmp(*argv, "-V") == 0 ||
+ strcmp(*argv, "--version") == 0)
+ print_version(EXIT_SUCCESS);
+ }
+
+ if (*argv && (strcmp(*argv, "-n") == 0 || strcmp(*argv, "--priority") == 0)) {
+ argc--;
+ argv++;
+ }
+
+ if (argc < 2 || !*argv) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ prio = strtol(*argv, &endptr, 10);
+ if (*endptr) {
+ warnx(_("invalid priority '%s'"), *argv);
+ errtryhelp(EXIT_FAILURE);
+ }
+ argc--;
+ argv++;
+
+ for (; argc > 0; argc--, argv++) {
+ if (strcmp(*argv, "-g") == 0 || strcmp(*argv, "--pgrp") == 0) {
+ which = PRIO_PGRP;
+ continue;
+ }
+ if (strcmp(*argv, "-u") == 0 || strcmp(*argv, "--user") == 0) {
+ which = PRIO_USER;
+ continue;
+ }
+ if (strcmp(*argv, "-p") == 0 || strcmp(*argv, "--pid") == 0) {
+ which = PRIO_PROCESS;
+ continue;
+ }
+ if (which == PRIO_USER) {
+ struct passwd *pwd = getpwnam(*argv);
+
+ if (pwd != NULL)
+ who = pwd->pw_uid;
+ else
+ who = strtol(*argv, &endptr, 10);
+ if (who < 0 || *endptr) {
+ warnx(_("unknown user %s"), *argv);
+ errs = 1;
+ continue;
+ }
+ } else {
+ who = strtol(*argv, &endptr, 10);
+ if (who < 0 || *endptr) {
+ /* TRANSLATORS: The first %s is one of the above
+ * three ID names. Read: "bad value for %s: %s" */
+ warnx(_("bad %s value: %s"), idtype[which], *argv);
+ errs = 1;
+ continue;
+ }
+ }
+ errs |= donice(which, who, prio);
+ }
+ return errs != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/sys-utils/rfkill.8 b/sys-utils/rfkill.8
new file mode 100644
index 0000000..373af79
--- /dev/null
+++ b/sys-utils/rfkill.8
@@ -0,0 +1,120 @@
+.\" -*- nroff -*-
+.TH RFKILL "8" "2017-07-06" "util-linux" "System Administration"
+.SH NAME
+rfkill \- tool for enabling and disabling wireless devices
+.SH SYNOPSIS
+.B rfkill
+.RI [ options ]
+.RI [ command ]
+.RI [ id|type \ ...]
+
+.SH DESCRIPTION
+.B rfkill
+lists, enabling and disabling wireless devices.
+
+The command "list" output format is deprecated and maintained for backward
+compatibility only. The new output format is the default when no command is
+specified or when the option \fB\-\-output\fR is used.
+
+The default output is subject to change. So whenever possible, you should
+avoid using default outputs in your scripts. Always explicitly define expected
+columns by using the \fB\-\-output\fR option together with a columns list in
+environments where a stable output is required.
+
+
+.SH OPTIONS
+.TP
+\fB\-J\fR, \fB\-\-json\fR
+Use JSON output format.
+.TP
+\fB\-n\fR, \fB\-\-noheadings\fR
+Do not print a header line.
+.TP
+\fB\-o\fR, \fB\-\-output\fR
+Specify which output columns to print. Use \-\-help to get a list of
+available columns.
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+Use the raw output format.
+.TP
+.B \-\-help
+Display help text and exit.
+.TP
+.B \-\-version
+Display version information and exit.
+.SH COMMANDS
+.TP
+.B help
+Display help text and exit.
+.TP
+.B event
+Listen for rfkill events and display them on stdout.
+.TP
+\fBlist \fR[\fIid\fR|\fItype\fR ...]
+List the current state of all available devices. The command output format is deprecated, see the section DESCRIPTION.
+It is a good idea to check with
+.B list
+command
+.IR id " or " type
+scope is appropriate before setting
+.BR block " or " unblock .
+Special
+.I all
+type string will match everything. Use of multiple
+.IR id " or " type
+arguments is supported.
+.TP
+\fBblock \fBid\fR|\fBtype\fR [...]
+Disable the corresponding device.
+.TP
+\fBunblock \fBid\fR|\fBtype\fR [...]
+Enable the corresponding device. If the device is hard\-blocked, for example
+via a hardware switch, it will remain unavailable though it is now
+soft\-unblocked.
+.SH EXAMPLE
+rfkill --output ID,TYPE
+.br
+rfkill block all
+.br
+rfkill unblock wlan
+.br
+rfkill block bluetooth uwb wimax wwan gps fm nfc
+.SH AUTHORS
+.B rfkill
+was originally written by
+.MT johannes@\:sipsolutions.\:net
+Johannes Berg
+.ME
+and
+.MT marcel@\:holtmann.\:org
+Marcel Holtmann
+.ME .
+The code has been later modified by
+.MT kerolasa@\:iki.\:fi
+Sami Kerola
+.ME
+and
+.MT kzak@\:redhat.\:com
+Karel Zak
+.ME
+for util-linux project.
+.PP
+This manual page was written by
+.MT linux@\:youmustbejoking.\:demon.\:co.uk
+Darren Salt
+.ME ,
+for the Debian project (and may be used by others).
+.SH SEE ALSO
+.BR powertop (8),
+.BR systemd-rfkill (8),
+.UR https://\:git.\:kernel.\:org/\:pub/\:scm/\:linux/\:kernel/\:git/\:torvalds/\:linux.git/\:tree/\:Documentation/\:driver-api/\:rfkill.rst
+Linux kernel documentation
+.UE
+.SH AVAILABILITY
+The rfkill command is part of the util\-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util\-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/rfkill.c b/sys-utils/rfkill.c
new file mode 100644
index 0000000..b00bf75
--- /dev/null
+++ b/sys-utils/rfkill.c
@@ -0,0 +1,751 @@
+/*
+ * /dev/rfkill userspace tool
+ *
+ * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2009 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright 2009 Tim Gardner <tim.gardner@canonical.com>
+ * Copyright 2017 Sami Kerola <kerolasa@iki.fi>
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ *
+ * Permission to use, copy, modify, and/or 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 <ctype.h>
+#include <getopt.h>
+#include <libsmartcols.h>
+#include <linux/rfkill.h>
+#include <poll.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "timeutils.h"
+#include "widechar.h"
+#include "xalloc.h"
+
+
+/*
+ * NFC supported by kernel since v3.10 (year 2013); FM and another types are from
+ * year 2009 (2.6.33) or older.
+ */
+#ifndef RFKILL_TYPE_NFC
+# ifndef RFKILL_TYPE_FM
+# define RFKILL_TYPE_FM RFKILL_TYPE_GPS + 1
+# endif
+# define RFKILL_TYPE_NFC RFKILL_TYPE_FM + 1
+# undef NUM_RFKILL_TYPES
+# define NUM_RFKILL_TYPES RFKILL_TYPE_NFC + 1
+#endif
+
+struct rfkill_type_str {
+ enum rfkill_type type; /* ID */
+ const char *name; /* generic name */
+ const char *desc; /* human readable name */
+};
+
+static const struct rfkill_type_str rfkill_type_strings[] = {
+ { .type = RFKILL_TYPE_ALL, .name = "all" },
+ { .type = RFKILL_TYPE_WLAN, .name = "wlan", .desc = "Wireless LAN" },
+ { .type = RFKILL_TYPE_WLAN, .name = "wifi" }, /* alias */
+ { .type = RFKILL_TYPE_BLUETOOTH, .name = "bluetooth", .desc = "Bluetooth" },
+ { .type = RFKILL_TYPE_UWB, .name = "uwb", .desc = "Ultra-Wideband" },
+ { .type = RFKILL_TYPE_UWB, .name = "ultrawideband" }, /* alias */
+ { .type = RFKILL_TYPE_WIMAX, .name = "wimax", .desc = "WiMAX" },
+ { .type = RFKILL_TYPE_WWAN, .name = "wwan", .desc = "Wireless WAN" },
+ { .type = RFKILL_TYPE_GPS, .name = "gps", .desc = "GPS" },
+ { .type = RFKILL_TYPE_FM, .name = "fm", .desc = "FM" },
+ { .type = RFKILL_TYPE_NFC, .name = "nfc", .desc = "NFC" },
+ { .type = NUM_RFKILL_TYPES, .name = NULL }
+};
+
+struct rfkill_id {
+ union {
+ enum rfkill_type type;
+ uint32_t index;
+ };
+ enum {
+ RFKILL_IS_INVALID,
+ RFKILL_IS_TYPE,
+ RFKILL_IS_INDEX,
+ RFKILL_IS_ALL
+ } result;
+};
+
+/* supported actions */
+enum {
+ ACT_LIST,
+ ACT_HELP,
+ ACT_EVENT,
+ ACT_BLOCK,
+ ACT_UNBLOCK,
+
+ ACT_LIST_OLD
+};
+
+static char *rfkill_actions[] = {
+ [ACT_LIST] = "list",
+ [ACT_HELP] = "help",
+ [ACT_EVENT] = "event",
+ [ACT_BLOCK] = "block",
+ [ACT_UNBLOCK] = "unblock"
+};
+
+/* column IDs */
+enum {
+ COL_DEVICE,
+ COL_ID,
+ COL_TYPE,
+ COL_DESC,
+ COL_SOFT,
+ COL_HARD
+};
+
+/* column names */
+struct colinfo {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+};
+
+/* columns descriptions */
+static const struct colinfo infos[] = {
+ [COL_DEVICE] = {"DEVICE", 0, 0, N_("kernel device name")},
+ [COL_ID] = {"ID", 2, SCOLS_FL_RIGHT, N_("device identifier value")},
+ [COL_TYPE] = {"TYPE", 0, 0, N_("device type name that can be used as identifier")},
+ [COL_DESC] = {"TYPE-DESC", 0, 0, N_("device type description")},
+ [COL_SOFT] = {"SOFT", 0, SCOLS_FL_RIGHT, N_("status of software block")},
+ [COL_HARD] = {"HARD", 0, SCOLS_FL_RIGHT, N_("status of hardware block")}
+};
+
+static int columns[ARRAY_SIZE(infos) * 2];
+static size_t ncolumns;
+
+struct control {
+ struct libscols_table *tb;
+ unsigned int
+ json:1,
+ no_headings:1,
+ raw:1;
+};
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ assert(name);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int get_column_id(size_t num)
+{
+ assert(num < ncolumns);
+ assert(columns[num] < (int)ARRAY_SIZE(infos));
+ return columns[num];
+}
+
+static const struct colinfo *get_column_info(int num)
+{
+ return &infos[get_column_id(num)];
+}
+
+static int string_to_action(const char *str)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(rfkill_actions); i++)
+ if (strcmp(str, rfkill_actions[i]) == 0)
+ return i;
+
+ return -EINVAL;
+}
+
+static int rfkill_ro_open(int nonblock)
+{
+ int fd;
+
+ fd = open(_PATH_DEV_RFKILL, O_RDONLY);
+ if (fd < 0) {
+ warn(_("cannot open %s"), _PATH_DEV_RFKILL);
+ return -errno;
+ }
+
+ if (nonblock && fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
+ warn(_("cannot set non-blocking %s"), _PATH_DEV_RFKILL);
+ close(fd);
+ return -errno;
+ }
+
+ return fd;
+}
+
+/* returns: 0 success, 1 read again, < 0 error */
+static int rfkill_read_event(int fd, struct rfkill_event *event)
+{
+ ssize_t len = read(fd, event, sizeof(*event));
+
+ if (len < 0) {
+ if (errno == EAGAIN)
+ return 1;
+ warn(_("cannot read %s"), _PATH_DEV_RFKILL);
+ return -errno;
+ }
+
+ if (len < RFKILL_EVENT_SIZE_V1) {
+ warnx(_("wrong size of rfkill event: %zu < %d"), len, RFKILL_EVENT_SIZE_V1);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static int rfkill_event(void)
+{
+ struct rfkill_event event;
+ struct timeval tv;
+ char date_buf[ISO_BUFSIZ];
+ struct pollfd p;
+ int fd, n;
+
+ fd = rfkill_ro_open(0);
+ if (fd < 0)
+ return -errno;
+
+ memset(&p, 0, sizeof(p));
+ p.fd = fd;
+ p.events = POLLIN | POLLHUP;
+
+ /* interrupted by signal only */
+ while (1) {
+ int rc = 1; /* recover-able error */
+
+ n = poll(&p, 1, -1);
+ if (n < 0) {
+ warn(_("failed to poll %s"), _PATH_DEV_RFKILL);
+ goto failed;
+ }
+
+ if (n)
+ rc = rfkill_read_event(fd, &event);
+ if (rc < 0)
+ goto failed;
+ if (rc)
+ continue;
+
+ gettimeofday(&tv, NULL);
+ strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA, date_buf,
+ sizeof(date_buf));
+ printf("%s: idx %u type %u op %u soft %u hard %u\n",
+ date_buf,
+ event.idx, event.type, event.op, event.soft, event.hard);
+ fflush(stdout);
+ }
+
+failed:
+ close(fd);
+ return -1;
+}
+
+static const char *get_sys_attr(uint32_t idx, const char *attr)
+{
+ static char name[128];
+ char path[PATH_MAX];
+ FILE *f;
+ char *p;
+
+ snprintf(path, sizeof(path), _PATH_SYS_RFKILL "/rfkill%u/%s", idx, attr);
+ f = fopen(path, "r");
+ if (!f)
+ goto done;
+ if (!fgets(name, sizeof(name), f))
+ goto done;
+ p = strchr(name, '\n');
+ if (p)
+ *p = '\0';
+done:
+ if (f)
+ fclose(f);
+ return name;
+}
+
+static struct rfkill_id rfkill_id_to_type(const char *s)
+{
+ const struct rfkill_type_str *p;
+ struct rfkill_id ret;
+
+ if (islower(*s)) {
+ for (p = rfkill_type_strings; p->name != NULL; p++) {
+ if (!strcmp(s, p->name)) {
+ ret.type = p->type;
+ if (!strcmp(s, "all"))
+ ret.result = RFKILL_IS_ALL;
+ else
+ ret.result = RFKILL_IS_TYPE;
+ return ret;
+ }
+ }
+ } else if (isdigit(*s)) {
+ /* assume a numeric character implies an index. */
+ char filename[64];
+
+ ret.index = strtou32_or_err(s, _("invalid identifier"));
+ snprintf(filename, sizeof(filename) - 1,
+ _PATH_SYS_RFKILL "/rfkill%" PRIu32 "/name", ret.index);
+ if (access(filename, F_OK) == 0)
+ ret.result = RFKILL_IS_INDEX;
+ else
+ ret.result = RFKILL_IS_INVALID;
+ return ret;
+ }
+
+ ret.result = RFKILL_IS_INVALID;
+ return ret;
+}
+
+static const char *rfkill_type_to_desc(enum rfkill_type type)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(rfkill_type_strings); i++) {
+ if (type == rfkill_type_strings[i].type)
+ return rfkill_type_strings[i].desc;
+ }
+
+ return NULL;
+}
+
+
+static int event_match(struct rfkill_event *event, struct rfkill_id *id)
+{
+ if (event->op != RFKILL_OP_ADD)
+ return 0;
+
+ /* filter out unwanted results */
+ switch (id->result) {
+ case RFKILL_IS_TYPE:
+ if (event->type != id->type)
+ return 0;
+ break;
+ case RFKILL_IS_INDEX:
+ if (event->idx != id->index)
+ return 0;
+ break;
+ case RFKILL_IS_ALL:
+ break;
+ default:
+ abort();
+ }
+
+ return 1;
+}
+
+static void fill_table_row(struct libscols_table *tb, struct rfkill_event *event)
+{
+ static struct libscols_line *ln;
+ size_t i;
+
+ assert(tb);
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln) {
+ errno = ENOMEM;
+ errx(EXIT_FAILURE, _("failed to allocate output line"));
+ }
+
+ for (i = 0; i < (size_t)ncolumns; i++) {
+ char *str = NULL;
+ switch (get_column_id(i)) {
+ case COL_DEVICE:
+ str = xstrdup(get_sys_attr(event->idx, "name"));
+ break;
+ case COL_ID:
+ xasprintf(&str, "%" PRIu32, event->idx);
+ break;
+ case COL_TYPE:
+ str = xstrdup(get_sys_attr(event->idx, "type"));
+ break;
+ case COL_DESC:
+ str = xstrdup(rfkill_type_to_desc(event->type));
+ break;
+ case COL_SOFT:
+ str = xstrdup(event->soft ? _("blocked") : _("unblocked"));
+ break;
+ case COL_HARD:
+ str = xstrdup(event->hard ? _("blocked") : _("unblocked"));
+ break;
+ default:
+ abort();
+ }
+ if (str && scols_line_refer_data(ln, i, str))
+ errx(EXIT_FAILURE, _("failed to add output data"));
+ }
+}
+
+static int rfkill_list_old(const char *param)
+{
+ struct rfkill_id id = { .result = RFKILL_IS_ALL };
+ struct rfkill_event event;
+ int fd, rc = 0;
+
+ if (param) {
+ id = rfkill_id_to_type(param);
+ if (id.result == RFKILL_IS_INVALID) {
+ warnx(_("invalid identifier: %s"), param);
+ return -EINVAL;
+ }
+ }
+
+ fd = rfkill_ro_open(1);
+
+ while (1) {
+ rc = rfkill_read_event(fd, &event);
+ if (rc < 0)
+ break;
+ if (rc == 1 && errno == EAGAIN) {
+ rc = 0; /* done */
+ break;
+ }
+ if (rc == 0 && event_match(&event, &id)) {
+ char *name = xstrdup(get_sys_attr(event.idx, "name")),
+ *type = xstrdup(rfkill_type_to_desc(event.type));
+
+ if (!type)
+ type = xstrdup(get_sys_attr(event.idx, "type"));
+
+ printf("%u: %s: %s\n", event.idx, name, type);
+ printf("\tSoft blocked: %s\n", event.soft ? "yes" : "no");
+ printf("\tHard blocked: %s\n", event.hard ? "yes" : "no");
+
+ free(name);
+ free(type);
+ }
+ }
+ close(fd);
+ return rc;
+}
+
+static void rfkill_list_init(struct control *ctrl)
+{
+ size_t i;
+
+ scols_init_debug(0);
+
+ ctrl->tb = scols_new_table();
+ if (!ctrl->tb)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_json(ctrl->tb, ctrl->json);
+ scols_table_enable_noheadings(ctrl->tb, ctrl->no_headings);
+ scols_table_enable_raw(ctrl->tb, ctrl->raw);
+
+ for (i = 0; i < (size_t) ncolumns; i++) {
+ const struct colinfo *col = get_column_info(i);
+ struct libscols_column *cl;
+
+ cl = scols_table_new_column(ctrl->tb, col->name, col->whint, col->flags);
+ if (!cl)
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ if (ctrl->json) {
+ int id = get_column_id(i);
+ if (id == COL_ID)
+ scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
+ }
+ }
+}
+
+static int rfkill_list_fill(struct control const *ctrl, const char *param)
+{
+ struct rfkill_id id = { .result = RFKILL_IS_ALL };
+ struct rfkill_event event;
+ int fd, rc = 0;
+
+ if (param) {
+ id = rfkill_id_to_type(param);
+ if (id.result == RFKILL_IS_INVALID) {
+ warnx(_("invalid identifier: %s"), param);
+ return -EINVAL;
+ }
+ }
+
+ fd = rfkill_ro_open(1);
+
+ while (1) {
+ rc = rfkill_read_event(fd, &event);
+ if (rc < 0)
+ break;
+ if (rc == 1 && errno == EAGAIN) {
+ rc = 0; /* done */
+ break;
+ }
+ if (rc == 0 && event_match(&event, &id))
+ fill_table_row(ctrl->tb, &event);
+ }
+ close(fd);
+ return rc;
+}
+
+static void rfkill_list_output(struct control const *ctrl)
+{
+ scols_print_table(ctrl->tb);
+ scols_unref_table(ctrl->tb);
+}
+
+static int rfkill_block(uint8_t block, const char *param)
+{
+ struct rfkill_id id;
+ struct rfkill_event event = {
+ .op = RFKILL_OP_CHANGE_ALL,
+ .soft = block,
+ 0
+ };
+ ssize_t len;
+ int fd;
+ char *message = NULL;
+
+ id = rfkill_id_to_type(param);
+
+ switch (id.result) {
+ case RFKILL_IS_INVALID:
+ warnx(_("invalid identifier: %s"), param);
+ return -1;
+ case RFKILL_IS_TYPE:
+ event.type = id.type;
+ xasprintf(&message, "type %s", param);
+ break;
+ case RFKILL_IS_INDEX:
+ event.op = RFKILL_OP_CHANGE;
+ event.idx = id.index;
+ xasprintf(&message, "id %d", id.index);
+ break;
+ case RFKILL_IS_ALL:
+ message = xstrdup("all");
+ break;
+ default:
+ abort();
+ }
+
+ fd = open(_PATH_DEV_RFKILL, O_RDWR);
+ if (fd < 0) {
+ warn(_("cannot open %s"), _PATH_DEV_RFKILL);
+ free(message);
+ return -errno;
+ }
+
+ len = write(fd, &event, sizeof(event));
+ if (len < 0)
+ warn(_("write failed: %s"), _PATH_DEV_RFKILL);
+ else {
+ openlog("rfkill", 0, LOG_USER);
+ syslog(LOG_NOTICE, "%s set for %s", block ? "block" : "unblock", message);
+ closelog();
+ }
+ free(message);
+ return close(fd);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ size_t i;
+
+ fputs(USAGE_HEADER, stdout);
+ fprintf(stdout, _(" %s [options] command [identifier ...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Tool for enabling and disabling wireless devices.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(" -J, --json use JSON output format\n"), stdout);
+ fputs(_(" -n, --noheadings don't print headings\n"), stdout);
+ fputs(_(" -o, --output <list> define which output columns to use\n"), stdout);
+ fputs(_(" --output-all output all columns\n"), stdout);
+ fputs(_(" -r, --raw use the raw output format\n"), stdout);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(24));
+
+ fputs(USAGE_COLUMNS, stdout);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(stdout, " %-10s %s\n", infos[i].name, _(infos[i].help));
+
+ fputs(USAGE_COMMANDS, stdout);
+
+ /*
+ * TRANSLATORS: command names should not be translated, explaining
+ * them as additional field after identifier is fine, for example
+ *
+ * list [identifier] (lista [tarkenne])
+ */
+ fputs(_(" help\n"), stdout);
+ fputs(_(" event\n"), stdout);
+ fputs(_(" list [identifier]\n"), stdout);
+ fputs(_(" block identifier\n"), stdout);
+ fputs(_(" unblock identifier\n"), stdout);
+
+ fprintf(stdout, USAGE_MAN_TAIL("rfkill(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ struct control ctrl = { 0 };
+ int c, act = ACT_LIST, list_all = 0;
+ char *outarg = NULL;
+ enum {
+ OPT_LIST_TYPES = CHAR_MAX + 1
+ };
+ static const struct option longopts[] = {
+ { "json", no_argument, NULL, 'J' },
+ { "noheadings", no_argument, NULL, 'n' },
+ { "output", required_argument, NULL, 'o' },
+ { "output-all", no_argument, NULL, OPT_LIST_TYPES },
+ { "raw", no_argument, NULL, 'r' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+ static const ul_excl_t excl[] = {
+ {'J', 'r'},
+ {0}
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ int ret = 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "Jno:rVh", longopts, NULL)) != -1) {
+ err_exclusive_options(c, longopts, excl, excl_st);
+ switch (c) {
+ case 'J':
+ ctrl.json = 1;
+ break;
+ case 'n':
+ ctrl.no_headings = 1;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case OPT_LIST_TYPES:
+ list_all = 1;
+ break;
+ case 'r':
+ ctrl.raw = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ act = string_to_action(*argv);
+ if (act < 0)
+ errtryhelp(EXIT_FAILURE);
+ argv++;
+ argc--;
+
+ /*
+ * For backward compatibility we use old output format if
+ * "list" explicitly specified and--output not defined.
+ */
+ if (!outarg && act == ACT_LIST)
+ act = ACT_LIST_OLD;
+ }
+
+ switch (act) {
+ case ACT_LIST_OLD:
+ /* Deprecated in favour of ACT_LIST */
+ if (!argc)
+ ret |= rfkill_list_old(NULL); /* ALL */
+ else while (argc) {
+ ret |= rfkill_list_old(*argv);
+ argc--;
+ argv++;
+ }
+ break;
+
+ case ACT_LIST:
+ columns[ncolumns++] = COL_ID;
+ columns[ncolumns++] = COL_TYPE;
+ columns[ncolumns++] = COL_DEVICE;
+ if (list_all)
+ columns[ncolumns++] = COL_DESC;
+ columns[ncolumns++] = COL_SOFT;
+ columns[ncolumns++] = COL_HARD;
+
+ if (outarg
+ && string_add_to_idarray(outarg, columns,
+ ARRAY_SIZE(columns), &ncolumns,
+ column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ rfkill_list_init(&ctrl);
+ if (!argc)
+ ret |= rfkill_list_fill(&ctrl, NULL); /* ALL */
+ else while (argc) {
+ ret |= rfkill_list_fill(&ctrl, *argv);
+ argc--;
+ argv++;
+ }
+ rfkill_list_output(&ctrl);
+ break;
+
+ case ACT_EVENT:
+ ret = rfkill_event();
+ break;
+
+ case ACT_HELP:
+ usage();
+ break;
+
+ case ACT_BLOCK:
+ while (argc) {
+ ret |= rfkill_block(1, *argv);
+ argc--;
+ argv++;
+ }
+ break;
+
+ case ACT_UNBLOCK:
+ while (argc) {
+ ret |= rfkill_block(0, *argv);
+ argv++;
+ argc--;
+ }
+ break;
+ }
+
+ return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/sys-utils/rtcwake.8 b/sys-utils/rtcwake.8
new file mode 100644
index 0000000..0972ff7
--- /dev/null
+++ b/sys-utils/rtcwake.8
@@ -0,0 +1,189 @@
+.\" Copyright (c) 2007, SUSE LINUX Products GmbH
+.\" Bernhard Walle <bwalle@suse.de>
+.\"
+.\" 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., 51 Franklin Street, Fifth Floor, Boston, MA
+.\" 02110-1301, USA.
+.\"
+.TH RTCWAKE 8 "June 2015" "util-linux" "System Administration"
+.SH NAME
+rtcwake \- enter a system sleep state until specified wakeup time
+.SH SYNOPSIS
+.B rtcwake
+[options]
+.RB [ \-d
+.IR device ]
+.RB [ \-m
+.IR standby_mode ]
+.RB { "\-s \fIseconds\fP" | "\-t \fItime_t\fP" }
+.SH DESCRIPTION
+This program is used to enter a system sleep state and to automatically
+wake from it at a specified time.
+.PP
+This uses cross-platform Linux interfaces to enter a system sleep state, and
+leave it no later than a specified time. It uses any RTC framework driver that
+supports standard driver model wakeup flags.
+.PP
+This is normally used like the old \fBapmsleep\fP utility, to wake from a suspend
+state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most platforms can
+implement those without analogues of BIOS, APM, or ACPI.
+.PP
+On some systems, this can also be used like \fBnvram-wakeup\fP, waking from states
+like ACPI S4 (suspend to disk). Not all systems have persistent media that are
+appropriate for such suspend modes.
+.PP
+Note that alarm functionality depends on hardware; not every RTC is able to setup
+an alarm up to 24 hours in the future.
+.PP
+The suspend setup may be interrupted by active hardware; for example wireless USB
+input devices that continue to send events for some fraction of a second after the
+return key is pressed.
+.B rtcwake
+tries to avoid this problem and it waits to terminal to settle down before
+entering a system sleep.
+
+.SH OPTIONS
+.TP
+.BR \-A , " \-\-adjfile " \fIfile
+Specify an alternative path to the adjust file.
+.TP
+.BR \-a , " \-\-auto"
+Read the clock mode (whether the hardware clock is set to UTC or local time)
+from the \fIadjtime\fP file, where
+.BR hwclock (8)
+stores that information. This is the default.
+.TP
+.BR \-\-date " \fItimestamp"
+Set the wakeup time to the value of the timestamp. Format of the
+timestamp can be any of the following:
+.TS
+tab(|);
+l2 l.
+YYYYMMDDhhmmss
+YYYY-MM-DD hh:mm:ss
+YYYY-MM-DD hh:mm|(seconds will be set to 00)
+YYYY-MM-DD|(time will be set to 00:00:00)
+hh:mm:ss|(date will be set to today)
+hh:mm|(date will be set to today, seconds to 00)
+tomorrow|(time is set to 00:00:00)
++5min
+.TE
+.TP
+.BR \-d , " \-\-device " \fIdevice
+Use the specified \fIdevice\fP instead of \fBrtc0\fP as realtime clock.
+This option is only relevant if your system has more than one RTC.
+You may specify \fBrtc1\fP, \fBrtc2\fP, ... here.
+.TP
+.BR \-l , " \-\-local"
+Assume that the hardware clock is set to local time, regardless of the
+contents of the \fIadjtime\fP file.
+.TP
+.B \-\-list\-modes
+List available \-\-mode option arguments.
+.TP
+.BR \-m , " \-\-mode " \fImode
+Go into the given standby state. Valid values for \fImode\fP are:
+.RS
+.TP
+.B standby
+ACPI state S1. This state offers minimal, though real, power savings, while
+providing a very low-latency transition back to a working system. This is the
+default mode.
+.TP
+.B freeze
+The processes are frozen, all the devices are suspended and all the processors
+idled. This state is a general state that does not need any platform-specific
+support, but it saves less power than Suspend-to-RAM, because the system is
+still in a running state. (Available since Linux 3.9.)
+.TP
+.B mem
+ACPI state S3 (Suspend-to-RAM). This state offers significant power savings as
+everything in the system is put into a low-power state, except for memory,
+which is placed in self-refresh mode to retain its contents.
+.TP
+.B disk
+ACPI state S4 (Suspend-to-disk). This state offers the greatest power savings,
+and can be used even in the absence of low-level platform support for power
+management. This state operates similarly to Suspend-to-RAM, but includes a
+final step of writing memory contents to disk.
+.TP
+.B off
+ACPI state S5 (Poweroff). This is done by calling '/sbin/shutdown'.
+Not officially supported by ACPI, but it usually works.
+.TP
+.B no
+Don't suspend, only set the RTC wakeup time.
+.TP
+.B on
+Don't suspend, but read the RTC device until an alarm time appears.
+This mode is useful for debugging.
+.TP
+.B disable
+Disable a previously set alarm.
+.TP
+.B show
+Print alarm information in format: "alarm: off|on <time>".
+The time is in ctime() output format, e.g., "alarm: on Tue Nov 16 04:48:45 2010".
+.RE
+.TP
+.BR \-n , " \-\-dry-run"
+This option does everything apart from actually setting up the alarm,
+suspending the system, or waiting for the alarm.
+.TP
+.BR \-s , " \-\-seconds " \fIseconds
+Set the wakeup time to \fIseconds\fP in the future from now.
+.TP
+.BR \-t , " \-\-time " \fItime_t
+Set the wakeup time to the absolute time \fItime_t\fP. \fItime_t\fP
+is the time in seconds since 1970-01-01, 00:00 UTC. Use the
+.BR date (1)
+tool to convert between human-readable time and \fItime_t\fP.
+.TP
+.BR \-u , " \-\-utc"
+Assume that the hardware clock is set to UTC (Universal Time Coordinated),
+regardless of the contents of the \fIadjtime\fP file.
+.TP
+.BR \-v , " \-\-verbose"
+Be verbose.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+Some PC systems can't currently exit sleep states such as \fBmem\fP
+using only the kernel code accessed by this driver.
+They need help from userspace code to make the framebuffer work again.
+.SH FILES
+.I /etc/adjtime
+.SH HISTORY
+The program was posted several times on LKML and other lists
+before appearing in kernel commit message for Linux 2.6 in the GIT
+commit 87ac84f42a7a580d0dd72ae31d6a5eb4bfe04c6d.
+.SH AUTHORS
+The program was written by David Brownell <dbrownell@users.sourceforge.net>
+and improved by Bernhard Walle <bwalle@suse.de>.
+.SH COPYRIGHT
+This is free software. You may redistribute copies of it under the terms
+of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
+There is NO WARRANTY, to the extent permitted by law.
+.SH "SEE ALSO"
+.BR hwclock (8),
+.BR date (1)
+.SH AVAILABILITY
+The rtcwake command is part of the util-linux package and is available from the
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/rtcwake.8.in b/sys-utils/rtcwake.8.in
new file mode 100644
index 0000000..6891f3a
--- /dev/null
+++ b/sys-utils/rtcwake.8.in
@@ -0,0 +1,189 @@
+.\" Copyright (c) 2007, SUSE LINUX Products GmbH
+.\" Bernhard Walle <bwalle@suse.de>
+.\"
+.\" 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., 51 Franklin Street, Fifth Floor, Boston, MA
+.\" 02110-1301, USA.
+.\"
+.TH RTCWAKE 8 "June 2015" "util-linux" "System Administration"
+.SH NAME
+rtcwake \- enter a system sleep state until specified wakeup time
+.SH SYNOPSIS
+.B rtcwake
+[options]
+.RB [ \-d
+.IR device ]
+.RB [ \-m
+.IR standby_mode ]
+.RB { "\-s \fIseconds\fP" | "\-t \fItime_t\fP" }
+.SH DESCRIPTION
+This program is used to enter a system sleep state and to automatically
+wake from it at a specified time.
+.PP
+This uses cross-platform Linux interfaces to enter a system sleep state, and
+leave it no later than a specified time. It uses any RTC framework driver that
+supports standard driver model wakeup flags.
+.PP
+This is normally used like the old \fBapmsleep\fP utility, to wake from a suspend
+state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most platforms can
+implement those without analogues of BIOS, APM, or ACPI.
+.PP
+On some systems, this can also be used like \fBnvram-wakeup\fP, waking from states
+like ACPI S4 (suspend to disk). Not all systems have persistent media that are
+appropriate for such suspend modes.
+.PP
+Note that alarm functionality depends on hardware; not every RTC is able to setup
+an alarm up to 24 hours in the future.
+.PP
+The suspend setup may be interrupted by active hardware; for example wireless USB
+input devices that continue to send events for some fraction of a second after the
+return key is pressed.
+.B rtcwake
+tries to avoid this problem and it waits to terminal to settle down before
+entering a system sleep.
+
+.SH OPTIONS
+.TP
+.BR \-A , " \-\-adjfile " \fIfile
+Specify an alternative path to the adjust file.
+.TP
+.BR \-a , " \-\-auto"
+Read the clock mode (whether the hardware clock is set to UTC or local time)
+from the \fIadjtime\fP file, where
+.BR hwclock (8)
+stores that information. This is the default.
+.TP
+.BR \-\-date " \fItimestamp"
+Set the wakeup time to the value of the timestamp. Format of the
+timestamp can be any of the following:
+.TS
+tab(|);
+l2 l.
+YYYYMMDDhhmmss
+YYYY-MM-DD hh:mm:ss
+YYYY-MM-DD hh:mm|(seconds will be set to 00)
+YYYY-MM-DD|(time will be set to 00:00:00)
+hh:mm:ss|(date will be set to today)
+hh:mm|(date will be set to today, seconds to 00)
+tomorrow|(time is set to 00:00:00)
++5min
+.TE
+.TP
+.BR \-d , " \-\-device " \fIdevice
+Use the specified \fIdevice\fP instead of \fBrtc0\fP as realtime clock.
+This option is only relevant if your system has more than one RTC.
+You may specify \fBrtc1\fP, \fBrtc2\fP, ... here.
+.TP
+.BR \-l , " \-\-local"
+Assume that the hardware clock is set to local time, regardless of the
+contents of the \fIadjtime\fP file.
+.TP
+.B \-\-list\-modes
+List available \-\-mode option arguments.
+.TP
+.BR \-m , " \-\-mode " \fImode
+Go into the given standby state. Valid values for \fImode\fP are:
+.RS
+.TP
+.B standby
+ACPI state S1. This state offers minimal, though real, power savings, while
+providing a very low-latency transition back to a working system. This is the
+default mode.
+.TP
+.B freeze
+The processes are frozen, all the devices are suspended and all the processors
+idled. This state is a general state that does not need any platform-specific
+support, but it saves less power than Suspend-to-RAM, because the system is
+still in a running state. (Available since Linux 3.9.)
+.TP
+.B mem
+ACPI state S3 (Suspend-to-RAM). This state offers significant power savings as
+everything in the system is put into a low-power state, except for memory,
+which is placed in self-refresh mode to retain its contents.
+.TP
+.B disk
+ACPI state S4 (Suspend-to-disk). This state offers the greatest power savings,
+and can be used even in the absence of low-level platform support for power
+management. This state operates similarly to Suspend-to-RAM, but includes a
+final step of writing memory contents to disk.
+.TP
+.B off
+ACPI state S5 (Poweroff). This is done by calling '/sbin/shutdown'.
+Not officially supported by ACPI, but it usually works.
+.TP
+.B no
+Don't suspend, only set the RTC wakeup time.
+.TP
+.B on
+Don't suspend, but read the RTC device until an alarm time appears.
+This mode is useful for debugging.
+.TP
+.B disable
+Disable a previously set alarm.
+.TP
+.B show
+Print alarm information in format: "alarm: off|on <time>".
+The time is in ctime() output format, e.g., "alarm: on Tue Nov 16 04:48:45 2010".
+.RE
+.TP
+.BR \-n , " \-\-dry-run"
+This option does everything apart from actually setting up the alarm,
+suspending the system, or waiting for the alarm.
+.TP
+.BR \-s , " \-\-seconds " \fIseconds
+Set the wakeup time to \fIseconds\fP in the future from now.
+.TP
+.BR \-t , " \-\-time " \fItime_t
+Set the wakeup time to the absolute time \fItime_t\fP. \fItime_t\fP
+is the time in seconds since 1970-01-01, 00:00 UTC. Use the
+.BR date (1)
+tool to convert between human-readable time and \fItime_t\fP.
+.TP
+.BR \-u , " \-\-utc"
+Assume that the hardware clock is set to UTC (Universal Time Coordinated),
+regardless of the contents of the \fIadjtime\fP file.
+.TP
+.BR \-v , " \-\-verbose"
+Be verbose.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+Some PC systems can't currently exit sleep states such as \fBmem\fP
+using only the kernel code accessed by this driver.
+They need help from userspace code to make the framebuffer work again.
+.SH FILES
+.I @ADJTIME_PATH@
+.SH HISTORY
+The program was posted several times on LKML and other lists
+before appearing in kernel commit message for Linux 2.6 in the GIT
+commit 87ac84f42a7a580d0dd72ae31d6a5eb4bfe04c6d.
+.SH AUTHORS
+The program was written by David Brownell <dbrownell@users.sourceforge.net>
+and improved by Bernhard Walle <bwalle@suse.de>.
+.SH COPYRIGHT
+This is free software. You may redistribute copies of it under the terms
+of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
+There is NO WARRANTY, to the extent permitted by law.
+.SH "SEE ALSO"
+.BR hwclock (8),
+.BR date (1)
+.SH AVAILABILITY
+The rtcwake command is part of the util-linux package and is available from the
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/rtcwake.c b/sys-utils/rtcwake.c
new file mode 100644
index 0000000..ec90c9c
--- /dev/null
+++ b/sys-utils/rtcwake.c
@@ -0,0 +1,686 @@
+/*
+ * rtcwake -- enter a system sleep state until specified wakeup time.
+ *
+ * This uses cross-platform Linux interfaces to enter a system sleep state,
+ * and leave it no later than a specified time. It uses any RTC framework
+ * driver that supports standard driver model wakeup flags.
+ *
+ * This is normally used like the old "apmsleep" utility, to wake from a
+ * suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most
+ * platforms can implement those without analogues of BIOS, APM, or ACPI.
+ *
+ * On some systems, this can also be used like "nvram-wakeup", waking
+ * from states like ACPI S4 (suspend to disk). Not all systems have
+ * persistent media that are appropriate for such suspend modes.
+ *
+ * The best way to set the system's RTC is so that it holds the current
+ * time in UTC. Use the "-l" flag to tell this program that the system
+ * RTC uses a local timezone instead (maybe you dual-boot MS-Windows).
+ * That flag should not be needed on systems with adjtime support.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/rtc.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "closestream.h"
+#include "env.h"
+#include "nls.h"
+#include "optutils.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "strv.h"
+#include "timeutils.h"
+#include "xalloc.h"
+
+#ifndef RTC_AF
+# define RTC_AF 0x20 /* Alarm interrupt */
+#endif
+
+#define ADJTIME_ZONE_BUFSIZ 8
+#define SYS_WAKEUP_PATH_TEMPLATE "/sys/class/rtc/%s/device/power/wakeup"
+#define SYS_POWER_STATE_PATH "/sys/power/state"
+#define DEFAULT_RTC_DEVICE "/dev/rtc0"
+
+enum rtc_modes { /* manual page --mode option explains these. */
+ OFF_MODE = 0,
+ NO_MODE,
+ ON_MODE,
+ DISABLE_MODE,
+ SHOW_MODE,
+
+ SYSFS_MODE /* keep it last */
+
+};
+
+static const char *rtcwake_mode_string[] = {
+ [OFF_MODE] = "off",
+ [NO_MODE] = "no",
+ [ON_MODE] = "on",
+ [DISABLE_MODE] = "disable",
+ [SHOW_MODE] = "show"
+};
+
+enum clock_modes {
+ CM_AUTO,
+ CM_UTC,
+ CM_LOCAL
+};
+
+struct rtcwake_control {
+ char *mode_str; /* name of the requested mode */
+ char **possible_modes; /* modes listed in /sys/power/state */
+ char *adjfile; /* adjtime file path */
+ enum clock_modes clock_mode; /* hwclock timezone */
+ time_t sys_time; /* system time */
+ time_t rtc_time; /* hardware time */
+ unsigned int verbose:1, /* verbose messaging */
+ dryrun:1; /* do not set alarm, suspend system, etc */
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Enter a system sleep state until a specified wakeup time.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --auto reads the clock mode from adjust file (default)\n"), out);
+ fprintf(out,
+ _(" -A, --adjfile <file> specifies the path to the adjust file\n"
+ " the default is %s\n"), _PATH_ADJTIME);
+ fputs(_(" --date <timestamp> date time of timestamp to wake\n"), out);
+ fputs(_(" -d, --device <device> select rtc device (rtc0|rtc1|...)\n"), out);
+ fputs(_(" -n, --dry-run does everything, but suspend\n"), out);
+ fputs(_(" -l, --local RTC uses local timezone\n"), out);
+ fputs(_(" --list-modes list available modes\n"), out);
+ fputs(_(" -m, --mode <mode> standby|mem|... sleep mode\n"), out);
+ fputs(_(" -s, --seconds <seconds> seconds to sleep\n"), out);
+ fputs(_(" -t, --time <time_t> time to wake\n"), out);
+ fputs(_(" -u, --utc RTC uses UTC\n"), out);
+ fputs(_(" -v, --verbose verbose messages\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+ printf(USAGE_MAN_TAIL("rtcwake(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int is_wakeup_enabled(const char *devname)
+{
+ char buf[128], *s;
+ FILE *f;
+ size_t skip = 0;
+
+ if (startswith(devname, "/dev/"))
+ skip = 5;
+ snprintf(buf, sizeof buf, SYS_WAKEUP_PATH_TEMPLATE, devname + skip);
+ f = fopen(buf, "r");
+ if (!f) {
+ warn(_("cannot open %s"), buf);
+ return 0;
+ }
+
+ s = fgets(buf, sizeof buf, f);
+ fclose(f);
+ if (!s)
+ return 0;
+ s = strchr(buf, '\n');
+ if (!s)
+ return 0;
+ *s = 0;
+ /* wakeup events could be disabled or not supported */
+ return strcmp(buf, "enabled") == 0;
+}
+
+static int get_basetimes(struct rtcwake_control *ctl, int fd)
+{
+ struct tm tm = { 0 };
+ struct rtc_time rtc;
+
+ /* This process works in RTC time, except when working
+ * with the system clock (which always uses UTC).
+ */
+ if (ctl->clock_mode == CM_UTC)
+ xsetenv("TZ", "UTC", 1);
+ tzset();
+ /* Read rtc and system clocks "at the same time", or as
+ * precisely (+/- a second) as we can read them.
+ */
+ if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) {
+ warn(_("read rtc time failed"));
+ return -1;
+ }
+
+ ctl->sys_time = time(NULL);
+ if (ctl->sys_time == (time_t)-1) {
+ warn(_("read system time failed"));
+ return -1;
+ }
+ /* Convert rtc_time to normal arithmetic-friendly form,
+ * updating tm.tm_wday as used by asctime().
+ */
+ tm.tm_sec = rtc.tm_sec;
+ tm.tm_min = rtc.tm_min;
+ tm.tm_hour = rtc.tm_hour;
+ tm.tm_mday = rtc.tm_mday;
+ tm.tm_mon = rtc.tm_mon;
+ tm.tm_year = rtc.tm_year;
+ tm.tm_isdst = -1; /* assume the system knows better than the RTC */
+
+ ctl->rtc_time = mktime(&tm);
+ if (ctl->rtc_time == (time_t)-1) {
+ warn(_("convert rtc time failed"));
+ return -1;
+ }
+
+ if (ctl->verbose) {
+ /* Unless the system uses UTC, either delta or tzone
+ * reflects a seconds offset from UTC. The value can
+ * help sort out problems like bugs in your C library. */
+ char s[64];
+ printf("\tdelta = %ld\n", ctl->sys_time - ctl->rtc_time);
+ printf("\ttzone = %ld\n", timezone);
+ printf("\ttzname = %s\n", tzname[daylight]);
+ gmtime_r(&ctl->sys_time, &tm);
+ printf("\tsystime = %ld, (UTC) %s",
+ (long) ctl->sys_time, asctime_r(&tm, s));
+ gmtime_r(&ctl->rtc_time, &tm);
+ printf("\trtctime = %ld, (UTC) %s",
+ (long) ctl->rtc_time, asctime_r(&tm, s));
+ }
+ return 0;
+}
+
+static int setup_alarm(struct rtcwake_control *ctl, int fd, time_t *wakeup)
+{
+ struct tm tm;
+ struct rtc_wkalrm wake = { 0 };
+
+ /* The wakeup time is in POSIX time (more or less UTC). Ideally
+ * RTCs use that same time; but PCs can't do that if they need to
+ * boot MS-Windows. Messy...
+ *
+ * When clock_mode == CM_UTC this process's timezone is UTC, so
+ * we'll pass a UTC date to the RTC.
+ *
+ * Else clock_mode == CM_LOCAL so the time given to the RTC will
+ * instead use the local time zone. */
+ localtime_r(wakeup, &tm);
+ wake.time.tm_sec = tm.tm_sec;
+ wake.time.tm_min = tm.tm_min;
+ wake.time.tm_hour = tm.tm_hour;
+ wake.time.tm_mday = tm.tm_mday;
+ wake.time.tm_mon = tm.tm_mon;
+ wake.time.tm_year = tm.tm_year;
+ /* wday, yday, and isdst fields are unused */
+ wake.time.tm_wday = -1;
+ wake.time.tm_yday = -1;
+ wake.time.tm_isdst = -1;
+ wake.enabled = 1;
+
+ if (!ctl->dryrun && ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
+ warn(_("set rtc wake alarm failed"));
+ return -1;
+ }
+ return 0;
+}
+
+static char **get_sys_power_states(struct rtcwake_control *ctl)
+{
+ int fd = -1;
+
+ if (!ctl->possible_modes) {
+ char buf[256] = { 0 };
+ ssize_t ss;
+
+ fd = open(SYS_POWER_STATE_PATH, O_RDONLY);
+ if (fd < 0)
+ goto nothing;
+ ss = read(fd, &buf, sizeof(buf) - 1);
+ if (ss <= 0)
+ goto nothing;
+ buf[ss] = '\0';
+ ctl->possible_modes = strv_split(buf, " \n");
+ close(fd);
+ }
+ return ctl->possible_modes;
+nothing:
+ if (fd >= 0)
+ close(fd);
+ return NULL;
+}
+
+static void wait_stdin(struct rtcwake_control *ctl)
+{
+ struct pollfd fd[] = {
+ {.fd = STDIN_FILENO, .events = POLLIN}
+ };
+ int tries = 0;
+
+ while (tries < 8 && poll(fd, 1, 10) == 1) {
+ if (ctl->verbose)
+ warnx(_("discarding stdin"));
+ xusleep(250000);
+ tcflush(STDIN_FILENO, TCIFLUSH);
+ tries++;
+ }
+}
+
+static void suspend_system(struct rtcwake_control *ctl)
+{
+ FILE *f = fopen(SYS_POWER_STATE_PATH, "w");
+
+ if (!f) {
+ warn(_("cannot open %s"), SYS_POWER_STATE_PATH);
+ return;
+ }
+
+ if (!ctl->dryrun) {
+ if (isatty(STDIN_FILENO))
+ wait_stdin(ctl);
+ fprintf(f, "%s\n", ctl->mode_str);
+ fflush(f);
+ }
+ /* this executes after wake from suspend */
+ if (close_stream(f))
+ errx(EXIT_FAILURE, _("write error"));
+}
+
+static int read_clock_mode(struct rtcwake_control *ctl)
+{
+ FILE *fp;
+ char linebuf[ADJTIME_ZONE_BUFSIZ];
+
+ fp = fopen(ctl->adjfile, "r");
+ if (!fp)
+ return -1;
+ /* skip two lines */
+ if (skip_fline(fp) || skip_fline(fp)) {
+ fclose(fp);
+ return -1;
+ }
+ /* read third line */
+ if (!fgets(linebuf, sizeof linebuf, fp)) {
+ fclose(fp);
+ return -1;
+ }
+
+ if (strncmp(linebuf, "UTC", 3) == 0)
+ ctl->clock_mode = CM_UTC;
+ else if (strncmp(linebuf, "LOCAL", 5) == 0)
+ ctl->clock_mode = CM_LOCAL;
+ else if (ctl->verbose)
+ warnx(_("unexpected third line in: %s: %s"), ctl->adjfile, linebuf);
+
+ fclose(fp);
+ return 0;
+}
+
+static int print_alarm(struct rtcwake_control *ctl, int fd)
+{
+ struct rtc_wkalrm wake;
+ struct tm tm = { 0 };
+ time_t alarm;
+ char s[CTIME_BUFSIZ];
+
+ if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) {
+ warn(_("read rtc alarm failed"));
+ return -1;
+ }
+
+ if (wake.enabled != 1 || wake.time.tm_year == -1) {
+ printf(_("alarm: off\n"));
+ return 0;
+ }
+ tm.tm_sec = wake.time.tm_sec;
+ tm.tm_min = wake.time.tm_min;
+ tm.tm_hour = wake.time.tm_hour;
+ tm.tm_mday = wake.time.tm_mday;
+ tm.tm_mon = wake.time.tm_mon;
+ tm.tm_year = wake.time.tm_year;
+ tm.tm_isdst = -1; /* assume the system knows better than the RTC */
+
+ alarm = mktime(&tm);
+ if (alarm == (time_t)-1) {
+ warn(_("convert time failed"));
+ return -1;
+ }
+ /* 0 if both UTC, or expresses diff if RTC in local time */
+ alarm += ctl->sys_time - ctl->rtc_time;
+ ctime_r(&alarm, s);
+ printf(_("alarm: on %s"), s);
+
+ return 0;
+}
+
+static int get_rtc_mode(struct rtcwake_control *ctl, const char *s)
+{
+ size_t i;
+ char **modes = get_sys_power_states(ctl), **m;
+
+ STRV_FOREACH(m, modes) {
+ if (strcmp(s, *m) == 0)
+ return SYSFS_MODE;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++)
+ if (!strcmp(s, rtcwake_mode_string[i]))
+ return i;
+
+ return -EINVAL;
+}
+
+static int open_dev_rtc(const char *devname)
+{
+ int fd;
+ char *devpath = NULL;
+
+ if (startswith(devname, "/dev"))
+ devpath = xstrdup(devname);
+ else
+ xasprintf(&devpath, "/dev/%s", devname);
+ fd = open(devpath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("%s: unable to find device"), devpath);
+ free(devpath);
+ return fd;
+}
+
+static void list_modes(struct rtcwake_control *ctl)
+{
+ size_t i;
+ char **modes = get_sys_power_states(ctl), **m;
+
+ if (!modes)
+ errx(EXIT_FAILURE, _("could not read: %s"), SYS_POWER_STATE_PATH);
+
+ STRV_FOREACH(m, modes)
+ printf("%s ", *m);
+
+ for (i = 0; i < ARRAY_SIZE(rtcwake_mode_string); i++)
+ printf("%s ", rtcwake_mode_string[i]);
+ putchar('\n');
+}
+
+int main(int argc, char **argv)
+{
+ struct rtcwake_control ctl = {
+ .mode_str = "suspend", /* default mode */
+ .adjfile = _PATH_ADJTIME,
+ .clock_mode = CM_AUTO
+ };
+ char *devname = DEFAULT_RTC_DEVICE;
+ unsigned seconds = 0;
+ int suspend = SYSFS_MODE;
+ int rc = EXIT_SUCCESS;
+ int t;
+ int fd;
+ time_t alarm = 0;
+ enum {
+ OPT_DATE = CHAR_MAX + 1,
+ OPT_LIST
+ };
+ static const struct option long_options[] = {
+ { "adjfile", required_argument, NULL, 'A' },
+ { "auto", no_argument, NULL, 'a' },
+ { "dry-run", no_argument, NULL, 'n' },
+ { "local", no_argument, NULL, 'l' },
+ { "utc", no_argument, NULL, 'u' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { "mode", required_argument, NULL, 'm' },
+ { "device", required_argument, NULL, 'd' },
+ { "seconds", required_argument, NULL, 's' },
+ { "time", required_argument, NULL, 't' },
+ { "date", required_argument, NULL, OPT_DATE },
+ { "list-modes", no_argument, NULL, OPT_LIST },
+ { NULL, 0, NULL, 0 }
+ };
+ static const ul_excl_t excl[] = {
+ { 'a', 'l', 'u' },
+ { 's', 't', OPT_DATE },
+ { 0 },
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((t = getopt_long(argc, argv, "A:ahd:lm:ns:t:uVv",
+ long_options, NULL)) != EOF) {
+ err_exclusive_options(t, long_options, excl, excl_st);
+ switch (t) {
+ case 'A':
+ /* for better compatibility with hwclock */
+ ctl.adjfile = optarg;
+ break;
+ case 'a':
+ ctl.clock_mode = CM_AUTO;
+ break;
+ case 'd':
+ devname = optarg;
+ break;
+ case 'l':
+ ctl.clock_mode = CM_LOCAL;
+ break;
+
+ case OPT_LIST:
+ list_modes(&ctl);
+ return EXIT_SUCCESS;
+
+ case 'm':
+ if ((suspend = get_rtc_mode(&ctl, optarg)) < 0)
+ errx(EXIT_FAILURE, _("unrecognized suspend state '%s'"), optarg);
+ ctl.mode_str = optarg;
+ break;
+ case 'n':
+ ctl.dryrun = 1;
+ break;
+ case 's':
+ /* alarm time, seconds-to-sleep (relative) */
+ seconds = strtou32_or_err(optarg, _("invalid seconds argument"));
+ break;
+ case 't':
+ /* alarm time, time_t (absolute, seconds since epoch) */
+ alarm = strtou32_or_err(optarg, _("invalid time argument"));
+ break;
+ case OPT_DATE:
+ { /* alarm time, see timestamp format from manual */
+ usec_t p;
+ if (parse_timestamp(optarg, &p) < 0)
+ errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
+ alarm = (time_t) (p / 1000000);
+ break;
+ }
+ case 'u':
+ ctl.clock_mode = CM_UTC;
+ break;
+ case 'v':
+ ctl.verbose = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (ctl.clock_mode == CM_AUTO && read_clock_mode(&ctl) < 0) {
+ printf(_("%s: assuming RTC uses UTC ...\n"), program_invocation_short_name);
+ ctl.clock_mode = CM_UTC;
+ }
+
+ if (ctl.verbose)
+ printf("%s", ctl.clock_mode == CM_UTC ? _("Using UTC time.\n") :
+ _("Using local time.\n"));
+
+ if (!alarm && !seconds && suspend != DISABLE_MODE && suspend != SHOW_MODE)
+ errx(EXIT_FAILURE, _("must provide wake time (see --seconds, --time and --date options)"));
+
+ /* device must exist and (if we'll sleep) be wakeup-enabled */
+ fd = open_dev_rtc(devname);
+
+ if (suspend != ON_MODE && suspend != NO_MODE && !is_wakeup_enabled(devname))
+ errx(EXIT_FAILURE, _("%s not enabled for wakeup events"), devname);
+
+ /* relative or absolute alarm time, normalized to time_t */
+ if (get_basetimes(&ctl, fd) < 0)
+ exit(EXIT_FAILURE);
+
+ if (ctl.verbose)
+ printf(_("alarm %ld, sys_time %ld, rtc_time %ld, seconds %u\n"),
+ alarm, ctl.sys_time, ctl.rtc_time, seconds);
+
+ if (suspend != DISABLE_MODE && suspend != SHOW_MODE) {
+ /* perform alarm setup when the show or disable modes are not set */
+ if (alarm) {
+ if (alarm < ctl.sys_time) {
+ char s[CTIME_BUFSIZ];
+
+ ctime_r(&alarm, s);
+ errx(EXIT_FAILURE, _("time doesn't go backward to %s"), s);
+ }
+ alarm -= ctl.sys_time - ctl.rtc_time;
+ } else
+ alarm = ctl.rtc_time + seconds + 1;
+
+ if (setup_alarm(&ctl, fd, &alarm) < 0)
+ exit(EXIT_FAILURE);
+
+ if (suspend == NO_MODE || suspend == ON_MODE) {
+ char s[CTIME_BUFSIZ];
+
+ ctime_r(&alarm, s);
+ printf(_("%s: wakeup using %s at %s"),
+ program_invocation_short_name, devname, s);
+ } else {
+ char s[CTIME_BUFSIZ];
+
+ ctime_r(&alarm, s);
+ printf(_("%s: wakeup from \"%s\" using %s at %s"),
+ program_invocation_short_name, ctl.mode_str, devname, s);
+ }
+ fflush(stdout);
+ xusleep(10 * 1000);
+ }
+
+ switch (suspend) {
+ case NO_MODE:
+ if (ctl.verbose)
+ printf(_("suspend mode: no; leaving\n"));
+ ctl.dryrun = 1; /* to skip disabling alarm at the end */
+ break;
+ case OFF_MODE:
+ {
+ char *arg[5];
+ int i = 0;
+
+ if (!access(_PATH_SHUTDOWN, X_OK)) {
+ arg[i++] = _PATH_SHUTDOWN;
+ arg[i++] = "-h";
+ arg[i++] = "-P";
+ arg[i++] = "now";
+ arg[i] = NULL;
+ } else if (!access(_PATH_POWEROFF, X_OK)) {
+ arg[i++] = _PATH_POWEROFF;
+ arg[i] = NULL;
+ } else {
+ arg[i] = NULL;
+ }
+
+ if (arg[0]) {
+ if (ctl.verbose)
+ printf(_("suspend mode: off; executing %s\n"),
+ arg[0]);
+ if (!ctl.dryrun) {
+ execv(arg[0], arg);
+ warn(_("failed to execute %s"), arg[0]);
+ rc = EX_EXEC_ENOENT;
+ }
+ } else {
+ /* Failed to find shutdown command */
+ warn(_("failed to find shutdown command"));
+ rc = EX_EXEC_ENOENT;
+ }
+ break;
+ }
+ case ON_MODE:
+ {
+ unsigned long data;
+
+ if (ctl.verbose)
+ printf(_("suspend mode: on; reading rtc\n"));
+ if (!ctl.dryrun) {
+ do {
+ t = read(fd, &data, sizeof data);
+ if (t < 0) {
+ warn(_("rtc read failed"));
+ break;
+ }
+ if (ctl.verbose)
+ printf("... %s: %03lx\n", devname, data);
+ } while (!(data & RTC_AF));
+ }
+ break;
+ }
+ case DISABLE_MODE:
+ /* just break, alarm gets disabled in the end */
+ if (ctl.verbose)
+ printf(_("suspend mode: disable; disabling alarm\n"));
+ break;
+ case SHOW_MODE:
+ if (ctl.verbose)
+ printf(_("suspend mode: show; printing alarm info\n"));
+ if (print_alarm(&ctl, fd))
+ rc = EXIT_FAILURE;
+ ctl.dryrun = 1; /* don't really disable alarm in the end, just show */
+ break;
+ default:
+ if (ctl.verbose)
+ printf(_("suspend mode: %s; suspending system\n"), ctl.mode_str);
+ sync();
+ suspend_system(&ctl);
+ }
+
+ if (!ctl.dryrun) {
+ struct rtc_wkalrm wake;
+
+ if (ioctl(fd, RTC_WKALM_RD, &wake) < 0) {
+ warn(_("read rtc alarm failed"));
+ rc = EXIT_FAILURE;
+ } else {
+ wake.enabled = 0;
+ if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
+ warn(_("disable rtc alarm interrupt failed"));
+ rc = EXIT_FAILURE;
+ }
+ }
+ }
+
+ close(fd);
+ return rc;
+}
diff --git a/sys-utils/setarch.8 b/sys-utils/setarch.8
new file mode 100644
index 0000000..7276fdd
--- /dev/null
+++ b/sys-utils/setarch.8
@@ -0,0 +1,143 @@
+.TH SETARCH 8 "December 2017" "util-linux" "System Administration"
+.SH NAME
+setarch \- change reported architecture in new program environment and/or set personality flags
+.SH SYNOPSIS
+.B setarch
+.RI [ arch ]
+[options]
+.RI [ program
+.RI [ argument ...]]
+.sp
+.B setarch
+.BR \-\-list | \-h | \-V
+.sp
+.B arch
+[options]
+.RI [ program
+.RI [ argument ...]]
+.SH DESCRIPTION
+.B setarch
+modifies execution domains and process personality flags.
+.PP
+The execution domains currently only affects the output of \fBuname \-m\fR.
+For example, on an AMD64 system, running \fBsetarch i386 \fIprogram\fR
+will cause \fIprogram\fR to see i686 instead of x86_64 as the machine type.
+It can also be used to set various personality options.
+The default \fIprogram\fR is \fB/bin/sh\fR.
+.PP
+Since version 2.33 the
+.I arch
+command line argument is optional and
+.B setarch
+may be used to change personality flags (ADDR_LIMIT_*, SHORT_INODE, etc) without
+modification of the execution domain.
+.SH OPTIONS
+.TP
+.B \-\-list
+List the architectures that \fBsetarch\fR knows about. Whether \fBsetarch\fR
+can actually set each of these architectures depends on the running kernel.
+.TP
+.B \-\-uname\-2.6
+Causes the \fIprogram\fR to see a kernel version number beginning with 2.6.
+Turns on UNAME26.
+.TP
+.BR \-v , " \-\-verbose"
+Be verbose.
+.TP
+\fB\-3\fR, \fB\-\-3gb\fR
+Specifies
+.I program
+should use a maximum of 3GB of address space. Supported on x86. Turns on
+ADDR_LIMIT_3GB.
+.TP
+\fB\-\-4gb\fR
+This option has no effect. It is retained for backward compatibility only,
+and may be removed in future releases.
+.TP
+\fB\-B\fR, \fB\-\-32bit\fR
+Limit the address space to 32 bits to emulate hardware. Supported on ARM
+and Alpha. Turns on ADDR_LIMIT_32BIT.
+.TP
+\fB\-F\fR, \fB\-\-fdpic\-funcptrs\fR
+Treat user-space function pointers to signal handlers as pointers to address
+descriptors. This option has no effect on architectures that do not support
+FDPIC ELF binaries. In kernel v4.14 support is limited to ARM, Blackfin,
+Fujitsu FR-V, and SuperH CPU architectures.
+.TP
+\fB\-I\fR, \fB\-\-short\-inode\fR
+Obsolete bug emulation flag. Turns on SHORT_INODE.
+.TP
+\fB\-L\fR, \fB\-\-addr\-compat\-layout\fR
+Provide legacy virtual address space layout. Use when the
+.I program
+binary does not have PT_GNU_STACK ELF header. Turns on
+ADDR_COMPAT_LAYOUT.
+.TP
+\fB\-R\fR, \fB\-\-addr\-no\-randomize\fR
+Disables randomization of the virtual address space. Turns on
+ADDR_NO_RANDOMIZE.
+.TP
+\fB\-S\fR, \fB\-\-whole\-seconds\fR
+Obsolete bug emulation flag. Turns on WHOLE_SECONDS.
+.TP
+\fB\-T\fR, \fB\-\-sticky\-timeouts\fR
+This makes
+.BR select (2),
+.BR pselect (2),
+and
+.BR ppoll (2)
+system calls preserve the timeout value instead of modifying it to reflect
+the amount of time not slept when interrupted by a signal handler. Use when
+.I program
+depends on this behavior. For more details see the timeout description in
+.BR select (2)
+manual page. Turns on STICKY_TIMEOUTS.
+.TP
+\fB\-X\fR, \fB\-\-read\-implies\-exec\fR
+If this is set then
+.BR mmap (3p)
+PROT_READ will also add the PROT_EXEC bit - as expected by legacy x86
+binaries. Notice that the ELF loader will automatically set this bit when
+it encounters a legacy binary. Turns on READ_IMPLIES_EXEC.
+.TP
+\fB\-Z\fR, \fB\-\-mmap\-page\-zero\fR
+SVr4 bug emulation that will set
+.BR mmap (3p)
+page zero as read-only. Use when
+.I program
+depends on this behavior, and the source code is not available to be fixed.
+Turns on MMAP_PAGE_ZERO.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXAMPLE
+setarch \-\-addr-no-randomize mytestprog
+.br
+setarch ppc32 rpmbuild \-\-target=ppc \-\-rebuild foo.src.rpm
+.br
+setarch ppc32 \-v \-vL3 rpmbuild \-\-target=ppc \-\-rebuild bar.src.rpm
+.br
+setarch ppc32 \-\-32bit rpmbuild \-\-target=ppc \-\-rebuild foo.src.rpm
+.SH AUTHORS
+.MT sopwith@redhat.com
+Elliot Lee
+.ME
+.br
+.MT jnovy@redhat.com
+Jindrich Novy
+.ME
+.br
+.MT kzak@redhat.com
+Karel Zak
+.ME
+.SH SEE ALSO
+.BR personality (2),
+.BR select (2)
+.SH AVAILABILITY
+The setarch command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/setarch.c b/sys-utils/setarch.c
new file mode 100644
index 0000000..b86be1e
--- /dev/null
+++ b/sys-utils/setarch.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2003-2007 Red Hat, Inc.
+ *
+ * This file is part of util-linux.
+ *
+ * This file 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 file 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.
+ *
+ *
+ * Written by Elliot Lee <sopwith@redhat.com>
+ * New personality options & code added by Jindrich Novy <jnovy@redhat.com>
+ * ADD_NO_RANDOMIZE flag added by Arjan van de Ven <arjanv@redhat.com>
+ * Help and MIPS support from Mike Frysinger (vapier@gentoo.org)
+ * Better error handling from Dmitry V. Levin (ldv@altlinux.org)
+ *
+ * based on ideas from the ppc32 util by Guy Streeter (2002-01), based on the
+ * sparc32 util by Jakub Jelinek (1998, 1999)
+ */
+
+#include <sys/personality.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/utsname.h>
+#include "nls.h"
+#include "c.h"
+#include "closestream.h"
+
+#ifndef HAVE_PERSONALITY
+# include <syscall.h>
+# define personality(pers) ((long)syscall(SYS_personality, pers))
+#endif
+
+#define turn_on(_flag, _opts) \
+ do { \
+ (_opts) |= _flag; \
+ if (verbose) \
+ printf(_("Switching on %s.\n"), #_flag); \
+ } while(0)
+
+#ifndef UNAME26
+# define UNAME26 0x0020000
+#endif
+#ifndef ADDR_NO_RANDOMIZE
+# define ADDR_NO_RANDOMIZE 0x0040000
+#endif
+#ifndef FDPIC_FUNCPTRS
+# define FDPIC_FUNCPTRS 0x0080000
+#endif
+#ifndef MMAP_PAGE_ZERO
+# define MMAP_PAGE_ZERO 0x0100000
+#endif
+#ifndef ADDR_COMPAT_LAYOUT
+# define ADDR_COMPAT_LAYOUT 0x0200000
+#endif
+#ifndef READ_IMPLIES_EXEC
+# define READ_IMPLIES_EXEC 0x0400000
+#endif
+#ifndef ADDR_LIMIT_32BIT
+# define ADDR_LIMIT_32BIT 0x0800000
+#endif
+#ifndef SHORT_INODE
+# define SHORT_INODE 0x1000000
+#endif
+#ifndef WHOLE_SECONDS
+# define WHOLE_SECONDS 0x2000000
+#endif
+#ifndef STICKY_TIMEOUTS
+# define STICKY_TIMEOUTS 0x4000000
+#endif
+#ifndef ADDR_LIMIT_3GB
+# define ADDR_LIMIT_3GB 0x8000000
+#endif
+
+
+struct arch_domain {
+ int perval; /* PER_* */
+ const char *target_arch;
+ const char *result_arch;
+};
+
+
+static void __attribute__((__noreturn__)) usage(int archwrapper)
+{
+ fputs(USAGE_HEADER, stdout);
+ if (!archwrapper)
+ printf(_(" %s [<arch>] [options] [<program> [<argument>...]]\n"), program_invocation_short_name);
+ else
+ printf(_(" %s [options] [<program> [<argument>...]]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Change the reported architecture and set personality flags.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(" -B, --32bit turns on ADDR_LIMIT_32BIT\n"), stdout);
+ fputs(_(" -F, --fdpic-funcptrs makes function pointers point to descriptors\n"), stdout);
+ fputs(_(" -I, --short-inode turns on SHORT_INODE\n"), stdout);
+ fputs(_(" -L, --addr-compat-layout changes the way virtual memory is allocated\n"), stdout);
+ fputs(_(" -R, --addr-no-randomize disables randomization of the virtual address space\n"), stdout);
+ fputs(_(" -S, --whole-seconds turns on WHOLE_SECONDS\n"), stdout);
+ fputs(_(" -T, --sticky-timeouts turns on STICKY_TIMEOUTS\n"), stdout);
+ fputs(_(" -X, --read-implies-exec turns on READ_IMPLIES_EXEC\n"), stdout);
+ fputs(_(" -Z, --mmap-page-zero turns on MMAP_PAGE_ZERO\n"), stdout);
+ fputs(_(" -3, --3gb limits the used address space to a maximum of 3 GB\n"), stdout);
+ fputs(_(" --4gb ignored (for backward compatibility only)\n"), stdout);
+ fputs(_(" --uname-2.6 turns on UNAME26\n"), stdout);
+ fputs(_(" -v, --verbose say what options are being switched on\n"), stdout);
+
+ if (!archwrapper)
+ fputs(_(" --list list settable architectures, and exit\n"), stdout);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(26));
+ printf(USAGE_MAN_TAIL("setarch(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * Returns inilialized list of all available execution domains.
+ */
+static struct arch_domain *init_arch_domains(void)
+{
+ static struct utsname un;
+ size_t i;
+
+ static struct arch_domain transitions[] =
+ {
+ {UNAME26, "uname26", NULL},
+ {PER_LINUX32, "linux32", NULL},
+ {PER_LINUX, "linux64", NULL},
+#if defined(__powerpc__) || defined(__powerpc64__)
+# ifdef __BIG_ENDIAN__
+ {PER_LINUX32, "ppc32", "ppc"},
+ {PER_LINUX32, "ppc", "ppc"},
+ {PER_LINUX, "ppc64", "ppc64"},
+ {PER_LINUX, "ppc64pseries", "ppc64"},
+ {PER_LINUX, "ppc64iseries", "ppc64"},
+# else
+ {PER_LINUX32, "ppc32", "ppcle"},
+ {PER_LINUX32, "ppc", "ppcle"},
+ {PER_LINUX32, "ppc32le", "ppcle"},
+ {PER_LINUX32, "ppcle", "ppcle"},
+ {PER_LINUX, "ppc64le", "ppc64le"},
+# endif
+#endif
+#if defined(__x86_64__) || defined(__i386__) || defined(__ia64__)
+ {PER_LINUX32, "i386", "i386"},
+ {PER_LINUX32, "i486", "i386"},
+ {PER_LINUX32, "i586", "i386"},
+ {PER_LINUX32, "i686", "i386"},
+ {PER_LINUX32, "athlon", "i386"},
+#endif
+#if defined(__x86_64__) || defined(__i386__)
+ {PER_LINUX, "x86_64", "x86_64"},
+#endif
+#if defined(__ia64__) || defined(__i386__)
+ {PER_LINUX, "ia64", "ia64"},
+#endif
+#if defined(__hppa__)
+ {PER_LINUX32, "parisc32", "parisc"},
+ {PER_LINUX32, "parisc", "parisc"},
+ {PER_LINUX, "parisc64", "parisc64"},
+#endif
+#if defined(__s390x__) || defined(__s390__)
+ {PER_LINUX32, "s390", "s390"},
+ {PER_LINUX, "s390x", "s390x"},
+#endif
+#if defined(__sparc64__) || defined(__sparc__)
+ {PER_LINUX32, "sparc", "sparc"},
+ {PER_LINUX32, "sparc32bash", "sparc"},
+ {PER_LINUX32, "sparc32", "sparc"},
+ {PER_LINUX, "sparc64", "sparc64"},
+#endif
+#if defined(__mips64__) || defined(__mips__)
+ {PER_LINUX32, "mips32", "mips"},
+ {PER_LINUX32, "mips", "mips"},
+ {PER_LINUX, "mips64", "mips64"},
+#endif
+#if defined(__alpha__)
+ {PER_LINUX, "alpha", "alpha"},
+ {PER_LINUX, "alphaev5", "alpha"},
+ {PER_LINUX, "alphaev56", "alpha"},
+ {PER_LINUX, "alphaev6", "alpha"},
+ {PER_LINUX, "alphaev67", "alpha"},
+#endif
+#if defined(__e2k__)
+ {PER_LINUX, "e2k", "e2k"},
+ {PER_LINUX, "e2kv4", "e2k"},
+ {PER_LINUX, "e2kv5", "e2k"},
+ {PER_LINUX, "e2kv6", "e2k"},
+ {PER_LINUX, "e2k4c", "e2k"},
+ {PER_LINUX, "e2k8c", "e2k"},
+ {PER_LINUX, "e2k1cp", "e2k"},
+ {PER_LINUX, "e2k8c2", "e2k"},
+ {PER_LINUX, "e2k12c", "e2k"},
+ {PER_LINUX, "e2k16c", "e2k"},
+ {PER_LINUX, "e2k2c3", "e2k"},
+#endif
+#if defined(__arm__) || defined(__aarch64__)
+# ifdef __BIG_ENDIAN__
+ {PER_LINUX32, "armv7b", "arm"},
+ {PER_LINUX32, "armv8b", "arm"},
+# else
+ {PER_LINUX32, "armv7l", "arm"},
+ {PER_LINUX32, "armv8l", "arm"},
+# endif
+ {PER_LINUX32, "armh", "arm"},
+ {PER_LINUX32, "arm", "arm"},
+ {PER_LINUX, "arm64", "aarch64"},
+ {PER_LINUX, "aarch64", "aarch64"},
+#endif
+ /* place holder, will be filled up at runtime */
+ {-1, NULL, NULL},
+ {-1, NULL, NULL}
+ };
+
+ /* Add the trivial transition {PER_LINUX, machine, machine} if no
+ * such target_arch is hardcoded yet. */
+ uname(&un);
+ for (i = 0; transitions[i].perval >= 0; i++)
+ if (!strcmp(un.machine, transitions[i].target_arch))
+ break;
+ if (transitions[i].perval < 0) {
+ unsigned long wrdsz = CHAR_BIT * sizeof(void *);
+ if (wrdsz == 32 || wrdsz == 64) {
+ /* fill up the place holder */
+ transitions[i].perval = wrdsz == 32 ? PER_LINUX32 : PER_LINUX;
+ transitions[i].target_arch = un.machine;
+ transitions[i].result_arch = un.machine;
+ }
+ }
+
+ return transitions;
+}
+
+/*
+ * List all execution domains from transitions
+ */
+static void list_arch_domains(struct arch_domain *doms)
+{
+ struct arch_domain *d;
+
+ for (d = doms; d->target_arch != NULL; d++)
+ printf("%s\n", d->target_arch);
+}
+
+static struct arch_domain *get_arch_domain(struct arch_domain *doms, const char *pers)
+{
+ struct arch_domain *d;
+
+ for (d = doms; d && d->perval >= 0; d++) {
+ if (!strcmp(pers, d->target_arch))
+ break;
+ }
+
+ return !d || d->perval < 0 ? NULL : d;
+}
+
+static void verify_arch_domain(struct arch_domain *doms, struct arch_domain *target, const char *wanted)
+{
+ struct utsname un;
+
+ if (!doms || !target || !target->result_arch)
+ return;
+
+ uname(&un);
+
+ if (!strcmp(un.machine, target->result_arch))
+ return;
+
+ if (!strcmp(target->result_arch, "i386") ||
+ !strcmp(target->result_arch, "arm")) {
+ struct arch_domain *dom;
+ for (dom = doms; dom->target_arch != NULL; dom++) {
+ if (!dom->result_arch || strcmp(dom->result_arch, target->result_arch))
+ continue;
+ if (!strcmp(dom->target_arch, un.machine))
+ return;
+ }
+ }
+
+ errx(EXIT_FAILURE, _("Kernel cannot set architecture to %s"), wanted);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *arch = NULL;
+ unsigned long options = 0;
+ int verbose = 0;
+ int archwrapper;
+ int c;
+ struct arch_domain *doms = NULL, *target = NULL;
+ unsigned long pers_value = 0;
+ char *shell = NULL, *shell_arg = NULL;
+
+ /* Options without equivalent short options */
+ enum {
+ OPT_4GB = CHAR_MAX + 1,
+ OPT_UNAME26,
+ OPT_LIST
+ };
+
+ /* Options --3gb and --4gb are for compatibility with an old
+ * Debian setarch implementation. */
+ static const struct option longopts[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"addr-no-randomize", no_argument, NULL, 'R'},
+ {"fdpic-funcptrs", no_argument, NULL, 'F'},
+ {"mmap-page-zero", no_argument, NULL, 'Z'},
+ {"addr-compat-layout", no_argument, NULL, 'L'},
+ {"read-implies-exec", no_argument, NULL, 'X'},
+ {"32bit", no_argument, NULL, 'B'},
+ {"short-inode", no_argument, NULL, 'I'},
+ {"whole-seconds", no_argument, NULL, 'S'},
+ {"sticky-timeouts", no_argument, NULL, 'T'},
+ {"3gb", no_argument, NULL, '3'},
+ {"4gb", no_argument, NULL, OPT_4GB},
+ {"uname-2.6", no_argument, NULL, OPT_UNAME26},
+ {"list", no_argument, NULL, OPT_LIST},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ if (argc < 1) {
+ warnx(_("Not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ archwrapper = strcmp(program_invocation_short_name, "setarch") != 0;
+ if (archwrapper) {
+ arch = program_invocation_short_name; /* symlinks to setarch */
+
+ /* Don't use ifdef sparc here, we get "Unrecognized architecture"
+ * error message later if necessary */
+ if (strcmp(arch, "sparc32bash") == 0) {
+ shell = "/bin/bash";
+ shell_arg = "";
+ goto set_arch;
+ }
+ } else {
+ if (1 < argc && *argv[1] != '-') {
+ arch = argv[1];
+ argv[1] = argv[0]; /* for getopt_long() to get the program name */
+ argv++;
+ argc--;
+ }
+ }
+
+ while ((c = getopt_long(argc, argv, "+hVv3BFILRSTXZ", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'R':
+ turn_on(ADDR_NO_RANDOMIZE, options);
+ break;
+ case 'F':
+ turn_on(FDPIC_FUNCPTRS, options);
+ break;
+ case 'Z':
+ turn_on(MMAP_PAGE_ZERO, options);
+ break;
+ case 'L':
+ turn_on(ADDR_COMPAT_LAYOUT, options);
+ break;
+ case 'X':
+ turn_on(READ_IMPLIES_EXEC, options);
+ break;
+ case 'B':
+ turn_on(ADDR_LIMIT_32BIT, options);
+ break;
+ case 'I':
+ turn_on(SHORT_INODE, options);
+ break;
+ case 'S':
+ turn_on(WHOLE_SECONDS, options);
+ break;
+ case 'T':
+ turn_on(STICKY_TIMEOUTS, options);
+ break;
+ case '3':
+ turn_on(ADDR_LIMIT_3GB, options);
+ break;
+ case OPT_4GB: /* just ignore this one */
+ break;
+ case OPT_UNAME26:
+ turn_on(UNAME26, options);
+ break;
+ case OPT_LIST:
+ if (!archwrapper) {
+ list_arch_domains(init_arch_domains());
+ return EXIT_SUCCESS;
+ } else
+ warnx(_("unrecognized option '--list'"));
+ /* fallthrough */
+
+ default:
+ errtryhelp(EXIT_FAILURE);
+ case 'h':
+ usage(archwrapper);
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ }
+ }
+
+ if (!arch && !options)
+ errx(EXIT_FAILURE, _("no architecture argument or personality flags specified"));
+
+ argc -= optind;
+ argv += optind;
+
+set_arch:
+ /* get execution domain (architecture) */
+ if (arch) {
+ doms = init_arch_domains();
+ target = get_arch_domain(doms, arch);
+
+ if (!target)
+ errx(EXIT_FAILURE, _("%s: Unrecognized architecture"), arch);
+ pers_value = target->perval;
+ }
+
+ /* add personality flags */
+ pers_value |= options;
+
+ /* call kernel */
+ if (personality(pers_value) < 0) {
+ /*
+ * Depending on architecture and kernel version, personality
+ * syscall is either capable or incapable of returning an error.
+ * If the return value is not an error, then it's the previous
+ * personality value, which can be an arbitrary value
+ * undistinguishable from an error value.
+ * To make things clear, a second call is needed.
+ */
+ if (personality(pers_value) < 0)
+ err(EXIT_FAILURE, _("failed to set personality to %s"), arch);
+ }
+
+ /* make sure architecture is set as expected */
+ if (arch)
+ verify_arch_domain(doms, target, arch);
+
+ if (!argc) {
+ shell = "/bin/sh";
+ shell_arg = "-sh";
+ }
+ if (verbose) {
+ printf(_("Execute command `%s'.\n"), shell ? shell : argv[0]);
+ /* flush all output streams before exec */
+ fflush(NULL);
+ }
+
+ /* Execute shell */
+ if (shell) {
+ execl(shell, shell_arg, (char *)NULL);
+ errexec(shell);
+ }
+
+ /* Execute on command line specified command */
+ execvp(argv[0], argv);
+ errexec(argv[0]);
+}
diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1
new file mode 100644
index 0000000..42d1a2f
--- /dev/null
+++ b/sys-utils/setpriv.1
@@ -0,0 +1,251 @@
+.TH SETPRIV 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+setpriv \- run a program with different Linux privilege settings
+.SH SYNOPSIS
+.B setpriv
+[options]
+.I program
+.RI [ arguments ]
+.SH DESCRIPTION
+Sets or queries various Linux privilege settings that are inherited across
+.BR execve (2).
+.PP
+In comparison to
+.BR su (1)
+and
+.BR runuser (1),
+.B setpriv
+neither uses PAM, nor does it prompt for a password.
+It is a simple, non-set-user-ID wrapper around
+.BR execve (2),
+and can be used to drop privileges in the same way as
+.BR setuidgid (8)
+from
+.BR daemontools ,
+.BR chpst (8)
+from
+.BR runit ,
+or similar tools shipped by other service managers.
+.SH OPTIONS
+.TP
+.B \-\-clear\-groups
+Clear supplementary groups.
+.TP
+.BR \-d , " \-\-dump"
+Dump the current privilege state.
+This option can be specified more than once to show extra,
+mostly useless, information. Incompatible with all other options.
+.TP
+.B \-\-groups \fIgroup\fR...
+Set supplementary groups. The argument is a comma-separated list of GIDs or names.
+.TP
+.BR \-\-inh\-caps " (" + | \- ) \fIcap "... or " \-\-ambient-caps " (" + | \- ) \fIcap "... or " \-\-bounding\-set " (" + | \- ) \fIcap ...
+Set the inheritable capabilities, ambient capabilities or the capability bounding set. See
+.BR capabilities (7).
+The argument is a comma-separated list of
+.BI + cap
+and
+.BI \- cap
+entries, which add or remove an entry respectively. \fIcap\fR can either be a
+human-readable name as seen in
+.BR capabilities (7)
+without the \fIcap_\fR prefix or of the format
+.BR cap_N ,
+where \fIN\fR is the internal capability index used by Linux.
+.B +all
+and
+.B \-all
+can be used to add or remove all caps.
+.IP
+The set of capabilities starts out as
+the current inheritable set for
+.BR \-\-inh\-caps ,
+the current ambient set for
+.B \-\-ambient\-caps
+and the current bounding set for
+.BR \-\-bounding\-set .
+.IP
+Note the following restrictions (detailed in
+.BR capabilities (7))
+regarding modifications to these capability sets:
+.RS
+.IP * 2
+A capability can be added to the inheritable set only if it is
+currently present in the bounding set.
+.IP *
+A capability can be added to the ambient set only if it is currently
+present in both the permitted and inheritable sets.
+.IP *
+Notwithstanding the syntax offered by
+.BR setpriv ,
+the kernel does not permit capabilities to be added to the bounding set.
+.RE
+.IP
+If you drop a capability from the bounding set without also dropping it from the
+inheritable set, you are likely to become confused. Do not do that.
+.TP
+.B \-\-keep\-groups
+Preserve supplementary groups. Only useful in conjunction with
+.BR \-\-rgid ,
+.BR \-\-egid ", or"
+.BR \-\-regid .
+.TP
+.B \-\-init\-groups
+Initialize supplementary groups using
+.BR initgroups "(3)."
+Only useful in conjunction with
+.B \-\-ruid
+or
+.BR \-\-reuid .
+.TP
+.B \-\-list\-caps
+List all known capabilities. This option must be specified alone.
+.TP
+.B \-\-no\-new\-privs
+Set the
+.I no_new_privs
+bit. With this bit set,
+.BR execve (2)
+will not grant new privileges.
+For example, the set-user-ID and set-group-ID bits as well
+as file capabilities will be disabled. (Executing binaries with these bits set
+will still work, but they will not gain privileges. Certain LSMs, especially
+AppArmor, may result in failures to execute certain programs.) This bit is
+inherited by child processes and cannot be unset. See
+.BR prctl (2)
+and
+.I Documentation/\:prctl/\:no_\:new_\:privs.txt
+in the Linux kernel source.
+.sp
+The
+.I no_new_privs
+bit is supported since Linux 3.5.
+.TP
+.BI \-\-rgid " gid\fR, " \-\-egid " gid\fR, " \-\-regid " gid"
+Set the real, effective, or both GIDs. The \fIgid\fR argument can be
+given as a textual group name.
+.sp
+For safety, you must specify one of
+.BR \-\-clear\-groups ,
+.BR \-\-groups ,
+.BR \-\-keep\-groups ", or"
+.B \-\-init\-groups
+if you set any primary
+.IR gid .
+.TP
+.BI \-\-ruid " uid\fR, " \-\-euid " uid\fR, " \-\-reuid " uid"
+Set the real, effective, or both UIDs. The \fIuid\fR argument can be
+given as a textual login name.
+.sp
+Setting a
+.I uid
+or
+.I gid
+does not change capabilities, although the exec call at the end might change
+capabilities. This means that, if you are root, you probably want to do
+something like:
+.sp
+.B " setpriv \-\-reuid=1000 \-\-regid=1000 \-\-inh\-caps=\-all"
+.TP
+.BR \-\-securebits " (" + | \- ) \fIsecurebit ...
+Set or clear securebits. The argument is a comma-separated list.
+The valid securebits are
+.IR noroot ,
+.IR noroot_locked ,
+.IR no_setuid_fixup ,
+.IR no_setuid_fixup_locked ,
+and
+.IR keep_caps_locked .
+.I keep_caps
+is cleared by
+.BR execve (2)
+and is therefore not allowed.
+.TP
+.BR "\-\-pdeathsig keep" | clear | <signal>
+Keep, clear or set the parent death signal. Some LSMs, most notably SELinux and
+AppArmor, clear the signal when the process' credentials change. Using
+\fB\-\-pdeathsig keep\fR will restore the parent death signal after changing
+credentials to remedy that situation.
+.TP
+.BI \-\-selinux\-label " label"
+Request a particular SELinux transition (using a transition on exec, not
+dyntrans). This will fail and cause
+.B setpriv
+to abort if SELinux is not in use, and the transition may be ignored or cause
+.BR execve (2)
+to fail at SELinux's whim. (In particular, this is unlikely to work in
+conjunction with
+.IR no_new_privs .)
+This is similar to
+.BR runcon (1).
+.TP
+.BI \-\-apparmor\-profile " profile"
+Request a particular AppArmor profile (using a transition on exec). This will
+fail and cause
+.B setpriv
+to abort if AppArmor is not in use, and the transition may be ignored or cause
+.BR execve (2)
+to fail at AppArmor's whim.
+.TP
+.B \-\-reset\-env
+Clears all the environment variables except TERM; initializes the environment variables HOME, SHELL, USER, LOGNAME
+according to the user's passwd entry; sets PATH to \fI/usr/local/bin:/bin:/usr/bin\fR for a regular user and to
+\fI/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\fR for root.
+.sp
+The environment variable PATH may be different on systems where
+.I /bin
+and
+.I /sbin
+are merged into
+.IR /usr .
+The environment variable SHELL defaults to \fI/bin/sh\fR if none is given in the user's
+passwd entry.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+If applying any specified option fails,
+.I program
+will not be run and
+.B setpriv
+will return with exit status 127.
+.PP
+Be careful with this tool \-\- it may have unexpected security consequences.
+For example, setting
+.I no_new_privs
+and then execing a program that is
+SELinux\-confined (as this tool would do) may prevent the SELinux
+restrictions from taking effect.
+.SH EXAMPLES
+If you're looking for behaviour similar to
+.BR su (1)/ runuser "(1), or " sudo (8)
+(without the
+.B \-g
+option), try something like:
+.sp
+.B " setpriv \-\-reuid=1000 \-\-regid=1000 \-\-init\-groups"
+.PP
+If you want to mimic daemontools'
+.BR setuid (8),
+try:
+.sp
+.B " setpriv \-\-reuid=1000 \-\-regid=1000 \-\-clear\-groups"
+.SH AUTHORS
+.MT luto@amacapital.net
+Andy Lutomirski
+.ME
+.SH SEE ALSO
+.BR runuser (1),
+.BR su (1),
+.BR prctl (2),
+.BR capabilities (7)
+.SH AVAILABILITY
+The
+.B setpriv
+command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c
new file mode 100644
index 0000000..f8a0364
--- /dev/null
+++ b/sys-utils/setpriv.c
@@ -0,0 +1,1067 @@
+/*
+ * setpriv(1) - set various kernel privilege bits and run something
+ *
+ * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <cap-ng.h>
+#include <errno.h>
+#include <getopt.h>
+#include <grp.h>
+#include <linux/securebits.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "caputils.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "pathnames.h"
+#include "signames.h"
+#include "env.h"
+
+#ifndef PR_SET_NO_NEW_PRIVS
+# define PR_SET_NO_NEW_PRIVS 38
+#endif
+#ifndef PR_GET_NO_NEW_PRIVS
+# define PR_GET_NO_NEW_PRIVS 39
+#endif
+
+#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */
+
+/* The shell to set SHELL env.variable if none is given in the user's passwd entry. */
+#define DEFAULT_SHELL "/bin/sh"
+
+static gid_t get_group(const char *s, const char *err);
+
+enum cap_type {
+ CAP_TYPE_EFFECTIVE = CAPNG_EFFECTIVE,
+ CAP_TYPE_PERMITTED = CAPNG_PERMITTED,
+ CAP_TYPE_INHERITABLE = CAPNG_INHERITABLE,
+ CAP_TYPE_BOUNDING = CAPNG_BOUNDING_SET,
+ CAP_TYPE_AMBIENT = (1 << 4)
+};
+
+/*
+ * Note: We are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105
+ * and we will therefore have problems if new capabilities are added. Once
+ * that bug is fixed, I'll (Andy Lutomirski) submit a corresponding fix to
+ * setpriv. In the mean time, the code here tries to work reasonably well.
+ */
+
+struct privctx {
+ unsigned int
+ nnp:1, /* no_new_privs */
+ have_ruid:1, /* real uid */
+ have_euid:1, /* effective uid */
+ have_rgid:1, /* real gid */
+ have_egid:1, /* effective gid */
+ have_passwd:1, /* passwd entry */
+ have_groups:1, /* add groups */
+ keep_groups:1, /* keep groups */
+ clear_groups:1, /* remove groups */
+ init_groups:1, /* initialize groups */
+ reset_env:1, /* reset environment */
+ have_securebits:1; /* remove groups */
+
+ /* uids and gids */
+ uid_t ruid, euid;
+ gid_t rgid, egid;
+
+ /* real user passwd entry */
+ struct passwd passwd;
+
+ /* supplementary groups */
+ size_t num_groups;
+ gid_t *groups;
+
+ /* caps */
+ const char *caps_to_inherit;
+ const char *ambient_caps;
+ const char *bounding_set;
+
+ /* securebits */
+ int securebits;
+ /* parent death signal (<0 clear, 0 nothing, >0 signal) */
+ int pdeathsig;
+
+ /* LSMs */
+ const char *selinux_label;
+ const char *apparmor_profile;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] <program> [<argument>...]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Run a program with different privilege settings.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -d, --dump show current state (and do not exec)\n"), out);
+ fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out);
+ fputs(_(" --ambient-caps <caps,...> set ambient capabilities\n"), out);
+ fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out);
+ fputs(_(" --bounding-set <caps> set capability bounding set\n"), out);
+ fputs(_(" --ruid <uid|user> set real uid\n"), out);
+ fputs(_(" --euid <uid|user> set effective uid\n"), out);
+ fputs(_(" --rgid <gid|user> set real gid\n"), out);
+ fputs(_(" --egid <gid|group> set effective gid\n"), out);
+ fputs(_(" --reuid <uid|user> set real and effective uid\n"), out);
+ fputs(_(" --regid <gid|group> set real and effective gid\n"), out);
+ fputs(_(" --clear-groups clear supplementary groups\n"), out);
+ fputs(_(" --keep-groups keep supplementary groups\n"), out);
+ fputs(_(" --init-groups initialize supplementary groups\n"), out);
+ fputs(_(" --groups <group,...> set supplementary groups by UID or name\n"), out);
+ fputs(_(" --securebits <bits> set securebits\n"), out);
+ fputs(_(" --pdeathsig keep|clear|<signame>\n"
+ " set or clear parent death signal\n"), out);
+ fputs(_(" --selinux-label <label> set SELinux label\n"), out);
+ fputs(_(" --apparmor-profile <pr> set AppArmor profile\n"), out);
+ fputs(_(" --reset-env clear all environment and initialize\n"
+ " HOME, SHELL, USER, LOGNAME and PATH\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(29));
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out);
+ printf(USAGE_MAN_TAIL("setpriv(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static int has_cap(enum cap_type which, unsigned int i)
+{
+ switch (which) {
+ case CAP_TYPE_EFFECTIVE:
+ case CAP_TYPE_BOUNDING:
+ case CAP_TYPE_INHERITABLE:
+ case CAP_TYPE_PERMITTED:
+ return capng_have_capability((capng_type_t)which, i);
+ case CAP_TYPE_AMBIENT:
+ return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET,
+ (unsigned long) i, 0UL, 0UL);
+ default:
+ warnx(_("invalid capability type"));
+ return -1;
+ }
+}
+
+/* Returns the number of capabilities printed. */
+static int print_caps(FILE *f, enum cap_type which)
+{
+ int i, n = 0, max = cap_last_cap();
+
+ for (i = 0; i <= max; i++) {
+ int ret = has_cap(which, i);
+
+ if (i == 0 && ret < 0)
+ return -1;
+
+ if (ret == 1) {
+ const char *name = capng_capability_to_name(i);
+ if (n)
+ fputc(',', f);
+ if (name)
+ fputs(name, f);
+ else
+ /* cap-ng has very poor handling of
+ * CAP_LAST_CAP changes. This is the
+ * best we can do. */
+ printf("cap_%d", i);
+ n++;
+ }
+ }
+
+ return n;
+}
+
+static void dump_one_secbit(int *first, int *bits, int bit, const char *name)
+{
+ if (*bits & bit) {
+ if (*first)
+ *first = 0;
+ else
+ printf(",");
+ fputs(name, stdout);
+ *bits &= ~bit;
+ }
+}
+
+static void dump_securebits(void)
+{
+ int first = 1;
+ int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0);
+
+ if (bits < 0) {
+ warnx(_("getting process secure bits failed"));
+ return;
+ }
+
+ printf(_("Securebits: "));
+
+ dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot");
+ dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked");
+ dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP,
+ "no_setuid_fixup");
+ dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP_LOCKED,
+ "no_setuid_fixup_locked");
+ bits &= ~SECBIT_KEEP_CAPS;
+ dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED,
+ "keep_caps_locked");
+ if (bits) {
+ if (first)
+ first = 0;
+ else
+ printf(",");
+ printf("0x%x", (unsigned)bits);
+ }
+
+ if (first)
+ printf(_("[none]\n"));
+ else
+ printf("\n");
+}
+
+static void dump_label(const char *name)
+{
+ char buf[4097];
+ ssize_t len;
+ int fd, e;
+
+ fd = open(_PATH_PROC_ATTR_CURRENT, O_RDONLY);
+ if (fd == -1) {
+ warn(_("cannot open %s"), _PATH_PROC_ATTR_CURRENT);
+ return;
+ }
+
+ len = read(fd, buf, sizeof(buf));
+ e = errno;
+ close(fd);
+ if (len < 0) {
+ errno = e;
+ warn(_("cannot read %s"), name);
+ return;
+ }
+ if (sizeof(buf) - 1 <= (size_t)len) {
+ warnx(_("%s: too long"), name);
+ return;
+ }
+
+ buf[len] = 0;
+ if (0 < len && buf[len - 1] == '\n')
+ buf[len - 1] = 0;
+ printf("%s: %s\n", name, buf);
+}
+
+static void dump_groups(void)
+{
+ int n = getgroups(0, NULL);
+ gid_t *groups;
+
+ if (n < 0) {
+ warn("getgroups failed");
+ return;
+ }
+
+ groups = xmalloc(n * sizeof(gid_t));
+ n = getgroups(n, groups);
+ if (n < 0) {
+ free(groups);
+ warn("getgroups failed");
+ return;
+ }
+
+ printf(_("Supplementary groups: "));
+ if (n == 0)
+ printf(_("[none]"));
+ else {
+ int i;
+ for (i = 0; i < n; i++) {
+ if (0 < i)
+ printf(",");
+ printf("%ld", (long)groups[i]);
+ }
+ }
+ printf("\n");
+ free(groups);
+}
+
+static void dump_pdeathsig(void)
+{
+ int pdeathsig;
+
+ if (prctl(PR_GET_PDEATHSIG, &pdeathsig) != 0) {
+ warn(_("get pdeathsig failed"));
+ return;
+ }
+
+ printf("Parent death signal: ");
+ if (pdeathsig && signum_to_signame(pdeathsig) != NULL)
+ printf("%s\n", signum_to_signame(pdeathsig));
+ else if (pdeathsig)
+ printf("%d\n", pdeathsig);
+ else
+ printf("[none]\n");
+}
+
+static void dump(int dumplevel)
+{
+ int x;
+ uid_t ru, eu, su;
+ gid_t rg, eg, sg;
+
+ if (getresuid(&ru, &eu, &su) == 0) {
+ printf(_("uid: %u\n"), ru);
+ printf(_("euid: %u\n"), eu);
+ /* Saved and fs uids always equal euid. */
+ if (3 <= dumplevel)
+ printf(_("suid: %u\n"), su);
+ } else
+ warn(_("getresuid failed"));
+
+ if (getresgid(&rg, &eg, &sg) == 0) {
+ printf("gid: %ld\n", (long)rg);
+ printf("egid: %ld\n", (long)eg);
+ /* Saved and fs gids always equal egid. */
+ if (dumplevel >= 3)
+ printf("sgid: %ld\n", (long)sg);
+ } else
+ warn(_("getresgid failed"));
+
+ dump_groups();
+
+ x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+ if (0 <= x)
+ printf("no_new_privs: %d\n", x);
+ else
+ warn("setting no_new_privs failed");
+
+ if (2 <= dumplevel) {
+ printf(_("Effective capabilities: "));
+ if (print_caps(stdout, CAP_TYPE_EFFECTIVE) == 0)
+ printf(_("[none]"));
+ printf("\n");
+
+ printf(_("Permitted capabilities: "));
+ if (print_caps(stdout, CAP_TYPE_PERMITTED) == 0)
+ printf(_("[none]"));
+ printf("\n");
+ }
+
+ printf(_("Inheritable capabilities: "));
+ if (print_caps(stdout, CAP_TYPE_INHERITABLE) == 0)
+ printf(_("[none]"));
+ printf("\n");
+
+ printf(_("Ambient capabilities: "));
+ x = print_caps(stdout, CAP_TYPE_AMBIENT);
+ if (x == 0)
+ printf(_("[none]"));
+ if (x < 0)
+ printf(_("[unsupported]"));
+ printf("\n");
+
+ printf(_("Capability bounding set: "));
+ if (print_caps(stdout, CAP_TYPE_BOUNDING) == 0)
+ printf(_("[none]"));
+ printf("\n");
+
+ dump_securebits();
+ dump_pdeathsig();
+
+ if (access(_PATH_SYS_SELINUX, F_OK) == 0)
+ dump_label(_("SELinux label"));
+
+ if (access(_PATH_SYS_APPARMOR, F_OK) == 0) {
+ dump_label(_("AppArmor profile"));
+ }
+}
+
+static void list_known_caps(void)
+{
+ int i, max = cap_last_cap();
+
+ for (i = 0; i <= max; i++) {
+ const char *name = capng_capability_to_name(i);
+ if (name)
+ printf("%s\n", name);
+ else
+ warnx(_("cap %d: libcap-ng is broken"), i);
+ }
+}
+
+static void parse_groups(struct privctx *opts, const char *str)
+{
+ char *groups = xstrdup(str);
+ char *buf = groups; /* We'll reuse it */
+ char *c;
+ size_t i = 0;
+
+ opts->have_groups = 1;
+ opts->num_groups = 0;
+ while ((c = strsep(&groups, ",")))
+ opts->num_groups++;
+
+ /* Start again */
+ strcpy(buf, str); /* It's exactly the right length */
+ groups = buf;
+
+ opts->groups = xcalloc(opts->num_groups, sizeof(gid_t));
+ while ((c = strsep(&groups, ",")))
+ opts->groups[i++] = get_group(c, _("Invalid supplementary group id"));
+
+ free(groups);
+}
+
+static void parse_pdeathsig(struct privctx *opts, const char *str)
+{
+ if (!strcmp(str, "keep")) {
+ if (prctl(PR_GET_PDEATHSIG, &opts->pdeathsig) != 0)
+ errx(SETPRIV_EXIT_PRIVERR,
+ _("failed to get parent death signal"));
+ } else if (!strcmp(str, "clear")) {
+ opts->pdeathsig = -1;
+ } else if ((opts->pdeathsig = signame_to_signum(str)) < 0) {
+ errx(EXIT_FAILURE, _("unknown signal: %s"), str);
+ }
+}
+
+static void do_setresuid(const struct privctx *opts)
+{
+ uid_t ruid, euid, suid;
+ if (getresuid(&ruid, &euid, &suid) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("getresuid failed"));
+ if (opts->have_ruid)
+ ruid = opts->ruid;
+ if (opts->have_euid)
+ euid = opts->euid;
+
+ /* Also copy effective to saved (for paranoia). */
+ if (setresuid(ruid, euid, euid) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("setresuid failed"));
+}
+
+static void do_setresgid(const struct privctx *opts)
+{
+ gid_t rgid, egid, sgid;
+ if (getresgid(&rgid, &egid, &sgid) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("getresgid failed"));
+ if (opts->have_rgid)
+ rgid = opts->rgid;
+ if (opts->have_egid)
+ egid = opts->egid;
+
+ /* Also copy effective to saved (for paranoia). */
+ if (setresgid(rgid, egid, egid) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("setresgid failed"));
+}
+
+static void bump_cap(unsigned int cap)
+{
+ if (capng_have_capability(CAPNG_PERMITTED, cap))
+ capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap);
+}
+
+static int cap_update(capng_act_t action,
+ enum cap_type type, unsigned int cap)
+{
+ switch (type) {
+ case CAP_TYPE_EFFECTIVE:
+ case CAP_TYPE_BOUNDING:
+ case CAP_TYPE_INHERITABLE:
+ case CAP_TYPE_PERMITTED:
+ return capng_update(action, (capng_type_t) type, cap);
+ case CAP_TYPE_AMBIENT:
+ {
+ int ret;
+
+ if (action == CAPNG_ADD)
+ ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE,
+ (unsigned long) cap, 0UL, 0UL);
+ else
+ ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER,
+ (unsigned long) cap, 0UL, 0UL);
+
+ return ret;
+ }
+ default:
+ errx(EXIT_FAILURE, _("unsupported capability type"));
+ return -1;
+ }
+}
+
+static void do_caps(enum cap_type type, const char *caps)
+{
+ char *my_caps = xstrdup(caps);
+ char *c;
+
+ while ((c = strsep(&my_caps, ","))) {
+ capng_act_t action;
+ if (*c == '+')
+ action = CAPNG_ADD;
+ else if (*c == '-')
+ action = CAPNG_DROP;
+ else
+ errx(EXIT_FAILURE, _("bad capability string"));
+
+ if (!strcmp(c + 1, "all")) {
+ int i;
+ /* It would be really bad if -all didn't drop all
+ * caps. It's better to just fail. */
+ if (cap_last_cap() > CAP_LAST_CAP)
+ errx(SETPRIV_EXIT_PRIVERR,
+ _("libcap-ng is too old for \"all\" caps"));
+ for (i = 0; i <= CAP_LAST_CAP; i++)
+ cap_update(action, type, i);
+ } else {
+ int cap = capng_name_to_capability(c + 1);
+ if (0 <= cap)
+ cap_update(action, type, cap);
+ else if (sscanf(c + 1, "cap_%d", &cap) == 1
+ && 0 <= cap && cap <= cap_last_cap())
+ cap_update(action, type, cap);
+ else
+ errx(EXIT_FAILURE,
+ _("unknown capability \"%s\""), c + 1);
+ }
+ }
+
+ free(my_caps);
+}
+
+static void parse_securebits(struct privctx *opts, const char *arg)
+{
+ char *buf = xstrdup(arg);
+ char *c;
+
+ opts->have_securebits = 1;
+ opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0);
+ if (opts->securebits < 0)
+ err(SETPRIV_EXIT_PRIVERR, _("getting process secure bits failed"));
+
+ if (opts->securebits & ~(int)(SECBIT_NOROOT |
+ SECBIT_NOROOT_LOCKED |
+ SECBIT_NO_SETUID_FIXUP |
+ SECBIT_NO_SETUID_FIXUP_LOCKED |
+ SECBIT_KEEP_CAPS |
+ SECBIT_KEEP_CAPS_LOCKED))
+ errx(SETPRIV_EXIT_PRIVERR,
+ _("unrecognized securebit set -- refusing to adjust"));
+
+ while ((c = strsep(&buf, ","))) {
+ if (*c != '+' && *c != '-')
+ errx(EXIT_FAILURE, _("bad securebits string"));
+
+ if (!strcmp(c + 1, "all")) {
+ if (*c == '-')
+ opts->securebits = 0;
+ else
+ errx(EXIT_FAILURE,
+ _("+all securebits is not allowed"));
+ } else {
+ int bit;
+ if (!strcmp(c + 1, "noroot"))
+ bit = SECBIT_NOROOT;
+ else if (!strcmp(c + 1, "noroot_locked"))
+ bit = SECBIT_NOROOT_LOCKED;
+ else if (!strcmp(c + 1, "no_setuid_fixup"))
+ bit = SECBIT_NO_SETUID_FIXUP;
+ else if (!strcmp(c + 1, "no_setuid_fixup_locked"))
+ bit = SECBIT_NO_SETUID_FIXUP_LOCKED;
+ else if (!strcmp(c + 1, "keep_caps"))
+ errx(EXIT_FAILURE,
+ _("adjusting keep_caps does not make sense"));
+ else if (!strcmp(c + 1, "keep_caps_locked"))
+ bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */
+ else
+ errx(EXIT_FAILURE, _("unrecognized securebit"));
+
+ if (*c == '+')
+ opts->securebits |= bit;
+ else
+ opts->securebits &= ~bit;
+ }
+ }
+
+ opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */
+
+ free(buf);
+}
+
+static void do_selinux_label(const char *label)
+{
+ int fd;
+ size_t len;
+
+ if (access(_PATH_SYS_SELINUX, F_OK) != 0)
+ errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running"));
+
+ fd = open(_PATH_PROC_ATTR_EXEC, O_RDWR);
+ if (fd == -1)
+ err(SETPRIV_EXIT_PRIVERR,
+ _("cannot open %s"), _PATH_PROC_ATTR_EXEC);
+
+ len = strlen(label);
+ errno = 0;
+ if (write(fd, label, len) != (ssize_t) len)
+ err(SETPRIV_EXIT_PRIVERR,
+ _("write failed: %s"), _PATH_PROC_ATTR_EXEC);
+
+ if (close(fd) != 0)
+ err(SETPRIV_EXIT_PRIVERR,
+ _("close failed: %s"), _PATH_PROC_ATTR_EXEC);
+}
+
+static void do_apparmor_profile(const char *label)
+{
+ FILE *f;
+
+ if (access(_PATH_SYS_APPARMOR, F_OK) != 0)
+ errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running"));
+
+ f = fopen(_PATH_PROC_ATTR_EXEC, "r+");
+ if (!f)
+ err(SETPRIV_EXIT_PRIVERR,
+ _("cannot open %s"), _PATH_PROC_ATTR_EXEC);
+
+ fprintf(f, "exec %s", label);
+
+ if (close_stream(f) != 0)
+ err(SETPRIV_EXIT_PRIVERR,
+ _("write failed: %s"), _PATH_PROC_ATTR_EXEC);
+}
+
+
+static void do_reset_environ(struct passwd *pw)
+{
+ char *term = getenv("TERM");
+
+ if (term)
+ term = xstrdup(term);
+#ifdef HAVE_CLEARENV
+ clearenv();
+#else
+ environ = NULL;
+#endif
+ if (term) {
+ xsetenv("TERM", term, 1);
+ free(term);
+ }
+
+ if (pw->pw_shell && *pw->pw_shell)
+ xsetenv("SHELL", pw->pw_shell, 1);
+ else
+ xsetenv("SHELL", DEFAULT_SHELL, 1);
+
+ xsetenv("HOME", pw->pw_dir, 1);
+ xsetenv("USER", pw->pw_name, 1);
+ xsetenv("LOGNAME", pw->pw_name, 1);
+
+ if (pw->pw_uid)
+ xsetenv("PATH", _PATH_DEFPATH, 1);
+ else
+ xsetenv("PATH", _PATH_DEFPATH_ROOT, 1);
+}
+
+static uid_t get_user(const char *s, const char *err)
+{
+ struct passwd *pw;
+ long tmp;
+ pw = getpwnam(s);
+ if (pw)
+ return pw->pw_uid;
+ tmp = strtol_or_err(s, err);
+ return tmp;
+}
+
+static gid_t get_group(const char *s, const char *err)
+{
+ struct group *gr;
+ long tmp;
+ gr = getgrnam(s);
+ if (gr)
+ return gr->gr_gid;
+ tmp = strtol_or_err(s, err);
+ return tmp;
+}
+
+static struct passwd *get_passwd(const char *s, uid_t *uid, const char *err)
+{
+ struct passwd *pw;
+ long tmp;
+ pw = getpwnam(s);
+ if (pw) {
+ *uid = pw->pw_uid;
+ } else {
+ tmp = strtol_or_err(s, err);
+ *uid = tmp;
+ pw = getpwuid(*uid);
+ }
+ return pw;
+}
+
+static struct passwd *passwd_copy(struct passwd *dst, const struct passwd *src)
+{
+ struct passwd *rv;
+ rv = memcpy(dst, src, sizeof(*dst));
+ rv->pw_name = xstrdup(rv->pw_name);
+ rv->pw_passwd = xstrdup(rv->pw_passwd);
+ rv->pw_gecos = xstrdup(rv->pw_gecos);
+ rv->pw_dir = xstrdup(rv->pw_dir);
+ rv->pw_shell = xstrdup(rv->pw_shell);
+ return rv;
+}
+
+int main(int argc, char **argv)
+{
+ enum {
+ NNP = CHAR_MAX + 1,
+ RUID,
+ EUID,
+ RGID,
+ EGID,
+ REUID,
+ REGID,
+ CLEAR_GROUPS,
+ KEEP_GROUPS,
+ INIT_GROUPS,
+ GROUPS,
+ INHCAPS,
+ AMBCAPS,
+ LISTCAPS,
+ CAPBSET,
+ SECUREBITS,
+ PDEATHSIG,
+ SELINUX_LABEL,
+ APPARMOR_PROFILE,
+ RESET_ENV
+ };
+
+ static const struct option longopts[] = {
+ { "dump", no_argument, NULL, 'd' },
+ { "nnp", no_argument, NULL, NNP },
+ { "no-new-privs", no_argument, NULL, NNP },
+ { "inh-caps", required_argument, NULL, INHCAPS },
+ { "ambient-caps", required_argument, NULL, AMBCAPS },
+ { "list-caps", no_argument, NULL, LISTCAPS },
+ { "ruid", required_argument, NULL, RUID },
+ { "euid", required_argument, NULL, EUID },
+ { "rgid", required_argument, NULL, RGID },
+ { "egid", required_argument, NULL, EGID },
+ { "reuid", required_argument, NULL, REUID },
+ { "regid", required_argument, NULL, REGID },
+ { "clear-groups", no_argument, NULL, CLEAR_GROUPS },
+ { "keep-groups", no_argument, NULL, KEEP_GROUPS },
+ { "init-groups", no_argument, NULL, INIT_GROUPS },
+ { "groups", required_argument, NULL, GROUPS },
+ { "bounding-set", required_argument, NULL, CAPBSET },
+ { "securebits", required_argument, NULL, SECUREBITS },
+ { "pdeathsig", required_argument, NULL, PDEATHSIG, },
+ { "selinux-label", required_argument, NULL, SELINUX_LABEL },
+ { "apparmor-profile", required_argument, NULL, APPARMOR_PROFILE },
+ { "help", no_argument, NULL, 'h' },
+ { "reset-env", no_argument, NULL, RESET_ENV, },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = {
+ /* keep in same order with enum definitions */
+ {CLEAR_GROUPS, KEEP_GROUPS, INIT_GROUPS, GROUPS},
+ {0}
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ int c;
+ struct privctx opts;
+ struct passwd *pw = NULL;
+ int dumplevel = 0;
+ int total_opts = 0;
+ int list_caps = 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ memset(&opts, 0, sizeof(opts));
+
+ while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) {
+ err_exclusive_options(c, longopts, excl, excl_st);
+ total_opts++;
+ switch (c) {
+ case 'd':
+ dumplevel++;
+ break;
+ case NNP:
+ if (opts.nnp)
+ errx(EXIT_FAILURE,
+ _("duplicate --no-new-privs option"));
+ opts.nnp = 1;
+ break;
+ case RUID:
+ if (opts.have_ruid)
+ errx(EXIT_FAILURE, _("duplicate ruid"));
+ opts.have_ruid = 1;
+ pw = get_passwd(optarg, &opts.ruid, _("failed to parse ruid"));
+ if (pw) {
+ passwd_copy(&opts.passwd, pw);
+ opts.have_passwd = 1;
+ }
+ break;
+ case EUID:
+ if (opts.have_euid)
+ errx(EXIT_FAILURE, _("duplicate euid"));
+ opts.have_euid = 1;
+ opts.euid = get_user(optarg, _("failed to parse euid"));
+ break;
+ case REUID:
+ if (opts.have_ruid || opts.have_euid)
+ errx(EXIT_FAILURE, _("duplicate ruid or euid"));
+ opts.have_ruid = opts.have_euid = 1;
+ pw = get_passwd(optarg, &opts.ruid, _("failed to parse reuid"));
+ opts.euid = opts.ruid;
+ if (pw) {
+ passwd_copy(&opts.passwd, pw);
+ opts.have_passwd = 1;
+ }
+ break;
+ case RGID:
+ if (opts.have_rgid)
+ errx(EXIT_FAILURE, _("duplicate rgid"));
+ opts.have_rgid = 1;
+ opts.rgid = get_group(optarg, _("failed to parse rgid"));
+ break;
+ case EGID:
+ if (opts.have_egid)
+ errx(EXIT_FAILURE, _("duplicate egid"));
+ opts.have_egid = 1;
+ opts.egid = get_group(optarg, _("failed to parse egid"));
+ break;
+ case REGID:
+ if (opts.have_rgid || opts.have_egid)
+ errx(EXIT_FAILURE, _("duplicate rgid or egid"));
+ opts.have_rgid = opts.have_egid = 1;
+ opts.rgid = opts.egid = get_group(optarg, _("failed to parse regid"));
+ break;
+ case CLEAR_GROUPS:
+ if (opts.clear_groups)
+ errx(EXIT_FAILURE,
+ _("duplicate --clear-groups option"));
+ opts.clear_groups = 1;
+ break;
+ case KEEP_GROUPS:
+ if (opts.keep_groups)
+ errx(EXIT_FAILURE,
+ _("duplicate --keep-groups option"));
+ opts.keep_groups = 1;
+ break;
+ case INIT_GROUPS:
+ if (opts.init_groups)
+ errx(EXIT_FAILURE,
+ _("duplicate --init-groups option"));
+ opts.init_groups = 1;
+ break;
+ case GROUPS:
+ if (opts.have_groups)
+ errx(EXIT_FAILURE,
+ _("duplicate --groups option"));
+ parse_groups(&opts, optarg);
+ break;
+ case PDEATHSIG:
+ if (opts.pdeathsig)
+ errx(EXIT_FAILURE,
+ _("duplicate --keep-pdeathsig option"));
+ parse_pdeathsig(&opts, optarg);
+ break;
+ case LISTCAPS:
+ list_caps = 1;
+ break;
+ case INHCAPS:
+ if (opts.caps_to_inherit)
+ errx(EXIT_FAILURE,
+ _("duplicate --inh-caps option"));
+ opts.caps_to_inherit = optarg;
+ break;
+ case AMBCAPS:
+ if (opts.ambient_caps)
+ errx(EXIT_FAILURE,
+ _("duplicate --ambient-caps option"));
+ opts.ambient_caps = optarg;
+ break;
+ case CAPBSET:
+ if (opts.bounding_set)
+ errx(EXIT_FAILURE,
+ _("duplicate --bounding-set option"));
+ opts.bounding_set = optarg;
+ break;
+ case SECUREBITS:
+ if (opts.have_securebits)
+ errx(EXIT_FAILURE,
+ _("duplicate --securebits option"));
+ parse_securebits(&opts, optarg);
+ break;
+ case SELINUX_LABEL:
+ if (opts.selinux_label)
+ errx(EXIT_FAILURE,
+ _("duplicate --selinux-label option"));
+ opts.selinux_label = optarg;
+ break;
+ case APPARMOR_PROFILE:
+ if (opts.apparmor_profile)
+ errx(EXIT_FAILURE,
+ _("duplicate --apparmor-profile option"));
+ opts.apparmor_profile = optarg;
+ break;
+ case RESET_ENV:
+ opts.reset_env = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (dumplevel) {
+ if (total_opts != dumplevel || optind < argc)
+ errx(EXIT_FAILURE,
+ _("--dump is incompatible with all other options"));
+ dump(dumplevel);
+ return EXIT_SUCCESS;
+ }
+
+ if (list_caps) {
+ if (total_opts != 1 || optind < argc)
+ errx(EXIT_FAILURE,
+ _("--list-caps must be specified alone"));
+ list_known_caps();
+ return EXIT_SUCCESS;
+ }
+
+ if (argc <= optind)
+ errx(EXIT_FAILURE, _("No program specified"));
+
+ if ((opts.have_rgid || opts.have_egid)
+ && !opts.keep_groups && !opts.clear_groups && !opts.init_groups
+ && !opts.have_groups)
+ errx(EXIT_FAILURE,
+ _("--[re]gid requires --keep-groups, --clear-groups, --init-groups, or --groups"));
+
+ if (opts.init_groups && !opts.have_ruid)
+ errx(EXIT_FAILURE,
+ _("--init-groups requires --ruid or --reuid"));
+
+ if (opts.init_groups && !opts.have_passwd)
+ errx(EXIT_FAILURE,
+ _("uid %ld not found, --init-groups requires an user that "
+ "can be found on the system"),
+ (long) opts.ruid);
+
+ if (opts.reset_env) {
+ if (opts.have_passwd)
+ /* pwd according to --ruid or --reuid */
+ pw = &opts.passwd;
+ else
+ /* pwd for the current user */
+ pw = getpwuid(getuid());
+ do_reset_environ(pw);
+ }
+
+ if (opts.nnp && prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
+ err(EXIT_FAILURE, _("disallow granting new privileges failed"));
+
+ if (opts.selinux_label)
+ do_selinux_label(opts.selinux_label);
+ if (opts.apparmor_profile)
+ do_apparmor_profile(opts.apparmor_profile);
+
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1)
+ err(EXIT_FAILURE, _("keep process capabilities failed"));
+
+ /* We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if
+ * possible. */
+ bump_cap(CAP_SETPCAP);
+ bump_cap(CAP_SETUID);
+ bump_cap(CAP_SETGID);
+ if (capng_apply(CAPNG_SELECT_CAPS) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("activate capabilities"));
+
+ if (opts.have_ruid || opts.have_euid) {
+ do_setresuid(&opts);
+ /* KEEPCAPS doesn't work for the effective mask. */
+ if (capng_apply(CAPNG_SELECT_CAPS) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities"));
+ }
+
+ if (opts.have_rgid || opts.have_egid)
+ do_setresgid(&opts);
+
+ if (opts.have_groups) {
+ if (setgroups(opts.num_groups, opts.groups) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("setgroups failed"));
+ } else if (opts.init_groups) {
+ if (initgroups(opts.passwd.pw_name, opts.passwd.pw_gid) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("initgroups failed"));
+ } else if (opts.clear_groups) {
+ gid_t x = 0;
+ if (setgroups(0, &x) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("setgroups failed"));
+ }
+
+ if (opts.have_securebits && prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("set process securebits failed"));
+
+ if (opts.bounding_set) {
+ do_caps(CAP_TYPE_BOUNDING, opts.bounding_set);
+ errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */
+ if (capng_apply(CAPNG_SELECT_BOUNDS) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("apply bounding set"));
+ }
+
+ if (opts.caps_to_inherit) {
+ do_caps(CAP_TYPE_INHERITABLE, opts.caps_to_inherit);
+ if (capng_apply(CAPNG_SELECT_CAPS) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("apply capabilities"));
+ }
+
+ if (opts.ambient_caps) {
+ do_caps(CAP_TYPE_AMBIENT, opts.ambient_caps);
+ }
+
+ /* Clear or set parent death signal */
+ if (opts.pdeathsig && prctl(PR_SET_PDEATHSIG, opts.pdeathsig < 0 ? 0 : opts.pdeathsig) != 0)
+ err(SETPRIV_EXIT_PRIVERR, _("set parent death signal failed"));
+
+ execvp(argv[optind], argv + optind);
+ errexec(argv[optind]);
+}
diff --git a/sys-utils/setsid.1 b/sys-utils/setsid.1
new file mode 100644
index 0000000..c025076
--- /dev/null
+++ b/sys-utils/setsid.1
@@ -0,0 +1,42 @@
+.\" Rick Sladkey <jrs@world.std.com>
+.\" In the public domain.
+.TH SETSID 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+setsid \- run a program in a new session
+.SH SYNOPSIS
+.B setsid
+[options]
+.I program
+.RI [ arguments ]
+.SH DESCRIPTION
+.B setsid
+runs a program in a new session. The command calls
+.BR fork (2)
+if already a process group leader. Otherwise, it executes a program in the
+current process. This default behavior is possible to override by
+the \fB\-\-fork\fR option.
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-ctty"
+Set the controlling terminal to the current one.
+.TP
+.BR \-f , " \-\-fork"
+Always create a new process.
+.TP
+.BR \-w , " \-\-wait"
+Wait for the execution of the program to end, and return the exit status of
+this program as the exit status of
+.BR setsid .
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+Rick Sladkey <jrs@world.std.com>
+.SH SEE ALSO
+.BR setsid (2)
+.SH AVAILABILITY
+The setsid command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/setsid.c b/sys-utils/setsid.c
new file mode 100644
index 0000000..5725e80
--- /dev/null
+++ b/sys-utils/setsid.c
@@ -0,0 +1,123 @@
+/*
+ * setsid.c -- execute a command in a new session
+ * Rick Sladkey <jrs@world.std.com>
+ * In the public domain.
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2001-01-18 John Fremlin <vii@penguinpowered.com>
+ * - fork in case we are process group leader
+ *
+ * 2008-08-20 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ * - if forked, wait on child process and emit its return code.
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %s [options] <program> [arguments ...]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Run a program in a new session.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -c, --ctty set the controlling terminal to the current one\n"), out);
+ fputs(_(" -f, --fork always fork\n"), out);
+ fputs(_(" -w, --wait wait program to exit, and use the same return\n"), out);
+
+ printf(USAGE_HELP_OPTIONS(16));
+
+ printf(USAGE_MAN_TAIL("setsid(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int ch, forcefork = 0;
+ int ctty = 0;
+ pid_t pid;
+ int status = 0;
+
+ static const struct option longopts[] = {
+ {"ctty", no_argument, NULL, 'c'},
+ {"fork", no_argument, NULL, 'f'},
+ {"wait", no_argument, NULL, 'w'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "+Vhcfw", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'c':
+ ctty=1;
+ break;
+ case 'f':
+ forcefork = 1;
+ break;
+ case 'w':
+ status = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (argc - optind < 1) {
+ warnx(_("no command specified"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (forcefork || getpgrp() == getpid()) {
+ pid = fork();
+ switch (pid) {
+ case -1:
+ err(EXIT_FAILURE, _("fork"));
+ case 0:
+ /* child */
+ break;
+ default:
+ /* parent */
+ if (!status)
+ return EXIT_SUCCESS;
+ if (wait(&status) != pid)
+ err(EXIT_FAILURE, "wait");
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ err(status, _("child %d did not exit normally"), pid);
+ }
+ }
+ if (setsid() < 0)
+ /* cannot happen */
+ err(EXIT_FAILURE, _("setsid failed"));
+
+ if (ctty && ioctl(STDIN_FILENO, TIOCSCTTY, 1))
+ err(EXIT_FAILURE, _("failed to set the controlling terminal"));
+ execvp(argv[optind], argv + optind);
+ errexec(argv[optind]);
+}
diff --git a/sys-utils/swapoff.8 b/sys-utils/swapoff.8
new file mode 100644
index 0000000..1a06b7e
--- /dev/null
+++ b/sys-utils/swapoff.8
@@ -0,0 +1 @@
+.so man8/swapon.8
diff --git a/sys-utils/swapoff.c b/sys-utils/swapoff.c
new file mode 100644
index 0000000..7bfb90a
--- /dev/null
+++ b/sys-utils/swapoff.c
@@ -0,0 +1,281 @@
+#include <stdio.h>
+#include <errno.h>
+#include <getopt.h>
+
+#ifdef HAVE_SYS_SWAP_H
+# include <sys/swap.h>
+#endif
+
+#include "nls.h"
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+
+#include "swapprober.h"
+#include "swapon-common.h"
+
+#if !defined(HAVE_SWAPOFF) && defined(SYS_swapoff)
+# include <sys/syscall.h>
+# define swapoff(path) syscall(SYS_swapoff, path)
+#endif
+
+static int verbose;
+static int all;
+
+#define QUIET 1
+#define CANONIC 1
+
+#define SWAPOFF_EX_OK 0 /* no errors */
+#define SWAPOFF_EX_ENOMEM 2 /* swapoff(2) failed due to OOM */
+#define SWAPOFF_EX_FAILURE 4 /* swapoff(2) failed due to another reason */
+#define SWAPOFF_EX_SYSERR 8 /* non-swaoff() errors */
+#define SWAPOFF_EX_USAGE 16 /* usage, permissions or syntax error */
+#define SWAPOFF_EX_ALLERR 32 /* --all all failed */
+#define SWAPOFF_EX_SOMEOK 64 /* --all some failed some OK */
+
+/*
+ * This function works like mnt_resolve_tag(), but it's able to read UUID/LABEL
+ * from regular swap files too (according to entries in /proc/swaps). Note that
+ * mnt_resolve_tag() and mnt_resolve_spec() works with system visible block
+ * devices only.
+ */
+static char *swapoff_resolve_tag(const char *name, const char *value,
+ struct libmnt_cache *cache)
+{
+ char *path;
+ struct libmnt_table *tb;
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+
+ /* this is usual case for block devices (and it's really fast as it uses
+ * udev /dev/disk/by-* symlinks by default */
+ path = mnt_resolve_tag(name, value, cache);
+ if (path)
+ return path;
+
+ /* try regular files from /proc/swaps */
+ tb = get_swaps();
+ if (!tb)
+ return NULL;
+
+ itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ if (!itr)
+ err(SWAPOFF_EX_SYSERR, _("failed to initialize libmount iterator"));
+
+ while (tb && mnt_table_next_fs(tb, itr, &fs) == 0) {
+ blkid_probe pr = NULL;
+ const char *src = mnt_fs_get_source(fs);
+ const char *type = mnt_fs_get_swaptype(fs);
+ const char *data = NULL;
+
+ if (!src || !type || strcmp(type, "file") != 0)
+ continue;
+ pr = get_swap_prober(src);
+ if (!pr)
+ continue;
+ blkid_probe_lookup_value(pr, name, &data, NULL);
+ if (data && strcmp(data, value) == 0)
+ path = xstrdup(src);
+ blkid_free_probe(pr);
+ if (path)
+ break;
+ }
+
+ mnt_free_iter(itr);
+ return path;
+}
+
+static int do_swapoff(const char *orig_special, int quiet, int canonic)
+{
+ const char *special = orig_special;
+ int rc = SWAPOFF_EX_OK;
+
+ if (verbose)
+ printf(_("swapoff %s\n"), orig_special);
+
+ if (!canonic) {
+ char *n, *v;
+
+ special = mnt_resolve_spec(orig_special, mntcache);
+ if (!special && blkid_parse_tag_string(orig_special, &n, &v) == 0) {
+ special = swapoff_resolve_tag(n, v, mntcache);
+ free(n);
+ free(v);
+ }
+ if (!special)
+ return cannot_find(orig_special);
+ }
+
+ if (swapoff(special) == 0)
+ rc = SWAPOFF_EX_OK; /* success */
+ else {
+ switch (errno) {
+ case EPERM:
+ errx(SWAPOFF_EX_USAGE, _("Not superuser."));
+ break;
+ case ENOMEM:
+ warn(_("%s: swapoff failed"), orig_special);
+ rc = SWAPOFF_EX_ENOMEM;
+ break;
+ default:
+ if (!quiet)
+ warn(_("%s: swapoff failed"), orig_special);
+ rc = SWAPOFF_EX_FAILURE;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+static int swapoff_by(const char *name, const char *value, int quiet)
+{
+ const char *special = swapoff_resolve_tag(name, value, mntcache);
+ return special ? do_swapoff(special, quiet, CANONIC) : cannot_find(value);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<spec>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Disable devices and files for paging and swapping.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all disable all swaps from /proc/swaps\n"
+ " -v, --verbose verbose mode\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+
+ fputs(_("\nThe <spec> parameter:\n" \
+ " -L <label> LABEL of device to be used\n" \
+ " -U <uuid> UUID of device to be used\n" \
+ " LABEL=<label> LABEL of device to be used\n" \
+ " UUID=<uuid> UUID of device to be used\n" \
+ " <device> name of device to be used\n" \
+ " <file> name of file to be used\n"), out);
+
+ printf(USAGE_MAN_TAIL("swapoff(8)"));
+ exit(SWAPOFF_EX_OK);
+}
+
+static int swapoff_all(void)
+{
+ int nerrs = 0, nsucc = 0;
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
+
+ if (!itr)
+ err(SWAPOFF_EX_SYSERR, _("failed to initialize libmount iterator"));
+
+ /*
+ * In case /proc/swaps exists, unswap stuff listed there. We are quiet
+ * but report errors in status. Errors might mean that /proc/swaps
+ * exists as ordinary file, not in procfs. do_swapoff() exits
+ * immediately on EPERM.
+ */
+ tb = get_swaps();
+
+ while (tb && mnt_table_find_next_fs(tb, itr, match_swap, NULL, &fs) == 0) {
+ if (do_swapoff(mnt_fs_get_source(fs), QUIET, CANONIC) == SWAPOFF_EX_OK)
+ nsucc++;
+ else
+ nerrs++;
+ }
+
+ /*
+ * Unswap stuff mentioned in /etc/fstab. Probably it was unmounted
+ * already, so errors are not bad. Doing swapoff -a twice should not
+ * give error messages.
+ */
+ tb = get_fstab();
+ mnt_reset_iter(itr, MNT_ITER_FORWARD);
+
+ while (tb && mnt_table_find_next_fs(tb, itr, match_swap, NULL, &fs) == 0) {
+ if (!is_active_swap(mnt_fs_get_source(fs)))
+ do_swapoff(mnt_fs_get_source(fs), QUIET, !CANONIC);
+ }
+
+ mnt_free_iter(itr);
+
+ if (nerrs == 0)
+ return SWAPOFF_EX_OK; /* all success */
+ else if (nsucc == 0)
+ return SWAPOFF_EX_ALLERR; /* all failed */
+
+ return SWAPOFF_EX_SOMEOK; /* some success, some failed */
+}
+
+int main(int argc, char *argv[])
+{
+ int status = 0, c;
+ size_t i;
+
+ static const struct option long_opts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "help", no_argument, NULL, 'h' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "ahvVL:U:",
+ long_opts, NULL)) != -1) {
+ switch (c) {
+ case 'a': /* all */
+ ++all;
+ break;
+ case 'v': /* be chatty */
+ ++verbose;
+ break;
+ case 'L':
+ add_label(optarg);
+ break;
+ case 'U':
+ add_uuid(optarg);
+ break;
+
+ case 'h': /* help */
+ usage();
+ case 'V': /* version */
+ print_version(SWAPOFF_EX_OK);
+ default:
+ errtryhelp(SWAPOFF_EX_USAGE);
+ }
+ }
+ argv += optind;
+
+ if (!all && !numof_labels() && !numof_uuids() && *argv == NULL) {
+ warnx(_("bad usage"));
+ errtryhelp(SWAPOFF_EX_USAGE);
+ }
+
+ mnt_init_debug(0);
+ mntcache = mnt_new_cache();
+
+ for (i = 0; i < numof_labels(); i++)
+ status |= swapoff_by("LABEL", get_label(i), !QUIET);
+
+ for (i = 0; i < numof_uuids(); i++)
+ status |= swapoff_by("UUID", get_uuid(i), !QUIET);
+
+ while (*argv != NULL)
+ status |= do_swapoff(*argv++, !QUIET, !CANONIC);
+
+ if (all)
+ status |= swapoff_all();
+
+ free_tables();
+ mnt_unref_cache(mntcache);
+
+ return status;
+}
diff --git a/sys-utils/swapon-common.c b/sys-utils/swapon-common.c
new file mode 100644
index 0000000..dd1593d
--- /dev/null
+++ b/sys-utils/swapon-common.c
@@ -0,0 +1,117 @@
+
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+
+#include "swapon-common.h"
+
+/*
+ * content of /proc/swaps and /etc/fstab
+ */
+static struct libmnt_table *swaps, *fstab;
+
+struct libmnt_cache *mntcache;
+
+static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)),
+ const char *filename, int line)
+{
+ if (filename)
+ warnx(_("%s: parse error at line %d -- ignored"), filename, line);
+ return 1;
+}
+
+struct libmnt_table *get_fstab(void)
+{
+ if (!fstab) {
+ fstab = mnt_new_table();
+ if (!fstab)
+ return NULL;
+ mnt_table_set_parser_errcb(fstab, table_parser_errcb);
+ mnt_table_set_cache(fstab, mntcache);
+ if (mnt_table_parse_fstab(fstab, NULL) != 0)
+ return NULL;
+ }
+
+ return fstab;
+}
+
+struct libmnt_table *get_swaps(void)
+{
+ if (!swaps) {
+ swaps = mnt_new_table();
+ if (!swaps)
+ return NULL;
+ mnt_table_set_cache(swaps, mntcache);
+ mnt_table_set_parser_errcb(swaps, table_parser_errcb);
+ if (mnt_table_parse_swaps(swaps, NULL) != 0)
+ return NULL;
+ }
+
+ return swaps;
+}
+
+void free_tables(void)
+{
+ mnt_unref_table(swaps);
+ mnt_unref_table(fstab);
+}
+
+int match_swap(struct libmnt_fs *fs, void *data __attribute__((unused)))
+{
+ return fs && mnt_fs_is_swaparea(fs);
+}
+
+int is_active_swap(const char *filename)
+{
+ struct libmnt_table *st = get_swaps();
+ return st && mnt_table_find_source(st, filename, MNT_ITER_BACKWARD);
+}
+
+
+int cannot_find(const char *special)
+{
+ warnx(_("cannot find the device for %s"), special);
+ return -1;
+}
+
+/*
+ * Lists with -L and -U option
+ */
+static const char **llist;
+static size_t llct;
+static const char **ulist;
+static size_t ulct;
+
+
+void add_label(const char *label)
+{
+ llist = xrealloc(llist, (++llct) * sizeof(char *));
+ llist[llct - 1] = label;
+}
+
+const char *get_label(size_t i)
+{
+ return i < llct ? llist[i] : NULL;
+}
+
+size_t numof_labels(void)
+{
+ return llct;
+}
+
+void add_uuid(const char *uuid)
+{
+ ulist = xrealloc(ulist, (++ulct) * sizeof(char *));
+ ulist[ulct - 1] = uuid;
+}
+
+const char *get_uuid(size_t i)
+{
+ return i < ulct ? ulist[i] : NULL;
+}
+
+size_t numof_uuids(void)
+{
+ return ulct;
+}
+
diff --git a/sys-utils/swapon-common.h b/sys-utils/swapon-common.h
new file mode 100644
index 0000000..d1b679f
--- /dev/null
+++ b/sys-utils/swapon-common.h
@@ -0,0 +1,25 @@
+#ifndef UTIL_LINUX_SWAPON_COMMON_H
+#define UTIL_LINUX_SWAPON_COMMON_H
+
+#include <libmount.h>
+
+extern struct libmnt_cache *mntcache;
+
+extern struct libmnt_table *get_fstab(void);
+extern struct libmnt_table *get_swaps(void);
+extern void free_tables(void);
+
+extern int match_swap(struct libmnt_fs *fs, void *data);
+extern int is_active_swap(const char *filename);
+
+extern int cannot_find(const char *special);
+
+extern void add_label(const char *label);
+extern const char *get_label(size_t i);
+extern size_t numof_labels(void);
+
+extern void add_uuid(const char *uuid);
+extern const char *get_uuid(size_t i);
+extern size_t numof_uuids(void);
+
+#endif /* UTIL_LINUX_SWAPON_COMMON_H */
diff --git a/sys-utils/swapon.8 b/sys-utils/swapon.8
new file mode 100644
index 0000000..486e028
--- /dev/null
+++ b/sys-utils/swapon.8
@@ -0,0 +1,280 @@
+.\" Copyright (c) 1980, 1991 Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)swapon.8 6.3 (Berkeley) 3/16/91
+.\"
+.TH SWAPON 8 "October 2014" "util-linux" "System Administration"
+.SH NAME
+swapon, swapoff \- enable/disable devices and files for paging and swapping
+.SH SYNOPSIS
+.B swapon
+[options]
+.RI [ specialfile ...]
+.br
+.B swapoff
+.RB [ \-va ]
+.RI [ specialfile ...]
+.SH DESCRIPTION
+.B swapon
+is used to specify devices on which paging and swapping are to take place.
+
+The device or file used is given by the
+.I specialfile
+parameter. It may be of the form
+.BI \-L " label"
+or
+.BI \-U " uuid"
+to indicate a device by label or uuid.
+
+Calls to
+.B swapon
+normally occur in the system boot scripts making all swap devices available, so
+that the paging and swapping activity is interleaved across several devices and
+files.
+
+.B swapoff
+disables swapping on the specified devices and files.
+When the
+.B \-a
+flag is given, swapping is disabled on all known swap devices and files
+(as found in
+.I /proc/swaps
+or
+.IR /etc/fstab ).
+
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-all"
+All devices marked as ``swap'' in
+.I /etc/fstab
+are made available, except for those with the ``noauto'' option.
+Devices that are already being used as swap are silently skipped.
+.TP
+.BR \-d , " \-\-discard" [ =\fIpolicy\fR]
+Enable swap discards, if the swap backing device supports the discard or
+trim operation. This may improve performance on some Solid State Devices,
+but often it does not. The option allows one to select between two
+available swap discard policies:
+.B \-\-discard=once
+to perform a single-time discard operation for the whole swap area at swapon;
+or
+.B \-\-discard=pages
+to asynchronously discard freed swap pages before they are available for reuse.
+If no policy is selected, the default behavior is to enable both discard types.
+The
+.I /etc/fstab
+mount options
+.BR discard ,
+.BR discard=once ,
+or
+.B discard=pages
+may also be used to enable discard flags.
+.TP
+.BR \-e , " \-\-ifexists"
+Silently skip devices that do not exist.
+The
+.I /etc/fstab
+mount option
+.B nofail
+may also be used to skip non-existing device.
+
+.TP
+.BR \-f , " \-\-fixpgsz"
+Reinitialize (exec mkswap) the swap space if its page size does not
+match that of the current running kernel.
+.BR mkswap (8)
+initializes the whole device and does not check for bad blocks.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BI \-L " label"
+Use the partition that has the specified
+.IR label .
+(For this, access to
+.I /proc/partitions
+is needed.)
+.TP
+.BR \-o , " \-\-options " \fIopts\fP
+Specify swap options by an fstab-compatible comma-separated string.
+For example:
+.RS
+.RS
+.sp
+.B "swapon \-o pri=1,discard=pages,nofail /dev/sda2"
+.sp
+.RE
+The \fIopts\fP string is evaluated last and overrides all other
+command line options.
+.RE
+.TP
+.BR \-p , " \-\-priority " \fIpriority\fP
+Specify the priority of the swap device.
+.I priority
+is a value between \-1 and 32767. Higher numbers indicate
+higher priority. See
+.BR swapon (2)
+for a full description of swap priorities. Add
+.BI pri= value
+to the option field of
+.I /etc/fstab
+for use with
+.BR "swapon \-a" .
+When no priority is defined, it defaults to \-1.
+.TP
+.BR \-s , " \-\-summary"
+Display swap usage summary by device. Equivalent to "cat /proc/swaps".
+This output format is DEPRECATED in favour
+of \fB\-\-show\fR that provides better control on output data.
+.TP
+.BR \-\-show [ =\fIcolumn\fR ...]
+Display a definable table of swap areas. See the
+.B \-\-help
+output for a list of available columns.
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.B \-\-noheadings
+Do not print headings when displaying
+.B \-\-show
+output.
+.TP
+.B \-\-raw
+Display
+.B \-\-show
+output without aligning table columns.
+.TP
+.B \-\-bytes
+Display swap size in bytes in
+.B \-\-show
+output instead of in user-friendly units.
+.TP
+.BI \-U " uuid"
+Use the partition that has the specified
+.IR uuid .
+.TP
+.BR \-v , " \-\-verbose"
+Be verbose.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.SH EXIT STATUS
+.B swapoff
+has the following exit status values since v2.36:
+.TP
+.B 0
+success
+.TP
+.B 2
+system has insufficient memory to stop swapping (OOM)
+.TP
+.B 4
+swapoff syscall failed for another reason
+.TP
+.B 8
+non-swapoff syscall system error (out of memory, ...)
+.TP
+.B 16
+usage or syntax error
+.TP
+.B 32
+all swapoff failed on \fB\-\-all\fR
+.TP
+.B 64
+some swapoff succeeded on \fB\-\-all\fR
+
+The command \fBswapoff \-\-all\fR returns 0 (all succeeded), 32 (all failed), or 64 (some
+failed, some succeeded).
+
+The old versions before v2.36 has no documented exit status, 0 means success in all versions.
+
+.SH ENVIRONMENT
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output.
+.IP LIBBLKID_DEBUG=all
+enables libblkid debug output.
+
+.SH FILES
+.I /dev/sd??
+standard paging devices
+.br
+.I /etc/fstab
+ascii filesystem description table
+.SH NOTES
+.SS Files with holes
+The swap file implementation in the kernel expects to be able to write to the
+file directly, without the assistance of the filesystem. This is a problem on
+files with holes or on copy-on-write files on filesystems like Btrfs.
+.sp
+Commands like
+.BR cp (1)
+or
+.BR truncate (1)
+create files with holes. These files will be rejected by swapon.
+.sp
+Preallocated files created by
+.BR fallocate (1)
+may be interpreted as files with holes too depending of the filesystem.
+Preallocated swap files are supported on XFS since Linux 4.18.
+.sp
+The most portable solution to create a swap file is to use
+.BR dd (1)
+and /dev/zero.
+.SS Btrfs
+Swap files on Btrfs are supported since Linux 5.0 on files with nocow attribute.
+See the
+.BR btrfs (5)
+manual page for more details.
+.SS NFS
+Swap over \fBNFS\fR may not work.
+.SS Suspend
+.B swapon
+automatically detects and rewrites a swap space signature with old software
+suspend data (e.g., S1SUSPEND, S2SUSPEND, ...). The problem is that if we don't
+do it, then we get data corruption the next time an attempt at unsuspending is
+made.
+.SH HISTORY
+The
+.B swapon
+command appeared in 4.0BSD.
+.SH SEE ALSO
+.BR swapoff (2),
+.BR swapon (2),
+.BR fstab (5),
+.BR init (8),
+.BR fallocate (1),
+.BR mkswap (8),
+.BR mount (8),
+.BR rc (8)
+.SH AVAILABILITY
+The swapon command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/swapon.c b/sys-utils/swapon.c
new file mode 100644
index 0000000..95b678a
--- /dev/null
+++ b/sys-utils/swapon.c
@@ -0,0 +1,1015 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "bitops.h"
+#include "blkdev.h"
+#include "pathnames.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "optutils.h"
+#include "closestream.h"
+
+#include "swapheader.h"
+#include "swapprober.h"
+#include "swapon-common.h"
+
+#ifdef HAVE_SYS_SWAP_H
+# include <sys/swap.h>
+#endif
+
+#ifndef SWAP_FLAG_DISCARD
+# define SWAP_FLAG_DISCARD 0x10000 /* enable discard for swap */
+#endif
+
+#ifndef SWAP_FLAG_DISCARD_ONCE
+# define SWAP_FLAG_DISCARD_ONCE 0x20000 /* discard swap area at swapon-time */
+#endif
+
+#ifndef SWAP_FLAG_DISCARD_PAGES
+# define SWAP_FLAG_DISCARD_PAGES 0x40000 /* discard page-clusters after use */
+#endif
+
+#define SWAP_FLAGS_DISCARD_VALID (SWAP_FLAG_DISCARD | SWAP_FLAG_DISCARD_ONCE | \
+ SWAP_FLAG_DISCARD_PAGES)
+
+#ifndef SWAP_FLAG_PREFER
+# define SWAP_FLAG_PREFER 0x8000 /* set if swap priority specified */
+#endif
+
+#ifndef SWAP_FLAG_PRIO_MASK
+# define SWAP_FLAG_PRIO_MASK 0x7fff
+#endif
+
+#ifndef SWAP_FLAG_PRIO_SHIFT
+# define SWAP_FLAG_PRIO_SHIFT 0
+#endif
+
+#if !defined(HAVE_SWAPON) && defined(SYS_swapon)
+# include <sys/syscall.h>
+# define swapon(path, flags) syscall(SYS_swapon, path, flags)
+#endif
+
+#define MAX_PAGESIZE (64 * 1024)
+
+#ifndef UUID_STR_LEN
+# define UUID_STR_LEN 37
+#endif
+
+enum {
+ SIG_SWAPSPACE = 1,
+ SIG_SWSUSPEND
+};
+
+/* column names */
+struct colinfo {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+};
+
+enum {
+ COL_PATH,
+ COL_TYPE,
+ COL_SIZE,
+ COL_USED,
+ COL_PRIO,
+ COL_UUID,
+ COL_LABEL
+};
+static struct colinfo infos[] = {
+ [COL_PATH] = { "NAME", 0.20, 0, N_("device file or partition path") },
+ [COL_TYPE] = { "TYPE", 0.20, SCOLS_FL_TRUNC, N_("type of the device")},
+ [COL_SIZE] = { "SIZE", 0.20, SCOLS_FL_RIGHT, N_("size of the swap area")},
+ [COL_USED] = { "USED", 0.20, SCOLS_FL_RIGHT, N_("bytes in use")},
+ [COL_PRIO] = { "PRIO", 0.20, SCOLS_FL_RIGHT, N_("swap priority")},
+ [COL_UUID] = { "UUID", 0.20, 0, N_("swap uuid")},
+ [COL_LABEL] = { "LABEL", 0.20, 0, N_("swap label")},
+};
+
+
+/* swap area properties */
+struct swap_prop {
+ int discard; /* discard policy */
+ int priority; /* non-prioritized swap by default */
+ int no_fail; /* skip device if not exist */
+};
+
+/* device description */
+struct swap_device {
+ const char *path; /* device or file to be turned on */
+ const char *label; /* swap label */
+ const char *uuid; /* unique identifier */
+ unsigned int pagesize;
+};
+
+/* control struct */
+struct swapon_ctl {
+ int columns[ARRAY_SIZE(infos) * 2]; /* --show columns */
+ int ncolumns; /* number of columns */
+
+ struct swap_prop props; /* global settings for all devices */
+
+ unsigned int
+ all:1, /* turn on all swap devices */
+ bytes:1, /* display --show in bytes */
+ fix_page_size:1, /* reinitialize page size */
+ no_heading:1, /* toggle --show headers */
+ raw:1, /* toggle --show alignment */
+ show:1, /* display --show information */
+ verbose:1; /* be chatty */
+};
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ assert(name);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static inline int get_column_id(const struct swapon_ctl *ctl, int num)
+{
+ assert(num < ctl->ncolumns);
+ assert(ctl->columns[num] < (int) ARRAY_SIZE(infos));
+
+ return ctl->columns[num];
+}
+
+static inline struct colinfo *get_column_info(const struct swapon_ctl *ctl, unsigned num)
+{
+ return &infos[get_column_id(ctl, num)];
+}
+
+static void add_scols_line(const struct swapon_ctl *ctl, struct libscols_table *table, struct libmnt_fs *fs)
+{
+ int i;
+ struct libscols_line *line;
+ blkid_probe pr = NULL;
+ const char *data;
+
+ assert(table);
+ assert(fs);
+
+ line = scols_table_new_line(table, NULL);
+ if (!line)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ data = mnt_fs_get_source(fs);
+ if (access(data, R_OK) == 0)
+ pr = get_swap_prober(data);
+ for (i = 0; i < ctl->ncolumns; i++) {
+ char *str = NULL;
+ off_t size;
+
+ switch (get_column_id(ctl, i)) {
+ case COL_PATH:
+ xasprintf(&str, "%s", mnt_fs_get_source(fs));
+ break;
+ case COL_TYPE:
+ xasprintf(&str, "%s", mnt_fs_get_swaptype(fs));
+ break;
+ case COL_SIZE:
+ size = mnt_fs_get_size(fs);
+ size *= 1024; /* convert to bytes */
+ if (ctl->bytes)
+ xasprintf(&str, "%jd", size);
+ else
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+ break;
+ case COL_USED:
+ size = mnt_fs_get_usedsize(fs);
+ size *= 1024; /* convert to bytes */
+ if (ctl->bytes)
+ xasprintf(&str, "%jd", size);
+ else
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+ break;
+ case COL_PRIO:
+ xasprintf(&str, "%d", mnt_fs_get_priority(fs));
+ break;
+ case COL_UUID:
+ if (pr && !blkid_probe_lookup_value(pr, "UUID", &data, NULL))
+ xasprintf(&str, "%s", data);
+ break;
+ case COL_LABEL:
+ if (pr && !blkid_probe_lookup_value(pr, "LABEL", &data, NULL))
+ xasprintf(&str, "%s", data);
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, i, str))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+ if (pr)
+ blkid_free_probe(pr);
+}
+
+static int display_summary(void)
+{
+ struct libmnt_table *st = get_swaps();
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+
+ if (!st)
+ return -1;
+
+ if (mnt_table_is_empty(st))
+ return 0;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ err(EXIT_FAILURE, _("failed to initialize libmount iterator"));
+
+ printf(_("%s\t\t\t\tType\t\tSize\tUsed\tPriority\n"), _("Filename"));
+
+ while (mnt_table_next_fs(st, itr, &fs) == 0) {
+ printf("%-39s\t%-8s\t%jd\t%jd\t%d\n",
+ mnt_fs_get_source(fs),
+ mnt_fs_get_swaptype(fs),
+ mnt_fs_get_size(fs),
+ mnt_fs_get_usedsize(fs),
+ mnt_fs_get_priority(fs));
+ }
+
+ mnt_free_iter(itr);
+ return 0;
+}
+
+static int show_table(struct swapon_ctl *ctl)
+{
+ struct libmnt_table *st = get_swaps();
+ struct libmnt_iter *itr = NULL;
+ struct libmnt_fs *fs;
+ int i;
+ struct libscols_table *table = NULL;
+
+ if (!st)
+ return -1;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ err(EXIT_FAILURE, _("failed to initialize libmount iterator"));
+
+ scols_init_debug(0);
+
+ table = scols_new_table();
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_raw(table, ctl->raw);
+ scols_table_enable_noheadings(table, ctl->no_heading);
+
+ for (i = 0; i < ctl->ncolumns; i++) {
+ struct colinfo *col = get_column_info(ctl, i);
+
+ if (!scols_table_new_column(table, col->name, col->whint, col->flags))
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ }
+
+ while (mnt_table_next_fs(st, itr, &fs) == 0)
+ add_scols_line(ctl, table, fs);
+
+ scols_print_table(table);
+ scols_unref_table(table);
+ mnt_free_iter(itr);
+ return 0;
+}
+
+/* calls mkswap */
+static int swap_reinitialize(struct swap_device *dev)
+{
+ pid_t pid;
+ int status, ret;
+ char const *cmd[7];
+ int idx=0;
+
+ assert(dev);
+ assert(dev->path);
+
+ warnx(_("%s: reinitializing the swap."), dev->path);
+
+ switch ((pid=fork())) {
+ case -1: /* fork error */
+ warn(_("fork failed"));
+ return -1;
+
+ case 0: /* child */
+ if (geteuid() != getuid()) {
+ /* in case someone uses swapon as setuid binary */
+ if (setgid(getgid()) < 0)
+ exit(EXIT_FAILURE);
+ if (setuid(getuid()) < 0)
+ exit(EXIT_FAILURE);
+ }
+
+ cmd[idx++] = "mkswap";
+ if (dev->label) {
+ cmd[idx++] = "-L";
+ cmd[idx++] = dev->label;
+ }
+ if (dev->uuid) {
+ cmd[idx++] = "-U";
+ cmd[idx++] = dev->uuid;
+ }
+ cmd[idx++] = dev->path;
+ cmd[idx++] = NULL;
+ execvp(cmd[0], (char * const *) cmd);
+ errexec(cmd[0]);
+
+ default: /* parent */
+ do {
+ ret = waitpid(pid, &status, 0);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret < 0) {
+ warn(_("waitpid failed"));
+ return -1;
+ }
+
+ /* mkswap returns: 0=suss, >0 error */
+ if (WIFEXITED(status) && WEXITSTATUS(status)==0)
+ return 0; /* ok */
+ break;
+ }
+ return -1; /* error */
+}
+
+/* Replaces unwanted SWSUSPEND signature with swap signature */
+static int swap_rewrite_signature(const struct swap_device *dev)
+{
+ int fd, rc = -1;
+
+ assert(dev);
+ assert(dev->path);
+ assert(dev->pagesize);
+
+ fd = open(dev->path, O_WRONLY);
+ if (fd == -1) {
+ warn(_("cannot open %s"), dev->path);
+ return -1;
+ }
+
+ if (lseek(fd, dev->pagesize - SWAP_SIGNATURE_SZ, SEEK_SET) < 0) {
+ warn(_("%s: lseek failed"), dev->path);
+ goto err;
+ }
+
+ if (write(fd, (void *) SWAP_SIGNATURE,
+ SWAP_SIGNATURE_SZ) != SWAP_SIGNATURE_SZ) {
+ warn(_("%s: write signature failed"), dev->path);
+ goto err;
+ }
+
+ rc = 0;
+err:
+ if (close_fd(fd) != 0) {
+ warn(_("write failed: %s"), dev->path);
+ rc = -1;
+ }
+ return rc;
+}
+
+static int swap_detect_signature(const char *buf, int *sig)
+{
+ assert(buf);
+ assert(sig);
+
+ if (memcmp(buf, SWAP_SIGNATURE, SWAP_SIGNATURE_SZ) == 0)
+ *sig = SIG_SWAPSPACE;
+
+ else if (memcmp(buf, "S1SUSPEND", 9) == 0 ||
+ memcmp(buf, "S2SUSPEND", 9) == 0 ||
+ memcmp(buf, "ULSUSPEND", 9) == 0 ||
+ memcmp(buf, "\xed\xc3\x02\xe9\x98\x56\xe5\x0c", 8) == 0 ||
+ memcmp(buf, "LINHIB0001", 10) == 0)
+ *sig = SIG_SWSUSPEND;
+ else
+ return 0;
+
+ return 1;
+}
+
+static char *swap_get_header(int fd, int *sig, unsigned int *pagesize)
+{
+ char *buf;
+ ssize_t datasz;
+ unsigned int page;
+
+ assert(sig);
+ assert(pagesize);
+
+ *pagesize = 0;
+ *sig = 0;
+
+ buf = xmalloc(MAX_PAGESIZE);
+
+ datasz = read(fd, buf, MAX_PAGESIZE);
+ if (datasz == (ssize_t) -1)
+ goto err;
+
+ for (page = 0x1000; page <= MAX_PAGESIZE; page <<= 1) {
+ /* skip 32k pagesize since this does not seem to
+ * be supported */
+ if (page == 0x8000)
+ continue;
+ /* the smallest swap area is PAGE_SIZE*10, it means
+ * 40k, that's less than MAX_PAGESIZE */
+ if (datasz < 0 || (size_t) datasz < (page - SWAP_SIGNATURE_SZ))
+ break;
+ if (swap_detect_signature(buf + page - SWAP_SIGNATURE_SZ, sig)) {
+ *pagesize = page;
+ break;
+ }
+ }
+
+ if (*pagesize)
+ return buf;
+err:
+ free(buf);
+ return NULL;
+}
+
+/* returns real size of swap space */
+static unsigned long long swap_get_size(const struct swap_device *dev,
+ const char *hdr)
+{
+ unsigned int last_page = 0;
+ const unsigned int swap_version = SWAP_VERSION;
+ const struct swap_header_v1_2 *s;
+
+ assert(dev);
+ assert(dev->pagesize > 0);
+
+ s = (const struct swap_header_v1_2 *) hdr;
+
+ if (s->version == swap_version)
+ last_page = s->last_page;
+ else if (swab32(s->version) == swap_version)
+ last_page = swab32(s->last_page);
+
+ return ((unsigned long long) last_page + 1) * dev->pagesize;
+}
+
+static void swap_get_info(struct swap_device *dev, const char *hdr)
+{
+ const struct swap_header_v1_2 *s = (const struct swap_header_v1_2 *) hdr;
+
+ assert(dev);
+
+ if (s && *s->volume_name)
+ dev->label = xstrdup(s->volume_name);
+
+ if (s && *s->uuid) {
+ const unsigned char *u = s->uuid;
+ char str[UUID_STR_LEN];
+
+ snprintf(str, sizeof(str),
+ "%02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x-"
+ "%02x%02x-%02x%02x%02x%02x%02x%02x",
+ u[0], u[1], u[2], u[3],
+ u[4], u[5], u[6], u[7],
+ u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]);
+ dev->uuid = xstrdup(str);
+ }
+}
+
+static int swapon_checks(const struct swapon_ctl *ctl, struct swap_device *dev)
+{
+ struct stat st;
+ int fd, sig;
+ char *hdr = NULL;
+ unsigned long long devsize = 0;
+ int permMask;
+
+ assert(ctl);
+ assert(dev);
+ assert(dev->path);
+
+ fd = open(dev->path, O_RDONLY);
+ if (fd == -1) {
+ warn(_("cannot open %s"), dev->path);
+ goto err;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ warn(_("stat of %s failed"), dev->path);
+ goto err;
+ }
+
+ permMask = S_ISBLK(st.st_mode) ? 07007 : 07077;
+ if ((st.st_mode & permMask) != 0)
+ warnx(_("%s: insecure permissions %04o, %04o suggested."),
+ dev->path, st.st_mode & 07777,
+ ~permMask & 0666);
+
+ if (S_ISREG(st.st_mode) && st.st_uid != 0)
+ warnx(_("%s: insecure file owner %d, 0 (root) suggested."),
+ dev->path, st.st_uid);
+
+ /* test for holes by LBT */
+ if (S_ISREG(st.st_mode)) {
+ if (st.st_blocks * 512 < st.st_size) {
+ warnx(_("%s: skipping - it appears to have holes."),
+ dev->path);
+ goto err;
+ }
+ devsize = st.st_size;
+ }
+
+ if (S_ISBLK(st.st_mode) && blkdev_get_size(fd, &devsize)) {
+ warnx(_("%s: get size failed"), dev->path);
+ goto err;
+ }
+
+ hdr = swap_get_header(fd, &sig, &dev->pagesize);
+ if (!hdr) {
+ warnx(_("%s: read swap header failed"), dev->path);
+ goto err;
+ }
+
+ if (ctl->verbose)
+ warnx(_("%s: found signature [pagesize=%d, signature=%s]"),
+ dev->path,
+ dev->pagesize,
+ sig == SIG_SWAPSPACE ? "swap" :
+ sig == SIG_SWSUSPEND ? "suspend" : "unknown");
+
+ if (sig == SIG_SWAPSPACE && dev->pagesize) {
+ unsigned long long swapsize = swap_get_size(dev, hdr);
+ int syspg = getpagesize();
+
+ if (ctl->verbose)
+ warnx(_("%s: pagesize=%d, swapsize=%llu, devsize=%llu"),
+ dev->path, dev->pagesize, swapsize, devsize);
+
+ if (swapsize > devsize) {
+ if (ctl->verbose)
+ warnx(_("%s: last_page 0x%08llx is larger"
+ " than actual size of swapspace"),
+ dev->path, swapsize);
+
+ } else if (syspg < 0 || (unsigned int) syspg != dev->pagesize) {
+ if (ctl->fix_page_size) {
+ int rc;
+
+ swap_get_info(dev, hdr);
+
+ warnx(_("%s: swap format pagesize does not match."),
+ dev->path);
+ rc = swap_reinitialize(dev);
+ if (rc < 0)
+ goto err;
+ } else
+ warnx(_("%s: swap format pagesize does not match. "
+ "(Use --fixpgsz to reinitialize it.)"),
+ dev->path);
+ }
+ } else if (sig == SIG_SWSUSPEND) {
+ /* We have to reinitialize swap with old (=useless) software suspend
+ * data. The problem is that if we don't do it, then we get data
+ * corruption the next time an attempt at unsuspending is made.
+ */
+ warnx(_("%s: software suspend data detected. "
+ "Rewriting the swap signature."),
+ dev->path);
+ if (swap_rewrite_signature(dev) < 0)
+ goto err;
+ }
+
+ free(hdr);
+ close(fd);
+ return 0;
+err:
+ if (fd != -1)
+ close(fd);
+ free(hdr);
+ return -1;
+}
+
+static int do_swapon(const struct swapon_ctl *ctl,
+ const struct swap_prop *prop,
+ const char *spec,
+ int canonic)
+{
+ struct swap_device dev = { .path = NULL };
+ int status;
+ int flags = 0;
+ int priority;
+
+ assert(ctl);
+ assert(prop);
+
+ if (!canonic) {
+ dev.path = mnt_resolve_spec(spec, mntcache);
+ if (!dev.path)
+ return cannot_find(spec);
+ } else
+ dev.path = spec;
+
+ priority = prop->priority;
+
+ if (swapon_checks(ctl, &dev))
+ return -1;
+
+#ifdef SWAP_FLAG_PREFER
+ if (priority >= 0) {
+ if (priority > SWAP_FLAG_PRIO_MASK)
+ priority = SWAP_FLAG_PRIO_MASK;
+
+ flags = SWAP_FLAG_PREFER
+ | ((priority & SWAP_FLAG_PRIO_MASK)
+ << SWAP_FLAG_PRIO_SHIFT);
+ }
+#endif
+ /*
+ * Validate the discard flags passed and set them
+ * accordingly before calling sys_swapon.
+ */
+ if (prop->discard && !(prop->discard & ~SWAP_FLAGS_DISCARD_VALID)) {
+ /*
+ * If we get here with both discard policy flags set,
+ * we just need to tell the kernel to enable discards
+ * and it will do correctly, just as we expect.
+ */
+ if ((prop->discard & SWAP_FLAG_DISCARD_ONCE) &&
+ (prop->discard & SWAP_FLAG_DISCARD_PAGES))
+ flags |= SWAP_FLAG_DISCARD;
+ else
+ flags |= prop->discard;
+ }
+
+ if (ctl->verbose)
+ printf(_("swapon %s\n"), dev.path);
+
+ status = swapon(dev.path, flags);
+ if (status < 0)
+ warn(_("%s: swapon failed"), dev.path);
+
+ return status;
+}
+
+static int swapon_by_label(struct swapon_ctl *ctl, const char *label)
+{
+ char *device = mnt_resolve_tag("LABEL", label, mntcache);
+ return device ? do_swapon(ctl, &ctl->props, device, TRUE) : cannot_find(label);
+}
+
+static int swapon_by_uuid(struct swapon_ctl *ctl, const char *uuid)
+{
+ char *device = mnt_resolve_tag("UUID", uuid, mntcache);
+ return device ? do_swapon(ctl, &ctl->props, device, TRUE) : cannot_find(uuid);
+}
+
+/* -o <options> or fstab */
+static int parse_options(struct swap_prop *props, const char *options)
+{
+ char *arg = NULL;
+ size_t argsz = 0;
+
+ assert(props);
+ assert(options);
+
+ if (mnt_optstr_get_option(options, "nofail", NULL, NULL) == 0)
+ props->no_fail = 1;
+
+ if (mnt_optstr_get_option(options, "discard", &arg, &argsz) == 0) {
+ props->discard |= SWAP_FLAG_DISCARD;
+
+ if (arg) {
+ /* only single-time discards are wanted */
+ if (strncmp(arg, "once", argsz) == 0)
+ props->discard |= SWAP_FLAG_DISCARD_ONCE;
+
+ /* do discard for every released swap page */
+ if (strncmp(arg, "pages", argsz) == 0)
+ props->discard |= SWAP_FLAG_DISCARD_PAGES;
+ }
+ }
+
+ arg = NULL;
+ if (mnt_optstr_get_option(options, "pri", &arg, NULL) == 0 && arg)
+ props->priority = atoi(arg);
+
+ return 0;
+}
+
+
+static int swapon_all(struct swapon_ctl *ctl)
+{
+ struct libmnt_table *tb = get_fstab();
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+ int status = 0;
+
+ if (!tb)
+ err(EXIT_FAILURE, _("failed to parse %s"), mnt_get_fstab_path());
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ err(EXIT_FAILURE, _("failed to initialize libmount iterator"));
+
+ while (mnt_table_find_next_fs(tb, itr, match_swap, NULL, &fs) == 0) {
+ /* defaults */
+ const char *opts;
+ const char *device;
+ struct swap_prop prop; /* per device setting */
+
+ if (mnt_fs_get_option(fs, "noauto", NULL, NULL) == 0) {
+ if (ctl->verbose)
+ warnx(_("%s: noauto option -- ignored"), mnt_fs_get_source(fs));
+ continue;
+ }
+
+ /* default setting */
+ prop = ctl->props;
+
+ /* overwrite default by setting from fstab */
+ opts = mnt_fs_get_options(fs);
+ if (opts)
+ parse_options(&prop, opts);
+
+ /* convert LABEL=, UUID= etc. from fstab to device name */
+ device = mnt_resolve_spec(mnt_fs_get_source(fs), mntcache);
+ if (!device) {
+ if (!prop.no_fail)
+ status |= cannot_find(mnt_fs_get_source(fs));
+ continue;
+ }
+
+ if (is_active_swap(device)) {
+ if (ctl->verbose)
+ warnx(_("%s: already active -- ignored"), device);
+ continue;
+ }
+
+ if (prop.no_fail && access(device, R_OK) != 0) {
+ if (ctl->verbose)
+ warnx(_("%s: inaccessible -- ignored"), device);
+ continue;
+ }
+
+ /* swapon */
+ status |= do_swapon(ctl, &prop, device, TRUE);
+ }
+
+ mnt_free_iter(itr);
+ return status;
+}
+
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<spec>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Enable devices and files for paging and swapping.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all enable all swaps from /etc/fstab\n"), out);
+ fputs(_(" -d, --discard[=<policy>] enable swap discards, if supported by device\n"), out);
+ fputs(_(" -e, --ifexists silently skip devices that do not exist\n"), out);
+ fputs(_(" -f, --fixpgsz reinitialize the swap space if necessary\n"), out);
+ fputs(_(" -o, --options <list> comma-separated list of swap options\n"), out);
+ fputs(_(" -p, --priority <prio> specify the priority of the swap device\n"), out);
+ fputs(_(" -s, --summary display summary about used swap devices (DEPRECATED)\n"), out);
+ fputs(_(" --show[=<columns>] display summary in definable table\n"), out);
+ fputs(_(" --noheadings don't print table heading (with --show)\n"), out);
+ fputs(_(" --raw use the raw output format (with --show)\n"), out);
+ fputs(_(" --bytes display swap size in bytes in --show output\n"), out);
+ fputs(_(" -v, --verbose verbose mode\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+
+ fputs(_("\nThe <spec> parameter:\n" \
+ " -L <label> synonym for LABEL=<label>\n"
+ " -U <uuid> synonym for UUID=<uuid>\n"
+ " LABEL=<label> specifies device by swap area label\n"
+ " UUID=<uuid> specifies device by swap area UUID\n"
+ " PARTLABEL=<label> specifies device by partition label\n"
+ " PARTUUID=<uuid> specifies device by partition UUID\n"
+ " <device> name of device to be used\n"
+ " <file> name of file to be used\n"), out);
+
+ fputs(_("\nAvailable discard policy types (for --discard):\n"
+ " once : only single-time area discards are issued\n"
+ " pages : freed pages are discarded before they are reused\n"
+ "If no policy is selected, both discard types are enabled (default).\n"), out);
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %-5s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("swapon(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int status = 0, c;
+ size_t i;
+ char *options = NULL;
+
+ enum {
+ BYTES_OPTION = CHAR_MAX + 1,
+ NOHEADINGS_OPTION,
+ RAW_OPTION,
+ SHOW_OPTION,
+ OPT_LIST_TYPES
+ };
+
+ static const struct option long_opts[] = {
+ { "priority", required_argument, NULL, 'p' },
+ { "discard", optional_argument, NULL, 'd' },
+ { "ifexists", no_argument, NULL, 'e' },
+ { "options", optional_argument, NULL, 'o' },
+ { "summary", no_argument, NULL, 's' },
+ { "fixpgsz", no_argument, NULL, 'f' },
+ { "all", no_argument, NULL, 'a' },
+ { "help", no_argument, NULL, 'h' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { "show", optional_argument, NULL, SHOW_OPTION },
+ { "output-all", no_argument, NULL, OPT_LIST_TYPES },
+ { "noheadings", no_argument, NULL, NOHEADINGS_OPTION },
+ { "raw", no_argument, NULL, RAW_OPTION },
+ { "bytes", no_argument, NULL, BYTES_OPTION },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'a','o','s', SHOW_OPTION },
+ { 'a','o', BYTES_OPTION },
+ { 'a','o', NOHEADINGS_OPTION },
+ { 'a','o', RAW_OPTION },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ struct swapon_ctl ctl;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ memset(&ctl, 0, sizeof(struct swapon_ctl));
+ ctl.props.priority = -1;
+
+ mnt_init_debug(0);
+ mntcache = mnt_new_cache();
+
+ while ((c = getopt_long(argc, argv, "ahd::efo:p:svVL:U:",
+ long_opts, NULL)) != -1) {
+
+ err_exclusive_options(c, long_opts, excl, excl_st);
+
+ switch (c) {
+ case 'a': /* all */
+ ctl.all = 1;
+ break;
+ case 'o':
+ options = optarg;
+ break;
+ case 'p': /* priority */
+ ctl.props.priority = strtos16_or_err(optarg,
+ _("failed to parse priority"));
+ break;
+ case 'L':
+ add_label(optarg);
+ break;
+ case 'U':
+ add_uuid(optarg);
+ break;
+ case 'd':
+ ctl.props.discard |= SWAP_FLAG_DISCARD;
+ if (optarg) {
+ if (*optarg == '=')
+ optarg++;
+
+ if (strcmp(optarg, "once") == 0)
+ ctl.props.discard |= SWAP_FLAG_DISCARD_ONCE;
+ else if (strcmp(optarg, "pages") == 0)
+ ctl.props.discard |= SWAP_FLAG_DISCARD_PAGES;
+ else
+ errx(EXIT_FAILURE, _("unsupported discard policy: %s"), optarg);
+ }
+ break;
+ case 'e': /* ifexists */
+ ctl.props.no_fail = 1;
+ break;
+ case 'f':
+ ctl.fix_page_size = 1;
+ break;
+ case 's': /* status report */
+ status = display_summary();
+ return status;
+ case 'v': /* be chatty */
+ ctl.verbose = 1;
+ break;
+ case SHOW_OPTION:
+ if (optarg) {
+ ctl.ncolumns = string_to_idarray(optarg,
+ ctl.columns,
+ ARRAY_SIZE(ctl.columns),
+ column_name_to_id);
+ if (ctl.ncolumns < 0)
+ return EXIT_FAILURE;
+ }
+ ctl.show = 1;
+ break;
+ case OPT_LIST_TYPES:
+ for (ctl.ncolumns = 0; (size_t)ctl.ncolumns < ARRAY_SIZE(infos); ctl.ncolumns++)
+ ctl.columns[ctl.ncolumns] = ctl.ncolumns;
+ break;
+ case NOHEADINGS_OPTION:
+ ctl.no_heading = 1;
+ break;
+ case RAW_OPTION:
+ ctl.raw = 1;
+ break;
+ case BYTES_OPTION:
+ ctl.bytes = 1;
+ break;
+ case 0:
+ break;
+
+ case 'h': /* help */
+ usage();
+ case 'V': /* version */
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argv += optind;
+
+ if (ctl.show || (!ctl.all && !numof_labels() && !numof_uuids() && *argv == NULL)) {
+ if (!ctl.ncolumns) {
+ /* default columns */
+ ctl.columns[ctl.ncolumns++] = COL_PATH;
+ ctl.columns[ctl.ncolumns++] = COL_TYPE;
+ ctl.columns[ctl.ncolumns++] = COL_SIZE;
+ ctl.columns[ctl.ncolumns++] = COL_USED;
+ ctl.columns[ctl.ncolumns++] = COL_PRIO;
+ }
+ status = show_table(&ctl);
+ return status;
+ }
+
+ if (ctl.props.no_fail && !ctl.all) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (ctl.all)
+ status |= swapon_all(&ctl);
+
+ if (options)
+ parse_options(&ctl.props, options);
+
+ for (i = 0; i < numof_labels(); i++)
+ status |= swapon_by_label(&ctl, get_label(i));
+
+ for (i = 0; i < numof_uuids(); i++)
+ status |= swapon_by_uuid(&ctl, get_uuid(i));
+
+ while (*argv != NULL)
+ status |= do_swapon(&ctl, &ctl.props, *argv++, FALSE);
+
+ free_tables();
+ mnt_unref_cache(mntcache);
+
+ return status;
+}
diff --git a/sys-utils/switch_root.8 b/sys-utils/switch_root.8
new file mode 100644
index 0000000..d345349
--- /dev/null
+++ b/sys-utils/switch_root.8
@@ -0,0 +1,61 @@
+.\" Karel Zak <kzak@redhat.com>
+.TH SWITCH_ROOT 8 "June 2009" "util-linux" "System Administration"
+.SH NAME
+switch_root \- switch to another filesystem as the root of the mount tree
+.SH SYNOPSIS
+.B switch_root
+.RB [ \-hV ]
+.LP
+.B switch_root
+.I newroot
+.I init
+.RI [ arg ...]
+.SH DESCRIPTION
+.B switch_root
+moves already mounted /proc, /dev, /sys and /run to
+.I newroot
+and makes
+.I newroot
+the new root filesystem and starts
+.I init
+process.
+
+.B WARNING: switch_root removes recursively all files and directories on the current root filesystem.
+
+.SH OPTIONS
+.IP "\fB\-h, \-\-help\fP"
+Display help text and exit.
+.IP "\fB\-V, \-\-version\fP"
+Display version information and exit.
+
+.SH EXIT STATUS
+.B switch_root
+returns 0 on success and 1 on failure.
+
+.SH NOTES
+switch_root will fail to function if
+.B newroot
+is not the root of a mount. If you want to switch root into a directory that
+does not meet this requirement then you can first use a bind-mounting trick to
+turn any directory into a mount point:
+.sp
+.nf
+.RS
+mount --bind $DIR $DIR
+.RE
+.fi
+
+.SH AUTHORS
+.nf
+Peter Jones <pjones@redhat.com>
+Jeremy Katz <katzj@redhat.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.BR chroot (2),
+.BR init (8),
+.BR mkinitrd (8),
+.BR mount (8)
+.SH AVAILABILITY
+The switch_root command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/switch_root.c b/sys-utils/switch_root.c
new file mode 100644
index 0000000..a42bcec
--- /dev/null
+++ b/sys-utils/switch_root.c
@@ -0,0 +1,262 @@
+/*
+ * switchroot.c - switch to new root directory and start init.
+ *
+ * Copyright 2002-2009 Red Hat, Inc. All rights reserved.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Peter Jones <pjones@redhat.com>
+ * Jeremy Katz <katzj@redhat.com>
+ */
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+#include "statfs_magic.h"
+
+#ifndef MS_MOVE
+#define MS_MOVE 8192
+#endif
+
+#ifndef MNT_DETACH
+#define MNT_DETACH 0x00000002 /* Just detach from the tree */
+#endif
+
+/* remove all files/directories below dirName -- don't cross mountpoints */
+static int recursiveRemove(int fd)
+{
+ struct stat rb;
+ DIR *dir;
+ int rc = -1;
+ int dfd;
+
+ if (!(dir = fdopendir(fd))) {
+ warn(_("failed to open directory"));
+ goto done;
+ }
+
+ /* fdopendir() precludes us from continuing to use the input fd */
+ dfd = dirfd(dir);
+
+ if (fstat(dfd, &rb)) {
+ warn(_("stat failed"));
+ goto done;
+ }
+
+ while(1) {
+ struct dirent *d;
+ int isdir = 0;
+
+ errno = 0;
+ if (!(d = readdir(dir))) {
+ if (errno) {
+ warn(_("failed to read directory"));
+ goto done;
+ }
+ break; /* end of directory */
+ }
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN)
+#endif
+ {
+ struct stat sb;
+
+ if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
+ warn(_("stat of %s failed"), d->d_name);
+ continue;
+ }
+
+ /* skip if device is not the same */
+ if (sb.st_dev != rb.st_dev)
+ continue;
+
+ /* remove subdirectories */
+ if (S_ISDIR(sb.st_mode)) {
+ int cfd;
+
+ cfd = openat(dfd, d->d_name, O_RDONLY);
+ if (cfd >= 0) {
+ recursiveRemove(cfd);
+ close(cfd);
+ }
+ isdir = 1;
+ }
+ }
+
+ if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0))
+ warn(_("failed to unlink %s"), d->d_name);
+ }
+
+ rc = 0; /* success */
+
+done:
+ if (dir)
+ closedir(dir);
+ return rc;
+}
+
+static int switchroot(const char *newroot)
+{
+ /* Don't try to unmount the old "/", there's no way to do it. */
+ const char *umounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
+ int i;
+ int cfd;
+ pid_t pid;
+ struct stat newroot_stat, sb;
+
+ if (stat(newroot, &newroot_stat) != 0) {
+ warn(_("stat of %s failed"), newroot);
+ return -1;
+ }
+
+ for (i = 0; umounts[i] != NULL; i++) {
+ char newmount[PATH_MAX];
+
+ snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]);
+
+ if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) {
+ /* mount point seems to be mounted already or stat failed */
+ umount2(umounts[i], MNT_DETACH);
+ continue;
+ }
+
+ if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
+ warn(_("failed to mount moving %s to %s"),
+ umounts[i], newmount);
+ warnx(_("forcing unmount of %s"), umounts[i]);
+ umount2(umounts[i], MNT_FORCE);
+ }
+ }
+
+ if (chdir(newroot)) {
+ warn(_("failed to change directory to %s"), newroot);
+ return -1;
+ }
+
+ cfd = open("/", O_RDONLY);
+ if (cfd < 0) {
+ warn(_("cannot open %s"), "/");
+ return -1;
+ }
+
+ if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
+ close(cfd);
+ warn(_("failed to mount moving %s to /"), newroot);
+ return -1;
+ }
+
+ if (chroot(".")) {
+ close(cfd);
+ warn(_("failed to change root"));
+ return -1;
+ }
+
+ pid = fork();
+ if (pid <= 0) {
+ struct statfs stfs;
+
+ if (fstatfs(cfd, &stfs) == 0 &&
+ (F_TYPE_EQUAL(stfs.f_type, STATFS_RAMFS_MAGIC) ||
+ F_TYPE_EQUAL(stfs.f_type, STATFS_TMPFS_MAGIC)))
+ recursiveRemove(cfd);
+ else
+ warn(_("old root filesystem is not an initramfs"));
+ if (pid == 0)
+ exit(EXIT_SUCCESS);
+ }
+
+ close(cfd);
+ return 0;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *output = stdout;
+ fputs(USAGE_HEADER, output);
+ fprintf(output, _(" %s [options] <newrootdir> <init> <args to init>\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, output);
+ fputs(_("Switch to another filesystem as the root of the mount tree.\n"), output);
+
+ fputs(USAGE_OPTIONS, output);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("switch_root(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ char *newroot, *init, **initargs;
+ int c;
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "+Vh", longopts, NULL)) != -1)
+ switch (c) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ if (argc < 3) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ newroot = argv[1];
+ init = argv[2];
+ initargs = &argv[2];
+
+ if (!*newroot || !*init) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (switchroot(newroot))
+ errx(EXIT_FAILURE, _("failed. Sorry."));
+
+ if (access(init, X_OK))
+ warn(_("cannot access %s"), init);
+
+ execv(init, initargs);
+ errexec(init);
+}
+
diff --git a/sys-utils/tunelp.8 b/sys-utils/tunelp.8
new file mode 100644
index 0000000..35287bc
--- /dev/null
+++ b/sys-utils/tunelp.8
@@ -0,0 +1,122 @@
+.\" Copyright (C) 1992-1997 Michael K. Johnson <johnsonm@redhat.com>
+.\" Copyright (C) 1998 Andrea Arcangeli <andrea@e-mind.com>
+.\" It may be distributed under the terms of the GNU General Public License,
+.\" version 2, or any higher version. See section COPYING of the GNU General
+.\" Public license for conditions under which this file may be redistributed.
+.\"
+.TH TUNELP 8 "October 2011" "util-linux" "System Administration"
+.SH NAME
+tunelp \- set various parameters for the lp device
+.SH SYNOPSIS
+.B tunelp
+[options]
+.I device
+.SH DESCRIPTION
+\fBtunelp\fP sets several parameters for the /dev/lp\fI?\fP devices, for
+better performance (or for any performance at all, if your printer won't work
+without it...) Without parameters, it tells whether the device is using
+interrupts, and if so, which one. With parameters, it sets the device
+characteristics accordingly.
+.SH OPTIONS
+.TP
+\fB\-i\fR, \fB\-\-irq\fR \fIargument\fR
+specifies the IRQ to use for the parallel port in question. If this is set
+to something non-zero, \-t and \-c have no effect. If your port does not use
+interrupts, this option will make printing stop. The command
+.B tunelp \-i 0
+restores non-interrupt driven (polling) action, and your printer should work
+again. If your parallel port does support interrupts, interrupt-driven
+printing should be somewhat faster and efficient, and will probably be
+desirable.
+.IP
+NOTE: This option will have no effect with kernel 2.1.131 or later since the
+irq is handled by the parport driver. You can change the parport irq for
+example via
+.IR /proc/parport/*/irq .
+Read
+.I /usr/src/linux/Documentation/admin-guide/parport.rst
+for more details on parport.
+.TP
+\fB\-t\fR, \fB\-\-time\fR \fImilliseconds\fR
+is the amount of time in jiffies that the driver waits if the printer doesn't
+take a character for the number of tries dictated by the \-c parameter. 10
+is the default value. If you want fastest possible printing, and don't care
+about system load, you may set this to 0. If you don't care how fast your
+printer goes, or are printing text on a slow printer with a buffer, then 500
+(5 seconds) should be fine, and will give you very low system load. This
+value generally should be lower for printing graphics than text, by a factor
+of approximately 10, for best performance.
+.TP
+\fB\-c\fR, \fB\-\-chars\fR \fIcharacters\fR
+is the number of times to try to output a character to the printer before
+sleeping for \-t \fITIME\fP. It is the number of times around a loop that
+tries to send a character to the printer. 120 appears to be a good value for
+most printers in polling mode. 1000 is the default, because there are some
+printers that become jerky otherwise, but you \fImust\fP set this to `1' to
+handle the maximal CPU efficiency if you are using interrupts. If you have a
+very fast printer, a value of 10 might make more sense even if in polling
+mode. If you have a \fIreally\fP old printer, you can increase this further.
+.IP
+Setting \-t \fITIME\fP to 0 is equivalent to setting \-c \fICHARS\fP to
+infinity.
+.TP
+\fB\-w\fR, \fB\-\-wait\fR \fImilliseconds\fR
+is the number of usec we wait while playing with the strobe signal. While
+most printers appear to be able to deal with an extremely short strobe, some
+printers demand a longer one. Increasing this from the default 1 may make it
+possible to print with those printers. This may also make it possible to use
+longer cables. It's also possible to decrease this value to 0 if your
+printer is fast enough or your machine is slow enough.
+.TP
+\fB\-a\fR, \fB\-\-abort\fR \fI<on|off>\fR
+This is whether to abort on printer error - the default is not to. If you
+are sitting at your computer, you probably want to be able to see an error
+and fix it, and have the printer go on printing. On the other hand, if you
+aren't, you might rather that your printer spooler find out that the printer
+isn't ready, quit trying, and send you mail about it. The choice is yours.
+.TP
+\fB\-o\fR, \fB\-\-check\-status\fR \fI<on|off>\fR
+This option is much like \-a. It makes any
+.BR open (2)
+of this device check to see that the device is on-line and not reporting any
+out of paper or other errors. This is the correct setting for most versions
+of lpd.
+.TP
+\fB\-C\fR, \fB\-\-careful\fR \fI<on|off>\fR
+This option adds extra ("careful") error checking. When this option is on,
+the printer driver will ensure that the printer is on-line and not reporting
+any out of paper or other errors before sending data. This is particularly
+useful for printers that normally appear to accept data when turned off.
+.IP
+NOTE: This option is obsolete because it's the default in 2.1.131 kernel or
+later.
+.TP
+\fB\-s\fR, \fB\-\-status\fR
+This option returns the current printer status, both as a decimal number from
+0..255, and as a list of active flags. When this option is specified, \-q
+off, turning off the display of the current IRQ, is implied.
+.TP
+\fB\-r\fR, \fB\-\-reset\fR
+This option resets the port. It requires a Linux kernel version of 1.1.80 or
+later.
+.TP
+\fB\-q\fR, \fB\-\-print\-irq\fR \fI<on|off>\fR
+This option sets printing the display of the current IRQ setting.
+.SH FILES
+.I /dev/lp?
+.br
+.I /proc/parport/*/*
+.SH NOTES
+.BR \-o ,
+.BR \-C ,
+and
+.B \-s
+all require a Linux kernel version of 1.1.76 or later.
+.PP
+.B \-C
+requires a Linux version prior to 2.1.131.
+.SH AVAILABILITY
+The tunelp command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/tunelp.c b/sys-utils/tunelp.c
new file mode 100644
index 0000000..731acd1
--- /dev/null
+++ b/sys-utils/tunelp.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 1992-1997 Michael K. Johnson, johnsonm@redhat.com
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License, version 2, or any later version. See file COPYING for
+ * information on distribution conditions.
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+/*
+ * $Log: tunelp.c,v $
+ * Revision 1.9 1998/06/08 19:37:11 janl
+ * Thus compiles tunelp with 2.1.103 kernels
+ *
+ * Revision 1.8 1997/07/06 00:14:06 aebr
+ * Fixes to silence -Wall.
+ *
+ * Revision 1.7 1997/06/20 16:10:38 janl
+ * tunelp refreshed from authors archive.
+ *
+ * Revision 1.9 1997/06/20 12:56:43 johnsonm
+ * Finished fixing license terms.
+ *
+ * Revision 1.8 1997/06/20 12:34:59 johnsonm
+ * Fixed copyright and license.
+ *
+ * Revision 1.7 1995/03/29 11:16:23 johnsonm
+ * TYPO fixed...
+ *
+ * Revision 1.6 1995/03/29 11:12:15 johnsonm
+ * Added third argument to ioctl needed with new kernels
+ *
+ * Revision 1.5 1995/01/13 10:33:43 johnsonm
+ * Chris's changes for new ioctl numbers and backwards compatibility
+ * and the reset ioctl.
+ *
+ * Revision 1.4 1995/01/03 17:42:14 johnsonm
+ * -s isn't supposed to take an argument; removed : after s in getopt...
+ *
+ * Revision 1.3 1995/01/03 07:36:49 johnsonm
+ * Fixed typo
+ *
+ * Revision 1.2 1995/01/03 07:33:44 johnsonm
+ * revisions for lp driver updates in Linux 1.1.76
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 1999-05-07 Merged LPTRUSTIRQ patch by Andrea Arcangeli (1998/11/29), aeb
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/lp.h>
+
+#include "nls.h"
+#include "closestream.h"
+#include "strutils.h"
+
+#define EXIT_LP_MALLOC 2
+#define EXIT_LP_BADVAL 3
+#define EXIT_LP_IO_ERR 4
+
+#define XALLOC_EXIT_CODE EXIT_LP_MALLOC
+#include "xalloc.h"
+
+struct command {
+ long op;
+ long val;
+ struct command *next;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] <device>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Set various parameters for the line printer.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -i, --irq <num> specify parallel port irq\n"), out);
+ fputs(_(" -t, --time <ms> driver wait time in milliseconds\n"), out);
+ fputs(_(" -c, --chars <num> number of output characters before sleep\n"), out);
+ fputs(_(" -w, --wait <us> strobe wait in micro seconds\n"), out);
+ /* TRANSLATORS: do not translate <on|off> arguments. The
+ argument reader does not recognize locale, unless `on' is
+ exactly that very same string. */
+ fputs(_(" -a, --abort <on|off> abort on error\n"), out);
+ fputs(_(" -o, --check-status <on|off> check printer status before printing\n"), out);
+ fputs(_(" -C, --careful <on|off> extra checking to status check\n"), out);
+ fputs(_(" -s, --status query printer status\n"), out);
+ fputs(_(" -r, --reset reset the port\n"), out);
+ fputs(_(" -q, --print-irq <on|off> display current irq setting\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(30));
+ printf(USAGE_MAN_TAIL("tunelp(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c, fd, irq, status, show_irq, offset = 0, retval;
+ char *filename;
+ struct stat statbuf;
+ struct command *cmds, *cmdst;
+ static const struct option longopts[] = {
+ {"irq", required_argument, NULL, 'i'},
+ {"time", required_argument, NULL, 't'},
+ {"chars", required_argument, NULL, 'c'},
+ {"wait", required_argument, NULL, 'w'},
+ {"abort", required_argument, NULL, 'a'},
+ {"check-status", required_argument, NULL, 'o'},
+ {"careful", required_argument, NULL, 'C'},
+ {"status", no_argument, NULL, 's'},
+ {"trust-irq", required_argument, NULL, 'T'},
+ {"reset", no_argument, NULL, 'r'},
+ {"print-irq", required_argument, NULL, 'q'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ strutils_set_exitcode(EXIT_LP_BADVAL);
+
+ if (argc < 2) {
+ warnx(_("not enough arguments"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ cmdst = cmds = xmalloc(sizeof(struct command));
+ cmds->next = NULL;
+
+ show_irq = 1;
+ while ((c = getopt_long(argc, argv, "t:c:w:a:i:ho:C:sq:rT:vV", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'i':
+ cmds->op = LPSETIRQ;
+ cmds->val = strtol_or_err(optarg, _("argument error"));
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 't':
+ cmds->op = LPTIME;
+ cmds->val = strtol_or_err(optarg, _("argument error"));
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'c':
+ cmds->op = LPCHAR;
+ cmds->val = strtol_or_err(optarg, _("argument error"));
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'w':
+ cmds->op = LPWAIT;
+ cmds->val = strtol_or_err(optarg, _("argument error"));
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'a':
+ cmds->op = LPABORT;
+ cmds->val = parse_switch(optarg, _("argument error"), "on", "off", NULL);
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'q':
+ show_irq = parse_switch(optarg, _("argument error"), "on", "off", NULL);
+ break;
+ case 'o':
+ cmds->op = LPABORTOPEN;
+ cmds->val = parse_switch(optarg, _("argument error"), "on", "off", NULL);
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'C':
+ cmds->op = LPCAREFUL;
+ cmds->val = parse_switch(optarg, _("argument error"), "on", "off", NULL);
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 's':
+ show_irq = 0;
+ cmds->op = LPGETSTATUS;
+ cmds->val = 0;
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+ case 'r':
+ cmds->op = LPRESET;
+ cmds->val = 0;
+ cmds->next = xmalloc(sizeof(struct command));
+ cmds = cmds->next;
+ cmds->next = NULL;
+ break;
+
+ case 'h':
+ usage();
+ case 'v':
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind != argc - 1) {
+ warnx(_("no device specified"));
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ filename = xstrdup(argv[optind]);
+ fd = open(filename, O_WRONLY | O_NONBLOCK, 0);
+ /* Need to open O_NONBLOCK in case ABORTOPEN is already set
+ * and printer is off or off-line or in an error condition.
+ * Otherwise we would abort...
+ */
+ if (fd < 0)
+ err(EXIT_FAILURE, "%s", filename);
+
+ if (fstat(fd, &statbuf))
+ err(EXIT_FAILURE, "%s: stat() failed", filename);
+
+ if (!S_ISCHR(statbuf.st_mode)) {
+ warnx(_("%s not an lp device"), filename);
+ errtryhelp(EXIT_FAILURE);
+ }
+ /* Allow for binaries compiled under a new kernel to work on
+ * the old ones The irq argument to ioctl isn't touched by
+ * the old kernels, but we don't want to cause the kernel to
+ * complain if we are using a new kernel
+ */
+ if (LPGETIRQ >= 0x0600 && ioctl(fd, LPGETIRQ, &irq) < 0
+ && errno == EINVAL)
+ /* We don't understand the new ioctls */
+ offset = 0x0600;
+
+ cmds = cmdst;
+ while (cmds->next) {
+ if (cmds->op == LPGETSTATUS) {
+ status = 0xdeadbeef;
+ retval = ioctl(fd, LPGETSTATUS - offset, &status);
+ if (retval < 0)
+ warnx(_("LPGETSTATUS error"));
+ else {
+ if (status == (int)0xdeadbeef)
+ /* a few 1.1.7x kernels will do this */
+ status = retval;
+ printf(_("%s status is %d"), filename, status);
+ if (!(status & LP_PBUSY))
+ printf(_(", busy"));
+ if (!(status & LP_PACK))
+ printf(_(", ready"));
+ if ((status & LP_POUTPA))
+ printf(_(", out of paper"));
+ if ((status & LP_PSELECD))
+ printf(_(", on-line"));
+ if (!(status & LP_PERRORP))
+ printf(_(", error"));
+ printf("\n");
+ }
+ } else if (ioctl(fd, cmds->op - offset, cmds->val) < 0)
+ warn(_("ioctl failed"));
+ cmdst = cmds;
+ cmds = cmds->next;
+ free(cmdst);
+ }
+
+ if (show_irq) {
+ irq = 0xdeadbeef;
+ retval = ioctl(fd, LPGETIRQ - offset, &irq);
+ if (retval == -1)
+ err(EXIT_LP_IO_ERR, _("LPGETIRQ error"));
+ if (irq == (int)0xdeadbeef)
+ /* up to 1.1.77 will do this */
+ irq = retval;
+ if (irq)
+ printf(_("%s using IRQ %d\n"), filename, irq);
+ else
+ printf(_("%s using polling\n"), filename);
+ }
+ free(filename);
+ close(fd);
+
+ return EXIT_SUCCESS;
+}
diff --git a/sys-utils/umount.8 b/sys-utils/umount.8
new file mode 100644
index 0000000..a66d119
--- /dev/null
+++ b/sys-utils/umount.8
@@ -0,0 +1,306 @@
+.\" Copyright (c) 1996 Andries Brouwer
+.\" This page is somewhat derived from a page that was
+.\" (c) 1980, 1989, 1991 The Regents of the University of California
+.\" and had been heavily modified by Rik Faith and myself.
+.\"
+.\" This is free documentation; 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.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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.,
+.\" 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+.\"
+.TH UMOUNT 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+umount \- unmount filesystems
+.SH SYNOPSIS
+.B umount \-a
+.RB [ \-dflnrv ]
+.RB [ \-t
+.IR fstype ]
+.RB [ \-O
+.IR option ...]
+.sp
+.B umount
+.RB [ \-dflnrv ]
+.RI { directory | device }...
+.sp
+.B umount
+.BR \-h | \-V
+
+.SH DESCRIPTION
+The
+.B umount
+command detaches the mentioned filesystem(s) from the file hierarchy. A
+filesystem is specified by giving the directory where it has been
+mounted. Giving the special device on which the filesystem lives may
+also work, but is obsolete, mainly because it will fail in case this
+device was mounted on more than one directory.
+.PP
+Note that a filesystem cannot be unmounted when it is 'busy' - for
+example, when there are open files on it, or when some process has its
+working directory there, or when a swap file on it is in use. The
+offending process could even be
+.B umount
+itself - it opens libc, and libc in its turn may open for example locale
+files. A lazy unmount avoids this problem, but it may introduce other
+issues. See \fB\-\-lazy\fR description below.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-all"
+All of the filesystems described in
+.I /proc/self/mountinfo
+(or in deprecated
+.IR /etc/mtab )
+are unmounted, except the proc, devfs, devpts, sysfs, rpc_pipefs and nfsd
+filesystems. This list of the filesystems may be replaced by \fB\-\-types\fR
+umount option.
+.TP
+.BR \-A , " \-\-all\-targets"
+Unmount all mountpoints in the current mount namespace
+for the specified filesystem.
+The filesystem can be specified by one of the mountpoints or the device name (or
+UUID, etc.). When this option is used together with \fB\-\-recursive\fR, then
+all nested mounts within the filesystem are recursively unmounted.
+This option is only supported on systems where
+.I /etc/mtab
+is a symlink
+to
+.IR /proc/mounts .
+.TP
+.BR \-c , " \-\-no\-canonicalize"
+Do not canonicalize paths. The paths canonicalization is based on
+.BR stat (2)
+and
+.BR readlink (2)
+system calls. These system calls may hang in some cases (for example on NFS if
+server is not available). The option has to be used with canonical path to the
+mount point.
+
+For more details about this option see the
+.BR mount (8)
+man page. Note that \fBumount\fR does not pass this option to the
+.BI /sbin/umount. type
+helpers.
+.TP
+.BR \-d , " \-\-detach\-loop"
+When the unmounted device was a loop device, also free this loop
+device. This option is unnecessary for devices initialized by
+.BR mount (8),
+in this case "autoclear" functionality is enabled by default.
+.TP
+.B \-\-fake
+Causes everything to be done except for the actual system call or umount helper
+execution; this 'fakes' unmounting the filesystem. It can be used to remove
+entries from the deprecated
+.I /etc/mtab
+that were unmounted earlier with the
+.B \-n
+option.
+.TP
+.BR \-f , " \-\-force"
+Force an unmount (in case of an unreachable NFS system).
+
+Note that this option does not guarantee that umount command does not hang.
+It's strongly recommended to use absolute paths without symlinks to avoid
+unwanted readlink and stat system calls on unreachable NFS in umount.
+.TP
+.BR \-i , " \-\-internal\-only"
+Do not call the \fB/sbin/umount.\fIfilesystem\fR helper even if it exists.
+By default such a helper program is called if it exists.
+.TP
+.BR \-l , " \-\-lazy"
+Lazy unmount. Detach the filesystem from the file hierarchy now,
+and clean up all references to this filesystem as soon as it is not busy
+anymore.
+
+A system reboot would be expected in near future if you're going to use this
+option for network filesystem or local filesystem with submounts. The
+recommended use-case for \fBumount \-l\fR is to prevent hangs on shutdown due to
+an unreachable network share where a normal umount will hang due to a downed
+server or a network partition. Remounts of the share will not be possible.
+
+.TP
+.BR \-N , " \-\-namespace " \fIns
+Perform umount in the mount namespace specified by \fIns\fR.
+\fIns\fR is either PID of process running in that namespace
+or special file representing that namespace.
+.sp
+.BR umount (8)
+switches to the namespace when it reads
+.IR /etc/fstab ,
+writes
+.I /etc/mtab
+(or writes to
+.IR /run/mount )
+and calls
+.BR umount (2)
+system call, otherwise it runs in the original namespace.
+It means that the target mount namespace does not have
+to contain any libraries or other requirements necessary to execute
+.BR umount (2)
+command.
+.sp
+See \fBmount_namespaces\fR(7) for more information.
+.TP
+.BR \-n , " \-\-no\-mtab"
+Unmount without writing in
+.IR /etc/mtab .
+.TP
+.BR \-O , " \-\-test\-opts " \fIoption\fR...
+Unmount only the filesystems that have the specified option set in
+.IR /etc/fstab .
+More than one option may be specified in a comma-separated list.
+Each option can be prefixed with
+.B no
+to indicate that no action should be taken for this option.
+.TP
+.BR \-q , " \-\-quiet"
+Suppress "not mounted" error messages.
+.TP
+.BR \-R , " \-\-recursive"
+Recursively unmount each specified directory. Recursion for each directory will
+stop if any unmount operation in the chain fails for any reason. The relationship
+between mountpoints is determined by
+.I /proc/self/mountinfo
+entries. The filesystem
+must be specified by mountpoint path; a recursive unmount by device name (or UUID)
+is unsupported.
+.TP
+.BR \-r , " \-\-read\-only"
+When an unmount fails, try to remount the filesystem read-only.
+.TP
+.BR \-t , " \-\-types " \fItype\fR...
+Indicate that the actions should only be taken on filesystems of the
+specified
+.IR type .
+More than one type may be specified in a comma-separated list. The list
+of filesystem types can be prefixed with
+.B no
+to indicate that no action should be taken for all of the mentioned types.
+Note that
+.B umount
+reads information about mounted filesystems from kernel (/proc/mounts) and
+filesystem names may be different than filesystem names used in the
+.I /etc/fstab
+(e.g., "nfs4" vs. "nfs").
+.TP
+.BR \-v , " \-\-verbose"
+Verbose mode.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NON-SUPERUSER UMOUNTS
+Normally, only the superuser can umount filesystems.
+However, when
+.I fstab
+contains the
+.B user
+option on a line, anybody can umount the corresponding filesystem. For more details see
+.BR mount (8)
+man page.
+.PP
+Since version 2.34 the \fBumount\fR command can be used to
+perform umount operation also
+for fuse filesystems if kernel mount table contains user's ID. In this case fstab
+user= mount option is not required.
+.PP
+Since version 2.35 \fBumount\fR command does not exit when user permissions are
+inadequate by internal libmount security rules. It drops suid permissions
+and continue as regular non-root user.
+This can be used to support use-cases where
+root permissions are not necessary (e.g., fuse filesystems, user namespaces,
+etc).
+.SH LOOP DEVICE
+The
+.B umount
+command will automatically detach loop device previously initialized by
+.BR mount (8)
+command independently of
+.IR /etc/mtab .
+
+In this case the device is initialized with "autoclear" flag (see
+.BR losetup (8)
+output for more details), otherwise it's necessary to use the option \fB \-\-detach\-loop\fR
+or call \fBlosetup \-d <device>\fR. The autoclear feature is supported since Linux 2.6.25.
+.SH EXTERNAL HELPERS
+The syntax of external unmount helpers is:
+.PP
+.RS
+.BI umount. suffix
+.RI { directory | device }
+.RB [ \-flnrv ]
+.RB [ \-N
+.IR namespace ]
+.RB [ \-t
+.IR type . subtype ]
+.RE
+.PP
+where \fIsuffix\fR is the filesystem type (or the value from a
+\fBuhelper=\fR or \fBhelper=\fR marker in the mtab file).
+The \fB\-t\fR option can be used for filesystems that
+have subtype support. For example:
+.PP
+.RS
+.B umount.fuse \-t fuse.sshfs
+.RE
+.PP
+A \fBuhelper=\fIsomething\fR marker (unprivileged helper) can appear in
+the \fI/etc/mtab\fR file when ordinary users need to be able to unmount
+a mountpoint that is not defined in \fI/etc/fstab\fR
+(for example for a device that was mounted by \fBudisks\fR(1)).
+.PP
+A \fBhelper=\fItype\fR marker in the mtab file will redirect
+all unmount requests
+to the \fB/sbin/umount.\fItype\fR helper independently of UID.
+.PP
+Note that \fI/etc/mtab\fR is currently deprecated and helper= and other
+userspace mount options are maintained by libmount.
+.SH ENVIRONMENT
+.IP LIBMOUNT_FSTAB=<path>
+overrides the default location of the fstab file (ignored for suid)
+.IP LIBMOUNT_MTAB=<path>
+overrides the default location of the mtab file (ignored for suid)
+.IP LIBMOUNT_DEBUG=all
+enables libmount debug output
+.SH FILES
+.TP
+.I /etc/mtab
+table of mounted filesystems (deprecated and usually replaced by
+symlink to
+.IR /proc/mounts )
+.TP
+.I /etc/fstab
+table of known filesystems
+.TP
+.I /proc/self/mountinfo
+table of mounted filesystems generated by kernel.
+.SH HISTORY
+A
+.B umount
+command appeared in Version 6 AT&T UNIX.
+.SH SEE ALSO
+.BR umount (2),
+.BR losetup (8),
+.BR mount_namespaces (7)
+.BR mount (8)
+.SH AVAILABILITY
+The umount command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/umount.c b/sys-utils/umount.c
new file mode 100644
index 0000000..056ffb8
--- /dev/null
+++ b/sys-utils/umount.c
@@ -0,0 +1,626 @@
+/*
+ * umount(8) -- mount a filesystem
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <libmount.h>
+
+#include "nls.h"
+#include "c.h"
+#include "env.h"
+#include "closestream.h"
+#include "pathnames.h"
+#include "canonicalize.h"
+
+#define XALLOC_EXIT_CODE MNT_EX_SYSERR
+#include "xalloc.h"
+
+#define OPTUTILS_EXIT_CODE MNT_EX_USAGE
+#include "optutils.h"
+
+static int quiet;
+
+static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)),
+ const char *filename, int line)
+{
+ if (filename)
+ warnx(_("%s: parse error at line %d -- ignored"), filename, line);
+ return 1;
+}
+
+
+static void __attribute__((__noreturn__)) umount_print_version(void)
+{
+ const char *ver = NULL;
+ const char **features = NULL, **p;
+
+ mnt_get_library_version(&ver);
+ mnt_get_library_features(&features);
+
+ printf(_("%s from %s (libmount %s"),
+ program_invocation_short_name,
+ PACKAGE_STRING,
+ ver);
+ p = features;
+ while (p && *p) {
+ fputs(p == features ? ": " : ", ", stdout);
+ fputs(*p++, stdout);
+ }
+ fputs(")\n", stdout);
+ exit(MNT_EX_SUCCESS);
+}
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %1$s [-hV]\n"
+ " %1$s -a [options]\n"
+ " %1$s [options] <source> | <directory>\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Unmount filesystems.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --all unmount all filesystems\n"), out);
+ fputs(_(" -A, --all-targets unmount all mountpoints for the given device in the\n"
+ " current namespace\n"), out);
+ fputs(_(" -c, --no-canonicalize don't canonicalize paths\n"), out);
+ fputs(_(" -d, --detach-loop if mounted loop device, also free this loop device\n"), out);
+ fputs(_(" --fake dry run; skip the umount(2) syscall\n"), out);
+ fputs(_(" -f, --force force unmount (in case of an unreachable NFS system)\n"), out);
+ fputs(_(" -i, --internal-only don't call the umount.<type> helpers\n"), out);
+ fputs(_(" -n, --no-mtab don't write to /etc/mtab\n"), out);
+ fputs(_(" -l, --lazy detach the filesystem now, clean up things later\n"), out);
+ fputs(_(" -O, --test-opts <list> limit the set of filesystems (use with -a)\n"), out);
+ fputs(_(" -R, --recursive recursively unmount a target with all its children\n"), out);
+ fputs(_(" -r, --read-only in case unmounting fails, try to remount read-only\n"), out);
+ fputs(_(" -t, --types <list> limit the set of filesystem types\n"), out);
+ fputs(_(" -v, --verbose say what is being done\n"), out);
+ fputs(_(" -q, --quiet suppress 'not mounted' error messages\n"), out);
+ fputs(_(" -N, --namespace <ns> perform umount in another namespace\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+ printf(USAGE_MAN_TAIL("umount(8)"));
+
+ exit(MNT_EX_SUCCESS);
+}
+
+static void suid_drop(struct libmnt_context *cxt)
+{
+ const uid_t ruid = getuid();
+ const uid_t euid = geteuid();
+
+ if (ruid != 0 && euid == 0) {
+ if (setgid(getgid()) < 0)
+ err(MNT_EX_FAIL, _("setgid() failed"));
+
+ if (setuid(getuid()) < 0)
+ err(MNT_EX_FAIL, _("setuid() failed"));
+ }
+
+ /* be paranoid and check it, setuid(0) has to fail */
+ if (ruid != 0 && setuid(0) == 0)
+ errx(MNT_EX_FAIL, _("drop permissions failed."));
+
+ mnt_context_force_unrestricted(cxt);
+}
+
+static void success_message(struct libmnt_context *cxt)
+{
+ const char *tgt, *src;
+
+ if (mnt_context_helper_executed(cxt)
+ || mnt_context_get_status(cxt) != 1)
+ return;
+
+ tgt = mnt_context_get_target(cxt);
+ if (!tgt)
+ return;
+
+ src = mnt_context_get_source(cxt);
+ if (src)
+ warnx(_("%s (%s) unmounted"), tgt, src);
+ else
+ warnx(_("%s unmounted"), tgt);
+}
+
+static int mk_exit_code(struct libmnt_context *cxt, int rc)
+{
+ char buf[BUFSIZ] = { 0 };
+
+ rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf));
+
+ /* suppress "not mounted" error message */
+ if (quiet &&
+ rc == MNT_EX_FAIL &&
+ mnt_context_syscall_called(cxt) &&
+ mnt_context_get_syscall_errno(cxt) == EINVAL)
+ return rc;
+
+ /* print errors/warnings */
+ if (*buf) {
+ const char *spec = mnt_context_get_target(cxt);
+ if (!spec)
+ spec = mnt_context_get_source(cxt);
+ if (!spec)
+ spec = "???";
+ warnx("%s: %s.", spec, buf);
+ }
+ return rc;
+}
+
+static int umount_all(struct libmnt_context *cxt)
+{
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+ int mntrc, ignored, rc = 0;
+
+ itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ if (!itr) {
+ warn(_("failed to initialize libmount iterator"));
+ return MNT_EX_SYSERR;
+ }
+
+ while (mnt_context_next_umount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
+
+ const char *tgt = mnt_fs_get_target(fs);
+
+ if (ignored) {
+ if (mnt_context_is_verbose(cxt))
+ printf(_("%-25s: ignored\n"), tgt);
+ } else {
+ int xrc = mk_exit_code(cxt, mntrc);
+
+ if (xrc == MNT_EX_SUCCESS
+ && mnt_context_is_verbose(cxt))
+ printf("%-25s: successfully unmounted\n", tgt);
+ rc |= xrc;
+ }
+ }
+
+ mnt_free_iter(itr);
+ return rc;
+}
+
+static int umount_one(struct libmnt_context *cxt, const char *spec)
+{
+ int rc;
+
+ if (!spec)
+ return MNT_EX_SOFTWARE;
+
+ if (mnt_context_set_target(cxt, spec))
+ err(MNT_EX_SYSERR, _("failed to set umount target"));
+
+ rc = mnt_context_umount(cxt);
+
+ if (rc == -EPERM
+ && mnt_context_is_restricted(cxt)
+ && mnt_context_tab_applied(cxt)
+ && !mnt_context_syscall_called(cxt)) {
+ /* Mountpoint exists, but failed something else in libmount,
+ * drop perms and try it again */
+ suid_drop(cxt);
+ rc = mnt_context_umount(cxt);
+ }
+
+ rc = mk_exit_code(cxt, rc);
+
+ if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt))
+ success_message(cxt);
+
+ mnt_reset_context(cxt);
+ return rc;
+}
+
+static struct libmnt_table *new_mountinfo(struct libmnt_context *cxt)
+{
+ struct libmnt_table *tb;
+ struct libmnt_ns *ns_old = mnt_context_switch_target_ns(cxt);
+
+ if (!ns_old)
+ err(MNT_EX_SYSERR, _("failed to switch namespace"));
+
+ tb = mnt_new_table();
+ if (!tb)
+ err(MNT_EX_SYSERR, _("libmount table allocation failed"));
+
+ mnt_table_set_parser_errcb(tb, table_parser_errcb);
+ mnt_table_set_cache(tb, mnt_context_get_cache(cxt));
+
+ if (mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO)) {
+ warn(_("failed to parse %s"), _PATH_PROC_MOUNTINFO);
+ mnt_unref_table(tb);
+ tb = NULL;
+ }
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ err(MNT_EX_SYSERR, _("failed to switch namespace"));
+
+ return tb;
+}
+
+/*
+ * like umount_one() but does not return error is @spec not mounted
+ */
+static int umount_one_if_mounted(struct libmnt_context *cxt, const char *spec)
+{
+ int rc;
+ struct libmnt_fs *fs;
+
+ rc = mnt_context_find_umount_fs(cxt, spec, &fs);
+ if (rc == 1) {
+ rc = MNT_EX_SUCCESS; /* already unmounted */
+ mnt_reset_context(cxt);
+ } else if (rc < 0) {
+ rc = mk_exit_code(cxt, rc); /* error */
+ mnt_reset_context(cxt);
+ } else
+ rc = umount_one(cxt, mnt_fs_get_target(fs));
+
+ return rc;
+}
+
+static int umount_do_recurse(struct libmnt_context *cxt,
+ struct libmnt_table *tb, struct libmnt_fs *fs)
+{
+ struct libmnt_fs *child;
+ struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ int rc;
+
+ if (!itr)
+ err(MNT_EX_SYSERR, _("libmount iterator allocation failed"));
+
+ /* umount all children */
+ for (;;) {
+ rc = mnt_table_next_child_fs(tb, itr, fs, &child);
+ if (rc < 0) {
+ warnx(_("failed to get child fs of %s"),
+ mnt_fs_get_target(fs));
+ rc = MNT_EX_SOFTWARE;
+ goto done;
+ } else if (rc == 1)
+ break; /* no more children */
+
+ rc = umount_do_recurse(cxt, tb, child);
+ if (rc != MNT_EX_SUCCESS)
+ goto done;
+ }
+
+ rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
+done:
+ mnt_free_iter(itr);
+ return rc;
+}
+
+static int umount_recursive(struct libmnt_context *cxt, const char *spec)
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ int rc;
+
+ tb = new_mountinfo(cxt);
+ if (!tb)
+ return MNT_EX_SOFTWARE;
+
+ /* it's always real mountpoint, don't assume that the target maybe a device */
+ mnt_context_disable_swapmatch(cxt, 1);
+
+ fs = mnt_table_find_target(tb, spec, MNT_ITER_BACKWARD);
+ if (fs)
+ rc = umount_do_recurse(cxt, tb, fs);
+ else {
+ rc = MNT_EX_USAGE;
+ if (!quiet)
+ warnx(access(spec, F_OK) == 0 ?
+ _("%s: not mounted") :
+ _("%s: not found"), spec);
+ }
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int umount_alltargets(struct libmnt_context *cxt, const char *spec, int rec)
+{
+ struct libmnt_fs *fs;
+ struct libmnt_table *tb;
+ struct libmnt_iter *itr = NULL;
+ dev_t devno = 0;
+ int rc;
+
+ /* Convert @spec to device name, Use the same logic like regular
+ * "umount <spec>".
+ */
+ rc = mnt_context_find_umount_fs(cxt, spec, &fs);
+ if (rc == 1) {
+ rc = MNT_EX_USAGE;
+ if (!quiet)
+ warnx(access(spec, F_OK) == 0 ?
+ _("%s: not mounted") :
+ _("%s: not found"), spec);
+ return rc;
+ }
+ if (rc < 0)
+ return mk_exit_code(cxt, rc); /* error */
+
+ if (!mnt_fs_get_srcpath(fs) || !mnt_fs_get_devno(fs))
+ errx(MNT_EX_USAGE, _("%s: failed to determine source "
+ "(--all-targets is unsupported on systems with "
+ "regular mtab file)."), spec);
+
+ itr = mnt_new_iter(MNT_ITER_BACKWARD);
+ if (!itr)
+ err(MNT_EX_SYSERR, _("libmount iterator allocation failed"));
+
+ /* get on @cxt independent mountinfo */
+ tb = new_mountinfo(cxt);
+ if (!tb) {
+ rc = MNT_EX_SOFTWARE;
+ goto done;
+ }
+
+ /* Note that @fs is from mount context and the context will be reset
+ * after each umount() call */
+ devno = mnt_fs_get_devno(fs);
+ fs = NULL;
+
+ mnt_reset_context(cxt);
+
+ while (mnt_table_next_fs(tb, itr, &fs) == 0) {
+ if (mnt_fs_get_devno(fs) != devno)
+ continue;
+ mnt_context_disable_swapmatch(cxt, 1);
+ if (rec)
+ rc = umount_do_recurse(cxt, tb, fs);
+ else
+ rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
+
+ if (rc != MNT_EX_SUCCESS)
+ break;
+ }
+
+done:
+ mnt_free_iter(itr);
+ mnt_unref_table(tb);
+
+ return rc;
+}
+
+/*
+ * Check path -- non-root user should not be able to resolve path which is
+ * unreadable for him.
+ */
+static char *sanitize_path(const char *path)
+{
+ char *p;
+
+ if (!path)
+ return NULL;
+
+ p = canonicalize_path_restricted(path);
+ if (!p)
+ err(MNT_EX_USAGE, "%s", path);
+
+ return p;
+}
+
+static pid_t parse_pid(const char *str)
+{
+ char *end;
+ pid_t ret;
+
+ errno = 0;
+ ret = strtoul(str, &end, 10);
+
+ if (ret < 0 || errno || end == str || (end && *end))
+ return 0;
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int c, rc = 0, all = 0, recursive = 0, alltargets = 0;
+ struct libmnt_context *cxt;
+ char *types = NULL;
+
+ enum {
+ UMOUNT_OPT_FAKE = CHAR_MAX + 1,
+ };
+
+ static const struct option longopts[] = {
+ { "all", no_argument, NULL, 'a' },
+ { "all-targets", no_argument, NULL, 'A' },
+ { "detach-loop", no_argument, NULL, 'd' },
+ { "fake", no_argument, NULL, UMOUNT_OPT_FAKE },
+ { "force", no_argument, NULL, 'f' },
+ { "help", no_argument, NULL, 'h' },
+ { "internal-only", no_argument, NULL, 'i' },
+ { "lazy", no_argument, NULL, 'l' },
+ { "no-canonicalize", no_argument, NULL, 'c' },
+ { "no-mtab", no_argument, NULL, 'n' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "read-only", no_argument, NULL, 'r' },
+ { "recursive", no_argument, NULL, 'R' },
+ { "test-opts", required_argument, NULL, 'O' },
+ { "types", required_argument, NULL, 't' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { "namespace", required_argument, NULL, 'N' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'A','a' }, /* all-targets,all */
+ { 'R','a' }, /* recursive,all */
+ { 'O','R','t'}, /* options,recursive,types */
+ { 'R','r' }, /* recursive,read-only */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ sanitize_env();
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ mnt_init_debug(0);
+ cxt = mnt_new_context();
+ if (!cxt)
+ err(MNT_EX_SYSERR, _("libmount context allocation failed"));
+
+ mnt_context_set_tables_errcb(cxt, table_parser_errcb);
+
+ while ((c = getopt_long(argc, argv, "aAcdfhilnqRrO:t:vVN:",
+ longopts, NULL)) != -1) {
+
+
+ /* only few options are allowed for non-root users */
+ if (mnt_context_is_restricted(cxt) && !strchr("hdilqVv", c))
+ suid_drop(cxt);
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'a':
+ all = 1;
+ break;
+ case 'A':
+ alltargets = 1;
+ break;
+ case 'c':
+ mnt_context_disable_canonicalize(cxt, TRUE);
+ break;
+ case 'd':
+ mnt_context_enable_loopdel(cxt, TRUE);
+ break;
+ case UMOUNT_OPT_FAKE:
+ mnt_context_enable_fake(cxt, TRUE);
+ break;
+ case 'f':
+ mnt_context_enable_force(cxt, TRUE);
+ break;
+ case 'i':
+ mnt_context_disable_helpers(cxt, TRUE);
+ break;
+ case 'l':
+ mnt_context_enable_lazy(cxt, TRUE);
+ break;
+ case 'n':
+ mnt_context_disable_mtab(cxt, TRUE);
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ mnt_context_enable_rdonly_umount(cxt, TRUE);
+ break;
+ case 'R':
+ recursive = TRUE;
+ break;
+ case 'O':
+ if (mnt_context_set_options_pattern(cxt, optarg))
+ err(MNT_EX_SYSERR, _("failed to set options pattern"));
+ break;
+ case 't':
+ types = optarg;
+ break;
+ case 'v':
+ mnt_context_enable_verbose(cxt, TRUE);
+ break;
+ case 'N':
+ {
+ char path[PATH_MAX];
+ pid_t pid = parse_pid(optarg);
+
+ if (pid)
+ snprintf(path, sizeof(path), "/proc/%i/ns/mnt", pid);
+
+ if (mnt_context_set_target_ns(cxt, pid ? path : optarg))
+ err(MNT_EX_SYSERR, _("failed to set target namespace to %s"), pid ? path : optarg);
+ break;
+ }
+
+ case 'h':
+ mnt_free_context(cxt);
+ usage();
+ case 'V':
+ mnt_free_context(cxt);
+ umount_print_version();
+ default:
+ errtryhelp(MNT_EX_USAGE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (all) {
+ if (argc) {
+ warnx(_("unexpected number of arguments"));
+ errtryhelp(MNT_EX_USAGE);
+ }
+ if (!types)
+ types = "noproc,nodevfs,nodevpts,nosysfs,norpc_pipefs,nonfsd,noselinuxfs";
+
+ mnt_context_set_fstype_pattern(cxt, types);
+ rc = umount_all(cxt);
+
+ } else if (argc < 1) {
+ warnx(_("bad usage"));
+ errtryhelp(MNT_EX_USAGE);
+
+ } else if (alltargets) {
+ while (argc--)
+ rc += umount_alltargets(cxt, *argv++, recursive);
+ } else if (recursive) {
+ while (argc--)
+ rc += umount_recursive(cxt, *argv++);
+ } else {
+ while (argc--) {
+ char *path = *argv;
+
+ if (mnt_context_is_restricted(cxt)
+ && !mnt_tag_is_valid(path))
+ path = sanitize_path(path);
+
+ rc += umount_one(cxt, path);
+
+ if (path != *argv)
+ free(path);
+ argv++;
+ }
+ }
+
+ mnt_free_context(cxt);
+ return (rc < 256) ? rc : 255;
+}
+
diff --git a/sys-utils/unshare.1 b/sys-utils/unshare.1
new file mode 100644
index 0000000..b193ec5
--- /dev/null
+++ b/sys-utils/unshare.1
@@ -0,0 +1,414 @@
+.TH UNSHARE 1 "February 2016" "util-linux" "User Commands"
+.SH NAME
+unshare \- run program in new namespaces
+.SH SYNOPSIS
+.B unshare
+[options]
+.RI [ program
+.RI [ arguments ]]
+.SH DESCRIPTION
+The
+.B unshare
+command creates new namespaces
+(as specified by the command-line options described below)
+and then executes the specified \fIprogram\fR.
+If \fIprogram\fR is not given, then ``${SHELL}'' is
+run (default: /bin/sh).
+.PP
+By default, a new namespace persists only as long as it has member processes.
+A new namespace can be made persistent even when it has no member processes
+by bind mounting
+/proc/\fIpid\fR/ns/\fItype\fR files to a filesystem path.
+A namespace that has been made persistent in this way can subsequently
+be entered with
+.BR \%nsenter (1)
+even after the \fIprogram\fR terminates (except PID namespaces where
+a permanently running init process is required).
+Once a persistent \%namespace is no longer needed,
+it can be unpersisted by using
+.BR umount (8)
+to remove the bind mount.
+See the \fBEXAMPLES\fR section for more details.
+.PP
+.B unshare
+since util-linux version 2.36 uses /\fIproc/[pid]/ns/pid_for_children\fP and \fI/proc/[pid]/ns/time_for_children\fP
+files for persistent PID and TIME namespaces. This change requires Linux kernel 4.17 or newer.
+.PP
+The following types of namespaces can be created with
+.BR unshare :
+.TP
+.B mount namespace
+Mounting and unmounting filesystems will not affect the rest of the system,
+except for filesystems which are explicitly marked as
+shared (with \fBmount \-\-make-shared\fP; see \fI/proc/self/mountinfo\fP or
+\fBfindmnt \-o+PROPAGATION\fP for the \fBshared\fP flags).
+For further details, see
+.BR mount_namespaces (7).
+.IP
+.B unshare
+since util-linux version 2.27 automatically sets propagation to \fBprivate\fP
+in a new mount namespace to make sure that the new namespace is really
+unshared. It's possible to disable this feature with option
+\fB\-\-propagation unchanged\fP.
+Note that \fBprivate\fP is the kernel default.
+.TP
+.B UTS namespace
+Setting hostname or domainname will not affect the rest of the system.
+For further details, see
+.BR uts_namespaces (7).
+.TP
+.B IPC namespace
+The process will have an independent namespace for POSIX message queues
+as well as System V \%message queues,
+semaphore sets and shared memory segments.
+For further details, see
+.BR ipc_namespaces (7).
+.TP
+.B network namespace
+The process will have independent IPv4 and IPv6 stacks, IP routing tables,
+firewall rules, the \fI/proc/net\fP and \fI/sys/class/net\fP directory trees,
+sockets, etc.
+For further details, see
+.BR network_namespaces (7).
+.TP
+.B PID namespace
+Children will have a distinct set of PID-to-process mappings from their parent.
+For further details, see
+.BR pid_namespaces (7).
+.TP
+.B cgroup namespace
+The process will have a virtualized view of \fI/proc\:/self\:/cgroup\fP, and new
+cgroup mounts will be rooted at the namespace cgroup root.
+For further details, see
+.BR cgroup_namespaces (7).
+.TP
+.B user namespace
+The process will have a distinct set of UIDs, GIDs and capabilities.
+For further details, see
+.BR user_namespaces (7).
+.TP
+.B time namespace
+The process can have a distinct view of
+.B CLOCK_MONOTONIC
+and/or
+.B CLOCK_BOOTTIME
+which can be changed using \fI/proc/self/timens_offsets\fP.
+For further details, see
+.BR time_namespaces (7).
+.SH OPTIONS
+.TP
+.BR \-i , " \-\-ipc" [ =\fIfile ]
+Unshare the IPC namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+.TP
+.BR \-m , " \-\-mount" [ =\fIfile ]
+Unshare the mount namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+Note that \fIfile\fP must be located on a mount whose propagation type
+is not \fBshared\fP (or an error results).
+Use the command \fBfindmnt \-o+PROPAGATION\fP
+when not sure about the current setting. See also the examples below.
+.TP
+.BR \-n , " \-\-net" [ =\fIfile ]
+Unshare the network namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+.TP
+.BR \-p , " \-\-pid" [ =\fIfile ]
+Unshare the PID namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+(Creation of a persistent PID namespace will fail if the
+.B \-\-fork
+option is not also specified.)
+.IP
+See also the \fB\-\-fork\fP and
+\fB\-\-mount-proc\fP options.
+.TP
+.BR \-u , " \-\-uts" [ =\fIfile ]
+Unshare the UTS namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+.TP
+.BR \-U , " \-\-user" [ =\fIfile ]
+Unshare the user namespace. If \fIfile\fP is specified, then a persistent
+namespace is created by a bind mount.
+.TP
+.BR \-C , " \-\-cgroup" [ =\fIfile ]
+Unshare the cgroup namespace. If \fIfile\fP is specified then persistent namespace is created
+by bind mount.
+.TP
+.BR \-T , " \-\-time" [ =\fIfile ]
+Unshare the time namespace. If \fIfile\fP is specified then a persistent
+namespace is created by a bind mount. The \fB\-\-monotonic\fP and
+\fB\-\-boottime\fP options can be used to specify the corresponding
+offset in the time namespace.
+.TP
+.BR \-f , " \-\-fork"
+Fork the specified \fIprogram\fR as a child process of \fBunshare\fR rather
+than running it directly. This is useful when creating a new PID namespace.
+Note that when \fBunshare\fR is waiting for the child process,
+then it ignores SIGINT and SIGTERM and does not forward any signals to the
+child. It is necessary to send signals to the child process.
+.TP
+.B \-\-keep\-caps
+When the \fB\-\-user\fP option is given, ensure that capabilities granted
+in the user namespace are preserved in the child process.
+.TP
+.BR \-\-kill\-child [ =\fIsigname ]
+When \fBunshare\fR terminates, have \fIsigname\fP be sent to the forked child process.
+Combined with \fB\-\-pid\fR this allows for an easy and reliable killing of the entire
+process tree below \fBunshare\fR.
+If not given, \fIsigname\fP defaults to \fBSIGKILL\fR.
+This option implies \fB\-\-fork\fR.
+.TP
+.BR \-\-mount\-proc [ =\fImountpoint ]
+Just before running the program, mount the proc filesystem at \fImountpoint\fP
+(default is /proc). This is useful when creating a new PID namespace. It also
+implies creating a new mount namespace since the /proc mount would otherwise
+mess up existing programs on the system. The new proc filesystem is explicitly
+mounted as private (with MS_PRIVATE|MS_REC).
+.TP
+.BI \-\-map\-user= uid|name
+Run the program only after the current effective user ID has been mapped to \fIuid\fP.
+If this option is specified multiple times, the last occurrence takes precedence.
+This option implies \fB\-\-user\fR.
+.TP
+.BI \-\-map\-group= gid|name
+Run the program only after the current effective group ID has been mapped to \fIgid\fP.
+If this option is specified multiple times, the last occurrence takes precedence.
+This option implies \fB\-\-setgroups=deny\fR and \fB\-\-user\fR.
+.TP
+.BR \-r , " \-\-map\-root\-user"
+Run the program only after the current effective user and group IDs have been mapped to
+the superuser UID and GID in the newly created user namespace. This makes it possible to
+conveniently gain capabilities needed to manage various aspects of the newly created
+namespaces (such as configuring interfaces in the network namespace or mounting filesystems in
+the mount namespace) even when run unprivileged. As a mere convenience feature, it does not support
+more sophisticated use cases, such as mapping multiple ranges of UIDs and GIDs.
+This option implies \fB\-\-setgroups=deny\fR and \fB\-\-user\fR.
+This option is equivalent to \fB\-\-map-user=0 \-\-map-group=0\fR.
+.TP
+.BR \-c , " \-\-map\-current\-user"
+Run the program only after the current effective user and group IDs have been mapped to
+the same UID and GID in the newly created user namespace. This option implies
+\fB\-\-setgroups=deny\fR and \fB\-\-user\fR.
+This option is equivalent to \fB\-\-map-user=$(id -ru) \-\-map-group=$(id -rg)\fR.
+.TP
+.BR "\-\-propagation private" | shared | slave | unchanged
+Recursively set the mount propagation flag in the new mount namespace. The default
+is to set the propagation to \fIprivate\fP. It is possible to disable this feature
+with the argument \fBunchanged\fR. The option is silently ignored when the mount
+namespace (\fB\-\-mount\fP) is not requested.
+.TP
+.BR "\-\-setgroups allow" | deny
+Allow or deny the
+.BR setgroups (2)
+system call in a user namespace.
+.sp
+To be able to call
+.BR setgroups (2),
+the calling process must at least have CAP_SETGID.
+But since Linux 3.19 a further restriction applies:
+the kernel gives permission to call
+.BR \%setgroups (2)
+only after the GID map (\fB/proc/\fIpid\fB/gid_map\fR) has been set.
+The GID map is writable by root when
+.BR \%setgroups (2)
+is enabled (i.e., \fBallow\fR, the default), and
+the GID map becomes writable by unprivileged processes when
+.BR \%setgroups (2)
+is permanently disabled (with \fBdeny\fR).
+.TP
+.BR \-R , " \-\-root=\fIdir"
+run the command with root directory set to \fIdir\fP.
+.TP
+.BR \-w , " \-\-wd=\fIdir"
+change working directory to \fIdir\fP.
+.TP
+.BR \-S , " \-\-setuid \fIuid"
+Set the user ID which will be used in the entered namespace.
+.TP
+.BR \-G , " \-\-setgid \fIgid"
+Set the group ID which will be used in the entered namespace and drop
+supplementary groups.
+.TP
+.BI \-\-monotonic " offset"
+Set the offset of
+.B CLOCK_MONOTONIC
+which will be used in the entered time namespace. This option requires
+unsharing a time namespace with \fB\-\-time\fP.
+.TP
+.BI \-\-boottime " offset"
+Set the offset of
+.B CLOCK_BOOTTIME
+which will be used in the entered time namespace. This option requires
+unsharing a time namespace with \fB\-\-time\fP.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+The proc and sysfs filesystems mounting as root in a user namespace have to be
+restricted so that a less privileged user can not get more access to sensitive
+files that a more privileged user made unavailable. In short the rule for proc
+and sysfs is as close to a bind mount as possible.
+.SH EXAMPLES
+The following command creates a PID namespace, using
+.B \-\-fork
+to ensure that the executed command is performed in a child process
+that (being the first process in the namespace) has PID 1.
+The
+.B \-\-mount-proc
+option ensures that a new mount namespace is also simultaneously created
+and that a new
+.BR proc (5)
+filesystem is mounted that contains information corresponding to the new
+PID namespace.
+When the
+.B readlink
+command terminates, the new namespaces are automatically torn down.
+.PP
+.in +4n
+.EX
+.B # unshare \-\-fork \-\-pid \-\-mount-proc readlink /proc/self
+1
+.EE
+.in
+.PP
+As an unprivileged user, create a new user namespace where the user's
+credentials are mapped to the root IDs inside the namespace:
+.PP
+.in +4n
+.EX
+.B $ id \-u; id \-g
+1000
+1000
+.B $ unshare \-\-user \-\-map-root-user \e
+.B " sh \-c \(aqwhoami; cat /proc/self/uid_map /proc/self/gid_map\(aq"
+root
+ 0 1000 1
+ 0 1000 1
+.EE
+.in
+.PP
+The first of the following commands creates a new persistent UTS namespace
+and modifies the hostname as seen in that namespace.
+The namespace is then entered with
+.BR nsenter (1)
+in order to display the modified hostname;
+this step demonstrates that the UTS namespace continues to exist
+even though the namespace had no member processes after the
+.B unshare
+command terminated.
+The namespace is then destroyed by removing the bind mount.
+.PP
+.in +4n
+.EX
+.B # touch /root/uts-ns
+.B # unshare \-\-uts=/root/uts-ns hostname FOO
+.B # nsenter \-\-uts=/root/uts-ns hostname
+FOO
+.B # umount /root/uts-ns
+.EE
+.in
+.PP
+The following commands
+establish a persistent mount namespace referenced by the bind mount
+.IR /root/namespaces/mnt .
+In order to ensure that the creation of that bind mount succeeds,
+the parent directory
+.RI ( /root/namespaces )
+is made a bind mount whose propagation type is not
+.BR shared .
+.PP
+.in +4n
+.EX
+.B # mount \-\-bind /root/namespaces /root/namespaces
+.B # mount \-\-make-private /root/namespaces
+.B # touch /root/namespaces/mnt
+.B # unshare \-\-mount=/root/namespaces/mnt
+.EE
+.in
+.PP
+The following commands demonstrate the use of the
+.B \-\-kill-child
+option when creating a PID namespace, in order to ensure that when
+.B unshare
+is killed, all of the processes within the PID namespace are killed.
+.PP
+.in +4n
+.EX
+.BR "# set +m " "# Don't print job status messages"
+.B # unshare \-\-pid \-\-fork \-\-mount\-proc \-\-kill\-child \-\- \e
+.B " bash \-\-norc \-c \(aq(sleep 555 &) && (ps a &) && sleep 999\(aq &"
+[1] 53456
+# PID TTY STAT TIME COMMAND
+ 1 pts/3 S+ 0:00 sleep 999
+ 3 pts/3 S+ 0:00 sleep 555
+ 5 pts/3 R+ 0:00 ps a
+
+.BR "# ps h \-o 'comm' $! " "# Show that background job is unshare(1)"
+unshare
+.BR "# kill $! " "# Kill unshare(1)"
+.B # pidof sleep
+.EE
+.in
+.PP
+The
+.B pidof
+command prints no output, because the
+.B sleep
+processes have been killed.
+More precisely, when the
+.B sleep
+process that has PID 1 in the namespace (i.e., the namespace's init process)
+was killed, this caused all other processes in the namespace to be killed.
+By contrast, a similar series of commands where the
+.B \-\-kill\-child
+option is not used shows that when
+.B unshare
+terminates, the processes in the PID namespace are not killed:
+.PP
+.in +4n
+.EX
+.B # unshare \-\-pid \-\-fork \-\-mount\-proc \-\- \e
+.B " bash \-\-norc \-c \(aq(sleep 555 &) && (ps a &) && sleep 999\(aq &"
+[1] 53479
+# PID TTY STAT TIME COMMAND
+ 1 pts/3 S+ 0:00 sleep 999
+ 3 pts/3 S+ 0:00 sleep 555
+ 5 pts/3 R+ 0:00 ps a
+
+.B # kill $!
+.B # pidof sleep
+53482 53480
+.EE
+.in
+.PP
+The following example demonstrates the creation of a time namespace
+where the boottime clock is set to a point several years in the past:
+.PP
+.in +4n
+.EX
+.BR "# uptime \-p " "# Show uptime in initial time namespace"
+up 21 hours, 30 minutes
+.B # unshare \-\-time \-\-fork \-\-boottime 300000000 uptime \-p
+up 9 years, 28 weeks, 1 day, 2 hours, 50 minutes
+.EE
+.in
+.SH AUTHORS
+.UR dottedmag@dottedmag.net
+Mikhail Gusarov
+.UE
+.br
+.UR kzak@redhat.com
+Karel Zak
+.UE
+.SH SEE ALSO
+.BR clone (2),
+.BR unshare (2),
+.BR namespaces (7),
+.BR mount (8)
+.SH AVAILABILITY
+The unshare command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/unshare.c b/sys-utils/unshare.c
new file mode 100644
index 0000000..cd5fe68
--- /dev/null
+++ b/sys-utils/unshare.c
@@ -0,0 +1,710 @@
+/*
+ * unshare(1) - command-line interface for unshare(2)
+ *
+ * Copyright (C) 2009 Mikhail Gusarov <dottedmag@dottedmag.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/prctl.h>
+#include <grp.h>
+
+/* we only need some defines missing in sys/mount.h, no libmount linkage */
+#include <libmount.h>
+
+#include "nls.h"
+#include "c.h"
+#include "caputils.h"
+#include "closestream.h"
+#include "namespace.h"
+#include "exec_shell.h"
+#include "xalloc.h"
+#include "pathnames.h"
+#include "all-io.h"
+#include "signames.h"
+#include "strutils.h"
+#include "pwdutils.h"
+
+/* synchronize parent and child by pipe */
+#define PIPE_SYNC_BYTE 0x06
+
+/* 'private' is kernel default */
+#define UNSHARE_PROPAGATION_DEFAULT (MS_REC | MS_PRIVATE)
+
+/* /proc namespace files and mountpoints for binds */
+static struct namespace_file {
+ int type; /* CLONE_NEW* */
+ const char *name; /* ns/<type> */
+ const char *target; /* user specified target for bind mount */
+} namespace_files[] = {
+ { .type = CLONE_NEWUSER, .name = "ns/user" },
+ { .type = CLONE_NEWCGROUP,.name = "ns/cgroup" },
+ { .type = CLONE_NEWIPC, .name = "ns/ipc" },
+ { .type = CLONE_NEWUTS, .name = "ns/uts" },
+ { .type = CLONE_NEWNET, .name = "ns/net" },
+ { .type = CLONE_NEWPID, .name = "ns/pid_for_children" },
+ { .type = CLONE_NEWNS, .name = "ns/mnt" },
+ { .type = CLONE_NEWTIME, .name = "ns/time_for_children" },
+ { .name = NULL }
+};
+
+static int npersists; /* number of persistent namespaces */
+
+enum {
+ SETGROUPS_NONE = -1,
+ SETGROUPS_DENY = 0,
+ SETGROUPS_ALLOW = 1,
+};
+
+static const char *setgroups_strings[] =
+{
+ [SETGROUPS_DENY] = "deny",
+ [SETGROUPS_ALLOW] = "allow"
+};
+
+static int setgroups_str2id(const char *str)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
+ if (strcmp(str, setgroups_strings[i]) == 0)
+ return i;
+
+ errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
+}
+
+static void setgroups_control(int action)
+{
+ const char *file = _PATH_PROC_SETGROUPS;
+ const char *cmd;
+ int fd;
+
+ if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
+ return;
+ cmd = setgroups_strings[action];
+
+ fd = open(file, O_WRONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return;
+ err(EXIT_FAILURE, _("cannot open %s"), file);
+ }
+
+ if (write_all(fd, cmd, strlen(cmd)))
+ err(EXIT_FAILURE, _("write failed %s"), file);
+ close(fd);
+}
+
+static void map_id(const char *file, uint32_t from, uint32_t to)
+{
+ char *buf;
+ int fd;
+
+ fd = open(file, O_WRONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), file);
+
+ xasprintf(&buf, "%u %u 1", from, to);
+ if (write_all(fd, buf, strlen(buf)))
+ err(EXIT_FAILURE, _("write failed %s"), file);
+ free(buf);
+ close(fd);
+}
+
+static unsigned long parse_propagation(const char *str)
+{
+ size_t i;
+ static const struct prop_opts {
+ const char *name;
+ unsigned long flag;
+ } opts[] = {
+ { "slave", MS_REC | MS_SLAVE },
+ { "private", MS_REC | MS_PRIVATE },
+ { "shared", MS_REC | MS_SHARED },
+ { "unchanged", 0 }
+ };
+
+ for (i = 0; i < ARRAY_SIZE(opts); i++) {
+ if (strcmp(opts[i].name, str) == 0)
+ return opts[i].flag;
+ }
+
+ errx(EXIT_FAILURE, _("unsupported propagation mode: %s"), str);
+}
+
+static void set_propagation(unsigned long flags)
+{
+ if (flags == 0)
+ return;
+
+ if (mount("none", "/", NULL, flags, NULL) != 0)
+ err(EXIT_FAILURE, _("cannot change root filesystem propagation"));
+}
+
+
+static int set_ns_target(int type, const char *path)
+{
+ struct namespace_file *ns;
+
+ for (ns = namespace_files; ns->name; ns++) {
+ if (ns->type != type)
+ continue;
+ ns->target = path;
+ npersists++;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bind_ns_files(pid_t pid)
+{
+ struct namespace_file *ns;
+ char src[PATH_MAX];
+
+ for (ns = namespace_files; ns->name; ns++) {
+ if (!ns->target)
+ continue;
+
+ snprintf(src, sizeof(src), "/proc/%u/%s", (unsigned) pid, ns->name);
+
+ if (mount(src, ns->target, NULL, MS_BIND, NULL) != 0)
+ err(EXIT_FAILURE, _("mount %s on %s failed"), src, ns->target);
+ }
+
+ return 0;
+}
+
+static ino_t get_mnt_ino(pid_t pid)
+{
+ struct stat st;
+ char path[PATH_MAX];
+
+ snprintf(path, sizeof(path), "/proc/%u/ns/mnt", (unsigned) pid);
+
+ if (stat(path, &st) != 0)
+ err(EXIT_FAILURE, _("cannot stat %s"), path);
+ return st.st_ino;
+}
+
+static void settime(time_t offset, clockid_t clk_id)
+{
+ char buf[sizeof(stringify_value(ULONG_MAX)) * 3];
+ int fd, len;
+
+ len = snprintf(buf, sizeof(buf), "%d %ld 0", clk_id, offset);
+
+ fd = open("/proc/self/timens_offsets", O_WRONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, _("failed to open /proc/self/timens_offsets"));
+
+ if (write(fd, buf, len) != len)
+ err(EXIT_FAILURE, _("failed to write to /proc/self/timens_offsets"));
+
+ close(fd);
+}
+
+static void bind_ns_files_from_child(pid_t *child, int fds[2])
+{
+ char ch;
+ pid_t ppid = getpid();
+ ino_t ino = get_mnt_ino(ppid);
+
+ if (pipe(fds) < 0)
+ err(EXIT_FAILURE, _("pipe failed"));
+
+ *child = fork();
+
+ switch (*child) {
+ case -1:
+ err(EXIT_FAILURE, _("fork failed"));
+
+ case 0: /* child */
+ close(fds[1]);
+ fds[1] = -1;
+
+ /* wait for parent */
+ if (read_all(fds[0], &ch, 1) != 1 && ch != PIPE_SYNC_BYTE)
+ err(EXIT_FAILURE, _("failed to read pipe"));
+ if (get_mnt_ino(ppid) == ino)
+ exit(EXIT_FAILURE);
+ bind_ns_files(ppid);
+ exit(EXIT_SUCCESS);
+ break;
+
+ default: /* parent */
+ close(fds[0]);
+ fds[0] = -1;
+ break;
+ }
+}
+
+static uid_t get_user(const char *s, const char *err)
+{
+ struct passwd *pw;
+ char *buf = NULL;
+ uid_t ret;
+
+ pw = xgetpwnam(s, &buf);
+ if (pw) {
+ ret = pw->pw_uid;
+ free(pw);
+ free(buf);
+ } else {
+ ret = strtoul_or_err(s, err);
+ }
+
+ return ret;
+}
+
+static gid_t get_group(const char *s, const char *err)
+{
+ struct group *gr;
+ char *buf = NULL;
+ gid_t ret;
+
+ gr = xgetgrnam(s, &buf);
+ if (gr) {
+ ret = gr->gr_gid;
+ free(gr);
+ free(buf);
+ } else {
+ ret = strtoul_or_err(s, err);
+ }
+
+ return ret;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Run a program with some namespaces unshared from the parent.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -m, --mount[=<file>] unshare mounts namespace\n"), out);
+ fputs(_(" -u, --uts[=<file>] unshare UTS namespace (hostname etc)\n"), out);
+ fputs(_(" -i, --ipc[=<file>] unshare System V IPC namespace\n"), out);
+ fputs(_(" -n, --net[=<file>] unshare network namespace\n"), out);
+ fputs(_(" -p, --pid[=<file>] unshare pid namespace\n"), out);
+ fputs(_(" -U, --user[=<file>] unshare user namespace\n"), out);
+ fputs(_(" -C, --cgroup[=<file>] unshare cgroup namespace\n"), out);
+ fputs(_(" -T, --time[=<file>] unshare time namespace\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -f, --fork fork before launching <program>\n"), out);
+ fputs(_(" --map-user=<uid>|<name> map current user to uid (implies --user)\n"), out);
+ fputs(_(" --map-group=<gid>|<name> map current group to gid (implies --user)\n"), out);
+ fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
+ fputs(_(" -c, --map-current-user map current user to itself (implies --user)\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" --kill-child[=<signame>] when dying, kill the forked child (implies --fork)\n"
+ " defaults to SIGKILL\n"), out);
+ fputs(_(" --mount-proc[=<dir>] mount proc filesystem first (implies --mount)\n"), out);
+ fputs(_(" --propagation slave|shared|private|unchanged\n"
+ " modify mount propagation in mount namespace\n"), out);
+ fputs(_(" --setgroups allow|deny control the setgroups syscall in user namespaces\n"), out);
+ fputs(_(" --keep-caps retain capabilities granted in user namespaces\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -R, --root=<dir> run the command with root directory set to <dir>\n"), out);
+ fputs(_(" -w, --wd=<dir> change working directory to <dir>\n"), out);
+ fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out);
+ fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out);
+ fputs(_(" --monotonic <offset> set clock monotonic offset (seconds) in time namespaces\n"), out);
+ fputs(_(" --boottime <offset> set clock boottime offset (seconds) in time namespaces\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(27));
+ printf(USAGE_MAN_TAIL("unshare(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ enum {
+ OPT_MOUNTPROC = CHAR_MAX + 1,
+ OPT_PROPAGATION,
+ OPT_SETGROUPS,
+ OPT_KILLCHILD,
+ OPT_KEEPCAPS,
+ OPT_MONOTONIC,
+ OPT_BOOTTIME,
+ OPT_MAPUSER,
+ OPT_MAPGROUP,
+ };
+ static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+
+ { "mount", optional_argument, NULL, 'm' },
+ { "uts", optional_argument, NULL, 'u' },
+ { "ipc", optional_argument, NULL, 'i' },
+ { "net", optional_argument, NULL, 'n' },
+ { "pid", optional_argument, NULL, 'p' },
+ { "user", optional_argument, NULL, 'U' },
+ { "cgroup", optional_argument, NULL, 'C' },
+ { "time", optional_argument, NULL, 'T' },
+
+ { "fork", no_argument, NULL, 'f' },
+ { "kill-child", optional_argument, NULL, OPT_KILLCHILD },
+ { "mount-proc", optional_argument, NULL, OPT_MOUNTPROC },
+ { "map-user", required_argument, NULL, OPT_MAPUSER },
+ { "map-group", required_argument, NULL, OPT_MAPGROUP },
+ { "map-root-user", no_argument, NULL, 'r' },
+ { "map-current-user", no_argument, NULL, 'c' },
+ { "propagation", required_argument, NULL, OPT_PROPAGATION },
+ { "setgroups", required_argument, NULL, OPT_SETGROUPS },
+ { "keep-caps", no_argument, NULL, OPT_KEEPCAPS },
+ { "setuid", required_argument, NULL, 'S' },
+ { "setgid", required_argument, NULL, 'G' },
+ { "root", required_argument, NULL, 'R' },
+ { "wd", required_argument, NULL, 'w' },
+ { "monotonic", required_argument, NULL, OPT_MONOTONIC },
+ { "boottime", required_argument, NULL, OPT_BOOTTIME },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int setgrpcmd = SETGROUPS_NONE;
+ int unshare_flags = 0;
+ int c, forkit = 0;
+ uid_t mapuser = -1;
+ gid_t mapgroup = -1;
+ int kill_child_signo = 0; /* 0 means --kill-child was not used */
+ const char *procmnt = NULL;
+ const char *newroot = NULL;
+ const char *newdir = NULL;
+ pid_t pid_bind = 0;
+ pid_t pid = 0;
+ int fds[2];
+ int status;
+ unsigned long propagation = UNSHARE_PROPAGATION_DEFAULT;
+ int force_uid = 0, force_gid = 0;
+ uid_t uid = 0, real_euid = geteuid();
+ gid_t gid = 0, real_egid = getegid();
+ int keepcaps = 0;
+ time_t monotonic = 0;
+ time_t boottime = 0;
+ int force_monotonic = 0;
+ int force_boottime = 0;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "+fhVmuinpCTUrR:w:S:G:c", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'f':
+ forkit = 1;
+ break;
+ case 'm':
+ unshare_flags |= CLONE_NEWNS;
+ if (optarg)
+ set_ns_target(CLONE_NEWNS, optarg);
+ break;
+ case 'u':
+ unshare_flags |= CLONE_NEWUTS;
+ if (optarg)
+ set_ns_target(CLONE_NEWUTS, optarg);
+ break;
+ case 'i':
+ unshare_flags |= CLONE_NEWIPC;
+ if (optarg)
+ set_ns_target(CLONE_NEWIPC, optarg);
+ break;
+ case 'n':
+ unshare_flags |= CLONE_NEWNET;
+ if (optarg)
+ set_ns_target(CLONE_NEWNET, optarg);
+ break;
+ case 'p':
+ unshare_flags |= CLONE_NEWPID;
+ if (optarg)
+ set_ns_target(CLONE_NEWPID, optarg);
+ break;
+ case 'U':
+ unshare_flags |= CLONE_NEWUSER;
+ if (optarg)
+ set_ns_target(CLONE_NEWUSER, optarg);
+ break;
+ case 'C':
+ unshare_flags |= CLONE_NEWCGROUP;
+ if (optarg)
+ set_ns_target(CLONE_NEWCGROUP, optarg);
+ break;
+ case 'T':
+ unshare_flags |= CLONE_NEWTIME;
+ if (optarg)
+ set_ns_target(CLONE_NEWTIME, optarg);
+ break;
+ case OPT_MOUNTPROC:
+ unshare_flags |= CLONE_NEWNS;
+ procmnt = optarg ? optarg : "/proc";
+ break;
+ case OPT_MAPUSER:
+ unshare_flags |= CLONE_NEWUSER;
+ mapuser = get_user(optarg, _("failed to parse uid"));
+ break;
+ case OPT_MAPGROUP:
+ unshare_flags |= CLONE_NEWUSER;
+ mapgroup = get_group(optarg, _("failed to parse gid"));
+ break;
+ case 'r':
+ unshare_flags |= CLONE_NEWUSER;
+ mapuser = 0;
+ mapgroup = 0;
+ break;
+ case 'c':
+ unshare_flags |= CLONE_NEWUSER;
+ mapuser = real_euid;
+ mapgroup = real_egid;
+ break;
+ case OPT_SETGROUPS:
+ setgrpcmd = setgroups_str2id(optarg);
+ break;
+ case OPT_PROPAGATION:
+ propagation = parse_propagation(optarg);
+ break;
+ case OPT_KILLCHILD:
+ forkit = 1;
+ if (optarg) {
+ if ((kill_child_signo = signame_to_signum(optarg)) < 0)
+ errx(EXIT_FAILURE, _("unknown signal: %s"),
+ optarg);
+ } else {
+ kill_child_signo = SIGKILL;
+ }
+ break;
+ case OPT_KEEPCAPS:
+ keepcaps = 1;
+ cap_last_cap(); /* Force last cap to be cached before we fork. */
+ break;
+ case 'S':
+ uid = strtoul_or_err(optarg, _("failed to parse uid"));
+ force_uid = 1;
+ break;
+ case 'G':
+ gid = strtoul_or_err(optarg, _("failed to parse gid"));
+ force_gid = 1;
+ break;
+ case 'R':
+ newroot = optarg;
+ break;
+ case 'w':
+ newdir = optarg;
+ break;
+ case OPT_MONOTONIC:
+ monotonic = strtoul_or_err(optarg, _("failed to parse monotonic offset"));
+ force_monotonic = 1;
+ break;
+ case OPT_BOOTTIME:
+ boottime = strtoul_or_err(optarg, _("failed to parse boottime offset"));
+ force_boottime = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if ((force_monotonic || force_boottime) && !(unshare_flags & CLONE_NEWTIME))
+ errx(EXIT_FAILURE, _("options --monotonic and --boottime require "
+ "unsharing of a time namespace (-t)"));
+
+ if (npersists && (unshare_flags & CLONE_NEWNS))
+ bind_ns_files_from_child(&pid_bind, fds);
+
+ if (-1 == unshare(unshare_flags))
+ err(EXIT_FAILURE, _("unshare failed"));
+
+ if (force_boottime)
+ settime(boottime, CLOCK_BOOTTIME);
+
+ if (force_monotonic)
+ settime(monotonic, CLOCK_MONOTONIC);
+
+ if (forkit) {
+ signal(SIGINT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+
+ /* force child forking before mountspace binding
+ * so pid_for_children is populated */
+ pid = fork();
+
+ switch(pid) {
+ case -1:
+ err(EXIT_FAILURE, _("fork failed"));
+ case 0: /* child */
+ if (pid_bind && (unshare_flags & CLONE_NEWNS))
+ close(fds[1]);
+ break;
+ default: /* parent */
+ break;
+ }
+ }
+
+ if (npersists && (pid || !forkit)) {
+ /* run in parent */
+ if (pid_bind && (unshare_flags & CLONE_NEWNS)) {
+ int rc;
+ char ch = PIPE_SYNC_BYTE;
+
+ /* signal child we are ready */
+ write_all(fds[1], &ch, 1);
+ close(fds[1]);
+ fds[1] = -1;
+
+ /* wait for bind_ns_files_from_child() */
+ do {
+ rc = waitpid(pid_bind, &status, 0);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, _("waitpid failed"));
+ }
+ if (WIFEXITED(status) &&
+ WEXITSTATUS(status) != EXIT_SUCCESS)
+ return WEXITSTATUS(status);
+ } while (rc < 0);
+ } else
+ /* simple way, just bind */
+ bind_ns_files(getpid());
+ }
+
+ if (pid) {
+ if (waitpid(pid, &status, 0) == -1)
+ err(EXIT_FAILURE, _("waitpid failed"));
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ if (WIFSIGNALED(status))
+ kill(getpid(), WTERMSIG(status));
+ err(EXIT_FAILURE, _("child exit failed"));
+ }
+
+ if (kill_child_signo != 0 && prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
+ err(EXIT_FAILURE, "prctl failed");
+
+ if (mapuser != (uid_t) -1)
+ map_id(_PATH_PROC_UIDMAP, mapuser, real_euid);
+
+ /* Since Linux 3.19 unprivileged writing of /proc/self/gid_map
+ * has been disabled unless /proc/self/setgroups is written
+ * first to permanently disable the ability to call setgroups
+ * in that user namespace. */
+ if (mapgroup != (gid_t) -1) {
+ if (setgrpcmd == SETGROUPS_ALLOW)
+ errx(EXIT_FAILURE, _("options --setgroups=allow and "
+ "--map-group are mutually exclusive"));
+ setgroups_control(SETGROUPS_DENY);
+ map_id(_PATH_PROC_GIDMAP, mapgroup, real_egid);
+ }
+
+ if (setgrpcmd != SETGROUPS_NONE)
+ setgroups_control(setgrpcmd);
+
+ if ((unshare_flags & CLONE_NEWNS) && propagation)
+ set_propagation(propagation);
+
+ if (newroot) {
+ if (chroot(newroot) != 0)
+ err(EXIT_FAILURE,
+ _("cannot change root directory to '%s'"), newroot);
+ newdir = newdir ?: "/";
+ }
+ if (newdir && chdir(newdir))
+ err(EXIT_FAILURE, _("cannot chdir to '%s'"), newdir);
+
+ if (procmnt) {
+ if (!newroot && mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0)
+ err(EXIT_FAILURE, _("umount %s failed"), procmnt);
+ if (mount("proc", procmnt, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
+ err(EXIT_FAILURE, _("mount %s failed"), procmnt);
+ }
+
+ if (force_gid) {
+ if (setgroups(0, NULL) != 0) /* drop supplementary groups */
+ err(EXIT_FAILURE, _("setgroups failed"));
+ if (setgid(gid) < 0) /* change GID */
+ err(EXIT_FAILURE, _("setgid failed"));
+ }
+ if (force_uid && setuid(uid) < 0) /* change UID */
+ err(EXIT_FAILURE, _("setuid failed"));
+
+ /* We use capabilities system calls to propagate the permitted
+ * capabilities into the ambient set because we have already
+ * forked so are in async-signal-safe context. */
+ if (keepcaps && (unshare_flags & CLONE_NEWUSER)) {
+ struct __user_cap_header_struct header = {
+ .version = _LINUX_CAPABILITY_VERSION_3,
+ .pid = 0,
+ };
+
+ struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] = {{ 0 }};
+ uint64_t effective, cap;
+
+ if (capget(&header, payload) < 0)
+ err(EXIT_FAILURE, _("capget failed"));
+
+ /* In order the make capabilities ambient, we first need to ensure
+ * that they are all inheritable. */
+ payload[0].inheritable = payload[0].permitted;
+ payload[1].inheritable = payload[1].permitted;
+
+ if (capset(&header, payload) < 0)
+ err(EXIT_FAILURE, _("capset failed"));
+
+ effective = ((uint64_t)payload[1].effective << 32) | (uint64_t)payload[0].effective;
+
+ for (cap = 0; cap < (sizeof(effective) * 8); cap++) {
+ /* This is the same check as cap_valid(), but using
+ * the runtime value for the last valid cap. */
+ if (cap > (uint64_t) cap_last_cap())
+ continue;
+
+ if ((effective & (1 << cap))
+ && prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0)
+ err(EXIT_FAILURE, _("prctl(PR_CAP_AMBIENT) failed"));
+ }
+ }
+
+ if (optind < argc) {
+ execvp(argv[optind], argv + optind);
+ errexec(argv[optind]);
+ }
+ exec_shell();
+}
diff --git a/sys-utils/wdctl.8 b/sys-utils/wdctl.8
new file mode 100644
index 0000000..efd8b59
--- /dev/null
+++ b/sys-utils/wdctl.8
@@ -0,0 +1,74 @@
+.\" wdctl.8 --
+.\" Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+.\" May be distributed under the GNU General Public License
+.TH WDCTL "8" "July 2014" "util-linux" "System Administration"
+.SH NAME
+wdctl \- show hardware watchdog status
+.SH SYNOPSIS
+.B wdctl
+[options]
+.RI [ device ...]
+.SH DESCRIPTION
+Show hardware watchdog status. The default device is
+.IR /dev/watchdog .
+If more than one device is specified then the output is separated by
+one blank line.
+.PP
+If the device is already used or user has no permissions to read from the device than
+.B wdctl
+reads data from sysfs. In this case information about supported features (flags) might be missing.
+.PP
+Note that the number of supported watchdog features is hardware specific.
+.SH OPTIONS
+.TP
+.BR \-f , " \-\-flags " \fIlist
+Print only the specified flags.
+.TP
+.BR \-F , " \-\-noflags"
+Do not print information about flags.
+.TP
+.BR \-I , " \-\-noident"
+Do not print watchdog identity information.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line for flags table.
+.IP "\fB\-o\fR, \fB\-\-output \fIlist\fP"
+Define the output columns to use in table of watchdog flags. If no
+output arrangement is specified, then a default set is used. Use
+.B \-\-help
+to get list of all supported columns.
+.TP
+.BR \-O , " \-\-oneline"
+Print all wanted information on one line in key="value" output format.
+.TP
+.BR \-r , " \-\-raw"
+Use the raw output format.
+.TP
+.BR \-s , " \-settimeout " \fIseconds
+Set the watchdog timeout in seconds.
+.TP
+.BR \-T , " \-\-notimeouts"
+Do not print watchdog timeouts.
+.IP "\fB\-x\fR, \fB\-\-flags\-only\fP"
+Same as \fB\-I \-T\fP.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH AUTHORS
+.MT kzak@\:redhat\:.com
+Karel Zak
+.ME
+.br
+.MT lennart@\:poettering\:.net
+Lennart Poettering
+.ME
+.SH AVAILABILITY
+The
+.B wdctl
+command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/wdctl.c b/sys-utils/wdctl.c
new file mode 100644
index 0000000..8de5d5a
--- /dev/null
+++ b/sys-utils/wdctl.c
@@ -0,0 +1,721 @@
+/*
+ * wdctl(8) - show hardware watchdog status
+ *
+ * Copyright (C) 2012 Lennart Poettering
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <sys/ioctl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <signal.h>
+#include <assert.h>
+#include <linux/watchdog.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <libsmartcols.h>
+
+#include "nls.h"
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "optutils.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "carefulputc.h"
+#include "path.h"
+
+/*
+ * since 2.6.18
+ */
+#ifndef WDIOC_SETPRETIMEOUT
+# define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
+# define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
+# define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
+# define WDIOF_POWEROVER 0x0040 /* Power over voltage */
+# define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */
+# define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */
+# define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */
+# define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */
+#endif
+
+/*
+ * since 3.5
+ */
+#ifndef WDIOF_ALARMONLY
+# define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers a management or
+ other external alarm not a reboot */
+#endif
+
+struct wdflag {
+ uint32_t flag;
+ const char *name;
+ const char *description;
+};
+
+static const struct wdflag wdflags[] = {
+ { WDIOF_CARDRESET, "CARDRESET", N_("Card previously reset the CPU") },
+ { WDIOF_EXTERN1, "EXTERN1", N_("External relay 1") },
+ { WDIOF_EXTERN2, "EXTERN2", N_("External relay 2") },
+ { WDIOF_FANFAULT, "FANFAULT", N_("Fan failed") },
+ { WDIOF_KEEPALIVEPING, "KEEPALIVEPING", N_("Keep alive ping reply") },
+ { WDIOF_MAGICCLOSE, "MAGICCLOSE", N_("Supports magic close char") },
+ { WDIOF_OVERHEAT, "OVERHEAT", N_("Reset due to CPU overheat") },
+ { WDIOF_POWEROVER, "POWEROVER", N_("Power over voltage") },
+ { WDIOF_POWERUNDER, "POWERUNDER", N_("Power bad/power fault") },
+ { WDIOF_PRETIMEOUT, "PRETIMEOUT", N_("Pretimeout (in seconds)") },
+ { WDIOF_SETTIMEOUT, "SETTIMEOUT", N_("Set timeout (in seconds)") },
+ { WDIOF_ALARMONLY, "ALARMONLY", N_("Not trigger reboot") }
+};
+
+
+/* column names */
+struct colinfo {
+ const char *name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ const char *help;
+};
+
+enum { COL_FLAG, COL_DESC, COL_STATUS, COL_BSTATUS, COL_DEVICE };
+
+/* columns descriptions */
+static struct colinfo infos[] = {
+ [COL_FLAG] = { "FLAG", 14, 0, N_("flag name") },
+ [COL_DESC] = { "DESCRIPTION", 0.1, SCOLS_FL_TRUNC, N_("flag description") },
+ [COL_STATUS] = { "STATUS", 1, SCOLS_FL_RIGHT, N_("flag status") },
+ [COL_BSTATUS] = { "BOOT-STATUS", 1, SCOLS_FL_RIGHT, N_("flag boot status") },
+ [COL_DEVICE] = { "DEVICE", 0.1, 0, N_("watchdog device name") }
+
+};
+
+static int columns[ARRAY_SIZE(infos) * 2];
+static int ncolumns;
+
+struct wd_device {
+ const char *devpath;
+
+ int timeout;
+ int timeleft;
+ int pretimeout;
+
+ uint32_t status;
+ uint32_t bstatus;
+ int nowayout;
+
+ struct watchdog_info ident;
+
+ unsigned int has_timeout : 1,
+ has_timeleft : 1,
+ has_pretimeout : 1,
+ has_nowayout : 1;
+};
+
+struct wd_control {
+ unsigned int show_oneline : 1,
+ show_raw : 1,
+ hide_headings : 1,
+ hide_flags : 1,
+ hide_ident : 1,
+ hide_timeouts : 1;
+};
+
+/* converts flag name to flag bit */
+static long name2bit(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(wdflags); i++) {
+ const char *cn = wdflags[i].name;
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return wdflags[i].flag;
+ }
+ warnx(_("unknown flag: %s"), name);
+ return -1;
+}
+
+static int column2id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static int get_column_id(int num)
+{
+ assert(num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+
+ return columns[num];
+}
+
+static struct colinfo *get_column_info(unsigned num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+/* We preffer cdev /dev/watchdog0 as this device has node in
+ * /sys/class/watchdog/. The old miscdev /dev/watchdog is fallback for old
+ * systemds only.
+ */
+static const char *get_default_device(void)
+{
+ const char **p;
+ static const char *devs[] = {
+ "/dev/watchdog0",
+ "/dev/watchdog",
+ NULL
+ };
+
+ for (p = devs; *p; p++) {
+ if (access(*p, F_OK) == 0)
+ return *p;
+ }
+
+ return NULL;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+ const char *dflt = get_default_device();
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] [<device> ...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Show the status of the hardware watchdog.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -f, --flags <list> print selected flags only\n"
+ " -F, --noflags don't print information about flags\n"
+ " -I, --noident don't print watchdog identity information\n"
+ " -n, --noheadings don't print headings for flags table\n"
+ " -O, --oneline print all information on one line\n"
+ " -o, --output <list> output columns of the flags\n"
+ " -r, --raw use raw output format for flags table\n"
+ " -T, --notimeouts don't print watchdog timeouts\n"
+ " -s, --settimeout <sec> set watchdog timeout\n"
+ " -x, --flags-only print only flags table (same as -I -T)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(24));
+ fputs(USAGE_SEPARATOR, out);
+
+ if (dflt)
+ fprintf(out, _("The default device is %s.\n"), dflt);
+ else
+ fprintf(out, _("No default device is available.\n"));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %13s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("wdctl(8)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static void add_flag_line(struct libscols_table *table, struct wd_device *wd, const struct wdflag *fl)
+{
+ int i;
+ struct libscols_line *line;
+
+ line = scols_table_new_line(table, NULL);
+ if (!line) {
+ warn(_("failed to allocate output line"));
+ return;
+ }
+
+ for (i = 0; i < ncolumns; i++) {
+ const char *str = NULL;
+
+ switch (get_column_id(i)) {
+ case COL_FLAG:
+ str = fl->name;
+ break;
+ case COL_DESC:
+ str = fl->description;
+ break;
+ case COL_STATUS:
+ str = wd->status & fl->flag ? "1" : "0";
+ break;
+ case COL_BSTATUS:
+ str = wd->bstatus & fl->flag ? "1" : "0";
+ break;
+ case COL_DEVICE:
+ str = wd->devpath;
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_set_data(line, i, str)) {
+ warn(_("failed to add output data"));
+ break;
+ }
+ }
+}
+
+static int show_flags(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted)
+{
+ size_t i;
+ int rc = -1;
+ struct libscols_table *table;
+ uint32_t flags;
+
+ /* information about supported bits is probably missing in /sys */
+ if (!wd->ident.options)
+ return 0;
+
+ scols_init_debug(0);
+
+ /* create output table */
+ table = scols_new_table();
+ if (!table) {
+ warn(_("failed to allocate output table"));
+ return -1;
+ }
+ scols_table_enable_raw(table, ctl->show_raw);
+ scols_table_enable_noheadings(table, ctl->hide_headings);
+
+ /* define columns */
+ for (i = 0; i < (size_t) ncolumns; i++) {
+ struct colinfo *col = get_column_info(i);
+
+ if (!scols_table_new_column(table, col->name, col->whint, col->flags)) {
+ warnx(_("failed to allocate output column"));
+ goto done;
+ }
+ }
+
+ /* fill-in table with data
+ * -- one line for each supported flag (option) */
+ flags = wd->ident.options;
+
+ for (i = 0; i < ARRAY_SIZE(wdflags); i++) {
+ if (wanted && !(wanted & wdflags[i].flag))
+ ; /* ignore */
+ else if (flags & wdflags[i].flag)
+ add_flag_line(table, wd, &wdflags[i]);
+
+ flags &= ~wdflags[i].flag;
+ }
+
+ if (flags)
+ warnx(_("%s: unknown flags 0x%x\n"), wd->devpath, flags);
+
+ scols_print_table(table);
+ rc = 0;
+done:
+ scols_unref_table(table);
+ return rc;
+}
+/*
+ * Warning: successfully opened watchdog has to be properly closed with magic
+ * close character otherwise the machine will be rebooted!
+ *
+ * Don't use err() or exit() here!
+ */
+static int set_watchdog(struct wd_device *wd, int timeout)
+{
+ int fd;
+ sigset_t sigs, oldsigs;
+ int rc = 0;
+
+ assert(wd->devpath);
+
+ sigemptyset(&oldsigs);
+ sigfillset(&sigs);
+ sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
+
+ fd = open(wd->devpath, O_WRONLY|O_CLOEXEC);
+
+ if (fd < 0) {
+ if (errno == EBUSY)
+ warnx(_("%s: watchdog already in use, terminating."),
+ wd->devpath);
+ warn(_("cannot open %s"), wd->devpath);
+ return -1;
+ }
+
+ for (;;) {
+ /* We just opened this to query the state, not to arm
+ * it hence use the magic close character */
+ static const char v = 'V';
+
+ if (write(fd, &v, 1) >= 0)
+ break;
+ if (errno != EINTR) {
+ warn(_("%s: failed to disarm watchdog"), wd->devpath);
+ break;
+ }
+ /* Let's try hard, since if we don't get this right
+ * the machine might end up rebooting. */
+ }
+
+ if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) != 0) {
+ rc = errno;
+ warn(_("cannot set timeout for %s"), wd->devpath);
+ }
+
+ if (close(fd))
+ warn(_("write failed"));
+ sigprocmask(SIG_SETMASK, &oldsigs, NULL);
+ printf(P_("Timeout has been set to %d second.\n",
+ "Timeout has been set to %d seconds.\n", timeout), timeout);
+
+ return rc;
+}
+
+/*
+ * Warning: successfully opened watchdog has to be properly closed with magic
+ * close character otherwise the machine will be rebooted!
+ *
+ * Don't use err() or exit() here!
+ */
+static int read_watchdog_from_device(struct wd_device *wd)
+{
+ int fd;
+ sigset_t sigs, oldsigs;
+
+ assert(wd->devpath);
+
+ sigemptyset(&oldsigs);
+ sigfillset(&sigs);
+ sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
+
+ fd = open(wd->devpath, O_WRONLY|O_CLOEXEC);
+
+ if (fd < 0)
+ return -errno;
+
+ if (ioctl(fd, WDIOC_GETSUPPORT, &wd->ident) < 0)
+ warn(_("%s: failed to get information about watchdog"), wd->devpath);
+ else {
+ ioctl(fd, WDIOC_GETSTATUS, &wd->status);
+ ioctl(fd, WDIOC_GETBOOTSTATUS, &wd->bstatus);
+
+ if (ioctl(fd, WDIOC_GETTIMEOUT, &wd->timeout) >= 0)
+ wd->has_timeout = 1;
+ if (ioctl(fd, WDIOC_GETPRETIMEOUT, &wd->pretimeout) >= 0)
+ wd->has_pretimeout = 1;
+ if (ioctl(fd, WDIOC_GETTIMELEFT, &wd->timeleft) >= 0)
+ wd->has_timeleft = 1;
+ }
+
+ for (;;) {
+ /* We just opened this to query the state, not to arm
+ * it hence use the magic close character */
+ static const char v = 'V';
+
+ if (write(fd, &v, 1) >= 0)
+ break;
+ if (errno != EINTR) {
+ warn(_("%s: failed to disarm watchdog"), wd->devpath);
+ break;
+ }
+ /* Let's try hard, since if we don't get this right
+ * the machine might end up rebooting. */
+ }
+
+ if (close(fd))
+ warn(_("write failed"));
+ sigprocmask(SIG_SETMASK, &oldsigs, NULL);
+
+ return 0;
+}
+
+/* Returns: <0 error, 0 success, 1 unssuported */
+static int read_watchdog_from_sysfs(struct wd_device *wd)
+{
+ struct path_cxt *sys;
+ struct stat st;
+ int rc;
+
+ rc = stat(wd->devpath, &st);
+ if (rc != 0)
+ return rc;
+
+ sys = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u",
+ major(st.st_rdev), minor(st.st_rdev));
+ if (!sys)
+ return -ENOMEM;
+
+ if (ul_path_get_dirfd(sys) < 0)
+ goto nosysfs; /* device not in /sys */
+
+ if (ul_path_access(sys, F_OK, "identity") != 0)
+ goto nosysfs; /* no info in /sys (old miscdev?) */
+
+ ul_path_read_buffer(sys, (char *) wd->ident.identity, sizeof(wd->ident.identity), "identity");
+
+ ul_path_scanf(sys, "status", "%x", &wd->status);
+ ul_path_read_u32(sys, &wd->bstatus, "bootstatus");
+
+ if (ul_path_read_s32(sys, &wd->nowayout, "nowayout") == 0)
+ wd->has_nowayout = 1;
+ if (ul_path_read_s32(sys, &wd->timeout, "timeout") == 0)
+ wd->has_timeout = 1;
+ if (ul_path_read_s32(sys, &wd->pretimeout, "pretimeout") == 0)
+ wd->has_pretimeout = 1;
+ if (ul_path_read_s32(sys, &wd->timeleft, "timeleft") == 0)
+ wd->has_timeleft = 1;
+
+ ul_unref_path(sys);
+ return 0;
+nosysfs:
+ ul_unref_path(sys);
+ return 1;
+}
+
+static int read_watchdog(struct wd_device *wd)
+{
+ int rc = read_watchdog_from_device(wd);
+
+ if (rc == -EBUSY || rc == -EACCES || rc == -EPERM)
+ rc = read_watchdog_from_sysfs(wd);
+
+ if (rc) {
+ warn(_("cannot read information about %s"), wd->devpath);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void show_timeouts(struct wd_device *wd)
+{
+ if (wd->has_timeout)
+ printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeout),
+ _("Timeout:"), wd->timeout);
+ if (wd->has_pretimeout)
+ printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->pretimeout),
+ _("Pre-timeout:"), wd->pretimeout);
+ if (wd->has_timeleft)
+ printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeleft),
+ _("Timeleft:"), wd->timeleft);
+}
+
+static void print_oneline(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted)
+{
+ printf("%s:", wd->devpath);
+
+ if (!ctl->hide_ident) {
+ printf(" VERSION=\"%x\"", wd->ident.firmware_version);
+
+ printf(" IDENTITY=");
+ fputs_quoted((char *) wd->ident.identity, stdout);
+ }
+ if (!ctl->hide_timeouts) {
+ if (wd->has_timeout)
+ printf(" TIMEOUT=\"%i\"", wd->timeout);
+ if (wd->has_pretimeout)
+ printf(" PRETIMEOUT=\"%i\"", wd->pretimeout);
+ if (wd->has_timeleft)
+ printf(" TIMELEFT=\"%i\"", wd->timeleft);
+ }
+
+ if (!ctl->hide_flags) {
+ size_t i;
+ uint32_t flags = wd->ident.options;
+
+ for (i = 0; i < ARRAY_SIZE(wdflags); i++) {
+ const struct wdflag *fl;
+
+ if ((wanted && !(wanted & wdflags[i].flag)) ||
+ !(flags & wdflags[i].flag))
+ continue;
+
+ fl= &wdflags[i];
+
+ printf(" %s=\"%s\"", fl->name,
+ wd->status & fl->flag ? "1" : "0");
+ printf(" %s_BOOT=\"%s\"", fl->name,
+ wd->bstatus & fl->flag ? "1" : "0");
+
+ }
+ }
+
+ fputc('\n', stdout);
+}
+
+static void print_device(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted)
+{
+ /* NAME=value one line output */
+ if (ctl->show_oneline) {
+ print_oneline(ctl, wd, wanted);
+ return;
+ }
+
+ /* pretty output */
+ if (!ctl->hide_ident) {
+ printf("%-15s%s\n", _("Device:"), wd->devpath);
+ printf("%-15s%s [%s %x]\n",
+ _("Identity:"),
+ wd->ident.identity,
+ _("version"),
+ wd->ident.firmware_version);
+ }
+ if (!ctl->hide_timeouts)
+ show_timeouts(wd);
+
+ if (!ctl->hide_flags)
+ show_flags(ctl, wd, wanted);
+}
+
+int main(int argc, char *argv[])
+{
+ struct wd_device wd;
+ struct wd_control ctl = { .hide_headings = 0 };
+ int c, res = EXIT_SUCCESS, count = 0;
+ uint32_t wanted = 0;
+ int timeout = 0;
+ const char *dflt_device = NULL;
+
+ static const struct option long_opts[] = {
+ { "flags", required_argument, NULL, 'f' },
+ { "flags-only", no_argument, NULL, 'x' },
+ { "help", no_argument, NULL, 'h' },
+ { "noflags", no_argument, NULL, 'F' },
+ { "noheadings", no_argument, NULL, 'n' },
+ { "noident", no_argument, NULL, 'I' },
+ { "notimeouts", no_argument, NULL, 'T' },
+ { "settimeout", required_argument, NULL, 's' },
+ { "output", required_argument, NULL, 'o' },
+ { "oneline", no_argument, NULL, 'O' },
+ { "raw", no_argument, NULL, 'r' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'F','f' }, /* noflags,flags*/
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv,
+ "d:f:hFnITo:s:OrVx", long_opts, NULL)) != -1) {
+
+ err_exclusive_options(c, long_opts, excl, excl_st);
+
+ switch(c) {
+ case 'o':
+ ncolumns = string_to_idarray(optarg,
+ columns, ARRAY_SIZE(columns),
+ column2id);
+ if (ncolumns < 0)
+ return EXIT_FAILURE;
+ break;
+ case 's':
+ timeout = strtos32_or_err(optarg, _("invalid timeout argument"));
+ break;
+ case 'f':
+ if (string_to_bitmask(optarg, (unsigned long *) &wanted, name2bit) != 0)
+ return EXIT_FAILURE;
+ break;
+ case 'F':
+ ctl.hide_flags = 1;
+ break;
+ case 'I':
+ ctl.hide_ident = 1;
+ break;
+ case 'T':
+ ctl.hide_timeouts = 1;
+ break;
+ case 'n':
+ ctl.hide_headings = 1;
+ break;
+ case 'r':
+ ctl.show_raw = 1;
+ break;
+ case 'O':
+ ctl.show_oneline = 1;
+ break;
+ case 'x':
+ ctl.hide_ident = 1;
+ ctl.hide_timeouts = 1;
+ break;
+
+ case 'h':
+ usage();
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (!ncolumns) {
+ /* default columns */
+ columns[ncolumns++] = COL_FLAG;
+ columns[ncolumns++] = COL_DESC;
+ columns[ncolumns++] = COL_STATUS;
+ columns[ncolumns++] = COL_BSTATUS;
+ }
+
+ /* Device no specified, use default. */
+ if (optind == argc) {
+ dflt_device = get_default_device();
+ if (!dflt_device)
+ err(EXIT_FAILURE, _("No default device is available."));
+ }
+
+ do {
+ int rc;
+
+ memset(&wd, 0, sizeof(wd));
+ wd.devpath = dflt_device ? dflt_device : argv[optind++];
+
+ if (count)
+ fputc('\n', stdout);
+ count++;
+
+ if (timeout) {
+ rc = set_watchdog(&wd, timeout);
+ if (rc) {
+ res = EXIT_FAILURE;
+ }
+ }
+
+ rc = read_watchdog(&wd);
+ if (rc) {
+ res = EXIT_FAILURE;
+ continue;
+ }
+
+ print_device(&ctl, &wd, wanted);
+
+ } while (optind < argc);
+
+ return res;
+}
diff --git a/sys-utils/zramctl.8 b/sys-utils/zramctl.8
new file mode 100644
index 0000000..abd6507
--- /dev/null
+++ b/sys-utils/zramctl.8
@@ -0,0 +1,131 @@
+.TH ZRAMCTL 8 "July 2014" "util-linux" "System Administration"
+.SH NAME
+zramctl \- set up and control zram devices
+.SH SYNOPSIS
+.ad l
+Get info:
+.sp
+.in +5
+.BR zramctl " [options]"
+.sp
+.in -5
+Reset zram:
+.sp
+.in +5
+.B "zramctl \-r"
+.IR zramdev ...
+.sp
+.in -5
+Print name of first unused zram device:
+.sp
+.in +5
+.B "zramctl \-f"
+.sp
+.in -5
+Set up a zram device:
+.sp
+.in +5
+.B zramctl
+.RB [ \-f " | "\fIzramdev\fP ]
+.RB [ \-s
+.IR size ]
+.RB [ \-t
+.IR number ]
+.RB [ \-a
+.IR algorithm ]
+.sp
+.in -5
+.ad b
+.SH DESCRIPTION
+.B zramctl
+is used to quickly set up zram device parameters, to reset zram devices, and to
+query the status of used zram devices.
+.PP
+If no option is given, all non-zero size zram devices are shown.
+.PP
+Note that \fIzramdev\fP node specified on command line has to already exist. The command
+.B zramctl
+creates a new /dev/zram<N> nodes only when \fB\-\-find\fR option specified. It's possible
+(and common) that after system boot /dev/zram<N> nodes are not created yet.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-algorithm lzo" | lz4 | lz4hc | deflate | 842
+Set the compression algorithm to be used for compressing data in the zram device.
+.TP
+.BR \-f , " \-\-find"
+Find the first unused zram device. If a \fB\-\-size\fR argument is present, then
+initialize the device.
+.TP
+.BR \-n , " \-\-noheadings"
+Do not print a header line in status output.
+.TP
+.BR \-o , " \-\-output " \fIlist
+Define the status output columns to be used. If no output arrangement is
+specified, then a default set is used.
+Use \fB\-\-help\fP to get a list of all supported columns.
+.TP
+.B \-\-output\-all
+Output all available columns.
+.TP
+.B \-\-raw
+Use the raw format for status output.
+.TP
+.BR \-r , " \-\-reset"
+Reset the options of the specified zram device(s). Zram device settings
+can be changed only after a reset.
+.TP
+.BR \-s , " \-\-size " \fIsize
+Create a zram device of the specified \fIsize\fR.
+Zram devices are aligned to memory pages; when the requested \fIsize\fR is
+not a multiple of the page size, it will be rounded up to the next multiple.
+When not otherwise specified, the unit of the \fIsize\fR parameter is bytes.
+.IP
+The \fIsize\fR argument may be followed by the multiplicative suffixes KiB (=1024),
+MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB"
+is optional, e.g., "K" has the same meaning as "KiB") or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+.TP
+.BR \-t , " \-\-streams " \fInumber
+Set the maximum number of compression streams that can be used for the device.
+The default is one stream.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+
+.SH EXIT STATUS
+.B zramctl
+returns 0 on success, nonzero on failure.
+
+.SH FILES
+.TP
+.I /dev/zram[0..N]
+zram block devices
+
+.SH EXAMPLE
+The following commands set up a zram device with a size of one gigabyte
+and use it as swap device.
+.nf
+.IP
+# zramctl --find --size 1024M
+/dev/zram0
+# mkswap /dev/zram0
+# swapon /dev/zram0
+ ...
+# swapoff /dev/zram0
+# zramctl --reset /dev/zram0
+.fi
+.SH AUTHORS
+.nf
+Timofey Titovets <nefelim4ag@gmail.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH SEE ALSO
+.UR http://git.\:kernel.\:org\:/cgit\:/linux\:/kernel\:/git\:/torvalds\:/linux.git\:/tree\:/Documentation\:/admin-guide\:/blockdev\:/zram.rst
+Linux kernel documentation
+.UE .
+.SH AVAILABILITY
+The zramctl command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/zramctl.c b/sys-utils/zramctl.c
new file mode 100644
index 0000000..003349f
--- /dev/null
+++ b/sys-utils/zramctl.c
@@ -0,0 +1,768 @@
+/*
+ * zramctl - control compressed block devices in RAM
+ *
+ * Copyright (c) 2014 Timofey Titovets <Nefelim4ag@gmail.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "sysfs.h"
+#include "optutils.h"
+#include "ismounted.h"
+#include "strv.h"
+#include "path.h"
+#include "pathnames.h"
+
+/*#define CONFIG_ZRAM_DEBUG*/
+
+#ifdef CONFIG_ZRAM_DEBUG
+# define DBG(x) do { fputs("zram: ", stderr); x; fputc('\n', stderr); } while(0)
+#else
+# define DBG(x)
+#endif
+
+/* status output columns */
+struct colinfo {
+ const char *name;
+ double whint;
+ int flags;
+ const char *help;
+};
+
+enum {
+ COL_NAME = 0,
+ COL_DISKSIZE,
+ COL_ORIG_SIZE,
+ COL_COMP_SIZE,
+ COL_ALGORITHM,
+ COL_STREAMS,
+ COL_ZEROPAGES,
+ COL_MEMTOTAL,
+ COL_MEMLIMIT,
+ COL_MEMUSED,
+ COL_MIGRATED,
+ COL_MOUNTPOINT
+};
+
+static const struct colinfo infos[] = {
+ [COL_NAME] = { "NAME", 0.25, 0, N_("zram device name") },
+ [COL_DISKSIZE] = { "DISKSIZE", 5, SCOLS_FL_RIGHT, N_("limit on the uncompressed amount of data") },
+ [COL_ORIG_SIZE] = { "DATA", 5, SCOLS_FL_RIGHT, N_("uncompressed size of stored data") },
+ [COL_COMP_SIZE] = { "COMPR", 5, SCOLS_FL_RIGHT, N_("compressed size of stored data") },
+ [COL_ALGORITHM] = { "ALGORITHM", 3, 0, N_("the selected compression algorithm") },
+ [COL_STREAMS] = { "STREAMS", 3, SCOLS_FL_RIGHT, N_("number of concurrent compress operations") },
+ [COL_ZEROPAGES] = { "ZERO-PAGES", 3, SCOLS_FL_RIGHT, N_("empty pages with no allocated memory") },
+ [COL_MEMTOTAL] = { "TOTAL", 5, SCOLS_FL_RIGHT, N_("all memory including allocator fragmentation and metadata overhead") },
+ [COL_MEMLIMIT] = { "MEM-LIMIT", 5, SCOLS_FL_RIGHT, N_("memory limit used to store compressed data") },
+ [COL_MEMUSED] = { "MEM-USED", 5, SCOLS_FL_RIGHT, N_("memory zram have been consumed to store compressed data") },
+ [COL_MIGRATED] = { "MIGRATED", 5, SCOLS_FL_RIGHT, N_("number of objects migrated by compaction") },
+ [COL_MOUNTPOINT]= { "MOUNTPOINT",0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") },
+};
+
+static int columns[ARRAY_SIZE(infos) * 2] = {-1};
+static int ncolumns;
+
+enum {
+ MM_ORIG_DATA_SIZE = 0,
+ MM_COMPR_DATA_SIZE,
+ MM_MEM_USED_TOTAL,
+ MM_MEM_LIMIT,
+ MM_MEM_USED_MAX,
+ MM_ZERO_PAGES,
+ MM_NUM_MIGRATED
+};
+
+static const char *mm_stat_names[] = {
+ [MM_ORIG_DATA_SIZE] = "orig_data_size",
+ [MM_COMPR_DATA_SIZE] = "compr_data_size",
+ [MM_MEM_USED_TOTAL] = "mem_used_total",
+ [MM_MEM_LIMIT] = "mem_limit",
+ [MM_MEM_USED_MAX] = "mem_used_max",
+ [MM_ZERO_PAGES] = "zero_pages",
+ [MM_NUM_MIGRATED] = "num_migrated"
+};
+
+struct zram {
+ char devname[32];
+ struct path_cxt *sysfs; /* device specific sysfs directory */
+ char **mm_stat;
+
+ unsigned int mm_stat_probed : 1,
+ control_probed : 1,
+ has_control : 1; /* has /sys/class/zram-control/ */
+};
+
+static unsigned int raw, no_headings, inbytes;
+static struct path_cxt *__control;
+
+static int get_column_id(int num)
+{
+ assert(num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+ return columns[num];
+}
+
+static const struct colinfo *get_column_info(int num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static void zram_reset_stat(struct zram *z)
+{
+ if (z) {
+ strv_free(z->mm_stat);
+ z->mm_stat = NULL;
+ z->mm_stat_probed = 0;
+ }
+}
+
+static void zram_set_devname(struct zram *z, const char *devname, size_t n)
+{
+ assert(z);
+
+ if (!devname)
+ snprintf(z->devname, sizeof(z->devname), "/dev/zram%zu", n);
+ else
+ xstrncpy(z->devname, devname, sizeof(z->devname));
+
+ DBG(fprintf(stderr, "set devname: %s", z->devname));
+ ul_unref_path(z->sysfs);
+ z->sysfs = NULL;
+ zram_reset_stat(z);
+}
+
+static int zram_get_devnum(struct zram *z)
+{
+ int n;
+
+ assert(z);
+
+ if (sscanf(z->devname, "/dev/zram%d", &n) == 1)
+ return n;
+ return -EINVAL;
+}
+
+static struct zram *new_zram(const char *devname)
+{
+ struct zram *z = xcalloc(1, sizeof(struct zram));
+
+ DBG(fprintf(stderr, "new: %p", z));
+ if (devname)
+ zram_set_devname(z, devname, 0);
+ return z;
+}
+
+static void free_zram(struct zram *z)
+{
+ if (!z)
+ return;
+ DBG(fprintf(stderr, "free: %p", z));
+ ul_unref_path(z->sysfs);
+ zram_reset_stat(z);
+ free(z);
+}
+
+static struct path_cxt *zram_get_sysfs(struct zram *z)
+{
+ assert(z);
+
+ if (!z->sysfs) {
+ dev_t devno = sysfs_devname_to_devno(z->devname);
+ if (!devno)
+ return NULL;
+ z->sysfs = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!z->sysfs)
+ return NULL;
+ if (*z->devname != '/')
+ /* canonicalize the device name according to /sys */
+ sysfs_blkdev_get_path(z->sysfs, z->devname, sizeof(z->devname));
+ }
+
+ return z->sysfs;
+}
+
+static inline int zram_exist(struct zram *z)
+{
+ assert(z);
+
+ errno = 0;
+ if (zram_get_sysfs(z) == NULL) {
+ errno = ENODEV;
+ return 0;
+ }
+
+ DBG(fprintf(stderr, "%s exists", z->devname));
+ return 1;
+}
+
+static int zram_set_u64parm(struct zram *z, const char *attr, uint64_t num)
+{
+ struct path_cxt *sysfs = zram_get_sysfs(z);
+ if (!sysfs)
+ return -EINVAL;
+ DBG(fprintf(stderr, "%s writing %ju to %s", z->devname, num, attr));
+ return ul_path_write_u64(sysfs, num, attr);
+}
+
+static int zram_set_strparm(struct zram *z, const char *attr, const char *str)
+{
+ struct path_cxt *sysfs = zram_get_sysfs(z);
+ if (!sysfs)
+ return -EINVAL;
+ DBG(fprintf(stderr, "%s writing %s to %s", z->devname, str, attr));
+ return ul_path_write_string(sysfs, str, attr);
+}
+
+
+static int zram_used(struct zram *z)
+{
+ uint64_t size;
+ struct path_cxt *sysfs = zram_get_sysfs(z);
+
+ if (sysfs &&
+ ul_path_read_u64(sysfs, &size, "disksize") == 0 &&
+ size > 0) {
+
+ DBG(fprintf(stderr, "%s used", z->devname));
+ return 1;
+ }
+ DBG(fprintf(stderr, "%s unused", z->devname));
+ return 0;
+}
+
+static int zram_has_control(struct zram *z)
+{
+ if (!z->control_probed) {
+ z->has_control = access(_PATH_SYS_CLASS "/zram-control/", F_OK) == 0 ? 1 : 0;
+ z->control_probed = 1;
+ DBG(fprintf(stderr, "zram-control: %s", z->has_control ? "yes" : "no"));
+ }
+
+ return z->has_control;
+}
+
+static struct path_cxt *zram_get_control(void)
+{
+ if (!__control)
+ __control = ul_new_path(_PATH_SYS_CLASS "/zram-control");
+ return __control;
+}
+
+static int zram_control_add(struct zram *z)
+{
+ int n;
+ struct path_cxt *ctl;
+
+ if (!zram_has_control(z) || !(ctl = zram_get_control()))
+ return -ENOSYS;
+
+ if (ul_path_read_s32(ctl, &n, "hot_add") != 0 || n < 0)
+ return n;
+
+ DBG(fprintf(stderr, "hot-add: %d", n));
+ zram_set_devname(z, NULL, n);
+ return 0;
+}
+
+static int zram_control_remove(struct zram *z)
+{
+ struct path_cxt *ctl;
+ int n;
+
+ if (!zram_has_control(z) || !(ctl = zram_get_control()))
+ return -ENOSYS;
+
+ n = zram_get_devnum(z);
+ if (n < 0)
+ return n;
+
+ DBG(fprintf(stderr, "hot-remove: %d", n));
+ return ul_path_write_u64(ctl, n, "hot_remove");
+}
+
+static struct zram *find_free_zram(void)
+{
+ struct zram *z = new_zram(NULL);
+ size_t i;
+ int isfree = 0;
+
+ for (i = 0; isfree == 0; i++) {
+ DBG(fprintf(stderr, "find free: checking zram%zu", i));
+ zram_set_devname(z, NULL, i);
+ if (!zram_exist(z) && zram_control_add(z) != 0)
+ break;
+ isfree = !zram_used(z);
+ }
+ if (!isfree) {
+ free_zram(z);
+ z = NULL;
+ }
+ return z;
+}
+
+static char *get_mm_stat(struct zram *z, size_t idx, int bytes)
+{
+ struct path_cxt *sysfs;
+ const char *name;
+ char *str = NULL;
+ uint64_t num;
+
+ assert(idx < ARRAY_SIZE(mm_stat_names));
+ assert(z);
+
+ sysfs = zram_get_sysfs(z);
+ if (!sysfs)
+ return NULL;
+
+ /* Linux >= 4.1 uses /sys/block/zram<id>/mm_stat */
+ if (!z->mm_stat && !z->mm_stat_probed) {
+ if (ul_path_read_string(sysfs, &str, "mm_stat") > 0 && str) {
+ z->mm_stat = strv_split(str, " ");
+
+ /* make sure kernel provides mm_stat as expected */
+ if (strv_length(z->mm_stat) < ARRAY_SIZE(mm_stat_names)) {
+ strv_free(z->mm_stat);
+ z->mm_stat = NULL;
+ }
+ }
+ z->mm_stat_probed = 1;
+ free(str);
+ str = NULL;
+ }
+
+ if (z->mm_stat) {
+ if (bytes)
+ return xstrdup(z->mm_stat[idx]);
+
+ num = strtou64_or_err(z->mm_stat[idx], _("Failed to parse mm_stat"));
+ return size_to_human_string(SIZE_SUFFIX_1LETTER, num);
+ }
+
+ /* Linux < 4.1 uses /sys/block/zram<id>/<attrname> */
+ name = mm_stat_names[idx];
+ if (bytes) {
+ ul_path_read_string(sysfs, &str, name);
+ return str;
+
+ }
+
+ if (ul_path_read_u64(sysfs, &num, name) == 0)
+ return size_to_human_string(SIZE_SUFFIX_1LETTER, num);
+
+ return NULL;
+}
+
+static void fill_table_row(struct libscols_table *tb, struct zram *z)
+{
+ static struct libscols_line *ln;
+ struct path_cxt *sysfs;
+ size_t i;
+ uint64_t num;
+
+ assert(tb);
+ assert(z);
+
+ DBG(fprintf(stderr, "%s: filling status table", z->devname));
+
+ sysfs = zram_get_sysfs(z);
+ if (!sysfs)
+ return;
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ for (i = 0; i < (size_t) ncolumns; i++) {
+ char *str = NULL;
+
+ switch (get_column_id(i)) {
+ case COL_NAME:
+ str = xstrdup(z->devname);
+ break;
+ case COL_DISKSIZE:
+ if (inbytes)
+ ul_path_read_string(sysfs, &str, "disksize");
+
+ else if (ul_path_read_u64(sysfs, &num, "disksize") == 0)
+ str = size_to_human_string(SIZE_SUFFIX_1LETTER, num);
+ break;
+ case COL_ALGORITHM:
+ {
+ char *alg = NULL;
+
+ ul_path_read_string(sysfs, &alg, "comp_algorithm");
+ if (alg) {
+ char* lbr = strrchr(alg, '[');
+ char* rbr = strrchr(alg, ']');
+
+ if (lbr != NULL && rbr != NULL && rbr - lbr > 1)
+ str = xstrndup(lbr + 1, rbr - lbr - 1);
+ free(alg);
+ }
+ break;
+ }
+ case COL_MOUNTPOINT:
+ {
+ char path[PATH_MAX] = { '\0' };
+ int fl;
+
+ check_mount_point(z->devname, &fl, path, sizeof(path));
+ if (*path)
+ str = xstrdup(path);
+ break;
+ }
+ case COL_STREAMS:
+ ul_path_read_string(sysfs, &str, "max_comp_streams");
+ break;
+ case COL_ZEROPAGES:
+ str = get_mm_stat(z, MM_ZERO_PAGES, 1);
+ break;
+ case COL_ORIG_SIZE:
+ str = get_mm_stat(z, MM_ORIG_DATA_SIZE, inbytes);
+ break;
+ case COL_COMP_SIZE:
+ str = get_mm_stat(z, MM_COMPR_DATA_SIZE, inbytes);
+ break;
+ case COL_MEMTOTAL:
+ str = get_mm_stat(z, MM_MEM_USED_TOTAL, inbytes);
+ break;
+ case COL_MEMLIMIT:
+ str = get_mm_stat(z, MM_MEM_LIMIT, inbytes);
+ break;
+ case COL_MEMUSED:
+ str = get_mm_stat(z, MM_MEM_USED_MAX, inbytes);
+ break;
+ case COL_MIGRATED:
+ str = get_mm_stat(z, MM_NUM_MIGRATED, inbytes);
+ break;
+ }
+ if (str && scols_line_refer_data(ln, i, str))
+ err(EXIT_FAILURE, _("failed to add output data"));
+ }
+}
+
+static void status(struct zram *z)
+{
+ struct libscols_table *tb;
+ size_t i;
+ DIR *dir;
+ struct dirent *d;
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_raw(tb, raw);
+ scols_table_enable_noheadings(tb, no_headings);
+
+ for (i = 0; i < (size_t) ncolumns; i++) {
+ const struct colinfo *col = get_column_info(i);
+
+ if (!scols_table_new_column(tb, col->name, col->whint, col->flags))
+ err(EXIT_FAILURE, _("failed to initialize output column"));
+ }
+
+ if (z) {
+ /* just one device specified */
+ fill_table_row(tb, z);
+ goto print_table;
+ }
+
+ /* list all used devices */
+ z = new_zram(NULL);
+ if (!(dir = opendir(_PATH_DEV)))
+ err(EXIT_FAILURE, _("cannot open %s"), _PATH_DEV);
+
+ while ((d = readdir(dir))) {
+ int n;
+ if (sscanf(d->d_name, "zram%d", &n) != 1)
+ continue;
+ zram_set_devname(z, NULL, n);
+ if (zram_exist(z) && zram_used(z))
+ fill_table_row(tb, z);
+ }
+ closedir(dir);
+ free_zram(z);
+
+print_table:
+ scols_print_table(tb);
+ scols_unref_table(tb);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _( " %1$s [options] <device>\n"
+ " %1$s -r <device> [...]\n"
+ " %1$s [options] -f | <device> -s <size>\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Set up and control zram devices.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --algorithm lzo|lz4|lz4hc|deflate|842 compression algorithm to use\n"), out);
+ fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out);
+ fputs(_(" -f, --find find a free device\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> columns to use for status output\n"), out);
+ fputs(_(" --output-all output all columns\n"), out);
+ fputs(_(" --raw use raw status output format\n"), out);
+ fputs(_(" -r, --reset reset all specified devices\n"), out);
+ fputs(_(" -s, --size <size> device size\n"), out);
+ fputs(_(" -t, --streams <number> number of compression streams\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(27));
+
+ fputs(USAGE_ARGUMENTS, out);
+ printf(USAGE_ARG_SIZE(_("<size>")));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("zramctl(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+/* actions */
+enum {
+ A_NONE = 0,
+ A_STATUS,
+ A_CREATE,
+ A_FINDONLY,
+ A_RESET
+};
+
+int main(int argc, char **argv)
+{
+ uintmax_t size = 0, nstreams = 0;
+ char *algorithm = NULL;
+ int rc = 0, c, find = 0, act = A_NONE;
+ struct zram *zram = NULL;
+
+ enum {
+ OPT_RAW = CHAR_MAX + 1,
+ OPT_LIST_TYPES
+ };
+
+ static const struct option longopts[] = {
+ { "algorithm", required_argument, NULL, 'a' },
+ { "bytes", no_argument, NULL, 'b' },
+ { "find", no_argument, NULL, 'f' },
+ { "help", no_argument, NULL, 'h' },
+ { "output", required_argument, NULL, 'o' },
+ { "output-all",no_argument, NULL, OPT_LIST_TYPES },
+ { "noheadings",no_argument, NULL, 'n' },
+ { "reset", no_argument, NULL, 'r' },
+ { "raw", no_argument, NULL, OPT_RAW },
+ { "size", required_argument, NULL, 's' },
+ { "streams", required_argument, NULL, 't' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ static const ul_excl_t excl[] = {
+ { 'f', 'o', 'r' },
+ { 'o', 'r', 's' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "a:bfho:nrs:t:V", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'a':
+ algorithm = optarg;
+ break;
+ case 'b':
+ inbytes = 1;
+ break;
+ case 'f':
+ find = 1;
+ break;
+ case 'o':
+ ncolumns = string_to_idarray(optarg,
+ columns, ARRAY_SIZE(columns),
+ column_name_to_id);
+ if (ncolumns < 0)
+ return EXIT_FAILURE;
+ break;
+ case OPT_LIST_TYPES:
+ for (ncolumns = 0; (size_t)ncolumns < ARRAY_SIZE(infos); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ break;
+ case 's':
+ size = strtosize_or_err(optarg, _("failed to parse size"));
+ act = A_CREATE;
+ break;
+ case 't':
+ nstreams = strtou64_or_err(optarg, _("failed to parse streams"));
+ break;
+ case 'r':
+ act = A_RESET;
+ break;
+ case OPT_RAW:
+ raw = 1;
+ break;
+ case 'n':
+ no_headings = 1;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (find && optind < argc)
+ errx(EXIT_FAILURE, _("option --find is mutually exclusive "
+ "with <device>"));
+ if (act == A_NONE)
+ act = find ? A_FINDONLY : A_STATUS;
+
+ if (act != A_RESET && optind + 1 < argc)
+ errx(EXIT_FAILURE, _("only one <device> at a time is allowed"));
+
+ if ((act == A_STATUS || act == A_FINDONLY) && (algorithm || nstreams))
+ errx(EXIT_FAILURE, _("options --algorithm and --streams "
+ "must be combined with --size"));
+
+ ul_path_init_debug();
+ ul_sysfs_init_debug();
+
+ switch (act) {
+ case A_STATUS:
+ if (!ncolumns) { /* default columns */
+ columns[ncolumns++] = COL_NAME;
+ columns[ncolumns++] = COL_ALGORITHM;
+ columns[ncolumns++] = COL_DISKSIZE;
+ columns[ncolumns++] = COL_ORIG_SIZE;
+ columns[ncolumns++] = COL_COMP_SIZE;
+ columns[ncolumns++] = COL_MEMTOTAL;
+ columns[ncolumns++] = COL_STREAMS;
+ columns[ncolumns++] = COL_MOUNTPOINT;
+ }
+ if (optind < argc) {
+ zram = new_zram(argv[optind++]);
+ if (!zram_exist(zram))
+ err(EXIT_FAILURE, "%s", zram->devname);
+ }
+ status(zram);
+ free_zram(zram);
+ break;
+ case A_RESET:
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no device specified"));
+ while (optind < argc) {
+ zram = new_zram(argv[optind]);
+ if (!zram_exist(zram)
+ || zram_set_u64parm(zram, "reset", 1)) {
+ warn(_("%s: failed to reset"), zram->devname);
+ rc = 1;
+ }
+ zram_control_remove(zram);
+ free_zram(zram);
+ optind++;
+ }
+ break;
+ case A_FINDONLY:
+ zram = find_free_zram();
+ if (!zram)
+ errx(EXIT_FAILURE, _("no free zram device found"));
+ printf("%s\n", zram->devname);
+ free_zram(zram);
+ break;
+ case A_CREATE:
+ if (find) {
+ zram = find_free_zram();
+ if (!zram)
+ errx(EXIT_FAILURE, _("no free zram device found"));
+ } else if (optind == argc)
+ errx(EXIT_FAILURE, _("no device specified"));
+ else {
+ zram = new_zram(argv[optind]);
+ if (!zram_exist(zram))
+ err(EXIT_FAILURE, "%s", zram->devname);
+ }
+
+ if (zram_set_u64parm(zram, "reset", 1))
+ err(EXIT_FAILURE, _("%s: failed to reset"), zram->devname);
+
+ if (nstreams &&
+ zram_set_u64parm(zram, "max_comp_streams", nstreams))
+ err(EXIT_FAILURE, _("%s: failed to set number of streams"), zram->devname);
+
+ if (algorithm &&
+ zram_set_strparm(zram, "comp_algorithm", algorithm))
+ err(EXIT_FAILURE, _("%s: failed to set algorithm"), zram->devname);
+
+ if (zram_set_u64parm(zram, "disksize", size))
+ err(EXIT_FAILURE, _("%s: failed to set disksize (%ju bytes)"),
+ zram->devname, size);
+ if (find)
+ printf("%s\n", zram->devname);
+ free_zram(zram);
+ break;
+ }
+
+ ul_unref_path(__control);
+ return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}