summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:30:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:30:35 +0000
commit378c18e5f024ac5a8aef4cb40d7c9aa9633d144c (patch)
tree44dfb6ca500d32cabd450649b322a42e70a30683 /lib
parentInitial commit. (diff)
downloadutil-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.tar.xz
util-linux-378c18e5f024ac5a8aef4cb40d7c9aa9633d144c.zip
Adding upstream version 2.38.1.upstream/2.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/Makemodule.am224
-rw-r--r--lib/blkdev.c483
-rw-r--r--lib/buffer.c289
-rw-r--r--lib/c_strtod.c109
-rw-r--r--lib/canonicalize.c249
-rw-r--r--lib/caputils.c118
-rw-r--r--lib/color-names.c64
-rw-r--r--lib/colors.c907
-rw-r--r--lib/cpuset.c415
-rw-r--r--lib/crc32.c142
-rw-r--r--lib/crc32c.c102
-rw-r--r--lib/encode.c79
-rw-r--r--lib/env.c239
-rw-r--r--lib/exec_shell.c51
-rw-r--r--lib/fileeq.c641
-rw-r--r--lib/fileutils.c313
-rw-r--r--lib/idcache.c117
-rw-r--r--lib/ismounted.c396
-rw-r--r--lib/jsonwrt.c229
-rw-r--r--lib/langinfo.c124
-rw-r--r--lib/linux_version.c75
-rw-r--r--lib/loopdev.c1924
-rw-r--r--lib/mangle.c169
-rw-r--r--lib/match.c53
-rw-r--r--lib/mbsalign.c627
-rw-r--r--lib/mbsedit.c225
-rw-r--r--lib/md5.c257
-rw-r--r--lib/meson.build99
-rw-r--r--lib/monotonic.c81
-rw-r--r--lib/pager.c317
-rw-r--r--lib/path.c1285
-rw-r--r--lib/plymouth-ctrl.c144
-rw-r--r--lib/procfs.c564
-rw-r--r--lib/pty-session.c749
-rw-r--r--lib/pwdutils.c158
-rw-r--r--lib/randutils.c245
-rw-r--r--lib/selinux-utils.c85
-rw-r--r--lib/sha1.c256
-rw-r--r--lib/signames.c173
-rw-r--r--lib/strutils.c1313
-rw-r--r--lib/strv.c403
-rw-r--r--lib/swapprober.c54
-rw-r--r--lib/sysfs.c1172
-rw-r--r--lib/terminal-colors.d.5404
-rw-r--r--lib/terminal-colors.d.5.adoc174
-rw-r--r--lib/timer.c98
-rw-r--r--lib/timeutils.c612
-rw-r--r--lib/ttyutils.c152
-rw-r--r--libblkid/COPYING8
-rw-r--r--libblkid/Makemodule.am19
-rw-r--r--libblkid/blkid.pc.in10
-rw-r--r--libblkid/docs/Makefile.am95
-rw-r--r--libblkid/docs/Makefile.in897
-rw-r--r--libblkid/docs/libblkid-config.xml57
-rw-r--r--libblkid/docs/libblkid-docs.xml82
-rw-r--r--libblkid/docs/libblkid-sections.txt208
-rw-r--r--libblkid/docs/version.xml1
-rw-r--r--libblkid/docs/version.xml.in1
-rw-r--r--libblkid/libblkid.374
-rw-r--r--libblkid/libblkid.3.adoc67
-rw-r--r--libblkid/meson.build152
-rw-r--r--libblkid/samples/Makemodule.am22
-rw-r--r--libblkid/samples/mkfs.c78
-rw-r--r--libblkid/samples/partitions.c96
-rw-r--r--libblkid/samples/superblocks.c67
-rw-r--r--libblkid/samples/topology.c88
-rw-r--r--libblkid/src/Makemodule.am232
-rw-r--r--libblkid/src/blkid.h.in470
-rw-r--r--libblkid/src/blkidP.h564
-rw-r--r--libblkid/src/cache.c225
-rw-r--r--libblkid/src/config.c201
-rw-r--r--libblkid/src/dev.c279
-rw-r--r--libblkid/src/devname.c647
-rw-r--r--libblkid/src/devno.c373
-rw-r--r--libblkid/src/encode.c263
-rw-r--r--libblkid/src/evaluate.c330
-rw-r--r--libblkid/src/getsize.c34
-rw-r--r--libblkid/src/init.c67
-rw-r--r--libblkid/src/libblkid.sym185
-rw-r--r--libblkid/src/partitions/aix.c57
-rw-r--r--libblkid/src/partitions/aix.h7
-rw-r--r--libblkid/src/partitions/atari.c312
-rw-r--r--libblkid/src/partitions/bsd.c188
-rw-r--r--libblkid/src/partitions/dos.c373
-rw-r--r--libblkid/src/partitions/gpt.c473
-rw-r--r--libblkid/src/partitions/mac.c200
-rw-r--r--libblkid/src/partitions/minix.c102
-rw-r--r--libblkid/src/partitions/partitions.c1526
-rw-r--r--libblkid/src/partitions/partitions.h74
-rw-r--r--libblkid/src/partitions/sgi.c87
-rw-r--r--libblkid/src/partitions/solaris_x86.c154
-rw-r--r--libblkid/src/partitions/sun.c125
-rw-r--r--libblkid/src/partitions/ultrix.c99
-rw-r--r--libblkid/src/partitions/unixware.c197
-rw-r--r--libblkid/src/probe.c2356
-rw-r--r--libblkid/src/read.c455
-rw-r--r--libblkid/src/resolve.c129
-rw-r--r--libblkid/src/save.c244
-rw-r--r--libblkid/src/superblocks/adaptec_raid.c116
-rw-r--r--libblkid/src/superblocks/apfs.c83
-rw-r--r--libblkid/src/superblocks/bcache.c79
-rw-r--r--libblkid/src/superblocks/befs.c544
-rw-r--r--libblkid/src/superblocks/bfs.c23
-rw-r--r--libblkid/src/superblocks/bitlocker.c191
-rw-r--r--libblkid/src/superblocks/bluestore.c53
-rw-r--r--libblkid/src/superblocks/btrfs.c259
-rw-r--r--libblkid/src/superblocks/cramfs.c62
-rw-r--r--libblkid/src/superblocks/ddf_raid.c141
-rw-r--r--libblkid/src/superblocks/drbd.c231
-rw-r--r--libblkid/src/superblocks/drbdmanage.c87
-rw-r--r--libblkid/src/superblocks/drbdproxy_datalog.c55
-rw-r--r--libblkid/src/superblocks/erofs.c73
-rw-r--r--libblkid/src/superblocks/exfat.c157
-rw-r--r--libblkid/src/superblocks/exfs.c191
-rw-r--r--libblkid/src/superblocks/ext.c368
-rw-r--r--libblkid/src/superblocks/f2fs.c101
-rw-r--r--libblkid/src/superblocks/gfs.c140
-rw-r--r--libblkid/src/superblocks/hfs.c340
-rw-r--r--libblkid/src/superblocks/highpoint_raid.c87
-rw-r--r--libblkid/src/superblocks/hpfs.c123
-rw-r--r--libblkid/src/superblocks/iso9660.c336
-rw-r--r--libblkid/src/superblocks/isw_raid.c67
-rw-r--r--libblkid/src/superblocks/jfs.c72
-rw-r--r--libblkid/src/superblocks/jmicron_raid.c65
-rw-r--r--libblkid/src/superblocks/linux_raid.c267
-rw-r--r--libblkid/src/superblocks/lsi_raid.c60
-rw-r--r--libblkid/src/superblocks/luks.c134
-rw-r--r--libblkid/src/superblocks/lvm.c264
-rw-r--r--libblkid/src/superblocks/minix.c187
-rw-r--r--libblkid/src/superblocks/mpool.c62
-rw-r--r--libblkid/src/superblocks/netware.c97
-rw-r--r--libblkid/src/superblocks/nilfs.c174
-rw-r--r--libblkid/src/superblocks/ntfs.c253
-rw-r--r--libblkid/src/superblocks/nvidia_raid.c64
-rw-r--r--libblkid/src/superblocks/ocfs.c216
-rw-r--r--libblkid/src/superblocks/promise_raid.c77
-rw-r--r--libblkid/src/superblocks/refs.c26
-rw-r--r--libblkid/src/superblocks/reiserfs.c136
-rw-r--r--libblkid/src/superblocks/romfs.c54
-rw-r--r--libblkid/src/superblocks/silicon_raid.c134
-rw-r--r--libblkid/src/superblocks/squashfs.c103
-rw-r--r--libblkid/src/superblocks/stratis.c127
-rw-r--r--libblkid/src/superblocks/superblocks.c882
-rw-r--r--libblkid/src/superblocks/superblocks.h118
-rw-r--r--libblkid/src/superblocks/swap.c178
-rw-r--r--libblkid/src/superblocks/sysv.c154
-rw-r--r--libblkid/src/superblocks/ubi.c51
-rw-r--r--libblkid/src/superblocks/ubifs.c121
-rw-r--r--libblkid/src/superblocks/udf.c595
-rw-r--r--libblkid/src/superblocks/ufs.c263
-rw-r--r--libblkid/src/superblocks/vdo.c48
-rw-r--r--libblkid/src/superblocks/vfat.c461
-rw-r--r--libblkid/src/superblocks/via_raid.c91
-rw-r--r--libblkid/src/superblocks/vmfs.c101
-rw-r--r--libblkid/src/superblocks/vxfs.c57
-rw-r--r--libblkid/src/superblocks/xfs.c286
-rw-r--r--libblkid/src/superblocks/zfs.c328
-rw-r--r--libblkid/src/superblocks/zonefs.c87
-rw-r--r--libblkid/src/tag.c468
-rw-r--r--libblkid/src/topology/dm.c133
-rw-r--r--libblkid/src/topology/evms.c77
-rw-r--r--libblkid/src/topology/ioctl.c74
-rw-r--r--libblkid/src/topology/lvm.c144
-rw-r--r--libblkid/src/topology/md.c154
-rw-r--r--libblkid/src/topology/sysfs.c124
-rw-r--r--libblkid/src/topology/topology.c381
-rw-r--r--libblkid/src/topology/topology.h25
-rw-r--r--libblkid/src/verify.c226
-rw-r--r--libblkid/src/version.c62
-rw-r--r--libfdisk/COPYING8
-rw-r--r--libfdisk/Makemodule.am15
-rw-r--r--libfdisk/docs/Makefile.am93
-rw-r--r--libfdisk/docs/Makefile.in894
-rw-r--r--libfdisk/docs/libfdisk-docs.xml94
-rw-r--r--libfdisk/docs/libfdisk-sections.txt400
-rw-r--r--libfdisk/docs/version.xml1
-rw-r--r--libfdisk/docs/version.xml.in1
-rw-r--r--libfdisk/fdisk.pc.in11
-rw-r--r--libfdisk/meson.build83
-rw-r--r--libfdisk/samples/Makemodule.am16
-rw-r--r--libfdisk/samples/mkpart-fullspec.c192
-rw-r--r--libfdisk/samples/mkpart.c205
-rw-r--r--libfdisk/src/Makemodule.am140
-rw-r--r--libfdisk/src/alignment.c721
-rw-r--r--libfdisk/src/ask.c1077
-rw-r--r--libfdisk/src/bsd.c1065
-rw-r--r--libfdisk/src/context.c1554
-rw-r--r--libfdisk/src/dos.c2912
-rw-r--r--libfdisk/src/fdiskP.h540
-rw-r--r--libfdisk/src/field.c88
-rw-r--r--libfdisk/src/gpt.c3351
-rw-r--r--libfdisk/src/init.c62
-rw-r--r--libfdisk/src/item.c255
-rw-r--r--libfdisk/src/iter.c82
-rw-r--r--libfdisk/src/label.c752
-rw-r--r--libfdisk/src/libfdisk.h.in895
-rw-r--r--libfdisk/src/libfdisk.sym322
-rw-r--r--libfdisk/src/partition.c1617
-rw-r--r--libfdisk/src/parttype.c569
-rw-r--r--libfdisk/src/script.c1756
-rw-r--r--libfdisk/src/sgi.c1210
-rw-r--r--libfdisk/src/sun.c1192
-rw-r--r--libfdisk/src/table.c797
-rw-r--r--libfdisk/src/test.c59
-rw-r--r--libfdisk/src/utils.c213
-rw-r--r--libfdisk/src/version.c125
-rw-r--r--libfdisk/src/wipe.c209
-rw-r--r--libmount/COPYING8
-rw-r--r--libmount/Makemodule.am15
-rw-r--r--libmount/docs/Makefile.am93
-rw-r--r--libmount/docs/Makefile.in894
-rw-r--r--libmount/docs/libmount-docs.xml86
-rw-r--r--libmount/docs/libmount-sections.txt452
-rw-r--r--libmount/docs/version.xml1
-rw-r--r--libmount/docs/version.xml.in1
-rw-r--r--libmount/meson.build98
-rw-r--r--libmount/mount.pc.in23
-rw-r--r--libmount/python/Makemodule.am35
-rw-r--r--libmount/python/__init__.py2
-rw-r--r--libmount/python/context.c1228
-rw-r--r--libmount/python/fs.c883
-rw-r--r--libmount/python/meson.build34
-rw-r--r--libmount/python/pylibmount.c326
-rw-r--r--libmount/python/pylibmount.h131
-rw-r--r--libmount/python/tab.c783
-rwxr-xr-xlibmount/python/test_mount_context.py173
-rwxr-xr-xlibmount/python/test_mount_tab.py164
-rwxr-xr-xlibmount/python/test_mount_tab_update.py59
-rw-r--r--libmount/src/Makemodule.am197
-rw-r--r--libmount/src/btrfs.c175
-rw-r--r--libmount/src/cache.c840
-rw-r--r--libmount/src/context.c3531
-rw-r--r--libmount/src/context_loopdev.c465
-rw-r--r--libmount/src/context_mount.c2018
-rw-r--r--libmount/src/context_umount.c1328
-rw-r--r--libmount/src/context_veritydev.c566
-rw-r--r--libmount/src/fs.c1671
-rw-r--r--libmount/src/fuzz.c35
-rw-r--r--libmount/src/init.c106
-rw-r--r--libmount/src/iter.c78
-rw-r--r--libmount/src/libmount.h.in1021
-rw-r--r--libmount/src/libmount.sym368
-rw-r--r--libmount/src/lock.c729
-rw-r--r--libmount/src/monitor.c980
-rw-r--r--libmount/src/mountP.h485
-rw-r--r--libmount/src/optmap.c269
-rw-r--r--libmount/src/optstr.c1482
-rw-r--r--libmount/src/tab.c2261
-rw-r--r--libmount/src/tab_diff.c377
-rw-r--r--libmount/src/tab_parse.c1281
-rw-r--r--libmount/src/tab_update.c1100
-rw-r--r--libmount/src/test.c65
-rw-r--r--libmount/src/utils.c1389
-rw-r--r--libmount/src/version.c154
-rw-r--r--libsmartcols/COPYING8
-rw-r--r--libsmartcols/Makemodule.am15
-rw-r--r--libsmartcols/docs/Makefile.am93
-rw-r--r--libsmartcols/docs/Makefile.in894
-rw-r--r--libsmartcols/docs/libsmartcols-docs.xml85
-rw-r--r--libsmartcols/docs/libsmartcols-sections.txt213
-rw-r--r--libsmartcols/docs/version.xml1
-rw-r--r--libsmartcols/docs/version.xml.in1
-rw-r--r--libsmartcols/meson.build53
-rw-r--r--libsmartcols/samples/Makemodule.am53
-rw-r--r--libsmartcols/samples/colors.c125
-rw-r--r--libsmartcols/samples/continuous.c140
-rw-r--r--libsmartcols/samples/fromfile.c347
-rw-r--r--libsmartcols/samples/grouping-overlay.c143
-rw-r--r--libsmartcols/samples/grouping-simple.c155
-rw-r--r--libsmartcols/samples/maxout.c61
-rw-r--r--libsmartcols/samples/title.c135
-rw-r--r--libsmartcols/samples/tree.c269
-rw-r--r--libsmartcols/samples/wrap.c111
-rw-r--r--libsmartcols/smartcols.pc.in10
-rw-r--r--libsmartcols/src/Makemodule.am62
-rw-r--r--libsmartcols/src/calculate.c454
-rw-r--r--libsmartcols/src/cell.c257
-rw-r--r--libsmartcols/src/column.c643
-rw-r--r--libsmartcols/src/grouping.c575
-rw-r--r--libsmartcols/src/init.c62
-rw-r--r--libsmartcols/src/iter.c74
-rw-r--r--libsmartcols/src/libsmartcols.h.in346
-rw-r--r--libsmartcols/src/libsmartcols.sym211
-rw-r--r--libsmartcols/src/line.c560
-rw-r--r--libsmartcols/src/print-api.c220
-rw-r--r--libsmartcols/src/print.c1243
-rw-r--r--libsmartcols/src/smartcolsP.h449
-rw-r--r--libsmartcols/src/symbols.c293
-rw-r--r--libsmartcols/src/table.c1754
-rw-r--r--libsmartcols/src/version.c62
-rw-r--r--libsmartcols/src/walk.c152
-rw-r--r--libuuid/COPYING5
-rw-r--r--libuuid/Makemodule.am10
-rw-r--r--libuuid/man/Makemodule.am27
-rw-r--r--libuuid/man/uuid.364
-rw-r--r--libuuid/man/uuid.3.adoc80
-rw-r--r--libuuid/man/uuid_clear.359
-rw-r--r--libuuid/man/uuid_clear.3.adoc75
-rw-r--r--libuuid/man/uuid_compare.358
-rw-r--r--libuuid/man/uuid_compare.3.adoc77
-rw-r--r--libuuid/man/uuid_copy.362
-rw-r--r--libuuid/man/uuid_copy.3.adoc79
-rw-r--r--libuuid/man/uuid_generate.390
-rw-r--r--libuuid/man/uuid_generate.3.adoc103
-rw-r--r--libuuid/man/uuid_generate_random.31
-rw-r--r--libuuid/man/uuid_generate_time.31
-rw-r--r--libuuid/man/uuid_generate_time_safe.31
-rw-r--r--libuuid/man/uuid_is_null.360
-rw-r--r--libuuid/man/uuid_is_null.3.adoc76
-rw-r--r--libuuid/man/uuid_parse.371
-rw-r--r--libuuid/man/uuid_parse.3.adoc87
-rw-r--r--libuuid/man/uuid_time.363
-rw-r--r--libuuid/man/uuid_time.3.adoc80
-rw-r--r--libuuid/man/uuid_unparse.369
-rw-r--r--libuuid/man/uuid_unparse.3.adoc84
-rw-r--r--libuuid/meson.build47
-rw-r--r--libuuid/src/Makemodule.am67
-rw-r--r--libuuid/src/clear.c43
-rw-r--r--libuuid/src/compare.c55
-rw-r--r--libuuid/src/copy.c45
-rw-r--r--libuuid/src/gen_uuid.c608
-rw-r--r--libuuid/src/isnull.c44
-rw-r--r--libuuid/src/libuuid.sym66
-rw-r--r--libuuid/src/pack.c69
-rw-r--r--libuuid/src/parse.c98
-rw-r--r--libuuid/src/predefined.c83
-rw-r--r--libuuid/src/test_uuid.c121
-rw-r--r--libuuid/src/unpack.c63
-rw-r--r--libuuid/src/unparse.c76
-rw-r--r--libuuid/src/uuid.h123
-rw-r--r--libuuid/src/uuidP.h94
-rw-r--r--libuuid/src/uuid_time.c171
-rw-r--r--libuuid/src/uuidd.h54
-rw-r--r--libuuid/uuid.pc.in11
334 files changed, 108062 insertions, 0 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
new file mode 100644
index 0000000..d38c21e
--- /dev/null
+++ b/lib/Makemodule.am
@@ -0,0 +1,224 @@
+#
+# Use only LGPL or Public domain (preferred) code in libcommon, otherwise add
+# your lib/file.c directly to the _SOURCES= of the target binary.
+#
+# THIS LIBRARY IS NOT DISTRIBUTED!
+#
+# It's just ar(1) archive used by build-system to keep things simple.
+#
+# Note that you need "make install-strip" (or proper rpm / Debian build)
+# to generate binaries with only relevant stuff.
+#
+EXTRA_LTLIBRARIES += libcommon.la
+libcommon_la_CFLAGS = $(AM_CFLAGS)
+libcommon_la_SOURCES = \
+ lib/blkdev.c \
+ lib/buffer.c \
+ lib/canonicalize.c \
+ lib/color-names.c \
+ lib/crc32.c \
+ lib/crc32c.c \
+ lib/c_strtod.c \
+ lib/encode.c \
+ lib/env.c \
+ lib/fileutils.c \
+ lib/idcache.c \
+ lib/jsonwrt.c \
+ lib/mangle.c \
+ lib/match.c \
+ lib/mbsalign.c \
+ lib/mbsedit.c\
+ lib/md5.c \
+ lib/pager.c \
+ lib/pwdutils.c \
+ lib/randutils.c \
+ lib/sha1.c \
+ lib/signames.c \
+ lib/strutils.c \
+ lib/strv.c \
+ lib/timeutils.c \
+ lib/ttyutils.c
+
+if LINUX
+libcommon_la_SOURCES += \
+ lib/linux_version.c \
+ lib/loopdev.c
+endif
+
+if !HAVE_LANGINFO_H
+libcommon_la_SOURCES += lib/langinfo.c
+endif
+
+if HAVE_CPU_SET_T
+libcommon_la_SOURCES += lib/cpuset.c
+endif
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+libcommon_la_SOURCES += lib/path.c
+libcommon_la_SOURCES += lib/sysfs.c
+libcommon_la_SOURCES += lib/procfs.c
+endif
+endif
+
+EXTRA_LTLIBRARIES += libtcolors.la
+libtcolors_la_CFLAGS = $(AM_CFLAGS)
+libtcolors_la_SOURCES = lib/colors.c lib/color-names.c include/colors.h include/color-names.h
+libtcolors_la_LIBADD =
+# tinfo or ncurses are optional
+if HAVE_TINFO
+libtcolors_la_LIBADD += $(TINFO_LIBS)
+libtcolors_la_CFLAGS += $(TINFO_CFLAGS)
+else
+if HAVE_NCURSES
+libtcolors_la_LIBADD += $(NCURSES_LIBS)
+libtcolors_la_CFLAGS += $(NCURSES_CFLAGS)
+endif
+endif # !HAVE_TINFO
+
+MANPAGES += lib/terminal-colors.d.5
+dist_noinst_DATA += lib/terminal-colors.d.5.adoc
+
+check_PROGRAMS += \
+ test_blkdev \
+ test_buffer \
+ test_canonicalize \
+ test_colors \
+ test_fileeq \
+ test_fileutils \
+ test_ismounted \
+ test_pwdutils \
+ test_mangle \
+ test_randutils \
+ test_remove_env \
+ test_strutils \
+ test_ttyutils \
+ test_timeutils \
+ test_c_strtod
+
+
+if LINUX
+if HAVE_CPU_SET_T
+check_PROGRAMS += test_cpuset
+endif
+check_PROGRAMS += \
+ test_sysfs \
+ test_procfs \
+ test_pager \
+ test_caputils \
+ test_loopdev \
+ test_linux_version
+endif
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+check_PROGRAMS += test_path
+endif
+endif
+
+test_caputils_SOURCES = lib/caputils.c
+test_caputils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CAPUTILS
+test_caputils_LDADD = $(LDADD) libcommon.la
+
+test_ttyutils_SOURCES = lib/ttyutils.c
+test_ttyutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TTYUTILS
+test_ttyutils_LDADD = $(LDADD) libcommon.la
+
+test_blkdev_SOURCES = lib/blkdev.c
+test_blkdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_BLKDEV
+test_blkdev_LDADD = $(LDADD) libcommon.la
+
+test_ismounted_SOURCES = lib/ismounted.c
+test_ismounted_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_ISMOUNTED
+test_ismounted_LDADD = libcommon.la $(LDADD)
+
+test_mangle_SOURCES = lib/mangle.c
+test_mangle_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_MANGLE
+
+test_strutils_SOURCES = lib/strutils.c
+test_strutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_STRUTILS
+
+test_c_strtod_SOURCES = lib/c_strtod.c
+test_c_strtod_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
+
+test_colors_SOURCES = lib/colors.c lib/color-names.c
+test_colors_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_COLORS
+test_colors_LDADD = $(LDADD) libtcolors.la
+
+test_randutils_SOURCES = lib/randutils.c
+test_randutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_RANDUTILS
+
+if HAVE_OPENAT
+if HAVE_DIRFD
+test_path_SOURCES = lib/path.c lib/fileutils.c
+if HAVE_CPU_SET_T
+test_path_SOURCES += lib/cpuset.c
+endif
+test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH
+test_path_LDADD = $(LDADD)
+endif
+endif
+
+if HAVE_PTY
+check_PROGRAMS += test_pty
+test_pty_SOURCES = lib/pty-session.c \
+ include/pty-session.h \
+ lib/monotonic.c
+test_pty_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PTY
+test_pty_LDADD = $(LDADD) libcommon.la $(MATH_LIBS) $(REALTIME_LIBS) -lutil
+endif
+
+if LINUX
+test_cpuset_SOURCES = lib/cpuset.c
+test_cpuset_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CPUSET
+
+test_sysfs_SOURCES = lib/sysfs.c lib/path.c lib/fileutils.c
+if HAVE_CPU_SET_T
+test_sysfs_SOURCES += lib/cpuset.c
+endif
+test_sysfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_SYSFS
+test_sysfs_LDADD = $(LDADD)
+
+test_procfs_SOURCES = lib/procfs.c lib/path.c lib/fileutils.c lib/strutils.c
+if HAVE_CPU_SET_T
+test_procfs_SOURCES += lib/cpuset.c
+endif
+test_procfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCFS
+test_procfs_LDADD = $(LDADD)
+
+test_pager_SOURCES = lib/pager.c
+test_pager_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PAGER
+
+test_linux_version_SOURCES = lib/linux_version.c
+test_linux_version_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LINUXVERSION
+endif
+
+test_fileeq_SOURCES = lib/fileeq.c
+test_fileeq_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEEQ
+
+test_fileutils_SOURCES = lib/fileutils.c
+test_fileutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEUTILS
+
+test_canonicalize_SOURCES = lib/canonicalize.c
+test_canonicalize_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CANONICALIZE
+
+test_timeutils_SOURCES = lib/timeutils.c lib/strutils.c
+test_timeutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TIMEUTILS
+
+test_pwdutils_SOURCES = lib/pwdutils.c
+test_pwdutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
+
+test_remove_env_SOURCES = lib/env.c
+test_remove_env_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
+
+test_buffer_SOURCES = lib/buffer.c lib/mbsalign.c
+test_buffer_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_BUFFER
+
+if LINUX
+test_loopdev_SOURCES = lib/loopdev.c \
+ lib/blkdev.c \
+ lib/linux_version.c \
+ $(test_sysfs_SOURCES) \
+ $(test_canonicalize_SOURCES)
+test_loopdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LOOPDEV
+endif
diff --git a/lib/blkdev.c b/lib/blkdev.c
new file mode 100644
index 0000000..cc1eea3
--- /dev/null
+++ b/lib/blkdev.c
@@ -0,0 +1,483 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+
+#ifdef HAVE_LINUX_BLKZONED_H
+#include <linux/blkzoned.h>
+#endif
+
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+
+#ifdef HAVE_SYS_DISK_H
+# include <sys/disk.h>
+#endif
+
+#ifndef EBADFD
+# define EBADFD 77 /* File descriptor in bad state */
+#endif
+
+#include "blkdev.h"
+#include "c.h"
+#include "linux_version.h"
+#include "fileutils.h"
+#include "nls.h"
+
+static long
+blkdev_valid_offset (int fd, off_t offset) {
+ char ch;
+
+ if (lseek (fd, offset, 0) < 0)
+ return 0;
+ if (read (fd, &ch, 1) < 1)
+ return 0;
+ return 1;
+}
+
+int is_blkdev(int fd)
+{
+ struct stat st;
+ return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode));
+}
+
+off_t
+blkdev_find_size (int fd) {
+ uintmax_t high, low = 0;
+
+ for (high = 1024; blkdev_valid_offset (fd, high); ) {
+ if (high == UINTMAX_MAX)
+ return -1;
+
+ low = high;
+
+ if (high >= UINTMAX_MAX/2)
+ high = UINTMAX_MAX;
+ else
+ high *= 2;
+ }
+
+ while (low < high - 1)
+ {
+ uintmax_t mid = (low + high) / 2;
+
+ if (blkdev_valid_offset (fd, mid))
+ low = mid;
+ else
+ high = mid;
+ }
+ blkdev_valid_offset (fd, 0);
+ return (low + 1);
+}
+
+/* get size in bytes */
+int
+blkdev_get_size(int fd, unsigned long long *bytes)
+{
+#ifdef DKIOCGETBLOCKCOUNT
+ /* Apple Darwin */
+ if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
+ *bytes <<= 9;
+ return 0;
+ }
+#endif
+
+#ifdef BLKGETSIZE64
+ if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
+ return 0;
+#endif
+
+#ifdef BLKGETSIZE
+ {
+ unsigned long size;
+
+ if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
+ *bytes = ((unsigned long long)size << 9);
+ return 0;
+ }
+ }
+
+#endif /* BLKGETSIZE */
+
+#ifdef DIOCGMEDIASIZE
+ /* FreeBSD */
+ if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
+ return 0;
+#endif
+
+#ifdef FDGETPRM
+ {
+ struct floppy_struct this_floppy;
+
+ if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
+ *bytes = ((unsigned long long) this_floppy.size) << 9;
+ return 0;
+ }
+ }
+#endif /* FDGETPRM */
+
+#if defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO)
+ {
+ /*
+ * This code works for FreeBSD 4.11 i386, except for the full device
+ * (such as /dev/ad0). It doesn't work properly for newer FreeBSD
+ * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE
+ * above however.
+ *
+ * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw,
+ * character) devices, so we need to check for S_ISCHR, too.
+ */
+ int part = -1;
+ struct disklabel lab;
+ struct partition *pp;
+ struct stat st;
+
+ if ((fstat(fd, &st) >= 0) &&
+ (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
+ part = st.st_rdev & 7;
+
+ if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) {
+ pp = &lab.d_partitions[part];
+ if (pp->p_size) {
+ *bytes = pp->p_size << 9;
+ return 0;
+ }
+ }
+ }
+#endif /* defined(HAVE_SYS_DISKLABEL_H) && defined(DIOCGDINFO) */
+
+ {
+ struct stat st;
+
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+ *bytes = st.st_size;
+ return 0;
+ }
+ if (!S_ISBLK(st.st_mode))
+ return -1;
+ }
+
+ *bytes = blkdev_find_size(fd);
+ return 0;
+}
+
+/* get 512-byte sector count */
+int
+blkdev_get_sectors(int fd, unsigned long long *sectors)
+{
+ unsigned long long bytes;
+
+ if (blkdev_get_size(fd, &bytes) == 0) {
+ *sectors = (bytes >> 9);
+ return 0;
+ }
+
+ return -1;
+}
+
+/*
+ * Get logical sector size.
+ *
+ * This is the smallest unit the storage device can
+ * address. It is typically 512 bytes.
+ */
+#ifdef BLKSSZGET
+int blkdev_get_sector_size(int fd, int *sector_size)
+{
+ if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
+ return 0;
+ return -1;
+}
+#else
+int blkdev_get_sector_size(int fd __attribute__((__unused__)), int *sector_size)
+{
+ *sector_size = DEFAULT_SECTOR_SIZE;
+ return 0;
+}
+#endif
+
+/*
+ * Get physical block device size. The BLKPBSZGET is supported since Linux
+ * 2.6.32. For old kernels is probably the best to assume that physical sector
+ * size is the same as logical sector size.
+ *
+ * Example:
+ *
+ * rc = blkdev_get_physector_size(fd, &physec);
+ * if (rc || physec == 0) {
+ * rc = blkdev_get_sector_size(fd, &physec);
+ * if (rc)
+ * physec = DEFAULT_SECTOR_SIZE;
+ * }
+ */
+#ifdef BLKPBSZGET
+int blkdev_get_physector_size(int fd, int *sector_size)
+{
+ if (ioctl(fd, BLKPBSZGET, sector_size) >= 0)
+ {
+ return 0;
+ }
+ return -1;
+}
+#else
+int blkdev_get_physector_size(int fd __attribute__((__unused__)), int *sector_size)
+{
+ *sector_size = DEFAULT_SECTOR_SIZE;
+ return 0;
+}
+#endif
+
+/*
+ * Return the alignment status of a device
+ */
+#ifdef BLKALIGNOFF
+int blkdev_is_misaligned(int fd)
+{
+ int aligned;
+
+ if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
+ return 0; /* probably kernel < 2.6.32 */
+ /*
+ * Note that kernel returns -1 as alignment offset if no compatible
+ * sizes and alignments exist for stacked devices
+ */
+ return aligned != 0 ? 1 : 0;
+}
+#else
+int blkdev_is_misaligned(int fd __attribute__((__unused__)))
+{
+ return 0;
+}
+#endif
+
+int open_blkdev_or_file(const struct stat *st, const char *name, const int oflag)
+{
+ int fd;
+
+ if (S_ISBLK(st->st_mode)) {
+ fd = open(name, oflag | O_EXCL);
+ } else
+ fd = open(name, oflag);
+ if (-1 < fd && !is_same_inode(fd, st)) {
+ close(fd);
+ errno = EBADFD;
+ return -1;
+ }
+ if (-1 < fd && S_ISBLK(st->st_mode) && blkdev_is_misaligned(fd))
+ warnx(_("warning: %s is misaligned"), name);
+ return fd;
+}
+
+#ifdef CDROM_GET_CAPABILITY
+int blkdev_is_cdrom(int fd)
+{
+ int ret;
+
+ if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0)
+ return 0;
+
+ return ret;
+}
+#else
+int blkdev_is_cdrom(int fd __attribute__((__unused__)))
+{
+ return 0;
+}
+#endif
+
+/*
+ * Get kernel's interpretation of the device's geometry.
+ *
+ * Returns the heads and sectors - but not cylinders
+ * as it's truncated for disks with more than 65535 tracks.
+ *
+ * Note that this is deprecated in favor of LBA addressing.
+ */
+#ifdef HDIO_GETGEO
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s)
+{
+ struct hd_geometry geometry;
+
+ if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
+ *h = geometry.heads;
+ *s = geometry.sectors;
+ return 0;
+ }
+#else
+int blkdev_get_geometry(int fd __attribute__((__unused__)),
+ unsigned int *h, unsigned int *s)
+{
+ *h = 0;
+ *s = 0;
+#endif
+ return -1;
+}
+
+/*
+ * Convert scsi type to human readable string.
+ */
+const char *blkdev_scsi_type_to_name(int type)
+{
+ switch (type) {
+ case SCSI_TYPE_DISK:
+ return "disk";
+ case SCSI_TYPE_TAPE:
+ return "tape";
+ case SCSI_TYPE_PRINTER:
+ return "printer";
+ case SCSI_TYPE_PROCESSOR:
+ return "processor";
+ case SCSI_TYPE_WORM:
+ return "worm";
+ case SCSI_TYPE_ROM:
+ return "rom";
+ case SCSI_TYPE_SCANNER:
+ return "scanner";
+ case SCSI_TYPE_MOD:
+ return "mo-disk";
+ case SCSI_TYPE_MEDIUM_CHANGER:
+ return "changer";
+ case SCSI_TYPE_COMM:
+ return "comm";
+ case SCSI_TYPE_RAID:
+ return "raid";
+ case SCSI_TYPE_ENCLOSURE:
+ return "enclosure";
+ case SCSI_TYPE_RBC:
+ return "rbc";
+ case SCSI_TYPE_OSD:
+ return "osd";
+ case SCSI_TYPE_NO_LUN:
+ return "no-lun";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/* return 0 on success */
+int blkdev_lock(int fd, const char *devname, const char *lockmode)
+{
+ int oper, rc, msg = 0;
+
+ if (!lockmode)
+ lockmode = getenv("LOCK_BLOCK_DEVICE");
+ if (!lockmode)
+ return 0;
+
+ if (strcasecmp(lockmode, "yes") == 0 ||
+ strcmp(lockmode, "1") == 0)
+ oper = LOCK_EX;
+
+ else if (strcasecmp(lockmode, "nonblock") == 0)
+ oper = LOCK_EX | LOCK_NB;
+
+ else if (strcasecmp(lockmode, "no") == 0 ||
+ strcmp(lockmode, "0") == 0)
+ return 0;
+ else {
+ warnx(_("unsupported lock mode: %s"), lockmode);
+ return -EINVAL;
+ }
+
+ if (!(oper & LOCK_NB)) {
+ /* Try non-block first to provide message */
+ rc = flock(fd, oper | LOCK_NB);
+ if (rc == 0)
+ return 0;
+ if (rc != 0 && errno == EWOULDBLOCK) {
+ fprintf(stderr, _("%s: %s: device already locked, waiting to get lock ... "),
+ program_invocation_short_name, devname);
+ msg = 1;
+ }
+ }
+ rc = flock(fd, oper);
+ if (rc != 0) {
+ switch (errno) {
+ case EWOULDBLOCK: /* LOCK_NB */
+ warnx(_("%s: device already locked"), devname);
+ break;
+ default:
+ warn(_("%s: failed to get lock"), devname);
+ }
+ } else if (msg)
+ fprintf(stderr, _("OK\n"));
+ return rc;
+}
+
+#ifdef HAVE_LINUX_BLKZONED_H
+struct blk_zone_report *blkdev_get_zonereport(int fd, uint64_t sector, uint32_t nzones)
+{
+ struct blk_zone_report *rep;
+ size_t rep_size;
+ int ret;
+
+ rep_size = sizeof(struct blk_zone_report) + sizeof(struct blk_zone) * 2;
+ rep = calloc(1, rep_size);
+ if (!rep)
+ return NULL;
+
+ rep->sector = sector;
+ rep->nr_zones = nzones;
+
+ ret = ioctl(fd, BLKREPORTZONE, rep);
+ if (ret || rep->nr_zones != nzones) {
+ free(rep);
+ return NULL;
+ }
+
+ return rep;
+}
+#endif
+
+
+#ifdef TEST_PROGRAM_BLKDEV
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+int
+main(int argc, char **argv)
+{
+ unsigned long long bytes;
+ unsigned long long sectors;
+ int sector_size, phy_sector_size;
+ int fd;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s device\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if ((fd = open(argv[1], O_RDONLY|O_CLOEXEC)) < 0)
+ err(EXIT_FAILURE, "open %s failed", argv[1]);
+
+ if (blkdev_get_size(fd, &bytes) < 0)
+ err(EXIT_FAILURE, "blkdev_get_size() failed");
+ if (blkdev_get_sectors(fd, &sectors) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sectors() failed");
+ if (blkdev_get_sector_size(fd, &sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_sector_size() failed");
+ if (blkdev_get_physector_size(fd, &phy_sector_size) < 0)
+ err(EXIT_FAILURE, "blkdev_get_physector_size() failed");
+
+ printf(" bytes: %llu\n", bytes);
+ printf(" sectors: %llu\n", sectors);
+ printf(" sector size: %d\n", sector_size);
+ printf("phy-sector size: %d\n", phy_sector_size);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_BLKDEV */
diff --git a/lib/buffer.c b/lib/buffer.c
new file mode 100644
index 0000000..1dba7dd
--- /dev/null
+++ b/lib/buffer.c
@@ -0,0 +1,289 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include "buffer.h"
+#include "mbsalign.h"
+
+void ul_buffer_reset_data(struct ul_buffer *buf)
+{
+ if (buf->begin)
+ buf->begin[0] = '\0';
+ buf->end = buf->begin;
+
+ if (buf->ptrs && buf->nptrs)
+ memset(buf->ptrs, 0, buf->nptrs * sizeof(char *));
+}
+
+void ul_buffer_free_data(struct ul_buffer *buf)
+{
+ assert(buf);
+
+ free(buf->begin);
+ buf->begin = NULL;
+ buf->end = NULL;
+ buf->sz = 0;
+
+ free(buf->ptrs);
+ buf->ptrs = NULL;
+ buf->nptrs = 0;
+
+ free(buf->encoded);
+ buf->encoded = NULL;
+ buf->encoded_sz = 0;
+}
+
+void ul_buffer_set_chunksize(struct ul_buffer *buf, size_t sz)
+{
+ buf->chunksize = sz;
+}
+
+int ul_buffer_is_empty(struct ul_buffer *buf)
+{
+ return buf->begin == buf->end;
+}
+
+int ul_buffer_save_pointer(struct ul_buffer *buf, unsigned short ptr_idx)
+{
+ if (ptr_idx >= buf->nptrs) {
+ char **tmp = realloc(buf->ptrs, (ptr_idx + 1) * sizeof(char *));
+
+ if (!tmp)
+ return -EINVAL;
+ buf->ptrs = tmp;
+ buf->nptrs = ptr_idx + 1;
+ }
+
+ buf->ptrs[ptr_idx] = buf->end;
+ return 0;
+}
+
+
+char *ul_buffer_get_pointer(struct ul_buffer *buf, unsigned short ptr_idx)
+{
+ if (ptr_idx < buf->nptrs)
+ return buf->ptrs[ptr_idx];
+ return NULL;
+}
+
+/* returns length from begin to the pointer */
+size_t ul_buffer_get_pointer_length(struct ul_buffer *buf, unsigned short ptr_idx)
+{
+ char *ptr = ul_buffer_get_pointer(buf, ptr_idx);
+
+ if (ptr && ptr > buf->begin)
+ return ptr - buf->begin;
+ return 0;
+}
+
+/* returns width of data in safe encoding (from the begin to the pointer) */
+size_t ul_buffer_get_safe_pointer_width(struct ul_buffer *buf, unsigned short ptr_idx)
+{
+ size_t len = ul_buffer_get_pointer_length(buf, ptr_idx);
+
+ if (!len)
+ return 0;
+
+ return mbs_safe_nwidth(buf->begin, len, NULL);
+}
+
+void ul_buffer_refer_string(struct ul_buffer *buf, char *str)
+{
+ if (buf->sz)
+ ul_buffer_free_data(buf);
+ buf->begin = str;
+ buf->sz = str ? strlen(str) : 0;
+ buf->end = buf->begin ? buf->begin + buf->sz : buf->begin;
+}
+
+int ul_buffer_alloc_data(struct ul_buffer *buf, size_t sz)
+{
+ char *tmp;
+ size_t len = 0;
+
+ assert(buf);
+
+ if (sz <= buf->sz)
+ return 0;
+
+ if (buf->end && buf->begin)
+ len = buf->end - buf->begin;
+
+ if (buf->chunksize)
+ sz = ((sz + buf->chunksize) / buf->chunksize) * buf->chunksize + 1;
+
+ tmp = realloc(buf->begin, sz);
+ if (!tmp)
+ return -ENOMEM;
+
+ buf->begin = tmp;
+ buf->end = buf->begin + len;
+ buf->sz = sz;
+
+ memset(buf->end, '\0', sz - len);
+
+ return 0;
+}
+
+int ul_buffer_append_data(struct ul_buffer *buf, const char *data, size_t sz)
+{
+ size_t maxsz = 0;
+
+ if (!buf)
+ return -EINVAL;
+ if (!data || !*data)
+ return 0;
+
+ if (buf->begin && buf->end)
+ maxsz = buf->sz - (buf->end - buf->begin);
+
+ if (maxsz <= sz + 1) {
+ int rc = ul_buffer_alloc_data(buf, buf->sz + sz + 1);
+ if (rc)
+ return rc;
+ }
+ if (!buf->end)
+ return -EINVAL; /* make static analyzers happy */
+
+ memcpy(buf->end, data, sz);
+ buf->end += sz;
+ *buf->end = '\0'; /* make sure it's terminated */
+ return 0;
+}
+
+int ul_buffer_append_string(struct ul_buffer *buf, const char *str)
+{
+ if (!str)
+ return 0;
+
+ return ul_buffer_append_data(buf, str, strlen(str));
+}
+
+int ul_buffer_append_ntimes(struct ul_buffer *buf, size_t n, const char *str)
+{
+ size_t i;
+ size_t len = strlen(str);
+
+ for (i = 0; len && i < n; i++) {
+ int rc = ul_buffer_append_data(buf, str, len);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+int ul_buffer_set_data(struct ul_buffer *buf, const char *data, size_t sz)
+{
+ ul_buffer_reset_data(buf);
+ return ul_buffer_append_data(buf, data, sz);
+}
+
+char *ul_buffer_get_data(struct ul_buffer *buf, size_t *sz, size_t *width)
+{
+ if (sz)
+ *sz = buf->end - buf->begin;
+ if (width)
+ *width = buf->begin && *buf->begin ? mbs_width(buf->begin) : 0;
+ return buf->begin;
+}
+
+/* size of allocated area (!= size of stored data */
+size_t ul_buffer_get_bufsiz(struct ul_buffer *buf)
+{
+ return buf->sz;
+}
+
+/* encode data by mbs_safe_encode() to avoid control and non-printable chars */
+char *ul_buffer_get_safe_data(struct ul_buffer *buf, size_t *sz, size_t *width, const char *safechars)
+{
+ char *data = ul_buffer_get_data(buf, NULL, NULL);
+ size_t encsz, wsz = 0;
+ char *res = NULL;
+
+ if (!data)
+ goto nothing;
+
+ encsz = mbs_safe_encode_size(buf->sz) + 1;
+ if (encsz > buf->encoded_sz) {
+ char *tmp = realloc(buf->encoded, encsz);
+ if (!tmp)
+ goto nothing;
+ buf->encoded = tmp;
+ buf->encoded_sz = encsz;
+ }
+
+ res = mbs_safe_encode_to_buffer(data, &wsz, buf->encoded, safechars);
+ if (!res || !wsz || wsz == (size_t) -1)
+ goto nothing;
+
+ if (width)
+ *width = wsz;
+ if (sz)
+ *sz = strlen(res);
+ return res;
+nothing:
+ if (width)
+ *width = 0;
+ if (sz)
+ *sz = 0;
+ return NULL;
+}
+
+
+#ifdef TEST_PROGRAM_BUFFER
+
+enum {
+ PTR_AAA = 0,
+ PTR_BBB,
+};
+
+int main(void)
+{
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ char *str;
+ size_t sz = 0;
+
+ ul_buffer_set_chunksize(&buf, 16);
+
+ ul_buffer_append_string(&buf, "AAA");
+ ul_buffer_append_data(&buf, "=", 1);
+ ul_buffer_append_string(&buf, "aaa");
+ ul_buffer_save_pointer(&buf, PTR_AAA);
+
+ ul_buffer_append_data(&buf, ",", 1);
+ ul_buffer_append_string(&buf, "BBB");
+ ul_buffer_append_string(&buf, "=");
+ ul_buffer_append_string(&buf, "bbb");
+ ul_buffer_save_pointer(&buf, PTR_BBB);
+
+ str = ul_buffer_get_data(&buf, &sz, NULL);
+ printf("data [%zu] '%s'\n", sz, str);
+
+ printf(" pointer data len: AAA=%zu, BBB=%zu\n",
+ ul_buffer_get_pointer_length(&buf, PTR_AAA),
+ ul_buffer_get_pointer_length(&buf, PTR_BBB));
+ printf(" pointer data width: AAA=%zu, BBB=%zu\n",
+ ul_buffer_get_safe_pointer_width(&buf, PTR_AAA),
+ ul_buffer_get_safe_pointer_width(&buf, PTR_BBB));
+
+ ul_buffer_reset_data(&buf);
+ ul_buffer_append_string(&buf, "This is really long string to test the buffer function.");
+ ul_buffer_save_pointer(&buf, PTR_AAA);
+ ul_buffer_append_string(&buf, " YES!");
+ str = ul_buffer_get_data(&buf, &sz, NULL);
+ printf("data [%zu] '%s'\n", sz, str);
+ printf(" pointer data len: AAA=%zu\n", ul_buffer_get_pointer_length(&buf, PTR_AAA));
+
+ ul_buffer_free_data(&buf);
+ str = strdup("foo");
+ ul_buffer_refer_string(&buf, str);
+ ul_buffer_append_data(&buf, ",", 1);
+ ul_buffer_append_string(&buf, "bar");
+ str = ul_buffer_get_data(&buf, &sz, NULL);
+ printf("data [%zu] '%s'\n", sz, str);
+
+ ul_buffer_free_data(&buf);
+}
+#endif /* TEST_PROGRAM_BUFFER */
diff --git a/lib/c_strtod.c b/lib/c_strtod.c
new file mode 100644
index 0000000..d25ee27
--- /dev/null
+++ b/lib/c_strtod.c
@@ -0,0 +1,109 @@
+/*
+ * Locale-independent strtod().
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
+ */
+#include "c.h"
+
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "c_strtod.h"
+
+#ifdef __APPLE__
+# include <xlocale.h>
+#endif
+
+#if defined(HAVE_NEWLOCALE) && (defined(HAVE_STRTOD_L) || defined(HAVE_USELOCALE))
+# define USE_CLOCALE
+#endif
+
+#if defined(USE_CLOCALE)
+static volatile locale_t c_locale;
+
+static locale_t get_c_locale(void)
+{
+ if (!c_locale)
+ c_locale = newlocale(LC_ALL_MASK, "C", (locale_t) 0);
+ return c_locale;
+}
+#endif
+
+
+double c_strtod(char const *str, char **end)
+{
+ double res;
+ int errsv;
+
+#if defined(USE_CLOCALE)
+ locale_t cl = get_c_locale();
+
+#if defined(HAVE_STRTOD_L)
+ /*
+ * A) try strtod_l() for "C" locale
+ */
+ if (cl)
+ return strtod_l(str, end, cl);
+#elif defined(HAVE_USELOCALE)
+ /*
+ * B) classic strtod(), but switch to "C" locale by uselocal()
+ */
+ if (cl) {
+ locale_t org_cl = uselocale(locale);
+ if (!org_cl)
+ return 0;
+
+ res = strtod(str, end);
+ errsv = errno;
+
+ uselocale(org_cl);
+ errno = errsv;
+ return res;
+ }
+#endif /* HAVE_USELOCALE */
+#endif /* USE_CLOCALE */
+ /*
+ * C) classic strtod(), but switch to "C" locale by setlocale()
+ */
+ char *org_locale = setlocale(LC_NUMERIC, NULL);
+
+ if (org_locale) {
+ org_locale = strdup(org_locale);
+ if (!org_locale)
+ return 0;
+
+ setlocale(LC_NUMERIC, "C");
+ }
+ res = strtod(str, end);
+ errsv = errno;
+
+ if (org_locale) {
+ setlocale(LC_NUMERIC, org_locale);
+ free(org_locale);
+ }
+ errno = errsv;
+ return res;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ double res;
+ char *end;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s decimal.number\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ res = c_strtod(argv[1], &end);
+ printf("Result: %g, errno: %d, endptr: '%s'\n", res, errno, end);
+
+ return errno ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+#endif
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
new file mode 100644
index 0000000..6f85b47
--- /dev/null
+++ b/lib/canonicalize.c
@@ -0,0 +1,249 @@
+/*
+ * canonicalize.c -- canonicalize pathname by removing symlinks
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2009-2013 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "canonicalize.h"
+#include "pathnames.h"
+#include "all-io.h"
+
+/*
+ * Converts private "dm-N" names to "/dev/mapper/<name>"
+ *
+ * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
+ * provides the real DM device names in /sys/block/<ptname>/dm/name
+ */
+char *__canonicalize_dm_name(const char *prefix, const char *ptname)
+{
+ FILE *f;
+ size_t sz;
+ char path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL;
+
+ if (!ptname || !*ptname)
+ return NULL;
+
+ if (!prefix)
+ prefix = "";
+
+ snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname);
+ if (!(f = fopen(path, "r" UL_CLOEXECSTR)))
+ return NULL;
+
+ /* read "<name>\n" from sysfs */
+ if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
+ name[sz - 1] = '\0';
+ snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name);
+
+ if ((prefix && *prefix) || access(path, F_OK) == 0)
+ res = strdup(path);
+ }
+ fclose(f);
+ return res;
+}
+
+char *canonicalize_dm_name(const char *ptname)
+{
+ return __canonicalize_dm_name(NULL, ptname);
+}
+
+static int is_dm_devname(char *canonical, char **name)
+{
+ struct stat sb;
+ char *p = strrchr(canonical, '/');
+
+ *name = NULL;
+
+ if (!p
+ || strncmp(p, "/dm-", 4) != 0
+ || !isdigit(*(p + 4))
+ || stat(canonical, &sb) != 0
+ || !S_ISBLK(sb.st_mode))
+ return 0;
+
+ *name = p + 1;
+ return 1;
+}
+
+/*
+ * This function does not canonicalize the path! It just prepends CWD before a
+ * relative path. If the path is no relative than returns NULL. The path does
+ * not have to exist.
+ */
+char *absolute_path(const char *path)
+{
+ char cwd[PATH_MAX], *res, *p;
+ size_t psz, csz;
+
+ if (!is_relative_path(path)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!getcwd(cwd, sizeof(cwd)))
+ return NULL;
+
+ /* simple clean up */
+ if (startswith(path, "./"))
+ path += 2;
+ else if (strcmp(path, ".") == 0)
+ path = NULL;
+
+ if (!path || !*path)
+ return strdup(cwd);
+
+ csz = strlen(cwd);
+ psz = strlen(path);
+
+ p = res = malloc(csz + 1 + psz + 1);
+ if (!res)
+ return NULL;
+
+ memcpy(p, cwd, csz);
+ p += csz;
+ *p++ = '/';
+ memcpy(p, path, psz + 1);
+
+ return res;
+}
+
+char *canonicalize_path(const char *path)
+{
+ char *canonical, *dmname;
+
+ if (!path || !*path)
+ return NULL;
+
+ canonical = realpath(path, NULL);
+ if (!canonical)
+ return strdup(path);
+
+ if (is_dm_devname(canonical, &dmname)) {
+ char *dm = canonicalize_dm_name(dmname);
+ if (dm) {
+ free(canonical);
+ return dm;
+ }
+ }
+
+ return canonical;
+}
+
+char *canonicalize_path_restricted(const char *path)
+{
+ char *canonical = NULL;
+ int errsv = 0;
+ int pipes[2];
+ ssize_t len;
+ pid_t pid;
+
+ if (!path || !*path)
+ return NULL;
+
+ if (pipe(pipes) != 0)
+ return NULL;
+
+ /*
+ * To accurately assume identity of getuid() we must use setuid()
+ * but if we do that, we lose ability to reassume euid of 0, so
+ * we fork to do the check to keep euid intact.
+ */
+ pid = fork();
+ switch (pid) {
+ case -1:
+ close(pipes[0]);
+ close(pipes[1]);
+ return NULL; /* fork error */
+ case 0:
+ close(pipes[0]); /* close unused end */
+ pipes[0] = -1;
+ errno = 0;
+
+ if (drop_permissions() != 0)
+ canonical = NULL; /* failed */
+ else {
+ char *dmname = NULL;
+
+ canonical = realpath(path, NULL);
+ if (canonical && is_dm_devname(canonical, &dmname)) {
+ char *dm = canonicalize_dm_name(dmname);
+ if (dm) {
+ free(canonical);
+ canonical = dm;
+ }
+ }
+ }
+
+ len = canonical ? (ssize_t) strlen(canonical) :
+ errno ? -errno : -EINVAL;
+
+ /* send length or errno */
+ write_all(pipes[1], (char *) &len, sizeof(len));
+ if (canonical)
+ write_all(pipes[1], canonical, len);
+ exit(0);
+ default:
+ break;
+ }
+
+ close(pipes[1]); /* close unused end */
+ pipes[1] = -1;
+
+ /* read size or -errno */
+ if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
+ goto done;
+ if (len < 0) {
+ errsv = -len;
+ goto done;
+ }
+
+ canonical = malloc(len + 1);
+ if (!canonical) {
+ errsv = ENOMEM;
+ goto done;
+ }
+ /* read path */
+ if (read_all(pipes[0], canonical, len) != len) {
+ errsv = errno;
+ goto done;
+ }
+ canonical[len] = '\0';
+done:
+ if (errsv) {
+ free(canonical);
+ canonical = NULL;
+ }
+ close(pipes[0]);
+
+ /* We make a best effort to reap child */
+ waitpid(pid, NULL, 0);
+
+ errno = errsv;
+ return canonical;
+}
+
+
+#ifdef TEST_PROGRAM_CANONICALIZE
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(stdout, "orig: %s\n", argv[1]);
+ fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
+ exit(EXIT_SUCCESS);
+}
+#endif
diff --git a/lib/caputils.c b/lib/caputils.c
new file mode 100644
index 0000000..987533a
--- /dev/null
+++ b/lib/caputils.c
@@ -0,0 +1,118 @@
+/*
+ * 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/prctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "c.h"
+#include "caputils.h"
+#include "pathnames.h"
+#include "procfs.h"
+
+static int test_cap(unsigned int cap)
+{
+ /* prctl returns 0 or 1 for valid caps, -1 otherwise */
+ return prctl(PR_CAPBSET_READ, cap, 0, 0, 0) >= 0;
+}
+
+static int cap_last_by_bsearch(int *ret)
+{
+ /* starting with cap=INT_MAX means we always know
+ * that cap1 is invalid after the first iteration */
+ int cap = INT_MAX;
+ unsigned int cap0 = 0, cap1 = INT_MAX;
+
+ while ((int)cap0 < cap) {
+ if (test_cap(cap))
+ cap0 = cap;
+ else
+ cap1 = cap;
+
+ cap = (cap0 + cap1) / 2U;
+ }
+
+ *ret = cap;
+ return 0;
+}
+
+static int cap_last_by_procfs(int *ret)
+{
+ FILE *f = fopen(_PATH_PROC_CAPLASTCAP, "r");
+ int rc = -EINVAL;
+
+ *ret = 0;
+
+ if (f && fd_is_procfs(fileno(f))) {
+ int cap;
+
+ /* we check if the cap after this one really isn't valid */
+ if (fscanf(f, "%d", &cap) == 1 &&
+ cap < INT_MAX && !test_cap(cap + 1)) {
+
+ *ret = cap;
+ rc = 0;
+ }
+ }
+
+ if (f)
+ fclose(f);
+ return rc;
+}
+
+int cap_last_cap(void)
+{
+ static int cap = -1;
+
+ if (cap != -1)
+ return cap;
+ if (cap_last_by_procfs(&cap) < 0)
+ cap_last_by_bsearch(&cap);
+
+ return cap;
+}
+
+#ifdef TEST_PROGRAM_CAPUTILS
+int main(int argc, char *argv[])
+{
+ int rc = 0, cap;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %1$s --last-by-procfs\n"
+ " %1$s --last-by-bsearch\n"
+ " %1$s --last\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(argv[1], "--last-by-procfs") == 0) {
+ rc = cap_last_by_procfs(&cap);
+ if (rc == 0)
+ printf("last cap: %d\n", cap);
+
+ } else if (strcmp(argv[1], "--last-by-bsearch") == 0) {
+ rc = cap_last_by_bsearch(&cap);
+ if (rc == 0)
+ printf("last cap: %d\n", cap);
+
+ } else if (strcmp(argv[1], "--last") == 0)
+ printf("last cap: %d\n", cap_last_cap());
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+#endif
diff --git a/lib/color-names.c b/lib/color-names.c
new file mode 100644
index 0000000..9b1505e
--- /dev/null
+++ b/lib/color-names.c
@@ -0,0 +1,64 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include "c.h"
+#include "color-names.h"
+
+struct ul_color_name {
+ const char *name;
+ const char *seq;
+};
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_color_name(const void *a0, const void *b0)
+{
+ const struct ul_color_name
+ *a = (const struct ul_color_name *) a0,
+ *b = (const struct ul_color_name *) b0;
+ return strcmp(a->name, b->name);
+}
+
+/*
+ * Maintains human readable color names
+ */
+const char *color_sequence_from_colorname(const char *str)
+{
+ static const struct ul_color_name basic_schemes[] = {
+ { "black", UL_COLOR_BLACK },
+ { "blink", UL_COLOR_BLINK },
+ { "blue", UL_COLOR_BLUE },
+ { "bold", UL_COLOR_BOLD },
+ { "brown", UL_COLOR_BROWN },
+ { "cyan", UL_COLOR_CYAN },
+ { "darkgray", UL_COLOR_DARK_GRAY },
+ { "gray", UL_COLOR_GRAY },
+ { "green", UL_COLOR_GREEN },
+ { "halfbright", UL_COLOR_HALFBRIGHT },
+ { "lightblue", UL_COLOR_BOLD_BLUE },
+ { "lightcyan", UL_COLOR_BOLD_CYAN },
+ { "lightgray,", UL_COLOR_GRAY },
+ { "lightgreen", UL_COLOR_BOLD_GREEN },
+ { "lightmagenta", UL_COLOR_BOLD_MAGENTA },
+ { "lightred", UL_COLOR_BOLD_RED },
+ { "magenta", UL_COLOR_MAGENTA },
+ { "red", UL_COLOR_RED },
+ { "reset", UL_COLOR_RESET, },
+ { "reverse", UL_COLOR_REVERSE },
+ { "yellow", UL_COLOR_BOLD_YELLOW },
+ { "white", UL_COLOR_WHITE }
+ };
+ struct ul_color_name key = { .name = str }, *res;
+
+ if (!str)
+ return NULL;
+
+ res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
+ sizeof(struct ul_color_name),
+ cmp_color_name);
+ return res ? res->seq : NULL;
+}
diff --git a/lib/colors.c b/lib/colors.c
new file mode 100644
index 0000000..e317519
--- /dev/null
+++ b/lib/colors.c
@@ -0,0 +1,907 @@
+/*
+ * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+# if defined(HAVE_NCURSESW_NCURSES_H)
+# include <ncursesw/ncurses.h>
+# elif defined(HAVE_NCURSES_NCURSES_H)
+# include <ncurses/ncurses.h>
+# elif defined(HAVE_NCURSES_H)
+# include <ncurses.h>
+# endif
+# if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+# elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+# elif defined(HAVE_TERM_H)
+# include <term.h>
+# endif
+#endif
+
+#include "c.h"
+#include "colors.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+#include "debug.h"
+
+/*
+ * Default behavior, may be overridden by terminal-colors.d/{enable,disable}.
+ */
+#ifdef USE_COLORS_BY_DEFAULT
+# define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */
+#else
+# define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */
+#endif
+
+/*
+ * terminal-colors.d debug stuff
+ */
+static UL_DEBUG_DEFINE_MASK(termcolors);
+UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define TERMCOLORS_DEBUG_INIT (1 << 1)
+#define TERMCOLORS_DEBUG_CONF (1 << 2)
+#define TERMCOLORS_DEBUG_SCHEME (1 << 3)
+#define TERMCOLORS_DEBUG_ALL 0xFFFF
+
+#define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
+
+/*
+ * terminal-colors.d file types
+ */
+enum {
+ UL_COLORFILE_DISABLE, /* .disable */
+ UL_COLORFILE_ENABLE, /* .enable */
+ UL_COLORFILE_SCHEME, /* .scheme */
+
+ __UL_COLORFILE_COUNT
+};
+
+struct ul_color_scheme {
+ char *name;
+ char *seq;
+};
+
+/*
+ * Global colors control struct
+ *
+ * The terminal-colors.d/ evaluation is based on "scores":
+ *
+ * filename score
+ * ---------------------------------------
+ * type 1
+ * @termname.type 10 + 1
+ * utilname.type 20 + 1
+ * utilname@termname.type 20 + 10 + 1
+ *
+ * the match with higher score wins. The score is per type.
+ */
+struct ul_color_ctl {
+ const char *utilname; /* util name */
+ const char *termname; /* terminal name ($TERM) */
+
+ char *sfile; /* path to scheme */
+
+ struct ul_color_scheme *schemes; /* array with color schemes */
+ size_t nschemes; /* number of the items */
+ size_t schemes_sz; /* number of the allocated items */
+
+ int mode; /* UL_COLORMODE_* */
+ unsigned int has_colors : 1, /* based on mode and scores[] */
+ disabled : 1, /* disable colors */
+ cs_configured : 1, /* color schemes read */
+ configured : 1; /* terminal-colors.d parsed */
+
+ int scores[__UL_COLORFILE_COUNT]; /* the best match */
+};
+
+/*
+ * Control struct, globally shared.
+ */
+static struct ul_color_ctl ul_colors;
+
+static void colors_free_schemes(struct ul_color_ctl *cc);
+static int colors_read_schemes(struct ul_color_ctl *cc);
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_scheme_name(const void *a0, const void *b0)
+{
+ const struct ul_color_scheme *a = (const struct ul_color_scheme *) a0,
+ *b = (const struct ul_color_scheme *) b0;
+ return strcmp(a->name, b->name);
+}
+
+/*
+ * Resets control struct (note that we don't allocate the struct)
+ */
+static void colors_reset(struct ul_color_ctl *cc)
+{
+ if (!cc)
+ return;
+
+ colors_free_schemes(cc);
+
+ free(cc->sfile);
+
+ cc->sfile = NULL;
+ cc->utilname = NULL;
+ cc->termname = NULL;
+ cc->mode = UL_COLORMODE_UNDEF;
+
+ memset(cc->scores, 0, sizeof(cc->scores));
+}
+
+static void colors_debug(struct ul_color_ctl *cc)
+{
+ size_t i;
+
+ if (!cc)
+ return;
+
+ printf("Colors:\n");
+ printf("\tutilname = '%s'\n", cc->utilname);
+ printf("\ttermname = '%s'\n", cc->termname);
+ printf("\tscheme file = '%s'\n", cc->sfile);
+ printf("\tmode = %s\n",
+ cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
+ cc->mode == UL_COLORMODE_AUTO ? "auto" :
+ cc->mode == UL_COLORMODE_NEVER ? "never" :
+ cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
+ printf("\thas_colors = %d\n", cc->has_colors);
+ printf("\tdisabled = %d\n", cc->disabled);
+ printf("\tconfigured = %d\n", cc->configured);
+ printf("\tcs configured = %d\n", cc->cs_configured);
+
+ fputc('\n', stdout);
+
+ for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
+ printf("\tscore %s = %d\n",
+ i == UL_COLORFILE_DISABLE ? "disable" :
+ i == UL_COLORFILE_ENABLE ? "enable" :
+ i == UL_COLORFILE_SCHEME ? "scheme" : "???",
+ cc->scores[i]);
+
+ fputc('\n', stdout);
+
+ for (i = 0; i < cc->nschemes; i++) {
+ printf("\tscheme #%02zu ", i);
+ color_scheme_enable(cc->schemes[i].name, NULL);
+ fputs(cc->schemes[i].name, stdout);
+ color_disable();
+ fputc('\n', stdout);
+ }
+ fputc('\n', stdout);
+}
+
+/*
+ * Parses [[<utilname>][@<termname>].]<type>
+ */
+static int filename_to_tokens(const char *str,
+ const char **name, size_t *namesz,
+ const char **term, size_t *termsz,
+ int *filetype)
+{
+ const char *type_start, *term_start, *p;
+
+ if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX)
+ return -EINVAL;
+
+ /* parse .type */
+ p = strrchr(str, '.');
+ type_start = p ? p + 1 : str;
+
+ if (strcmp(type_start, "disable") == 0)
+ *filetype = UL_COLORFILE_DISABLE;
+ else if (strcmp(type_start, "enable") == 0)
+ *filetype = UL_COLORFILE_ENABLE;
+ else if (strcmp(type_start, "scheme") == 0)
+ *filetype = UL_COLORFILE_SCHEME;
+ else {
+ DBG(CONF, ul_debug("unknown type '%s'", type_start));
+ return 1; /* unknown type */
+ }
+
+ if (type_start == str)
+ return 0; /* "type" only */
+
+ /* parse @termname */
+ p = strchr(str, '@');
+ term_start = p ? p + 1 : NULL;
+ if (term_start) {
+ *term = term_start;
+ *termsz = type_start - term_start - 1;
+ if (term_start - 1 == str)
+ return 0; /* "@termname.type" */
+ }
+
+ /* parse utilname */
+ p = term_start ? term_start : type_start;
+ *name = str;
+ *namesz = p - str - 1;
+
+ return 0;
+}
+
+/*
+ * Scans @dirname and select the best matches for UL_COLORFILE_* types.
+ * The result is stored to cc->scores. The path to the best "scheme"
+ * file is stored to cc->scheme.
+ */
+static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
+{
+ DIR *dir;
+ int rc = 0;
+ struct dirent *d;
+ char sfile[PATH_MAX] = { '\0' };
+ size_t namesz, termsz;
+
+ if (!dirname || !cc || !cc->utilname || !*cc->utilname)
+ return -EINVAL;
+
+ DBG(CONF, ul_debug("reading dir: '%s'", dirname));
+
+ dir = opendir(dirname);
+ if (!dir)
+ return -errno;
+
+ namesz = strlen(cc->utilname);
+ termsz = cc->termname ? strlen(cc->termname) : 0;
+
+ while ((d = readdir(dir))) {
+ int type, score = 1;
+ const char *tk_name = NULL, *tk_term = NULL;
+ size_t tk_namesz = 0, tk_termsz = 0;
+
+ if (*d->d_name == '.')
+ continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK &&
+ d->d_type != DT_REG)
+ continue;
+#endif
+ if (filename_to_tokens(d->d_name,
+ &tk_name, &tk_namesz,
+ &tk_term, &tk_termsz, &type) != 0)
+ continue;
+
+ /* count theoretical score before we check names to avoid
+ * unnecessary strcmp() */
+ if (tk_name)
+ score += 20;
+ if (tk_term)
+ score += 10;
+
+ DBG(CONF, ul_debug("item '%s': score=%d "
+ "[cur: %d, name(%zu): %s, term(%zu): %s]",
+ d->d_name, score, cc->scores[type],
+ tk_namesz, tk_name,
+ tk_termsz, tk_term));
+
+
+ if (score < cc->scores[type])
+ continue;
+
+ /* filter out by names */
+ if (tk_namesz && (tk_namesz != namesz ||
+ strncmp(tk_name, cc->utilname, namesz) != 0))
+ continue;
+
+ if (tk_termsz && (termsz == 0 || tk_termsz != termsz ||
+ strncmp(tk_term, cc->termname, termsz) != 0))
+ continue;
+
+ DBG(CONF, ul_debug("setting '%s' from %d -to-> %d",
+ type == UL_COLORFILE_SCHEME ? "scheme" :
+ type == UL_COLORFILE_DISABLE ? "disable" :
+ type == UL_COLORFILE_ENABLE ? "enable" : "???",
+ cc->scores[type], score));
+ cc->scores[type] = score;
+ if (type == UL_COLORFILE_SCHEME)
+ strncpy(sfile, d->d_name, sizeof(sfile));
+ }
+
+ if (*sfile) {
+ sfile[sizeof(sfile) - 1] = '\0';
+ if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0)
+ rc = -ENOMEM;
+ }
+
+ closedir(dir);
+ return rc;
+}
+
+/* atexit() wrapper */
+static void colors_deinit(void)
+{
+ colors_reset(&ul_colors);
+}
+
+/*
+ * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
+ */
+static char *colors_get_homedir(char *buf, size_t bufsz)
+{
+ char *p = getenv("XDG_CONFIG_HOME");
+
+ if (p) {
+ snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p);
+ return buf;
+ }
+
+ p = getenv("HOME");
+ if (p) {
+ snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p);
+ return buf;
+ }
+
+ return NULL;
+}
+
+/* canonicalize sequence */
+static int cn_sequence(const char *str, char **seq)
+{
+ char *in, *out;
+ int len;
+
+ if (!str)
+ return -EINVAL;
+
+ *seq = NULL;
+
+ /* convert logical names like "red" to the real sequence */
+ if (*str != '\\' && isalpha(*str)) {
+ const char *s = color_sequence_from_colorname(str);
+ *seq = strdup(s ? s : str);
+
+ return *seq ? 0 : -ENOMEM;
+ }
+
+ /* convert xx;yy sequences to "\033[xx;yy" */
+ if ((len = asprintf(seq, "\033[%sm", str)) < 1)
+ return -ENOMEM;
+
+ for (in = *seq, out = *seq; in && *in; in++) {
+ if (*in != '\\') {
+ *out++ = *in;
+ continue;
+ }
+ switch(*(in + 1)) {
+ case 'a':
+ *out++ = '\a'; /* Bell */
+ break;
+ case 'b':
+ *out++ = '\b'; /* Backspace */
+ break;
+ case 'e':
+ *out++ = '\033'; /* Escape */
+ break;
+ case 'f':
+ *out++ = '\f'; /* Form Feed */
+ break;
+ case 'n':
+ *out++ = '\n'; /* Newline */
+ break;
+ case 'r':
+ *out++ = '\r'; /* Carriage Return */
+ break;
+ case 't':
+ *out++ = '\t'; /* Tab */
+ break;
+ case 'v':
+ *out++ = '\v'; /* Vertical Tab */
+ break;
+ case '\\':
+ *out++ = '\\'; /* Backslash */
+ break;
+ case '_':
+ *out++ = ' '; /* Space */
+ break;
+ case '#':
+ *out++ = '#'; /* Hash mark */
+ break;
+ case '?':
+ *out++ = '?'; /* Question mark */
+ break;
+ default:
+ *out++ = *in;
+ *out++ = *(in + 1);
+ break;
+ }
+ in++;
+ }
+
+ if (out) {
+ assert ((out - *seq) <= len);
+ *out = '\0';
+ }
+
+ return 0;
+}
+
+
+/*
+ * Adds one color sequence to array with color scheme.
+ * When returning success (0) this function takes ownership of
+ * @seq and @name, which have to be allocated strings.
+ */
+static int colors_add_scheme(struct ul_color_ctl *cc,
+ char *name,
+ char *seq0)
+{
+ struct ul_color_scheme *cs = NULL;
+ char *seq = NULL;
+ int rc;
+
+ if (!cc || !name || !*name || !seq0 || !*seq0)
+ return -EINVAL;
+
+ DBG(SCHEME, ul_debug("add '%s'", name));
+
+ rc = cn_sequence(seq0, &seq);
+ if (rc)
+ return rc;
+
+ rc = -ENOMEM;
+
+ /* convert logical name (e.g. "red") to real ESC code */
+ if (isalpha(*seq)) {
+ const char *s = color_sequence_from_colorname(seq);
+ char *p;
+
+ if (!s) {
+ DBG(SCHEME, ul_debug("unknown logical name: %s", seq));
+ rc = -EINVAL;
+ goto err;
+ }
+
+ p = strdup(s);
+ if (!p)
+ goto err;
+ free(seq);
+ seq = p;
+ }
+
+ /* enlarge the array */
+ if (cc->nschemes == cc->schemes_sz) {
+ void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
+ * sizeof(struct ul_color_scheme));
+ if (!tmp)
+ goto err;
+ cc->schemes = tmp;
+ cc->schemes_sz = cc->nschemes + 10;
+ }
+
+ /* add a new item */
+ cs = &cc->schemes[cc->nschemes];
+ cs->seq = seq;
+ cs->name = strdup(name);
+ if (!cs->name)
+ goto err;
+
+ cc->nschemes++;
+ return 0;
+err:
+ if (cs) {
+ free(cs->seq);
+ free(cs->name);
+ cs->seq = cs->name = NULL;
+ } else
+ free(seq);
+ return rc;
+}
+
+/*
+ * Deallocates all regards to color schemes
+ */
+static void colors_free_schemes(struct ul_color_ctl *cc)
+{
+ size_t i;
+
+ DBG(SCHEME, ul_debug("free scheme"));
+
+ for (i = 0; i < cc->nschemes; i++) {
+ free(cc->schemes[i].name);
+ free(cc->schemes[i].seq);
+ }
+
+ free(cc->schemes);
+ cc->schemes = NULL;
+ cc->nschemes = 0;
+ cc->schemes_sz = 0;
+}
+
+/*
+ * The scheme configuration has to be sorted for bsearch
+ */
+static void colors_sort_schemes(struct ul_color_ctl *cc)
+{
+ if (!cc->nschemes)
+ return;
+
+ DBG(SCHEME, ul_debug("sort scheme"));
+
+ qsort(cc->schemes, cc->nschemes,
+ sizeof(struct ul_color_scheme), cmp_scheme_name);
+}
+
+/*
+ * Returns just one color scheme
+ */
+static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
+ const char *name)
+{
+ struct ul_color_scheme key = { .name = (char *) name}, *res;
+
+ if (!cc || !name || !*name)
+ return NULL;
+
+ if (!cc->cs_configured) {
+ int rc = colors_read_schemes(cc);
+ if (rc)
+ return NULL;
+ }
+ if (!cc->nschemes)
+ return NULL;
+
+ DBG(SCHEME, ul_debug("search '%s'", name));
+
+ res = bsearch(&key, cc->schemes, cc->nschemes,
+ sizeof(struct ul_color_scheme),
+ cmp_scheme_name);
+
+ return res && res->seq ? res : NULL;
+}
+
+/*
+ * Parses filenames in terminal-colors.d
+ */
+static int colors_read_configuration(struct ul_color_ctl *cc)
+{
+ int rc = -ENOENT;
+ char *dirname, buf[PATH_MAX];
+
+ cc->termname = getenv("TERM");
+
+ dirname = colors_get_homedir(buf, sizeof(buf));
+ if (dirname)
+ rc = colors_readdir(cc, dirname); /* ~/.config */
+ if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
+ rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */
+
+ cc->configured = 1;
+ return rc;
+}
+
+/*
+ * Reads terminal-colors.d/ scheme file into array schemes
+ */
+static int colors_read_schemes(struct ul_color_ctl *cc)
+{
+ int rc = 0;
+ FILE *f = NULL;
+ char buf[BUFSIZ],
+ cn[129], seq[129];
+
+ if (!cc->configured)
+ rc = colors_read_configuration(cc);
+
+ cc->cs_configured = 1;
+
+ if (rc || !cc->sfile)
+ goto done;
+
+ DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile));
+
+ f = fopen(cc->sfile, "r");
+ if (!f) {
+ rc = -errno;
+ goto done;
+ }
+
+ while (fgets(buf, sizeof(buf), f)) {
+ char *p = strchr(buf, '\n');
+
+ if (!p) {
+ if (feof(f))
+ p = strchr(buf, '\0');
+ else {
+ rc = -errno;
+ goto done;
+ }
+ }
+ *p = '\0';
+ p = (char *) skip_blank(buf);
+ if (*p == '\0' || *p == '#')
+ continue;
+
+ rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq);
+ if (rc == 2 && *cn && *seq) {
+ rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */
+ if (rc)
+ goto done;
+ }
+ }
+ rc = 0;
+
+done:
+ if (f)
+ fclose(f);
+ colors_sort_schemes(cc);
+
+ return rc;
+}
+
+
+static void termcolors_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG);
+}
+
+static int colors_terminal_is_ready(void)
+{
+ int ncolors = -1;
+
+#if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
+ {
+ int ret;
+
+ if (setupterm(NULL, STDOUT_FILENO, &ret) == 0 && ret == 1)
+ ncolors = tigetnum("colors");
+ }
+#endif
+ if (1 < ncolors) {
+ DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors));
+ return 1;
+ }
+
+ DBG(CONF, ul_debug("terminal is NOT ready (no colors)"));
+ return 0;
+}
+
+/**
+ * colors_init:
+ * @mode: UL_COLORMODE_*
+ * @name: util argv[0]
+ *
+ * Initialize private color control struct and initialize the colors
+ * status. The color schemes are parsed on demand by colors_get_scheme().
+ *
+ * Returns: >0 on success.
+ */
+int colors_init(int mode, const char *name)
+{
+ int ready = -1;
+ struct ul_color_ctl *cc = &ul_colors;
+
+ cc->utilname = name;
+
+ termcolors_init_debug();
+
+ if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO))
+ cc->mode = UL_COLORMODE_NEVER;
+ else
+ cc->mode = mode;
+
+ if (cc->mode == UL_COLORMODE_UNDEF
+ && (ready = colors_terminal_is_ready())) {
+ int rc = colors_read_configuration(cc);
+ if (rc)
+ cc->mode = UL_COLORMODE_DEFAULT;
+ else {
+
+ /* evaluate scores */
+ if (cc->scores[UL_COLORFILE_DISABLE] >
+ cc->scores[UL_COLORFILE_ENABLE])
+ cc->mode = UL_COLORMODE_NEVER;
+ else
+ cc->mode = UL_COLORMODE_DEFAULT;
+
+ atexit(colors_deinit);
+ }
+ }
+
+ switch (cc->mode) {
+ case UL_COLORMODE_AUTO:
+ cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready;
+ break;
+ case UL_COLORMODE_ALWAYS:
+ cc->has_colors = 1;
+ break;
+ case UL_COLORMODE_NEVER:
+ default:
+ cc->has_colors = 0;
+ }
+
+ ON_DBG(CONF, colors_debug(cc));
+
+ return cc->has_colors;
+}
+
+/*
+ * Temporary disable colors (this setting is independent on terminal-colors.d/)
+ */
+void colors_off(void)
+{
+ ul_colors.disabled = 1;
+}
+
+/*
+ * Enable colors
+ */
+void colors_on(void)
+{
+ ul_colors.disabled = 0;
+}
+
+/*
+ * Is terminal-colors.d/ configured to use colors?
+ */
+int colors_wanted(void)
+{
+ return ul_colors.has_colors;
+}
+
+/*
+ * Returns mode
+ */
+int colors_mode(void)
+{
+ return ul_colors.mode;
+}
+
+/*
+ * Enable @seq color
+ */
+void color_fenable(const char *seq, FILE *f)
+{
+ if (!ul_colors.disabled && ul_colors.has_colors && seq)
+ fputs(seq, f);
+}
+
+/*
+ * Returns escape sequence by logical @name, if undefined then returns @dflt.
+ */
+const char *color_scheme_get_sequence(const char *name, const char *dflt)
+{
+ struct ul_color_scheme *cs;
+
+ if (ul_colors.disabled || !ul_colors.has_colors)
+ return NULL;
+
+ cs = colors_get_scheme(&ul_colors, name);
+ return cs && cs->seq ? cs->seq : dflt;
+}
+
+/*
+ * Enable color by logical @name, if undefined enable @dflt.
+ */
+void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
+{
+ const char *seq = color_scheme_get_sequence(name, dflt);
+
+ if (!seq)
+ return;
+ color_fenable(seq, f);
+}
+
+
+/*
+ * Disable previously enabled color
+ */
+void color_fdisable(FILE *f)
+{
+ if (!ul_colors.disabled && ul_colors.has_colors)
+ fputs(UL_COLOR_RESET, f);
+}
+
+/*
+ * Parses @str to return UL_COLORMODE_*
+ */
+int colormode_from_string(const char *str)
+{
+ size_t i;
+ static const char *modes[] = {
+ [UL_COLORMODE_AUTO] = "auto",
+ [UL_COLORMODE_NEVER] = "never",
+ [UL_COLORMODE_ALWAYS] = "always",
+ [UL_COLORMODE_UNDEF] = ""
+ };
+
+ if (!str || !*str)
+ return -EINVAL;
+
+ assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ if (strcasecmp(str, modes[i]) == 0)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Parses @str and exit(EXIT_FAILURE) on error
+ */
+int colormode_or_err(const char *str, const char *errmsg)
+{
+ const char *p = str && *str == '=' ? str + 1 : str;
+ int colormode;
+
+ colormode = colormode_from_string(p);
+ if (colormode < 0)
+ errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);
+
+ return colormode;
+}
+
+#ifdef TEST_PROGRAM_COLORS
+# include <getopt.h>
+int main(int argc, char *argv[])
+{
+ static const struct option longopts[] = {
+ { "mode", required_argument, NULL, 'm' },
+ { "color", required_argument, NULL, 'c' },
+ { "color-scheme", required_argument, NULL, 'C' },
+ { "name", required_argument, NULL, 'n' },
+ { NULL, 0, NULL, 0 }
+ };
+ int c, mode = UL_COLORMODE_UNDEF; /* default */
+ const char *color = "red", *name = NULL, *color_scheme = NULL;
+ const char *seq = NULL;
+
+ while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ color = optarg;
+ break;
+ case 'C':
+ color_scheme = optarg;
+ break;
+ case 'm':
+ mode = colormode_or_err(optarg, "unsupported color mode");
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ default:
+ fprintf(stderr, "usage: %s [options]\n"
+ " -m, --mode <auto|never|always> default is undefined\n"
+ " -c, --color <red|blue|...> color for the test message\n"
+ " -C, --color-scheme <name> color for the test message\n"
+ " -n, --name <utilname> util name\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+ }
+
+ colors_init(mode, name ? name : program_invocation_short_name);
+
+ seq = color_sequence_from_colorname(color);
+
+ if (color_scheme)
+ color_scheme_enable(color_scheme, seq);
+ else
+ color_enable(seq);
+ printf("Hello World!");
+ color_disable();
+ fputc('\n', stdout);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_COLORS */
+
diff --git a/lib/cpuset.c b/lib/cpuset.c
new file mode 100644
index 0000000..9c3284c
--- /dev/null
+++ b/lib/cpuset.c
@@ -0,0 +1,415 @@
+/*
+ * Terminology:
+ *
+ * cpuset - (libc) cpu_set_t data structure represents set of CPUs
+ * cpumask - string with hex mask (e.g. "0x00000001")
+ * cpulist - string with CPU ranges (e.g. "0-3,5,7,8")
+ *
+ * Based on code from taskset.c and Linux kernel.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
+
+#include "cpuset.h"
+#include "c.h"
+
+static inline int val_to_char(int v)
+{
+ if (v >= 0 && v < 10)
+ return '0' + v;
+ if (v >= 10 && v < 16)
+ return ('a' - 10) + v;
+ return -1;
+}
+
+static inline int char_to_val(int c)
+{
+ int cl;
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ cl = tolower(c);
+ if (cl >= 'a' && cl <= 'f')
+ return cl + (10 - 'a');
+ return -1;
+}
+
+static const char *nexttoken(const char *q, int sep)
+{
+ if (q)
+ q = strchr(q, sep);
+ if (q)
+ q++;
+ return q;
+}
+
+/*
+ * Number of bits in a CPU bitmask on current system
+ */
+int get_max_number_of_cpus(void)
+{
+#ifdef SYS_sched_getaffinity
+ int n, cpus = 2048;
+ size_t setsize;
+ cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL);
+
+ if (!set)
+ return -1; /* error */
+
+ for (;;) {
+ CPU_ZERO_S(setsize, set);
+
+ /* the library version does not return size of cpumask_t */
+ n = syscall(SYS_sched_getaffinity, 0, setsize, set);
+
+ if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) {
+ cpuset_free(set);
+ cpus *= 2;
+ set = cpuset_alloc(cpus, &setsize, NULL);
+ if (!set)
+ return -1; /* error */
+ continue;
+ }
+ cpuset_free(set);
+ return n * 8;
+ }
+#endif
+ return -1;
+}
+
+/*
+ * Allocates a new set for ncpus and returns size in bytes and size in bits
+ */
+cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits)
+{
+ cpu_set_t *set = CPU_ALLOC(ncpus);
+
+ if (!set)
+ return NULL;
+ if (setsize)
+ *setsize = CPU_ALLOC_SIZE(ncpus);
+ if (nbits)
+ *nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus));
+ return set;
+}
+
+void cpuset_free(cpu_set_t *set)
+{
+ CPU_FREE(set);
+}
+
+#if !HAVE_DECL_CPU_ALLOC
+/* Please, use CPU_COUNT_S() macro. This is fallback */
+int __cpuset_count_s(size_t setsize, const cpu_set_t *set)
+{
+ int s = 0;
+ const __cpu_mask *p = set->__bits;
+ const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)];
+
+ while (p < end) {
+ __cpu_mask l = *p++;
+
+ if (l == 0)
+ continue;
+# if LONG_BIT > 32
+ l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul);
+ l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul);
+ l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful);
+ l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful);
+ l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful);
+ l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful);
+# else
+ l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul);
+ l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul);
+ l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful);
+ l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful);
+ l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful);
+# endif
+ s += l;
+ }
+ return s;
+}
+#endif
+
+/*
+ * Returns human readable representation of the cpuset. The output format is
+ * a list of CPUs with ranges (for example, "0,1,3-9").
+ */
+char *cpulist_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ size_t i;
+ char *ptr = str;
+ int entry_made = 0;
+ size_t max = cpuset_nbits(setsize);
+
+ for (i = 0; i < max; i++) {
+ if (CPU_ISSET_S(i, setsize, set)) {
+ int rlen;
+ size_t j, run = 0;
+ entry_made = 1;
+ for (j = i + 1; j < max; j++) {
+ if (CPU_ISSET_S(j, setsize, set))
+ run++;
+ else
+ break;
+ }
+ if (!run)
+ rlen = snprintf(ptr, len, "%zu,", i);
+ else if (run == 1) {
+ rlen = snprintf(ptr, len, "%zu,%zu,", i, i + 1);
+ i++;
+ } else {
+ rlen = snprintf(ptr, len, "%zu-%zu,", i, i + run);
+ i += run;
+ }
+ if (rlen < 0 || (size_t) rlen >= len)
+ return NULL;
+ ptr += rlen;
+ len -= rlen;
+ }
+ }
+ ptr -= entry_made;
+ *ptr = '\0';
+
+ return str;
+}
+
+/*
+ * Returns string with CPU mask.
+ */
+char *cpumask_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ char *ptr = str;
+ char *ret = NULL;
+ int cpu;
+
+ for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) {
+ char val = 0;
+
+ if (len == (size_t) (ptr - str))
+ break;
+
+ if (CPU_ISSET_S(cpu, setsize, set))
+ val |= 1;
+ if (CPU_ISSET_S(cpu + 1, setsize, set))
+ val |= 2;
+ if (CPU_ISSET_S(cpu + 2, setsize, set))
+ val |= 4;
+ if (CPU_ISSET_S(cpu + 3, setsize, set))
+ val |= 8;
+
+ if (!ret && val)
+ ret = ptr;
+ *ptr++ = val_to_char(val);
+ }
+ *ptr = '\0';
+ return ret ? ret : ptr - 1;
+}
+
+/*
+ * Parses string with CPUs mask.
+ */
+int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize)
+{
+ int len = strlen(str);
+ const char *ptr = str + len - 1;
+ int cpu = 0;
+
+ /* skip 0x, it's all hex anyway */
+ if (len > 1 && !memcmp(str, "0x", 2L))
+ str += 2;
+
+ CPU_ZERO_S(setsize, set);
+
+ while (ptr >= str) {
+ char val;
+
+ /* cpu masks in /sys uses comma as a separator */
+ if (*ptr == ',')
+ ptr--;
+
+ val = char_to_val(*ptr);
+ if (val == (char) -1)
+ return -1;
+ if (val & 1)
+ CPU_SET_S(cpu, setsize, set);
+ if (val & 2)
+ CPU_SET_S(cpu + 1, setsize, set);
+ if (val & 4)
+ CPU_SET_S(cpu + 2, setsize, set);
+ if (val & 8)
+ CPU_SET_S(cpu + 3, setsize, set);
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+static int nextnumber(const char *str, char **end, unsigned int *result)
+{
+ errno = 0;
+ if (str == NULL || *str == '\0' || !isdigit(*str))
+ return -EINVAL;
+ *result = (unsigned int) strtoul(str, end, 10);
+ if (errno)
+ return -errno;
+ if (str == *end)
+ return -EINVAL;
+ return 0;
+}
+
+/*
+ * Parses string with list of CPU ranges.
+ * Returns 0 on success.
+ * Returns 1 on error.
+ * Returns 2 if fail is set and a cpu number passed in the list doesn't fit
+ * into the cpu_set. If fail is not set cpu numbers that do not fit are
+ * ignored and 0 is returned instead.
+ */
+int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail)
+{
+ size_t max = cpuset_nbits(setsize);
+ const char *p, *q;
+ char *end = NULL;
+
+ q = str;
+ CPU_ZERO_S(setsize, set);
+
+ while (p = q, q = nexttoken(q, ','), p) {
+ unsigned int a; /* beginning of range */
+ unsigned int b; /* end of range */
+ unsigned int s; /* stride */
+ const char *c1, *c2;
+
+ if (nextnumber(p, &end, &a) != 0)
+ return 1;
+ b = a;
+ s = 1;
+ p = end;
+
+ c1 = nexttoken(p, '-');
+ c2 = nexttoken(p, ',');
+
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if (nextnumber(c1, &end, &b) != 0)
+ return 1;
+
+ c1 = end && *end ? nexttoken(end, ':') : NULL;
+
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if (nextnumber(c1, &end, &s) != 0)
+ return 1;
+ if (s == 0)
+ return 1;
+ }
+ }
+
+ if (!(a <= b))
+ return 1;
+ while (a <= b) {
+ if (fail && (a >= max))
+ return 2;
+ CPU_SET_S(a, setsize, set);
+ a += s;
+ }
+ }
+
+ if (end && *end)
+ return 1;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_CPUSET
+
+#include <getopt.h>
+
+int main(int argc, char *argv[])
+{
+ cpu_set_t *set;
+ size_t setsize, buflen, nbits;
+ char *buf, *mask = NULL, *range = NULL;
+ int ncpus = 2048, rc, c;
+
+ static const struct option longopts[] = {
+ { "ncpus", 1, NULL, 'n' },
+ { "mask", 1, NULL, 'm' },
+ { "range", 1, NULL, 'r' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'n':
+ ncpus = atoi(optarg);
+ break;
+ case 'm':
+ mask = strdup(optarg);
+ break;
+ case 'r':
+ range = strdup(optarg);
+ break;
+ default:
+ goto usage_err;
+ }
+ }
+
+ if (!mask && !range)
+ goto usage_err;
+
+ set = cpuset_alloc(ncpus, &setsize, &nbits);
+ if (!set)
+ err(EXIT_FAILURE, "failed to allocate cpu set");
+
+ /*
+ fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n",
+ ncpus, nbits, setsize);
+ */
+
+ buflen = 7 * nbits;
+ buf = malloc(buflen);
+ if (!buf)
+ err(EXIT_FAILURE, "failed to allocate cpu set buffer");
+
+ if (mask)
+ rc = cpumask_parse(mask, set, setsize);
+ else
+ rc = cpulist_parse(range, set, setsize, 0);
+
+ if (rc)
+ errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range);
+
+ printf("%-15s = %15s ", mask ? : range,
+ cpumask_create(buf, buflen, set, setsize));
+ printf("[%s]\n", cpulist_create(buf, buflen, set, setsize));
+
+ free(buf);
+ free(mask);
+ free(range);
+ cpuset_free(set);
+
+ return EXIT_SUCCESS;
+
+usage_err:
+ fprintf(stderr,
+ "usage: %s [--ncpus <num>] --mask <mask> | --range <list>\n",
+ program_invocation_short_name);
+ exit(EXIT_FAILURE);
+}
+#endif
diff --git a/lib/crc32.c b/lib/crc32.c
new file mode 100644
index 0000000..824693d
--- /dev/null
+++ b/lib/crc32.c
@@ -0,0 +1,142 @@
+/*
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ *
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1.
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way,
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to high-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly.
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera-
+ * tions for all combinations of data and CRC register values.
+ *
+ * The values must be right-shifted by eight bits by the "updcrc"
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions.
+ * polynomial $edb88320
+ *
+ */
+
+#include <stdio.h>
+
+#include "crc32.h"
+
+
+static const uint32_t crc32_tab[] = {
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+};
+
+static inline uint32_t crc32_add_char(uint32_t crc, unsigned char c)
+{
+ return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8);
+}
+
+/*
+ * This a generic crc32() function, it takes seed as an argument,
+ * and does __not__ xor at the end. Then individual users can do
+ * whatever they need.
+ */
+uint32_t ul_crc32(uint32_t seed, const unsigned char *buf, size_t len)
+{
+ uint32_t crc = seed;
+ const unsigned char *p = buf;
+
+ while (len) {
+ crc = crc32_add_char(crc, *p++);
+ len--;
+ }
+
+ return crc;
+}
+
+uint32_t ul_crc32_exclude_offset(uint32_t seed, const unsigned char *buf, size_t len,
+ size_t exclude_off, size_t exclude_len)
+{
+ uint32_t crc = seed;
+ const unsigned char *p = buf;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ unsigned char x = *p++;
+
+ if (i >= exclude_off && i < exclude_off + exclude_len)
+ x = 0;
+
+ crc = crc32_add_char(crc, x);
+ }
+
+ return crc;
+}
+
diff --git a/lib/crc32c.c b/lib/crc32c.c
new file mode 100644
index 0000000..49e7543
--- /dev/null
+++ b/lib/crc32c.c
@@ -0,0 +1,102 @@
+/*
+ * This code is from freebsd/sys/libkern/crc32.c
+ *
+ * Simplest table-based crc32c. Performance is not important
+ * for checking crcs on superblocks
+ */
+
+/*-
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ */
+
+#include "crc32c.h"
+
+static const uint32_t crc32Table[256] = {
+ 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L,
+ 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL,
+ 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL,
+ 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L,
+ 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL,
+ 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L,
+ 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L,
+ 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL,
+ 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL,
+ 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L,
+ 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L,
+ 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL,
+ 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L,
+ 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL,
+ 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL,
+ 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L,
+ 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L,
+ 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L,
+ 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L,
+ 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L,
+ 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L,
+ 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L,
+ 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L,
+ 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L,
+ 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L,
+ 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L,
+ 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L,
+ 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L,
+ 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L,
+ 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L,
+ 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L,
+ 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L,
+ 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL,
+ 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L,
+ 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L,
+ 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL,
+ 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L,
+ 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL,
+ 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL,
+ 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L,
+ 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L,
+ 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL,
+ 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL,
+ 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L,
+ 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL,
+ 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L,
+ 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L,
+ 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL,
+ 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L,
+ 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL,
+ 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL,
+ 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L,
+ 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL,
+ 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L,
+ 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L,
+ 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL,
+ 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL,
+ 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L,
+ 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L,
+ 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL,
+ 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L,
+ 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL,
+ 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL,
+ 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L
+};
+
+/*
+ *This was singletable_crc32c() in bsd
+ *
+ * If you will not be passing crc back into this function to process more bytes,
+ * the answer is:
+ *
+ * crc = crc32c(~0L, buf, size);
+ * [ crc = crc32c(crc, buf, size); ]
+ * crc ^= ~0L
+ *
+ */
+uint32_t
+crc32c(uint32_t crc, const void *buf, size_t size)
+{
+ const uint8_t *p = buf;
+
+ while (size--)
+ crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+
+ return crc;
+}
diff --git a/lib/encode.c b/lib/encode.c
new file mode 100644
index 0000000..10b5971
--- /dev/null
+++ b/lib/encode.c
@@ -0,0 +1,79 @@
+/*
+ * Based on code from libblkid,
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2020 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "c.h"
+#include "encode.h"
+
+size_t ul_encode_to_utf8(int enc, unsigned char *dest, size_t len,
+ const unsigned char *src, size_t count)
+{
+ size_t i, j;
+ uint32_t c;
+ uint16_t c2;
+
+ for (j = i = 0; i < count; i++) {
+ if (enc == UL_ENCODE_UTF16LE) {
+ if (i+2 > count)
+ break;
+ c = (src[i+1] << 8) | src[i];
+ i++;
+ } else if (enc == UL_ENCODE_UTF16BE) {
+ if (i+2 > count)
+ break;
+ c = (src[i] << 8) | src[i+1];
+ i++;
+ } else if (enc == UL_ENCODE_LATIN1) {
+ c = src[i];
+ } else {
+ return 0;
+ }
+ if ((enc == UL_ENCODE_UTF16LE || enc == UL_ENCODE_UTF16BE) &&
+ c >= 0xD800 && c <= 0xDBFF && i+2 < count) {
+ if (enc == UL_ENCODE_UTF16LE)
+ c2 = (src[i+2] << 8) | src[i+1];
+ else
+ c2 = (src[i+1] << 8) | src[i+2];
+ if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
+ c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00);
+ i += 2;
+ }
+ }
+ if (c == 0) {
+ dest[j] = '\0';
+ break;
+ }
+
+ if (c < 0x80) {
+ if (j+1 >= len)
+ break;
+ dest[j++] = (uint8_t) c;
+ } else if (c < 0x800) {
+ if (j+2 >= len)
+ break;
+ dest[j++] = (uint8_t) (0xc0 | (c >> 6));
+ dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+ } else if (c < 0x10000) {
+ if (j+3 >= len)
+ break;
+ dest[j++] = (uint8_t) (0xe0 | (c >> 12));
+ dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+ dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+ } else {
+ if (j+4 >= len)
+ break;
+ dest[j++] = (uint8_t) (0xf0 | (c >> 18));
+ dest[j++] = (uint8_t) (0x80 | ((c >> 12) & 0x3f));
+ dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+ dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+ }
+ }
+ dest[j] = '\0';
+ return j;
+}
diff --git a/lib/env.c b/lib/env.c
new file mode 100644
index 0000000..a3dd335
--- /dev/null
+++ b/lib/env.c
@@ -0,0 +1,239 @@
+/*
+ * environ[] array cleanup code and getenv() wrappers
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#else
+#define PR_GET_DUMPABLE 3
+#endif
+#if (!defined(HAVE_PRCTL) && defined(linux))
+#include <sys/syscall.h>
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "env.h"
+
+#ifndef HAVE_ENVIRON_DECL
+extern char **environ;
+#endif
+
+static char * const forbid[] = {
+ "BASH_ENV=", /* GNU creeping featurism strikes again... */
+ "ENV=",
+ "HOME=",
+ "IFS=",
+ "KRB_CONF=",
+ "LD_", /* anything with the LD_ prefix */
+ "LIBPATH=",
+ "MAIL=",
+ "NLSPATH=",
+ "PATH=",
+ "SHELL=",
+ "SHLIB_PATH=",
+ (char *) 0
+};
+
+/* these are allowed, but with no slashes inside
+ (to work around security problems in GNU gettext) */
+static char * const noslash[] = {
+ "LANG=",
+ "LANGUAGE=",
+ "LC_", /* anything with the LC_ prefix */
+ (char *) 0
+};
+
+
+struct ul_env_list {
+ char *env;
+ struct ul_env_list *next;
+};
+
+/*
+ * Saves @name env.variable to @ls, returns pointer to the new head of the list.
+ */
+static struct ul_env_list *env_list_add(struct ul_env_list *ls0, const char *str)
+{
+ struct ul_env_list *ls;
+ char *p;
+ size_t sz = 0;
+
+ if (!str || !*str)
+ return ls0;
+
+ sz = strlen(str) + 1;
+ p = malloc(sizeof(struct ul_env_list) + sz);
+ if (!p)
+ return ls0;
+
+ ls = (struct ul_env_list *) p;
+ p += sizeof(struct ul_env_list);
+ memcpy(p, str, sz);
+ ls->env = p;
+
+ ls->next = ls0;
+ return ls;
+}
+
+/*
+ * Use setenv() for all stuff in @ls.
+ *
+ * It would be possible to use putenv(), but we want to keep @ls free()-able.
+ */
+int env_list_setenv(struct ul_env_list *ls)
+{
+ int rc = 0;
+
+ while (ls && rc == 0) {
+ if (ls->env) {
+ char *val = strchr(ls->env, '=');
+ if (!val)
+ continue;
+ *val = '\0';
+ rc = setenv(ls->env, val + 1, 0);
+ *val = '=';
+ }
+ ls = ls->next;
+ }
+ return rc;
+}
+
+void env_list_free(struct ul_env_list *ls)
+{
+ while (ls) {
+ struct ul_env_list *x = ls;
+ ls = ls->next;
+ free(x);
+ }
+}
+
+/*
+ * Removes unwanted variables from environ[]. If @org is not NULL than stores
+ * unwnated variables to the list.
+ */
+void __sanitize_env(struct ul_env_list **org)
+{
+ char **envp = environ;
+ char * const *bad;
+ char **cur;
+ int last = 0;
+
+ for (cur = envp; *cur; cur++)
+ last++;
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = forbid; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) == 0) {
+ if (org)
+ *org = env_list_add(*org, *cur);
+ last = remote_entry(envp, cur - envp, last);
+ cur--;
+ break;
+ }
+ }
+ }
+
+ for (cur = envp; *cur; cur++) {
+ for (bad = noslash; *bad; bad++) {
+ if (strncmp(*cur, *bad, strlen(*bad)) != 0)
+ continue;
+ if (!strchr(*cur, '/'))
+ continue; /* OK */
+ if (org)
+ *org = env_list_add(*org, *cur);
+ last = remote_entry(envp, cur - envp, last);
+ cur--;
+ break;
+ }
+ }
+}
+
+void sanitize_env(void)
+{
+ __sanitize_env(NULL);
+}
+
+char *safe_getenv(const char *arg)
+{
+ uid_t ruid = getuid();
+
+ if (ruid != 0 || (ruid != geteuid()) || (getgid() != getegid()))
+ return NULL;
+#ifdef HAVE_PRCTL
+ if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#else
+#if (defined(linux) && defined(SYS_prctl))
+ if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+ return NULL;
+#endif
+#endif
+#ifdef HAVE_SECURE_GETENV
+return secure_getenv(arg);
+#elif HAVE___SECURE_GETENV
+ return __secure_getenv(arg);
+#else
+ return getenv(arg);
+#endif
+}
+
+#ifdef TEST_PROGRAM
+int main(void)
+{
+ char *const *bad;
+ char copy[32];
+ char *p;
+ int retval = EXIT_SUCCESS;
+ struct ul_env_list *removed = NULL;
+
+ for (bad = forbid; *bad; bad++) {
+ strcpy(copy, *bad);
+ p = strchr(copy, '=');
+ if (p)
+ *p = '\0';
+ setenv(copy, copy, 1);
+ }
+
+ /* removed */
+ __sanitize_env(&removed);
+
+ /* check removal */
+ for (bad = forbid; *bad; bad++) {
+ strcpy(copy, *bad);
+ p = strchr(copy, '=');
+ if (p)
+ *p = '\0';
+ p = getenv(copy);
+ if (p) {
+ warnx("%s was not removed", copy);
+ retval = EXIT_FAILURE;
+ }
+ }
+
+ /* restore removed */
+ env_list_setenv(removed);
+
+ /* check restore */
+ for (bad = forbid; *bad; bad++) {
+ strcpy(copy, *bad);
+ p = strchr(copy, '=');
+ if (p)
+ *p = '\0';
+ p = getenv(copy);
+ if (!p) {
+ warnx("%s was not restored", copy);
+ retval = EXIT_FAILURE;
+ }
+ }
+
+ env_list_free(removed);
+
+ return retval;
+}
+#endif
diff --git a/lib/exec_shell.c b/lib/exec_shell.c
new file mode 100644
index 0000000..6fef6c7
--- /dev/null
+++ b/lib/exec_shell.c
@@ -0,0 +1,51 @@
+/*
+ * exec_shell() - launch a shell, else exit!
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <libgen.h>
+
+#include "nls.h"
+#include "c.h"
+#include "xalloc.h"
+
+#include "exec_shell.h"
+
+#define DEFAULT_SHELL "/bin/sh"
+
+void __attribute__((__noreturn__)) exec_shell(void)
+{
+ const char *shell = getenv("SHELL");
+ char *shellc;
+ const char *shell_basename;
+ char *arg0;
+
+ if (!shell)
+ shell = DEFAULT_SHELL;
+
+ shellc = xstrdup(shell);
+ shell_basename = basename(shellc);
+ arg0 = xmalloc(strlen(shell_basename) + 2);
+ arg0[0] = '-';
+ strcpy(arg0 + 1, shell_basename);
+
+ execl(shell, arg0, (char *)NULL);
+ errexec(shell);
+}
diff --git a/lib/fileeq.c b/lib/fileeq.c
new file mode 100644
index 0000000..d3d9aa3
--- /dev/null
+++ b/lib/fileeq.c
@@ -0,0 +1,641 @@
+/*
+ * compare files content
+ *
+ * The goal is to minimize number of data we need to read from the files and be
+ * ready to compare large set of files, it means reuse the previous data if
+ * possible. It never read entire file if not necessary.
+ *
+ * The another goal is to minimize number of open files (imagine "hardlink /"),
+ * the code can open only two files and reopen the file next time if
+ * necessary.
+ *
+ * This code supports multiple comparison methods. The very basic step which is
+ * generic for all methods is to read and compare an "intro" (a few bytes from
+ * the begging of the file). This intro buffer is always cached in 'struct
+ * ul_fileeq_data', this intro buffer is addresses as block=0. This primitive
+ * thing can reduce a lot ...
+ *
+ * The next steps depend on selected method:
+ *
+ * * memcmp method: always read data to userspace, nothing is cached, compare
+ * directly files content; fast for small sets of the small files.
+ *
+ * * Linux crypto API: zero-copy method based on sendfile(), data blocks are
+ * send to the kernel hash functions (sha1, ...), and only hash digest is read
+ * and cached in usersapce. Fast for large set of (large) files.
+ *
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com> [October 2021]
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Linux crypto */
+#ifdef HAVE_LINUX_IF_ALG_H
+# include <sys/socket.h>
+# include <linux/if_alg.h>
+# include <sys/param.h>
+# include <sys/sendfile.h>
+# define USE_HARDLINK_CRYPTOAPI 1
+#endif
+
+#include "c.h"
+#include "all-io.h"
+#include "fileeq.h"
+#include "debug.h"
+
+static UL_DEBUG_DEFINE_MASK(ulfileeq);
+UL_DEBUG_DEFINE_MASKNAMES(ulfileeq) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULFILEEQ_DEBUG_INIT (1 << 1)
+#define ULFILEEQ_DEBUG_CRYPTO (1 << 2)
+#define ULFILEEQ_DEBUG_DATA (1 << 3)
+#define ULFILEEQ_DEBUG_EQ (1 << 4)
+
+#define DBG(m, x) __UL_DBG(ulfileeq, ULFILEEQ_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulfileeq, ULFILEEQ_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulfileeq)
+#include "debugobj.h"
+
+static void ul_fileeq_init_debug(void)
+{
+ if (ulfileeq_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulfileeq, ULFILEEQ_DEBUG_, 0, ULFILEEQ_DEBUG);
+}
+
+enum {
+ UL_FILEEQ_MEMCMP,
+ UL_FILEEQ_SHA1,
+ UL_FILEEQ_SHA256,
+ UL_FILEEQ_CRC32
+};
+
+struct ul_fileeq_method {
+ int id;
+ const char *name; /* name used by applications */
+ const char *kname; /* name used by kernel crypto */
+ short digsiz;
+};
+
+static const struct ul_fileeq_method ul_eq_methods[] = {
+ [UL_FILEEQ_MEMCMP] = {
+ .id = UL_FILEEQ_MEMCMP, .name = "memcmp"
+ },
+#ifdef USE_HARDLINK_CRYPTOAPI
+ [UL_FILEEQ_SHA1] = {
+ .id = UL_FILEEQ_SHA1, .name = "sha1",
+ .digsiz = 20, .kname = "sha1"
+ },
+ [UL_FILEEQ_SHA256] = {
+ .id = UL_FILEEQ_SHA256, .name = "sha256",
+ .digsiz = 32, .kname = "sha256"
+ },
+
+ [UL_FILEEQ_CRC32] = {
+ .id = UL_FILEEQ_CRC32, .name = "crc32",
+ .digsiz = 4, .kname = "crc32c"
+ }
+#endif
+};
+
+#ifdef USE_HARDLINK_CRYPTOAPI
+static void deinit_crypto_api(struct ul_fileeq *eq)
+{
+ if (!eq)
+ return;
+
+ DBG(CRYPTO, ul_debugobj(eq, "deinit"));
+
+ if (eq->fd_cip >= 0)
+ close(eq->fd_cip);
+ if (eq->fd_api >= 0)
+ close(eq->fd_api);
+
+ eq->fd_cip = eq->fd_api = -1;
+
+}
+
+static int init_crypto_api(struct ul_fileeq *eq)
+{
+ struct sockaddr_alg sa = {
+ .salg_family = AF_ALG,
+ .salg_type = "hash",
+ };
+
+ assert(eq->method);
+ assert(eq->method->kname);
+ assert(eq->fd_api == -1);
+ assert(eq->fd_cip == -1);
+
+ DBG(CRYPTO, ul_debugobj(eq, "init [%s]", eq->method->kname));
+
+ assert(sizeof(sa.salg_name) > strlen(eq->method->kname) + 1);
+ memcpy(&sa.salg_name, eq->method->kname, strlen(eq->method->kname) + 1);
+
+ if ((eq->fd_api = socket(AF_ALG, SOCK_SEQPACKET, 0)) < 0)
+ goto fail;
+ if (bind(eq->fd_api, (struct sockaddr *) &sa, sizeof(sa)) != 0)
+ goto fail;
+ if ((eq->fd_cip = accept(eq->fd_api, NULL, 0)) < 0)
+ goto fail;
+ return 0;
+fail:
+ deinit_crypto_api(eq);
+ return -1;
+}
+#endif
+
+int ul_fileeq_init(struct ul_fileeq *eq, const char *method)
+{
+ size_t i;
+
+ ul_fileeq_init_debug();
+ DBG(EQ, ul_debugobj(eq, "init [%s]", method));
+
+ memset(eq, 0, sizeof(*eq));
+ eq->fd_api = -1;
+ eq->fd_cip = -1;
+
+ for (i = 0; i < ARRAY_SIZE(ul_eq_methods); i++) {
+ const struct ul_fileeq_method *m = &ul_eq_methods[i];
+
+ if (strcmp(m->name, method) == 0) {
+ eq->method = m;
+ break;
+ }
+ }
+
+ if (!eq->method)
+ return -1;
+#ifdef USE_HARDLINK_CRYPTOAPI
+ if (eq->method->id != UL_FILEEQ_MEMCMP
+ && init_crypto_api(eq) != 0)
+ return -1;
+#endif
+ return 0;
+}
+
+void ul_fileeq_deinit(struct ul_fileeq *eq)
+{
+ if (!eq)
+ return;
+
+ DBG(EQ, ul_debugobj(eq, "deinit"));
+#ifdef USE_HARDLINK_CRYPTOAPI
+ deinit_crypto_api(eq);
+#endif
+ free(eq->buf_a);
+ free(eq->buf_b);
+}
+
+void ul_fileeq_data_close_file(struct ul_fileeq_data *data)
+{
+ assert(data);
+
+ if (data->fd >= 0) {
+ DBG(DATA, ul_debugobj(data, "close"));
+ close(data->fd);
+ }
+ data->fd = -1;
+}
+
+void ul_fileeq_data_init(struct ul_fileeq_data *data)
+{
+ DBG(DATA, ul_debugobj(data, "init"));
+ memset(data, 0, sizeof(*data));
+ data->fd = -1;
+}
+
+void ul_fileeq_data_deinit(struct ul_fileeq_data *data)
+{
+ assert(data);
+
+ DBG(DATA, ul_debugobj(data, "deinit"));
+ free(data->blocks);
+ data->blocks = NULL;
+ data->nblocks = 0;
+ data->maxblocks = 0;
+ data->is_eof = 0;
+ data->name = NULL;
+
+ ul_fileeq_data_close_file(data);
+}
+
+int ul_fileeq_data_associated(struct ul_fileeq_data *data)
+{
+ return data->name != NULL;
+}
+
+void ul_fileeq_data_set_file(struct ul_fileeq_data *data, const char *name)
+{
+ assert(data);
+ assert(name);
+
+ DBG(DATA, ul_debugobj(data, "set file: %s", name));
+ ul_fileeq_data_init(data);
+ data->name = name;
+}
+
+size_t ul_fileeq_set_size(struct ul_fileeq *eq, uint64_t filesiz,
+ size_t readsiz, size_t memsiz)
+{
+ uint64_t nreads, maxdigs;
+ size_t digsiz;
+
+ assert(eq);
+
+ eq->filesiz = filesiz;
+
+ switch (eq->method->id) {
+ case UL_FILEEQ_MEMCMP:
+ /* align file size */
+ filesiz = (filesiz + readsiz) / readsiz * readsiz;
+ break;
+ default:
+ digsiz = eq->method->digsiz;
+ if (readsiz < digsiz)
+ readsiz = digsiz;
+ /* align file size */
+ filesiz = (filesiz + readsiz) / readsiz * readsiz;
+ /* calculate limits */
+ maxdigs = memsiz / digsiz;
+ if (maxdigs == 0)
+ maxdigs = 1;
+ nreads = filesiz / readsiz;
+ /* enlarge readsize for large files */
+ if (nreads > maxdigs)
+ readsiz = filesiz / maxdigs;
+ break;
+ }
+
+ eq->readsiz = readsiz;
+ eq->blocksmax = filesiz / readsiz;
+
+ DBG(EQ, ul_debugobj(eq, "set sizes: filesiz=%ju, maxblocks=%" PRIu64 ", readsiz=%zu",
+ eq->filesiz, eq->blocksmax, eq->readsiz));
+ return eq->blocksmax;
+}
+
+static unsigned char *get_buffer(struct ul_fileeq *eq)
+{
+ if (!eq->buf_a)
+ eq->buf_a = malloc(eq->readsiz);
+ if (!eq->buf_b)
+ eq->buf_b = malloc(eq->readsiz);
+
+ if (!eq->buf_a || !eq->buf_b)
+ return NULL;
+
+ if (eq->buf_last == eq->buf_b)
+ eq->buf_last = eq->buf_a;
+ else
+ eq->buf_last = eq->buf_b;
+
+ return eq->buf_last;
+}
+
+#define get_cached_nblocks(_d) \
+ ((_d)->nblocks ? (_d)->nblocks - 1 : 0)
+
+#define get_cached_offset(_e, _d) \
+ ((_d)->nblocks == 0 ? 0 : \
+ sizeof((_d)->intro) \
+ + (get_cached_nblocks(_d) * (_e)->readsiz))
+
+
+static int get_fd(struct ul_fileeq *eq, struct ul_fileeq_data *data, off_t *off)
+{
+ off_t o = get_cached_offset(eq, data);
+
+ assert(eq);
+ assert(data);
+
+
+ if (data->fd < 0) {
+ DBG(DATA, ul_debugobj(data, "open: %s", data->name));
+ data->fd = open(data->name, O_RDONLY);
+ if (data->fd < 0)
+ return data->fd;
+
+#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE)
+ ignore_result( posix_fadvise(data->fd, o, 0, POSIX_FADV_SEQUENTIAL) );
+#endif
+ if (o) {
+ DBG(DATA, ul_debugobj(data, "lseek off=%ju", (uintmax_t) o));
+ lseek(data->fd, o, SEEK_SET);
+ }
+ }
+
+ if (off)
+ *off = o;
+
+ return data->fd;
+}
+
+static void memcmp_reset(struct ul_fileeq *eq, struct ul_fileeq_data *data)
+{
+ /* only intro[] is cached */
+ if (data->nblocks)
+ data->nblocks = 1;
+ /* reset file possition */
+ if (data->fd >= 0)
+ lseek(data->fd, get_cached_offset(eq, data), SEEK_SET);
+ data->is_eof = 0;
+}
+
+static ssize_t read_block(struct ul_fileeq *eq, struct ul_fileeq_data *data,
+ size_t n, unsigned char **block)
+{
+ int fd;
+ off_t off = 0;
+ ssize_t rsz;
+
+ if (data->is_eof || n > eq->blocksmax)
+ return 0;
+
+ fd = get_fd(eq, data, &off);
+ if (fd < 0)
+ return fd;
+
+ DBG(DATA, ul_debugobj(data, " read block off=%ju", (uintmax_t) off));
+
+ *block = get_buffer(eq);
+ if (!*block)
+ return -ENOMEM;
+
+ rsz = read_all(data->fd, (char *) *block, eq->readsiz);
+ if (rsz < 0) {
+ DBG(DATA, ul_debugobj(data, " read failed"));
+ return rsz;
+ }
+ off += rsz;
+ data->nblocks++;
+
+ if (rsz == 0 || (uint64_t) off >= eq->filesiz) {
+ data->is_eof = 1;
+ ul_fileeq_data_close_file(data);
+ }
+
+ DBG(DATA, ul_debugobj(data, " read sz=%zu", rsz));
+ return rsz;
+}
+
+#ifdef USE_HARDLINK_CRYPTOAPI
+static ssize_t get_digest(struct ul_fileeq *eq, struct ul_fileeq_data *data,
+ size_t n, unsigned char **block)
+{
+ off_t off = 0;
+ ssize_t rsz;
+ size_t sz;
+ int fd;
+
+ if (n > eq->blocksmax)
+ return 0;
+
+ /* return already cached if alvalable */
+ if (n < get_cached_nblocks(data)) {
+ DBG(DATA, ul_debugobj(data, " digest cached"));
+ assert(data->blocks);
+ *block = data->blocks + (n * eq->method->digsiz);
+ return eq->method->digsiz;
+ }
+
+ if (data->is_eof) {
+ DBG(DATA, ul_debugobj(data, " file EOF"));
+ return 0;
+ }
+
+ /* read new block */
+ fd = get_fd(eq, data, &off);
+ if (fd < 0)
+ return fd;
+
+ DBG(DATA, ul_debugobj(data, " read digest off=%ju", (uintmax_t) off));
+
+ sz = eq->method->digsiz;
+
+ if (!data->blocks) {
+ DBG(DATA, ul_debugobj(data, " alloc cache %zu", eq->blocksmax * sz));
+ data->blocks = malloc(eq->blocksmax * sz);
+ if (!data->blocks)
+ return -ENOMEM;
+ }
+
+ assert(n <= eq->blocksmax);
+
+ rsz = sendfile(eq->fd_cip, data->fd, NULL, eq->readsiz);
+ DBG(DATA, ul_debugobj(data, " sent %zu [%zu wanted] to cipher", rsz, eq->readsiz));
+
+ if (rsz < 0)
+ return rsz;
+
+ off += rsz;
+
+ /* get block digest (note 1st block is data->intro */
+ *block = data->blocks + (n * eq->method->digsiz);
+ rsz = read_all(eq->fd_cip, (char *) *block, sz);
+
+ if (rsz > 0)
+ data->nblocks++;
+ if (rsz == 0 || (uint64_t) off >= eq->filesiz) {
+ data->is_eof = 1;
+ ul_fileeq_data_close_file(data);
+ }
+ DBG(DATA, ul_debugobj(data, " get %zuB digest", rsz));
+ return rsz;
+}
+#endif
+
+static ssize_t get_intro(struct ul_fileeq *eq, struct ul_fileeq_data *data,
+ unsigned char **block)
+{
+ if (data->nblocks == 0) {
+ int fd = get_fd(eq, data, NULL);
+ ssize_t rsz;
+
+ if (fd < 0)
+ return -1;
+ rsz = read_all(fd, (char *) data->intro, sizeof(data->intro));
+ DBG(DATA, ul_debugobj(data, " read %zu bytes intro", sizeof(data->intro)));
+ if (rsz <= 0)
+ return -1;
+ data->nblocks = 1;
+ }
+
+ DBG(DATA, ul_debugobj(data, " return intro"));
+ *block = data->intro;
+ return sizeof(data->intro);
+}
+
+static ssize_t get_cmp_data(struct ul_fileeq *eq, struct ul_fileeq_data *data,
+ size_t blockno, unsigned char **block)
+{
+ if (blockno == 0)
+ return get_intro(eq, data, block);
+
+ blockno--;
+
+ switch (eq->method->id) {
+ case UL_FILEEQ_MEMCMP:
+ return read_block(eq, data, blockno, block);
+ default:
+ break;
+ }
+#ifdef USE_HARDLINK_CRYPTOAPI
+ return get_digest(eq, data, blockno, block);
+#else
+ return -1;
+#endif
+}
+
+#define CMP(a, b) ((a) > (b) ? 1 : ((a) < (b) ? -1 : 0))
+
+int ul_fileeq(struct ul_fileeq *eq,
+ struct ul_fileeq_data *a, struct ul_fileeq_data *b)
+{
+ int cmp;
+ size_t n = 0;
+
+ DBG(EQ, ul_debugobj(eq, "--> compare %s %s", a->name, b->name));
+
+ if (eq->method->id == UL_FILEEQ_MEMCMP) {
+ memcmp_reset(eq, a);
+ memcmp_reset(eq, b);
+ }
+
+ do {
+ unsigned char *da, *db;
+ ssize_t ca, cb;
+
+ DBG(EQ, ul_debugobj(eq, "compare block #%zu", n));
+
+ ca = get_cmp_data(eq, a, n, &da);
+ if (ca < 0)
+ goto done;
+ cb = get_cmp_data(eq, b, n, &db);
+ if (cb < 0)
+ goto done;
+ if (ca != cb || ca == 0) {
+ cmp = CMP(ca, cb);
+ break;
+
+ }
+ cmp = memcmp(da, db, ca);
+ DBG(EQ, ul_debugobj(eq, "#%zu=%s", n, cmp == 0 ? "match" : "not-match"));
+ n++;
+ } while (cmp == 0);
+
+ if (cmp == 0) {
+ if (!a->is_eof || !b->is_eof)
+ goto done; /* filesize chnaged? */
+
+ DBG(EQ, ul_debugobj(eq, "<-- MATCH"));
+ return 1;
+ }
+done:
+ DBG(EQ, ul_debugobj(eq, " <-- NOT-MATCH"));
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_FILEEQ
+# include <getopt.h>
+# include <err.h>
+
+int main(int argc, char *argv[])
+{
+ struct ul_fileeq eq;
+ struct ul_fileeq_data a, b, c;
+ const char *method = "sha1";
+ static const struct option longopts[] = {
+ { "method", required_argument, NULL, 'm' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+ int ch, rc;
+ const char *file_a = NULL, *file_b = NULL, *file_c = NULL;
+ struct stat st_a, st_b, st_c;
+
+ while ((ch = getopt_long(argc, argv, "m:", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'm':
+ method = optarg;
+ break;
+ case 'h':
+ printf("usage: %s [options] <file> <file>\n"
+ " -m, --method <memcmp|sha1|crc32> compare method\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (optind < argc)
+ file_a = argv[optind++];
+ if (optind < argc)
+ file_b = argv[optind++];
+ if (optind < argc)
+ file_c = argv[optind++];
+
+ if (!file_a || !file_b)
+ errx(EXIT_FAILURE, "no files specified, see --help");
+
+ if (stat(file_a, &st_a) != 0 || !S_ISREG(st_a.st_mode))
+ errx(EXIT_FAILURE, "%s: wrong file", file_a);
+ if (stat(file_b, &st_b) != 0 || !S_ISREG(st_b.st_mode))
+ errx(EXIT_FAILURE, "%s: wrong file", file_b);
+ if (file_c && (stat(file_c, &st_c) != 0 || !S_ISREG(st_c.st_mode)))
+ errx(EXIT_FAILURE, "%s: wrong file", file_c);
+
+
+ if (st_a.st_size != st_b.st_size ||
+ (file_c && st_a.st_size != st_c.st_size))
+ errx(EXIT_FAILURE, "size of the files does not match");
+
+
+ rc = ul_fileeq_init(&eq, method);
+ if (rc != 0 && strcmp(method, "memcmp") != 0) {
+ method = "memcmp";
+ rc = ul_fileeq_init(&eq, method);
+ }
+ if (rc < 0)
+ err(EXIT_FAILURE, "failed to initialize files comparior");
+
+ ul_fileeq_data_set_file(&a, file_a);
+ ul_fileeq_data_set_file(&b, file_b);
+
+ /* 3rd is optional */
+ if (file_c)
+ ul_fileeq_data_set_file(&c, file_c);
+
+ /* filesiz, readsiz, memsiz */
+ ul_fileeq_set_size(&eq, st_a.st_size, 1024*1024, 4*1024);
+
+ rc = ul_fileeq(&eq, &a, &b);
+
+ printf("1st vs. 2nd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH");
+ if (file_c) {
+ rc = ul_fileeq(&eq, &a, &c);
+ printf("1st vs. 3rd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH");
+
+ rc = ul_fileeq(&eq, &b, &c);
+ printf("2st vs. 3rd: %s\n", rc == 1 ? "MATCH" : "NOT-MATCH");
+ }
+
+ ul_fileeq_data_deinit(&a);
+ ul_fileeq_data_deinit(&b);
+
+ if (file_c)
+ ul_fileeq_data_deinit(&c);
+
+ ul_fileeq_deinit(&eq);
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_FILEEQ */
diff --git a/lib/fileutils.c b/lib/fileutils.c
new file mode 100644
index 0000000..7779e10
--- /dev/null
+++ b/lib/fileutils.c
@@ -0,0 +1,313 @@
+/*
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi>
+ * Copyright (C) 2012-2020 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <string.h>
+
+#include "c.h"
+#include "all-io.h"
+#include "fileutils.h"
+#include "pathnames.h"
+
+int mkstemp_cloexec(char *template)
+{
+#ifdef HAVE_MKOSTEMP
+ return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+#else
+ int fd, old_flags, errno_save;
+
+ fd = mkstemp(template);
+ if (fd < 0)
+ return fd;
+
+ old_flags = fcntl(fd, F_GETFD, 0);
+ if (old_flags < 0)
+ goto unwind;
+ if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0)
+ goto unwind;
+
+ return fd;
+
+unwind:
+ errno_save = errno;
+ unlink(template);
+ close(fd);
+ errno = errno_save;
+
+ return -1;
+#endif
+}
+
+/* Create open temporary file in safe way. Please notice that the
+ * file permissions are -rw------- by default. */
+int xmkstemp(char **tmpname, const char *dir, const char *prefix)
+{
+ char *localtmp;
+ const char *tmpenv;
+ mode_t old_mode;
+ int fd, rc;
+
+ /* Some use cases must be capable of being moved atomically
+ * with rename(2), which is the reason why dir is here. */
+ tmpenv = dir ? dir : getenv("TMPDIR");
+ if (!tmpenv)
+ tmpenv = _PATH_TMP;
+
+ rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
+ if (rc < 0)
+ return -1;
+
+ old_mode = umask(077);
+ fd = mkstemp_cloexec(localtmp);
+ umask(old_mode);
+ if (fd == -1) {
+ free(localtmp);
+ localtmp = NULL;
+ }
+ *tmpname = localtmp;
+ return fd;
+}
+
+#ifdef F_DUPFD_CLOEXEC
+int dup_fd_cloexec(int oldfd, int lowfd)
+#else
+int dup_fd_cloexec(int oldfd, int lowfd __attribute__((__unused__)))
+#endif
+{
+ int fd, flags, errno_save;
+
+#ifdef F_DUPFD_CLOEXEC
+ fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd);
+ if (fd >= 0)
+ return fd;
+#endif
+
+ fd = dup(oldfd);
+ if (fd < 0)
+ return fd;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ goto unwind;
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+ goto unwind;
+
+ return fd;
+
+unwind:
+ errno_save = errno;
+ close(fd);
+ errno = errno_save;
+
+ return -1;
+}
+
+/*
+ * portable getdtablesize()
+ */
+unsigned int get_fd_tabsize(void)
+{
+ int m;
+
+#if defined(HAVE_GETDTABLESIZE)
+ m = getdtablesize();
+#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+ struct rlimit rl;
+
+ getrlimit(RLIMIT_NOFILE, &rl);
+ m = rl.rlim_cur;
+#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+ m = sysconf(_SC_OPEN_MAX);
+#else
+ m = OPEN_MAX;
+#endif
+ return m;
+}
+
+void ul_close_all_fds(unsigned int first, unsigned int last)
+{
+ struct dirent *d;
+ DIR *dir;
+
+ dir = opendir(_PATH_PROC_FDDIR);
+ if (dir) {
+ while ((d = xreaddir(dir))) {
+ char *end;
+ unsigned int fd;
+ int dfd;
+
+ errno = 0;
+ fd = strtoul(d->d_name, &end, 10);
+
+ if (errno || end == d->d_name || !end || *end)
+ continue;
+ dfd = dirfd(dir);
+ if (dfd < 0)
+ continue;
+ if ((unsigned int)dfd == fd)
+ continue;
+ if (fd < first || last < fd)
+ continue;
+ close(fd);
+ }
+ closedir(dir);
+ } else {
+ unsigned fd, tbsz = get_fd_tabsize();
+
+ for (fd = 0; fd < tbsz; fd++) {
+ if (first <= fd && fd <= last)
+ close(fd);
+ }
+ }
+}
+
+#ifdef TEST_PROGRAM_FILEUTILS
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file}", argv[0]);
+
+ if (strcmp(argv[1], "--mkstemp") == 0) {
+ FILE *f;
+ char *tmpname = NULL;
+
+ f = xfmkstemp(&tmpname, NULL, "test");
+ unlink(tmpname);
+ free(tmpname);
+ fclose(f);
+
+ } else if (strcmp(argv[1], "--close-fds") == 0) {
+ ignore_result( dup(STDIN_FILENO) );
+ ignore_result( dup(STDIN_FILENO) );
+ ignore_result( dup(STDIN_FILENO) );
+
+# ifdef HAVE_CLOSE_RANGE
+ if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0)
+# endif
+ ul_close_all_fds(STDERR_FILENO + 1, ~0U);
+
+ } else if (strcmp(argv[1], "--copy-file") == 0) {
+ int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO);
+ if (ret == UL_COPY_READ_ERROR)
+ err(EXIT_FAILURE, "read");
+ else if (ret == UL_COPY_WRITE_ERROR)
+ err(EXIT_FAILURE, "write");
+ }
+ return EXIT_SUCCESS;
+}
+#endif
+
+
+int ul_mkdir_p(const char *path, mode_t mode)
+{
+ char *p, *dir;
+ int rc = 0;
+
+ if (!path || !*path)
+ return -EINVAL;
+
+ dir = p = strdup(path);
+ if (!dir)
+ return -ENOMEM;
+
+ if (*p == '/')
+ p++;
+
+ while (p && *p) {
+ char *e = strchr(p, '/');
+ if (e)
+ *e = '\0';
+ if (*p) {
+ rc = mkdir(dir, mode);
+ if (rc && errno != EEXIST)
+ break;
+ rc = 0;
+ }
+ if (!e)
+ break;
+ *e = '/';
+ p = e + 1;
+ }
+
+ free(dir);
+ return rc;
+}
+
+/* returns basename and keeps dirname in the @path, if @path is "/" (root)
+ * then returns empty string */
+char *stripoff_last_component(char *path)
+{
+ char *p = path ? strrchr(path, '/') : NULL;
+
+ if (!p)
+ return NULL;
+ *p = '\0';
+ return p + 1;
+}
+
+static int copy_file_simple(int from, int to)
+{
+ ssize_t nr;
+ char buf[BUFSIZ];
+
+ while ((nr = read_all(from, buf, sizeof(buf))) > 0)
+ if (write_all(to, buf, nr) == -1)
+ return UL_COPY_WRITE_ERROR;
+ if (nr < 0)
+ return UL_COPY_READ_ERROR;
+#ifdef HAVE_EXPLICIT_BZERO
+ explicit_bzero(buf, sizeof(buf));
+#endif
+ return 0;
+}
+
+/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */
+int ul_copy_file(int from, int to)
+{
+#ifdef HAVE_SENDFILE
+ struct stat st;
+ ssize_t nw;
+
+ if (fstat(from, &st) == -1)
+ return UL_COPY_READ_ERROR;
+ if (!S_ISREG(st.st_mode))
+ return copy_file_simple(from, to);
+ if (sendfile_all(to, from, NULL, st.st_size) < 0)
+ return copy_file_simple(from, to);
+ /* ensure we either get an EOF or an error */
+ while ((nw = sendfile_all(to, from, NULL, 16*1024*1024)) != 0)
+ if (nw < 0)
+ return copy_file_simple(from, to);
+ return 0;
+#else
+ return copy_file_simple(from, to);
+#endif
+}
+
+int ul_reopen(int fd, int flags)
+{
+ ssize_t ssz;
+ char buf[PATH_MAX];
+ char fdpath[ sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX)) ];
+
+ snprintf(fdpath, sizeof(fdpath), _PATH_PROC_FDDIR "/%d", fd);
+
+ ssz = readlink(fdpath, buf, sizeof(buf) - 1);
+ if (ssz < 0)
+ return -errno;
+
+ assert(ssz > 0);
+
+ buf[ssz] = '\0';
+
+ return open(buf, flags);
+}
diff --git a/lib/idcache.c b/lib/idcache.c
new file mode 100644
index 0000000..5550223
--- /dev/null
+++ b/lib/idcache.c
@@ -0,0 +1,117 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <wchar.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+
+#include "c.h"
+#include "idcache.h"
+
+struct identry *get_id(struct idcache *ic, unsigned long int id)
+{
+ struct identry *ent;
+
+ if (!ic)
+ return NULL;
+
+ for (ent = ic->ent; ent; ent = ent->next) {
+ if (ent->id == id)
+ return ent;
+ }
+
+ return NULL;
+}
+
+struct idcache *new_idcache(void)
+{
+ return calloc(1, sizeof(struct idcache));
+}
+
+void free_idcache(struct idcache *ic)
+{
+ struct identry *ent = ic->ent;
+
+ while (ent) {
+ struct identry *next = ent->next;
+ free(ent->name);
+ free(ent);
+ ent = next;
+ }
+
+ free(ic);
+}
+
+static void add_id(struct idcache *ic, char *name, unsigned long int id)
+{
+ struct identry *ent, *x;
+ int w = 0;
+
+ ent = calloc(1, sizeof(struct identry));
+ if (!ent)
+ return;
+ ent->id = id;
+
+ if (name) {
+#ifdef HAVE_WIDECHAR
+ wchar_t wc[LOGIN_NAME_MAX + 1];
+
+ if (mbstowcs(wc, name, LOGIN_NAME_MAX) > 0) {
+ wc[LOGIN_NAME_MAX] = '\0';
+ w = wcswidth(wc, LOGIN_NAME_MAX);
+ }
+ else
+#endif
+ w = strlen(name);
+ }
+
+ /* note, we ignore names with non-printable widechars */
+ if (w > 0) {
+ ent->name = strdup(name);
+ if (!ent->name) {
+ free(ent);
+ return;
+ }
+ } else {
+ if (asprintf(&ent->name, "%lu", id) < 0) {
+ free(ent);
+ return;
+ }
+ }
+
+ for (x = ic->ent; x && x->next; x = x->next);
+
+ if (x)
+ x->next = ent;
+ else
+ ic->ent = ent;
+
+ if (w <= 0)
+ w = ent->name ? strlen(ent->name) : 0;
+ ic->width = ic->width < w ? w : ic->width;
+}
+
+void add_uid(struct idcache *cache, unsigned long int id)
+{
+ struct identry *ent= get_id(cache, id);
+
+ if (!ent) {
+ struct passwd *pw = getpwuid((uid_t) id);
+ add_id(cache, pw ? pw->pw_name : NULL, id);
+ }
+}
+
+void add_gid(struct idcache *cache, unsigned long int id)
+{
+ struct identry *ent = get_id(cache, id);
+
+ if (!ent) {
+ struct group *gr = getgrgid((gid_t) id);
+ add_id(cache, gr ? gr->gr_name : NULL, id);
+ }
+}
+
diff --git a/lib/ismounted.c b/lib/ismounted.c
new file mode 100644
index 0000000..31be71a
--- /dev/null
+++ b/lib/ismounted.c
@@ -0,0 +1,396 @@
+/*
+ * ismounted.c --- Check to see if the filesystem was mounted
+ *
+ * Copyright (C) 1995,1996,1997,1998,1999,2000,2008 Theodore Ts'o.
+ *
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#include <string.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <sys/param.h>
+
+#ifndef __linux__
+# ifdef HAVE_SYS_UCRED_H
+# include <sys/ucred.h>
+# endif
+# ifdef HAVE_SYS_MOUNT_H
+# include <sys/mount.h>
+# endif
+#endif
+
+#include "pathnames.h"
+#include "strutils.h"
+#include "ismounted.h"
+#include "c.h"
+#ifdef __linux__
+# include "loopdev.h"
+#endif
+
+
+
+#ifdef HAVE_MNTENT_H
+/*
+ * Helper function which checks a file in /etc/mtab format to see if a
+ * filesystem is mounted. Returns an error if the file doesn't exist
+ * or can't be opened.
+ */
+static int check_mntent_file(const char *mtab_file, const char *file,
+ int *mount_flags, char *mtpt, int mtlen)
+{
+ struct mntent *mnt;
+ struct stat st_buf;
+ int retval = 0;
+ dev_t file_dev=0, file_rdev=0;
+ ino_t file_ino=0;
+ FILE *f;
+ int fd;
+
+ *mount_flags = 0;
+ if ((f = setmntent (mtab_file, "r")) == NULL)
+ return errno;
+
+ if (stat(file, &st_buf) == 0) {
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ file_rdev = st_buf.st_rdev;
+#endif /* __GNU__ */
+ } else {
+ file_dev = st_buf.st_dev;
+ file_ino = st_buf.st_ino;
+ }
+ }
+
+ while ((mnt = getmntent (f)) != NULL) {
+ if (mnt->mnt_fsname[0] != '/')
+ continue;
+ if (strcmp(file, mnt->mnt_fsname) == 0)
+ break;
+ if (stat(mnt->mnt_fsname, &st_buf) != 0)
+ continue;
+
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__
+ if (file_rdev && file_rdev == st_buf.st_rdev)
+ break;
+#ifdef __linux__
+ /* maybe the file is loopdev backing file */
+ if (file_dev
+ && major(st_buf.st_rdev) == LOOPDEV_MAJOR
+ && loopdev_is_used(mnt->mnt_fsname, file, 0, 0, 0))
+ break;
+#endif /* __linux__ */
+#endif /* __GNU__ */
+ } else {
+ if (file_dev && ((file_dev == st_buf.st_dev) &&
+ (file_ino == st_buf.st_ino)))
+ break;
+ }
+ }
+
+ if (mnt == NULL) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ /*
+ * Do an extra check to see if this is the root device. We
+ * can't trust /etc/mtab, and /proc/mounts will only list
+ * /dev/root for the root filesystem. Argh. Instead we
+ * check if the given device has the same major/minor number
+ * as the device that the root directory is on.
+ */
+ if (file_rdev && stat("/", &st_buf) == 0 &&
+ st_buf.st_dev == file_rdev) {
+ *mount_flags = MF_MOUNTED;
+ if (mtpt)
+ xstrncpy(mtpt, "/", mtlen);
+ goto is_root;
+ }
+#endif /* __GNU__ */
+ goto errout;
+ }
+#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */
+ /* Validate the entry in case /etc/mtab is out of date */
+ /*
+ * We need to be paranoid, because some broken distributions
+ * (read: Slackware) don't initialize /etc/mtab before checking
+ * all of the non-root filesystems on the disk.
+ */
+ if (stat(mnt->mnt_dir, &st_buf) < 0) {
+ retval = errno;
+ if (retval == ENOENT) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s does not exist)\n",
+ mtab_file, mnt->mnt_dir);
+#endif /* DEBUG */
+ retval = 0;
+ }
+ goto errout;
+ }
+ if (file_rdev && (st_buf.st_dev != file_rdev)) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s not mounted on %s)\n",
+ mtab_file, file, mnt->mnt_dir);
+#endif /* DEBUG */
+ goto errout;
+ }
+#endif /* __GNU__ */
+ *mount_flags = MF_MOUNTED;
+
+#ifdef MNTOPT_RO
+ /* Check to see if the ro option is set */
+ if (hasmntopt(mnt, MNTOPT_RO))
+ *mount_flags |= MF_READONLY;
+#endif
+
+ if (mtpt)
+ xstrncpy(mtpt, mnt->mnt_dir, mtlen);
+ /*
+ * Check to see if we're referring to the root filesystem.
+ * If so, do a manual check to see if we can open /etc/mtab
+ * read/write, since if the root is mounted read/only, the
+ * contents of /etc/mtab may not be accurate.
+ */
+ if (!strcmp(mnt->mnt_dir, "/")) {
+is_root:
+#define TEST_FILE "/.ismount-test-file"
+ *mount_flags |= MF_ISROOT;
+ fd = open(TEST_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
+ if (fd < 0) {
+ if (errno == EROFS)
+ *mount_flags |= MF_READONLY;
+ } else
+ close(fd);
+ (void) unlink(TEST_FILE);
+ }
+ retval = 0;
+errout:
+ endmntent (f);
+ return retval;
+}
+
+static int check_mntent(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ int retval;
+
+#ifdef DEBUG
+ retval = check_mntent_file("/tmp/mtab", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0)
+ return 0;
+#endif /* DEBUG */
+#ifdef __linux__
+ retval = check_mntent_file("/proc/mounts", file, mount_flags,
+ mtpt, mtlen);
+ if (retval == 0 && (*mount_flags != 0))
+ return 0;
+ if (access("/proc/mounts", R_OK) == 0) {
+ *mount_flags = 0;
+ return retval;
+ }
+#endif /* __linux__ */
+#if defined(MOUNTED) || defined(_PATH_MOUNTED)
+#ifndef MOUNTED
+#define MOUNTED _PATH_MOUNTED
+#endif /* MOUNTED */
+ retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen);
+ return retval;
+#else
+ *mount_flags = 0;
+ return 0;
+#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */
+}
+
+#else
+#if defined(HAVE_GETMNTINFO)
+
+static int check_getmntinfo(const char *file, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ struct statfs *mp;
+ int len, n;
+ const char *s1;
+ char *s2;
+
+ n = getmntinfo(&mp, MNT_NOWAIT);
+ if (n == 0)
+ return errno;
+
+ len = sizeof(_PATH_DEV) - 1;
+ s1 = file;
+ if (strncmp(_PATH_DEV, s1, len) == 0)
+ s1 += len;
+
+ *mount_flags = 0;
+ while (--n >= 0) {
+ s2 = mp->f_mntfromname;
+ if (strncmp(_PATH_DEV, s2, len) == 0) {
+ s2 += len - 1;
+ *s2 = 'r';
+ }
+ if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) {
+ *mount_flags = MF_MOUNTED;
+ break;
+ }
+ ++mp;
+ }
+ if (mtpt && n >= 0)
+ xstrncpy(mtpt, mp->f_mntonname, mtlen);
+ return 0;
+}
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+
+/*
+ * Check to see if we're dealing with the swap device.
+ */
+static int is_swap_device(const char *file)
+{
+ FILE *f;
+ char buf[1024], *cp;
+ dev_t file_dev;
+ struct stat st_buf;
+ int ret = 0;
+
+ file_dev = 0;
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ if ((stat(file, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode))
+ file_dev = st_buf.st_rdev;
+#endif /* __GNU__ */
+
+ if (!(f = fopen("/proc/swaps", "r" UL_CLOEXECSTR)))
+ return 0;
+ /* Skip the first line */
+ if (!fgets(buf, sizeof(buf), f))
+ goto leave;
+ if (*buf && strncmp(buf, "Filename\t", 9) != 0)
+ /* Linux <=2.6.19 contained a bug in the /proc/swaps
+ * code where the header would not be displayed
+ */
+ goto valid_first_line;
+
+ while (fgets(buf, sizeof(buf), f)) {
+valid_first_line:
+ if ((cp = strchr(buf, ' ')) != NULL)
+ *cp = 0;
+ if ((cp = strchr(buf, '\t')) != NULL)
+ *cp = 0;
+ if (strcmp(buf, file) == 0) {
+ ret++;
+ break;
+ }
+#ifndef __GNU__
+ if (file_dev && (stat(buf, &st_buf) == 0) &&
+ S_ISBLK(st_buf.st_mode) &&
+ file_dev == st_buf.st_rdev) {
+ ret++;
+ break;
+ }
+#endif /* __GNU__ */
+ }
+
+leave:
+ fclose(f);
+ return ret;
+}
+
+
+/*
+ * check_mount_point() determines if the device is mounted or otherwise
+ * busy, and fills in mount_flags with one or more of the following flags:
+ * MF_MOUNTED, MF_ISROOT, MF_READONLY, MF_SWAP, and MF_BUSY. If mtpt is
+ * non-NULL, the directory where the device is mounted is copied to where mtpt
+ * is pointing, up to mtlen characters.
+ */
+#ifdef __TURBOC__
+ #pragma argsused
+#endif
+int check_mount_point(const char *device, int *mount_flags,
+ char *mtpt, int mtlen)
+{
+ int retval = 0;
+
+ if (is_swap_device(device)) {
+ *mount_flags = MF_MOUNTED | MF_SWAP;
+ if (mtpt && mtlen)
+ xstrncpy(mtpt, "[SWAP]", mtlen);
+ } else {
+#ifdef HAVE_MNTENT_H
+ retval = check_mntent(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef HAVE_GETMNTINFO
+ retval = check_getmntinfo(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef __GNUC__
+ #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
+#endif
+ *mount_flags = 0;
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+ }
+ if (retval)
+ return retval;
+
+#ifdef __linux__ /* This only works on Linux 2.6+ systems */
+ {
+ struct stat st_buf;
+ int fd;
+ if ((stat(device, &st_buf) != 0) ||
+ !S_ISBLK(st_buf.st_mode))
+ return 0;
+ fd = open(device, O_RDONLY|O_EXCL|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ if (errno == EBUSY)
+ *mount_flags |= MF_BUSY;
+ } else
+ close(fd);
+ }
+#endif
+
+ return 0;
+}
+
+int is_mounted(const char *file)
+{
+ int retval;
+ int mount_flags = 0;
+
+ retval = check_mount_point(file, &mount_flags, NULL, 0);
+ if (retval)
+ return 0;
+ return mount_flags & MF_MOUNTED;
+}
+
+#ifdef TEST_PROGRAM_ISMOUNTED
+int main(int argc, char **argv)
+{
+ int flags = 0;
+ char devname[PATH_MAX];
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s device\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (check_mount_point(argv[1], &flags, devname, sizeof(devname)) == 0 &&
+ (flags & MF_MOUNTED)) {
+ if (flags & MF_SWAP)
+ printf("used swap device\n");
+ else
+ printf("mounted on %s\n", devname);
+ return EXIT_SUCCESS;
+ }
+
+ printf("not mounted\n");
+ return EXIT_FAILURE;
+}
+#endif /* DEBUG */
diff --git a/lib/jsonwrt.c b/lib/jsonwrt.c
new file mode 100644
index 0000000..8ca1d4d
--- /dev/null
+++ b/lib/jsonwrt.c
@@ -0,0 +1,229 @@
+/*
+ * JSON output formatting functions.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <cctype.h>
+
+#include "c.h"
+#include "jsonwrt.h"
+
+/*
+ * Requirements enumerated via testing (V8, Firefox, IE11):
+ *
+ * var charsToEscape = [];
+ * for (var i = 0; i < 65535; i += 1) {
+ * try {
+ * JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}');
+ * } catch (e) {
+ * charsToEscape.push(i);
+ * }
+ * }
+ */
+static void fputs_quoted_case_json(const char *data, FILE *out, int dir)
+{
+ const char *p;
+
+ fputc('"', out);
+ for (p = data; p && *p; p++) {
+
+ const unsigned int c = (unsigned int) *p;
+
+ /* From http://www.json.org
+ *
+ * The double-quote and backslashes would break out a string or
+ * init an escape sequence if not escaped.
+ *
+ * Note that single-quotes and forward slashes, while they're
+ * in the JSON spec, don't break double-quoted strings.
+ */
+ if (c == '"' || c == '\\') {
+ fputc('\\', out);
+ fputc(c, out);
+ continue;
+ }
+
+ /* All non-control characters OK; do the case swap as required. */
+ if (c >= 0x20) {
+ /*
+ * Don't use locale sensitive ctype.h functions for regular
+ * ASCII chars, because for example with Turkish locale
+ * (aka LANG=tr_TR.UTF-8) toupper('I') returns 'I'.
+ */
+ if (c <= 127)
+ fputc(dir == 1 ? c_toupper(c) :
+ dir == -1 ? c_tolower(c) : *p, out);
+ else
+ fputc(dir == 1 ? toupper(c) :
+ dir == -1 ? tolower(c) : *p, out);
+ continue;
+ }
+
+ /* In addition, all chars under ' ' break Node's/V8/Chrome's, and
+ * Firefox's JSON.parse function
+ */
+ switch (c) {
+ /* Handle short-hand cases to reduce output size. C
+ * has most of the same stuff here, so if there's an
+ * "Escape for C" function somewhere in the STL, we
+ * should probably be using it.
+ */
+ case '\b':
+ fputs("\\b", out);
+ break;
+ case '\t':
+ fputs("\\t", out);
+ break;
+ case '\n':
+ fputs("\\n", out);
+ break;
+ case '\f':
+ fputs("\\f", out);
+ break;
+ case '\r':
+ fputs("\\r", out);
+ break;
+ default:
+ /* Other assorted control characters */
+ fprintf(out, "\\u00%02x", c);
+ break;
+ }
+ }
+ fputc('"', out);
+}
+
+#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0)
+#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1)
+#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1)
+
+void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent)
+{
+ fmt->out = out;
+ fmt->indent = indent;
+ fmt->after_close = 0;
+}
+
+int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt)
+{
+ return fmt->out == NULL ? 0 : 1;
+}
+
+void ul_jsonwrt_indent(struct ul_jsonwrt *fmt)
+{
+ int i;
+
+ for (i = 0; i < fmt->indent; i++)
+ fputs(" ", fmt->out);
+}
+
+void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type)
+{
+ if (name) {
+ if (fmt->after_close)
+ fputs(",\n", fmt->out);
+ ul_jsonwrt_indent(fmt);
+ fputs_quoted_json_lower(name, fmt->out);
+ } else {
+ if (fmt->after_close)
+ fputs(",", fmt->out);
+ else
+ ul_jsonwrt_indent(fmt);
+ }
+
+ switch (type) {
+ case UL_JSON_OBJECT:
+ fputs(name ? ": {\n" : "{\n", fmt->out);
+ fmt->indent++;
+ break;
+ case UL_JSON_ARRAY:
+ fputs(name ? ": [\n" : "[\n", fmt->out);
+ fmt->indent++;
+ break;
+ case UL_JSON_VALUE:
+ fputs(name ? ": " : " ", fmt->out);
+ break;
+ }
+ fmt->after_close = 0;
+}
+
+void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type)
+{
+ if (fmt->indent == 1) {
+ fputs("\n}\n", fmt->out);
+ fmt->indent--;
+ fmt->after_close = 1;
+ return;
+ }
+ assert(fmt->indent > 0);
+
+ switch (type) {
+ case UL_JSON_OBJECT:
+ fmt->indent--;
+ fputc('\n', fmt->out);
+ ul_jsonwrt_indent(fmt);
+ fputs("}", fmt->out);
+ break;
+ case UL_JSON_ARRAY:
+ fmt->indent--;
+ fputc('\n', fmt->out);
+ ul_jsonwrt_indent(fmt);
+ fputs("]", fmt->out);
+ break;
+ case UL_JSON_VALUE:
+ break;
+ }
+
+ fmt->after_close = 1;
+}
+
+void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt,
+ const char *name, const char *data)
+{
+ ul_jsonwrt_value_open(fmt, name);
+ if (data && *data)
+ fputs(data, fmt->out);
+ else
+ fputs("null", fmt->out);
+ ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt,
+ const char *name, const char *data)
+{
+ ul_jsonwrt_value_open(fmt, name);
+ if (data && *data)
+ fputs_quoted_json(data, fmt->out);
+ else
+ fputs("null", fmt->out);
+ ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt,
+ const char *name, uint64_t data)
+{
+ ul_jsonwrt_value_open(fmt, name);
+ fprintf(fmt->out, "%"PRIu64, data);
+ ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt,
+ const char *name, int data)
+{
+ ul_jsonwrt_value_open(fmt, name);
+ fputs(data ? "true" : "false", fmt->out);
+ ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_null(struct ul_jsonwrt *fmt,
+ const char *name)
+{
+ ul_jsonwrt_value_open(fmt, name);
+ fputs("null", fmt->out);
+ ul_jsonwrt_value_close(fmt);
+}
diff --git a/lib/langinfo.c b/lib/langinfo.c
new file mode 100644
index 0000000..a200085
--- /dev/null
+++ b/lib/langinfo.c
@@ -0,0 +1,124 @@
+/*
+ * This is callback solution for systems without nl_langinfo(), this function
+ * returns hardcoded and on locale setting indepndent value.
+ *
+ * See langinfo.h man page for more details.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include "nls.h"
+
+char *langinfo_fallback(nl_item item)
+{
+ switch (item) {
+ case CODESET:
+ return "ISO-8859-1";
+ case THOUSEP:
+ return ",";
+ case D_T_FMT:
+ case ERA_D_T_FMT:
+ return "%a %b %e %H:%M:%S %Y";
+ case D_FMT:
+ case ERA_D_FMT:
+ return "%m/%d/%y";
+ case T_FMT:
+ case ERA_T_FMT:
+ return "%H:%M:%S";
+ case T_FMT_AMPM:
+ return "%I:%M:%S %p";
+ case AM_STR:
+ return "AM";
+ case PM_STR:
+ return "PM";
+ case DAY_1:
+ return "Sunday";
+ case DAY_2:
+ return "Monday";
+ case DAY_3:
+ return "Tuesday";
+ case DAY_4:
+ return "Wednesday";
+ case DAY_5:
+ return "Thursday";
+ case DAY_6:
+ return "Friday";
+ case DAY_7:
+ return "Saturday";
+ case ABDAY_1:
+ return "Sun";
+ case ABDAY_2:
+ return "Mon";
+ case ABDAY_3:
+ return "Tue";
+ case ABDAY_4:
+ return "Wed";
+ case ABDAY_5:
+ return "Thu";
+ case ABDAY_6:
+ return "Fri";
+ case ABDAY_7:
+ return "Sat";
+ case MON_1:
+ return "January";
+ case MON_2:
+ return "February";
+ case MON_3:
+ return "March";
+ case MON_4:
+ return "April";
+ case MON_5:
+ return "May";
+ case MON_6:
+ return "June";
+ case MON_7:
+ return "July";
+ case MON_8:
+ return "August";
+ case MON_9:
+ return "September";
+ case MON_10:
+ return "October";
+ case MON_11:
+ return "November";
+ case MON_12:
+ return "December";
+ case ABMON_1:
+ return "Jan";
+ case ABMON_2:
+ return "Feb";
+ case ABMON_3:
+ return "Mar";
+ case ABMON_4:
+ return "Apr";
+ case ABMON_5:
+ return "May";
+ case ABMON_6:
+ return "Jun";
+ case ABMON_7:
+ return "Jul";
+ case ABMON_8:
+ return "Aug";
+ case ABMON_9:
+ return "Sep";
+ case ABMON_10:
+ return "Oct";
+ case ABMON_11:
+ return "Nov";
+ case ABMON_12:
+ return "Dec";
+ case ALT_DIGITS:
+ return "\0\0\0\0\0\0\0\0\0\0";
+ case CRNCYSTR:
+ return "-";
+ case YESEXPR:
+ return "^[yY]";
+ case NOEXPR:
+ return "^[nN]";
+ default:
+ return "";
+ }
+}
+
diff --git a/lib/linux_version.c b/lib/linux_version.c
new file mode 100644
index 0000000..119869e
--- /dev/null
+++ b/lib/linux_version.c
@@ -0,0 +1,75 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#include "c.h"
+#include "linux_version.h"
+
+int get_linux_version (void)
+{
+ static int kver = -1;
+ struct utsname uts;
+ int x = 0, y = 0, z = 0;
+ int n;
+
+ if (kver != -1)
+ return kver;
+ if (uname(&uts))
+ return kver = 0;
+
+ n = sscanf(uts.release, "%d.%d.%d", &x, &y, &z);
+ if (n < 1 || n > 3)
+ return kver = 0;
+
+ return kver = KERNEL_VERSION(x, y, z);
+}
+
+#ifdef TEST_PROGRAM_LINUXVERSION
+# include <stdlib.h>
+int main(int argc, char *argv[])
+{
+ int rc = EXIT_FAILURE;
+
+ if (argc == 1) {
+ printf("Linux version: %d\n", get_linux_version());
+ rc = EXIT_SUCCESS;
+
+ } else if (argc == 5) {
+ const char *oper = argv[1];
+
+ int x = atoi(argv[2]),
+ y = atoi(argv[3]),
+ z = atoi(argv[4]);
+ int kver = get_linux_version();
+ int uver = KERNEL_VERSION(x, y, z);
+
+ if (strcmp(oper, "==") == 0)
+ rc = kver == uver;
+ else if (strcmp(oper, "<=") == 0)
+ rc = kver <= uver;
+ else if (strcmp(oper, ">=") == 0)
+ rc = kver >= uver;
+ else
+ errx(EXIT_FAILURE, "unsupported operator");
+
+ if (rc)
+ printf("match\n");
+ else
+ printf("not-match [%d %s %d, x.y.z: %d.%d.%d]\n",
+ kver, oper, uver, x, y, z);
+
+ rc = rc ? EXIT_SUCCESS : EXIT_FAILURE;
+
+ } else
+ fprintf(stderr, "Usage:\n"
+ " %s [<oper> <x> <y> <z>]\n"
+ "supported operators:\n"
+ " ==, <=, >=\n",
+ program_invocation_short_name);
+
+ return rc;
+}
+#endif
diff --git a/lib/loopdev.c b/lib/loopdev.c
new file mode 100644
index 0000000..05376dd
--- /dev/null
+++ b/lib/loopdev.c
@@ -0,0 +1,1924 @@
+
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * -- based on mount/losetup.c
+ *
+ * Simple library for work with loop devices.
+ *
+ * - requires kernel 2.6.x
+ * - reads info from /sys/block/loop<N>/loop/<attr> (new kernels)
+ * - reads info by ioctl
+ * - supports *unlimited* number of loop devices
+ * - supports /dev/loop<N> as well as /dev/loop/<N>
+ * - minimize overhead (fd, loopinfo, ... are shared for all operations)
+ * - setup (associate device and backing file)
+ * - delete (dis-associate file)
+ * - old LOOP_{SET,GET}_STATUS (32bit) ioctls are unsupported
+ * - extendible
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+#include "linux_version.h"
+#include "c.h"
+#include "sysfs.h"
+#include "pathnames.h"
+#include "loopdev.h"
+#include "canonicalize.h"
+#include "blkdev.h"
+#include "debug.h"
+#include "fileutils.h"
+
+
+#define LOOPDEV_MAX_TRIES 10
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(loopdev);
+UL_DEBUG_DEFINE_MASKNAMES(loopdev) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define LOOPDEV_DEBUG_INIT (1 << 1)
+#define LOOPDEV_DEBUG_CXT (1 << 2)
+#define LOOPDEV_DEBUG_ITER (1 << 3)
+#define LOOPDEV_DEBUG_SETUP (1 << 4)
+
+#define DBG(m, x) __UL_DBG(loopdev, LOOPDEV_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(loopdev, LOOPDEV_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(loopdev)
+#include "debugobj.h"
+
+static void loopdev_init_debug(void)
+{
+ if (loopdev_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG);
+}
+
+/*
+ * see loopcxt_init()
+ */
+#define loopcxt_ioctl_enabled(_lc) (!((_lc)->flags & LOOPDEV_FL_NOIOCTL))
+#define loopcxt_sysfs_available(_lc) (!((_lc)->flags & LOOPDEV_FL_NOSYSFS)) \
+ && !loopcxt_ioctl_enabled(_lc)
+
+/*
+ * @lc: context
+ * @device: device name, absolute device path or NULL to reset the current setting
+ *
+ * Sets device, absolute paths (e.g. "/dev/loop<N>") are unchanged, device
+ * names ("loop<N>") are converted to the path (/dev/loop<N> or to
+ * /dev/loop/<N>)
+ *
+ * This sets the device name, but does not check if the device exists!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
+{
+ if (!lc)
+ return -EINVAL;
+
+ if (lc->fd >= 0) {
+ close(lc->fd);
+ DBG(CXT, ul_debugobj(lc, "closing old open fd"));
+ }
+ lc->fd = -1;
+ lc->mode = 0;
+ lc->blocksize = 0;
+ lc->has_info = 0;
+ lc->info_failed = 0;
+ *lc->device = '\0';
+ memset(&lc->config, 0, sizeof(lc->config));
+
+ /* set new */
+ if (device) {
+ if (*device != '/') {
+ const char *dir = _PATH_DEV;
+
+ /* compose device name for /dev/loop<n> or /dev/loop/<n> */
+ if (lc->flags & LOOPDEV_FL_DEVSUBDIR) {
+ if (strlen(device) < 5)
+ return -1;
+ device += 4;
+ dir = _PATH_DEV_LOOP "/"; /* _PATH_DEV uses tailing slash */
+ }
+ snprintf(lc->device, sizeof(lc->device), "%s%s",
+ dir, device);
+ } else
+ xstrncpy(lc->device, device, sizeof(lc->device));
+
+ DBG(CXT, ul_debugobj(lc, "%s name assigned", device));
+ }
+
+ ul_unref_path(lc->sysfs);
+ lc->sysfs = NULL;
+ return 0;
+}
+
+int loopcxt_has_device(struct loopdev_cxt *lc)
+{
+ return lc && *lc->device;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPDEV_FL_* flags
+ *
+ * Initialize loop handler.
+ *
+ * We have two sets of the flags:
+ *
+ * * LOOPDEV_FL_* flags control loopcxt_* API behavior
+ *
+ * * LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls
+ *
+ * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2)
+ * syscall to open loop device. By default is the device open read-only.
+ *
+ * The exception is loopcxt_setup_device(), where the device is open read-write
+ * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_init(struct loopdev_cxt *lc, int flags)
+{
+ int rc;
+ struct stat st;
+ struct loopdev_cxt dummy = UL_LOOPDEVCXT_EMPTY;
+
+ if (!lc)
+ return -EINVAL;
+
+ loopdev_init_debug();
+ DBG(CXT, ul_debugobj(lc, "initialize context"));
+
+ memcpy(lc, &dummy, sizeof(dummy));
+ lc->flags = flags;
+
+ rc = loopcxt_set_device(lc, NULL);
+ if (rc)
+ return rc;
+
+ if (stat(_PATH_SYS_BLOCK, &st) || !S_ISDIR(st.st_mode)) {
+ lc->flags |= LOOPDEV_FL_NOSYSFS;
+ lc->flags &= ~LOOPDEV_FL_NOIOCTL;
+ DBG(CXT, ul_debugobj(lc, "init: disable /sys usage"));
+ }
+
+ if (!(lc->flags & LOOPDEV_FL_NOSYSFS) &&
+ get_linux_version() >= KERNEL_VERSION(2,6,37)) {
+ /*
+ * Use only sysfs for basic information about loop devices
+ */
+ lc->flags |= LOOPDEV_FL_NOIOCTL;
+ DBG(CXT, ul_debugobj(lc, "init: ignore ioctls"));
+ }
+
+ if (!(lc->flags & LOOPDEV_FL_CONTROL) && !stat(_PATH_DEV_LOOPCTL, &st)) {
+ lc->flags |= LOOPDEV_FL_CONTROL;
+ DBG(CXT, ul_debugobj(lc, "init: loop-control detected "));
+ }
+
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Deinitialize loop context
+ */
+void loopcxt_deinit(struct loopdev_cxt *lc)
+{
+ int errsv = errno;
+
+ if (!lc)
+ return;
+
+ DBG(CXT, ul_debugobj(lc, "de-initialize"));
+
+ free(lc->filename);
+ lc->filename = NULL;
+
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ loopcxt_deinit_iterator(lc);
+
+ errno = errsv;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns newly allocated device path.
+ */
+char *loopcxt_strdup_device(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device)
+ return NULL;
+ return strdup(lc->device);
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer device name in the @lc struct.
+ */
+const char *loopcxt_get_device(struct loopdev_cxt *lc)
+{
+ return lc && *lc->device ? lc->device : NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer to the sysfs context (see lib/sysfs.c)
+ */
+static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS))
+ return NULL;
+
+ if (!lc->sysfs) {
+ dev_t devno = sysfs_devname_to_devno(lc->device);
+ if (!devno) {
+ DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno"));
+ return NULL;
+ }
+
+ lc->sysfs = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!lc->sysfs)
+ DBG(CXT, ul_debugobj(lc, "sysfs: init failed"));
+ }
+
+ return lc->sysfs;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: file descriptor to the open loop device or <0 on error. The mode
+ * depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is
+ * read-only.
+ */
+int loopcxt_get_fd(struct loopdev_cxt *lc)
+{
+ if (!lc || !*lc->device)
+ return -EINVAL;
+
+ if (lc->fd < 0) {
+ lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY;
+ lc->fd = open(lc->device, lc->mode | O_CLOEXEC);
+ DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device,
+ lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro"));
+ }
+ return lc->fd;
+}
+
+int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->fd = fd;
+ lc->mode = mode;
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPITER_FL_* flags
+ *
+ * Iterator can be used to scan list of the free or used loop devices.
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags)
+{
+ struct loopdev_iter *iter;
+ struct stat st;
+
+ if (!lc)
+ return -EINVAL;
+
+
+ iter = &lc->iter;
+ DBG(ITER, ul_debugobj(iter, "initialize"));
+
+ /* always zeroize
+ */
+ memset(iter, 0, sizeof(*iter));
+ iter->ncur = -1;
+ iter->flags = flags;
+ iter->default_check = 1;
+
+ if (!lc->extra_check) {
+ /*
+ * Check for /dev/loop/<N> subdirectory
+ */
+ if (!(lc->flags & LOOPDEV_FL_DEVSUBDIR) &&
+ stat(_PATH_DEV_LOOP, &st) == 0 && S_ISDIR(st.st_mode))
+ lc->flags |= LOOPDEV_FL_DEVSUBDIR;
+
+ lc->extra_check = 1;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_deinit_iterator(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter;
+
+ if (!lc)
+ return -EINVAL;
+
+ iter = &lc->iter;
+ DBG(ITER, ul_debugobj(iter, "de-initialize"));
+
+ free(iter->minors);
+ if (iter->proc)
+ fclose(iter->proc);
+ if (iter->sysblock)
+ closedir(iter->sysblock);
+
+ memset(iter, 0, sizeof(*iter));
+ return 0;
+}
+
+/*
+ * Same as loopcxt_set_device, but also checks if the device is
+ * associated with any file.
+ *
+ * Returns: <0 on error, 0 on success, 1 device does not match with
+ * LOOPITER_FL_{USED,FREE} flags.
+ */
+static int loopiter_set_device(struct loopdev_cxt *lc, const char *device)
+{
+ int rc = loopcxt_set_device(lc, device);
+ int used;
+
+ if (rc)
+ return rc;
+
+ if (!(lc->iter.flags & LOOPITER_FL_USED) &&
+ !(lc->iter.flags & LOOPITER_FL_FREE))
+ return 0; /* caller does not care about device status */
+
+ if (!is_loopdev(lc->device)) {
+ DBG(ITER, ul_debugobj(&lc->iter, "%s does not exist", lc->device));
+ return -errno;
+ }
+
+ DBG(ITER, ul_debugobj(&lc->iter, "%s exist", lc->device));
+
+ used = loopcxt_get_offset(lc, NULL) == 0;
+
+ if ((lc->iter.flags & LOOPITER_FL_USED) && used)
+ return 0;
+
+ if ((lc->iter.flags & LOOPITER_FL_FREE) && !used)
+ return 0;
+
+ DBG(ITER, ul_debugobj(&lc->iter, "failed to use %s device", lc->device));
+
+ ignore_result( loopcxt_set_device(lc, NULL) );
+ return 1;
+}
+
+static int cmpnum(const void *p1, const void *p2)
+{
+ return (((* (const int *) p1) > (* (const int *) p2)) -
+ ((* (const int *) p1) < (* (const int *) p2)));
+}
+
+/*
+ * The classic scandir() is more expensive and less portable.
+ * We needn't full loop device names -- loop numbers (loop<N>)
+ * are enough.
+ */
+static int loop_scandir(const char *dirname, int **ary, int hasprefix)
+{
+ DIR *dir;
+ struct dirent *d;
+ unsigned int n, count = 0, arylen = 0;
+
+ if (!dirname || !ary)
+ return 0;
+
+ DBG(ITER, ul_debug("scan dir: %s", dirname));
+
+ dir = opendir(dirname);
+ if (!dir)
+ return 0;
+ free(*ary);
+ *ary = NULL;
+
+ while((d = readdir(dir))) {
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_BLK && d->d_type != DT_UNKNOWN &&
+ d->d_type != DT_LNK)
+ continue;
+#endif
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ if (hasprefix) {
+ /* /dev/loop<N> */
+ if (sscanf(d->d_name, "loop%u", &n) != 1)
+ continue;
+ } else {
+ /* /dev/loop/<N> */
+ char *end = NULL;
+
+ errno = 0;
+ n = strtol(d->d_name, &end, 10);
+ if (d->d_name == end || (end && *end) || errno)
+ continue;
+ }
+ if (n < LOOPDEV_DEFAULT_NNODES)
+ continue; /* ignore loop<0..7> */
+
+ if (count + 1 > arylen) {
+ int *tmp;
+
+ arylen += 1;
+
+ tmp = realloc(*ary, arylen * sizeof(int));
+ if (!tmp) {
+ free(*ary);
+ *ary = NULL;
+ closedir(dir);
+ return -1;
+ }
+ *ary = tmp;
+ }
+ if (*ary)
+ (*ary)[count++] = n;
+ }
+ if (count && *ary)
+ qsort(*ary, count, sizeof(int), cmpnum);
+
+ closedir(dir);
+ return count;
+}
+
+/*
+ * Set the next *used* loop device according to /proc/partitions.
+ *
+ * Loop devices smaller than 512 bytes are invisible for this function.
+ */
+static int loopcxt_next_from_proc(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter = &lc->iter;
+ char buf[BUFSIZ];
+
+ DBG(ITER, ul_debugobj(iter, "scan /proc/partitions"));
+
+ if (!iter->proc)
+ iter->proc = fopen(_PATH_PROC_PARTITIONS, "r" UL_CLOEXECSTR);
+ if (!iter->proc)
+ return 1;
+
+ while (fgets(buf, sizeof(buf), iter->proc)) {
+ unsigned int m;
+ char name[128 + 1];
+
+
+ if (sscanf(buf, " %u %*s %*s %128[^\n ]",
+ &m, name) != 2 || m != LOOPDEV_MAJOR)
+ continue;
+
+ DBG(ITER, ul_debugobj(iter, "checking %s", name));
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Set the next *used* loop device according to
+ * /sys/block/loopN/loop/backing_file (kernel >= 2.6.37 is required).
+ *
+ * This is preferred method.
+ */
+static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter = &lc->iter;
+ struct dirent *d;
+ int fd;
+
+ DBG(ITER, ul_debugobj(iter, "scanning /sys/block"));
+
+ if (!iter->sysblock)
+ iter->sysblock = opendir(_PATH_SYS_BLOCK);
+
+ if (!iter->sysblock)
+ return 1;
+
+ fd = dirfd(iter->sysblock);
+
+ while ((d = readdir(iter->sysblock))) {
+ char name[NAME_MAX + 18 + 1];
+ struct stat st;
+
+ DBG(ITER, ul_debugobj(iter, "check %s", d->d_name));
+
+ if (strcmp(d->d_name, ".") == 0
+ || strcmp(d->d_name, "..") == 0
+ || strncmp(d->d_name, "loop", 4) != 0)
+ continue;
+
+ snprintf(name, sizeof(name), "%s/loop/backing_file", d->d_name);
+ if (fstatat(fd, name, &st, 0) != 0)
+ continue;
+
+ if (loopiter_set_device(lc, d->d_name) == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * @lc: context, has to initialized by loopcxt_init_iterator()
+ *
+ * Returns: 0 on success, -1 on error, 1 at the end of scanning. The details
+ * about the current loop device are available by
+ * loopcxt_get_{fd,backing_file,device,offset, ...} functions.
+ */
+int loopcxt_next(struct loopdev_cxt *lc)
+{
+ struct loopdev_iter *iter;
+
+ if (!lc)
+ return -EINVAL;
+
+
+ iter = &lc->iter;
+ if (iter->done)
+ return 1;
+
+ DBG(ITER, ul_debugobj(iter, "next"));
+
+ /* A) Look for used loop devices in /proc/partitions ("losetup -a" only)
+ */
+ if (iter->flags & LOOPITER_FL_USED) {
+ int rc;
+
+ if (loopcxt_sysfs_available(lc))
+ rc = loopcxt_next_from_sysfs(lc);
+ else
+ rc = loopcxt_next_from_proc(lc);
+ if (rc == 0)
+ return 0;
+ goto done;
+ }
+
+ /* B) Classic way, try first eight loop devices (default number
+ * of loop devices). This is enough for 99% of all cases.
+ */
+ if (iter->default_check) {
+ DBG(ITER, ul_debugobj(iter, "next: default check"));
+ for (++iter->ncur; iter->ncur < LOOPDEV_DEFAULT_NNODES;
+ iter->ncur++) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", iter->ncur);
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+ iter->default_check = 0;
+ }
+
+ /* C) the worst possibility, scan whole /dev or /dev/loop/<N>
+ */
+ if (!iter->minors) {
+ DBG(ITER, ul_debugobj(iter, "next: scanning /dev"));
+ iter->nminors = (lc->flags & LOOPDEV_FL_DEVSUBDIR) ?
+ loop_scandir(_PATH_DEV_LOOP, &iter->minors, 0) :
+ loop_scandir(_PATH_DEV, &iter->minors, 1);
+ iter->ncur = -1;
+ }
+ for (++iter->ncur; iter->ncur < iter->nminors; iter->ncur++) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", iter->minors[iter->ncur]);
+
+ if (loopiter_set_device(lc, name) == 0)
+ return 0;
+ }
+done:
+ loopcxt_deinit_iterator(lc);
+ return 1;
+}
+
+/*
+ * @device: path to device
+ */
+int is_loopdev(const char *device)
+{
+ struct stat st;
+ int rc = 0;
+
+ if (!device || stat(device, &st) != 0 || !S_ISBLK(st.st_mode))
+ rc = 0;
+ else if (major(st.st_rdev) == LOOPDEV_MAJOR)
+ rc = 1;
+ else if (sysfs_devno_is_wholedisk(st.st_rdev)) {
+ /* It's possible that kernel creates a device with a different
+ * major number ... check by /sys it's really loop device.
+ */
+ char name[PATH_MAX], *cn, *p = NULL;
+
+ snprintf(name, sizeof(name), _PATH_SYS_DEVBLOCK "/%d:%d",
+ major(st.st_rdev), minor(st.st_rdev));
+ cn = canonicalize_path(name);
+ if (cn)
+ p = stripoff_last_component(cn);
+ rc = p && startswith(p, "loop");
+ free(cn);
+ }
+
+ if (rc == 0)
+ errno = ENODEV;
+ return rc;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns result from LOOP_GET_STAT64 ioctl or NULL on error.
+ */
+struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc)
+{
+ int fd;
+
+ if (!lc || lc->info_failed) {
+ errno = EINVAL;
+ return NULL;
+ }
+ errno = 0;
+ if (lc->has_info)
+ return &lc->config.info;
+
+ fd = loopcxt_get_fd(lc);
+ if (fd < 0)
+ return NULL;
+
+ if (ioctl(fd, LOOP_GET_STATUS64, &lc->config.info) == 0) {
+ lc->has_info = 1;
+ lc->info_failed = 0;
+ DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK"));
+ return &lc->config.info;
+ }
+
+ lc->info_failed = 1;
+ DBG(CXT, ul_debugobj(lc, "reading loop_info64 FAILED"));
+
+ return NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns (allocated) string with path to the file associated
+ * with the current loop device.
+ */
+char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ char *res = NULL;
+
+ if (sysfs)
+ /*
+ * This is always preferred, the loop_info64
+ * has too small buffer for the filename.
+ */
+ ul_path_read_string(sysfs, &res, "loop/backing_file");
+
+ if (!res && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+
+ if (lo) {
+ lo->lo_file_name[LO_NAME_SIZE - 2] = '*';
+ lo->lo_file_name[LO_NAME_SIZE - 1] = '\0';
+ res = strdup((char *) lo->lo_file_name);
+ }
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_file [%s]", res));
+ return res;
+}
+
+/*
+ * @lc: context
+ * @offset: returns offset number for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ if (ul_path_read_u64(sysfs, offset, "loop/offset") == 0)
+ rc = 0;
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (offset)
+ *offset = lo->lo_offset;
+ rc = 0;
+ } else
+ rc = -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_offset [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @blocksize: returns logical blocksize for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_blocksize(struct loopdev_cxt *lc, uint64_t *blocksize)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ if (ul_path_read_u64(sysfs, blocksize, "queue/logical_block_size") == 0)
+ rc = 0;
+
+ /* Fallback based on BLKSSZGET ioctl */
+ if (rc) {
+ int fd = loopcxt_get_fd(lc);
+ int sz = 0;
+
+ if (fd < 0)
+ return -EINVAL;
+ rc = blkdev_get_sector_size(fd, &sz);
+ if (rc)
+ return rc;
+
+ *blocksize = sz;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_blocksize [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @sizelimit: returns size limit for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+ int rc = -EINVAL;
+
+ if (sysfs)
+ if (ul_path_read_u64(sysfs, size, "loop/sizelimit") == 0)
+ rc = 0;
+
+ if (rc && loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo) {
+ if (size)
+ *size = lo->lo_sizelimit;
+ rc = 0;
+ } else
+ rc = -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "get_sizelimit [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns encryption type
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ /* not provided by sysfs */
+ if (lo) {
+ if (type)
+ *type = lo->lo_encrypt_type;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_encrypt_type [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns crypt name
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+
+ if (lo)
+ return (char *) lo->lo_crypt_name;
+
+ DBG(CXT, ul_debugobj(lc, "get_crypt_name failed"));
+ return NULL;
+}
+
+/*
+ * @lc: context
+ * @devno: returns backing file devno
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ if (lo) {
+ if (devno)
+ *devno = lo->lo_device;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_devno [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ * @ino: returns backing file inode
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino)
+{
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ int rc;
+
+ if (lo) {
+ if (ino)
+ *ino = lo->lo_inode;
+ rc = 0;
+ } else
+ rc = -errno;
+
+ DBG(CXT, ul_debugobj(lc, "get_backing_inode [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Check if the kernel supports partitioned loop devices.
+ *
+ * Notes:
+ * - kernels < 3.2 support partitioned loop devices and PT scanning
+ * only if max_part= module parameter is non-zero
+ *
+ * - kernels >= 3.2 always support partitioned loop devices
+ *
+ * - kernels >= 3.2 always support BLKPG_{ADD,DEL}_PARTITION ioctls
+ *
+ * - kernels >= 3.2 enable PT scanner only if max_part= is non-zero or if the
+ * LO_FLAGS_PARTSCAN flag is set for the device. The PT scanner is disabled
+ * by default.
+ *
+ * See kernel commit e03c8dd14915fabc101aa495828d58598dc5af98.
+ */
+int loopmod_supports_partscan(void)
+{
+ int rc, ret = 0;
+ FILE *f;
+
+ if (get_linux_version() >= KERNEL_VERSION(3,2,0))
+ return 1;
+
+ f = fopen("/sys/module/loop/parameters/max_part", "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+ rc = fscanf(f, "%d", &ret);
+ fclose(f);
+ return rc == 1 ? ret : 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the partscan flags is set *or* (for old kernels) partitions
+ * scanning is enabled for all loop devices.
+ */
+int loopcxt_is_partscan(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ /* kernel >= 3.2 */
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/partscan") == 0)
+ return fl;
+ }
+
+ /* old kernels (including kernels without loopN/loop/<flags> directory */
+ return loopmod_supports_partscan();
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the autoclear flags is set.
+ */
+int loopcxt_is_autoclear(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/autoclear") == 0)
+ return fl;
+ }
+
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_AUTOCLEAR;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the readonly flags is set.
+ */
+int loopcxt_is_readonly(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "ro") == 0)
+ return fl;
+ }
+
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_READ_ONLY;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the dio flags is set.
+ */
+int loopcxt_is_dio(struct loopdev_cxt *lc)
+{
+ struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+ if (sysfs) {
+ int fl;
+ if (ul_path_read_s32(sysfs, &fl, "loop/dio") == 0)
+ return fl;
+ }
+ if (loopcxt_ioctl_enabled(lc)) {
+ struct loop_info64 *lo = loopcxt_get_info(lc);
+ if (lo)
+ return lo->lo_flags & LO_FLAGS_DIRECT_IO;
+ }
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @st: backing file stat or NULL
+ * @backing_file: filename
+ * @offset: offset (use LOOPDEV_FL_OFFSET if specified)
+ * @sizelimit: size limit (use LOOPDEV_FL_SIZELIMIT if specified)
+ * @flags: LOOPDEV_FL_{OFFSET,SIZELIMIT}
+ *
+ * Returns 1 if the current @lc loopdev is associated with the given backing
+ * file. Note that the preferred way is to use devno and inode number rather
+ * than filename. The @backing_file filename is poor solution usable in case
+ * that you don't have rights to call stat().
+ *
+ * LOOPDEV_FL_SIZELIMIT requires LOOPDEV_FL_OFFSET being set as well.
+ *
+ * Don't forget that old kernels provide very restricted (in size) backing
+ * filename by LOOP_GET_STAT64 ioctl only.
+ */
+int loopcxt_is_used(struct loopdev_cxt *lc,
+ struct stat *st,
+ const char *backing_file,
+ uint64_t offset,
+ uint64_t sizelimit,
+ int flags)
+{
+ ino_t ino = 0;
+ dev_t dev = 0;
+
+ if (!lc)
+ return 0;
+
+ DBG(CXT, ul_debugobj(lc, "checking %s vs. %s",
+ loopcxt_get_device(lc),
+ backing_file));
+
+ if (st && loopcxt_get_backing_inode(lc, &ino) == 0 &&
+ loopcxt_get_backing_devno(lc, &dev) == 0) {
+
+ if (ino == st->st_ino && dev == st->st_dev)
+ goto found;
+
+ /* don't use filename if we have devno and inode */
+ return 0;
+ }
+
+ /* poor man's solution */
+ if (backing_file) {
+ char *name = loopcxt_get_backing_file(lc);
+ int rc = name && strcmp(name, backing_file) == 0;
+
+ free(name);
+ if (rc)
+ goto found;
+ }
+
+ return 0;
+found:
+ if (flags & LOOPDEV_FL_OFFSET) {
+ uint64_t off = 0;
+
+ int rc = loopcxt_get_offset(lc, &off) == 0 && off == offset;
+
+ if (rc && flags & LOOPDEV_FL_SIZELIMIT) {
+ uint64_t sz = 0;
+
+ return loopcxt_get_sizelimit(lc, &sz) == 0 && sz == sizelimit;
+ }
+ return rc;
+ }
+ return 1;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->config.info.lo_offset = offset;
+
+ DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset));
+ return 0;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->config.info.lo_sizelimit = sizelimit;
+
+ DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit));
+ return 0;
+}
+
+/*
+ * The blocksize will be used by loopcxt_set_device(). For already exiting
+ * devices use loopcxt_ioctl_blocksize().
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->blocksize = blocksize;
+
+ DBG(CXT, ul_debugobj(lc, "set blocksize=%jd", blocksize));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags)
+{
+ if (!lc)
+ return -EINVAL;
+ lc->config.info.lo_flags = flags;
+
+ DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags));
+ return 0;
+}
+
+/*
+ * @lc: context
+ * @filename: backing file path (the path will be canonicalized)
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
+{
+ if (!lc)
+ return -EINVAL;
+
+ lc->filename = canonicalize_path(filename);
+ if (!lc->filename)
+ return -errno;
+
+ xstrncpy((char *)lc->config.info.lo_file_name, lc->filename, LO_NAME_SIZE);
+
+ DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->config.info.lo_file_name));
+ return 0;
+}
+
+/*
+ * In kernels prior to v3.9, if the offset or sizelimit options
+ * are used, the block device's size won't be synced automatically.
+ * blockdev --getsize64 and filesystems will use the backing
+ * file size until the block device has been re-opened or the
+ * LOOP_SET_CAPACITY ioctl is called to sync the sizes.
+ *
+ * Since mount -oloop uses the LO_FLAGS_AUTOCLEAR option and passes
+ * the open file descriptor to the mount system call, we need to use
+ * the ioctl. Calling losetup directly doesn't have this problem since
+ * it closes the device when it exits and whatever consumes the device
+ * next will re-open it, causing the resync.
+ */
+static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
+{
+ uint64_t size, expected_size;
+ int dev_fd;
+ struct stat st;
+
+ if (!lc->config.info.lo_offset && !lc->config.info.lo_sizelimit)
+ return 0;
+
+ if (fstat(file_fd, &st)) {
+ DBG(CXT, ul_debugobj(lc, "failed to fstat backing file"));
+ return -errno;
+ }
+ if (S_ISBLK(st.st_mode)) {
+ if (blkdev_get_size(file_fd,
+ (unsigned long long *) &expected_size)) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine device size"));
+ return -errno;
+ }
+ } else
+ expected_size = st.st_size;
+
+ if (expected_size == 0 || expected_size <= lc->config.info.lo_offset) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine expected size"));
+ return 0; /* ignore this error */
+ }
+
+ if (lc->config.info.lo_offset > 0)
+ expected_size -= lc->config.info.lo_offset;
+
+ if (lc->config.info.lo_sizelimit > 0 && lc->config.info.lo_sizelimit < expected_size)
+ expected_size = lc->config.info.lo_sizelimit;
+
+ dev_fd = loopcxt_get_fd(lc);
+ if (dev_fd < 0) {
+ DBG(CXT, ul_debugobj(lc, "failed to get loop FD"));
+ return -errno;
+ }
+
+ if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) {
+ DBG(CXT, ul_debugobj(lc, "failed to determine loopdev size"));
+ return -errno;
+ }
+
+ /* It's block device, so, align to 512-byte sectors */
+ if (expected_size % 512) {
+ DBG(CXT, ul_debugobj(lc, "expected size misaligned to 512-byte sectors"));
+ expected_size = (expected_size >> 9) << 9;
+ }
+
+ if (expected_size != size) {
+ DBG(CXT, ul_debugobj(lc, "warning: loopdev and expected "
+ "size mismatch (%ju/%ju)",
+ size, expected_size));
+
+ if (loopcxt_ioctl_capacity(lc)) {
+ /* ioctl not available */
+ if (errno == ENOTTY || errno == EINVAL)
+ errno = ERANGE;
+ return -errno;
+ }
+
+ if (blkdev_get_size(dev_fd, (unsigned long long *) &size))
+ return -errno;
+
+ if (expected_size != size) {
+ errno = ERANGE;
+ DBG(CXT, ul_debugobj(lc, "failed to set loopdev size, "
+ "size: %ju, expected: %ju",
+ size, expected_size));
+ return -errno;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Associate the current device (see loopcxt_{set,get}_device()) with
+ * a file (see loopcxt_set_backing_file()).
+ *
+ * The device is initialized read-write by default. If you want read-only
+ * device then set LO_FLAGS_READ_ONLY by loopcxt_set_flags(). The LOOPDEV_FL_*
+ * flags are ignored and modified according to LO_FLAGS_*.
+ *
+ * If the device is already open by loopcxt_get_fd() then this setup device
+ * function will re-open the device to fix read/write mode.
+ *
+ * The device is also initialized read-only if the backing file is not
+ * possible to open read-write (e.g. read-only FS).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_setup_device(struct loopdev_cxt *lc)
+{
+ int file_fd, dev_fd, mode = O_RDWR, flags = O_CLOEXEC;
+ int rc = -1, cnt = 0, err, again;
+ int errsv = 0;
+ int fallback = 0;
+
+ if (!lc || !*lc->device || !lc->filename)
+ return -EINVAL;
+
+ DBG(SETUP, ul_debugobj(lc, "device setup requested"));
+
+ /*
+ * Open backing file and device
+ */
+ if (lc->config.info.lo_flags & LO_FLAGS_READ_ONLY)
+ mode = O_RDONLY;
+
+ if (lc->config.info.lo_flags & LO_FLAGS_DIRECT_IO)
+ flags |= O_DIRECT;
+
+ if ((file_fd = open(lc->filename, mode | flags)) < 0) {
+ if (mode != O_RDONLY && (errno == EROFS || errno == EACCES))
+ file_fd = open(lc->filename, (mode = O_RDONLY) | flags);
+
+ if (file_fd < 0) {
+ DBG(SETUP, ul_debugobj(lc, "open backing file failed: %m"));
+ return -errno;
+ }
+ }
+ DBG(SETUP, ul_debugobj(lc, "backing file open: OK"));
+
+ if (lc->fd != -1 && lc->mode != mode) {
+ DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)"));
+ close(lc->fd);
+ lc->fd = -1;
+ lc->mode = 0;
+ }
+
+ if (mode == O_RDONLY) {
+ lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */
+ lc->config.info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */
+ } else {
+ lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */
+ lc->config.info.lo_flags &= ~LO_FLAGS_READ_ONLY;
+ lc->flags &= ~LOOPDEV_FL_RDONLY;
+ }
+
+ do {
+ errno = 0;
+ dev_fd = loopcxt_get_fd(lc);
+ if (dev_fd >= 0 || lc->control_ok == 0)
+ break;
+ if (errno != EACCES && errno != ENOENT)
+ break;
+ /* We have permissions to open /dev/loop-control, but open
+ * /dev/loopN failed with EACCES, it's probably because udevd
+ * does not applied chown yet. Let's wait a moment. */
+ xusleep(25000);
+ } while (cnt++ < 16);
+
+ if (dev_fd < 0) {
+ rc = -errno;
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "device open: OK"));
+
+ /*
+ * Atomic way to configure all by one ioctl call
+ * -- since Linux v5.8-rc1, commit 3448914e8cc550ba792d4ccc74471d1ca4293aae
+ */
+ lc->config.fd = file_fd;
+ if (lc->blocksize > 0)
+ lc->config.block_size = lc->blocksize;
+
+ if (ioctl(dev_fd, LOOP_CONFIGURE, &lc->config) < 0) {
+ rc = -errno;
+ errsv = errno;
+ if (errno != EINVAL && errno != ENOTTY && errno != ENOSYS) {
+ DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE failed: %m"));
+ goto err;
+ }
+ fallback = 1;
+ } else {
+ DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE: OK"));
+ }
+
+ /*
+ * Old deprecated way; first assign backing file FD and then in the
+ * second step set loop device properties.
+ */
+ if (fallback) {
+ if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
+ rc = -errno;
+ errsv = errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m"));
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK"));
+
+ if (lc->blocksize > 0
+ && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) {
+ errsv = -rc;
+ goto err;
+ }
+
+ do {
+ err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info);
+ again = err && errno == EAGAIN;
+ if (again)
+ xusleep(250000);
+ } while (again);
+
+ if (err) {
+ rc = -errno;
+ errsv = errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+ goto err;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+ }
+
+ if ((rc = loopcxt_check_size(lc, file_fd)))
+ goto err;
+
+ close(file_fd);
+
+ memset(&lc->config, 0, sizeof(lc->config));
+ lc->has_info = 0;
+ lc->info_failed = 0;
+
+ DBG(SETUP, ul_debugobj(lc, "success [rc=0]"));
+ return 0;
+err:
+ if (file_fd >= 0)
+ close(file_fd);
+ if (dev_fd >= 0 && rc != -EBUSY)
+ ioctl(dev_fd, LOOP_CLR_FD, 0);
+ if (errsv)
+ errno = errsv;
+
+ DBG(SETUP, ul_debugobj(lc, "failed [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @lc: context
+ *
+ * Update status of the current device (see loopcxt_{set,get}_device()).
+ *
+ * Note that once initialized, kernel accepts only selected changes:
+ * LO_FLAGS_AUTOCLEAR and LO_FLAGS_PARTSCAN
+ * For more see linux/drivers/block/loop.c:loop_set_status()
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_ioctl_status(struct loopdev_cxt *lc)
+{
+ int dev_fd, rc = -1, err, again, tries = 0;
+
+ errno = 0;
+ dev_fd = loopcxt_get_fd(lc);
+
+ if (dev_fd < 0) {
+ rc = -errno;
+ return rc;
+ }
+ DBG(SETUP, ul_debugobj(lc, "device open: OK"));
+
+ do {
+ err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info);
+ again = err && errno == EAGAIN;
+ if (again) {
+ xusleep(250000);
+ tries++;
+ }
+ } while (again && tries <= LOOPDEV_MAX_TRIES);
+
+ if (err) {
+ rc = -errno;
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+ return rc;
+ }
+
+ DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+ return 0;
+}
+
+int loopcxt_ioctl_capacity(struct loopdev_cxt *lc)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Kernels prior to v2.6.30 don't support this ioctl */
+ if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_CAPACITY failed: %m"));
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "capacity set"));
+ return 0;
+}
+
+int loopcxt_ioctl_dio(struct loopdev_cxt *lc, unsigned long use_dio)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Kernels prior to v4.4 don't support this ioctl */
+ if (ioctl(fd, LOOP_SET_DIRECT_IO, use_dio) < 0) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_DIRECT_IO failed: %m"));
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "direct io set"));
+ return 0;
+}
+
+/*
+ * Kernel uses "unsigned long" as ioctl arg, but we use u64 for all sizes to
+ * keep loopdev internal API simple.
+ */
+int loopcxt_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize)
+{
+ int fd = loopcxt_get_fd(lc);
+ int err, again, tries = 0;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ do {
+ /* Kernels prior to v4.14 don't support this ioctl */
+ err = ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) blocksize);
+ again = err && errno == EAGAIN;
+ if (again) {
+ xusleep(250000);
+ tries++;
+ } else if (err) {
+ int rc = -errno;
+ DBG(CXT, ul_debugobj(lc, "LOOP_SET_BLOCK_SIZE failed: %m"));
+ return rc;
+ }
+ } while (again && tries <= LOOPDEV_MAX_TRIES);
+
+ DBG(CXT, ul_debugobj(lc, "logical block size set"));
+ return 0;
+}
+
+int loopcxt_delete_device(struct loopdev_cxt *lc)
+{
+ int fd = loopcxt_get_fd(lc);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
+ DBG(CXT, ul_debugobj(lc, "LOOP_CLR_FD failed: %m"));
+ return -errno;
+ }
+
+ DBG(CXT, ul_debugobj(lc, "device removed"));
+ return 0;
+}
+
+int loopcxt_add_device(struct loopdev_cxt *lc)
+{
+ int rc = -EINVAL;
+ int ctl, nr = -1;
+ const char *p, *dev = loopcxt_get_device(lc);
+
+ if (!dev)
+ goto done;
+
+ if (!(lc->flags & LOOPDEV_FL_CONTROL)) {
+ rc = -ENOSYS;
+ goto done;
+ }
+
+ p = strrchr(dev, '/');
+ if (!p || (sscanf(p, "/loop%d", &nr) != 1 && sscanf(p, "/%d", &nr) != 1)
+ || nr < 0)
+ goto done;
+
+ ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+ if (ctl >= 0) {
+ DBG(CXT, ul_debugobj(lc, "add_device %d", nr));
+ rc = ioctl(ctl, LOOP_CTL_ADD, nr);
+ close(ctl);
+ }
+ lc->control_ok = rc >= 0 ? 1 : 0;
+done:
+ DBG(CXT, ul_debugobj(lc, "add_device done [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Note that LOOP_CTL_GET_FREE ioctl is supported since kernel 3.1. In older
+ * kernels we have to check all loop devices to found unused one.
+ *
+ * See kernel commit 770fe30a46a12b6fb6b63fbe1737654d28e8484.
+ */
+int loopcxt_find_unused(struct loopdev_cxt *lc)
+{
+ int rc = -1;
+
+ DBG(CXT, ul_debugobj(lc, "find_unused requested"));
+
+ if (lc->flags & LOOPDEV_FL_CONTROL) {
+ int ctl;
+
+ DBG(CXT, ul_debugobj(lc, "using loop-control"));
+
+ ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+ if (ctl >= 0)
+ rc = ioctl(ctl, LOOP_CTL_GET_FREE);
+ if (rc >= 0) {
+ char name[16];
+ snprintf(name, sizeof(name), "loop%d", rc);
+
+ rc = loopiter_set_device(lc, name);
+ }
+ lc->control_ok = ctl >= 0 && rc == 0 ? 1 : 0;
+ if (ctl >= 0)
+ close(ctl);
+ DBG(CXT, ul_debugobj(lc, "find_unused by loop-control [rc=%d]", rc));
+ }
+
+ if (rc < 0) {
+ DBG(CXT, ul_debugobj(lc, "using loop scan"));
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
+ if (rc)
+ return rc;
+
+ rc = loopcxt_next(lc);
+ loopcxt_deinit_iterator(lc);
+ DBG(CXT, ul_debugobj(lc, "find_unused by scan [rc=%d]", rc));
+ }
+ return rc;
+}
+
+
+
+/*
+ * Return: TRUE/FALSE
+ */
+int loopdev_is_autoclear(const char *device)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (!device)
+ return 0;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (!rc)
+ rc = loopcxt_is_autoclear(&lc);
+
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+char *loopdev_get_backing_file(const char *device)
+{
+ struct loopdev_cxt lc;
+ char *res = NULL;
+
+ if (!device)
+ return NULL;
+ if (loopcxt_init(&lc, 0))
+ return NULL;
+ if (loopcxt_set_device(&lc, device) == 0)
+ res = loopcxt_get_backing_file(&lc);
+
+ loopcxt_deinit(&lc);
+ return res;
+}
+
+int loopdev_has_backing_file(const char *device)
+{
+ char *tmp = loopdev_get_backing_file(device);
+
+ if (tmp) {
+ free(tmp);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Returns: TRUE/FALSE
+ */
+int loopdev_is_used(const char *device, const char *filename,
+ uint64_t offset, uint64_t sizelimit, int flags)
+{
+ struct loopdev_cxt lc;
+ struct stat st;
+ int rc = 0;
+
+ if (!device || !filename)
+ return 0;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (rc)
+ return rc;
+
+ rc = !stat(filename, &st);
+ rc = loopcxt_is_used(&lc, rc ? &st : NULL, filename, offset, sizelimit, flags);
+
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+int loopdev_delete(const char *device)
+{
+ struct loopdev_cxt lc;
+ int rc;
+
+ if (!device)
+ return -EINVAL;
+
+ rc = loopcxt_init(&lc, 0);
+ if (!rc)
+ rc = loopcxt_set_device(&lc, device);
+ if (!rc)
+ rc = loopcxt_delete_device(&lc);
+ loopcxt_deinit(&lc);
+ return rc;
+}
+
+/*
+ * Returns: 0 = success, < 0 error, 1 not found
+ */
+int loopcxt_find_by_backing_file(struct loopdev_cxt *lc, const char *filename,
+ uint64_t offset, uint64_t sizelimit, int flags)
+{
+ int rc, hasst;
+ struct stat st;
+
+ if (!filename)
+ return -EINVAL;
+
+ hasst = !stat(filename, &st);
+
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+ if (rc)
+ return rc;
+
+ while ((rc = loopcxt_next(lc)) == 0) {
+
+ if (loopcxt_is_used(lc, hasst ? &st : NULL,
+ filename, offset, sizelimit, flags))
+ break;
+ }
+
+ loopcxt_deinit_iterator(lc);
+ return rc;
+}
+
+/*
+ * Returns: 0 = not found, < 0 error, 1 found, 2 found full size and offset match
+ */
+int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
+ uint64_t offset, uint64_t sizelimit)
+{
+ int rc, hasst;
+ struct stat st;
+
+ if (!filename)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(lc, "find_overlap requested"));
+ hasst = !stat(filename, &st);
+
+ rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+ if (rc)
+ return rc;
+
+ while ((rc = loopcxt_next(lc)) == 0) {
+ uint64_t lc_sizelimit, lc_offset;
+
+ rc = loopcxt_is_used(lc, hasst ? &st : NULL,
+ filename, offset, sizelimit, 0);
+ /*
+ * Either the loopdev is unused or we've got an error which can
+ * happen when we are racing with device autoclear. Just ignore
+ * this loopdev...
+ */
+ if (rc <= 0)
+ continue;
+
+ DBG(CXT, ul_debugobj(lc, "found %s backed by %s",
+ loopcxt_get_device(lc), filename));
+
+ rc = loopcxt_get_offset(lc, &lc_offset);
+ if (rc) {
+ DBG(CXT, ul_debugobj(lc, "failed to get offset for device %s",
+ loopcxt_get_device(lc)));
+ continue;
+ }
+ rc = loopcxt_get_sizelimit(lc, &lc_sizelimit);
+ if (rc) {
+ DBG(CXT, ul_debugobj(lc, "failed to get sizelimit for device %s",
+ loopcxt_get_device(lc)));
+ continue;
+ }
+
+ /* full match */
+ if (lc_sizelimit == sizelimit && lc_offset == offset) {
+ DBG(CXT, ul_debugobj(lc, "overlapping loop device %s (full match)",
+ loopcxt_get_device(lc)));
+ rc = 2;
+ goto found;
+ }
+
+ /* overlap */
+ if (lc_sizelimit != 0 && offset >= lc_offset + lc_sizelimit)
+ continue;
+ if (sizelimit != 0 && offset + sizelimit <= lc_offset)
+ continue;
+
+ DBG(CXT, ul_debugobj(lc, "overlapping loop device %s",
+ loopcxt_get_device(lc)));
+ rc = 1;
+ goto found;
+ }
+
+ if (rc == 1)
+ rc = 0; /* not found */
+found:
+ loopcxt_deinit_iterator(lc);
+ DBG(CXT, ul_debugobj(lc, "find_overlap done [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Returns allocated string with device name
+ */
+char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, uint64_t sizelimit, int flags)
+{
+ struct loopdev_cxt lc;
+ char *res = NULL;
+
+ if (!filename)
+ return NULL;
+
+ if (loopcxt_init(&lc, 0))
+ return NULL;
+ if (loopcxt_find_by_backing_file(&lc, filename, offset, sizelimit, flags) == 0)
+ res = loopcxt_strdup_device(&lc);
+ loopcxt_deinit(&lc);
+
+ return res;
+}
+
+/*
+ * Returns number of loop devices associated with @file, if only one loop
+ * device is associated with the given @filename and @loopdev is not NULL then
+ * @loopdev returns name of the device.
+ */
+int loopdev_count_by_backing_file(const char *filename, char **loopdev)
+{
+ struct loopdev_cxt lc;
+ int count = 0, rc;
+
+ if (!filename)
+ return -1;
+
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ return rc;
+ if (loopcxt_init_iterator(&lc, LOOPITER_FL_USED))
+ return -1;
+
+ while(loopcxt_next(&lc) == 0) {
+ char *backing = loopcxt_get_backing_file(&lc);
+
+ if (!backing || strcmp(backing, filename) != 0) {
+ free(backing);
+ continue;
+ }
+
+ free(backing);
+ if (loopdev && count == 0)
+ *loopdev = loopcxt_strdup_device(&lc);
+ count++;
+ }
+
+ loopcxt_deinit(&lc);
+
+ if (loopdev && count > 1) {
+ free(*loopdev);
+ *loopdev = NULL;
+ }
+ return count;
+}
+
+#ifdef TEST_PROGRAM_LOOPDEV
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ goto usage;
+
+ if (strcmp(argv[1], "--is-loopdev") == 0 && argc == 3)
+ printf("%s: %s\n", argv[2], is_loopdev(argv[2]) ? "OK" : "FAIL");
+ else
+ goto usage;
+
+ return EXIT_SUCCESS;
+usage:
+ fprintf(stderr, "usage: %1$s --is-loopdev <dev>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+}
+#endif
+
diff --git a/lib/mangle.c b/lib/mangle.c
new file mode 100644
index 0000000..1a3b89a
--- /dev/null
+++ b/lib/mangle.c
@@ -0,0 +1,169 @@
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mangle.h"
+#include "c.h"
+
+#define isoctal(a) (((a) & ~7) == '0')
+
+#define from_hex(c) (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10)
+
+#define is_unwanted_char(x) (strchr(" \t\n\\", (unsigned int) x) != NULL)
+
+
+char *mangle(const char *s)
+{
+ char *ss, *sp;
+
+ if (!s)
+ return NULL;
+
+ ss = sp = malloc(4 * strlen(s) + 1);
+ if (!sp)
+ return NULL;
+ while(1) {
+ if (!*s) {
+ *sp = '\0';
+ break;
+ }
+ if (is_unwanted_char(*s)) {
+ *sp++ = '\\';
+ *sp++ = '0' + ((*s & 0300) >> 6);
+ *sp++ = '0' + ((*s & 070) >> 3);
+ *sp++ = '0' + (*s & 07);
+ } else
+ *sp++ = *s;
+ s++;
+ }
+ return ss;
+}
+
+
+void unmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+
+ if (!s)
+ return;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 3 < len - 1 && isoctal(s[1]) &&
+ isoctal(s[2]) && isoctal(s[3])) {
+
+ *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+}
+
+size_t unhexmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+ const char *buf0 = buf;
+
+ if (!s)
+ return 0;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 3 < len - 1 && s[1] == 'x' &&
+ isxdigit(s[2]) && isxdigit(s[3])) {
+
+ *buf++ = from_hex(s[2]) << 4 | from_hex(s[3]);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+ return buf - buf0 + 1;
+}
+
+static inline const char *skip_nonspaces(const char *s)
+{
+ while (s && *s && !(*s == ' ' || *s == '\t'))
+ s++;
+ return s;
+}
+
+/*
+ * Returns mallocated buffer or NULL in case of error.
+ */
+char *unmangle(const char *s, const char **end)
+{
+ char *buf;
+ const char *e;
+ size_t sz;
+
+ if (!s)
+ return NULL;
+
+ e = skip_nonspaces(s);
+ sz = e - s + 1;
+
+ if (end)
+ *end = e;
+ if (e == s)
+ return NULL; /* empty string */
+
+ buf = malloc(sz);
+ if (!buf)
+ return NULL;
+
+ unmangle_to_buffer(s, buf, sz);
+ return buf;
+}
+
+#ifdef TEST_PROGRAM_MANGLE
+#include <errno.h>
+int main(int argc, char *argv[])
+{
+ char *p = NULL;
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s --mangle|unmangle <string>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (!strcmp(argv[1], "--mangle")) {
+ p = mangle(argv[2]);
+ printf("mangled: '%s'\n", p);
+ free(p);
+ }
+
+ else if (!strcmp(argv[1], "--unmangle")) {
+ char *x = unmangle(argv[2], NULL);
+
+ if (x) {
+ printf("unmangled: '%s'\n", x);
+ free(x);
+ }
+
+ x = strdup(argv[2]);
+ if (x) {
+ unmangle_to_buffer(x, x, strlen(x) + 1);
+
+ printf("self-unmangled: '%s'\n", x);
+ free(x);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_MANGLE */
diff --git a/lib/match.c b/lib/match.c
new file mode 100644
index 0000000..a286a19
--- /dev/null
+++ b/lib/match.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+
+#include "match.h"
+
+/*
+ * match_fstype:
+ * @type: filesystem type
+ * @pattern: filesystem name or comma delimited list of names
+ *
+ * The @pattern list of filesystem can be prefixed with a global
+ * "no" prefix to invert matching of the whole list. The "no" could
+ * also be used for individual items in the @pattern list. So,
+ * "nofoo,bar" has the same meaning as "nofoo,nobar".
+ */
+int match_fstype(const char *type, const char *pattern)
+{
+ int no = 0; /* negated types list */
+ int len;
+ const char *p;
+
+ if (!pattern && !type)
+ return 1;
+ if (!pattern)
+ return 0;
+
+ if (!strncmp(pattern, "no", 2)) {
+ no = 1;
+ pattern += 2;
+ }
+
+ /* Does type occur in types, separated by commas? */
+ len = strlen(type);
+ p = pattern;
+ while(1) {
+ if (!strncmp(p, "no", 2) && !strncasecmp(p+2, type, len) &&
+ (p[len+2] == 0 || p[len+2] == ','))
+ return 0;
+ if (strncasecmp(p, type, len) == 0 && (p[len] == 0 || p[len] == ','))
+ return !no;
+ p = strchr(p,',');
+ if (!p)
+ break;
+ p++;
+ }
+ return no;
+}
diff --git a/lib/mbsalign.c b/lib/mbsalign.c
new file mode 100644
index 0000000..e251202
--- /dev/null
+++ b/lib/mbsalign.c
@@ -0,0 +1,627 @@
+/* Align/Truncate a string in a given screen width
+ Copyright (C) 2009-2010 Free Software Foundation, Inc.
+
+ This program 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 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/>. */
+
+/* Written by Pádraig Brady. */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "mbsalign.h"
+#include "strutils.h"
+#include "widechar.h"
+
+/*
+ * Counts number of cells in multibyte string. All control and
+ * non-printable chars are ignored.
+ *
+ * Returns: number of cells.
+ */
+size_t mbs_nwidth(const char *buf, size_t bufsz)
+{
+ const char *p = buf, *last = buf;
+ size_t width = 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (p && *p && bufsz)
+ last = p + (bufsz - 1);
+
+ while (p && *p && p <= last) {
+ if (iscntrl((unsigned char) *p)) {
+ p++;
+
+ /* try detect "\e[x;ym" and skip on success */
+ if (*p && *p == '[') {
+ const char *e = p;
+ while (*e && e < last && *e != 'm')
+ e++;
+ if (*e == 'm')
+ p = e + 1;
+ }
+ continue;
+ }
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+ if (len == 0)
+ break;
+ if (len > 0 && iswprint(wc)) {
+ int x = wcwidth(wc);
+ if (x > 0)
+ width += x;
+ } else if (len == (size_t) -1 || len == (size_t) -2)
+ len = 1;
+ p += len;
+#else
+ if (isprint((unsigned char) *p))
+ width++;
+ p++;
+#endif
+ }
+
+ return width;
+}
+
+size_t mbs_width(const char *s)
+{
+ if (!s || !*s)
+ return 0;
+ return mbs_nwidth(s, strlen(s));
+}
+
+/*
+ * Counts number of cells in multibyte string. For all control and
+ * non-printable chars is the result width enlarged to store \x?? hex
+ * sequence. See mbs_safe_encode().
+ *
+ * Returns: number of cells, @sz returns number of bytes.
+ */
+size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz)
+{
+ const char *p = buf, *last = buf;
+ size_t width = 0, bytes = 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (p && *p && bufsz)
+ last = p + (bufsz - 1);
+
+ while (p && *p && p <= last) {
+ if ((p < last && *p == '\\' && *(p + 1) == 'x')
+ || iscntrl((unsigned char) *p)) {
+ width += 4, bytes += 4; /* *p encoded to \x?? */
+ p++;
+ }
+#ifdef HAVE_WIDECHAR
+ else {
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+ if (len == 0)
+ break;
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ if (isprint((unsigned char) *p))
+ width += 1, bytes += 1;
+ else
+ width += 4, bytes += 4;
+
+ } else if (!iswprint(wc)) {
+ width += len * 4; /* hex encode whole sequence */
+ bytes += len * 4;
+ } else {
+ width += wcwidth(wc); /* number of cells */
+ bytes += len; /* number of bytes */
+ }
+ p += len;
+ }
+#else
+ else if (!isprint((unsigned char) *p)) {
+ width += 4, bytes += 4; /* *p encoded to \x?? */
+ p++;
+ } else {
+ width++, bytes++;
+ p++;
+ }
+#endif
+ }
+
+ if (sz)
+ *sz = bytes;
+ return width;
+}
+
+size_t mbs_safe_width(const char *s)
+{
+ if (!s || !*s)
+ return 0;
+ return mbs_safe_nwidth(s, strlen(s), NULL);
+}
+
+/*
+ * Copy @s to @buf and replace control and non-printable chars with
+ * \x?? hex sequence. The @width returns number of cells. The @safechars
+ * are not encoded.
+ *
+ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
+ * bytes.
+ */
+char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, const char *safechars)
+{
+ const char *p = s;
+ char *r;
+ size_t sz = s ? strlen(s) : 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (!sz || !buf)
+ return NULL;
+
+ r = buf;
+ *width = 0;
+
+ while (p && *p) {
+ if (safechars && strchr(safechars, *p)) {
+ *r++ = *p++;
+ continue;
+ }
+
+ if ((*p == '\\' && *(p + 1) == 'x')
+ || iscntrl((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ p++;
+ }
+#ifdef HAVE_WIDECHAR
+ else {
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+ if (len == 0)
+ break; /* end of string */
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ /*
+ * Not valid multibyte sequence -- maybe it's
+ * printable char according to the current locales.
+ */
+ if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ (*width)++;
+ *r++ = *p;
+ }
+ } else if (!iswprint(wc)) {
+ size_t i;
+ for (i = 0; i < len; i++) {
+ sprintf(r, "\\x%02x", (unsigned char) p[i]);
+ r += 4;
+ *width += 4;
+ }
+ } else {
+ memcpy(r, p, len);
+ r += len;
+ *width += wcwidth(wc);
+ }
+ p += len;
+ }
+#else
+ else if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ p++;
+ r += 4;
+ *width += 4;
+ } else {
+ *r++ = *p++;
+ (*width)++;
+ }
+#endif
+ }
+
+ *r = '\0';
+ return buf;
+}
+
+/*
+ * Copy @s to @buf and replace broken sequences to \x?? hex sequence. The
+ * @width returns number of cells. The @safechars are not encoded.
+ *
+ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
+ * bytes.
+ */
+char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf)
+{
+ const char *p = s;
+ char *r;
+ size_t sz = s ? strlen(s) : 0;
+
+#ifdef HAVE_WIDECHAR
+ mbstate_t st;
+ memset(&st, 0, sizeof(st));
+#endif
+ if (!sz || !buf)
+ return NULL;
+
+ r = buf;
+ *width = 0;
+
+ while (p && *p) {
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+#else
+ size_t len = 1;
+#endif
+
+ if (len == 0)
+ break; /* end of string */
+
+ if (len == (size_t) -1 || len == (size_t) -2) {
+ len = 1;
+ /*
+ * Not valid multibyte sequence -- maybe it's
+ * printable char according to the current locales.
+ */
+ if (!isprint((unsigned char) *p)) {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ (*width)++;
+ *r++ = *p;
+ }
+ } else if (*p == '\\' && *(p + 1) == 'x') {
+ sprintf(r, "\\x%02x", (unsigned char) *p);
+ r += 4;
+ *width += 4;
+ } else {
+ memcpy(r, p, len);
+ r += len;
+ *width += wcwidth(wc);
+ }
+ p += len;
+ }
+
+ *r = '\0';
+ return buf;
+}
+
+size_t mbs_safe_encode_size(size_t bytes)
+{
+ return (bytes * 4) + 1;
+}
+
+/*
+ * Returns allocated string where all control and non-printable chars are
+ * replaced with \x?? hex sequence.
+ */
+char *mbs_safe_encode(const char *s, size_t *width)
+{
+ size_t sz = s ? strlen(s) : 0;
+ char *buf, *ret = NULL;
+
+ if (!sz)
+ return NULL;
+ buf = malloc(mbs_safe_encode_size(sz));
+ if (buf)
+ ret = mbs_safe_encode_to_buffer(s, width, buf, NULL);
+ if (!ret)
+ free(buf);
+ return ret;
+}
+
+/*
+ * Returns allocated string where all broken widechars chars are
+ * replaced with \x?? hex sequence.
+ */
+char *mbs_invalid_encode(const char *s, size_t *width)
+{
+ size_t sz = s ? strlen(s) : 0;
+ char *buf, *ret = NULL;
+
+ if (!sz)
+ return NULL;
+ buf = malloc(mbs_safe_encode_size(sz));
+ if (buf)
+ ret = mbs_invalid_encode_to_buffer(s, width, buf);
+ if (!ret)
+ free(buf);
+ return ret;
+}
+
+#ifdef HAVE_WIDECHAR
+
+static bool
+wc_ensure_printable (wchar_t *wchars)
+{
+ bool replaced = false;
+ wchar_t *wc = wchars;
+ while (*wc)
+ {
+ if (!iswprint ((wint_t) *wc))
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ replaced = true;
+ }
+ wc++;
+ }
+ return replaced;
+}
+
+/* Truncate wchar string to width cells.
+ * Returns number of cells used. */
+
+static size_t
+wc_truncate (wchar_t *wc, size_t width)
+{
+ size_t cells = 0;
+ int next_cells = 0;
+
+ while (*wc)
+ {
+ next_cells = wcwidth (*wc);
+ if (next_cells == -1) /* non printable */
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ next_cells = 1;
+ }
+ if (cells + next_cells > width)
+ break;
+
+ cells += next_cells;
+ wc++;
+ }
+ *wc = L'\0';
+ return cells;
+}
+
+static int
+rpl_wcswidth (const wchar_t *s, size_t n)
+{
+ int ret = 0;
+
+ while (n-- > 0 && *s != L'\0')
+ {
+ int nwidth = wcwidth (*s++);
+ if (nwidth == -1) /* non printable */
+ return -1;
+ if (ret > (INT_MAX - nwidth)) /* overflow */
+ return -1;
+ ret += nwidth;
+ }
+
+ return ret;
+}
+#endif /* HAVE_WIDECHAR */
+
+/* Truncate multi-byte string to @width and returns number of
+ * bytes of the new string @str, and in @width returns number
+ * of cells.
+ */
+size_t
+mbs_truncate(char *str, size_t *width)
+{
+ ssize_t bytes = strlen(str);
+#ifdef HAVE_WIDECHAR
+ ssize_t sz = mbstowcs(NULL, str, 0);
+ wchar_t *wcs = NULL;
+
+ if (sz == (ssize_t) -1)
+ goto done;
+
+ wcs = calloc(1, (sz + 1) * sizeof(wchar_t));
+ if (!wcs)
+ goto done;
+
+ if (!mbstowcs(wcs, str, sz))
+ goto done;
+ *width = wc_truncate(wcs, *width);
+ bytes = wcstombs(str, wcs, bytes);
+done:
+ free(wcs);
+#else
+ if (bytes >= 0 && *width < (size_t) bytes)
+ bytes = *width;
+#endif
+ if (bytes >= 0)
+ str[bytes] = '\0';
+ return bytes;
+}
+
+/* Write N_SPACES space characters to DEST while ensuring
+ nothing is written beyond DEST_END. A terminating NUL
+ is always added to DEST.
+ A pointer to the terminating NUL is returned. */
+
+static char*
+mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces, int padchar)
+{
+ for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--)
+ *dest++ = padchar;
+ *dest = '\0';
+ return dest;
+}
+
+size_t
+mbsalign (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align, int flags)
+{
+ return mbsalign_with_padding(src, dest, dest_size, width, align, flags, ' ');
+}
+
+/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
+ characters; write the result into the DEST_SIZE-byte buffer, DEST.
+ ALIGNMENT specifies whether to left- or right-justify or to center.
+ If SRC requires more than *WIDTH columns, truncate it to fit.
+ When centering, the number of trailing spaces may be one less than the
+ number of leading spaces. The FLAGS parameter is unused at present.
+ Return the length in bytes required for the final result, not counting
+ the trailing NUL. A return value of DEST_SIZE or larger means there
+ wasn't enough space. DEST will be NUL terminated in any case.
+ Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
+ or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
+ Update *WIDTH to indicate how many columns were used before padding. */
+
+size_t
+mbsalign_with_padding (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align,
+#ifdef HAVE_WIDECHAR
+ int flags,
+#else
+ int flags __attribute__((__unused__)),
+#endif
+ int padchar)
+{
+ size_t ret = -1;
+ size_t src_size = strlen (src) + 1;
+ char *newstr = NULL;
+ wchar_t *str_wc = NULL;
+ const char *str_to_print = src;
+ size_t n_cols = src_size - 1;
+ size_t n_used_bytes = n_cols; /* Not including NUL */
+ size_t n_spaces = 0, space_left;
+
+#ifdef HAVE_WIDECHAR
+ bool conversion = false;
+ bool wc_enabled = false;
+
+ /* In multi-byte locales convert to wide characters
+ to allow easy truncation. Also determine number
+ of screen columns used. */
+ if (MB_CUR_MAX > 1)
+ {
+ size_t src_chars = mbstowcs (NULL, src, 0);
+ if (src_chars == (size_t) -1)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ src_chars += 1; /* make space for NUL */
+ str_wc = malloc (src_chars * sizeof (wchar_t));
+ if (str_wc == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ if (mbstowcs (str_wc, src, src_chars) != 0)
+ {
+ str_wc[src_chars - 1] = L'\0';
+ wc_enabled = true;
+ conversion = wc_ensure_printable (str_wc);
+ n_cols = rpl_wcswidth (str_wc, src_chars);
+ }
+ }
+
+ /* If we transformed or need to truncate the source string
+ then create a modified copy of it. */
+ if (wc_enabled && (conversion || (n_cols > *width)))
+ {
+ if (conversion)
+ {
+ /* May have increased the size by converting
+ \t to \uFFFD for example. */
+ src_size = wcstombs(NULL, str_wc, 0) + 1;
+ }
+ newstr = malloc (src_size);
+ if (newstr == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ str_to_print = newstr;
+ n_cols = wc_truncate (str_wc, *width);
+ n_used_bytes = wcstombs (newstr, str_wc, src_size);
+ }
+
+mbsalign_unibyte:
+#endif
+
+ if (n_cols > *width) /* Unibyte truncation required. */
+ {
+ n_cols = *width;
+ n_used_bytes = n_cols;
+ }
+
+ if (*width > n_cols) /* Padding required. */
+ n_spaces = *width - n_cols;
+
+ /* indicate to caller how many cells needed (not including padding). */
+ *width = n_cols;
+
+ /* indicate to caller how many bytes needed (not including NUL). */
+ ret = n_used_bytes + (n_spaces * 1);
+
+ /* Write as much NUL terminated output to DEST as possible. */
+ if (dest_size != 0)
+ {
+ char *dest_end = dest + dest_size - 1;
+ size_t start_spaces;
+ size_t end_spaces;
+
+ switch (align)
+ {
+ case MBS_ALIGN_CENTER:
+ start_spaces = n_spaces / 2 + n_spaces % 2;
+ end_spaces = n_spaces / 2;
+ break;
+ case MBS_ALIGN_LEFT:
+ start_spaces = 0;
+ end_spaces = n_spaces;
+ break;
+ case MBS_ALIGN_RIGHT:
+ start_spaces = n_spaces;
+ end_spaces = 0;
+ break;
+ default:
+ abort();
+ }
+
+ dest = mbs_align_pad (dest, dest_end, start_spaces, padchar);
+ space_left = dest_end - dest;
+ dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left));
+ mbs_align_pad (dest, dest_end, end_spaces, padchar);
+ }
+#ifdef HAVE_WIDECHAR
+mbsalign_cleanup:
+#endif
+ free (str_wc);
+ free (newstr);
+
+ return ret;
+}
diff --git a/lib/mbsedit.c b/lib/mbsedit.c
new file mode 100644
index 0000000..8ce5901
--- /dev/null
+++ b/lib/mbsedit.c
@@ -0,0 +1,225 @@
+/*
+ * Very simple multibyte buffer editor. Allows to maintaine the current
+ * position in the string, add and remove chars on the current position.
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "mbsalign.h"
+#include "mbsedit.h"
+
+struct mbs_editor *mbs_new_edit(char *buf, size_t bufsz, size_t ncells)
+{
+ struct mbs_editor *edit = calloc(1, sizeof(*edit));
+
+ if (edit) {
+ edit->buf = buf;
+ edit->max_bytes = bufsz;
+ edit->max_cells = ncells;
+ edit->cur_cells = mbs_safe_width(buf);
+ edit->cur_bytes = strlen(buf);
+ }
+ return edit;
+}
+
+char *mbs_free_edit(struct mbs_editor *edit)
+{
+ char *ret = edit ? edit->buf : NULL;
+
+ free(edit);
+ return ret;
+}
+
+static size_t mbs_next(const char *str, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t n = 0;
+
+ if (!str || !*str)
+ return 0;
+
+ n = mbrtowc(&wc, str, MB_CUR_MAX, NULL);
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!str || !*str)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+static size_t mbs_prev(const char *start, const char *end, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = 0;
+ const char *p, *prev;
+ size_t n = 0;
+
+ if (!start || !end || start == end || !*start)
+ return 0;
+
+ prev = p = start;
+ while (p < end) {
+ n = mbrtowc(&wc, p, MB_CUR_MAX, NULL);
+ prev = p;
+
+ if (n == (size_t) -1 || n == (size_t) -2)
+ p++;
+ else
+ p += n;
+ }
+
+ if (prev == end)
+ return 0;
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!start || !end || start == end || !*start)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+int mbs_edit_goto(struct mbs_editor *edit, int where)
+{
+ switch (where) {
+ case MBS_EDIT_LEFT:
+ if (edit->cursor == 0)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_prev(edit->buf, edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor -= n;
+ edit->cursor_cells -= cells;
+ }
+ }
+ break;
+ case MBS_EDIT_RIGHT:
+ if (edit->cursor_cells >= edit->cur_cells)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_next(edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor += n;
+ edit->cursor_cells += cells;
+ }
+ }
+ break;
+ case MBS_EDIT_HOME:
+ edit->cursor = 0;
+ edit->cursor_cells = 0;
+ break;
+ case MBS_EDIT_END:
+ edit->cursor = edit->cur_bytes;
+ edit->cursor_cells = edit->cur_cells;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Remove next MB from @str, returns number of removed bytes */
+static size_t remove_next(char *str, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t bytes, move_bytes, n;
+
+ n = mbs_next(str, ncells);
+ bytes = strlen(str);
+ move_bytes = bytes - n;
+
+ memmove(str, str + n, move_bytes);
+ str[bytes - n] = '\0';
+ return n;
+}
+
+static size_t mbs_insert(char *str, wint_t c, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t n = 1, bytes;
+ char *in;
+
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = (wchar_t) c;
+ char in_buf[MB_CUR_MAX];
+
+ n = wctomb(in_buf, wc);
+ if (n == (size_t) -1)
+ return n;
+ *ncells = wcwidth(wc);
+ in = in_buf;
+#else
+ *ncells = 1;
+ in = (char *) &c;
+#endif
+ bytes = strlen(str);
+
+ memmove(str + n, str, bytes);
+ memcpy(str, in, n);
+ str[bytes + n] = '\0';
+ return n;
+}
+
+static int mbs_edit_remove(struct mbs_editor *edit)
+{
+ size_t n, ncells;
+
+ if (edit->cur_cells == 0 || edit->cursor >= edit->cur_bytes)
+ return 1;
+
+ n = remove_next(edit->buf + edit->cursor, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cur_bytes -= n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}
+
+int mbs_edit_delete(struct mbs_editor *edit)
+{
+ if (edit->cursor >= edit->cur_bytes
+ && mbs_edit_goto(edit, MBS_EDIT_LEFT) == 1)
+ return 1;
+
+ return mbs_edit_remove(edit);
+}
+
+int mbs_edit_backspace(struct mbs_editor *edit)
+{
+ if (mbs_edit_goto(edit, MBS_EDIT_LEFT) == 0)
+ return mbs_edit_remove(edit);
+ return 1;
+}
+
+int mbs_edit_insert(struct mbs_editor *edit, wint_t c)
+{
+ size_t n, ncells;
+
+ if (edit->cur_bytes + MB_CUR_MAX > edit->max_bytes)
+ return 1;
+
+ n = mbs_insert(edit->buf + edit->cursor, c, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cursor += n;
+ edit->cursor_cells += ncells;
+ edit->cur_bytes += n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}
diff --git a/lib/md5.c b/lib/md5.c
new file mode 100644
index 0000000..3765ab9
--- /dev/null
+++ b/lib/md5.c
@@ -0,0 +1,257 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h> /* for memcpy() */
+
+#include "md5.h"
+
+#if !defined(WORDS_BIGENDIAN)
+# define byteReverse(buf, len) /* Nothing */
+#else
+static void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse(unsigned char *buf, unsigned longs)
+{
+ uint32_t t;
+ do {
+ t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif /* !ASM_MD5 */
+#endif /* !WORDS_BIGENDIAN */
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void ul_MD5Init(struct UL_MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void ul_MD5Update(struct UL_MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+ uint32_t t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void ul_MD5Final(unsigned char digest[UL_MD5LENGTH], struct UL_MD5Context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform.
+ * Use memcpy to avoid aliasing problems. On most systems,
+ * this will be optimized away to the same code.
+ */
+ memcpy(&ctx->in[14 * sizeof(uint32_t)], &ctx->bits[0], 4);
+ memcpy(&ctx->in[15 * sizeof(uint32_t)], &ctx->bits[1], 4);
+
+ ul_MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, UL_MD5LENGTH);
+ memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void ul_MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
+
diff --git a/lib/meson.build b/lib/meson.build
new file mode 100644
index 0000000..0847d59
--- /dev/null
+++ b/lib/meson.build
@@ -0,0 +1,99 @@
+lib_common_sources = '''
+ blkdev.c
+ buffer.c
+ canonicalize.c
+ color-names.c
+ crc32.c
+ crc32c.c
+ c_strtod.c
+ encode.c
+ env.c
+ fileutils.c
+ idcache.c
+ jsonwrt.c
+ mangle.c
+ match.c
+ mbsalign.c
+ mbsedit.c
+ md5.c
+ pager.c
+ procfs.c
+ pwdutils.c
+ randutils.c
+ sha1.c
+ signames.c
+ strutils.c
+ strv.c
+ timeutils.c
+ ttyutils.c
+'''.split()
+
+idcache_c = files('idcache.c')
+randutils_c = files('randutils.c')
+md5_c = files('md5.c')
+sha1_c = files('sha1.c')
+strutils_c = files('strutils.c')
+strv_c = files('strv.c')
+
+lib_common_sources += [idcache_c,
+ randutils_c,
+ md5_c,
+ sha1_c,
+ strutils_c,
+ strv_c]
+
+monotonic_c = files('monotonic.c')
+timer_c = files('timer.c')
+swapprober_c = files('swapprober.c')
+pty_session_c = files('pty-session.c')
+ismounted_c = files('ismounted.c')
+exec_shell_c = files('exec_shell.c')
+fileeq_c = files('fileeq.c')
+
+if LINUX
+ lib_common_sources += '''
+ caputils.c
+ linux_version.c
+ loopdev.c
+'''.split()
+endif
+
+if build_plymouth_support
+ lib_common_sources += '''
+ plymouth-ctrl.c
+'''.split()
+endif
+
+if conf.get('HAVE_LANGINFO_H') in [1]
+ lib_common_sources += 'langinfo.c'
+endif
+
+if conf.get('HAVE_CPU_SET_T') in [1]
+ lib_common_sources += 'cpuset.c'
+endif
+
+if conf.get('HAVE_OPENAT') in [1] and conf.get('HAVE_DIRFD') in [1]
+ lib_common_sources += '''
+ path.c
+ procfs.c
+ sysfs.c
+'''.split()
+endif
+
+lib_common = static_library(
+ 'common',
+ lib_common_sources,
+ include_directories : dir_include)
+
+
+lib_color_sources = files('''
+ colors.c
+ color-names.c
+'''.split())
+# colors.h include/color-names.h
+
+lib_tcolors = static_library(
+ 'tcolors',
+ lib_color_sources,
+ include_directories : dir_include,
+ dependencies : curses_libs)
diff --git a/lib/monotonic.c b/lib/monotonic.c
new file mode 100644
index 0000000..f0aeba6
--- /dev/null
+++ b/lib/monotonic.c
@@ -0,0 +1,81 @@
+/*
+ * Please, don't add this file to libcommon because clock_gettime() requires
+ * -lrt on systems with old libc.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <time.h>
+#include <signal.h>
+#ifdef HAVE_SYSINFO
+#include <sys/sysinfo.h>
+#endif
+#include <sys/time.h>
+
+#include "c.h"
+#include "monotonic.h"
+
+int get_boot_time(struct timeval *boot_time)
+{
+#ifdef CLOCK_BOOTTIME
+ struct timespec hires_uptime;
+ struct timeval lores_uptime;
+#endif
+ struct timeval now;
+#ifdef HAVE_SYSINFO
+ struct sysinfo info;
+#endif
+
+ if (gettimeofday(&now, NULL) != 0)
+ return -errno;
+#ifdef CLOCK_BOOTTIME
+ if (clock_gettime(CLOCK_BOOTTIME, &hires_uptime) == 0) {
+ TIMESPEC_TO_TIMEVAL(&lores_uptime, &hires_uptime);
+ timersub(&now, &lores_uptime, boot_time);
+ return 0;
+ }
+#endif
+#ifdef HAVE_SYSINFO
+ /* fallback */
+ if (sysinfo(&info) != 0)
+ return -errno;
+
+ boot_time->tv_sec = now.tv_sec - info.uptime;
+ boot_time->tv_usec = 0;
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+time_t get_suspended_time(void)
+{
+#if defined(CLOCK_BOOTTIME) && defined(CLOCK_MONOTONIC)
+ struct timespec boot, mono;
+
+ if (clock_gettime(CLOCK_BOOTTIME, &boot) == 0 &&
+ clock_gettime(CLOCK_MONOTONIC, &mono) == 0)
+ return boot.tv_sec - mono.tv_sec;
+#endif
+ return 0;
+}
+
+int gettime_monotonic(struct timeval *tv)
+{
+#ifdef CLOCK_MONOTONIC
+ /* Can slew only by ntp and adjtime */
+ int ret;
+ struct timespec ts;
+
+ /* Linux specific, can't slew */
+ if (!(ret = clock_gettime(UL_CLOCK_MONOTONIC, &ts))) {
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / 1000;
+ }
+ return ret;
+#else
+ return gettimeofday(tv, NULL);
+#endif
+}
+
+
diff --git a/lib/pager.c b/lib/pager.c
new file mode 100644
index 0000000..747521e
--- /dev/null
+++ b/lib/pager.c
@@ -0,0 +1,317 @@
+/*
+ * Based on linux-perf/git scm
+ *
+ * Some modifications and simplifications for util-linux
+ * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "ttyutils.h"
+#include "pager.h"
+
+#define NULL_DEVICE "/dev/null"
+
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+
+struct child_process {
+ const char **argv;
+ pid_t pid;
+ int in;
+ int out;
+ int err;
+
+ int org_err;
+ int org_out;
+ struct sigaction orig_sigint;
+ struct sigaction orig_sighup;
+ struct sigaction orig_sigterm;
+ struct sigaction orig_sigquit;
+ struct sigaction orig_sigpipe;
+
+ unsigned no_stdin:1;
+ void (*preexec_cb)(void);
+};
+static struct child_process pager_process;
+
+static inline void close_pair(int fd[2])
+{
+ close(fd[0]);
+ close(fd[1]);
+}
+
+static int start_command(struct child_process *cmd)
+{
+ int need_in;
+ int fdin[2];
+
+ /*
+ * In case of errors we must keep the promise to close FDs
+ * that have been passed in via ->in and ->out.
+ */
+ need_in = !cmd->no_stdin && cmd->in < 0;
+ if (need_in) {
+ if (pipe(fdin) < 0) {
+ if (cmd->out > 0)
+ close(cmd->out);
+ return -1;
+ }
+ cmd->in = fdin[1];
+ }
+
+ fflush(NULL);
+ cmd->pid = fork();
+ if (!cmd->pid) {
+ if (need_in) {
+ dup2(fdin[0], STDIN_FILENO);
+ close_pair(fdin);
+ } else if (cmd->in > 0) {
+ dup2(cmd->in, STDIN_FILENO);
+ close(cmd->in);
+ }
+
+ cmd->preexec_cb();
+ execvp(cmd->argv[0], (char *const*) cmd->argv);
+ errexec(cmd->argv[0]);
+ }
+
+ if (cmd->pid < 0) {
+ if (need_in)
+ close_pair(fdin);
+ else if (0 <= cmd->in)
+ close(cmd->in);
+ return -1;
+ }
+
+ if (need_in)
+ close(fdin[0]);
+ else if (0 <= cmd->in)
+ close(cmd->in);
+ return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+ for (;;) {
+ int status, code;
+ pid_t waiting = waitpid(pid, &status, 0);
+
+ if (waiting < 0) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
+ }
+ if (waiting != pid)
+ return -1;
+ if (WIFSIGNALED(status))
+ return -1;
+
+ if (!WIFEXITED(status))
+ return -1;
+ code = WEXITSTATUS(status);
+ switch (code) {
+ case 127:
+ return -1;
+ case 0:
+ return 0;
+ default:
+ return -1;
+ }
+ }
+}
+
+static int finish_command(struct child_process *cmd)
+{
+ return wait_or_whine(cmd->pid);
+}
+
+static void pager_preexec(void)
+{
+ /*
+ * Work around bug in "less" by not starting it until we
+ * have real input
+ */
+ fd_set in, ex;
+
+ FD_ZERO(&in);
+ FD_SET(STDIN_FILENO, &in);
+ ex = in;
+
+ select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
+
+ if (setenv("LESS", "FRSX", 0) != 0)
+ warn(_("failed to set the %s environment variable"), "LESS");
+}
+
+static void wait_for_pager(void)
+{
+ if (pager_process.pid == 0)
+ return;
+
+ fflush(stdout);
+ fflush(stderr);
+ /* signal EOF to pager */
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+ wait_for_pager();
+ raise(signo);
+}
+
+static int has_command(const char *cmd)
+{
+ const char *path;
+ char *p, *s;
+ int rc = 0;
+
+ if (!cmd)
+ goto done;
+ if (*cmd == '/') {
+ rc = access(cmd, X_OK) == 0;
+ goto done;
+ }
+
+ path = getenv("PATH");
+ if (!path)
+ goto done;
+ p = xstrdup(path);
+ if (!p)
+ goto done;
+
+ for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
+ int fd = open(s, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ continue;
+ rc = faccessat(fd, cmd, X_OK, 0) == 0;
+ close(fd);
+ if (rc)
+ break;
+ }
+ free(p);
+done:
+ /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/
+ return rc;
+}
+
+static void __setup_pager(void)
+{
+ const char *pager = getenv("PAGER");
+ struct sigaction sa;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+
+ if (!pager)
+ pager = "less";
+ else if (!*pager || !strcmp(pager, "cat"))
+ return;
+
+ if (!has_command(pager))
+ return;
+
+ /* spawn the pager */
+ pager_argv[2] = pager;
+ pager_process.argv = pager_argv;
+ pager_process.in = -1;
+ pager_process.preexec_cb = pager_preexec;
+
+ if (start_command(&pager_process))
+ return;
+
+ /* original process continues, but writes to the pipe */
+ dup2(pager_process.in, STDOUT_FILENO);
+ if (isatty(STDERR_FILENO))
+ dup2(pager_process.in, STDERR_FILENO);
+ close(pager_process.in);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = wait_for_pager_signal;
+
+ /* this makes sure that the parent terminates after the pager */
+ sigaction(SIGINT, &sa, &pager_process.orig_sigint);
+ sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
+ sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
+ sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
+ sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
+}
+
+/* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
+ */
+void pager_redirect(void)
+{
+ if (pager_process.pid)
+ return; /* already running */
+
+ __setup_pager();
+
+ atexit(wait_for_pager);
+}
+
+/* Setup pager and redirect output, the pager may be closed by pager_close().
+ */
+void pager_open(void)
+{
+ if (pager_process.pid)
+ return; /* already running */
+
+ pager_process.org_out = dup(STDOUT_FILENO);
+ pager_process.org_err = dup(STDERR_FILENO);
+
+ __setup_pager();
+}
+
+/* Close pager and restore original std{out,err}.
+ */
+void pager_close(void)
+{
+ if (pager_process.pid == 0)
+ return;
+
+ wait_for_pager();
+
+ /* restore original output */
+ dup2(pager_process.org_out, STDOUT_FILENO);
+ dup2(pager_process.org_err, STDERR_FILENO);
+
+ close(pager_process.org_out);
+ close(pager_process.org_err);
+
+ /* restore original segnals setting */
+ sigaction(SIGINT, &pager_process.orig_sigint, NULL);
+ sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
+ sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
+ sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
+ sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
+
+ memset(&pager_process, 0, sizeof(pager_process));
+}
+
+#ifdef TEST_PROGRAM_PAGER
+
+#define MAX 255
+
+int main(int argc __attribute__ ((__unused__)),
+ char *argv[] __attribute__ ((__unused__)))
+{
+ int i;
+
+ pager_redirect();
+ for (i = 0; i < MAX; i++)
+ printf("%d\n", i);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_PAGER */
diff --git a/lib/path.c b/lib/path.c
new file mode 100644
index 0000000..42b4ead
--- /dev/null
+++ b/lib/path.c
@@ -0,0 +1,1285 @@
+/*
+ * Simple functions to access files. Paths can be globally prefixed to read
+ * data from an alternative source (e.g. a /proc dump for regression tests).
+ *
+ * The paths is possible to format by printf-like way for functions with "f"
+ * postfix in the name (e.g. readf, openf, ... ul_path_readf_u64()).
+ *
+ * The ul_path_read_* API is possible to use without path_cxt handler. In this
+ * case is not possible to use global prefix and printf-like formatting.
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com> [February 2018]
+ */
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "path.h"
+#include "debug.h"
+#include "strutils.h"
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulpath);
+UL_DEBUG_DEFINE_MASKNAMES(ulpath) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULPATH_DEBUG_INIT (1 << 1)
+#define ULPATH_DEBUG_CXT (1 << 2)
+
+#define DBG(m, x) __UL_DBG(ulpath, ULPATH_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulpath, ULPATH_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpath)
+#include "debugobj.h"
+
+void ul_path_init_debug(void)
+{
+ if (ulpath_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulpath, ULPATH_DEBUG_, 0, ULPATH_DEBUG);
+}
+
+struct path_cxt *ul_new_path(const char *dir, ...)
+{
+ struct path_cxt *pc = calloc(1, sizeof(*pc));
+
+ if (!pc)
+ return NULL;
+
+ DBG(CXT, ul_debugobj(pc, "alloc"));
+
+ pc->refcount = 1;
+ pc->dir_fd = -1;
+
+ if (dir) {
+ int rc;
+ va_list ap;
+
+ va_start(ap, dir);
+ rc = vasprintf(&pc->dir_path, dir, ap);
+ va_end(ap);
+
+ if (rc < 0 || !pc->dir_path)
+ goto fail;
+ }
+ return pc;
+fail:
+ ul_unref_path(pc);
+ return NULL;
+}
+
+void ul_ref_path(struct path_cxt *pc)
+{
+ if (pc)
+ pc->refcount++;
+}
+
+void ul_unref_path(struct path_cxt *pc)
+{
+ if (!pc)
+ return;
+
+ pc->refcount--;
+
+ if (pc->refcount <= 0) {
+ DBG(CXT, ul_debugobj(pc, "dealloc"));
+ if (pc->dialect)
+ pc->free_dialect(pc);
+ ul_path_close_dirfd(pc);
+ free(pc->dir_path);
+ free(pc->prefix);
+ free(pc);
+ }
+}
+
+int ul_path_set_prefix(struct path_cxt *pc, const char *prefix)
+{
+ char *p = NULL;
+
+ assert(pc->dir_fd < 0);
+
+ if (prefix) {
+ p = strdup(prefix);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ free(pc->prefix);
+ pc->prefix = p;
+ DBG(CXT, ul_debugobj(pc, "new prefix: '%s'", p));
+ return 0;
+}
+
+const char *ul_path_get_prefix(struct path_cxt *pc)
+{
+ return pc ? pc->prefix : NULL;
+}
+
+int ul_path_set_dir(struct path_cxt *pc, const char *dir)
+{
+ char *p = NULL;
+
+ if (dir) {
+ p = strdup(dir);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ if (pc->dir_fd >= 0) {
+ close(pc->dir_fd);
+ pc->dir_fd = -1;
+ }
+
+ free(pc->dir_path);
+ pc->dir_path = p;
+ DBG(CXT, ul_debugobj(pc, "new dir: '%s'", p));
+ return 0;
+}
+
+const char *ul_path_get_dir(struct path_cxt *pc)
+{
+ return pc ? pc->dir_path : NULL;
+}
+
+int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *))
+{
+ pc->dialect = data;
+ pc->free_dialect = free_data;
+ DBG(CXT, ul_debugobj(pc, "(re)set dialect"));
+ return 0;
+}
+
+void *ul_path_get_dialect(struct path_cxt *pc)
+{
+ return pc ? pc->dialect : NULL;
+}
+
+int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *))
+{
+ pc->redirect_on_enoent = func;
+ return 0;
+}
+
+static const char *get_absdir(struct path_cxt *pc)
+{
+ int rc;
+ const char *dirpath;
+
+ if (!pc->prefix)
+ return pc->dir_path;
+
+ dirpath = pc->dir_path;
+ if (!dirpath)
+ return pc->prefix;
+ if (*dirpath == '/')
+ dirpath++;
+
+ rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, dirpath);
+ if (rc < 0)
+ return NULL;
+ if ((size_t)rc >= sizeof(pc->path_buffer)) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ return pc->path_buffer;
+}
+
+int ul_path_is_accessible(struct path_cxt *pc)
+{
+ const char *path;
+ assert(pc);
+
+ if (pc->dir_fd >= 0)
+ return 1;
+
+ path = get_absdir(pc);
+ if (!path)
+ return 0;
+ return access(path, F_OK) == 0;
+}
+
+int ul_path_get_dirfd(struct path_cxt *pc)
+{
+ assert(pc);
+ assert(pc->dir_path);
+
+ if (pc->dir_fd < 0) {
+ const char *path = get_absdir(pc);
+ if (!path)
+ return -errno;
+
+ DBG(CXT, ul_debugobj(pc, "opening dir: '%s'", path));
+ pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC);
+ }
+
+ return pc->dir_fd;
+}
+
+/* Note that next ul_path_get_dirfd() will reopen the directory */
+void ul_path_close_dirfd(struct path_cxt *pc)
+{
+ assert(pc);
+
+ if (pc->dir_fd >= 0) {
+ DBG(CXT, ul_debugobj(pc, "closing dir"));
+ close(pc->dir_fd);
+ pc->dir_fd = -1;
+ }
+}
+
+int ul_path_isopen_dirfd(struct path_cxt *pc)
+{
+ return pc && pc->dir_fd >= 0;
+}
+
+static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap)
+{
+ int rc;
+
+ errno = 0;
+
+ rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap);
+ if (rc < 0) {
+ if (!errno)
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if ((size_t)rc >= sizeof(pc->path_buffer)) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ return pc->path_buffer;
+}
+
+char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
+{
+ if (path) {
+ int rc;
+ va_list ap;
+ const char *tail = NULL, *dirpath = pc->dir_path;
+
+ va_start(ap, path);
+ tail = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ if (dirpath && *dirpath == '/')
+ dirpath++;
+ if (tail && *tail == '/')
+ tail++;
+
+ rc = snprintf(buf, bufsz, "%s/%s/%s",
+ pc->prefix ? pc->prefix : "",
+ dirpath ? dirpath : "",
+ tail ? tail : "");
+
+ if ((size_t)rc >= bufsz) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ } else {
+ const char *tmp = get_absdir(pc);
+
+ if (!tmp)
+ return NULL;
+ xstrncpy(buf, tmp, bufsz);
+ }
+
+ return buf;
+}
+
+
+int ul_path_access(struct path_cxt *pc, int mode, const char *path)
+{
+ int rc;
+
+ if (!pc) {
+ rc = access(path, mode);
+ DBG(CXT, ul_debug("access '%s' [no context, rc=%d]", path, rc));
+ } else {
+ int dir = ul_path_get_dirfd(pc);
+ if (dir < 0)
+ return dir;
+ if (*path == '/')
+ path++;
+
+ rc = faccessat(dir, path, mode, 0);
+
+ if (rc && errno == ENOENT
+ && pc->redirect_on_enoent
+ && pc->redirect_on_enoent(pc, path, &dir) == 0)
+ rc = faccessat(dir, path, mode, 0);
+
+ DBG(CXT, ul_debugobj(pc, "access: '%s' [rc=%d]", path, rc));
+ }
+ return rc;
+}
+
+int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
+{
+ va_list ap;
+ const char *p;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_access(pc, mode, p);
+}
+
+int ul_path_stat(struct path_cxt *pc, struct stat *sb, int flags, const char *path)
+{
+ int rc;
+
+ if (!pc) {
+ rc = path ? stat(path, sb) : -EINVAL;
+ DBG(CXT, ul_debug("stat '%s' [no context, rc=%d]", path, rc));
+ } else {
+ int dir = ul_path_get_dirfd(pc);
+ if (dir < 0)
+ return dir;
+ if (path) {
+ if (*path == '/')
+ path++;
+ rc = fstatat(dir, path, sb, flags);
+
+ } else
+ rc = fstat(dir, sb); /* dir itself */
+
+ if (rc && errno == ENOENT
+ && path
+ && pc->redirect_on_enoent
+ && pc->redirect_on_enoent(pc, path, &dir) == 0)
+ rc = fstatat(dir, path, sb, 0);
+
+ DBG(CXT, ul_debugobj(pc, "stat '%s' [rc=%d]", path, rc));
+ }
+ return rc;
+}
+
+int ul_path_open(struct path_cxt *pc, int flags, const char *path)
+{
+ int fd;
+
+ if (!path)
+ return -EINVAL;
+ if (!pc) {
+ fd = open(path, flags);
+ DBG(CXT, ul_debug("opening '%s' [no context]", path));
+ } else {
+ int fdx;
+ int dir = ul_path_get_dirfd(pc);
+ if (dir < 0)
+ return dir;
+
+ if (*path == '/')
+ path++;
+
+ fdx = fd = openat(dir, path, flags);
+
+ if (fd < 0 && errno == ENOENT
+ && pc->redirect_on_enoent
+ && pc->redirect_on_enoent(pc, path, &dir) == 0)
+ fd = openat(dir, path, flags);
+
+ DBG(CXT, ul_debugobj(pc, "opening '%s'%s", path, fdx != fd ? " [redirected]" : ""));
+ }
+ return fd;
+}
+
+int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap)
+{
+ const char *p = ul_path_mkpath(pc, path, ap);
+
+ return !p ? -errno : ul_path_open(pc, flags, p);
+}
+
+int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, path);
+ rc = ul_path_vopenf(pc, flags, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+/*
+ * Maybe stupid, but good enough ;-)
+ */
+static int mode2flags(const char *mode)
+{
+ int flags = 0;
+ const char *p;
+
+ for (p = mode; p && *p; p++) {
+ if (*p == 'r' && *(p + 1) == '+')
+ flags |= O_RDWR;
+ else if (*p == 'r')
+ flags |= O_RDONLY;
+
+ else if (*p == 'w' && *(p + 1) == '+')
+ flags |= O_RDWR | O_TRUNC;
+ else if (*p == 'w')
+ flags |= O_WRONLY | O_TRUNC;
+
+ else if (*p == 'a' && *(p + 1) == '+')
+ flags |= O_RDWR | O_APPEND;
+ else if (*p == 'a')
+ flags |= O_WRONLY | O_APPEND;
+#ifdef O_CLOEXEC
+ else if (*p == *UL_CLOEXECSTR)
+ flags |= O_CLOEXEC;
+#endif
+ }
+
+ return flags;
+}
+
+FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path)
+{
+ int flags = mode2flags(mode);
+ int fd = ul_path_open(pc, flags, path);
+
+ if (fd < 0)
+ return NULL;
+
+ return fdopen(fd, mode);
+}
+
+
+FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap)
+{
+ const char *p = ul_path_mkpath(pc, path, ap);
+
+ return !p ? NULL : ul_path_fopen(pc, mode, p);
+}
+
+FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
+{
+ FILE *f;
+ va_list ap;
+
+ va_start(ap, path);
+ f = ul_path_vfopenf(pc, mode, path, ap);
+ va_end(ap);
+
+ return f;
+}
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendir(struct path_cxt *pc, const char *path)
+{
+ DIR *dir;
+ int fd = -1;
+
+ if (path)
+ fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+ else if (pc->dir_path) {
+ int dirfd;
+
+ DBG(CXT, ul_debugobj(pc, "duplicate dir path"));
+ dirfd = ul_path_get_dirfd(pc);
+ if (dirfd >= 0)
+ fd = dup_fd_cloexec(dirfd, STDERR_FILENO + 1);
+ }
+
+ if (fd < 0)
+ return NULL;
+
+ dir = fdopendir(fd);
+ if (!dir) {
+ close(fd);
+ return NULL;
+ }
+ if (!path)
+ rewinddir(dir);
+ return dir;
+}
+
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap)
+{
+ const char *p = ul_path_mkpath(pc, path, ap);
+
+ return !p ? NULL : ul_path_opendir(pc, p);
+}
+
+/*
+ * Open directory @path in read-onl mode. If the path is NULL then duplicate FD
+ * to the directory addressed by @pc.
+ */
+DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
+{
+ va_list ap;
+ DIR *dir;
+
+ va_start(ap, path);
+ dir = ul_path_vopendirf(pc, path, ap);
+ va_end(ap);
+
+ return dir;
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path)
+{
+ int dirfd;
+ ssize_t ssz;
+
+ if (!path) {
+ const char *p = get_absdir(pc);
+ if (!p)
+ return -errno;
+ ssz = readlink(p, buf, bufsiz - 1);
+ } else {
+ dirfd = ul_path_get_dirfd(pc);
+ if (dirfd < 0)
+ return dirfd;
+
+ if (*path == '/')
+ path++;
+
+ ssz = readlinkat(dirfd, path, buf, bufsiz - 1);
+ }
+
+ if (ssz >= 0)
+ buf[ssz] = '\0';
+ return ssz;
+}
+
+/*
+ * If @path is NULL then readlink is called on @pc directory.
+ */
+ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_readlink(pc, buf, bufsiz, p);
+}
+
+int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path)
+{
+ int rc, errsv;
+ int fd;
+
+ fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ DBG(CXT, ul_debug(" reading '%s'", path));
+ rc = read_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap)
+{
+ const char *p = ul_path_mkpath(pc, path, ap);
+
+ return !p ? -errno : ul_path_read(pc, buf, len, p);
+}
+
+int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, path);
+ rc = ul_path_vreadf(pc, buf, len, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+
+/*
+ * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ
+ * (send patch if you need something bigger;-)
+ *
+ * Returns size of the string without \0, nothing is allocated if returns <= 0.
+ */
+int ul_path_read_string(struct path_cxt *pc, char **str, const char *path)
+{
+ char buf[BUFSIZ];
+ int rc;
+
+ if (!str)
+ return -EINVAL;
+
+ *str = NULL;
+ rc = ul_path_read(pc, buf, sizeof(buf) - 1, path);
+ if (rc < 0)
+ return rc;
+
+ /* Remove tailing newline (usual in sysfs) */
+ if (rc > 0 && *(buf + rc - 1) == '\n')
+ --rc;
+ if (rc == 0)
+ return 0;
+
+ buf[rc] = '\0';
+ *str = strdup(buf);
+ if (!*str)
+ rc = -ENOMEM;
+
+ return rc;
+}
+
+int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_string(pc, str, p);
+}
+
+int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path)
+{
+ int rc = ul_path_read(pc, buf, bufsz - 1, path);
+
+ if (rc == 0)
+ buf[0] = '\0';
+
+ else if (rc > 0) {
+ /* Remove tailing newline (usual in sysfs) */
+ if (*(buf + rc - 1) == '\n')
+ buf[--rc] = '\0';
+ else
+ buf[rc - 1] = '\0';
+ }
+
+ return rc;
+}
+
+int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_buffer(pc, buf, bufsz, p);
+}
+
+int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...)
+{
+ FILE *f;
+ va_list fmt_ap;
+ int rc;
+
+ f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path);
+ if (!f)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug(" fscanf [%s] '%s'", fmt, path));
+
+ va_start(fmt_ap, fmt);
+ rc = vfscanf(f, fmt, fmt_ap);
+ va_end(fmt_ap);
+
+ fclose(f);
+ return rc;
+}
+
+int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
+{
+ FILE *f;
+ va_list fmt_ap;
+ int rc;
+
+ f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+ if (!f)
+ return -EINVAL;
+
+ va_start(fmt_ap, fmt);
+ rc = vfscanf(f, fmt, fmt_ap);
+ va_end(fmt_ap);
+
+ fclose(f);
+ return rc;
+}
+
+
+int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path)
+{
+ int64_t x = 0;
+ int rc;
+
+ rc = ul_path_scanf(pc, path, "%"SCNd64, &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_s64(pc, res, p);
+}
+
+int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path)
+{
+ uint64_t x = 0;
+ int rc;
+
+ rc = ul_path_scanf(pc, path, "%"SCNu64, &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_u64(pc, res, p);
+}
+
+int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path)
+{
+ int rc, x = 0;
+
+ rc = ul_path_scanf(pc, path, "%d", &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_s32(pc, res, p);
+}
+
+int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path)
+{
+ int rc;
+ unsigned int x = 0;
+
+ rc = ul_path_scanf(pc, path, "%u", &x);
+ if (rc != 1)
+ return -1;
+ if (res)
+ *res = x;
+ return 0;
+}
+
+int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_u32(pc, res, p);
+}
+
+int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path)
+{
+ int rc, maj = 0, min = 0;
+
+ rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min);
+ if (rc != 2)
+ return -1;
+ if (res)
+ *res = makedev(maj, min);
+ return 0;
+}
+
+int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_read_majmin(pc, res, p);
+}
+
+int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path)
+{
+ int rc, errsv;
+ int fd;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ rc = write_all(fd, str, strlen(str));
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_write_string(pc, str, p);
+}
+
+int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path)
+{
+ char buf[sizeof(stringify_value(LLONG_MAX))];
+ int rc, errsv;
+ int fd, len;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(buf, sizeof(buf), "%" PRId64, num);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ rc = len < 0 ? -errno : -E2BIG;
+ else
+ rc = write_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path)
+{
+ char buf[sizeof(stringify_value(ULLONG_MAX))];
+ int rc, errsv;
+ int fd, len;
+
+ fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
+ if (fd < 0)
+ return -errno;
+
+ len = snprintf(buf, sizeof(buf), "%" PRIu64, num);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ rc = len < 0 ? -errno : -E2BIG;
+ else
+ rc = write_all(fd, buf, len);
+
+ errsv = errno;
+ close(fd);
+ errno = errsv;
+ return rc;
+}
+
+int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_write_u64(pc, num, p);
+
+}
+
+int ul_path_count_dirents(struct path_cxt *pc, const char *path)
+{
+ DIR *dir;
+ int r = 0;
+
+ dir = ul_path_opendir(pc, path);
+ if (!dir)
+ return 0;
+
+ while (xreaddir(dir)) r++;
+
+ closedir(dir);
+ return r;
+}
+
+int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
+{
+ const char *p;
+ va_list ap;
+
+ va_start(ap, path);
+ p = ul_path_mkpath(pc, path, ap);
+ va_end(ap);
+
+ return !p ? -errno : ul_path_count_dirents(pc, p);
+}
+
+/* first call (when @sub is NULL) opens the directory, last call closes the diretory */
+int ul_path_next_dirent(struct path_cxt *pc, DIR **sub, const char *dirname, struct dirent **d)
+{
+ if (!pc || !sub || !d)
+ return -EINVAL;
+
+ if (!*sub) {
+ *sub = ul_path_opendir(pc, dirname);
+ if (!*sub)
+ return -errno;
+ }
+
+ *d = xreaddir(*sub);
+ if (*d)
+ return 0;
+
+ closedir(*sub);
+ *sub = NULL;
+ return 1;
+}
+
+/*
+ * Like fopen() but, @path is always prefixed by @prefix. This function is
+ * useful in case when ul_path_* API is overkill.
+ */
+FILE *ul_prefix_fopen(const char *prefix, const char *path, const char *mode)
+{
+ char buf[PATH_MAX];
+
+ if (!path)
+ return NULL;
+ if (!prefix)
+ return fopen(path, mode);
+ if (*path == '/')
+ path++;
+
+ snprintf(buf, sizeof(buf), "%s/%s", prefix, path);
+ return fopen(buf, mode);
+}
+
+#ifdef HAVE_CPU_SET_T
+static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap)
+{
+ FILE *f;
+ size_t setsize, len = maxcpus * 7;
+ char buf[len];
+ int rc;
+
+ *set = NULL;
+
+ f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
+ if (!f)
+ return -errno;
+
+ rc = fgets(buf, len, f) == NULL ? -errno : 0;
+ fclose(f);
+
+ if (rc)
+ return rc;
+
+ len = strlen(buf);
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ *set = cpuset_alloc(maxcpus, &setsize, NULL);
+ if (!*set)
+ return -ENOMEM;
+
+ if (islist) {
+ if (cpulist_parse(buf, *set, setsize, 0)) {
+ cpuset_free(*set);
+ return -EINVAL;
+ }
+ } else {
+ if (cpumask_parse(buf, *set, setsize)) {
+ cpuset_free(*set);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ int rc = 0;
+
+ va_start(ap, path);
+ rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
+{
+ va_list ap;
+ int rc = 0;
+
+ va_start(ap, path);
+ rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+#endif /* HAVE_CPU_SET_T */
+
+
+#ifdef TEST_PROGRAM_PATH
+#include <getopt.h>
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fprintf(stdout, " %s [options] <dir> <command>\n\n", program_invocation_short_name);
+ fputs(" -p, --prefix <dir> redirect hardcoded paths to <dir>\n", stdout);
+
+ fputs(" Commands:\n", stdout);
+ fputs(" read-u64 <file> read uint64_t from file\n", stdout);
+ fputs(" read-s64 <file> read int64_t from file\n", stdout);
+ fputs(" read-u32 <file> read uint32_t from file\n", stdout);
+ fputs(" read-s32 <file> read int32_t from file\n", stdout);
+ fputs(" read-string <file> read string from file\n", stdout);
+ fputs(" read-majmin <file> read devno from file\n", stdout);
+ fputs(" read-link <file> read symlink\n", stdout);
+ fputs(" write-string <file> <str> write string from file\n", stdout);
+ fputs(" write-u64 <file> <str> write uint64_t from file\n", stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ const char *prefix = NULL, *dir, *file, *command;
+ struct path_cxt *pc = NULL;
+
+ static const struct option longopts[] = {
+ { "prefix", 1, NULL, 'p' },
+ { "help", 0, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'p':
+ prefix = optarg;
+ break;
+ case 'h':
+ usage();
+ break;
+ default:
+ err(EXIT_FAILURE, "try --help");
+ }
+ }
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<dir> not defined");
+ dir = argv[optind++];
+
+ ul_path_init_debug();
+
+ pc = ul_new_path("%s", dir);
+ if (!pc)
+ err(EXIT_FAILURE, "failed to initialize path context");
+ if (prefix)
+ ul_path_set_prefix(pc, prefix);
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<command> not defined");
+ command = argv[optind++];
+
+ if (strcmp(command, "read-u32") == 0) {
+ uint32_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_u32(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %u\n", file, res);
+
+ if (ul_path_readf_u32(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %u\n", file, res);
+
+ } else if (strcmp(command, "read-s32") == 0) {
+ int32_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_s32(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %d\n", file, res);
+
+ if (ul_path_readf_s32(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %d\n", file, res);
+
+ } else if (strcmp(command, "read-u64") == 0) {
+ uint64_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_u64(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %" PRIu64 "\n", file, res);
+
+ if (ul_path_readf_u64(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %" PRIu64 "\n", file, res);
+
+ } else if (strcmp(command, "read-s64") == 0) {
+ int64_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_s64(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read u64 failed");
+ printf("read: %s: %" PRIu64 "\n", file, res);
+
+ if (ul_path_readf_s64(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf u64 failed");
+ printf("readf: %s: %" PRIu64 "\n", file, res);
+
+ } else if (strcmp(command, "read-majmin") == 0) {
+ dev_t res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_majmin(pc, &res, file) != 0)
+ err(EXIT_FAILURE, "read maj:min failed");
+ printf("read: %s: %d\n", file, (int) res);
+
+ if (ul_path_readf_majmin(pc, &res, "%s", file) != 0)
+ err(EXIT_FAILURE, "readf maj:min failed");
+ printf("readf: %s: %d\n", file, (int) res);
+
+ } else if (strcmp(command, "read-string") == 0) {
+ char *res;
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_read_string(pc, &res, file) <= 0)
+ err(EXIT_FAILURE, "read string failed");
+ printf("read: %s: %s\n", file, res);
+
+ if (ul_path_readf_string(pc, &res, "%s", file) <= 0)
+ err(EXIT_FAILURE, "readf string failed");
+ printf("readf: %s: %s\n", file, res);
+
+ } else if (strcmp(command, "read-link") == 0) {
+ char res[PATH_MAX];
+
+ if (optind == argc)
+ errx(EXIT_FAILURE, "<file> not defined");
+ file = argv[optind++];
+
+ if (ul_path_readlink(pc, res, sizeof(res), file) < 0)
+ err(EXIT_FAILURE, "read symlink failed");
+ printf("read: %s: %s\n", file, res);
+
+ if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0)
+ err(EXIT_FAILURE, "readf symlink failed");
+ printf("readf: %s: %s\n", file, res);
+
+ } else if (strcmp(command, "write-string") == 0) {
+ char *str;
+
+ if (optind + 1 == argc)
+ errx(EXIT_FAILURE, "<file> <string> not defined");
+ file = argv[optind++];
+ str = argv[optind++];
+
+ if (ul_path_write_string(pc, str, file) != 0)
+ err(EXIT_FAILURE, "write string failed");
+ if (ul_path_writef_string(pc, str, "%s", file) != 0)
+ err(EXIT_FAILURE, "writef string failed");
+
+ } else if (strcmp(command, "write-u64") == 0) {
+ uint64_t num;
+
+ if (optind + 1 == argc)
+ errx(EXIT_FAILURE, "<file> <num> not defined");
+ file = argv[optind++];
+ num = strtoumax(argv[optind++], NULL, 0);
+
+ if (ul_path_write_u64(pc, num, file) != 0)
+ err(EXIT_FAILURE, "write u64 failed");
+ if (ul_path_writef_u64(pc, num, "%s", file) != 0)
+ err(EXIT_FAILURE, "writef u64 failed");
+ }
+
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_PATH */
+
diff --git a/lib/plymouth-ctrl.c b/lib/plymouth-ctrl.c
new file mode 100644
index 0000000..2d3deda
--- /dev/null
+++ b/lib/plymouth-ctrl.c
@@ -0,0 +1,144 @@
+/*
+ * plymouth-ctrl.c Simply communications with plymouthd
+ * to avoid forked sub processes and/or
+ * missed plymouth send commands tool
+ * due a plymouthd replacement.
+ *
+ * Copyright (c) 2016 SUSE Linux GmbH, All rights reserved.
+ * Copyright (c) 2016 Werner Fink <werner@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, 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 (see the file COPYING); if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Werner Fink <werner@suse.de>
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "all-io.h"
+#include "c.h"
+#include "nls.h"
+#include "plymouth-ctrl.h"
+
+static int can_read(int fd, const long timeout)
+{
+ struct pollfd fds = {
+ .fd = fd,
+ .events = POLLIN|POLLPRI,
+ .revents = 0,
+ };
+ int ret;
+
+ do {
+ ret = poll(&fds, 1, timeout);
+ } while ((ret < 0) && (errno == EINTR));
+
+ return (ret == 1) && (fds.revents & (POLLIN|POLLPRI));
+}
+
+static int open_un_socket_and_connect(void)
+{
+ /* The abstract UNIX socket of plymouth */
+ struct sockaddr_un su = {
+ .sun_family = AF_UNIX,
+ .sun_path = PLYMOUTH_SOCKET_PATH,
+ };
+ const int one = 1;
+ int fd, ret;
+
+ fd = socket(PF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0) {
+ warnx(_("cannot open UNIX socket"));
+ goto err;
+ }
+
+ ret = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (ret < 0) {
+ warnx(_("cannot set option for UNIX socket"));
+ close(fd);
+ fd = -1;
+ goto err;
+ }
+
+ /* Note, the abstract PLYMOUTH_SOCKET_PATH has a leading NULL byte */
+ ret = connect(fd, (struct sockaddr *) &su,
+ offsetof(struct sockaddr_un, sun_path) + 1 + strlen(su.sun_path+1));
+ if (ret < 0) {
+ if (errno != ECONNREFUSED)
+ warnx(_("cannot connect on UNIX socket"));
+ close(fd);
+ fd = -1;
+ goto err;
+ }
+err:
+ return fd;
+}
+
+int plymouth_command(int cmd, ...)
+{
+ uint8_t answer[2], command[2];
+ struct sigaction sp, op;
+ int fdsock = -1, ret = 0;
+
+ sigemptyset (&sp.sa_mask);
+ sp.sa_handler = SIG_IGN;
+ sp.sa_flags = SA_RESTART;
+ sigaction(SIGPIPE, &sp, &op);
+
+ /* The plymouthd does read at least two bytes. */
+ command[1] = '\0';
+ switch (cmd) {
+ case MAGIC_PING:
+ fdsock = open_un_socket_and_connect();
+ if (fdsock >= 0) {
+ command[0] = cmd;
+ write_all(fdsock, command, sizeof(command));
+ }
+ break;
+ case MAGIC_QUIT:
+ fdsock = open_un_socket_and_connect();
+ if (fdsock >= 0) {
+ command[0] = cmd;
+ write_all(fdsock, command, sizeof(command));
+ }
+ break;
+ default:
+ warnx(_("the plymouth request %c is not implemented"), cmd);
+ case '?':
+ goto err;
+ }
+
+ answer[0] = '\0';
+ if (fdsock >= 0) {
+ if (can_read(fdsock, 1000))
+ read_all(fdsock, (char *) &answer[0], sizeof(answer));
+ close(fdsock);
+ }
+ sigaction(SIGPIPE, &op, NULL);
+ ret = (answer[0] == ANSWER_ACK) ? 1 : 0;
+err:
+ return ret;
+}
+
diff --git a/lib/procfs.c b/lib/procfs.c
new file mode 100644
index 0000000..4d6d25b
--- /dev/null
+++ b/lib/procfs.c
@@ -0,0 +1,564 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/vfs.h>
+#include <errno.h>
+
+#include "c.h"
+#include "pathnames.h"
+#include "procfs.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "debug.h"
+#include "strutils.h"
+#include "statfs_magic.h"
+
+static void procfs_process_deinit_path(struct path_cxt *pc);
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulprocfs);
+UL_DEBUG_DEFINE_MASKNAMES(ulprocfs) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULPROCFS_DEBUG_INIT (1 << 1)
+#define ULPROCFS_DEBUG_CXT (1 << 2)
+
+#define DBG(m, x) __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulprocfs)
+#include "debugobj.h"
+
+void ul_procfs_init_debug(void)
+{
+ if (ulprocfs_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulprocfs, ULPROCFS_DEBUG_, 0, ULPROCFS_DEBUG);
+}
+
+struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix)
+{
+ struct path_cxt *pc = ul_new_path(NULL);
+
+ if (!pc)
+ return NULL;
+ if (prefix)
+ ul_path_set_prefix(pc, prefix);
+
+ if (procfs_process_init_path(pc, pid) != 0) {
+ ul_unref_path(pc);
+ return NULL;
+ }
+
+ DBG(CXT, ul_debugobj(pc, "alloc"));
+ return pc;
+}
+
+/*
+ * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process.
+ *
+ * The function is possible to call in loop and without sysfs_procfs_deinit_path().
+ * The procfs_process_deinit_path() is automatically called by ul_unref_path().
+ *
+ */
+int procfs_process_init_path(struct path_cxt *pc, pid_t pid)
+{
+ struct procfs_process *prc;
+ int rc;
+ char buf[sizeof(_PATH_PROC) + sizeof(stringify_value(UINT32_MAX)) + 2];
+
+ /* define path to pid stuff */
+ snprintf(buf, sizeof(buf), _PATH_PROC "/%zu", (size_t) pid);
+ rc = ul_path_set_dir(pc, buf);
+ if (rc)
+ return rc;
+
+ /* make sure path exists */
+ rc = ul_path_get_dirfd(pc);
+ if (rc < 0)
+ return rc;
+
+ /* initialize procfs specific stuff */
+ prc = ul_path_get_dialect(pc);
+ if (!prc) {
+ DBG(CXT, ul_debugobj(pc, "alloc new procfs handler"));
+ prc = calloc(1, sizeof(struct procfs_process));
+ if (!prc)
+ return -ENOMEM;
+
+ ul_path_set_dialect(pc, prc, procfs_process_deinit_path);
+ }
+
+ DBG(CXT, ul_debugobj(pc, "init procfs stuff"));
+
+ prc->pid = pid;
+ return 0;
+}
+
+static void procfs_process_deinit_path(struct path_cxt *pc)
+{
+ struct procfs_process *prc;
+
+ if (!pc)
+ return;
+
+ DBG(CXT, ul_debugobj(pc, "deinit"));
+
+ prc = ul_path_get_dialect(pc);
+ if (!prc)
+ return;
+
+ free(prc);
+ ul_path_set_dialect(pc, NULL, NULL);
+}
+
+static ssize_t read_procfs_file(int fd, char *buf, size_t bufsz)
+{
+ ssize_t sz = 0;
+ size_t i;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ sz = read_all(fd, buf, bufsz);
+ if (sz <= 0)
+ return sz;
+
+ for (i = 0; i < (size_t) sz; i++) {
+ if (buf[i] == '\0')
+ buf[i] = ' ';
+ }
+ buf[sz - 1] = '\0';
+ return sz;
+}
+
+static ssize_t procfs_process_get_line_for(struct path_cxt *pc, char *buf, size_t bufsz,
+ const char *fname)
+{
+ int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, fname);
+
+ if (fd >= 0) {
+ ssize_t sz = read_procfs_file(fd, buf, bufsz);
+ close(fd);
+ return sz;
+ }
+ return -errno;
+}
+
+ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+ return procfs_process_get_line_for(pc, buf, bufsz, "cmdline");
+}
+
+ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+ return procfs_process_get_line_for(pc, buf, bufsz, "comm");
+}
+
+ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+ return procfs_process_get_line_for(pc, buf, bufsz, "stat");
+}
+
+int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid)
+{
+ struct stat sb;
+ int rc;
+
+ if ((rc = ul_path_stat(pc, &sb, 0, NULL)) == 0)
+ *uid = sb.st_uid;
+ return rc;
+}
+
+/*
+ * returns the next task TID, the @sub is automatically initialized
+ * when called first time and closed after last call or you can
+ * call closedir()* when you need to break the loop.
+ *
+ * Returns: <0 on error, 0 on success, >1 done
+ *
+ * Example:
+ *
+ * pid_t tid;
+ * DIR *sub = NULL;
+ * path_cxt *pc = ul_new_procfs_path(123, NULL);
+ *
+ * while (procfs_process_next_tid(pc, &sub, &tid) == 0)
+ * printf("task: %d", (int) tid);
+ *
+ */
+int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid)
+{
+ struct dirent *d;
+
+ if (!pc || !sub || !tid)
+ return -EINVAL;
+
+ if (!*sub) {
+ *sub = ul_path_opendir(pc, "task");
+ if (!*sub)
+ return -errno;
+ }
+
+ while ((d = xreaddir(*sub))) {
+ if (procfs_dirent_get_pid(d, tid) == 0)
+ return 0;
+ }
+
+ closedir(*sub);
+ *sub = NULL;
+ return 1;
+}
+
+int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd)
+{
+ struct dirent *d;
+
+ if (!pc || !sub || !fd)
+ return -EINVAL;
+
+ if (!*sub) {
+ *sub = ul_path_opendir(pc, "fd");
+ if (!*sub)
+ return -errno;
+ }
+
+ while ((d = xreaddir(*sub))) {
+ uint64_t num;
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_LNK && d->d_type != DT_UNKNOWN)
+ continue;
+#endif
+ if (ul_strtou64(d->d_name, &num, 10) < 0)
+ continue;
+ *fd = num;
+ return 0;
+ }
+
+ closedir(*sub);
+ *sub = NULL;
+ return 1;
+}
+
+/*
+ * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill
+ */
+
+/* stupid, but good enough as a basic filter */
+int procfs_dirent_is_process(struct dirent *d)
+{
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN)
+ return 0;
+#endif
+ if (!isdigit((unsigned char) *d->d_name))
+ return 0;
+
+ return 1;
+}
+
+int procfs_dirent_get_pid(struct dirent *d, pid_t *pid)
+{
+ uint64_t num;
+
+ if (!procfs_dirent_is_process(d))
+ return -EINVAL;
+
+ if (ul_strtou64(d->d_name, &num, 10) < 0)
+ return -EINVAL;
+
+ *pid = (pid_t) num;
+ return 0;
+}
+
+int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid)
+{
+ struct stat st;
+
+ if (!procfs_dirent_is_process(d))
+ return -EINVAL;
+
+ if (fstatat(dirfd(procfs), d->d_name, &st, 0))
+ return -EINVAL;
+
+ *uid = st.st_uid;
+ return 0;
+}
+
+int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid)
+{
+ uid_t x;
+
+ if (procfs_dirent_get_uid(procfs, d, &x) == 0)
+ return x == uid;
+
+ return 0;
+}
+
+/* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME.
+ * The minimal of the @buf has to be 32 bytes. */
+int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz)
+{
+ FILE *f;
+ size_t sz;
+ char tmp[1024], *p, *end = NULL;
+
+ if (bufsz < 32)
+ return -EINVAL;
+ if (!procfs_dirent_is_process(d))
+ return -EINVAL;
+
+ snprintf(tmp, sizeof(tmp), "%s/stat", d->d_name);
+ f = fopen_at(dirfd(procfs), tmp, O_CLOEXEC|O_RDONLY, "r");
+ if (!f)
+ return -errno;
+
+ p = fgets(tmp, sizeof(tmp), f);
+ fclose(f);
+ if (!p)
+ return -errno;
+
+ /* skip PID */
+ while (*p && *p != '(')
+ p++;
+
+ /* skip extra '(' */
+ while (*p && *p == '(')
+ p++;
+
+ end = p;
+ while (*end && *end != ')')
+ end++;
+
+ sz = end - p;
+ if (sz > bufsz)
+ sz = bufsz - 1;
+
+ memcpy(buf, p, sz);
+ buf[sz] = '\0';
+
+ return 0;
+}
+
+int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name)
+{
+ char buf[33];
+
+ if (procfs_dirent_get_name(procfs, d, buf, sizeof(buf)) == 0)
+ return strcmp(name, buf) == 0;
+
+ return 0;
+}
+
+/* checks if fd is file in a procfs;
+ * returns 1 if true, 0 if false or couldn't determine */
+int fd_is_procfs(int fd)
+{
+ struct statfs st;
+ int ret;
+
+ do {
+ errno = 0;
+ ret = fstatfs(fd, &st);
+
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ return 0;
+ xusleep(250000);
+ }
+ } while (ret != 0);
+
+ return st.f_type == STATFS_PROC_MAGIC;
+}
+
+static char *strdup_procfs_file(pid_t pid, const char *name)
+{
+ char buf[BUFSIZ];
+ char *re = NULL;
+ int fd;
+
+ snprintf(buf, sizeof(buf), _PATH_PROC "/%d/%s", (int) pid, name);
+ fd = open(buf, O_CLOEXEC|O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ if (read_procfs_file(fd, buf, sizeof(buf)) > 0)
+ re = strdup(buf);
+ close(fd);
+ return re;
+}
+
+char *pid_get_cmdname(pid_t pid)
+{
+ return strdup_procfs_file(pid, "comm");
+}
+
+char *pid_get_cmdline(pid_t pid)
+{
+ return strdup_procfs_file(pid, "cmdline");
+}
+
+#ifdef TEST_PROGRAM_PROCFS
+
+static int test_tasks(int argc, char *argv[])
+{
+ DIR *sub = NULL;
+ struct path_cxt *pc;
+ pid_t tid = 0, pid;
+
+ if (argc != 2)
+ return EXIT_FAILURE;
+
+ pid = strtol(argv[1], (char **) NULL, 10);
+ printf("PID=%d, TIDs:", pid);
+
+ pc = ul_new_procfs_path(pid, NULL);
+ if (!pc)
+ err(EXIT_FAILURE, "alloc procfs handler failed");
+
+ while (procfs_process_next_tid(pc, &sub, &tid) == 0)
+ printf(" %d", tid);
+
+ printf("\n");
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
+
+static int test_fds(int argc, char *argv[])
+{
+ DIR *sub = NULL;
+ struct path_cxt *pc;
+ pid_t pid;
+ int fd = -1;
+
+ if (argc != 2)
+ return EXIT_FAILURE;
+
+ pid = strtol(argv[1], (char **) NULL, 10);
+ printf("PID=%d, FDs:", pid);
+
+ pc = ul_new_procfs_path(pid, NULL);
+ if (!pc)
+ err(EXIT_FAILURE, "alloc procfs handler failed");
+
+ while (procfs_process_next_fd(pc, &sub, &fd) == 0)
+ printf(" %d", fd);
+
+ fputc('\n', stdout);
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
+
+static int test_processes(int argc, char *argv[])
+{
+ DIR *dir;
+ struct dirent *d;
+ char *name = NULL;
+ uid_t uid = (uid_t) -1;
+ char buf[128];
+
+ if (argc >= 3 && strcmp(argv[1], "--name") == 0)
+ name = argv[2];
+ if (argc >= 3 && strcmp(argv[1], "--uid") == 0)
+ uid = (uid_t) atol(argv[2]);
+
+ dir = opendir(_PATH_PROC);
+ if (!dir)
+ err(EXIT_FAILURE, "cannot open proc");
+
+ while ((d = xreaddir(dir))) {
+ pid_t pid = 0;
+
+ if (procfs_dirent_get_pid(d, &pid) != 0)
+ continue;
+ if (name && !procfs_dirent_match_name(dir, d, name))
+ continue;
+ if (uid != (uid_t) -1 && !procfs_dirent_match_uid(dir, d, uid))
+ continue;
+ procfs_dirent_get_name(dir, d, buf, sizeof(buf));
+ printf(" %d [%s]", pid, buf);
+ }
+
+ fputc('\n', stdout);
+ closedir(dir);
+ return EXIT_SUCCESS;
+}
+
+static int test_one_process(int argc, char *argv[])
+{
+ pid_t pid;
+ struct path_cxt *pc;
+ char buf[BUFSIZ];
+ uid_t uid = (uid_t) -1;
+
+ if (argc != 2)
+ return EXIT_FAILURE;
+ pid = strtol(argv[1], (char **) NULL, 10);
+
+ pc = ul_new_procfs_path(pid, NULL);
+ if (!pc)
+ err(EXIT_FAILURE, "cannot alloc procfs handler");
+
+ printf("%d\n", (int) pid);
+
+ procfs_process_get_uid(pc, &uid);
+ printf(" UID: %zu\n", (size_t) uid);
+
+ procfs_process_get_cmdline(pc, buf, sizeof(buf));
+ printf(" CMDLINE: '%s'\n", buf);
+
+ procfs_process_get_cmdname(pc, buf, sizeof(buf));
+ printf(" COMM: '%s'\n", buf);
+
+ ul_unref_path(pc);
+ return EXIT_SUCCESS;
+}
+
+static int test_isprocfs(int argc, char *argv[])
+{
+ const char *name = argc > 1 ? argv[1] : "/proc";
+ int fd = open(name, O_RDONLY);
+ int is = 0;
+
+ if (fd >= 0) {
+ is = fd_is_procfs(fd);
+ close(fd);
+ } else
+ err(EXIT_FAILURE, "cannot open %s", name);
+
+ printf("%s: %s procfs\n", name, is ? "is" : "is NOT");
+ return is ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %1$s --tasks <pid>\n"
+ " %1$s --fds <pid>\n"
+ " %1$s --is-procfs [<dir>]\n"
+ " %1$s --processes [---name <name>] [--uid <uid>]\n"
+ " %1$s --one <pid>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(argv[1], "--tasks") == 0)
+ return test_tasks(argc - 1, argv + 1);
+ if (strcmp(argv[1], "--fds") == 0)
+ return test_fds(argc - 1, argv + 1);
+ if (strcmp(argv[1], "--processes") == 0)
+ return test_processes(argc - 1, argv + 1);
+ if (strcmp(argv[1], "--is-procfs") == 0)
+ return test_isprocfs(argc - 1, argv + 1);
+ if (strcmp(argv[1], "--one") == 0)
+ return test_one_process(argc - 1, argv + 1);
+
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_PROCUTILS */
diff --git a/lib/pty-session.c b/lib/pty-session.c
new file mode 100644
index 0000000..6f038e1
--- /dev/null
+++ b/lib/pty-session.c
@@ -0,0 +1,749 @@
+/*
+ * This is pseudo-terminal container for child process where parent creates a
+ * proxy between the current std{in,out,etrr} and the child's pty. Advantages:
+ *
+ * - child has no access to parent's terminal (e.g. su --pty)
+ * - parent can log all traffic between user and child's terminal (e.g. script(1))
+ * - it's possible to start commands on terminal although parent has no terminal
+ *
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com> in Jul 2019
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <pty.h>
+#include <poll.h>
+#include <sys/signalfd.h>
+#include <paths.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <inttypes.h>
+
+#include "c.h"
+#include "all-io.h"
+#include "ttyutils.h"
+#include "pty-session.h"
+#include "monotonic.h"
+#include "debug.h"
+
+static UL_DEBUG_DEFINE_MASK(ulpty);
+UL_DEBUG_DEFINE_MASKNAMES(ulpty) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULPTY_DEBUG_INIT (1 << 1)
+#define ULPTY_DEBUG_SETUP (1 << 2)
+#define ULPTY_DEBUG_SIG (1 << 3)
+#define ULPTY_DEBUG_IO (1 << 4)
+#define ULPTY_DEBUG_DONE (1 << 5)
+#define ULPTY_DEBUG_ALL 0xFFFF
+
+#define DBG(m, x) __UL_DBG(ulpty, ULPTY_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulpty, ULPTY_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpty)
+#include "debugobj.h"
+
+void ul_pty_init_debug(int mask)
+{
+ if (ulpty_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulpty, ULPTY_DEBUG_, mask, ULPTY_DEBUG);
+}
+
+struct ul_pty *ul_new_pty(int is_stdin_tty)
+{
+ struct ul_pty *pty = calloc(1, sizeof(*pty));
+
+ if (!pty)
+ return NULL;
+
+ DBG(SETUP, ul_debugobj(pty, "alloc handler"));
+ pty->isterm = is_stdin_tty;
+ pty->master = -1;
+ pty->slave = -1;
+ pty->sigfd = -1;
+ pty->child = (pid_t) -1;
+
+ return pty;
+}
+
+void ul_free_pty(struct ul_pty *pty)
+{
+ free(pty);
+}
+
+void ul_pty_slave_echo(struct ul_pty *pty, int enable)
+{
+ assert(pty);
+ pty->slave_echo = enable ? 1 : 0;
+}
+
+int ul_pty_get_delivered_signal(struct ul_pty *pty)
+{
+ assert(pty);
+ return pty->delivered_signal;
+}
+
+struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty)
+{
+ assert(pty);
+ return &pty->callbacks;
+}
+
+void ul_pty_set_callback_data(struct ul_pty *pty, void *data)
+{
+ assert(pty);
+ pty->callback_data = data;
+}
+
+void ul_pty_set_child(struct ul_pty *pty, pid_t child)
+{
+ assert(pty);
+ pty->child = child;
+}
+
+int ul_pty_get_childfd(struct ul_pty *pty)
+{
+ assert(pty);
+ return pty->master;
+}
+
+pid_t ul_pty_get_child(struct ul_pty *pty)
+{
+ assert(pty);
+ return pty->child;
+}
+
+/* it's active when signals are redirected to sigfd */
+int ul_pty_is_running(struct ul_pty *pty)
+{
+ assert(pty);
+ return pty->sigfd >= 0;
+}
+
+void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv)
+{
+ assert(pty);
+ if (!tv) {
+ DBG(IO, ul_debugobj(pty, "mainloop time: clear"));
+ timerclear(&pty->next_callback_time);
+ } else {
+ pty->next_callback_time.tv_sec = tv->tv_sec;
+ pty->next_callback_time.tv_usec = tv->tv_usec;
+ DBG(IO, ul_debugobj(pty, "mainloop time: %"PRId64".%06"PRId64,
+ (int64_t) tv->tv_sec, (int64_t) tv->tv_usec));
+ }
+}
+
+static void pty_signals_cleanup(struct ul_pty *pty)
+{
+ if (pty->sigfd != -1)
+ close(pty->sigfd);
+ pty->sigfd = -1;
+
+ /* restore original setting */
+ sigprocmask(SIG_SETMASK, &pty->orgsig, NULL);
+}
+
+/* call me before fork() */
+int ul_pty_setup(struct ul_pty *pty)
+{
+ struct termios attrs;
+ sigset_t ourset;
+ int rc = 0;
+
+ assert(pty->sigfd == -1);
+
+ /* save the current signals setting */
+ sigprocmask(0, NULL, &pty->orgsig);
+
+ if (pty->isterm) {
+ DBG(SETUP, ul_debugobj(pty, "create for terminal"));
+
+ /* original setting of the current terminal */
+ if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) {
+ rc = -errno;
+ goto done;
+ }
+
+ attrs = pty->stdin_attrs;
+ if (pty->slave_echo)
+ attrs.c_lflag |= ECHO;
+ else
+ attrs.c_lflag &= ~ECHO;
+
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win);
+ /* create master+slave */
+ rc = openpty(&pty->master, &pty->slave, NULL, &attrs, &pty->win);
+ if (rc)
+ goto done;
+
+ /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */
+ cfmakeraw(&attrs);
+ tcsetattr(STDIN_FILENO, TCSANOW, &attrs);
+ } else {
+ DBG(SETUP, ul_debugobj(pty, "create for non-terminal"));
+
+ rc = openpty(&pty->master, &pty->slave, NULL, NULL, NULL);
+ if (rc)
+ goto done;
+
+ tcgetattr(pty->slave, &attrs);
+
+ if (pty->slave_echo)
+ attrs.c_lflag |= ECHO;
+ else
+ attrs.c_lflag &= ~ECHO;
+
+ tcsetattr(pty->slave, TCSANOW, &attrs);
+ }
+
+ sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
+ rc = -errno;
+ goto done;
+ }
+
+ sigemptyset(&ourset);
+ sigaddset(&ourset, SIGCHLD);
+ sigaddset(&ourset, SIGWINCH);
+ sigaddset(&ourset, SIGALRM);
+ sigaddset(&ourset, SIGTERM);
+ sigaddset(&ourset, SIGINT);
+ sigaddset(&ourset, SIGQUIT);
+
+ if (pty->callbacks.flush_logs)
+ sigaddset(&ourset, SIGUSR1);
+
+ if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0)
+ rc = -errno;
+done:
+ if (rc)
+ ul_pty_cleanup(pty);
+
+ DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]",
+ pty->master, pty->slave, rc));
+ return rc;
+}
+
+/* cleanup in parent process */
+void ul_pty_cleanup(struct ul_pty *pty)
+{
+ struct termios rtt;
+
+ pty_signals_cleanup(pty);
+
+ if (pty->master == -1 || !pty->isterm)
+ return;
+
+ DBG(DONE, ul_debugobj(pty, "cleanup"));
+ rtt = pty->stdin_attrs;
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt);
+}
+
+int ul_pty_chownmod_slave(struct ul_pty *pty, uid_t uid, gid_t gid, mode_t mode)
+{
+ if (fchown(pty->slave, uid, gid))
+ return -errno;
+ if (fchmod(pty->slave, mode))
+ return -errno;
+ return 0;
+}
+
+/* call me in child process */
+void ul_pty_init_slave(struct ul_pty *pty)
+{
+ DBG(SETUP, ul_debugobj(pty, "initialize slave"));
+
+ setsid();
+
+ ioctl(pty->slave, TIOCSCTTY, 1);
+ close(pty->master);
+
+ dup2(pty->slave, STDIN_FILENO);
+ dup2(pty->slave, STDOUT_FILENO);
+ dup2(pty->slave, STDERR_FILENO);
+
+ close(pty->slave);
+
+ if (pty->sigfd >= 0)
+ close(pty->sigfd);
+
+ pty->slave = -1;
+ pty->master = -1;
+ pty->sigfd = -1;
+
+ sigprocmask(SIG_SETMASK, &pty->orgsig, NULL);
+
+ DBG(SETUP, ul_debugobj(pty, "... initialize slave done"));
+}
+
+static int write_output(char *obuf, ssize_t bytes)
+{
+ DBG(IO, ul_debug(" writing output"));
+
+ if (write_all(STDOUT_FILENO, obuf, bytes)) {
+ DBG(IO, ul_debug(" writing output *failed*"));
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int write_to_child(struct ul_pty *pty, char *buf, size_t bufsz)
+{
+ return write_all(pty->master, buf, bufsz);
+}
+
+/*
+ * The pty is usually faster than shell, so it's a good idea to wait until
+ * the previous message has been already read by shell from slave before we
+ * write to master. This is necessary especially for EOF situation when we can
+ * send EOF to master before shell is fully initialized, to workaround this
+ * problem we wait until slave is empty. For example:
+ *
+ * echo "date" | su --pty
+ *
+ * Unfortunately, the child (usually shell) can ignore stdin at all, so we
+ * don't wait forever to avoid dead locks...
+ *
+ * Note that su --pty is primarily designed for interactive sessions as it
+ * maintains master+slave tty stuff within the session. Use pipe to write to
+ * pty and assume non-interactive (tee-like) behavior is NOT well supported.
+ */
+void ul_pty_write_eof_to_child(struct ul_pty *pty)
+{
+ unsigned int tries = 0;
+ struct pollfd fds[] = {
+ { .fd = pty->slave, .events = POLLIN }
+ };
+ char c = DEF_EOF;
+
+ DBG(IO, ul_debugobj(pty, " waiting for empty slave"));
+ while (poll(fds, 1, 10) == 1 && tries < 8) {
+ DBG(IO, ul_debugobj(pty, " slave is not empty"));
+ xusleep(250000);
+ tries++;
+ }
+ if (tries < 8)
+ DBG(IO, ul_debugobj(pty, " slave is empty now"));
+
+ DBG(IO, ul_debugobj(pty, " sending EOF to master"));
+ write_to_child(pty, &c, sizeof(char));
+}
+
+static int mainloop_callback(struct ul_pty *pty)
+{
+ int rc;
+
+ if (!pty->callbacks.mainloop)
+ return 0;
+
+ DBG(IO, ul_debugobj(pty, "calling mainloop callback"));
+ rc = pty->callbacks.mainloop(pty->callback_data);
+
+ DBG(IO, ul_debugobj(pty, " callback done [rc=%d]", rc));
+ return rc;
+}
+
+static int handle_io(struct ul_pty *pty, int fd, int *eof)
+{
+ char buf[BUFSIZ];
+ ssize_t bytes;
+ int rc = 0;
+ sigset_t set;
+
+ DBG(IO, ul_debugobj(pty, " handle I/O on fd=%d", fd));
+ *eof = 0;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGTTIN);
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ /* read from active FD */
+ bytes = read(fd, buf, sizeof(buf));
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ if (bytes < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ return -errno;
+ }
+
+ if (bytes == 0) {
+ *eof = 1;
+ return 0;
+ }
+
+ /* from stdin (user) to command */
+ if (fd == STDIN_FILENO) {
+ DBG(IO, ul_debugobj(pty, " stdin --> master %zd bytes", bytes));
+
+ if (write_to_child(pty, buf, bytes))
+ return -errno;
+
+ /* without sync write_output() will write both input &
+ * shell output that looks like double echoing */
+ fdatasync(pty->master);
+
+ /* from command (master) to stdout */
+ } else if (fd == pty->master) {
+ DBG(IO, ul_debugobj(pty, " master --> stdout %zd bytes", bytes));
+ write_output(buf, bytes);
+ }
+
+ if (pty->callbacks.log_stream_activity)
+ rc = pty->callbacks.log_stream_activity(
+ pty->callback_data, fd, buf, bytes);
+
+ return rc;
+}
+
+void ul_pty_wait_for_child(struct ul_pty *pty)
+{
+ int status;
+ pid_t pid;
+ int options = 0;
+
+ if (pty->child == (pid_t) -1)
+ return;
+
+ DBG(SIG, ul_debug("waiting for child [child=%d]", (int) pty->child));
+
+ if (ul_pty_is_running(pty)) {
+ /* wait for specific child */
+ options = WNOHANG;
+ for (;;) {
+ pid = waitpid(pty->child, &status, options);
+ DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid));
+ if (pid != (pid_t) - 1) {
+ if (pty->callbacks.child_die)
+ pty->callbacks.child_die(
+ pty->callback_data,
+ pty->child, status);
+ ul_pty_set_child(pty, (pid_t) -1);
+ } else
+ break;
+ }
+ } else {
+ /* final wait */
+ while ((pid = waitpid(-1, &status, options)) > 0) {
+ DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid));
+ if (pid == pty->child) {
+ if (pty->callbacks.child_die)
+ pty->callbacks.child_die(
+ pty->callback_data,
+ pty->child, status);
+ ul_pty_set_child(pty, (pid_t) -1);
+ }
+ }
+ }
+}
+
+static int handle_signal(struct ul_pty *pty, int fd)
+{
+ struct signalfd_siginfo info;
+ ssize_t bytes;
+ int rc = 0;
+
+ DBG(SIG, ul_debugobj(pty, " handle signal on fd=%d", fd));
+
+ bytes = read(fd, &info, sizeof(info));
+ if (bytes != sizeof(info)) {
+ if (bytes < 0 && (errno == EAGAIN || errno == EINTR))
+ return 0;
+ return -errno;
+ }
+
+ switch (info.ssi_signo) {
+ case SIGCHLD:
+ DBG(SIG, ul_debugobj(pty, " get signal SIGCHLD"));
+
+ if (info.ssi_code == CLD_EXITED
+ || info.ssi_code == CLD_KILLED
+ || info.ssi_code == CLD_DUMPED) {
+
+ if (pty->callbacks.child_wait)
+ pty->callbacks.child_wait(pty->callback_data,
+ pty->child);
+ else
+ ul_pty_wait_for_child(pty);
+
+ } else if (info.ssi_status == SIGSTOP && pty->child > 0) {
+ pty->callbacks.child_sigstop(pty->callback_data,
+ pty->child);
+ }
+
+ if (pty->child <= 0) {
+ DBG(SIG, ul_debugobj(pty, " no child, setting leaving timeout"));
+ pty->poll_timeout = 10;
+ timerclear(&pty->next_callback_time);
+ }
+ return 0;
+ case SIGWINCH:
+ DBG(SIG, ul_debugobj(pty, " get signal SIGWINCH"));
+ if (pty->isterm) {
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win);
+ ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win);
+
+ if (pty->callbacks.log_signal)
+ rc = pty->callbacks.log_signal(pty->callback_data,
+ &info, (void *) &pty->win);
+ }
+ break;
+ case SIGTERM:
+ /* fallthrough */
+ case SIGINT:
+ /* fallthrough */
+ case SIGQUIT:
+ DBG(SIG, ul_debugobj(pty, " get signal SIG{TERM,INT,QUIT}"));
+ pty->delivered_signal = info.ssi_signo;
+ /* Child termination is going to generate SIGCHLD (see above) */
+ if (pty->child > 0)
+ kill(pty->child, SIGTERM);
+
+ if (pty->callbacks.log_signal)
+ rc = pty->callbacks.log_signal(pty->callback_data,
+ &info, (void *) &pty->win);
+ break;
+ case SIGUSR1:
+ DBG(SIG, ul_debugobj(pty, " get signal SIGUSR1"));
+ if (pty->callbacks.flush_logs)
+ rc = pty->callbacks.flush_logs(pty->callback_data);
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+/* loop in parent */
+int ul_pty_proxy_master(struct ul_pty *pty)
+{
+ int rc = 0, ret, eof = 0;
+ enum {
+ POLLFD_SIGNAL = 0,
+ POLLFD_MASTER,
+ POLLFD_STDIN
+
+ };
+ struct pollfd pfd[] = {
+ [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP },
+ [POLLFD_MASTER] = { .fd = pty->master, .events = POLLIN | POLLERR | POLLHUP },
+ [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP }
+ };
+
+ /* We use signalfd, and standard signals by handlers are completely blocked */
+ assert(pty->sigfd >= 0);
+
+ pfd[POLLFD_SIGNAL].fd = pty->sigfd;
+ pty->poll_timeout = -1;
+
+ while (!pty->delivered_signal) {
+ size_t i;
+ int errsv, timeout;
+
+ DBG(IO, ul_debugobj(pty, "--poll() loop--"));
+
+ /* note, callback usually updates @next_callback_time */
+ if (timerisset(&pty->next_callback_time)) {
+ struct timeval now;
+
+ DBG(IO, ul_debugobj(pty, " callback requested"));
+ gettime_monotonic(&now);
+ if (timercmp(&now, &pty->next_callback_time, >)) {
+ rc = mainloop_callback(pty);
+ if (rc)
+ break;
+ }
+ }
+
+ /* set timeout */
+ if (timerisset(&pty->next_callback_time)) {
+ struct timeval now, rest;
+
+ gettime_monotonic(&now);
+ timersub(&pty->next_callback_time, &now, &rest);
+ timeout = (rest.tv_sec * 1000) + (rest.tv_usec / 1000);
+ } else
+ timeout = pty->poll_timeout;
+
+ /* wait for input, signal or timeout */
+ DBG(IO, ul_debugobj(pty, "calling poll() [timeout=%dms]", timeout));
+ ret = poll(pfd, ARRAY_SIZE(pfd), timeout);
+
+ errsv = errno;
+ DBG(IO, ul_debugobj(pty, "poll() rc=%d", ret));
+
+ /* error */
+ if (ret < 0) {
+ if (errsv == EAGAIN)
+ continue;
+ rc = -errno;
+ break;
+ }
+
+ /* timeout */
+ if (ret == 0) {
+ if (timerisset(&pty->next_callback_time)) {
+ rc = mainloop_callback(pty);
+ if (rc == 0)
+ continue;
+ } else {
+ rc = 0;
+ }
+
+ DBG(IO, ul_debugobj(pty, "leaving poll() loop [timeout=%d, rc=%d]", timeout, rc));
+ break;
+ }
+ /* event */
+ for (i = 0; i < ARRAY_SIZE(pfd); i++) {
+ if (pfd[i].revents == 0)
+ continue;
+
+ DBG(IO, ul_debugobj(pty, " active pfd[%s].fd=%d %s %s %s %s",
+ i == POLLFD_STDIN ? "stdin" :
+ i == POLLFD_MASTER ? "master" :
+ i == POLLFD_SIGNAL ? "signal" : "???",
+ pfd[i].fd,
+ pfd[i].revents & POLLIN ? "POLLIN" : "",
+ pfd[i].revents & POLLHUP ? "POLLHUP" : "",
+ pfd[i].revents & POLLERR ? "POLLERR" : "",
+ pfd[i].revents & POLLNVAL ? "POLLNVAL" : ""));
+
+ if (i == POLLFD_SIGNAL)
+ rc = handle_signal(pty, pfd[i].fd);
+ else if (pfd[i].revents & POLLIN)
+ rc = handle_io(pty, pfd[i].fd, &eof); /* data */
+
+ if (rc) {
+ ul_pty_write_eof_to_child(pty);
+ break;
+ }
+
+ if (i == POLLFD_SIGNAL)
+ continue;
+
+ /* EOF maybe detected in two ways; they are as follows:
+ * A) poll() return POLLHUP event after close()
+ * B) read() returns 0 (no data)
+ *
+ * POLLNVAL means that fd is closed.
+ */
+ if ((pfd[i].revents & POLLHUP) || (pfd[i].revents & POLLNVAL) || eof) {
+ DBG(IO, ul_debugobj(pty, " ignore FD"));
+ pfd[i].fd = -1;
+ if (i == POLLFD_STDIN) {
+ ul_pty_write_eof_to_child(pty);
+ DBG(IO, ul_debugobj(pty, " ignore STDIN"));
+ }
+ }
+ }
+ if (rc)
+ break;
+ }
+
+ if (rc && pty->child && pty->child != (pid_t) -1 && !pty->delivered_signal) {
+ kill(pty->child, SIGTERM);
+ sleep(2);
+ kill(pty->child, SIGKILL);
+ }
+
+ pty_signals_cleanup(pty);
+
+ DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc));
+ return rc;
+}
+
+#ifdef TEST_PROGRAM_PTY
+/*
+ * $ make test_pty
+ * $ ./test_pty
+ *
+ * ... and see for example tty(1) or "ps afu"
+ */
+static void child_sigstop(void *data __attribute__((__unused__)), pid_t child)
+{
+ kill(getpid(), SIGSTOP);
+ kill(child, SIGCONT);
+}
+
+int main(int argc, char *argv[])
+{
+ struct ul_pty_callbacks *cb;
+ const char *shell, *command = NULL, *shname = NULL;
+ int caught_signal = 0;
+ pid_t child;
+ struct ul_pty *pty;
+
+ shell = getenv("SHELL");
+ if (shell == NULL)
+ shell = _PATH_BSHELL;
+ if (argc == 2)
+ command = argv[1];
+
+ ul_pty_init_debug(0);
+
+ pty = ul_new_pty(isatty(STDIN_FILENO));
+ if (!pty)
+ err(EXIT_FAILURE, "failed to allocate PTY handler");
+
+ cb = ul_pty_get_callbacks(pty);
+ cb->child_sigstop = child_sigstop;
+
+ if (ul_pty_setup(pty))
+ err(EXIT_FAILURE, "failed to create pseudo-terminal");
+
+ fflush(stdout); /* ??? */
+
+ switch ((int) (child = fork())) {
+ case -1: /* error */
+ ul_pty_cleanup(pty);
+ err(EXIT_FAILURE, "cannot create child process");
+ break;
+
+ case 0: /* child */
+ ul_pty_init_slave(pty);
+
+ signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */
+
+ shname = strrchr(shell, '/');
+ shname = shname ? shname + 1 : shell;
+
+ if (command)
+ execl(shell, shname, "-c", command, (char *)NULL);
+ else
+ execl(shell, shname, "-i", (char *)NULL);
+ err(EXIT_FAILURE, "failed to execute %s", shell);
+ break;
+
+ default:
+ break;
+ }
+
+ /* parent */
+ ul_pty_set_child(pty, child);
+
+ /* this is the main loop */
+ ul_pty_proxy_master(pty);
+
+ /* all done; cleanup and kill */
+ caught_signal = ul_pty_get_delivered_signal(pty);
+
+ if (!caught_signal && ul_pty_get_child(pty) != (pid_t)-1)
+ ul_pty_wait_for_child(pty); /* final wait */
+
+ if (caught_signal && ul_pty_get_child(pty) != (pid_t)-1) {
+ fprintf(stderr, "\nSession terminated, killing shell...");
+ kill(child, SIGTERM);
+ sleep(2);
+ kill(child, SIGKILL);
+ fprintf(stderr, " ...killed.\n");
+ }
+
+ ul_pty_cleanup(pty);
+ ul_free_pty(pty);
+ return EXIT_SUCCESS;
+}
+
+#endif /* TEST_PROGRAM */
+
diff --git a/lib/pwdutils.c b/lib/pwdutils.c
new file mode 100644
index 0000000..1c1f13e
--- /dev/null
+++ b/lib/pwdutils.c
@@ -0,0 +1,158 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <stdlib.h>
+#include <assert.h>
+
+#include "c.h"
+#include "pwdutils.h"
+#include "xalloc.h"
+
+/* Returns allocated passwd and allocated pwdbuf to store passwd strings
+ * fields. In case of error returns NULL and set errno, for unknown user set
+ * errno to EINVAL
+ */
+struct passwd *xgetpwnam(const char *username, char **pwdbuf)
+{
+ struct passwd *pwd = NULL, *res = NULL;
+ int rc;
+
+ assert(pwdbuf);
+ assert(username);
+
+ *pwdbuf = xmalloc(UL_GETPW_BUFSIZ);
+ pwd = xcalloc(1, sizeof(struct passwd));
+
+ errno = 0;
+ rc = getpwnam_r(username, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res);
+ if (rc != 0) {
+ errno = rc;
+ goto failed;
+ }
+ if (!res) {
+ errno = EINVAL;
+ goto failed;
+ }
+ return pwd;
+failed:
+ free(pwd);
+ free(*pwdbuf);
+ return NULL;
+}
+
+/* Returns allocated group and allocated grpbuf to store group strings
+ * fields. In case of error returns NULL and set errno, for unknown group set
+ * errno to EINVAL
+ */
+struct group *xgetgrnam(const char *groupname, char **grpbuf)
+{
+ struct group *grp = NULL, *res = NULL;
+ int rc;
+
+ assert(grpbuf);
+ assert(groupname);
+
+ *grpbuf = xmalloc(UL_GETPW_BUFSIZ);
+ grp = xcalloc(1, sizeof(struct group));
+
+ errno = 0;
+ rc = getgrnam_r(groupname, grp, *grpbuf, UL_GETPW_BUFSIZ, &res);
+ if (rc != 0) {
+ errno = rc;
+ goto failed;
+ }
+ if (!res) {
+ errno = EINVAL;
+ goto failed;
+ }
+ return grp;
+failed:
+ free(grp);
+ free(*grpbuf);
+ return NULL;
+}
+
+struct passwd *xgetpwuid(uid_t uid, char **pwdbuf)
+{
+ struct passwd *pwd = NULL, *res = NULL;
+ int rc;
+
+ assert(pwdbuf);
+
+ *pwdbuf = xmalloc(UL_GETPW_BUFSIZ);
+ pwd = xcalloc(1, sizeof(struct passwd));
+
+ errno = 0;
+ rc = getpwuid_r(uid, pwd, *pwdbuf, UL_GETPW_BUFSIZ, &res);
+ if (rc != 0) {
+ errno = rc;
+ goto failed;
+ }
+ if (!res) {
+ errno = EINVAL;
+ goto failed;
+ }
+ return pwd;
+failed:
+ free(pwd);
+ free(*pwdbuf);
+ return NULL;
+}
+
+char *xgetlogin(void)
+{
+ struct passwd *pw = NULL;
+ uid_t ruid;
+
+ /* GNU Hurd implementation has an extension where a process can exist in a
+ * non-conforming environment, and thus be outside the realms of POSIX
+ * process identifiers; on this platform, getuid() fails with a status of
+ * (uid_t)(-1) and sets errno if a program is run from a non-conforming
+ * environment.
+ *
+ * http://austingroupbugs.net/view.php?id=511
+ *
+ * The same implementation is useful for other systems, since getlogin(3)
+ * shouldn't be used as actual identification.
+ */
+ errno = 0;
+ ruid = getuid();
+
+ if (errno == 0)
+ pw = getpwuid(ruid);
+ if (pw && pw->pw_name && *pw->pw_name)
+ return xstrdup(pw->pw_name);
+
+ return NULL;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ char *buf = NULL;
+ struct passwd *pwd = NULL;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <username>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ pwd = xgetpwnam(argv[1], &buf);
+ if (!pwd)
+ err(EXIT_FAILURE, "failed to get %s pwd entry", argv[1]);
+
+ printf("Username: %s\n", pwd->pw_name);
+ printf("UID: %d\n", pwd->pw_uid);
+ printf("HOME: %s\n", pwd->pw_dir);
+ printf("GECO: %s\n", pwd->pw_gecos);
+
+ free(pwd);
+ free(buf);
+
+ printf("Current: %s\n", (buf = xgetlogin()));
+ free(buf);
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/randutils.c b/lib/randutils.c
new file mode 100644
index 0000000..2ffe9b4
--- /dev/null
+++ b/lib/randutils.c
@@ -0,0 +1,245 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * General purpose random utilities. Based on libuuid code.
+ *
+ * This code is free software; you can redistribute it and/or modify it under
+ * the terms of the Modified BSD License. The complete text of the license is
+ * available in the Documentation/licenses/COPYING.BSD-3-Clause file.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
+#include "c.h"
+#include "randutils.h"
+#include "nls.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#ifdef HAVE_GETRANDOM
+# include <sys/random.h>
+#elif defined (__linux__)
+# if !defined(SYS_getrandom) && defined(__NR_getrandom)
+ /* usable kernel-headers, but old glibc-headers */
+# define SYS_getrandom __NR_getrandom
+# endif
+#endif
+
+#if !defined(HAVE_GETRANDOM) && defined(SYS_getrandom)
+/* libc without function, but we have syscall */
+#define GRND_NONBLOCK 0x01
+#define GRND_RANDOM 0x02
+static int getrandom(void *buf, size_t buflen, unsigned int flags)
+{
+ return (syscall(SYS_getrandom, buf, buflen, flags));
+}
+# define HAVE_GETRANDOM
+#endif
+
+#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48)
+#define DO_JRAND_MIX
+THREAD_LOCAL unsigned short ul_jrand_seed[3];
+#endif
+
+int rand_get_number(int low_n, int high_n)
+{
+ return rand() % (high_n - low_n + 1) + low_n;
+}
+
+static void crank_random(void)
+{
+ int i;
+ struct timeval tv;
+ unsigned int n_pid, n_uid;
+
+ gettimeofday(&tv, NULL);
+ n_pid = getpid();
+ n_uid = getuid();
+ srand((n_pid << 16) ^ n_uid ^ tv.tv_sec ^ tv.tv_usec);
+
+#ifdef DO_JRAND_MIX
+ ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF);
+ ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF);
+ ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16;
+#endif
+ /* Crank the random number generator a few times */
+ gettimeofday(&tv, NULL);
+ for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--)
+ rand();
+}
+
+int random_get_fd(void)
+{
+ int i, fd;
+
+ fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+ if (fd == -1)
+ fd = open("/dev/random", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+ if (fd >= 0) {
+ i = fcntl(fd, F_GETFD);
+ if (i >= 0)
+ fcntl(fd, F_SETFD, i | FD_CLOEXEC);
+ }
+ crank_random();
+ return fd;
+}
+
+/*
+ * Generate a stream of random nbytes into buf.
+ * Use /dev/urandom if possible, and if not,
+ * use glibc pseudo-random functions.
+ */
+#define UL_RAND_READ_ATTEMPTS 8
+#define UL_RAND_READ_DELAY 125000 /* microseconds */
+
+/*
+ * Write @nbytes random bytes into @buf.
+ *
+ * Returns 0 for good quality of random bytes or 1 for weak quality.
+ */
+int ul_random_get_bytes(void *buf, size_t nbytes)
+{
+ unsigned char *cp = (unsigned char *)buf;
+ size_t i, n = nbytes;
+ int lose_counter = 0;
+
+#ifdef HAVE_GETRANDOM
+ while (n > 0) {
+ int x;
+
+ errno = 0;
+ x = getrandom(cp, n, GRND_NONBLOCK);
+ if (x > 0) { /* success */
+ n -= x;
+ cp += x;
+ lose_counter = 0;
+ errno = 0;
+ } else if (errno == ENOSYS) { /* kernel without getrandom() */
+ break;
+
+ } else if (errno == EAGAIN && lose_counter < UL_RAND_READ_ATTEMPTS) {
+ xusleep(UL_RAND_READ_DELAY); /* no entropy, wait and try again */
+ lose_counter++;
+ } else
+ break;
+ }
+
+ if (errno == ENOSYS)
+#endif
+ /*
+ * We've been built against headers that support getrandom, but the
+ * running kernel does not. Fallback to reading from /dev/{u,}random
+ * as before
+ */
+ {
+ int fd = random_get_fd();
+
+ lose_counter = 0;
+ if (fd >= 0) {
+ while (n > 0) {
+ ssize_t x = read(fd, cp, n);
+ if (x <= 0) {
+ if (lose_counter++ > UL_RAND_READ_ATTEMPTS)
+ break;
+ xusleep(UL_RAND_READ_DELAY);
+ continue;
+ }
+ n -= x;
+ cp += x;
+ lose_counter = 0;
+ }
+
+ close(fd);
+ }
+ }
+ /*
+ * We do this all the time, but this is the only source of
+ * randomness if /dev/random/urandom is out to lunch.
+ */
+ crank_random();
+ for (cp = buf, i = 0; i < nbytes; i++)
+ *cp++ ^= (rand() >> 7) & 0xFF;
+
+#ifdef DO_JRAND_MIX
+ {
+ unsigned short tmp_seed[3];
+
+ memcpy(tmp_seed, ul_jrand_seed, sizeof(tmp_seed));
+ ul_jrand_seed[2] = ul_jrand_seed[2] ^ syscall(__NR_gettid);
+ for (cp = buf, i = 0; i < nbytes; i++)
+ *cp++ ^= (jrand48(tmp_seed) >> 7) & 0xFF;
+ memcpy(ul_jrand_seed, tmp_seed,
+ sizeof(ul_jrand_seed)-sizeof(unsigned short));
+ }
+#endif
+
+ return n != 0;
+}
+
+
+/*
+ * Tell source of randomness.
+ */
+const char *random_tell_source(void)
+{
+#ifdef HAVE_GETRANDOM
+ return _("getrandom() function");
+#else
+ size_t i;
+ static const char *random_sources[] = {
+ "/dev/urandom",
+ "/dev/random"
+ };
+
+ for (i = 0; i < ARRAY_SIZE(random_sources); i++) {
+ if (!access(random_sources[i], R_OK))
+ return random_sources[i];
+ }
+#endif
+ return _("libc pseudo-random functions");
+}
+
+#ifdef TEST_PROGRAM_RANDUTILS
+#include <inttypes.h>
+
+int main(int argc, char *argv[])
+{
+ size_t i, n;
+ int64_t *vp, v;
+ char *buf;
+ size_t bufsz;
+
+ n = argc == 1 ? 16 : atoi(argv[1]);
+
+ printf("Multiple random calls:\n");
+ for (i = 0; i < n; i++) {
+ ul_random_get_bytes(&v, sizeof(v));
+ printf("#%02zu: %25"PRIu64"\n", i, v);
+ }
+
+
+ printf("One random call:\n");
+ bufsz = n * sizeof(*vp);
+ buf = malloc(bufsz);
+ if (!buf)
+ err(EXIT_FAILURE, "failed to allocate buffer");
+
+ ul_random_get_bytes(buf, bufsz);
+ for (i = 0; i < n; i++) {
+ vp = (int64_t *) (buf + (i * sizeof(*vp)));
+ printf("#%02zu: %25"PRIu64"\n", i, *vp);
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_RANDUTILS */
diff --git a/lib/selinux-utils.c b/lib/selinux-utils.c
new file mode 100644
index 0000000..c0df891
--- /dev/null
+++ b/lib/selinux-utils.c
@@ -0,0 +1,85 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com> [January 2021]
+ */
+#include <selinux/context.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "selinux-utils.h"
+
+/* set the SELinux security context used for _creating_ a new file system object
+ *
+ * returns 0 on success,
+ * or <0 on error
+ */
+int ul_setfscreatecon_from_file(char *orig_file)
+{
+ if (is_selinux_enabled() > 0) {
+ char *scontext = NULL;
+
+ if (getfilecon(orig_file, &scontext) < 0)
+ return -1;
+ if (setfscreatecon(scontext) < 0) {
+ freecon(scontext);
+ return -1;
+ }
+ freecon(scontext);
+ }
+ return 0;
+}
+
+/* returns 1 if user has access to @class and @perm ("passwd", "chfn")
+ * or 0 on error,
+ * or 0 if has no access -- in this case sets @user_cxt to user-context
+ */
+int ul_selinux_has_access(const char *classstr, const char *perm, char **user_cxt)
+{
+ char *user;
+ int rc;
+
+ if (user_cxt)
+ *user_cxt = NULL;
+
+ if (getprevcon(&user) != 0)
+ return 0;
+
+ rc = selinux_check_access(user, user, classstr, perm, NULL);
+ if (rc != 0 && user_cxt)
+ *user_cxt = user;
+ else
+ freecon(user);
+
+ return rc == 0 ? 1 : 0;
+}
+
+/* Gets the default context for @path and @st_mode.
+ *
+ * returns 0 on success,
+ * or <0 on error
+ */
+int ul_selinux_get_default_context(const char *path, int st_mode, char **cxt)
+{
+ struct selabel_handle *hnd;
+ struct selinux_opt options[SELABEL_NOPT] = {};
+ int rc = 0;
+
+ *cxt = NULL;
+
+ hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
+ if (!hnd)
+ return -errno;
+
+ if (selabel_lookup(hnd, cxt, path, st_mode) != 0)
+ rc = -errno
+ ;
+ selabel_close(hnd);
+
+ return rc;
+}
diff --git a/lib/sha1.c b/lib/sha1.c
new file mode 100644
index 0000000..22d33b3
--- /dev/null
+++ b/lib/sha1.c
@@ -0,0 +1,256 @@
+/*
+ * SHA-1 in C by Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ *
+ * Test Vectors (from FIPS PUB 180-1)
+ * 1) "abc": A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+ * 2) "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq": 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+ * 3) A million repetitions of "a": 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+ */
+
+#define UL_SHA1HANDSOFF
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "sha1.h"
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+#ifdef WORDS_BIGENDIAN
+# define blk0(i) block->l[i]
+#else
+# define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#endif
+
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void ul_SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
+{
+ uint32_t a, b, c, d, e;
+
+ typedef union {
+ unsigned char c[64];
+ uint32_t l[16];
+ } CHAR64LONG16;
+
+#ifdef UL_SHA1HANDSOFF
+ CHAR64LONG16 block[1]; /* use array to appear as a pointer */
+
+ memcpy(block, buffer, 64);
+#else
+ /* The following had better never be used because it causes the
+ * pointer-to-const buffer to be cast into a pointer to non-const.
+ * And the result is written through. I threw a "const" in, hoping
+ * this will cause a diagnostic.
+ */
+ CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a, b, c, d, e, 0);
+ R0(e, a, b, c, d, 1);
+ R0(d, e, a, b, c, 2);
+ R0(c, d, e, a, b, 3);
+ R0(b, c, d, e, a, 4);
+ R0(a, b, c, d, e, 5);
+ R0(e, a, b, c, d, 6);
+ R0(d, e, a, b, c, 7);
+ R0(c, d, e, a, b, 8);
+ R0(b, c, d, e, a, 9);
+ R0(a, b, c, d, e, 10);
+ R0(e, a, b, c, d, 11);
+ R0(d, e, a, b, c, 12);
+ R0(c, d, e, a, b, 13);
+ R0(b, c, d, e, a, 14);
+ R0(a, b, c, d, e, 15);
+ R1(e, a, b, c, d, 16);
+ R1(d, e, a, b, c, 17);
+ R1(c, d, e, a, b, 18);
+ R1(b, c, d, e, a, 19);
+ R2(a, b, c, d, e, 20);
+ R2(e, a, b, c, d, 21);
+ R2(d, e, a, b, c, 22);
+ R2(c, d, e, a, b, 23);
+ R2(b, c, d, e, a, 24);
+ R2(a, b, c, d, e, 25);
+ R2(e, a, b, c, d, 26);
+ R2(d, e, a, b, c, 27);
+ R2(c, d, e, a, b, 28);
+ R2(b, c, d, e, a, 29);
+ R2(a, b, c, d, e, 30);
+ R2(e, a, b, c, d, 31);
+ R2(d, e, a, b, c, 32);
+ R2(c, d, e, a, b, 33);
+ R2(b, c, d, e, a, 34);
+ R2(a, b, c, d, e, 35);
+ R2(e, a, b, c, d, 36);
+ R2(d, e, a, b, c, 37);
+ R2(c, d, e, a, b, 38);
+ R2(b, c, d, e, a, 39);
+ R3(a, b, c, d, e, 40);
+ R3(e, a, b, c, d, 41);
+ R3(d, e, a, b, c, 42);
+ R3(c, d, e, a, b, 43);
+ R3(b, c, d, e, a, 44);
+ R3(a, b, c, d, e, 45);
+ R3(e, a, b, c, d, 46);
+ R3(d, e, a, b, c, 47);
+ R3(c, d, e, a, b, 48);
+ R3(b, c, d, e, a, 49);
+ R3(a, b, c, d, e, 50);
+ R3(e, a, b, c, d, 51);
+ R3(d, e, a, b, c, 52);
+ R3(c, d, e, a, b, 53);
+ R3(b, c, d, e, a, 54);
+ R3(a, b, c, d, e, 55);
+ R3(e, a, b, c, d, 56);
+ R3(d, e, a, b, c, 57);
+ R3(c, d, e, a, b, 58);
+ R3(b, c, d, e, a, 59);
+ R4(a, b, c, d, e, 60);
+ R4(e, a, b, c, d, 61);
+ R4(d, e, a, b, c, 62);
+ R4(c, d, e, a, b, 63);
+ R4(b, c, d, e, a, 64);
+ R4(a, b, c, d, e, 65);
+ R4(e, a, b, c, d, 66);
+ R4(d, e, a, b, c, 67);
+ R4(c, d, e, a, b, 68);
+ R4(b, c, d, e, a, 69);
+ R4(a, b, c, d, e, 70);
+ R4(e, a, b, c, d, 71);
+ R4(d, e, a, b, c, 72);
+ R4(c, d, e, a, b, 73);
+ R4(b, c, d, e, a, 74);
+ R4(a, b, c, d, e, 75);
+ R4(e, a, b, c, d, 76);
+ R4(d, e, a, b, c, 77);
+ R4(c, d, e, a, b, 78);
+ R4(b, c, d, e, a, 79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef UL_SHA1HANDSOFF
+ memset(block, '\0', sizeof(block));
+#endif
+}
+
+/* SHA1Init - Initialize new context */
+
+void ul_SHA1Init(UL_SHA1_CTX *context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+/* Run your data through this. */
+
+void ul_SHA1Update(UL_SHA1_CTX *context, const unsigned char *data, uint32_t len)
+{
+ uint32_t i;
+
+ uint32_t j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64 - j));
+ ul_SHA1Transform(context->state, context->buffer);
+ for (; i + 63 < len; i += 64) {
+ ul_SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ } else
+ i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+/* Add padding and return the message digest. */
+
+void ul_SHA1Final(unsigned char digest[20], UL_SHA1_CTX *context)
+{
+ unsigned i;
+
+ unsigned char finalcount[8];
+
+ unsigned char c;
+
+#if 0 /* untested "improvement" by DHR */
+ /* Convert context->count to a sequence of bytes
+ * in finalcount. Second element first, but
+ * big-endian order within element.
+ * But we do it all backwards.
+ */
+ unsigned char *fcp = &finalcount[8];
+
+ for (i = 0; i < 2; i++) {
+ uint32_t t = context->count[i];
+
+ int j;
+
+ for (j = 0; j < 4; t >>= 8, j++)
+ *--fcp = (unsigned char)t}
+#else
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
+ }
+#endif
+ c = 0200;
+ ul_SHA1Update(context, &c, 1);
+ while ((context->count[0] & 504) != 448) {
+ c = 0000;
+ ul_SHA1Update(context, &c, 1);
+ }
+ ul_SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+ }
+ /* Wipe variables */
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void ul_SHA1(char *hash_out, const char *str, unsigned len)
+{
+ UL_SHA1_CTX ctx;
+ unsigned int ii;
+
+ ul_SHA1Init(&ctx);
+ for (ii = 0; ii < len; ii += 1)
+ ul_SHA1Update(&ctx, (const unsigned char *)str + ii, 1);
+ ul_SHA1Final((unsigned char *)hash_out, &ctx);
+ hash_out[20] = '\0';
+}
diff --git a/lib/signames.c b/lib/signames.c
new file mode 100644
index 0000000..064776a
--- /dev/null
+++ b/lib/signames.c
@@ -0,0 +1,173 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+
+ * Written by:
+ * Sami Kerola <kerolasa@iki.fi>
+ * Karel Zak <kzak@redhat.com>
+ * Niklas Hambüchen <mail@nh2.me>
+ */
+
+#include <ctype.h> /* for isdigit() */
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "c.h"
+#include "strutils.h"
+#include "signames.h"
+
+static const struct ul_signal_name {
+ const char *name;
+ int val;
+} ul_signames[] = {
+ /* POSIX signals */
+ { "HUP", SIGHUP }, /* 1 */
+ { "INT", SIGINT }, /* 2 */
+ { "QUIT", SIGQUIT }, /* 3 */
+ { "ILL", SIGILL }, /* 4 */
+#ifdef SIGTRAP
+ { "TRAP", SIGTRAP }, /* 5 */
+#endif
+ { "ABRT", SIGABRT }, /* 6 */
+#ifdef SIGIOT
+ { "IOT", SIGIOT }, /* 6, same as SIGABRT */
+#endif
+#ifdef SIGEMT
+ { "EMT", SIGEMT }, /* 7 (mips,alpha,sparc*) */
+#endif
+#ifdef SIGBUS
+ { "BUS", SIGBUS }, /* 7 (arm,i386,m68k,ppc), 10 (mips,alpha,sparc*) */
+#endif
+ { "FPE", SIGFPE }, /* 8 */
+ { "KILL", SIGKILL }, /* 9 */
+ { "USR1", SIGUSR1 }, /* 10 (arm,i386,m68k,ppc), 30 (alpha,sparc*), 16 (mips) */
+ { "SEGV", SIGSEGV }, /* 11 */
+ { "USR2", SIGUSR2 }, /* 12 (arm,i386,m68k,ppc), 31 (alpha,sparc*), 17 (mips) */
+ { "PIPE", SIGPIPE }, /* 13 */
+ { "ALRM", SIGALRM }, /* 14 */
+ { "TERM", SIGTERM }, /* 15 */
+#ifdef SIGSTKFLT
+ { "STKFLT", SIGSTKFLT }, /* 16 (arm,i386,m68k,ppc) */
+#endif
+ { "CHLD", SIGCHLD }, /* 17 (arm,i386,m68k,ppc), 20 (alpha,sparc*), 18 (mips) */
+#ifdef SIGCLD
+ { "CLD", SIGCLD }, /* same as SIGCHLD (mips) */
+#endif
+ { "CONT", SIGCONT }, /* 18 (arm,i386,m68k,ppc), 19 (alpha,sparc*), 25 (mips) */
+ { "STOP", SIGSTOP }, /* 19 (arm,i386,m68k,ppc), 17 (alpha,sparc*), 23 (mips) */
+ { "TSTP", SIGTSTP }, /* 20 (arm,i386,m68k,ppc), 18 (alpha,sparc*), 24 (mips) */
+ { "TTIN", SIGTTIN }, /* 21 (arm,i386,m68k,ppc,alpha,sparc*), 26 (mips) */
+ { "TTOU", SIGTTOU }, /* 22 (arm,i386,m68k,ppc,alpha,sparc*), 27 (mips) */
+#ifdef SIGURG
+ { "URG", SIGURG }, /* 23 (arm,i386,m68k,ppc), 16 (alpha,sparc*), 21 (mips) */
+#endif
+#ifdef SIGXCPU
+ { "XCPU", SIGXCPU }, /* 24 (arm,i386,m68k,ppc,alpha,sparc*), 30 (mips) */
+#endif
+#ifdef SIGXFSZ
+ { "XFSZ", SIGXFSZ }, /* 25 (arm,i386,m68k,ppc,alpha,sparc*), 31 (mips) */
+#endif
+#ifdef SIGVTALRM
+ { "VTALRM", SIGVTALRM }, /* 26 (arm,i386,m68k,ppc,alpha,sparc*), 28 (mips) */
+#endif
+#ifdef SIGPROF
+ { "PROF", SIGPROF }, /* 27 (arm,i386,m68k,ppc,alpha,sparc*), 29 (mips) */
+#endif
+#ifdef SIGWINCH
+ { "WINCH", SIGWINCH }, /* 28 (arm,i386,m68k,ppc,alpha,sparc*), 20 (mips) */
+#endif
+#ifdef SIGIO
+ { "IO", SIGIO }, /* 29 (arm,i386,m68k,ppc), 23 (alpha,sparc*), 22 (mips) */
+#endif
+#ifdef SIGPOLL
+ { "POLL", SIGPOLL }, /* same as SIGIO */
+#endif
+#ifdef SIGINFO
+ { "INFO", SIGINFO }, /* 29 (alpha) */
+#endif
+#ifdef SIGLOST
+ { "LOST", SIGLOST }, /* 29 (arm,i386,m68k,ppc,sparc*) */
+#endif
+#ifdef SIGPWR
+ { "PWR", SIGPWR }, /* 30 (arm,i386,m68k,ppc), 29 (alpha,sparc*), 19 (mips) */
+#endif
+#ifdef SIGUNUSED
+ { "UNUSED", SIGUNUSED }, /* 31 (arm,i386,m68k,ppc) */
+#endif
+#ifdef SIGSYS
+ { "SYS", SIGSYS }, /* 31 (mips,alpha,sparc*) */
+#endif
+};
+
+#ifdef SIGRTMIN
+static int rtsig_to_signum(const char *sig)
+{
+ int num, maxi = 0;
+ char *ep = NULL;
+
+ if (strncasecmp(sig, "min+", 4) == 0)
+ sig += 4;
+ else if (strncasecmp(sig, "max-", 4) == 0) {
+ sig += 4;
+ maxi = 1;
+ }
+ if (!isdigit(*sig))
+ return -1;
+ errno = 0;
+ num = strtol(sig, &ep, 10);
+ if (!ep || sig == ep || errno || num < 0)
+ return -1;
+ num = maxi ? SIGRTMAX - num : SIGRTMIN + num;
+ if (num < SIGRTMIN || SIGRTMAX < num)
+ return -1;
+ return num;
+}
+#endif
+
+int signame_to_signum(const char *sig)
+{
+ size_t n;
+
+ if (!strncasecmp(sig, "sig", 3))
+ sig += 3;
+#ifdef SIGRTMIN
+ /* RT signals */
+ if (!strncasecmp(sig, "rt", 2))
+ return rtsig_to_signum(sig + 2);
+#endif
+ /* Normal signals */
+ for (n = 0; n < ARRAY_SIZE(ul_signames); n++) {
+ if (!strcasecmp(ul_signames[n].name, sig))
+ return ul_signames[n].val;
+ }
+ return -1;
+}
+
+const char *signum_to_signame(int signum)
+{
+ size_t n;
+
+ for (n = 0; n < ARRAY_SIZE(ul_signames); n++) {
+ if (ul_signames[n].val == signum) {
+ return ul_signames[n].name;
+ }
+ }
+
+ return NULL;
+}
+
+int get_signame_by_idx(size_t idx, const char **signame, int *signum)
+{
+ if (idx >= ARRAY_SIZE(ul_signames))
+ return -1;
+
+ if (signame)
+ *signame = ul_signames[idx].name;
+ if (signum)
+ *signum = ul_signames[idx].val;
+ return 0;
+
+}
+
diff --git a/lib/strutils.c b/lib/strutils.c
new file mode 100644
index 0000000..104eb38
--- /dev/null
+++ b/lib/strutils.c
@@ -0,0 +1,1313 @@
+/*
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ *
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "bitops.h"
+#include "pathnames.h"
+
+static int STRTOXX_EXIT_CODE = EXIT_FAILURE;
+
+void strutils_set_exitcode(int ex) {
+ STRTOXX_EXIT_CODE = ex;
+}
+
+static int do_scale_by_power (uintmax_t *x, int base, int power)
+{
+ while (power--) {
+ if (UINTMAX_MAX / base < *x)
+ return -ERANGE;
+ *x *= base;
+ }
+ return 0;
+}
+
+/*
+ * strtosize() - convert string to size (uintmax_t).
+ *
+ * Supported suffixes:
+ *
+ * XiB or X for 2^N
+ * where X = {K,M,G,T,P,E,Z,Y}
+ * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only)
+ * for example:
+ * 10KiB = 10240
+ * 10K = 10240
+ *
+ * XB for 10^N
+ * where X = {K,M,G,T,P,E,Z,Y}
+ * for example:
+ * 10KB = 10000
+ *
+ * The optional 'power' variable returns number associated with used suffix
+ * {K,M,G,T,P,E,Z,Y} = {1,2,3,4,5,6,7,8}.
+ *
+ * The function also supports decimal point, for example:
+ * 0.5MB = 500000
+ * 0.5MiB = 512000
+ *
+ * Note that the function does not accept numbers with '-' (negative sign)
+ * prefix.
+ */
+int parse_size(const char *str, uintmax_t *res, int *power)
+{
+ const char *p;
+ char *end;
+ uintmax_t x, frac = 0;
+ int base = 1024, rc = 0, pwr = 0, frac_zeros = 0;
+
+ static const char *suf = "KMGTPEZY";
+ static const char *suf2 = "kmgtpezy";
+ const char *sp;
+
+ *res = 0;
+
+ if (!str || !*str) {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ /* Only positive numbers are acceptable
+ *
+ * Note that this check is not perfect, it would be better to
+ * use lconv->negative_sign. But coreutils use the same solution,
+ * so it's probably good enough...
+ */
+ p = str;
+ while (isspace((unsigned char) *p))
+ p++;
+ if (*p == '-') {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ errno = 0, end = NULL;
+ x = strtoumax(str, &end, 0);
+
+ if (end == str ||
+ (errno != 0 && (x == UINTMAX_MAX || x == 0))) {
+ rc = errno ? -errno : -EINVAL;
+ goto err;
+ }
+ if (!end || !*end)
+ goto done; /* without suffix */
+ p = end;
+
+ /*
+ * Check size suffixes
+ */
+check_suffix:
+ if (*(p + 1) == 'i' && (*(p + 2) == 'B' || *(p + 2) == 'b') && !*(p + 3))
+ base = 1024; /* XiB, 2^N */
+ else if ((*(p + 1) == 'B' || *(p + 1) == 'b') && !*(p + 2))
+ base = 1000; /* XB, 10^N */
+ else if (*(p + 1)) {
+ struct lconv const *l = localeconv();
+ const char *dp = l ? l->decimal_point : NULL;
+ size_t dpsz = dp ? strlen(dp) : 0;
+
+ if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) {
+ const char *fstr = p + dpsz;
+
+ for (p = fstr; *p == '0'; p++)
+ frac_zeros++;
+ fstr = p;
+ if (isdigit(*fstr)) {
+ errno = 0, end = NULL;
+ frac = strtoumax(fstr, &end, 0);
+ if (end == fstr ||
+ (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) {
+ rc = errno ? -errno : -EINVAL;
+ goto err;
+ }
+ } else
+ end = (char *) p;
+
+ if (frac && (!end || !*end)) {
+ rc = -EINVAL;
+ goto err; /* without suffix, but with frac */
+ }
+ p = end;
+ goto check_suffix;
+ }
+ rc = -EINVAL;
+ goto err; /* unexpected suffix */
+ }
+
+ sp = strchr(suf, *p);
+ if (sp)
+ pwr = (sp - suf) + 1;
+ else {
+ sp = strchr(suf2, *p);
+ if (sp)
+ pwr = (sp - suf2) + 1;
+ else {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+
+ rc = do_scale_by_power(&x, base, pwr);
+ if (power)
+ *power = pwr;
+ if (frac && pwr) {
+ int i;
+ uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1;
+
+ /* mega, giga, ... */
+ do_scale_by_power(&frac_base, base, pwr);
+
+ /* maximal divisor for last digit (e.g. for 0.05 is
+ * frac_div=100, for 0.054 is frac_div=1000, etc.)
+ *
+ * Reduce frac if too large.
+ */
+ while (frac_div < frac) {
+ if (frac_div <= UINTMAX_MAX/10)
+ frac_div *= 10;
+ else
+ frac /= 10;
+ }
+
+ /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */
+ for (i = 0; i < frac_zeros; i++) {
+ if (frac_div <= UINTMAX_MAX/10)
+ frac_div *= 10;
+ else
+ frac /= 10;
+ }
+
+ /*
+ * Go backwardly from last digit and add to result what the
+ * digit represents in the frac_base. For example 0.25G
+ *
+ * 5 means 1GiB / (100/5)
+ * 2 means 1GiB / (10/2)
+ */
+ do {
+ unsigned int seg = frac % 10; /* last digit of the frac */
+ uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */
+
+ frac /= 10; /* remove last digit from frac */
+ frac_poz *= 10;
+
+ if (seg && seg_div / seg)
+ x += frac_base / (seg_div / seg);
+ } while (frac);
+ }
+done:
+ *res = x;
+err:
+ if (rc < 0)
+ errno = -rc;
+ return rc;
+}
+
+int strtosize(const char *str, uintmax_t *res)
+{
+ return parse_size(str, res, NULL);
+}
+
+int isdigit_strend(const char *str, const char **end)
+{
+ const char *p;
+
+ for (p = str; p && *p && isdigit((unsigned char) *p); p++);
+
+ if (end)
+ *end = p;
+ return p && p > str && !*p;
+}
+
+int isxdigit_strend(const char *str, const char **end)
+{
+ const char *p;
+
+ for (p = str; p && *p && isxdigit((unsigned char) *p); p++);
+
+ if (end)
+ *end = p;
+
+ return p && p > str && !*p;
+}
+
+/*
+ * parse_switch(argv[i], "on", "off", "yes", "no", NULL);
+ */
+int parse_switch(const char *arg, const char *errmesg, ...)
+{
+ const char *a, *b;
+ va_list ap;
+
+ va_start(ap, errmesg);
+ do {
+ a = va_arg(ap, char *);
+ if (!a)
+ break;
+ b = va_arg(ap, char *);
+ if (!b)
+ break;
+
+ if (strcmp(arg, a) == 0) {
+ va_end(ap);
+ return 1;
+ }
+
+ if (strcmp(arg, b) == 0) {
+ va_end(ap);
+ return 0;
+ }
+ } while (1);
+ va_end(ap);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, arg);
+}
+
+#ifndef HAVE_MEMPCPY
+void *mempcpy(void *restrict dest, const void *restrict src, size_t n)
+{
+ return ((char *)memcpy(dest, src, n)) + n;
+}
+#endif
+
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+ size_t i;
+
+ for (i = 0; i < maxlen; i++) {
+ if (s[i] == '\0')
+ return i;
+ }
+ return maxlen;
+}
+#endif
+
+#ifndef HAVE_STRNCHR
+char *strnchr(const char *s, size_t maxlen, int c)
+{
+ for (; maxlen-- && *s != '\0'; ++s)
+ if (*s == (char)c)
+ return (char *)s;
+ return NULL;
+}
+#endif
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *s, size_t n)
+{
+ size_t len = strnlen(s, n);
+ char *new = malloc((len + 1) * sizeof(char));
+ if (!new)
+ return NULL;
+ new[len] = '\0';
+ return (char *) memcpy(new, s, len);
+}
+#endif
+
+/*
+ * convert strings to numbers; returns <0 on error, and 0 on success
+ */
+int ul_strtos64(const char *str, int64_t *num, int base)
+{
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ return -EINVAL;
+ *num = (int64_t) strtoimax(str, &end, base);
+
+ if (errno || str == end || (end && *end))
+ return -EINVAL;
+ return 0;
+}
+
+int ul_strtou64(const char *str, uint64_t *num, int base)
+{
+ char *end = NULL;
+ int64_t tmp;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ return -EINVAL;
+
+ /* we need to ignore negative numbers, note that for invalid negative
+ * number strtoimax() returns negative number too, so we do not
+ * need to check errno here */
+ tmp = (int64_t) strtoimax(str, &end, base);
+ if (tmp < 0)
+ errno = ERANGE;
+ else {
+ errno = 0;
+ *num = strtoumax(str, &end, base);
+ }
+
+ if (errno || str == end || (end && *end))
+ return -EINVAL;
+ return 0;
+}
+
+int ul_strtos32(const char *str, int32_t *num, int base)
+{
+ int64_t tmp;
+ int rc;
+
+ rc = ul_strtos64(str, &tmp, base);
+ if (rc == 0 && (tmp < INT32_MIN || tmp > INT32_MAX))
+ rc = -(errno = ERANGE);
+ if (rc == 0)
+ *num = (int32_t) tmp;
+ return rc;
+}
+
+int ul_strtou32(const char *str, uint32_t *num, int base)
+{
+ uint64_t tmp;
+ int rc;
+
+ rc = ul_strtou64(str, &tmp, base);
+ if (rc == 0 && tmp > UINT32_MAX)
+ rc = -(errno = ERANGE);
+ if (rc == 0)
+ *num = (uint32_t) tmp;
+ return rc;
+}
+
+/*
+ * Convert strings to numbers in defined range and print message on error.
+ *
+ * These functions are used when we read input from users (getopt() etc.). It's
+ * better to consolidate the code and keep it all based on 64-bit numbers then
+ * implement it for 32 and 16-bit numbers too.
+ */
+int64_t str2num_or_err(const char *str, int base, const char *errmesg,
+ int64_t low, int64_t up)
+{
+ int64_t num = 0;
+ int rc;
+
+ rc = ul_strtos64(str, &num, base);
+ if (rc == 0 && ((low && num < low) || (up && num > up)))
+ rc = -(errno = ERANGE);
+
+ if (rc) {
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+uint64_t str2unum_or_err(const char *str, int base, const char *errmesg, uint64_t up)
+{
+ uint64_t num = 0;
+ int rc;
+
+ rc = ul_strtou64(str, &num, base);
+ if (rc == 0 && (up && num > up))
+ rc = -(errno = ERANGE);
+
+ if (rc) {
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+ }
+ return num;
+}
+
+double strtod_or_err(const char *str, const char *errmesg)
+{
+ double num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtod(str, &end);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+long double strtold_or_err(const char *str, const char *errmesg)
+{
+ double num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtold(str, &end);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+long strtol_or_err(const char *str, const char *errmesg)
+{
+ long num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtol(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+unsigned long strtoul_or_err(const char *str, const char *errmesg)
+{
+ unsigned long num;
+ char *end = NULL;
+
+ errno = 0;
+ if (str == NULL || *str == '\0')
+ goto err;
+ num = strtoul(str, &end, 10);
+
+ if (errno || str == end || (end && *end))
+ goto err;
+
+ return num;
+err:
+ if (errno == ERANGE)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uintmax_t strtosize_or_err(const char *str, const char *errmesg)
+{
+ uintmax_t num;
+
+ if (strtosize(str, &num) == 0)
+ return num;
+
+ if (errno)
+ err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+ errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+
+void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg)
+{
+ long double user_input;
+
+ user_input = strtold_or_err(str, errmesg);
+ tv->tv_sec = (time_t) user_input;
+ tv->tv_usec = (suseconds_t)((user_input - tv->tv_sec) * 1000000);
+}
+
+time_t strtotime_or_err(const char *str, const char *errmesg)
+{
+ int64_t user_input;
+
+ user_input = strtos64_or_err(str, errmesg);
+ return (time_t) user_input;
+}
+
+/*
+ * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must
+ * be 11 bytes.
+ */
+char *xstrmode(mode_t mode, char *str)
+{
+ unsigned short i = 0;
+
+ if (S_ISDIR(mode))
+ str[i++] = 'd';
+ else if (S_ISLNK(mode))
+ str[i++] = 'l';
+ else if (S_ISCHR(mode))
+ str[i++] = 'c';
+ else if (S_ISBLK(mode))
+ str[i++] = 'b';
+ else if (S_ISSOCK(mode))
+ str[i++] = 's';
+ else if (S_ISFIFO(mode))
+ str[i++] = 'p';
+ else if (S_ISREG(mode))
+ str[i++] = '-';
+
+ str[i++] = mode & S_IRUSR ? 'r' : '-';
+ str[i++] = mode & S_IWUSR ? 'w' : '-';
+ str[i++] = (mode & S_ISUID
+ ? (mode & S_IXUSR ? 's' : 'S')
+ : (mode & S_IXUSR ? 'x' : '-'));
+ str[i++] = mode & S_IRGRP ? 'r' : '-';
+ str[i++] = mode & S_IWGRP ? 'w' : '-';
+ str[i++] = (mode & S_ISGID
+ ? (mode & S_IXGRP ? 's' : 'S')
+ : (mode & S_IXGRP ? 'x' : '-'));
+ str[i++] = mode & S_IROTH ? 'r' : '-';
+ str[i++] = mode & S_IWOTH ? 'w' : '-';
+ str[i++] = (mode & S_ISVTX
+ ? (mode & S_IXOTH ? 't' : 'T')
+ : (mode & S_IXOTH ? 'x' : '-'));
+ str[i] = '\0';
+
+ return str;
+}
+
+/*
+ * returns exponent (2^x=n) in range KiB..EiB (2^10..2^60)
+ */
+static int get_exp(uint64_t n)
+{
+ int shft;
+
+ for (shft = 10; shft <= 60; shft += 10) {
+ if (n < (1ULL << shft))
+ break;
+ }
+ return shft - 10;
+}
+
+char *size_to_human_string(int options, uint64_t bytes)
+{
+ char buf[32];
+ int dec, exp;
+ uint64_t frac;
+ const char *letters = "BKMGTPE";
+ char suffix[sizeof(" KiB")], *psuf = suffix;
+ char c;
+
+ if (options & SIZE_SUFFIX_SPACE)
+ *psuf++ = ' ';
+
+
+ exp = get_exp(bytes);
+ c = *(letters + (exp ? exp / 10 : 0));
+ dec = exp ? bytes / (1ULL << exp) : bytes;
+ frac = exp ? bytes % (1ULL << exp) : 0;
+
+ *psuf++ = c;
+
+ if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) {
+ *psuf++ = 'i';
+ *psuf++ = 'B';
+ }
+
+ *psuf = '\0';
+
+ /* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n",
+ * exp, suffix[0], dec, frac);
+ */
+
+ /* round */
+ if (frac) {
+ /* get 3 digits after decimal point */
+ if (frac >= UINT64_MAX / 1000)
+ frac = ((frac / 1024) * 1000) / (1ULL << (exp - 10)) ;
+ else
+ frac = (frac * 1000) / (1ULL << (exp)) ;
+
+ if (options & SIZE_DECIMAL_2DIGITS) {
+ /* round 4/5 and keep 2 digits after decimal point */
+ frac = (frac + 5) / 10 ;
+ } else {
+ /* round 4/5 and keep 1 digit after decimal point */
+ frac = ((frac + 50) / 100) * 10 ;
+ }
+
+ /* rounding could have overflowed */
+ if (frac == 100) {
+ dec++;
+ frac = 0;
+ }
+ }
+
+ if (frac) {
+ struct lconv const *l = localeconv();
+ char *dp = l ? l->decimal_point : NULL;
+ int len;
+
+ if (!dp || !*dp)
+ dp = ".";
+
+ len = snprintf(buf, sizeof(buf), "%d%s%02" PRIu64, dec, dp, frac);
+ if (len > 0 && (size_t) len < sizeof(buf)) {
+ /* remove potential extraneous zero */
+ if (buf[len - 1] == '0')
+ buf[len--] = '\0';
+ /* append suffix */
+ xstrncpy(buf+len, suffix, sizeof(buf) - len);
+ } else
+ *buf = '\0'; /* snprintf error */
+ } else
+ snprintf(buf, sizeof(buf), "%d%s", dec, suffix);
+
+ return strdup(buf);
+}
+
+/*
+ * Parses comma delimited list to array with IDs, for example:
+ *
+ * "aaa,bbb,ccc" --> ary[0] = FOO_AAA;
+ * ary[1] = FOO_BBB;
+ * ary[3] = FOO_CCC;
+ *
+ * The function name2id() provides conversion from string to ID.
+ *
+ * Returns: >= 0 : number of items added to ary[]
+ * -1 : parse error or unknown item
+ * -2 : arysz reached
+ */
+int string_to_idarray(const char *list, int ary[], size_t arysz,
+ int (name2id)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+ size_t n = 0;
+
+ if (!list || !*list || !ary || !arysz || !name2id)
+ return -1;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ int id;
+
+ if (n >= arysz)
+ return -2;
+ if (!begin)
+ begin = p; /* begin of the column name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ id = name2id(begin, end - begin);
+ if (id == -1)
+ return -1;
+ ary[ n++ ] = id;
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return n;
+}
+
+/*
+ * Parses the array like string_to_idarray but if format is "+aaa,bbb"
+ * it adds fields to array instead of replacing them.
+ */
+int string_add_to_idarray(const char *list, int ary[], size_t arysz,
+ size_t *ary_pos, int (name2id)(const char *, size_t))
+{
+ const char *list_add;
+ int r;
+
+ if (!list || !*list || !ary_pos || *ary_pos > arysz)
+ return -1;
+
+ if (list[0] == '+')
+ list_add = &list[1];
+ else {
+ list_add = list;
+ *ary_pos = 0;
+ }
+
+ r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id);
+ if (r > 0)
+ *ary_pos += r;
+ return r;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2id() function and the 'id' is used
+ * as a position in the 'ary' bit array. It means that the 'id' has to be in
+ * range <0..N> where N < sizeof(ary) * NBBY.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitarray(const char *list,
+ char *ary,
+ int (*name2bit)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+
+ if (!list || !name2bit || !ary)
+ return -EINVAL;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ int bit;
+
+ if (!begin)
+ begin = p; /* begin of the level name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ bit = name2bit(begin, end - begin);
+ if (bit < 0)
+ return bit;
+ setbit(ary, bit);
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2flag() function and the flags is
+ * set to the 'mask'
+*
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitmask(const char *list,
+ unsigned long *mask,
+ long (*name2flag)(const char *, size_t))
+{
+ const char *begin = NULL, *p;
+
+ if (!list || !name2flag || !mask)
+ return -EINVAL;
+
+ for (p = list; p && *p; p++) {
+ const char *end = NULL;
+ long flag;
+
+ if (!begin)
+ begin = p; /* begin of the level name */
+ if (*p == ',')
+ end = p; /* terminate the name */
+ if (*(p + 1) == '\0')
+ end = p + 1; /* end of string */
+ if (!begin || !end)
+ continue;
+ if (end <= begin)
+ return -1;
+
+ flag = name2flag(begin, end - begin);
+ if (flag < 0)
+ return flag; /* error */
+ *mask |= flag;
+ begin = NULL;
+ if (end && !*end)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Parse the lower and higher values in a string containing
+ * "lower:higher" or "lower-higher" format. Note that either
+ * the lower or the higher values may be missing, and the def
+ * value will be assigned to it by default.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int parse_range(const char *str, int *lower, int *upper, int def)
+{
+ char *end = NULL;
+
+ if (!str)
+ return 0;
+
+ *upper = *lower = def;
+ errno = 0;
+
+ if (*str == ':') { /* <:N> */
+ str++;
+ *upper = strtol(str, &end, 10);
+ if (errno || !end || *end || end == str)
+ return -1;
+ } else {
+ *upper = *lower = strtol(str, &end, 10);
+ if (errno || !end || end == str)
+ return -1;
+
+ if (*end == ':' && !*(end + 1)) /* <M:> */
+ *upper = def;
+ else if (*end == '-' || *end == ':') { /* <M:N> <M-N> */
+ str = end + 1;
+ end = NULL;
+ errno = 0;
+ *upper = strtol(str, &end, 10);
+
+ if (errno || !end || *end || end == str)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static const char *next_path_segment(const char *str, size_t *sz)
+{
+ const char *start, *p;
+
+ start = str;
+ *sz = 0;
+ while (start && *start == '/' && *(start + 1) == '/')
+ start++;
+
+ if (!start || !*start)
+ return NULL;
+
+ for (*sz = 1, p = start + 1; *p && *p != '/'; p++) {
+ (*sz)++;
+ }
+
+ return start;
+}
+
+int streq_paths(const char *a, const char *b)
+{
+ while (a && b) {
+ size_t a_sz, b_sz;
+ const char *a_seg = next_path_segment(a, &a_sz);
+ const char *b_seg = next_path_segment(b, &b_sz);
+
+ /*
+ fprintf(stderr, "A>>>(%zu) '%s'\n", a_sz, a_seg);
+ fprintf(stderr, "B>>>(%zu) '%s'\n", b_sz, b_seg);
+ */
+
+ /* end of the path */
+ if (a_sz + b_sz == 0)
+ return 1;
+
+ /* ignore tailing slash */
+ if (a_sz + b_sz == 1 &&
+ ((a_seg && *a_seg == '/') || (b_seg && *b_seg == '/')))
+ return 1;
+
+ if (!a_seg || !b_seg)
+ break;
+ if (a_sz != b_sz || strncmp(a_seg, b_seg, a_sz) != 0)
+ break;
+
+ a = a_seg + a_sz;
+ b = b_seg + b_sz;
+ };
+
+ return 0;
+}
+
+/* concatenate two strings to a new string, the size of the second string is limited by @b */
+char *strnconcat(const char *s, const char *suffix, size_t b)
+{
+ size_t a;
+ char *r;
+
+ if (!s && !suffix)
+ return strdup("");
+ if (!s)
+ return strndup(suffix, b);
+ if (!suffix)
+ return strdup(s);
+
+ assert(s);
+ assert(suffix);
+
+ a = strlen(s);
+ if (b > ((size_t) -1) - a)
+ return NULL;
+
+ r = malloc(a + b + 1);
+ if (!r)
+ return NULL;
+
+ memcpy(r, s, a);
+ memcpy(r + a, suffix, b);
+ r[a+b] = 0;
+
+ return r;
+}
+
+/* concatenate two strings to a new string */
+char *strconcat(const char *s, const char *suffix)
+{
+ return strnconcat(s, suffix, suffix ? strlen(suffix) : 0);
+}
+
+/* concatenate @s and string defined by @format to a new string */
+char *strfconcat(const char *s, const char *format, ...)
+{
+ va_list ap;
+ char *val, *res;
+ int sz;
+
+ va_start(ap, format);
+ sz = vasprintf(&val, format, ap);
+ va_end(ap);
+
+ if (sz < 0)
+ return NULL;
+
+ res = strnconcat(s, val, sz);
+ free(val);
+ return res;
+}
+
+int strappend(char **a, const char *b)
+{
+ size_t al, bl;
+ char *tmp;
+
+ if (!a)
+ return -EINVAL;
+ if (!b || !*b)
+ return 0;
+ if (!*a) {
+ *a = strdup(b);
+ return !*a ? -ENOMEM : 0;
+ }
+
+ al = strlen(*a);
+ bl = strlen(b);
+
+ tmp = realloc(*a, al + bl + 1);
+ if (!tmp)
+ return -ENOMEM;
+ *a = tmp;
+ memcpy((*a) + al, b, bl + 1);
+ return 0;
+}
+
+static size_t strcspn_escaped(const char *s, const char *reject)
+{
+ int escaped = 0;
+ int n;
+
+ for (n=0; s[n]; n++) {
+ if (escaped)
+ escaped = 0;
+ else if (s[n] == '\\')
+ escaped = 1;
+ else if (strchr(reject, s[n]))
+ break;
+ }
+
+ /* if s ends in \, return index of previous char */
+ return n - escaped;
+}
+
+/*
+ * Like strchr() but ignores @c if escaped by '\', '\\' is interpreted like '\'.
+ *
+ * For example for @c='X':
+ *
+ * "abcdXefgXh" --> "XefgXh"
+ * "abcd\XefgXh" --> "Xh"
+ * "abcd\\XefgXh" --> "XefgXh"
+ * "abcd\\\XefgXh" --> "Xh"
+ * "abcd\Xefg\Xh" --> (null)
+ *
+ * "abcd\\XefgXh" --> "\XefgXh" for @c='\\'
+ */
+char *ul_strchr_escaped(const char *s, int c)
+{
+ char *p;
+ int esc = 0;
+
+ for (p = (char *) s; p && *p; p++) {
+ if (!esc && *p == '\\') {
+ esc = 1;
+ continue;
+ }
+ if (*p == c && (!esc || c == '\\'))
+ return p;
+ esc = 0;
+ }
+
+ return NULL;
+}
+
+/* Split a string into words. */
+const char *split(const char **state, size_t *l, const char *separator, int quoted)
+{
+ const char *current;
+
+ current = *state;
+
+ if (!*current) {
+ assert(**state == '\0');
+ return NULL;
+ }
+
+ current += strspn(current, separator);
+ if (!*current) {
+ *state = current;
+ return NULL;
+ }
+
+ if (quoted && strchr("\'\"", *current)) {
+ char quotechars[2] = {*current, '\0'};
+
+ *l = strcspn_escaped(current + 1, quotechars);
+ if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
+ (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
+ /* right quote missing or garbage at the end */
+ *state = current;
+ return NULL;
+ }
+ *state = current++ + *l + 2;
+ } else if (quoted) {
+ *l = strcspn_escaped(current, separator);
+ if (current[*l] && !strchr(separator, current[*l])) {
+ /* unfinished escape */
+ *state = current;
+ return NULL;
+ }
+ *state = current + *l;
+ } else {
+ *l = strcspn(current, separator);
+ *state = current + *l;
+ }
+
+ return current;
+}
+
+/* Rewind file pointer forward to new line. */
+int skip_fline(FILE *fp)
+{
+ int ch;
+
+ do {
+ if ((ch = fgetc(fp)) == EOF)
+ return 1;
+ if (ch == '\n')
+ return 0;
+ } while (1);
+}
+
+
+/* compare two strings, but ignoring non-alnum and case of the characters, for example
+ * "Hello (123)!" is the same as "hello123".
+ */
+int ul_stralnumcmp(const char *p1, const char *p2)
+{
+ const unsigned char *s1 = (const unsigned char *) p1;
+ const unsigned char *s2 = (const unsigned char *) p2;
+ unsigned char c1, c2;
+
+ do {
+ do {
+ c1 = (unsigned char) *s1++;
+ } while (c1 != '\0' && !isalnum((unsigned int) c1));
+
+ do {
+ c2 = (unsigned char) *s2++;
+ } while (c2 != '\0' && !isalnum((unsigned int) c2));
+
+ if (c1 != '\0')
+ c1 = tolower(c1);
+ if (c2 != '\0')
+ c2 = tolower(c2);
+ if (c1 == '\0')
+ return c1 - c2;
+ } while (c1 == c2);
+
+ return c1 - c2;
+}
+
+#ifdef TEST_PROGRAM_STRUTILS
+struct testS {
+ char *name;
+ char *value;
+};
+
+static int test_strdup_to_member(int argc, char *argv[])
+{
+ struct testS *xx;
+
+ if (argc < 3)
+ return EXIT_FAILURE;
+
+ xx = calloc(1, sizeof(*xx));
+ if (!xx)
+ err(EXIT_FAILURE, "calloc() failed");
+
+ strdup_to_struct_member(xx, name, argv[1]);
+ strdup_to_struct_member(xx, value, argv[2]);
+
+ if (strcmp(xx->name, argv[1]) != 0 &&
+ strcmp(xx->value, argv[2]) != 0)
+ errx(EXIT_FAILURE, "strdup_to_struct_member() failed");
+
+ printf("1: '%s', 2: '%s'\n", xx->name, xx->value);
+
+ free(xx->name);
+ free(xx->value);
+ free(xx);
+ return EXIT_SUCCESS;
+}
+
+static int test_strutils_sizes(int argc, char *argv[])
+{
+ uintmax_t size = 0;
+ char *hum1, *hum2, *hum3;
+
+ if (argc < 2)
+ return EXIT_FAILURE;
+
+ if (strtosize(argv[1], &size))
+ errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
+
+ hum1 = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+ hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE, size);
+ hum3 = size_to_human_string(SIZE_SUFFIX_3LETTER |
+ SIZE_SUFFIX_SPACE |
+ SIZE_DECIMAL_2DIGITS, size);
+
+ printf("%25s : %20ju : %8s : %12s : %13s\n", argv[1], size, hum1, hum2, hum3);
+ free(hum1);
+ free(hum2);
+ free(hum3);
+
+ return EXIT_SUCCESS;
+}
+
+static int test_strutils_cmp_paths(int argc, char *argv[])
+{
+ int rc = streq_paths(argv[1], argv[2]);
+
+ if (argc < 3)
+ return EXIT_FAILURE;
+
+ printf("%s: '%s' '%s'\n", rc == 1 ? "YES" : "NOT", argv[1], argv[2]);
+ return EXIT_SUCCESS;
+}
+
+static int test_strutils_normalize(int argc, char *argv[])
+{
+ unsigned char *src, *dst, *org;
+ size_t sz, len;
+
+ if (argc < 2)
+ return EXIT_FAILURE;
+
+ org = (unsigned char *) strdup(argv[1]);
+ src = (unsigned char *) strdup((char *) org);
+ len = strlen((char *) src);
+ dst = malloc(len + 1);
+
+ if (!org || !src || !dst)
+ goto done;
+
+ /* two buffers */
+ sz = __normalize_whitespace(src, len, dst, len + 1);
+ printf("1: '%s' --> '%s' [sz=%zu]\n", src, dst, sz);
+
+ /* one buffer */
+ sz = normalize_whitespace(src);
+ printf("2: '%s' --> '%s' [sz=%zu]\n", org, src, sz);
+
+done:
+ free(src);
+ free(dst);
+ free(org);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc == 3 && strcmp(argv[1], "--size") == 0) {
+ return test_strutils_sizes(argc - 1, argv + 1);
+
+ } else if (argc == 4 && strcmp(argv[1], "--cmp-paths") == 0) {
+ return test_strutils_cmp_paths(argc - 1, argv + 1);
+
+ } else if (argc == 4 && strcmp(argv[1], "--strdup-member") == 0) {
+ return test_strdup_to_member(argc - 1, argv + 1);
+
+ } else if (argc == 4 && strcmp(argv[1], "--stralnumcmp") == 0) {
+ printf("%s\n", ul_stralnumcmp(argv[2], argv[3]) == 0 ?
+ "match" : "dismatch");
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--normalize") == 0) {
+ return test_strutils_normalize(argc - 1, argv + 1);
+
+
+ } else if (argc == 3 && strcmp(argv[1], "--strtos64") == 0) {
+ printf("'%s'-->%jd\n", argv[2], strtos64_or_err(argv[2], "strtos64 failed"));
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--strtou64") == 0) {
+ printf("'%s'-->%ju\n", argv[2], strtou64_or_err(argv[2], "strtou64 failed"));
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--strtos32") == 0) {
+ printf("'%s'-->%d\n", argv[2], strtos32_or_err(argv[2], "strtos32 failed"));
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--strtou32") == 0) {
+ printf("'%s'-->%u\n", argv[2], strtou32_or_err(argv[2], "strtou32 failed"));
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--strtos16") == 0) {
+ printf("'%s'-->%hd\n", argv[2], strtos16_or_err(argv[2], "strtos16 failed"));
+ return EXIT_SUCCESS;
+ } else if (argc == 3 && strcmp(argv[1], "--strtou16") == 0) {
+ printf("'%s'-->%hu\n", argv[2], strtou16_or_err(argv[2], "strtou16 failed"));
+ return EXIT_SUCCESS;
+
+ } else if (argc == 4 && strcmp(argv[1], "--strchr-escaped") == 0) {
+ printf("\"%s\" --> \"%s\"\n", argv[2], ul_strchr_escaped(argv[2], *argv[3]));
+ return EXIT_SUCCESS;
+
+ } else {
+ fprintf(stderr, "usage: %1$s --size <number>[suffix]\n"
+ " %1$s --cmp-paths <path> <path>\n"
+ " %1$s --strdup-member <str> <str>\n"
+ " %1$s --stralnumcmp <str> <str>\n"
+ " %1$s --normalize <str>\n"
+ " %1$s --strto{s,u}{16,32,64} <str>\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM_STRUTILS */
diff --git a/lib/strv.c b/lib/strv.c
new file mode 100644
index 0000000..58a4c97
--- /dev/null
+++ b/lib/strv.c
@@ -0,0 +1,403 @@
+/*
+ *
+ * Copyright 2010 Lennart Poettering
+ *
+ * This 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.
+ *
+ *
+ * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
+ * Modified the original version from systemd project for util-linux.
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include "strutils.h"
+#include "strv.h"
+
+void strv_clear(char **l) {
+ char **k;
+
+ if (!l)
+ return;
+
+ for (k = l; *k; k++)
+ free(*k);
+
+ *l = NULL;
+}
+
+char **strv_free(char **l) {
+ strv_clear(l);
+ free(l);
+ return NULL;
+}
+
+char **strv_copy(char * const *l) {
+ char **r, **k;
+
+ k = r = malloc(sizeof(char *) * (strv_length(l) + 1));
+ if (!r)
+ return NULL;
+
+ if (l)
+ for (; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ *k = NULL;
+ return r;
+}
+
+unsigned strv_length(char * const *l) {
+ unsigned n = 0;
+
+ if (!l)
+ return 0;
+
+ for (; *l; l++)
+ n++;
+
+ return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap) {
+ const char *s;
+ char **a;
+ unsigned n = 0, i = 0;
+ va_list aq;
+
+ /* As a special trick we ignore all listed strings that equal
+ * (const char*) -1. This is supposed to be used with the
+ * STRV_IFNOTNULL() macro to include possibly NULL strings in
+ * the string list. */
+
+ if (x) {
+ n = x == (const char*) -1 ? 0 : 1;
+
+ va_copy(aq, ap);
+ while ((s = va_arg(aq, const char*))) {
+ if (s == (const char*) -1)
+ continue;
+
+ n++;
+ }
+
+ va_end(aq);
+ }
+
+ a = malloc(sizeof(char *) * (n + 1));
+ if (!a)
+ return NULL;
+
+ if (x) {
+ if (x != (const char*) -1) {
+ a[i] = strdup(x);
+ if (!a[i])
+ goto fail;
+ i++;
+ }
+
+ while ((s = va_arg(ap, const char*))) {
+
+ if (s == (const char*) -1)
+ continue;
+
+ a[i] = strdup(s);
+ if (!a[i])
+ goto fail;
+
+ i++;
+ }
+ }
+
+ a[i] = NULL;
+
+ return a;
+
+fail:
+ strv_free(a);
+ return NULL;
+}
+
+char **strv_new(const char *x, ...) {
+ char **r;
+ va_list ap;
+
+ va_start(ap, x);
+ r = strv_new_ap(x, ap);
+ va_end(ap);
+
+ return r;
+}
+
+int strv_extend_strv(char ***a, char **b) {
+ int r;
+ char **s;
+
+ STRV_FOREACH(s, b) {
+ r = strv_extend(a, *s);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
+ int r;
+ char **s;
+
+ STRV_FOREACH(s, b) {
+ char *v;
+
+ v = strconcat(*s, suffix);
+ if (!v)
+ return -ENOMEM;
+
+ r = strv_push(a, v);
+ if (r < 0) {
+ free(v);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+
+#define _FOREACH_WORD(word, length, s, separator, quoted, state) \
+ for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
+ _FOREACH_WORD(word, length, s, separator, false, state)
+
+
+char **strv_split(const char *s, const char *separator) {
+ const char *word, *state;
+ size_t l;
+ unsigned n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_SEPARATOR(word, l, s, separator, state)
+ n++;
+
+ r = malloc(sizeof(char *) * (n + 1));
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_SEPARATOR(word, l, s, separator, state) {
+ r[i] = strndup(word, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char *strv_join(char **l, const char *separator) {
+ char *r, *e;
+ char **s;
+ size_t n, k;
+
+ if (!separator)
+ separator = " ";
+
+ k = strlen(separator);
+
+ n = 0;
+ STRV_FOREACH(s, l) {
+ if (n != 0)
+ n += k;
+ n += strlen(*s);
+ }
+
+ r = malloc(n + 1);
+ if (!r)
+ return NULL;
+
+ e = r;
+ STRV_FOREACH(s, l) {
+ if (e != r)
+ e = stpcpy(e, separator);
+
+ e = stpcpy(e, *s);
+ }
+
+ *e = 0;
+
+ return r;
+}
+
+int strv_push(char ***l, char *value) {
+ char **c;
+ unsigned n, m;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+
+ /* Increase and check for overflow */
+ m = n + 2;
+ if (m < n)
+ return -ENOMEM;
+
+ c = realloc(*l, sizeof(char *) * m);
+ if (!c)
+ return -ENOMEM;
+
+ c[n] = value;
+ c[n+1] = NULL;
+
+ *l = c;
+ return 0;
+}
+
+int strv_push_prepend(char ***l, char *value) {
+ char **c;
+ unsigned n, m, i;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+
+ /* increase and check for overflow */
+ m = n + 2;
+ if (m < n)
+ return -ENOMEM;
+
+ c = malloc(sizeof(char *) * m);
+ if (!c)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++)
+ c[i+1] = (*l)[i];
+
+ c[0] = value;
+ c[n+1] = NULL;
+
+ free(*l);
+ *l = c;
+
+ return 0;
+}
+
+int strv_consume(char ***l, char *value) {
+ int r;
+
+ r = strv_push(l, value);
+ if (r < 0)
+ free(value);
+
+ return r;
+}
+
+int strv_consume_prepend(char ***l, char *value) {
+ int r;
+
+ r = strv_push_prepend(l, value);
+ if (r < 0)
+ free(value);
+
+ return r;
+}
+
+int strv_extend(char ***l, const char *value) {
+ char *v;
+
+ if (!value)
+ return 0;
+
+ v = strdup(value);
+ if (!v)
+ return -ENOMEM;
+
+ return strv_consume(l, v);
+}
+
+char **strv_remove(char **l, const char *s) {
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of s in the string list, edits
+ * in-place. */
+
+ for (f = t = l; *f; f++)
+ if (strcmp(*f, s) == 0)
+ free(*f);
+ else
+ *(t++) = *f;
+
+ *t = NULL;
+ return l;
+}
+
+int strv_extendf(char ***l, const char *format, ...) {
+ va_list ap;
+ char *x;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&x, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return -ENOMEM;
+
+ return strv_consume(l, x);
+}
+
+int strv_extendv(char ***l, const char *format, va_list ap) {
+ char *x;
+ int r;
+
+ r = vasprintf(&x, format, ap);
+ if (r < 0)
+ return -ENOMEM;
+
+ return strv_consume(l, x);
+}
+
+char **strv_reverse(char **l) {
+ unsigned n, i;
+
+ n = strv_length(l);
+ if (n <= 1)
+ return l;
+
+ for (i = 0; i < n / 2; i++) {
+ char *t;
+
+ t = l[i];
+ l[i] = l[n-1-i];
+ l[n-1-i] = t;
+ }
+
+ return l;
+}
diff --git a/lib/swapprober.c b/lib/swapprober.c
new file mode 100644
index 0000000..594496f
--- /dev/null
+++ b/lib/swapprober.c
@@ -0,0 +1,54 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include "c.h"
+#include "nls.h"
+
+#include "swapheader.h"
+#include "swapprober.h"
+
+blkid_probe get_swap_prober(const char *devname)
+{
+ blkid_probe pr;
+ int rc;
+ const char *version = NULL;
+ char *swap_filter[] = { "swap", NULL };
+
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr) {
+ warn(_("%s: unable to probe device"), devname);
+ return NULL;
+ }
+
+ blkid_probe_enable_superblocks(pr, TRUE);
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_VERSION);
+
+ blkid_probe_filter_superblocks_type(pr, BLKID_FLTR_ONLYIN, swap_filter);
+
+ rc = blkid_do_safeprobe(pr);
+ if (rc == -1)
+ warn(_("%s: unable to probe device"), devname);
+ else if (rc == -2)
+ warnx(_("%s: ambiguous probing result; use wipefs(8)"), devname);
+ else if (rc == 1)
+ warnx(_("%s: not a valid swap partition"), devname);
+
+ if (rc == 0) {
+ /* Only the SWAPSPACE2 is supported. */
+ if (blkid_probe_lookup_value(pr, "VERSION", &version, NULL) == 0
+ && version
+ && strcmp(version, stringify_value(SWAP_VERSION)) != 0)
+ warnx(_("%s: unsupported swap version '%s'"),
+ devname, version);
+ else
+ return pr;
+ }
+
+ blkid_free_probe(pr);
+ return NULL;
+}
diff --git a/lib/sysfs.c b/lib/sysfs.c
new file mode 100644
index 0000000..84af4fe
--- /dev/null
+++ b/lib/sysfs.c
@@ -0,0 +1,1172 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "pathnames.h"
+#include "sysfs.h"
+#include "fileutils.h"
+#include "all-io.h"
+#include "debug.h"
+#include "strutils.h"
+
+static void sysfs_blkdev_deinit_path(struct path_cxt *pc);
+static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd);
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+static UL_DEBUG_DEFINE_MASK(ulsysfs);
+UL_DEBUG_DEFINE_MASKNAMES(ulsysfs) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define ULSYSFS_DEBUG_INIT (1 << 1)
+#define ULSYSFS_DEBUG_CXT (1 << 2)
+
+#define DBG(m, x) __UL_DBG(ulsysfs, ULSYSFS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(ulsysfs, ULSYSFS_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulsysfs)
+#include "debugobj.h"
+
+void ul_sysfs_init_debug(void)
+{
+ if (ulsysfs_debug_mask)
+ return;
+ __UL_INIT_DEBUG_FROM_ENV(ulsysfs, ULSYSFS_DEBUG_, 0, ULSYSFS_DEBUG);
+}
+
+struct path_cxt *ul_new_sysfs_path(dev_t devno, struct path_cxt *parent, const char *prefix)
+{
+ struct path_cxt *pc = ul_new_path(NULL);
+
+ if (!pc)
+ return NULL;
+ if (prefix)
+ ul_path_set_prefix(pc, prefix);
+
+ if (sysfs_blkdev_init_path(pc, devno, parent) != 0) {
+ ul_unref_path(pc);
+ return NULL;
+ }
+
+ DBG(CXT, ul_debugobj(pc, "alloc"));
+ return pc;
+}
+
+/*
+ * sysfs_blkdev_* is sysfs extension to ul_path_* API for block devices.
+ *
+ * The function is possible to call in loop and without sysfs_blkdev_deinit_path().
+ * The sysfs_blkdev_deinit_path() is automatically called by ul_unref_path().
+ *
+ */
+int sysfs_blkdev_init_path(struct path_cxt *pc, dev_t devno, struct path_cxt *parent)
+{
+ struct sysfs_blkdev *blk;
+ int rc;
+ char buf[sizeof(_PATH_SYS_DEVBLOCK)
+ + sizeof(stringify_value(UINT32_MAX)) * 2
+ + 3];
+
+ /* define path to devno stuff */
+ snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d", major(devno), minor(devno));
+ rc = ul_path_set_dir(pc, buf);
+ if (rc)
+ return rc;
+
+ /* make sure path exists */
+ rc = ul_path_get_dirfd(pc);
+ if (rc < 0)
+ return rc;
+
+ /* initialize sysfs blkdev specific stuff */
+ blk = ul_path_get_dialect(pc);
+ if (!blk) {
+ DBG(CXT, ul_debugobj(pc, "alloc new sysfs handler"));
+ blk = calloc(1, sizeof(struct sysfs_blkdev));
+ if (!blk)
+ return -ENOMEM;
+
+ ul_path_set_dialect(pc, blk, sysfs_blkdev_deinit_path);
+ ul_path_set_enoent_redirect(pc, sysfs_blkdev_enoent_redirect);
+ }
+
+ DBG(CXT, ul_debugobj(pc, "init sysfs stuff"));
+
+ blk->devno = devno;
+ sysfs_blkdev_set_parent(pc, parent);
+
+ return 0;
+}
+
+static void sysfs_blkdev_deinit_path(struct path_cxt *pc)
+{
+ struct sysfs_blkdev *blk;
+
+ if (!pc)
+ return;
+
+ DBG(CXT, ul_debugobj(pc, "deinit"));
+
+ blk = ul_path_get_dialect(pc);
+ if (!blk)
+ return;
+
+ ul_unref_path(blk->parent);
+ free(blk);
+
+ ul_path_set_dialect(pc, NULL, NULL);
+}
+
+int sysfs_blkdev_set_parent(struct path_cxt *pc, struct path_cxt *parent)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+
+ if (!pc || !blk)
+ return -EINVAL;
+
+ if (blk->parent) {
+ ul_unref_path(blk->parent);
+ blk->parent = NULL;
+ }
+
+ if (parent) {
+ ul_ref_path(parent);
+ blk->parent = parent;
+ } else
+ blk->parent = NULL;
+
+ DBG(CXT, ul_debugobj(pc, "new parent"));
+ return 0;
+}
+
+struct path_cxt *sysfs_blkdev_get_parent(struct path_cxt *pc)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+ return blk ? blk->parent : NULL;
+}
+
+/*
+ * Redirects ENOENT errors to the parent, if the path is to the queue/
+ * sysfs directory. For example
+ *
+ * /sys/dev/block/8:1/queue/logical_block_size redirects to
+ * /sys/dev/block/8:0/queue/logical_block_size
+ */
+static int sysfs_blkdev_enoent_redirect(struct path_cxt *pc, const char *path, int *dirfd)
+{
+ struct sysfs_blkdev *blk = ul_path_get_dialect(pc);
+
+ if (blk && blk->parent && path && strncmp(path, "queue/", 6) == 0) {
+ *dirfd = ul_path_get_dirfd(blk->parent);
+ if (*dirfd >= 0) {
+ DBG(CXT, ul_debugobj(pc, "%s redirected to parent", path));
+ return 0;
+ }
+ }
+ return 1; /* no redirect */
+}
+
+char *sysfs_blkdev_get_name(struct path_cxt *pc, char *buf, size_t bufsiz)
+{
+ char link[PATH_MAX];
+ char *name;
+ ssize_t sz;
+
+ /* read /sys/dev/block/<maj:min> link */
+ sz = ul_path_readlink(pc, link, sizeof(link), NULL);
+ if (sz < 0)
+ return NULL;
+
+ name = strrchr(link, '/');
+ if (!name)
+ return NULL;
+
+ name++;
+ sz = strlen(name);
+ if ((size_t) sz + 1 > bufsiz)
+ return NULL;
+
+ memcpy(buf, name, sz + 1);
+ sysfs_devname_sys_to_dev(buf);
+ return buf;
+}
+
+int sysfs_blkdev_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
+{
+ char path[NAME_MAX + 6 + 1];
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_DIR &&
+ d->d_type != DT_LNK &&
+ d->d_type != DT_UNKNOWN)
+ return 0;
+#endif
+ size_t len = 0;
+
+ if (parent_name) {
+ const char *p = parent_name;
+
+ /* /dev/sda --> "sda" */
+ if (*parent_name == '/') {
+ p = strrchr(parent_name, '/');
+ if (!p)
+ return 0;
+ p++;
+ }
+
+ len = strlen(p);
+ if ((strlen(d->d_name) <= len) || (strncmp(p, d->d_name, len) != 0))
+ len = 0;
+ }
+
+ if (len > 0) {
+ /* partitions subdir name is
+ * "<parent>[:digit:]" or "<parent>p[:digit:]"
+ */
+ return ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
+ || isdigit(*(d->d_name + len)));
+ }
+
+ /* Cannot use /partition file, not supported on old sysfs */
+ snprintf(path, sizeof(path), "%s/start", d->d_name);
+
+ return faccessat(dirfd(dir), path, R_OK, 0) == 0;
+}
+
+int sysfs_blkdev_count_partitions(struct path_cxt *pc, const char *devname)
+{
+ DIR *dir;
+ struct dirent *d;
+ int r = 0;
+
+ dir = ul_path_opendir(pc, NULL);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ if (sysfs_blkdev_is_partition_dirent(dir, d, devname))
+ r++;
+ }
+
+ closedir(dir);
+ return r;
+}
+
+/*
+ * Converts @partno (partition number) to devno of the partition.
+ * The @pc handles wholedisk device.
+ *
+ * Note that this code does not expect any special format of the
+ * partitions devnames.
+ */
+dev_t sysfs_blkdev_partno_to_devno(struct path_cxt *pc, int partno)
+{
+ DIR *dir;
+ struct dirent *d;
+ dev_t devno = 0;
+
+ dir = ul_path_opendir(pc, NULL);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ int n;
+
+ if (!sysfs_blkdev_is_partition_dirent(dir, d, NULL))
+ continue;
+
+ if (ul_path_readf_s32(pc, &n, "%s/partition", d->d_name))
+ continue;
+
+ if (n == partno) {
+ if (ul_path_readf_majmin(pc, &devno, "%s/dev", d->d_name) == 0)
+ break;
+ }
+ }
+
+ closedir(dir);
+ DBG(CXT, ul_debugobj(pc, "partno (%d) -> devno (%d)", (int) partno, (int) devno));
+ return devno;
+}
+
+
+/*
+ * Returns slave name if there is only one slave, otherwise returns NULL.
+ * The result should be deallocated by free().
+ */
+char *sysfs_blkdev_get_slave(struct path_cxt *pc)
+{
+ DIR *dir;
+ struct dirent *d;
+ char *name = NULL;
+
+ dir = ul_path_opendir(pc, "slaves");
+ if (!dir)
+ return NULL;
+
+ while ((d = xreaddir(dir))) {
+ if (name)
+ goto err; /* more slaves */
+ name = strdup(d->d_name);
+ }
+
+ closedir(dir);
+ return name;
+err:
+ free(name);
+ closedir(dir);
+ return NULL;
+}
+
+
+#define SUBSYSTEM_LINKNAME "/subsystem"
+
+/*
+ * For example:
+ *
+ * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
+ * 1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
+ *
+ * The function check if <chain>/subsystem symlink exists, if yes then returns
+ * basename of the readlink result, and remove the last subdirectory from the
+ * <chain> path.
+ */
+static char *get_subsystem(char *chain, char *buf, size_t bufsz)
+{
+ size_t len;
+ char *p;
+
+ if (!chain || !*chain)
+ return NULL;
+
+ len = strlen(chain);
+ if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX)
+ return NULL;
+
+ do {
+ ssize_t sz;
+
+ /* append "/subsystem" to the path */
+ memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME));
+
+ /* try if subsystem symlink exists */
+ sz = readlink(chain, buf, bufsz - 1);
+
+ /* remove last subsystem from chain */
+ chain[len] = '\0';
+ p = strrchr(chain, '/');
+ if (p) {
+ *p = '\0';
+ len = p - chain;
+ }
+
+ if (sz > 0) {
+ /* we found symlink to subsystem, return basename */
+ buf[sz] = '\0';
+ return basename(buf);
+ }
+
+ } while (p);
+
+ return NULL;
+}
+
+/*
+ * Returns complete path to the device, the patch contains all subsystems
+ * used for the device.
+ */
+char *sysfs_blkdev_get_devchain(struct path_cxt *pc, char *buf, size_t bufsz)
+{
+ /* read /sys/dev/block/<maj>:<min> symlink */
+ ssize_t sz = ul_path_readlink(pc, buf, bufsz, NULL);
+ const char *prefix;
+ size_t psz = 0;
+
+ if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz)
+ return NULL;
+
+ sz++;
+ prefix = ul_path_get_prefix(pc);
+ if (prefix)
+ psz = strlen(prefix);
+
+ /* create absolute patch from the link */
+ memmove(buf + psz + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz);
+ if (prefix)
+ memcpy(buf, prefix, psz);
+
+ memcpy(buf + psz, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1);
+ return buf;
+}
+
+/*
+ * The @subsys returns the next subsystem in the chain. Function modifies
+ * @devchain string.
+ *
+ * Returns: 0 in success, <0 on error, 1 on end of chain
+ */
+int sysfs_blkdev_next_subsystem(struct path_cxt *pc __attribute__((unused)),
+ char *devchain, char **subsys)
+{
+ char subbuf[PATH_MAX];
+ char *sub;
+
+ if (!subsys || !devchain)
+ return -EINVAL;
+
+ *subsys = NULL;
+
+ while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) {
+ *subsys = strdup(sub);
+ if (!*subsys)
+ return -ENOMEM;
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static int is_hotpluggable_subsystem(const char *name)
+{
+ static const char * const hotplug_subsystems[] = {
+ "usb",
+ "ieee1394",
+ "pcmcia",
+ "mmc",
+ "ccw"
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++)
+ if (strcmp(name, hotplug_subsystems[i]) == 0)
+ return 1;
+
+ return 0;
+}
+
+int sysfs_blkdev_is_hotpluggable(struct path_cxt *pc)
+{
+ char buf[PATH_MAX], *chain, *sub;
+ int rc = 0;
+
+
+ /* check /sys/dev/block/<maj>:<min>/removable attribute */
+ if (ul_path_read_s32(pc, &rc, "removable") == 0 && rc == 1)
+ return 1;
+
+ chain = sysfs_blkdev_get_devchain(pc, buf, sizeof(buf));
+
+ while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
+ rc = is_hotpluggable_subsystem(sub);
+ if (rc) {
+ free(sub);
+ break;
+ }
+ free(sub);
+ }
+
+ return rc;
+}
+
+static int get_dm_wholedisk(struct path_cxt *pc, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ int rc = 0;
+ char *name;
+
+ /* Note, sysfs_blkdev_get_slave() returns the first slave only,
+ * if there is more slaves, then return NULL
+ */
+ name = sysfs_blkdev_get_slave(pc);
+ if (!name)
+ return -1;
+
+ if (diskname && len)
+ xstrncpy(diskname, name, len);
+
+ if (diskdevno) {
+ *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
+ if (!*diskdevno)
+ rc = -1;
+ }
+
+ free(name);
+ return rc;
+}
+
+/*
+ * Returns by @diskdevno whole disk device devno and (optionally) by
+ * @diskname the whole disk device name.
+ */
+int sysfs_blkdev_get_wholedisk( struct path_cxt *pc,
+ char *diskname,
+ size_t len,
+ dev_t *diskdevno)
+{
+ int is_part = 0;
+
+ if (!pc)
+ return -1;
+
+ is_part = ul_path_access(pc, F_OK, "partition") == 0;
+ if (!is_part) {
+ /*
+ * Extra case for partitions mapped by device-mapper.
+ *
+ * All regular partitions (added by BLKPG ioctl or kernel PT
+ * parser) have the /sys/.../partition file. The partitions
+ * mapped by DM don't have such file, but they have "part"
+ * prefix in DM UUID.
+ */
+ char *uuid = NULL, *tmp, *prefix;
+
+ ul_path_read_string(pc, &uuid, "dm/uuid");
+ tmp = uuid;
+ prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+ if (prefix && strncasecmp(prefix, "part", 4) == 0)
+ is_part = 1;
+ free(uuid);
+
+ if (is_part &&
+ get_dm_wholedisk(pc, diskname, len, diskdevno) == 0)
+ /*
+ * partitioned device, mapped by DM
+ */
+ goto done;
+
+ is_part = 0;
+ }
+
+ if (!is_part) {
+ /*
+ * unpartitioned device
+ */
+ if (diskname && !sysfs_blkdev_get_name(pc, diskname, len))
+ goto err;
+ if (diskdevno)
+ *diskdevno = sysfs_blkdev_get_devno(pc);
+
+ } else {
+ /*
+ * partitioned device
+ * - readlink /sys/dev/block/8:1 = ../../block/sda/sda1
+ * - dirname ../../block/sda/sda1 = ../../block/sda
+ * - basename ../../block/sda = sda
+ */
+ char linkpath[PATH_MAX];
+ char *name;
+ ssize_t linklen;
+
+ linklen = ul_path_readlink(pc, linkpath, sizeof(linkpath), NULL);
+ if (linklen < 0)
+ goto err;
+
+ stripoff_last_component(linkpath); /* dirname */
+ name = stripoff_last_component(linkpath); /* basename */
+ if (!name)
+ goto err;
+
+ sysfs_devname_sys_to_dev(name);
+ if (diskname && len)
+ xstrncpy(diskname, name, len);
+
+ if (diskdevno) {
+ *diskdevno = __sysfs_devname_to_devno(ul_path_get_prefix(pc), name, NULL);
+ if (!*diskdevno)
+ goto err;
+ }
+ }
+
+done:
+ return 0;
+err:
+ return -1;
+}
+
+int sysfs_devno_to_wholedisk(dev_t devno, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ struct path_cxt *pc;
+ int rc = 0;
+
+ if (!devno)
+ return -EINVAL;
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ return -ENOMEM;
+
+ rc = sysfs_blkdev_get_wholedisk(pc, diskname, len, diskdevno);
+ ul_unref_path(pc);
+ return rc;
+}
+
+/*
+ * Returns 1 if the device is private device mapper device. The @uuid
+ * (if not NULL) returns DM device UUID, use free() to deallocate.
+ */
+int sysfs_devno_is_dm_private(dev_t devno, char **uuid)
+{
+ struct path_cxt *pc = NULL;
+ char *id = NULL;
+ int rc = 0;
+
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ goto done;
+ if (ul_path_read_string(pc, &id, "dm/uuid") <= 0 || !id)
+ goto done;
+
+ /* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
+ * is the "LVM" prefix and "-<name>" postfix).
+ */
+ if (strncmp(id, "LVM-", 4) == 0) {
+ char *p = strrchr(id + 4, '-');
+
+ if (p && *(p + 1))
+ rc = 1;
+
+ /* Private Stratis devices prefix the UUID with "stratis-1-private"
+ */
+ } else if (strncmp(id, "stratis-1-private", 17) == 0) {
+ rc = 1;
+ }
+done:
+ ul_unref_path(pc);
+ if (uuid)
+ *uuid = id;
+ else
+ free(id);
+ return rc;
+}
+
+/*
+ * Return 0 or 1, or < 0 in case of error
+ */
+int sysfs_devno_is_wholedisk(dev_t devno)
+{
+ dev_t disk;
+
+ if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
+ return -1;
+
+ return devno == disk;
+}
+
+
+int sysfs_blkdev_scsi_get_hctl(struct path_cxt *pc, int *h, int *c, int *t, int *l)
+{
+ char buf[PATH_MAX], *hctl;
+ struct sysfs_blkdev *blk;
+ ssize_t len;
+
+ blk = ul_path_get_dialect(pc);
+
+ if (!blk || blk->hctl_error)
+ return -EINVAL;
+ if (blk->has_hctl)
+ goto done;
+
+ blk->hctl_error = 1;
+ len = ul_path_readlink(pc, buf, sizeof(buf), "device");
+ if (len < 0)
+ return len;
+
+ hctl = strrchr(buf, '/');
+ if (!hctl)
+ return -1;
+ hctl++;
+
+ if (sscanf(hctl, "%u:%u:%u:%u", &blk->scsi_host, &blk->scsi_channel,
+ &blk->scsi_target, &blk->scsi_lun) != 4)
+ return -1;
+
+ blk->has_hctl = 1;
+done:
+ if (h)
+ *h = blk->scsi_host;
+ if (c)
+ *c = blk->scsi_channel;
+ if (t)
+ *t = blk->scsi_target;
+ if (l)
+ *l = blk->scsi_lun;
+
+ blk->hctl_error = 0;
+ return 0;
+}
+
+
+static char *scsi_host_attribute_path(
+ struct path_cxt *pc,
+ const char *type,
+ char *buf,
+ size_t bufsz,
+ const char *attr)
+{
+ int len;
+ int host;
+ const char *prefix;
+
+ if (sysfs_blkdev_scsi_get_hctl(pc, &host, NULL, NULL, NULL))
+ return NULL;
+
+ prefix = ul_path_get_prefix(pc);
+ if (!prefix)
+ prefix = "";
+
+ if (attr)
+ len = snprintf(buf, bufsz, "%s%s/%s_host/host%d/%s",
+ prefix, _PATH_SYS_CLASS, type, host, attr);
+ else
+ len = snprintf(buf, bufsz, "%s%s/%s_host/host%d",
+ prefix, _PATH_SYS_CLASS, type, host);
+
+ return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
+}
+
+char *sysfs_blkdev_scsi_host_strdup_attribute(struct path_cxt *pc,
+ const char *type, const char *attr)
+{
+ char buf[1024];
+ int rc;
+ FILE *f;
+
+ if (!attr || !type ||
+ !scsi_host_attribute_path(pc, type, buf, sizeof(buf), attr))
+ return NULL;
+
+ if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
+ return NULL;
+
+ rc = fscanf(f, "%1023[^\n]", buf);
+ fclose(f);
+
+ return rc == 1 ? strdup(buf) : NULL;
+}
+
+int sysfs_blkdev_scsi_host_is(struct path_cxt *pc, const char *type)
+{
+ char buf[PATH_MAX];
+ struct stat st;
+
+ if (!type || !scsi_host_attribute_path(pc, type,
+ buf, sizeof(buf), NULL))
+ return 0;
+
+ return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+static char *scsi_attribute_path(struct path_cxt *pc,
+ char *buf, size_t bufsz, const char *attr)
+{
+ int len, h, c, t, l;
+ const char *prefix;
+
+ if (sysfs_blkdev_scsi_get_hctl(pc, &h, &c, &t, &l) != 0)
+ return NULL;
+
+ prefix = ul_path_get_prefix(pc);
+ if (!prefix)
+ prefix = "";
+
+ if (attr)
+ len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d/%s",
+ prefix, _PATH_SYS_SCSI,
+ h,c,t,l, attr);
+ else
+ len = snprintf(buf, bufsz, "%s%s/devices/%d:%d:%d:%d",
+ prefix, _PATH_SYS_SCSI,
+ h,c,t,l);
+ return (len < 0 || (size_t) len >= bufsz) ? NULL : buf;
+}
+
+int sysfs_blkdev_scsi_has_attribute(struct path_cxt *pc, const char *attr)
+{
+ char path[PATH_MAX];
+ struct stat st;
+
+ if (!scsi_attribute_path(pc, path, sizeof(path), attr))
+ return 0;
+
+ return stat(path, &st) == 0;
+}
+
+int sysfs_blkdev_scsi_path_contains(struct path_cxt *pc, const char *pattern)
+{
+ char path[PATH_MAX], linkc[PATH_MAX];
+ struct stat st;
+ ssize_t len;
+
+ if (!scsi_attribute_path(pc, path, sizeof(path), NULL))
+ return 0;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ len = readlink(path, linkc, sizeof(linkc) - 1);
+ if (len < 0)
+ return 0;
+
+ linkc[len] = '\0';
+ return strstr(linkc, pattern) != NULL;
+}
+
+static dev_t read_devno(const char *path)
+{
+ FILE *f;
+ int maj = 0, min = 0;
+ dev_t dev = 0;
+
+ f = fopen(path, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+
+ if (fscanf(f, "%d:%d", &maj, &min) == 2)
+ dev = makedev(maj, min);
+ fclose(f);
+ return dev;
+}
+
+int sysfs_devname_is_hidden(const char *prefix, const char *name)
+{
+ char buf[PATH_MAX];
+ int rc = 0, hidden = 0, len;
+ FILE *f;
+
+ if (strncmp("/dev/", name, 5) == 0)
+ return 0;
+
+ if (!prefix)
+ prefix = "";
+ /*
+ * Create path to /sys/block/<name>/hidden
+ */
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/hidden",
+ prefix, name);
+
+ if (len < 0 || (size_t) len + 1 > sizeof(buf))
+ return 0;
+
+ f = fopen(buf, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+
+ rc = fscanf(f, "%d", &hidden);
+ fclose(f);
+
+ return rc == 1 ? hidden : 0;
+}
+
+
+dev_t __sysfs_devname_to_devno(const char *prefix, const char *name, const char *parent)
+{
+ char buf[PATH_MAX];
+ char *_name = NULL, *_parent = NULL; /* name as encoded in sysfs */
+ dev_t dev = 0;
+ int len;
+
+ if (!prefix)
+ prefix = "";
+
+ assert(name);
+
+ if (strncmp("/dev/", name, 5) == 0) {
+ /*
+ * Read from /dev
+ */
+ struct stat st;
+
+ if (stat(name, &st) == 0) {
+ dev = st.st_rdev;
+ goto done;
+ }
+ name += 5; /* unaccessible, or not node in /dev */
+ }
+
+ _name = strdup(name);
+ if (!_name)
+ goto done;
+ sysfs_devname_dev_to_sys(_name);
+
+ if (parent) {
+ _parent = strdup(parent);
+ if (!_parent)
+ goto done;
+ }
+
+ if (parent && strncmp("dm-", name, 3) != 0) {
+ /*
+ * Create path to /sys/block/<parent>/<name>/dev
+ */
+ sysfs_devname_dev_to_sys(_parent);
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
+ prefix, _parent, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+
+ /* don't try anything else for dm-* */
+ dev = read_devno(buf);
+ goto done;
+ }
+
+ /*
+ * Read from /sys/block/<sysname>/dev
+ */
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/dev",
+ prefix, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+ dev = read_devno(buf);
+
+ /*
+ * Read from /sys/block/<parent>/<partition>/dev
+ */
+ if (!dev && parent && startswith(name, parent)) {
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/%s/dev",
+ prefix, _parent, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+ dev = read_devno(buf);
+ }
+
+ /*
+ * Read from /sys/block/<sysname>/device/dev
+ */
+ if (!dev) {
+ len = snprintf(buf, sizeof(buf),
+ "%s" _PATH_SYS_BLOCK "/%s/device/dev",
+ prefix, _name);
+ if (len < 0 || (size_t) len >= sizeof(buf))
+ goto done;
+ dev = read_devno(buf);
+ }
+done:
+ free(_name);
+ free(_parent);
+ return dev;
+}
+
+dev_t sysfs_devname_to_devno(const char *name)
+{
+ return __sysfs_devname_to_devno(NULL, name, NULL);
+}
+
+char *sysfs_blkdev_get_path(struct path_cxt *pc, char *buf, size_t bufsiz)
+{
+ const char *name = sysfs_blkdev_get_name(pc, buf, bufsiz);
+ char *res = NULL;
+ size_t sz;
+ struct stat st;
+
+ if (!name)
+ goto done;
+
+ sz = strlen(name);
+ if (sz + sizeof("/dev/") > bufsiz)
+ goto done;
+
+ /* create the final "/dev/<name>" string */
+ memmove(buf + 5, name, sz + 1);
+ memcpy(buf, "/dev/", 5);
+
+ if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == sysfs_blkdev_get_devno(pc))
+ res = buf;
+done:
+ return res;
+}
+
+dev_t sysfs_blkdev_get_devno(struct path_cxt *pc)
+{
+ return ((struct sysfs_blkdev *) ul_path_get_dialect(pc))->devno;
+}
+
+/*
+ * Returns devname (e.g. "/dev/sda1") for the given devno.
+ *
+ * Please, use more robust blkid_devno_to_devname() in your applications.
+ */
+char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
+{
+ struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
+ char *res = NULL;
+
+ if (pc) {
+ res = sysfs_blkdev_get_path(pc, buf, bufsiz);
+ ul_unref_path(pc);
+ }
+ return res;
+}
+
+char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
+{
+ struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
+ char *res = NULL;
+
+ if (pc) {
+ res = sysfs_blkdev_get_name(pc, buf, bufsiz);
+ ul_unref_path(pc);
+ }
+ return res;
+}
+
+int sysfs_devno_count_partitions(dev_t devno)
+{
+ struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL);
+ int n = 0;
+
+ if (pc) {
+ char buf[PATH_MAX + 1];
+ char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf));
+
+ n = sysfs_blkdev_count_partitions(pc, name);
+ ul_unref_path(pc);
+ }
+ return n;
+}
+
+char *sysfs_chrdev_devno_to_devname(dev_t devno, char *buf, size_t bufsiz)
+{
+ char link[PATH_MAX];
+ struct path_cxt *pc;
+ char *name;
+ ssize_t sz;
+
+ pc = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u", major(devno), minor(devno));
+ if (!pc)
+ return NULL;
+
+ /* read /sys/dev/char/<maj:min> link */
+ sz = ul_path_readlink(pc, link, sizeof(link), NULL);
+ ul_unref_path(pc);
+
+ if (sz < 0)
+ return NULL;
+
+ name = strrchr(link, '/');
+ if (!name)
+ return NULL;
+
+ name++;
+ sz = strlen(name);
+ if ((size_t) sz + 1 > bufsiz)
+ return NULL;
+
+ memcpy(buf, name, sz + 1);
+ sysfs_devname_sys_to_dev(buf);
+ return buf;
+
+}
+
+
+
+#ifdef TEST_PROGRAM_SYSFS
+#include <errno.h>
+#include <err.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+ struct path_cxt *pc;
+ char *devname;
+ dev_t devno, disk_devno;
+ char path[PATH_MAX], *sub, *chain;
+ char diskname[32];
+ int i, is_part, rc = EXIT_SUCCESS;
+ uint64_t u64;
+
+ if (argc != 2)
+ errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
+
+ ul_sysfs_init_debug();
+
+ devname = argv[1];
+ devno = sysfs_devname_to_devno(devname);
+
+ if (!devno)
+ err(EXIT_FAILURE, "failed to read devno");
+
+ printf("non-context:\n");
+ printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+ printf(" DEVNAME: %s\n", sysfs_devno_to_devname(devno, path, sizeof(path)));
+ printf(" DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
+
+ sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
+ printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
+ printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
+
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ goto done;
+
+ printf("context based:\n");
+ devno = sysfs_blkdev_get_devno(pc);
+ printf(" DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+ printf(" DEVNAME: %s\n", sysfs_blkdev_get_name(pc, path, sizeof(path)));
+ printf(" DEVPATH: %s\n", sysfs_blkdev_get_path(pc, path, sizeof(path)));
+
+ sysfs_devno_to_wholedisk(devno, diskname, sizeof(diskname), &disk_devno);
+ printf(" WHOLEDISK-DEVNO: %u (%d:%d)\n", (unsigned int) disk_devno, major(disk_devno), minor(disk_devno));
+ printf(" WHOLEDISK-DEVNAME: %s\n", diskname);
+
+ is_part = ul_path_access(pc, F_OK, "partition") == 0;
+ printf(" PARTITION: %s\n", is_part ? "YES" : "NOT");
+
+ if (is_part && disk_devno) {
+ struct path_cxt *disk_pc = ul_new_sysfs_path(disk_devno, NULL, NULL);
+ sysfs_blkdev_set_parent(pc, disk_pc);
+
+ ul_unref_path(disk_pc);
+ }
+
+ printf(" HOTPLUG: %s\n", sysfs_blkdev_is_hotpluggable(pc) ? "yes" : "no");
+ printf(" SLAVES: %d\n", ul_path_count_dirents(pc, "slaves"));
+
+ if (!is_part) {
+ printf("First 5 partitions:\n");
+ for (i = 1; i <= 5; i++) {
+ dev_t dev = sysfs_blkdev_partno_to_devno(pc, i);
+ if (dev)
+ printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
+ }
+ }
+
+ if (ul_path_read_u64(pc, &u64, "size") != 0)
+ printf(" (!) read SIZE failed\n");
+ else
+ printf(" SIZE: %jd\n", u64);
+
+ if (ul_path_read_s32(pc, &i, "queue/hw_sector_size"))
+ printf(" (!) read SECTOR failed\n");
+ else
+ printf(" SECTOR: %d\n", i);
+
+
+ chain = sysfs_blkdev_get_devchain(pc, path, sizeof(path));
+ printf(" SUBSUSTEMS:\n");
+
+ while (chain && sysfs_blkdev_next_subsystem(pc, chain, &sub) == 0) {
+ printf("\t%s\n", sub);
+ free(sub);
+ }
+
+ rc = EXIT_SUCCESS;
+done:
+ ul_unref_path(pc);
+ return rc;
+}
+#endif /* TEST_PROGRAM_SYSFS */
diff --git a/lib/terminal-colors.d.5 b/lib/terminal-colors.d.5
new file mode 100644
index 0000000..7bea79c
--- /dev/null
+++ b/lib/terminal-colors.d.5
@@ -0,0 +1,404 @@
+'\" t
+.\" Title: terminal-colors.d
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: File formats
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "TERMINAL\-COLORS.D" "5" "2022-05-11" "util\-linux 2.38.1" "File formats"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+terminal-colors.d \- configure output colorization for various utilities
+.SH "SYNOPSIS"
+.sp
+/etc/terminal\-colors.d/\fI[[name][@term].][type]\fP
+.SH "DESCRIPTION"
+.sp
+Files in this directory determine the default behavior for utilities when coloring output.
+.sp
+The \fIname\fP is a utility name. The name is optional and when none is specified then the file is used for all unspecified utilities.
+.sp
+The \fIterm\fP is a terminal identifier (the \fBTERM\fP environment variable). The terminal identifier is optional and when none is specified then the file is used for all unspecified terminals.
+.sp
+The \fItype\fP is a file type. Supported file types are:
+.sp
+\fBdisable\fP
+.RS 4
+Turns off output colorization for all compatible utilities.
+.RE
+.sp
+\fBenable\fP
+.RS 4
+Turns on output colorization; any matching \fBdisable\fP files are ignored.
+.RE
+.sp
+\fBscheme\fP
+.RS 4
+Specifies colors used for output. The file format may be specific to the utility, the default format is described below.
+.RE
+.sp
+If there are more files that match for a utility, then the file with the more specific filename wins. For example, the filename "@xterm.scheme" has less priority than "dmesg@xterm.scheme". The lowest priority are those files without a utility name and terminal identifier (e.g., "disable").
+.sp
+The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting.
+.SH "DEFAULT SCHEME FILES FORMAT"
+.sp
+The following statement is recognized:
+.RS 3
+.ll -.6i
+.sp
+\fBname color\-sequence\fP
+.br
+.RE
+.ll
+.sp
+The \fBname\fP is a logical name of color sequence (for example "error"). The names are specific to the utilities. For more details always see the \fBCOLORS\fP section in the man page for the utility.
+.sp
+The \fBcolor\-sequence\fP is a color name, ASCII color sequences or escape sequences.
+.SS "Color names"
+.sp
+black, blink, blue, bold, brown, cyan, darkgray, gray, green, halfbright, lightblue, lightcyan, lightgray, lightgreen, lightmagenta, lightred, magenta, red, reset, reverse, and yellow.
+.SS "ANSI color sequences"
+.sp
+The color sequences are composed of sequences of numbers separated by semicolons. The most common codes are:
+.RS 3
+.ll -.6i
+.TS
+allbox tab(:);
+lt lt.
+T{
+.sp
+0
+T}:T{
+.sp
+to restore default color
+T}
+T{
+.sp
+1
+T}:T{
+.sp
+for brighter colors
+T}
+T{
+.sp
+4
+T}:T{
+.sp
+for underlined text
+T}
+T{
+.sp
+5
+T}:T{
+.sp
+for flashing text
+T}
+T{
+.sp
+30
+T}:T{
+.sp
+for black foreground
+T}
+T{
+.sp
+31
+T}:T{
+.sp
+for red foreground
+T}
+T{
+.sp
+32
+T}:T{
+.sp
+for green foreground
+T}
+T{
+.sp
+33
+T}:T{
+.sp
+for yellow (or brown) foreground
+T}
+T{
+.sp
+34
+T}:T{
+.sp
+for blue foreground
+T}
+T{
+.sp
+35
+T}:T{
+.sp
+for purple foreground
+T}
+T{
+.sp
+36
+T}:T{
+.sp
+for cyan foreground
+T}
+T{
+.sp
+37
+T}:T{
+.sp
+for white (or gray) foreground
+T}
+T{
+.sp
+40
+T}:T{
+.sp
+for black background
+T}
+T{
+.sp
+41
+T}:T{
+.sp
+for red background
+T}
+T{
+.sp
+42
+T}:T{
+.sp
+for green background
+T}
+T{
+.sp
+43
+T}:T{
+.sp
+for yellow (or brown) background
+T}
+T{
+.sp
+44
+T}:T{
+.sp
+for blue background
+T}
+T{
+.sp
+45
+T}:T{
+.sp
+for purple background
+T}
+T{
+.sp
+46
+T}:T{
+.sp
+for cyan background
+T}
+T{
+.sp
+47
+T}:T{
+.sp
+for white (or gray) background
+T}
+.TE
+.sp
+.br
+.RE
+.ll
+.SS "Escape sequences"
+.sp
+To specify control or blank characters in the color sequences, C\-style \(rs\-escaped notation can be used:
+.RS 3
+.ll -.6i
+.TS
+allbox tab(:);
+lt lt.
+T{
+.sp
+\fB\(rsa\fP
+T}:T{
+.sp
+Bell (ASCII 7)
+T}
+T{
+.sp
+\fB\(rsb\fP
+T}:T{
+.sp
+Backspace (ASCII 8)
+T}
+T{
+.sp
+\fB\(rse\fP
+T}:T{
+.sp
+Escape (ASCII 27)
+T}
+T{
+.sp
+\fB\(rsf\fP
+T}:T{
+.sp
+Form feed (ASCII 12)
+T}
+T{
+.sp
+\fB\(rsn\fP
+T}:T{
+.sp
+Newline (ASCII 10)
+T}
+T{
+.sp
+\fB\(rsr\fP
+T}:T{
+.sp
+Carriage Return (ASCII 13)
+T}
+T{
+.sp
+\fB\(rst\fP
+T}:T{
+.sp
+Tab (ASCII 9)
+T}
+T{
+.sp
+\fB\(rsv\fP
+T}:T{
+.sp
+Vertical Tab (ASCII 11)
+T}
+T{
+.sp
+\fB\(rs?\fP
+T}:T{
+.sp
+Delete (ASCII 127)
+T}
+T{
+.sp
+\fB\(rs_\fP
+T}:T{
+.sp
+Space
+T}
+T{
+.sp
+\fB\(rs\(rs\fP
+T}:T{
+.sp
+Backslash (\(rs)
+T}
+T{
+.sp
+\fB\(rs^\fP
+T}:T{
+.sp
+Caret (^)
+T}
+T{
+.sp
+\fB\(rs#\fP
+T}:T{
+.sp
+Hash mark (#)
+T}
+.TE
+.sp
+.br
+.RE
+.ll
+.sp
+Please note that escapes are necessary to enter a space, backslash, caret, or any control character anywhere in the string, as well as a hash mark as the first character.
+.sp
+For example, to use a red background for alert messages in the output of \fBdmesg\fP(1), use:
+.RS 3
+.ll -.6i
+.sp
+\fBecho \(aqalert 37;41\(aq >> /etc/terminal\-colors.d/dmesg.scheme\fP
+.br
+.RE
+.ll
+.SS "Comments"
+.sp
+Lines where the first non\-blank character is a # (hash) are ignored. Any other use of the hash character is not interpreted as introducing a comment.
+.SH "ENVIRONMENT"
+.sp
+\fBTERMINAL_COLORS_DEBUG\fP=all
+.RS 4
+enables debug output.
+.RE
+.SH "FILES"
+.sp
+\fI$XDG_CONFIG_HOME/terminal\-colors.d\fP
+.sp
+\fI$HOME/.config/terminal\-colors.d\fP
+.sp
+\fI/etc/terminal\-colors.d\fP
+.SH "EXAMPLE"
+.sp
+Disable colors for all compatible utilities:
+.RS 3
+.ll -.6i
+.sp
+\fBtouch /etc/terminal\-colors.d/disable\fP
+.br
+.RE
+.ll
+.sp
+Disable colors for all compatible utils on a vt100 terminal:
+.RS 3
+.ll -.6i
+.sp
+\fBtouch /etc/terminal\-colors.d/@vt100.disable\fP
+.br
+.RE
+.ll
+.sp
+Disable colors for all compatible utils except \fBdmesg\fP(1):
+.RS 3
+.ll -.6i
+.sp
+\fBtouch /etc/terminal\-colors.d/disable\fP
+.sp
+\fBtouch /etc/terminal\-colors.d/dmesg.enable\fP
+.br
+.RE
+.ll
+.SH "COMPATIBILITY"
+.sp
+The \fBterminal\-colors.d\fP functionality is currently supported by all util\-linux utilities which provides colorized output. For more details always see the \fBCOLORS\fP section in the man page for the utility.
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+\fBterminal\-colors.d\fP is part of the util\-linux package which can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/lib/terminal-colors.d.5.adoc b/lib/terminal-colors.d.5.adoc
new file mode 100644
index 0000000..f09cce1
--- /dev/null
+++ b/lib/terminal-colors.d.5.adoc
@@ -0,0 +1,174 @@
+//po4a: entry man manual
+////
+terminal-colors.d.5 --
+Copyright 2014 Ondrej Oprala <ooprala@redhat.com>
+Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+Copyright 2014 Red Hat, Inc.
+May be distributed under the GNU General Public License
+////
+
+= terminal-colors.d(5)
+:doctype: manpage
+:man manual: File formats
+:man source: util-linux {release-version}
+:page-layout: base
+:configfile: terminal-colors.d
+
+== NAME
+
+terminal-colors.d - configure output colorization for various utilities
+
+== SYNOPSIS
+
+/etc/terminal-colors.d/_[[name][@term].][type]_
+
+== DESCRIPTION
+
+Files in this directory determine the default behavior for utilities when coloring output.
+
+The _name_ is a utility name. The name is optional and when none is specified then the file is used for all unspecified utilities.
+
+The _term_ is a terminal identifier (the *TERM* environment variable). The terminal identifier is optional and when none is specified then the file is used for all unspecified terminals.
+
+The _type_ is a file type. Supported file types are:
+
+*disable*::
+Turns off output colorization for all compatible utilities.
+
+*enable*::
+Turns on output colorization; any matching *disable* files are ignored.
+
+*scheme*::
+Specifies colors used for output. The file format may be specific to the utility, the default format is described below.
+
+If there are more files that match for a utility, then the file with the more specific filename wins. For example, the filename "@xterm.scheme" has less priority than "dmesg@xterm.scheme". The lowest priority are those files without a utility name and terminal identifier (e.g., "disable").
+
+The user-specific _$XDG_CONFIG_HOME/terminal-colors.d_ or _$HOME/.config/terminal-colors.d_ overrides the global setting.
+
+== DEFAULT SCHEME FILES FORMAT
+
+The following statement is recognized:
+
+____
+*name color-sequence*
+____
+
+The *name* is a logical name of color sequence (for example "error"). The names are specific to the utilities. For more details always see the *COLORS* section in the man page for the utility.
+
+The *color-sequence* is a color name, ASCII color sequences or escape sequences.
+
+=== Color names
+
+black, blink, blue, bold, brown, cyan, darkgray, gray, green, halfbright, lightblue, lightcyan, lightgray, lightgreen, lightmagenta, lightred, magenta, red, reset, reverse, and yellow.
+
+=== ANSI color sequences
+
+The color sequences are composed of sequences of numbers separated by semicolons. The most common codes are:
+
+____
+[cols=",",]
+|===
+|0 |to restore default color
+|1 |for brighter colors
+|4 |for underlined text
+|5 |for flashing text
+|30 |for black foreground
+|31 |for red foreground
+|32 |for green foreground
+|33 |for yellow (or brown) foreground
+|34 |for blue foreground
+|35 |for purple foreground
+|36 |for cyan foreground
+|37 |for white (or gray) foreground
+|40 |for black background
+|41 |for red background
+|42 |for green background
+|43 |for yellow (or brown) background
+|44 |for blue background
+|45 |for purple background
+|46 |for cyan background
+|47 |for white (or gray) background
+|===
+____
+
+=== Escape sequences
+
+To specify control or blank characters in the color sequences, C-style \-escaped notation can be used:
+
+____
+[cols=",",]
+|===
+|*\a* |Bell (ASCII 7)
+|*\b* |Backspace (ASCII 8)
+|*\e* |Escape (ASCII 27)
+|*\f* |Form feed (ASCII 12)
+|*\n* |Newline (ASCII 10)
+|*\r* |Carriage Return (ASCII 13)
+|*\t* |Tab (ASCII 9)
+|*\v* |Vertical Tab (ASCII 11)
+|*\?* |Delete (ASCII 127)
+|*\_* |Space
+|*\\* |Backslash (\)
+|*\^* |Caret (^)
+|*\#* |Hash mark (#)
+|===
+____
+
+Please note that escapes are necessary to enter a space, backslash, caret, or any control character anywhere in the string, as well as a hash mark as the first character.
+
+For example, to use a red background for alert messages in the output of *dmesg*(1), use:
+
+____
+*echo 'alert 37;41' >> /etc/terminal-colors.d/dmesg.scheme*
+____
+
+=== Comments
+
+Lines where the first non-blank character is a # (hash) are ignored. Any other use of the hash character is not interpreted as introducing a comment.
+
+== ENVIRONMENT
+
+*TERMINAL_COLORS_DEBUG*=all::
+enables debug output.
+
+== FILES
+
+_$XDG_CONFIG_HOME/terminal-colors.d_
+
+_$HOME/.config/terminal-colors.d_
+
+_/etc/terminal-colors.d_
+
+== EXAMPLE
+
+Disable colors for all compatible utilities:
+
+____
+*touch /etc/terminal-colors.d/disable*
+____
+
+Disable colors for all compatible utils on a vt100 terminal:
+
+____
+*touch /etc/terminal-colors.d/@vt100.disable*
+____
+
+Disable colors for all compatible utils except *dmesg*(1):
+
+____
+*touch /etc/terminal-colors.d/disable*
+
+*touch /etc/terminal-colors.d/dmesg.enable*
+____
+
+== COMPATIBILITY
+
+The *terminal-colors.d* functionality is currently supported by all util-linux utilities which provides colorized output. For more details always see the *COLORS* section in the man page for the utility.
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-config.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/lib/timer.c b/lib/timer.c
new file mode 100644
index 0000000..cfa18f6
--- /dev/null
+++ b/lib/timer.c
@@ -0,0 +1,98 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Please, don't add this file to libcommon because timers requires
+ * -lrt on systems with old libc (and probably also -lpthread for static
+ * build).
+ */
+#include <time.h>
+#include <signal.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "timer.h"
+
+/*
+ * Note the timeout is used for the first signal, then the signal is send
+ * repeatedly in interval ~1% of the original timeout to avoid race in signal
+ * handling -- for example you want to use timer to define timeout for a
+ * syscall:
+ *
+ * setup_timer()
+ * syscall()
+ * cancel_timer()
+ *
+ * if the timeout is too short than it's possible that the signal is delivered
+ * before application enter the syscall function. For this reason timer send
+ * the signal repeatedly.
+ *
+ * The applications need to ensure that they can tolerate multiple signal
+ * deliveries.
+ */
+#ifdef HAVE_TIMER_CREATE
+int setup_timer(struct ul_timer *timer,
+ struct itimerval *timeout,
+ void (*timeout_handler)(int, siginfo_t *, void *))
+{
+ time_t sec = timeout->it_value.tv_sec;
+ long usec = timeout->it_value.tv_usec;
+ struct sigaction sig_a;
+ static struct sigevent sig_e = {
+ .sigev_notify = SIGEV_SIGNAL,
+ .sigev_signo = SIGALRM
+ };
+ struct itimerspec val = {
+ .it_value.tv_sec = sec,
+ .it_value.tv_nsec = usec * 1000,
+ .it_interval.tv_sec = sec / 100,
+ .it_interval.tv_nsec = (sec ? sec % 100 : 1) * 10*1000*1000
+ };
+
+ if (sigemptyset(&sig_a.sa_mask))
+ return 1;
+
+ sig_a.sa_flags = SA_SIGINFO;
+ sig_a.sa_sigaction = timeout_handler;
+
+ if (sigaction(SIGALRM, &sig_a, NULL))
+ return 1;
+ if (timer_create(CLOCK_MONOTONIC, &sig_e, &timer->t_id))
+ return 1;
+ if (timer_settime(timer->t_id, 0, &val, NULL))
+ return 1;
+ return 0;
+}
+void cancel_timer(struct ul_timer *timer)
+{
+ timer_delete(timer->t_id);
+}
+
+#else /* !HAVE_TIMER_CREATE */
+
+int setup_timer(struct ul_timer *timer,
+ struct itimerval *timeout,
+ void (*timeout_handler)(int, siginfo_t *, void *))
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof sa);
+ memset(timer, 0, sizeof(*timer));
+
+ sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
+ sa.sa_sigaction = timeout_handler;
+
+ if (sigaction(SIGALRM, &sa, &timer->old_sa))
+ return 1;
+ if (setitimer(ITIMER_REAL, timeout, &timer->old_timer) != 0)
+ return 1;
+ return 0;
+}
+
+void cancel_timer(struct ul_timer *timer)
+{
+ setitimer(ITIMER_REAL, &timer->old_timer, NULL);
+ sigaction(SIGALRM, &timer->old_sa, NULL);
+
+}
+#endif /* !HAVE_TIMER_CREATE */
diff --git a/lib/timeutils.c b/lib/timeutils.c
new file mode 100644
index 0000000..2e28ada
--- /dev/null
+++ b/lib/timeutils.c
@@ -0,0 +1,612 @@
+/***
+ First set of functions in this file are part of systemd, and were
+ copied to util-linux at August 2013.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd 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.
+
+ systemd 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 util-linux; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <inttypes.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "timeutils.h"
+
+#define WHITESPACE " \t\n\r"
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+
+static int parse_sec(const char *t, usec_t *usec)
+{
+ static const struct {
+ const char *suffix;
+ usec_t usec;
+ } table[] = {
+ { "seconds", USEC_PER_SEC },
+ { "second", USEC_PER_SEC },
+ { "sec", USEC_PER_SEC },
+ { "s", USEC_PER_SEC },
+ { "minutes", USEC_PER_MINUTE },
+ { "minute", USEC_PER_MINUTE },
+ { "min", USEC_PER_MINUTE },
+ { "months", USEC_PER_MONTH },
+ { "month", USEC_PER_MONTH },
+ { "msec", USEC_PER_MSEC },
+ { "ms", USEC_PER_MSEC },
+ { "m", USEC_PER_MINUTE },
+ { "hours", USEC_PER_HOUR },
+ { "hour", USEC_PER_HOUR },
+ { "hr", USEC_PER_HOUR },
+ { "h", USEC_PER_HOUR },
+ { "days", USEC_PER_DAY },
+ { "day", USEC_PER_DAY },
+ { "d", USEC_PER_DAY },
+ { "weeks", USEC_PER_WEEK },
+ { "week", USEC_PER_WEEK },
+ { "w", USEC_PER_WEEK },
+ { "years", USEC_PER_YEAR },
+ { "year", USEC_PER_YEAR },
+ { "y", USEC_PER_YEAR },
+ { "usec", 1ULL },
+ { "us", 1ULL },
+ { "", USEC_PER_SEC }, /* default is sec */
+ };
+
+ const char *p;
+ usec_t r = 0;
+ int something = FALSE;
+
+ assert(t);
+ assert(usec);
+
+ p = t;
+ for (;;) {
+ long long l, z = 0;
+ char *e;
+ unsigned i, n = 0;
+
+ p += strspn(p, WHITESPACE);
+
+ if (*p == 0) {
+ if (!something)
+ return -EINVAL;
+
+ break;
+ }
+
+ errno = 0;
+ l = strtoll(p, &e, 10);
+
+ if (errno > 0)
+ return -errno;
+
+ if (l < 0)
+ return -ERANGE;
+
+ if (*e == '.') {
+ char *b = e + 1;
+
+ errno = 0;
+ z = strtoll(b, &e, 10);
+ if (errno > 0)
+ return -errno;
+
+ if (z < 0)
+ return -ERANGE;
+
+ if (e == b)
+ return -EINVAL;
+
+ n = e - b;
+
+ } else if (e == p)
+ return -EINVAL;
+
+ e += strspn(e, WHITESPACE);
+
+ for (i = 0; i < ARRAY_SIZE(table); i++)
+ if (startswith(e, table[i].suffix)) {
+ usec_t k = (usec_t) z * table[i].usec;
+
+ for (; n > 0; n--)
+ k /= 10;
+
+ r += (usec_t) l *table[i].usec + k;
+ p = e + strlen(table[i].suffix);
+
+ something = TRUE;
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(table))
+ return -EINVAL;
+
+ }
+
+ *usec = r;
+
+ return 0;
+}
+
+int parse_timestamp(const char *t, usec_t *usec)
+{
+ static const struct {
+ const char *name;
+ const int nr;
+ } day_nr[] = {
+ { "Sunday", 0 },
+ { "Sun", 0 },
+ { "Monday", 1 },
+ { "Mon", 1 },
+ { "Tuesday", 2 },
+ { "Tue", 2 },
+ { "Wednesday", 3 },
+ { "Wed", 3 },
+ { "Thursday", 4 },
+ { "Thu", 4 },
+ { "Friday", 5 },
+ { "Fri", 5 },
+ { "Saturday", 6 },
+ { "Sat", 6 },
+ };
+
+ const char *k;
+ struct tm tm, copy;
+ time_t x;
+ usec_t plus = 0, minus = 0, ret;
+ int r, weekday = -1;
+ unsigned i;
+
+ /*
+ * Allowed syntaxes:
+ *
+ * 2012-09-22 16:34:22
+ * 2012-09-22T16:34:22
+ * 2012-09-22 16:34 (seconds will be set to 0)
+ * 2012-09-22 (time will be set to 00:00:00)
+ * 16:34:22 (date will be set to today)
+ * 16:34 (date will be set to today, seconds to 0)
+ * now
+ * yesterday (time is set to 00:00:00)
+ * today (time is set to 00:00:00)
+ * tomorrow (time is set to 00:00:00)
+ * +5min
+ * -5days
+ *
+ */
+
+ assert(t);
+ assert(usec);
+
+ x = time(NULL);
+ localtime_r(&x, &tm);
+ tm.tm_isdst = -1;
+
+ if (streq(t, "now"))
+ goto finish;
+
+ else if (streq(t, "today")) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "yesterday")) {
+ tm.tm_mday--;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (streq(t, "tomorrow")) {
+ tm.tm_mday++;
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+
+ } else if (t[0] == '+') {
+
+ r = parse_sec(t + 1, &plus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ } else if (t[0] == '-') {
+
+ r = parse_sec(t + 1, &minus);
+ if (r < 0)
+ return r;
+
+ goto finish;
+
+ } else if (endswith(t, " ago")) {
+ char *z;
+
+ z = strndup(t, strlen(t) - 4);
+ if (!z)
+ return -ENOMEM;
+
+ r = parse_sec(z, &minus);
+ free(z);
+ if (r < 0)
+ return r;
+
+ goto finish;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
+ size_t skip;
+
+ if (!startswith_no_case(t, day_nr[i].name))
+ continue;
+
+ skip = strlen(day_nr[i].name);
+ if (t[skip] != ' ')
+ continue;
+
+ weekday = day_nr[i].nr;
+ t += skip + 1;
+ break;
+ }
+
+ copy = tm;
+ k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y-%m-%d", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%H:%M:%S", &tm);
+ if (k && *k == 0)
+ goto finish;
+
+ tm = copy;
+ k = strptime(t, "%H:%M", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ tm = copy;
+ k = strptime(t, "%Y%m%d%H%M%S", &tm);
+ if (k && *k == 0) {
+ tm.tm_sec = 0;
+ goto finish;
+ }
+
+ return -EINVAL;
+
+ finish:
+ x = mktime(&tm);
+ if (x == (time_t)-1)
+ return -EINVAL;
+
+ if (weekday >= 0 && tm.tm_wday != weekday)
+ return -EINVAL;
+
+ ret = (usec_t) x *USEC_PER_SEC;
+
+ ret += plus;
+ if (ret > minus)
+ ret -= minus;
+ else
+ ret = 0;
+
+ *usec = ret;
+
+ return 0;
+}
+
+/* Returns the difference in seconds between its argument and GMT. If if TP is
+ * invalid or no DST information is available default to UTC, that is, zero.
+ * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
+ * Derived from glibc/time/strftime_l.c
+ */
+int get_gmtoff(const struct tm *tp)
+{
+ if (tp->tm_isdst < 0)
+ return 0;
+
+#if HAVE_TM_GMTOFF
+ return tp->tm_gmtoff;
+#else
+ struct tm tm;
+ struct tm gtm;
+ struct tm ltm = *tp;
+ time_t lt;
+
+ tzset();
+ lt = mktime(&ltm);
+ /* Check if mktime returning -1 is an error or a valid time_t */
+ if (lt == (time_t) -1) {
+ if (! localtime_r(&lt, &tm)
+ || ((ltm.tm_sec ^ tm.tm_sec)
+ | (ltm.tm_min ^ tm.tm_min)
+ | (ltm.tm_hour ^ tm.tm_hour)
+ | (ltm.tm_mday ^ tm.tm_mday)
+ | (ltm.tm_mon ^ tm.tm_mon)
+ | (ltm.tm_year ^ tm.tm_year)))
+ return 0;
+ }
+
+ if (! gmtime_r(&lt, &gtm))
+ return 0;
+
+ /* Calculate the GMT offset, that is, the difference between the
+ * TP argument (ltm) and GMT (gtm).
+ *
+ * Compute intervening leap days correctly even if year is negative.
+ * Take care to avoid int overflow in leap day calculations, but it's OK
+ * to assume that A and B are close to each other.
+ */
+ int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
+ int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = a100 >> 2;
+ int b400 = b100 >> 2;
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+
+ int years = ltm.tm_year - gtm.tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (ltm.tm_yday - gtm.tm_yday));
+
+ return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
+ + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
+#endif
+}
+
+static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
+{
+ char *p = buf;
+ int len;
+
+ if (flags & ISO_DATE) {
+ len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
+ tm->tm_year + (long) 1900,
+ tm->tm_mon + 1, tm->tm_mday);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
+ if (bufsz < 1)
+ goto err;
+ *p++ = (flags & ISO_T) ? 'T' : ' ';
+ bufsz--;
+ }
+
+ if (flags & ISO_TIME) {
+ len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
+ tm->tm_min, tm->tm_sec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if (flags & ISO_DOTUSEC) {
+ len = snprintf(p, bufsz, ".%06"PRId64, (int64_t) usec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+
+ } else if (flags & ISO_COMMAUSEC) {
+ len = snprintf(p, bufsz, ",%06"PRId64, (int64_t) usec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ }
+
+ if (flags & ISO_TIMEZONE) {
+ int tmin = get_gmtoff(tm) / 60;
+ int zhour = tmin / 60;
+ int zmin = abs(tmin % 60);
+ len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ }
+ return 0;
+ err:
+ warnx(_("format_iso_time: buffer overflow."));
+ return -1;
+}
+
+/* timeval to ISO 8601 */
+int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm;
+ struct tm *rc;
+
+ if (flags & ISO_GMTIME)
+ rc = gmtime_r(&tv->tv_sec, &tm);
+ else
+ rc = localtime_r(&tv->tv_sec, &tm);
+
+ if (rc)
+ return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz);
+
+ warnx(_("time %"PRId64" is out of range."), (int64_t)(tv->tv_sec));
+ return -1;
+}
+
+/* struct tm to ISO 8601 */
+int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz)
+{
+ return format_iso_time(tm, 0, flags, buf, bufsz);
+}
+
+/* time_t to ISO 8601 */
+int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm;
+ struct tm *rc;
+
+ if (flags & ISO_GMTIME)
+ rc = gmtime_r(t, &tm);
+ else
+ rc = localtime_r(t, &tm);
+
+ if (rc)
+ return format_iso_time(&tm, 0, flags, buf, bufsz);
+
+ warnx(_("time %"PRId64" is out of range."), (int64_t)*t);
+ return -1;
+}
+
+/* relative time functions */
+static inline int time_is_thisyear(struct tm const *const tm,
+ struct tm const *const tmnow)
+{
+ return tm->tm_year == tmnow->tm_year;
+}
+
+static inline int time_is_today(struct tm const *const tm,
+ struct tm const *const tmnow)
+{
+ return (tm->tm_yday == tmnow->tm_yday &&
+ time_is_thisyear(tm, tmnow));
+}
+
+int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
+{
+ struct tm tm, tmnow;
+ int rc = 0;
+
+ if (now->tv_sec == 0)
+ gettimeofday(now, NULL);
+
+ localtime_r(t, &tm);
+ localtime_r(&now->tv_sec, &tmnow);
+
+ if (time_is_today(&tm, &tmnow)) {
+ rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
+ if (rc < 0 || (size_t) rc > bufsz)
+ return -1;
+ rc = 1;
+
+ } else if (time_is_thisyear(&tm, &tmnow)) {
+ if (flags & UL_SHORTTIME_THISYEAR_HHMM)
+ rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
+ else
+ rc = strftime(buf, bufsz, "%b%d", &tm);
+ } else
+ rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
+
+ return rc <= 0 ? -1 : 0;
+}
+
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm)
+{
+ const char *zone = getenv("TZ");
+ time_t ret;
+
+ setenv("TZ", "", 1);
+ tzset();
+ ret = mktime(tm);
+ if (zone)
+ setenv("TZ", zone, 1);
+ else
+ unsetenv("TZ");
+ tzset();
+ return ret;
+}
+#endif /* HAVE_TIMEGM */
+
+#ifdef TEST_PROGRAM_TIMEUTILS
+
+int main(int argc, char *argv[])
+{
+ struct timeval tv = { 0 };
+ char buf[ISO_BUFSIZ];
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (strcmp(argv[1], "--timestamp") == 0) {
+ usec_t usec = 0;
+
+ parse_timestamp(argv[2], &usec);
+ tv.tv_sec = (time_t) (usec / 1000000);
+ tv.tv_usec = usec % 1000000;
+ } else {
+ tv.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
+ if (argc == 3)
+ tv.tv_usec = strtos64_or_err(argv[2], "failed to parse <usec>");
+ }
+
+ strtimeval_iso(&tv, ISO_DATE, buf, sizeof(buf));
+ printf("Date: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_TIME, buf, sizeof(buf));
+ printf("Time: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
+ buf, sizeof(buf));
+ printf("Full: '%s'\n", buf);
+
+ strtimeval_iso(&tv, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
+ printf("Zone: '%s'\n", buf);
+
+ return EXIT_SUCCESS;
+}
+
+#endif /* TEST_PROGRAM_TIMEUTILS */
diff --git a/lib/ttyutils.c b/lib/ttyutils.c
new file mode 100644
index 0000000..7064565
--- /dev/null
+++ b/lib/ttyutils.c
@@ -0,0 +1,152 @@
+/*
+ * No copyright is claimed. This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "ttyutils.h"
+
+
+static int get_env_int(const char *name)
+{
+ const char *cp = getenv(name);
+
+ if (cp) {
+ char *end = NULL;
+ long x;
+
+ errno = 0;
+ x = strtol(cp, &end, 10);
+
+ if (errno == 0 && end && *end == '\0' && end > cp &&
+ x > 0 && x <= INT_MAX)
+ return x;
+ }
+
+ return -1;
+}
+
+int get_terminal_dimension(int *cols, int *lines)
+{
+ int c = 0, l = 0;
+
+#if defined(TIOCGWINSZ)
+ struct winsize w_win;
+ if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w_win) == 0) {
+ c = w_win.ws_col;
+ l = w_win.ws_row;
+ }
+#elif defined(TIOCGSIZE)
+ struct ttysize t_win;
+ if (ioctl (STDOUT_FILENO, TIOCGSIZE, &t_win) == 0) {
+ c = t_win.ts_cols;
+ l = t_win.ts_lines;
+ }
+#endif
+ if (cols) {
+ if (c <= 0)
+ c = get_env_int("COLUMNS");
+ *cols = c;
+ }
+ if (lines) {
+ if (l <= 0)
+ l = get_env_int("LINES");
+ *lines = l;
+ }
+ return 0;
+}
+
+int get_terminal_width(int default_width)
+{
+ int width = 0;
+
+ get_terminal_dimension(&width, NULL);
+
+ return width > 0 ? width : default_width;
+}
+
+int get_terminal_stdfd(void)
+{
+ if (isatty(STDIN_FILENO))
+ return STDIN_FILENO;
+ if (isatty(STDOUT_FILENO))
+ return STDOUT_FILENO;
+ if (isatty(STDERR_FILENO))
+ return STDERR_FILENO;
+
+ return -EINVAL;
+}
+
+int get_terminal_name(const char **path,
+ const char **name,
+ const char **number)
+{
+ const char *tty;
+ const char *p;
+ int fd;
+
+
+ if (name)
+ *name = NULL;
+ if (path)
+ *path = NULL;
+ if (number)
+ *number = NULL;
+
+ fd = get_terminal_stdfd();
+ if (fd < 0)
+ return fd; /* error */
+
+ tty = ttyname(fd);
+ if (!tty)
+ return -1;
+
+ if (path)
+ *path = tty;
+ if (name || number)
+ tty = strncmp(tty, "/dev/", 5) == 0 ? tty + 5 : tty;
+ if (name)
+ *name = tty;
+ if (number) {
+ for (p = tty; p && *p; p++) {
+ if (isdigit(*p)) {
+ *number = p;
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+int get_terminal_type(const char **type)
+{
+ *type = getenv("TERM");
+ if (*type)
+ return -EINVAL;
+ return 0;
+}
+
+#ifdef TEST_PROGRAM_TTYUTILS
+# include <stdlib.h>
+int main(void)
+{
+ const char *path, *name, *num;
+ int c, l;
+
+ if (get_terminal_name(&path, &name, &num) == 0) {
+ fprintf(stderr, "tty path: %s\n", path);
+ fprintf(stderr, "tty name: %s\n", name);
+ fprintf(stderr, "tty number: %s\n", num);
+ }
+ get_terminal_dimension(&c, &l);
+ fprintf(stderr, "tty cols: %d\n", c);
+ fprintf(stderr, "tty lines: %d\n", l);
+
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM_TTYUTILS */
diff --git a/libblkid/COPYING b/libblkid/COPYING
new file mode 100644
index 0000000..1c4252a
--- /dev/null
+++ b/libblkid/COPYING
@@ -0,0 +1,8 @@
+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.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPL-2.1-or-later file.
diff --git a/libblkid/Makemodule.am b/libblkid/Makemodule.am
new file mode 100644
index 0000000..19ed31f
--- /dev/null
+++ b/libblkid/Makemodule.am
@@ -0,0 +1,19 @@
+if BUILD_LIBBLKID
+
+include libblkid/src/Makemodule.am
+include libblkid/samples/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libblkid/docs
+endif
+
+pkgconfig_DATA += libblkid/blkid.pc
+PATHFILES += libblkid/blkid.pc
+
+MANPAGES += libblkid/libblkid.3
+dist_noinst_DATA += libblkid/libblkid.3.adoc
+
+EXTRA_DIST += libblkid/COPYING
+
+endif # BUILD_LIBBLKID
diff --git a/libblkid/blkid.pc.in b/libblkid/blkid.pc.in
new file mode 100644
index 0000000..3316c6b
--- /dev/null
+++ b/libblkid/blkid.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: blkid
+Description: Block device id library
+Version: @LIBBLKID_VERSION@
+Cflags: -I${includedir}/blkid
+Libs: -L${libdir} -lblkid
diff --git a/libblkid/docs/Makefile.am b/libblkid/docs/Makefile.am
new file mode 100644
index 0000000..c735a13
--- /dev/null
+++ b/libblkid/docs/Makefile.am
@@ -0,0 +1,95 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libblkid
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=--deprecated-guards="BLKID_DISABLE_DEPRECATED"
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space blkid
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libblkid/src/blkid.h
+CFILE_GLOB=$(top_srcdir)/libblkid/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=blkidP.h partitions.h superblocks.h \
+ topology.h aix.h dos.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml $(srcdir)/libblkid-config.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in $(srcdir)/libblkid-config.xml
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
+
diff --git a/libblkid/docs/Makefile.in b/libblkid/docs/Makefile.in
new file mode 100644
index 0000000..4c0986f
--- /dev/null
+++ b/libblkid/docs/Makefile.in
@@ -0,0 +1,897 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# WARNING: this is not gtk-doc.make file from gtk-doc project. This
+# file has been modified to match with util-linux requirements:
+#
+# * install files to $datadir
+# * don't maintain generated files in git repository
+# * don't distribute the final html files
+# * don't require --enable-gtk-doc for "make dist"
+# * support out-of-tree build ($srcdir != $builddir)
+#
+# -- kzak, Nov 2009
+#
+
+####################################
+# Everything below here is generic #
+####################################
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = libblkid/docs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_vscript.m4 \
+ $(top_srcdir)/m4/compiler.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/tls.m4 \
+ $(top_srcdir)/m4/ul.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = version.xml
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/version.xml.in \
+ $(top_srcdir)/config/gtk-doc.make
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ADJTIME_PATH = @ADJTIME_PATH@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+ASCIIDOCTOR = @ASCIIDOCTOR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BSD_WARN_CFLAGS = @BSD_WARN_CFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTSETUP_CFLAGS = @CRYPTSETUP_CFLAGS@
+CRYPTSETUP_LIBS = @CRYPTSETUP_LIBS@
+CRYPTSETUP_LIBS_STATIC = @CRYPTSETUP_LIBS_STATIC@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DAEMON_CFLAGS = @DAEMON_CFLAGS@
+DAEMON_LDFLAGS = @DAEMON_LDFLAGS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+ECONF_CFLAGS = @ECONF_CFLAGS@
+ECONF_LIBS = @ECONF_LIBS@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZING_ENGINE_LDFLAGS = @FUZZING_ENGINE_LDFLAGS@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBBLKID_DATE = @LIBBLKID_DATE@
+LIBBLKID_VERSION = @LIBBLKID_VERSION@
+LIBBLKID_VERSION_INFO = @LIBBLKID_VERSION_INFO@
+LIBFDISK_MAJOR_VERSION = @LIBFDISK_MAJOR_VERSION@
+LIBFDISK_MINOR_VERSION = @LIBFDISK_MINOR_VERSION@
+LIBFDISK_PATCH_VERSION = @LIBFDISK_PATCH_VERSION@
+LIBFDISK_PC_REQUIRES = @LIBFDISK_PC_REQUIRES@
+LIBFDISK_VERSION = @LIBFDISK_VERSION@
+LIBFDISK_VERSION_INFO = @LIBFDISK_VERSION_INFO@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMOUNT_MAJOR_VERSION = @LIBMOUNT_MAJOR_VERSION@
+LIBMOUNT_MINOR_VERSION = @LIBMOUNT_MINOR_VERSION@
+LIBMOUNT_PATCH_VERSION = @LIBMOUNT_PATCH_VERSION@
+LIBMOUNT_VERSION = @LIBMOUNT_VERSION@
+LIBMOUNT_VERSION_INFO = @LIBMOUNT_VERSION_INFO@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSMARTCOLS_VERSION = @LIBSMARTCOLS_VERSION@
+LIBSMARTCOLS_VERSION_INFO = @LIBSMARTCOLS_VERSION_INFO@
+LIBTOOL = @LIBTOOL@
+LIBUSER_CFLAGS = @LIBUSER_CFLAGS@
+LIBUSER_LIBS = @LIBUSER_LIBS@
+LIBUUID_VERSION = @LIBUUID_VERSION@
+LIBUUID_VERSION_INFO = @LIBUUID_VERSION_INFO@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAGIC_LIBS = @MAGIC_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MATH_LIBS = @MATH_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NCURSES5_CONFIG = @NCURSES5_CONFIG@
+NCURSES6_CONFIG = @NCURSES6_CONFIG@
+NCURSESW5_CONFIG = @NCURSESW5_CONFIG@
+NCURSESW6_CONFIG = @NCURSESW6_CONFIG@
+NCURSESW_CFLAGS = @NCURSESW_CFLAGS@
+NCURSESW_LIBS = @NCURSESW_LIBS@
+NCURSES_CFLAGS = @NCURSES_CFLAGS@
+NCURSES_LIBS = @NCURSES_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NO_UNUSED_WARN_CFLAGS = @NO_UNUSED_WARN_CFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+PYTHON_WARN_CFLAGS = @PYTHON_WARN_CFLAGS@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+READLINE_LIBS_STATIC = @READLINE_LIBS_STATIC@
+REALTIME_LIBS = @REALTIME_LIBS@
+RTAS_LIBS = @RTAS_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SELINUX_LIBS_STATIC = @SELINUX_LIBS_STATIC@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SOLIB_CFLAGS = @SOLIB_CFLAGS@
+SOLIB_LDFLAGS = @SOLIB_LDFLAGS@
+STRIP = @STRIP@
+SUID_CFLAGS = @SUID_CFLAGS@
+SUID_LDFLAGS = @SUID_LDFLAGS@
+SYSCONFSTATICDIR = @SYSCONFSTATICDIR@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@
+SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+TINFOW_CFLAGS = @TINFOW_CFLAGS@
+TINFOW_LIBS = @TINFOW_LIBS@
+TINFO_CFLAGS = @TINFO_CFLAGS@
+TINFO_LIBS = @TINFO_LIBS@
+TINFO_LIBS_STATIC = @TINFO_LIBS_STATIC@
+UBSAN_LDFLAGS = @UBSAN_LDFLAGS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+VSCRIPT_LDFLAGS = @VSCRIPT_LDFLAGS@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XSLTPROC = @XSLTPROC@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bashcompletiondir = @bashcompletiondir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+sysconfstaticdir = @sysconfstaticdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+usrbin_execdir = @usrbin_execdir@
+usrlib_execdir = @usrlib_execdir@
+usrsbin_execdir = @usrsbin_execdir@
+vendordir = @vendordir@
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE = libblkid
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR = ../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS =
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS = --deprecated-guards="BLKID_DISABLE_DEPRECATED"
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space blkid
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS =
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS =
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB = $(top_builddir)/libblkid/src/blkid.h
+CFILE_GLOB = $(top_srcdir)/libblkid/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES =
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES = blkidP.h partitions.h superblocks.h \
+ topology.h aix.h dos.h
+
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES =
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml $(srcdir)/libblkid-config.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files =
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS =
+GTKDOC_LIBS =
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_CC = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_LD = $(LIBTOOL) --tag=CC --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_RUN =
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_RUN = $(LIBTOOL) --mode=execute
+
+# We set GPATH here; this gives us semantics for GNU make
+# which are more like other make's VPATH, when it comes to
+# whether a source that is a target of one rule is then
+# searched for in VPATH/GPATH.
+#
+GPATH = $(srcdir)
+TARGET_DIR = $(docdir)/$(DOC_MODULE)
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES = version.xml
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST = $(content_files) $(HTML_IMAGES) $(DOC_MAIN_SGML_FILE) \
+ $(DOC_MODULE)-sections.txt version.xml.in \
+ $(srcdir)/libblkid-config.xml
+# $(DOC_MODULE)-overrides.txt
+DOC_STAMPS = scan-build.stamp sgml-build.stamp html-build.stamp \
+ $(srcdir)/setup.stamp $(srcdir)/sgml.stamp \
+ $(srcdir)/html.stamp
+
+SCANOBJ_FILES = \
+ $(DOC_MODULE).args \
+ $(DOC_MODULE).hierarchy \
+ $(DOC_MODULE).interfaces \
+ $(DOC_MODULE).prerequisites \
+ $(DOC_MODULE).signals \
+ $(DOC_MODULE).types # util-linux: we don't use types
+
+REPORT_FILES = \
+ $(DOC_MODULE)-undocumented.txt \
+ $(DOC_MODULE)-undeclared.txt \
+ $(DOC_MODULE)-unused.txt
+
+CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/config/gtk-doc.make $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libblkid/docs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign libblkid/docs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/config/gtk-doc.make $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+version.xml: $(top_builddir)/config.status $(srcdir)/version.xml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile all-local
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-local
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am all-local check check-am clean clean-generic \
+ clean-libtool clean-local cscopelist-am ctags-am distclean \
+ distclean-generic distclean-libtool distclean-local distdir \
+ dvi dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-data-local install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am uninstall-local
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_GTK_DOC_TRUE@all-local: html-build.stamp
+@ENABLE_GTK_DOC_FALSE@all-local:
+
+docs: html-build.stamp
+
+$(REPORT_FILES): sgml-build.stamp
+
+#### setup ####
+
+setup-build.stamp:
+ -@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
+ echo 'gtk-doc: Preparing build'; \
+ files=`echo $(EXTRA_DIST) $(expand_content_files) $(srcdir)/$(DOC_MODULE).types`; \
+ if test "x$$files" != "x" ; then \
+ for file in $$files ; do \
+ test -f $(abs_srcdir)/$$file && \
+ cp -p $(abs_srcdir)/$$file $(abs_builddir)/; \
+ done \
+ fi \
+ fi
+ @touch setup-build.stamp
+
+setup.stamp: setup-build.stamp
+ @true
+
+#### scan ####
+
+scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB) $(srcdir)/$(DOC_MODULE)-*.txt $(content_files)
+
+ @test -f $(DOC_MODULE)-sections.txt || \
+ cp $(srcdir)/$(DOC_MODULE)-sections.txt $(builddir)
+
+ $(AM_V_GEN)gtkdoc-scan --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --ignore-decorators="__ul_attribute__\(.*\)" \
+ --ignore-headers="$(IGNORE_HFILES)" \
+ --output-dir=$(builddir) \
+ $(SCAN_OPTIONS) $(EXTRA_HFILES)
+
+ @ if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \
+ CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" RUN="$(GTKDOC_RUN)" \
+ CFLAGS="$(GTKDOC_CFLAGS) $(CFLAGS)" LDFLAGS="$(GTKDOC_LIBS) \
+ $(LDFLAGS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) \
+ --module=$(DOC_MODULE) --output-dir=$(builddir) ; \
+ else \
+ for i in $(SCANOBJ_FILES) ; do \
+ test -f $$i || touch $$i ; \
+ done \
+ fi
+ @ touch scan-build.stamp
+
+$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp
+ @true
+
+#### templates ####
+#
+#tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(srcdir)/$(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt
+# @echo 'gtk-doc: Rebuilding template files'
+# test -z $(builddir)/tmpl || $(MKDIR_P) $(builddir)/tmpl
+# gtkdoc-mktmpl --module=$(DOC_MODULE) \
+# $(MKTMPL_OPTIONS)
+# touch tmpl-build.stamp
+#
+#tmpl.stamp: tmpl-build.stamp
+# @true
+#
+#tmpl/*.sgml:
+# @true
+#
+
+#### xml ####
+
+sgml-build.stamp: setup.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-decl.txt $(DOC_MODULE)-sections.txt $(expand_content_files)
+ $(AM_V_GEN)gtkdoc-mkdb --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --output-format=xml \
+ --ignore-files="$(IGNORE_HFILES)" \
+ --expand-content-files="$(expand_content_files)" \
+ --main-sgml-file=$(srcdir)/$(DOC_MAIN_SGML_FILE) \
+ $(MKDB_OPTIONS)
+ @touch sgml-build.stamp
+
+sgml.stamp: sgml-build.stamp
+ @true
+
+#### html ####
+
+html-build.stamp: sgml.stamp $(srcdir)/$(DOC_MAIN_SGML_FILE) $(content_files)
+ @rm -rf $(builddir)/html
+ @$(MKDIR_P) $(builddir)/html
+ $(AM_V_GEN)cd $(builddir)/html && \
+ gtkdoc-mkhtml --path="$(abs_builddir):$(abs_builddir)/xml:$(abs_srcdir)" \
+ $(MKHTML_OPTIONS) \
+ $(DOC_MODULE) \
+ $(abs_srcdir)/$(DOC_MAIN_SGML_FILE)
+
+ @test "x$(HTML_IMAGES)" = "x" || \
+ ( cd $(srcdir) && cp $(HTML_IMAGES) $(abs_builddir)/html )
+
+ $(AM_V_GEN)gtkdoc-fixxref --module-dir=html \
+ --module=$(DOC_MODULE) \
+ --html-dir=$(HTML_DIR) \
+ $(FIXXREF_OPTIONS)
+ @touch html-build.stamp
+
+##############
+
+clean-local:
+ rm -f *~ *.bak
+ rm -rf .libs
+
+distclean-local:
+ rm -rf xml html $(REPORT_FILES) *.stamp \
+ $(DOC_MODULE)-overrides.txt \
+ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
+ test $(abs_builddir) == $(abs_srcdir) || \
+ rm -f $(DOC_MODULE)-*.txt $(DOC_MODULE)-*.xml *.xml.in
+
+install-data-local:
+ installfiles=`echo $(builddir)/html/*`; \
+ if test "$$installfiles" = '$(builddir)/html/*'; \
+ then echo '-- Nothing to install' ; \
+ else \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ $(mkinstalldirs) $${installdir} ; \
+ for i in $$installfiles; do \
+ echo '-- Installing '$$i ; \
+ $(INSTALL_DATA) $$i $${installdir}; \
+ done; \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp2 \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp2; \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp; \
+ fi; \
+ ! which gtkdoc-rebase >/dev/null 2>&1 || \
+ gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$${installdir} ; \
+ fi
+
+uninstall-local:
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ rm -rf $${installdir}
+
+#
+# Require gtk-doc when making dist
+#
+@ENABLE_GTK_DOC_TRUE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@ @echo "*** gtk-doc must be installed and enabled in order to make dist"
+@ENABLE_GTK_DOC_FALSE@ @false
+
+#dist-hook: dist-check-gtkdoc dist-hook-local sgml.stamp html-build.stamp
+# mkdir $(distdir)/tmpl
+# mkdir $(distdir)/xml
+# mkdir $(distdir)/html
+# -cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl
+# -cp $(srcdir)/xml/*.xml $(distdir)/xml
+# cp $(srcdir)/html/* $(distdir)/html
+# -cp $(srcdir)/$(DOC_MODULE).types $(distdir)/
+# -cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/
+# cd $(distdir) && rm -f $(DISTCLEANFILES)
+# ! which gtkdoc-rebase >/dev/null 2>&1 || \
+# gtkdoc-rebase --online --relative --html-dir=$(distdir)/html
+#
+#.PHONY : dist-hook-local docs
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/libblkid/docs/libblkid-config.xml b/libblkid/docs/libblkid-config.xml
new file mode 100644
index 0000000..89fbb7f
--- /dev/null
+++ b/libblkid/docs/libblkid-config.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY version SYSTEM "version.xml">
+]>
+<refentry id="libblkid-config">
+<refmeta>
+<refentrytitle role="top_of_page" id="libblkid-config.top_of_page">Config file</refentrytitle>
+<manvolnum>3</manvolnum>
+<refmiscinfo>LIBBLKID Library</refmiscinfo>
+</refmeta>
+
+<refnamediv>
+<refname>Config file</refname>
+<refpurpose>config file to control paths and basic library behavior</refpurpose>
+</refnamediv>
+
+<refsect1 id="libblkid-config.description" role="desc">
+<title role="desc.title">Description</title>
+<para>
+The standard location of the
+/etc/blkid.conf config file can be overridden by the environment variable
+BLKID_CONF. The following options control the libblkid library:
+</para>
+
+<variablelist role="params">
+ <varlistentry>
+ <term>SEND_UEVENT=<parameter>yes|not</parameter></term>
+ <listitem><simpara>
+ Sends uevent when /dev/disk/by-{label,uuid}/
+ symlink does not match with LABEL or UUID on the device. Default is "yes".
+ </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>CACHE_FILE=<parameter>path</parameter></term>
+ <listitem><simpara>
+ Overrides the standard location of the cache file. This
+ setting can be overridden by the environment variable BLKID_FILE. Default is
+ /etc/blkid.tab.
+ </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>EVALUATE=<parameter>method</parameter></term>
+ <listitem><simpara>
+ Defines LABEL and UUID evaluation method(s). Currently,
+ the libblkid library supports "udev" and "scan" methods. More than one methods
+ may be specified in a comma separated list. Default is "udev,scan". The "udev"
+ method uses udev /dev/disk/by-* symlinks and the "scan" method scans all
+ block devices from the /proc/partitions file.
+ </simpara></listitem>
+ </varlistentry>
+</variablelist>
+
+</refsect1>
+
+</refentry>
diff --git a/libblkid/docs/libblkid-docs.xml b/libblkid/docs/libblkid-docs.xml
new file mode 100644
index 0000000..0dc5270
--- /dev/null
+++ b/libblkid/docs/libblkid-docs.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>libblkid Reference Manual</title>
+ <releaseinfo>for libblkid version &version;</releaseinfo>
+ <copyright>
+ <year>2009-2022</year>
+ <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+ </copyright>
+ </bookinfo>
+
+ <part id="gtk">
+ <title>libblkid Overview</title>
+ <partintro>
+ <para>
+The libblkid library is used to identify block devices (disks) as to their
+content (e.g. filesystem type, partitions) as well as extracting additional
+information such as filesystem labels/volume names, partitions, unique
+identifiers/serial numbers, etc. A common use is to allow use of LABEL= and
+UUID= tags instead of hard-coding specific block device names into
+configuration files.
+ </para>
+ <para>
+The libblkid library
+was written by Andreas Dilger for the ext2 filesystem utilities, with input
+from Ted Ts'o. The library was subsequently heavily modified by Ted Ts'o.
+ </para>
+ <para>
+The low-level probing code, topology and partitions support was written
+by Karel Zak. Currently, the library is maintained by Karel Zak.
+ </para>
+ <para>
+The library is part of the util-linux package since version 2.15 and is
+available from https://www.kernel.org/pub/linux/utils/util-linux/.
+ </para>
+ </partintro>
+ <xi:include href="xml/libblkid-config.xml"/>
+ </part>
+
+ <part>
+ <title>High-level</title>
+ <xi:include href="xml/evaluate.xml"/>
+ <xi:include href="xml/cache.xml"/>
+ <xi:include href="xml/search.xml"/>
+ </part>
+ <part>
+ <title>Low-level</title>
+ <xi:include href="xml/init.xml"/>
+ <xi:include href="xml/lowprobe.xml"/>
+ <xi:include href="xml/lowprobe-tags.xml"/>
+ <xi:include href="xml/superblocks.xml"/>
+ <xi:include href="xml/partitions.xml"/>
+ <xi:include href="xml/topology.xml"/>
+ </part>
+ <part>
+ <title>Common utils</title>
+ <xi:include href="xml/encode.xml"/>
+ <xi:include href="xml/misc.xml"/>
+ </part>
+
+ <index id="api-index">
+ <title>API Index</title>
+ <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.30">
+ <title>Index of new symbols in 2.30</title>
+ <xi:include href="xml/api-index-2.30.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.31">
+ <title>Index of new symbols in 2.31</title>
+ <xi:include href="xml/api-index-2.31.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.35">
+ <title>Index of new symbols in 2.36</title>
+ <xi:include href="xml/api-index-2.36.xml"><xi:fallback /></xi:include>
+ </index>
+</book>
diff --git a/libblkid/docs/libblkid-sections.txt b/libblkid/docs/libblkid-sections.txt
new file mode 100644
index 0000000..c0dc0d4
--- /dev/null
+++ b/libblkid/docs/libblkid-sections.txt
@@ -0,0 +1,208 @@
+<SECTION>
+<FILE>evaluate</FILE>
+blkid_evaluate_tag
+blkid_evaluate_spec
+</SECTION>
+
+<SECTION>
+<FILE>init</FILE>
+blkid_init_debug
+</SECTION>
+
+<SECTION>
+<FILE>cache</FILE>
+blkid_cache
+blkid_gc_cache
+blkid_get_cache
+blkid_put_cache
+blkid_probe_all
+blkid_probe_all_removable
+blkid_probe_all_new
+blkid_verify
+</SECTION>
+
+<SECTION>
+<FILE>search</FILE>
+blkid_dev
+blkid_dev_devname
+blkid_dev_has_tag
+blkid_dev_iterate
+blkid_dev_iterate_begin
+blkid_dev_iterate_end
+blkid_dev_next
+blkid_dev_set_search
+blkid_find_dev_with_tag
+blkid_get_dev
+blkid_get_devname
+blkid_get_tag_value
+blkid_tag_iterate
+blkid_tag_iterate_begin
+blkid_tag_iterate_end
+blkid_tag_next
+</SECTION>
+
+<SECTION>
+<FILE>lowprobe</FILE>
+blkid_probe
+blkid_free_probe
+blkid_new_probe
+blkid_new_probe_from_filename
+blkid_probe_get_devno
+blkid_probe_get_fd
+blkid_probe_get_offset
+blkid_probe_get_sectors
+blkid_probe_get_sectorsize
+blkid_probe_get_size
+blkid_probe_get_wholedisk_devno
+blkid_probe_hide_range
+blkid_probe_is_wholedisk
+blkid_probe_reset_buffers
+blkid_probe_reset_hints
+blkid_probe_set_device
+blkid_probe_set_hint
+blkid_probe_set_sectorsize
+blkid_probe_step_back
+blkid_reset_probe
+</SECTION>
+
+<SECTION>
+<FILE>lowprobe-tags</FILE>
+blkid_do_fullprobe
+blkid_do_wipe
+blkid_do_probe
+blkid_do_safeprobe
+<SUBSECTION>
+blkid_probe_get_value
+blkid_probe_has_value
+blkid_probe_lookup_value
+blkid_probe_numof_values
+</SECTION>
+
+<SECTION>
+<FILE>partitions</FILE>
+blkid_partlist
+blkid_partition
+blkid_parttable
+blkid_probe_enable_partitions
+blkid_probe_set_partitions_flags
+blkid_probe_filter_partitions_type
+blkid_probe_invert_partitions_filter
+blkid_probe_reset_partitions_filter
+<SUBSECTION>
+blkid_known_pttype
+blkid_partitions_get_name
+<SUBSECTION>
+blkid_partition_get_name
+blkid_partition_get_flags
+blkid_partition_get_partno
+blkid_partition_get_size
+blkid_partition_get_start
+blkid_partition_get_table
+blkid_partition_get_type
+blkid_partition_get_type_string
+blkid_partition_get_uuid
+blkid_partition_is_extended
+blkid_partition_is_logical
+blkid_partition_is_primary
+<SUBSECTION>
+blkid_partlist_get_partition
+blkid_partlist_get_partition_by_partno
+blkid_partlist_numof_partitions
+blkid_partlist_devno_to_partition
+blkid_partlist_get_table
+<SUBSECTION>
+blkid_parttable_get_id
+blkid_parttable_get_offset
+blkid_parttable_get_parent
+blkid_parttable_get_type
+<SUBSECTION>
+blkid_probe_get_partitions
+</SECTION>
+
+<SECTION>
+<FILE>superblocks</FILE>
+blkid_probe_enable_superblocks
+<SUBSECTION>
+blkid_known_fstype
+blkid_superblocks_get_name
+<SUBSECTION>
+blkid_probe_filter_superblocks_type
+blkid_probe_filter_superblocks_usage
+blkid_probe_invert_superblocks_filter
+blkid_probe_reset_superblocks_filter
+blkid_probe_set_superblocks_flags
+<SUBSECTION>
+blkid_probe_reset_filter
+blkid_probe_filter_types
+blkid_probe_filter_usage
+blkid_probe_invert_filter
+blkid_probe_set_request
+</SECTION>
+
+<SECTION>
+<FILE>topology</FILE>
+blkid_topology
+blkid_probe_enable_topology
+<SUBSECTION>
+blkid_probe_get_topology
+blkid_topology_get_alignment_offset
+blkid_topology_get_dax
+blkid_topology_get_logical_sector_size
+blkid_topology_get_minimum_io_size
+blkid_topology_get_optimal_io_size
+blkid_topology_get_physical_sector_size
+</SECTION>
+
+<SECTION>
+<FILE>encode</FILE>
+blkid_encode_string
+blkid_safe_string
+</SECTION>
+
+<SECTION>
+<FILE>misc</FILE>
+blkid_loff_t
+blkid_devno_to_devname
+blkid_devno_to_wholedisk
+blkid_get_dev_size
+blkid_get_library_version
+blkid_parse_tag_string
+blkid_parse_version_string
+blkid_send_uevent
+BLKID_VERSION
+BLKID_DATE
+BLKID_FLTR_NOTIN
+BLKID_FLTR_ONLYIN
+BLKID_DEV_CREATE
+BLKID_DEV_FIND
+BLKID_DEV_NORMAL
+BLKID_DEV_VERIFY
+BLKID_PARTS_ENTRY_DETAILS
+BLKID_PARTS_FORCE_GPT
+BLKID_PARTS_MAGIC
+BLKID_PROBREQ_LABEL
+BLKID_PROBREQ_LABELRAW
+BLKID_PROBREQ_SECTYPE
+BLKID_PROBREQ_TYPE
+BLKID_PROBREQ_USAGE
+BLKID_PROBREQ_UUID
+BLKID_PROBREQ_UUIDRAW
+BLKID_PROBREQ_VERSION
+BLKID_SUBLKS_BADCSUM
+BLKID_SUBLKS_DEFAULT
+BLKID_SUBLKS_LABEL
+BLKID_SUBLKS_LABELRAW
+BLKID_SUBLKS_MAGIC
+BLKID_SUBLKS_SECTYPE
+BLKID_SUBLKS_TYPE
+BLKID_SUBLKS_USAGE
+BLKID_SUBLKS_UUID
+BLKID_SUBLKS_UUIDRAW
+BLKID_SUBLKS_VERSION
+BLKID_USAGE_CRYPTO
+BLKID_USAGE_FILESYSTEM
+BLKID_USAGE_OTHER
+BLKID_USAGE_RAID
+</SECTION>
+
+
diff --git a/libblkid/docs/version.xml b/libblkid/docs/version.xml
new file mode 100644
index 0000000..d8c7561
--- /dev/null
+++ b/libblkid/docs/version.xml
@@ -0,0 +1 @@
+2.38.1
diff --git a/libblkid/docs/version.xml.in b/libblkid/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libblkid/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libblkid/libblkid.3 b/libblkid/libblkid.3
new file mode 100644
index 0000000..95175f6
--- /dev/null
+++ b/libblkid/libblkid.3
@@ -0,0 +1,74 @@
+'\" t
+.\" Title: libblkid
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-08-04
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "LIBBLKID" "3" "2022-08-04" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+libblkid \- block device identification library
+.SH "SYNOPSIS"
+.sp
+\fB#include <blkid.h>\fP
+.sp
+\fBcc\fP \fIfile.c\fP \fB\-lblkid\fP
+.SH "DESCRIPTION"
+.sp
+The \fBlibblkid\fP library is used to identify block devices (disks) as to their content (e.g., filesystem type) as well as extracting additional information such as filesystem labels/volume names, unique identifiers/serial numbers. A common use is to allow use of \fBLABEL=\fP and \fBUUID=\fP tags instead of hard\-coding specific block device names into configuration files.
+.sp
+The low\-level part of the library also allows the extraction of information about partitions and block device topology.
+.sp
+The high\-level part of the library keeps information about block devices in a cache file and is verified to still be valid before being returned to the user (if the user has read permission on the raw block device, otherwise not). The cache file also allows unprivileged users (normally anyone other than root, or those not in the "disk" group) to locate devices by label/id. The standard location of the cache file can be overridden by the environment variable \fBBLKID_FILE\fP.
+.sp
+In situations where one is getting information about a single known device, it does not impact performance whether the cache is used or not (unless you are not able to read the block device directly).
+.sp
+The high\-level part of the library supports two methods to determine \fBLABEL/UUID\fP. It reads information directly from a block device or read information from /dev/disk/by\-* udev symlinks. The udev is preferred method by default.
+.sp
+If you are dealing with multiple devices, use of the cache is highly recommended (even if empty) as devices will be scanned at most one time and the on\-disk cache will be updated if possible.
+.sp
+In some cases (modular kernels), block devices are not even visible until after they are accessed the first time, so it is critical that there is some way to locate these devices without enumerating only visible devices, so the use of the cache file is \fBrequired\fP in this situation.
+.SH "CONFIGURATION FILE"
+.sp
+The standard location of the \fI/etc/blkid.conf\fP config file can be overridden by the environment variable \fBBLKID_CONF\fP. For more details about the config file see \fBblkid\fP(8) man page.
+.SH "AUTHORS"
+.sp
+\fBlibblkid\fP was written by Andreas Dilger for the ext2 filesystem utilities, with input from Ted Ts\(cqo. The library was subsequently heavily modified by Ted Ts\(cqo.
+.sp
+The low\-level probing code was rewritten by Karel Zak.
+.SH "COPYING"
+.sp
+\fBlibblkid\fP is available under the terms of the GNU Library General Public License (LGPL), version 2 (or at your discretion any later version).
+.SH "SEE ALSO"
+.sp
+\fBblkid\fP(8),
+\fBfindfs\fP(8)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibblkid\fP library is part of the util\-linux package since version 2.15. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libblkid/libblkid.3.adoc b/libblkid/libblkid.3.adoc
new file mode 100644
index 0000000..18302a0
--- /dev/null
+++ b/libblkid/libblkid.3.adoc
@@ -0,0 +1,67 @@
+//po4a: entry man manual
+////
+Copyright 2001 Andreas Dilger (adilger@turbolinux.com)
+This man page was created for libblkid.so.1.0 from e2fsprogs-1.24.
+This file may be copied under the terms of the GNU Lesser General Public License.
+Created Wed Sep 14 12:02:12 2001, Andreas Dilger
+////
+= libblkid(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libblkid
+:firstversion: 2.15
+
+== NAME
+
+libblkid - block device identification library
+
+== SYNOPSIS
+
+*#include <blkid.h>*
+
+*cc* _file.c_ *-lblkid*
+
+== DESCRIPTION
+
+The *libblkid* library is used to identify block devices (disks) as to their content (e.g., filesystem type) as well as extracting additional information such as filesystem labels/volume names, unique identifiers/serial numbers. A common use is to allow use of *LABEL=* and *UUID=* tags instead of hard-coding specific block device names into configuration files.
+
+The low-level part of the library also allows the extraction of information about partitions and block device topology.
+
+The high-level part of the library keeps information about block devices in a cache file and is verified to still be valid before being returned to the user (if the user has read permission on the raw block device, otherwise not). The cache file also allows unprivileged users (normally anyone other than root, or those not in the "disk" group) to locate devices by label/id. The standard location of the cache file can be overridden by the environment variable *BLKID_FILE*.
+
+In situations where one is getting information about a single known device, it does not impact performance whether the cache is used or not (unless you are not able to read the block device directly).
+
+The high-level part of the library supports two methods to determine *LABEL/UUID*. It reads information directly from a block device or read information from /dev/disk/by-* udev symlinks. The udev is preferred method by default.
+
+If you are dealing with multiple devices, use of the cache is highly recommended (even if empty) as devices will be scanned at most one time and the on-disk cache will be updated if possible.
+
+In some cases (modular kernels), block devices are not even visible until after they are accessed the first time, so it is critical that there is some way to locate these devices without enumerating only visible devices, so the use of the cache file is *required* in this situation.
+
+== CONFIGURATION FILE
+
+The standard location of the _/etc/blkid.conf_ config file can be overridden by the environment variable *BLKID_CONF*. For more details about the config file see *blkid*(8) man page.
+
+== AUTHORS
+
+*libblkid* was written by Andreas Dilger for the ext2 filesystem utilities, with input from Ted Ts'o. The library was subsequently heavily modified by Ted Ts'o.
+
+The low-level probing code was rewritten by Karel Zak.
+
+== COPYING
+
+*libblkid* is available under the terms of the GNU Library General Public License (LGPL), version 2 (or at your discretion any later version).
+
+== SEE ALSO
+
+*blkid*(8),
+*findfs*(8)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libblkid/meson.build b/libblkid/meson.build
new file mode 100644
index 0000000..6444bf3
--- /dev/null
+++ b/libblkid/meson.build
@@ -0,0 +1,152 @@
+dir_libblkid = include_directories('.', 'src')
+
+defs = configuration_data()
+defs.set('LIBBLKID_DATE', libblkid_date)
+defs.set('LIBBLKID_VERSION', pc_version)
+
+blkid_h = configure_file(
+ input : 'src/blkid.h.in',
+ output : 'blkid.h',
+ configuration : defs,
+ install : build_libblkid,
+ install_dir : join_paths(get_option('includedir'), 'blkid'),
+)
+
+lib_blkid_sources = '''
+ src/blkidP.h
+ src/init.c
+ src/cache.c
+ src/config.c
+ src/dev.c
+ src/devname.c
+ src/devno.c
+ src/encode.c
+ src/evaluate.c
+ src/getsize.c
+ src/probe.c
+ src/read.c
+ src/resolve.c
+ src/save.c
+ src/tag.c
+ src/verify.c
+ src/version.c
+
+ src/partitions/aix.c
+ src/partitions/aix.h
+ src/partitions/atari.c
+ src/partitions/bsd.c
+ src/partitions/dos.c
+ src/partitions/gpt.c
+ src/partitions/mac.c
+ src/partitions/minix.c
+ src/partitions/partitions.c
+ src/partitions/partitions.h
+ src/partitions/sgi.c
+ src/partitions/solaris_x86.c
+ src/partitions/sun.c
+ src/partitions/ultrix.c
+ src/partitions/unixware.c
+
+ src/superblocks/adaptec_raid.c
+ src/superblocks/apfs.c
+ src/superblocks/bcache.c
+ src/superblocks/befs.c
+ src/superblocks/bfs.c
+ src/superblocks/bitlocker.c
+ src/superblocks/bluestore.c
+ src/superblocks/btrfs.c
+ src/superblocks/cramfs.c
+ src/superblocks/ddf_raid.c
+ src/superblocks/drbd.c
+ src/superblocks/drbdproxy_datalog.c
+ src/superblocks/drbdmanage.c
+ src/superblocks/exfat.c
+ src/superblocks/exfs.c
+ src/superblocks/ext.c
+ src/superblocks/f2fs.c
+ src/superblocks/gfs.c
+ src/superblocks/hfs.c
+ src/superblocks/highpoint_raid.c
+ src/superblocks/hpfs.c
+ src/superblocks/iso9660.c
+ src/superblocks/isw_raid.c
+ src/superblocks/jfs.c
+ src/superblocks/jmicron_raid.c
+ src/superblocks/linux_raid.c
+ src/superblocks/lsi_raid.c
+ src/superblocks/luks.c
+ src/superblocks/lvm.c
+ src/superblocks/minix.c
+ src/superblocks/mpool.c
+ src/superblocks/netware.c
+ src/superblocks/nilfs.c
+ src/superblocks/ntfs.c
+ src/superblocks/refs.c
+ src/superblocks/nvidia_raid.c
+ src/superblocks/ocfs.c
+ src/superblocks/promise_raid.c
+ src/superblocks/reiserfs.c
+ src/superblocks/romfs.c
+ src/superblocks/silicon_raid.c
+ src/superblocks/squashfs.c
+ src/superblocks/stratis.c
+ src/superblocks/superblocks.c
+ src/superblocks/superblocks.h
+ src/superblocks/swap.c
+ src/superblocks/sysv.c
+ src/superblocks/ubi.c
+ src/superblocks/ubifs.c
+ src/superblocks/udf.c
+ src/superblocks/ufs.c
+ src/superblocks/vdo.c
+ src/superblocks/vfat.c
+ src/superblocks/via_raid.c
+ src/superblocks/vmfs.c
+ src/superblocks/vxfs.c
+ src/superblocks/xfs.c
+ src/superblocks/zfs.c
+ src/superblocks/zonefs.c
+ src/superblocks/erofs.c
+
+ src/topology/topology.c
+ src/topology/topology.h
+'''.split()
+
+if LINUX
+ lib_blkid_sources += '''
+ src/topology/dm.c
+ src/topology/evms.c
+ src/topology/ioctl.c
+ src/topology/lvm.c
+ src/topology/md.c
+ src/topology/sysfs.c
+ '''.split()
+endif
+
+libblkid_sym = 'src/libblkid.sym'
+libblkid_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libblkid_sym)
+
+if build_libblkid and not have_dirfd and not have_ddfd
+ error('neither dirfd nor ddfd are available')
+endif
+
+lib_blkid = both_libraries(
+ 'blkid',
+ list_h,
+ lib_blkid_sources,
+ include_directories : [dir_include, dir_libblkid],
+ link_depends : libblkid_sym,
+ version : libblkid_version,
+ link_args : ['-Wl,--version-script=@0@'.format(libblkid_sym_path)],
+ link_with : lib_common,
+ dependencies : build_libblkid ? [] : disabler(),
+ install : build_libblkid)
+
+lib_blkid_static = lib_blkid.get_static_lib()
+
+if build_libblkid
+ pkgconfig.generate(lib_blkid,
+ description : 'Block device id library',
+ subdirs : 'blkid',
+ version : pc_version)
+endif
diff --git a/libblkid/samples/Makemodule.am b/libblkid/samples/Makemodule.am
new file mode 100644
index 0000000..dd05fc9
--- /dev/null
+++ b/libblkid/samples/Makemodule.am
@@ -0,0 +1,22 @@
+
+check_PROGRAMS += \
+ sample-mkfs \
+ sample-partitions \
+ sample-superblocks \
+ sample-topology
+
+sample_mkfs_SOURCES = libblkid/samples/mkfs.c
+sample_mkfs_LDADD = libblkid.la $(LDADD)
+sample_mkfs_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir)
+
+sample_partitions_SOURCES = libblkid/samples/partitions.c
+sample_partitions_LDADD = libblkid.la $(LDADD)
+sample_partitions_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir)
+
+sample_superblocks_SOURCES = libblkid/samples/superblocks.c
+sample_superblocks_LDADD = libblkid.la $(LDADD)
+sample_superblocks_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir)
+
+sample_topology_SOURCES = libblkid/samples/topology.c
+sample_topology_LDADD = libblkid.la $(LDADD)
+sample_topology_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir)
diff --git a/libblkid/samples/mkfs.c b/libblkid/samples/mkfs.c
new file mode 100644
index 0000000..250782a
--- /dev/null
+++ b/libblkid/samples/mkfs.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ char *devname;
+ blkid_probe pr;
+ blkid_topology tp;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device> "
+ "-- checks based on libblkid for mkfs-like programs.\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ devname = argv[1];
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ err(EXIT_FAILURE, "%s: failed to create a new libblkid probe",
+ devname);
+
+ /*
+ * check Filesystems / Partitions overwrite
+ */
+
+ /* enable partitions probing (superblocks are enabled by default) */
+ blkid_probe_enable_partitions(pr, TRUE);
+
+ rc = blkid_do_fullprobe(pr);
+ if (rc == -1)
+ errx(EXIT_FAILURE, "%s: blkid_do_fullprobe() failed", devname);
+ else if (rc == 0) {
+ const char *type;
+
+ if (!blkid_probe_lookup_value(pr, "TYPE", &type, NULL))
+ errx(EXIT_FAILURE, "%s: appears to contain an existing "
+ "%s superblock", devname, type);
+
+ if (!blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL))
+ errx(EXIT_FAILURE, "%s: appears to contain a partition "
+ "table (%s)", devname, type);
+ }
+
+ /*
+ * get topology details
+ */
+ tp = blkid_probe_get_topology(pr);
+ if (!tp)
+ errx(EXIT_FAILURE, "%s: failed to read topology", devname);
+
+
+ /* ... your mkfs.<type> code or so ...
+
+ off = blkid_topology_get_alignment_offset(tp);
+
+ */
+
+ blkid_free_probe(pr);
+
+ return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/partitions.c b/libblkid/samples/partitions.c
new file mode 100644
index 0000000..c5830e6
--- /dev/null
+++ b/libblkid/samples/partitions.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+ int i, nparts;
+ char *devname;
+ blkid_probe pr;
+ blkid_partlist ls;
+ blkid_parttable root_tab;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device|file> "
+ "-- prints partitions\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ devname = argv[1];
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ err(EXIT_FAILURE, "%s: failed to create a new libblkid probe",
+ devname);
+ /* Binary interface */
+ ls = blkid_probe_get_partitions(pr);
+ if (!ls)
+ errx(EXIT_FAILURE, "%s: failed to read partitions\n", devname);
+
+ /*
+ * Print info about the primary (root) partition table
+ */
+ root_tab = blkid_partlist_get_table(ls);
+ if (!root_tab)
+ errx(EXIT_FAILURE, "%s: does not contains any "
+ "known partition table\n", devname);
+
+ printf("size: %jd, sector size: %u, PT: %s, offset: %jd, id=%s\n---\n",
+ (intmax_t)blkid_probe_get_size(pr),
+ blkid_probe_get_sectorsize(pr),
+ blkid_parttable_get_type(root_tab),
+ (intmax_t)blkid_parttable_get_offset(root_tab),
+ blkid_parttable_get_id(root_tab));
+
+ /*
+ * List partitions
+ */
+ nparts = blkid_partlist_numof_partitions(ls);
+ if (!nparts)
+ goto done;
+
+ for (i = 0; i < nparts; i++) {
+ const char *p;
+ blkid_partition par = blkid_partlist_get_partition(ls, i);
+ blkid_parttable tab = blkid_partition_get_table(par);
+
+ printf("#%d: %10llu %10llu 0x%x",
+ blkid_partition_get_partno(par),
+ (unsigned long long) blkid_partition_get_start(par),
+ (unsigned long long) blkid_partition_get_size(par),
+ blkid_partition_get_type(par));
+
+ if (root_tab != tab)
+ /* subpartition (BSD, Minix, ...) */
+ printf(" (%s)", blkid_parttable_get_type(tab));
+
+ p = blkid_partition_get_name(par);
+ if (p)
+ printf(" name='%s'", p);
+ p = blkid_partition_get_uuid(par);
+ if (p)
+ printf(" uuid='%s'", p);
+ p = blkid_partition_get_type_string(par);
+ if (p)
+ printf(" type='%s'", p);
+
+ putc('\n', stdout);
+ }
+
+done:
+ blkid_free_probe(pr);
+ return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/superblocks.c b/libblkid/samples/superblocks.c
new file mode 100644
index 0000000..7d95557
--- /dev/null
+++ b/libblkid/samples/superblocks.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ char *devname;
+ blkid_probe pr;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device> "
+ "-- prints superblocks details about the device\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ devname = argv[1];
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ err(EXIT_FAILURE, "%s: failed to create a new libblkid probe",
+ devname);
+
+ /* enable topology probing */
+ blkid_probe_enable_superblocks(pr, TRUE);
+
+ /* set all flags */
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_LABELRAW |
+ BLKID_SUBLKS_UUID | BLKID_SUBLKS_UUIDRAW |
+ BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
+ BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION |
+ BLKID_SUBLKS_MAGIC);
+
+ rc = blkid_do_safeprobe(pr);
+ if (rc == -1)
+ errx(EXIT_FAILURE, "%s: blkid_do_safeprobe() failed", devname);
+ else if (rc == 1)
+ warnx("%s: cannot gather information about superblocks", devname);
+ else {
+ int i, nvals = blkid_probe_numof_values(pr);
+
+ for (i = 0; i < nvals; i++) {
+ const char *name, *data;
+
+ blkid_probe_get_value(pr, i, &name, &data, NULL);
+ printf("\t%s = %s\n", name, data);
+ }
+ }
+
+ blkid_free_probe(pr);
+ return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/topology.c b/libblkid/samples/topology.c
new file mode 100644
index 0000000..7d21567
--- /dev/null
+++ b/libblkid/samples/topology.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ char *devname;
+ blkid_probe pr;
+ blkid_topology tp;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <device> "
+ "-- prints topology details about the device\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ devname = argv[1];
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ err(EXIT_FAILURE, "%s: failed to create a new libblkid probe",
+ devname);
+ /*
+ * Binary interface
+ */
+ tp = blkid_probe_get_topology(pr);
+ if (tp) {
+ printf("----- binary interface:\n");
+ printf("\talignment offset : %lu\n",
+ blkid_topology_get_alignment_offset(tp));
+ printf("\tminimum io size : %lu\n",
+ blkid_topology_get_minimum_io_size(tp));
+ printf("\toptimal io size : %lu\n",
+ blkid_topology_get_optimal_io_size(tp));
+ printf("\tlogical sector size : %lu\n",
+ blkid_topology_get_logical_sector_size(tp));
+ printf("\tphysical sector size : %lu\n",
+ blkid_topology_get_physical_sector_size(tp));
+ printf("\tdax support : %lu\n",
+ blkid_topology_get_dax(tp));
+ }
+
+ /*
+ * NAME=value interface
+ */
+
+ /* enable topology probing */
+ blkid_probe_enable_topology(pr, TRUE);
+
+ /* disable superblocks probing (enabled by default) */
+ blkid_probe_enable_superblocks(pr, FALSE);
+
+ rc = blkid_do_fullprobe(pr);
+ if (rc == -1)
+ errx(EXIT_FAILURE, "%s: blkid_do_fullprobe() failed", devname);
+ else if (rc == 1)
+ warnx("%s: missing topology information", devname);
+ else {
+ int i, nvals = blkid_probe_numof_values(pr);
+
+ printf("----- NAME=value interface (values: %d):\n", nvals);
+
+ for (i = 0; i < nvals; i++) {
+ const char *name, *data;
+
+ blkid_probe_get_value(pr, i, &name, &data, NULL);
+ printf("\t%s = %s\n", name, data);
+ }
+ }
+
+ blkid_free_probe(pr);
+ return EXIT_SUCCESS;
+}
diff --git a/libblkid/src/Makemodule.am b/libblkid/src/Makemodule.am
new file mode 100644
index 0000000..16a0a28
--- /dev/null
+++ b/libblkid/src/Makemodule.am
@@ -0,0 +1,232 @@
+
+# blkid.h is generated, so it's store in builddir!
+blkidincdir = $(includedir)/blkid
+nodist_blkidinc_HEADERS = libblkid/src/blkid.h
+
+usrlib_exec_LTLIBRARIES += libblkid.la
+libblkid_la_SOURCES = \
+ include/list.h \
+ \
+ libblkid/src/blkidP.h \
+ libblkid/src/init.c \
+ libblkid/src/cache.c \
+ libblkid/src/config.c \
+ libblkid/src/dev.c \
+ libblkid/src/devname.c \
+ libblkid/src/devno.c \
+ libblkid/src/encode.c \
+ libblkid/src/evaluate.c \
+ libblkid/src/getsize.c \
+ libblkid/src/probe.c \
+ libblkid/src/read.c \
+ libblkid/src/resolve.c \
+ libblkid/src/save.c \
+ libblkid/src/superblocks/superblocks.h \
+ libblkid/src/tag.c \
+ libblkid/src/verify.c \
+ libblkid/src/version.c \
+ \
+ libblkid/src/partitions/aix.c \
+ libblkid/src/partitions/aix.h \
+ libblkid/src/partitions/atari.c \
+ libblkid/src/partitions/bsd.c \
+ libblkid/src/partitions/dos.c \
+ libblkid/src/partitions/gpt.c \
+ libblkid/src/partitions/mac.c \
+ libblkid/src/partitions/minix.c \
+ libblkid/src/partitions/partitions.c \
+ libblkid/src/partitions/partitions.h \
+ libblkid/src/partitions/sgi.c \
+ libblkid/src/partitions/solaris_x86.c \
+ libblkid/src/partitions/sun.c \
+ libblkid/src/partitions/ultrix.c \
+ libblkid/src/partitions/unixware.c \
+ \
+ libblkid/src/superblocks/adaptec_raid.c \
+ libblkid/src/superblocks/apfs.c \
+ libblkid/src/superblocks/bcache.c \
+ libblkid/src/superblocks/befs.c \
+ libblkid/src/superblocks/bfs.c \
+ libblkid/src/superblocks/bitlocker.c \
+ libblkid/src/superblocks/bluestore.c \
+ libblkid/src/superblocks/btrfs.c \
+ libblkid/src/superblocks/cramfs.c \
+ libblkid/src/superblocks/ddf_raid.c \
+ libblkid/src/superblocks/drbd.c \
+ libblkid/src/superblocks/drbdproxy_datalog.c \
+ libblkid/src/superblocks/drbdmanage.c \
+ libblkid/src/superblocks/exfat.c \
+ libblkid/src/superblocks/exfs.c \
+ libblkid/src/superblocks/ext.c \
+ libblkid/src/superblocks/f2fs.c \
+ libblkid/src/superblocks/gfs.c \
+ libblkid/src/superblocks/hfs.c \
+ libblkid/src/superblocks/highpoint_raid.c \
+ libblkid/src/superblocks/hpfs.c \
+ libblkid/src/superblocks/iso9660.c \
+ libblkid/src/superblocks/isw_raid.c \
+ libblkid/src/superblocks/jfs.c \
+ libblkid/src/superblocks/jmicron_raid.c \
+ libblkid/src/superblocks/linux_raid.c \
+ libblkid/src/superblocks/lsi_raid.c \
+ libblkid/src/superblocks/luks.c \
+ libblkid/src/superblocks/lvm.c \
+ libblkid/src/superblocks/minix.c \
+ libblkid/src/superblocks/mpool.c \
+ libblkid/src/superblocks/netware.c \
+ libblkid/src/superblocks/nilfs.c \
+ libblkid/src/superblocks/ntfs.c \
+ libblkid/src/superblocks/refs.c \
+ libblkid/src/superblocks/nvidia_raid.c \
+ libblkid/src/superblocks/ocfs.c \
+ libblkid/src/superblocks/promise_raid.c \
+ libblkid/src/superblocks/reiserfs.c \
+ libblkid/src/superblocks/romfs.c \
+ libblkid/src/superblocks/silicon_raid.c \
+ libblkid/src/superblocks/squashfs.c \
+ libblkid/src/superblocks/stratis.c \
+ libblkid/src/superblocks/superblocks.c \
+ libblkid/src/superblocks/superblocks.h \
+ libblkid/src/superblocks/swap.c \
+ libblkid/src/superblocks/sysv.c \
+ libblkid/src/superblocks/ubi.c \
+ libblkid/src/superblocks/ubifs.c \
+ libblkid/src/superblocks/udf.c \
+ libblkid/src/superblocks/ufs.c \
+ libblkid/src/superblocks/vdo.c \
+ libblkid/src/superblocks/vfat.c \
+ libblkid/src/superblocks/via_raid.c \
+ libblkid/src/superblocks/vmfs.c \
+ libblkid/src/superblocks/vxfs.c \
+ libblkid/src/superblocks/xfs.c \
+ libblkid/src/superblocks/zfs.c \
+ libblkid/src/superblocks/zonefs.c \
+ libblkid/src/superblocks/erofs.c \
+ \
+ libblkid/src/topology/topology.c \
+ libblkid/src/topology/topology.h
+
+if LINUX
+libblkid_la_SOURCES += \
+ libblkid/src/topology/dm.c \
+ libblkid/src/topology/evms.c \
+ libblkid/src/topology/ioctl.c \
+ libblkid/src/topology/lvm.c \
+ libblkid/src/topology/md.c \
+ libblkid/src/topology/sysfs.c
+endif
+
+libblkid_la_LIBADD = libcommon.la
+
+EXTRA_libblkid_la_DEPENDENCIES = \
+ libblkid/src/libblkid.sym
+
+libblkid_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_libblkid_incdir) \
+ -I$(top_srcdir)/libblkid/src
+
+libblkid_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libblkid_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libblkid/src/libblkid.sym
+endif
+libblkid_la_LDFLAGS += -version-info $(LIBBLKID_VERSION_INFO)
+
+EXTRA_DIST += \
+ libblkid/src/libblkid.sym
+
+if BUILD_LIBBLKID_TESTS
+check_PROGRAMS += \
+ test_blkid_cache \
+ test_blkid_config \
+ test_blkid_dev \
+ test_blkid_devname \
+ test_blkid_devno \
+ test_blkid_evaluate \
+ test_blkid_read \
+ test_blkid_resolve \
+ test_blkid_save \
+ test_blkid_tag \
+ test_blkid_verify
+
+blkid_tests_cflags = -DTEST_PROGRAM $(libblkid_la_CFLAGS)
+blkid_tests_ldflags =
+blkid_tests_ldadd = $(LDADD) libblkid.la
+blkid_tests_ldflags += -static
+
+test_blkid_cache_SOURCES = libblkid/src/cache.c
+test_blkid_cache_CFLAGS = $(blkid_tests_cflags)
+test_blkid_cache_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_cache_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_config_SOURCES = libblkid/src/config.c
+test_blkid_config_CFLAGS = $(blkid_tests_cflags)
+test_blkid_config_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_config_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_dev_SOURCES = libblkid/src/dev.c
+test_blkid_dev_CFLAGS = $(blkid_tests_cflags)
+test_blkid_dev_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_dev_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_devname_SOURCES = libblkid/src/devname.c
+test_blkid_devname_CFLAGS = $(blkid_tests_cflags)
+test_blkid_devname_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_devname_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_devno_SOURCES = libblkid/src/devno.c
+test_blkid_devno_CFLAGS = $(blkid_tests_cflags)
+test_blkid_devno_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_devno_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_evaluate_SOURCES = libblkid/src/evaluate.c
+test_blkid_evaluate_CFLAGS = $(blkid_tests_cflags)
+test_blkid_evaluate_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_evaluate_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_read_SOURCES = libblkid/src/read.c
+test_blkid_read_CFLAGS = $(blkid_tests_cflags)
+test_blkid_read_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_read_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_resolve_SOURCES = libblkid/src/resolve.c
+test_blkid_resolve_CFLAGS = $(blkid_tests_cflags)
+test_blkid_resolve_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_resolve_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_save_SOURCES = libblkid/src/save.c
+test_blkid_save_CFLAGS = $(blkid_tests_cflags)
+test_blkid_save_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_save_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_tag_SOURCES = libblkid/src/tag.c
+test_blkid_tag_CFLAGS = $(blkid_tests_cflags)
+test_blkid_tag_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_tag_LDADD = $(blkid_tests_ldadd)
+
+test_blkid_verify_SOURCES = libblkid/src/verify.c
+test_blkid_verify_CFLAGS = $(blkid_tests_cflags)
+test_blkid_verify_LDFLAGS = $(blkid_tests_ldflags)
+test_blkid_verify_LDADD = $(blkid_tests_ldadd)
+
+endif # BUILD_LIBBLKID_TESTS
+
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libblkid:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libblkid.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libblkid.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libblkid.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libblkid.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libblkid.so); \
+ fi
+
+uninstall-hook-libblkid:
+ rm -f $(DESTDIR)$(libdir)/libblkid.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libblkid
+UNINSTALL_HOOKS += uninstall-hook-libblkid
diff --git a/libblkid/src/blkid.h.in b/libblkid/src/blkid.h.in
new file mode 100644
index 0000000..3cd4116
--- /dev/null
+++ b/libblkid/src/blkid.h.in
@@ -0,0 +1,470 @@
+/*
+ * blkid.h - Interface for libblkid, a library to identify block devices
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ * Copyright (C) 2008 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
+ */
+
+#ifndef _BLKID_BLKID_H
+#define _BLKID_BLKID_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BLKID_VERSION "@LIBBLKID_VERSION@"
+#define BLKID_DATE "@LIBBLKID_DATE@"
+
+/**
+ * blkid_dev:
+ *
+ * The device object keeps information about one device
+ */
+typedef struct blkid_struct_dev *blkid_dev;
+
+/**
+ * blkid_cache:
+ *
+ * information about all system devices
+ */
+typedef struct blkid_struct_cache *blkid_cache;
+
+/**
+ * blkid_probe:
+ *
+ * low-level probing setting
+ */
+typedef struct blkid_struct_probe *blkid_probe;
+
+/**
+ * blkid_topology:
+ *
+ * device topology information
+ */
+typedef struct blkid_struct_topology *blkid_topology;
+
+/**
+ * blkid_partlist
+ *
+ * list of all detected partitions and partitions tables
+ */
+typedef struct blkid_struct_partlist *blkid_partlist;
+
+/**
+ * blkid_partition:
+ *
+ * information about a partition
+ */
+typedef struct blkid_struct_partition *blkid_partition;
+
+/**
+ * blkid_parttable:
+ *
+ * information about a partition table
+ */
+typedef struct blkid_struct_parttable *blkid_parttable;
+
+/**
+ * blkid_loff_t:
+ *
+ * 64-bit signed number for offsets and sizes
+ */
+typedef int64_t blkid_loff_t;
+
+/**
+ * blkid_tag_iterate:
+ *
+ * tags iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_tag_iterate *blkid_tag_iterate;
+
+/**
+ * blkid_dev_iterate:
+ *
+ * devices iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_dev_iterate *blkid_dev_iterate;
+
+/*
+ * Flags for blkid_get_dev
+ *
+ * BLKID_DEV_CREATE Create an empty device structure if not found
+ * in the cache.
+ * BLKID_DEV_VERIFY Make sure the device structure corresponds
+ * with reality.
+ * BLKID_DEV_FIND Just look up a device entry, and return NULL
+ * if it is not found.
+ * BLKID_DEV_NORMAL Get a valid device structure, either from the
+ * cache or by probing the device.
+ */
+#define BLKID_DEV_FIND 0x0000
+#define BLKID_DEV_CREATE 0x0001
+#define BLKID_DEV_VERIFY 0x0002
+#define BLKID_DEV_NORMAL (BLKID_DEV_CREATE | BLKID_DEV_VERIFY)
+
+
+#ifndef __GNUC_PREREQ
+# if defined __GNUC__ && defined __GNUC_MINOR__
+# define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+# else
+# define __GNUC_PREREQ(maj, min) 0
+# endif
+#endif
+
+#ifndef __ul_attribute__
+# if __GNUC_PREREQ (3, 4)
+# define __ul_attribute__(_a_) __attribute__(_a_)
+# else
+# define __ul_attribute__(_a_)
+# endif
+#endif
+
+/* init.c */
+extern void blkid_init_debug(int mask);
+
+/* cache.c */
+extern void blkid_put_cache(blkid_cache cache);
+extern int blkid_get_cache(blkid_cache *cache, const char *filename);
+extern void blkid_gc_cache(blkid_cache cache);
+
+/* dev.c */
+extern const char *blkid_dev_devname(blkid_dev dev)
+ __ul_attribute__((warn_unused_result));
+
+extern blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache);
+extern int blkid_dev_set_search(blkid_dev_iterate iter,
+ const char *search_type, const char *search_value);
+extern int blkid_dev_next(blkid_dev_iterate iterate, blkid_dev *dev);
+extern void blkid_dev_iterate_end(blkid_dev_iterate iterate);
+
+/* devno.c */
+extern char *blkid_devno_to_devname(dev_t devno)
+ __ul_attribute__((warn_unused_result));
+extern int blkid_devno_to_wholedisk(dev_t dev, char *diskname,
+ size_t len, dev_t *diskdevno)
+ __ul_attribute__((warn_unused_result));
+
+/* devname.c */
+extern int blkid_probe_all(blkid_cache cache);
+extern int blkid_probe_all_new(blkid_cache cache);
+extern int blkid_probe_all_removable(blkid_cache cache);
+
+extern blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags);
+
+/* getsize.c */
+extern blkid_loff_t blkid_get_dev_size(int fd);
+
+/* verify.c */
+extern blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev);
+
+/* read.c */
+
+/* resolve.c */
+extern char *blkid_get_tag_value(blkid_cache cache, const char *tagname,
+ const char *devname)
+ __ul_attribute__((warn_unused_result));
+extern char *blkid_get_devname(blkid_cache cache, const char *token,
+ const char *value)
+ __ul_attribute__((warn_unused_result));
+
+/* tag.c */
+extern blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev);
+extern int blkid_tag_next(blkid_tag_iterate iterate,
+ const char **type, const char **value);
+extern void blkid_tag_iterate_end(blkid_tag_iterate iterate);
+extern int blkid_dev_has_tag(blkid_dev dev, const char *type, const char *value);
+
+extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
+ const char *type,
+ const char *value);
+
+extern int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val);
+
+/* version.c */
+extern int blkid_parse_version_string(const char *ver_string)
+ __ul_attribute__((nonnull));
+extern int blkid_get_library_version(const char **ver_string,
+ const char **date_string);
+
+/* encode.c */
+extern int blkid_encode_string(const char *str, char *str_enc, size_t len);
+extern int blkid_safe_string(const char *str, char *str_safe, size_t len);
+
+/* evaluate.c */
+extern int blkid_send_uevent(const char *devname, const char *action);
+extern char *blkid_evaluate_tag(const char *token, const char *value,
+ blkid_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
+ __ul_attribute__((warn_unused_result));
+
+/* probe.c */
+extern blkid_probe blkid_new_probe(void)
+ __ul_attribute__((warn_unused_result));
+extern blkid_probe blkid_new_probe_from_filename(const char *filename)
+ __ul_attribute__((warn_unused_result))
+ __ul_attribute__((nonnull));
+extern void blkid_free_probe(blkid_probe pr);
+
+extern void blkid_reset_probe(blkid_probe pr);
+extern int blkid_probe_reset_buffers(blkid_probe pr);
+extern int blkid_probe_hide_range(blkid_probe pr, uint64_t off, uint64_t len);
+
+extern int blkid_probe_set_device(blkid_probe pr, int fd,
+ blkid_loff_t off, blkid_loff_t size)
+ __ul_attribute__((nonnull));
+
+extern dev_t blkid_probe_get_devno(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern int blkid_probe_is_wholedisk(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern blkid_loff_t blkid_probe_get_size(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern blkid_loff_t blkid_probe_get_offset(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern unsigned int blkid_probe_get_sectorsize(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_set_sectorsize(blkid_probe pr, unsigned int sz)
+ __ul_attribute__((nonnull));
+extern blkid_loff_t blkid_probe_get_sectors(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern int blkid_probe_get_fd(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern int blkid_probe_set_hint(blkid_probe pr, const char *name, uint64_t value)
+ __ul_attribute__((nonnull));
+extern void blkid_probe_reset_hints(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+/*
+ * superblocks probing
+ */
+extern int blkid_known_fstype(const char *fstype)
+ __ul_attribute__((nonnull));
+
+extern int blkid_superblocks_get_name(size_t idx, const char **name, int *usage);
+
+extern int blkid_probe_enable_superblocks(blkid_probe pr, int enable)
+ __ul_attribute__((nonnull));
+
+#define BLKID_SUBLKS_LABEL (1 << 1) /* read LABEL from superblock */
+#define BLKID_SUBLKS_LABELRAW (1 << 2) /* read and define LABEL_RAW result value*/
+#define BLKID_SUBLKS_UUID (1 << 3) /* read UUID from superblock */
+#define BLKID_SUBLKS_UUIDRAW (1 << 4) /* read and define UUID_RAW result value */
+#define BLKID_SUBLKS_TYPE (1 << 5) /* define TYPE result value */
+#define BLKID_SUBLKS_SECTYPE (1 << 6) /* define compatible fs type (second type) */
+#define BLKID_SUBLKS_USAGE (1 << 7) /* define USAGE result value */
+#define BLKID_SUBLKS_VERSION (1 << 8) /* read FS type from superblock */
+#define BLKID_SUBLKS_MAGIC (1 << 9) /* define SBMAGIC and SBMAGIC_OFFSET */
+#define BLKID_SUBLKS_BADCSUM (1 << 10) /* allow a bad checksum */
+
+#define BLKID_SUBLKS_DEFAULT (BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | \
+ BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE)
+
+extern int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_reset_superblocks_filter(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_invert_superblocks_filter(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+/**
+ * BLKID_FLTR_NOTIN
+ */
+#define BLKID_FLTR_NOTIN 1
+/**
+ * BLKID_FLTR_ONLYIN
+ */
+#define BLKID_FLTR_ONLYIN 2
+extern int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[])
+ __ul_attribute__((nonnull));
+
+#define BLKID_USAGE_FILESYSTEM (1 << 1)
+#define BLKID_USAGE_RAID (1 << 2)
+#define BLKID_USAGE_CRYPTO (1 << 3)
+#define BLKID_USAGE_OTHER (1 << 4)
+extern int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage)
+ __ul_attribute__((nonnull));
+
+/*
+ * topology probing
+ */
+extern int blkid_probe_enable_topology(blkid_probe pr, int enable)
+ __ul_attribute__((nonnull));
+
+/* binary interface */
+extern blkid_topology blkid_probe_get_topology(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern unsigned long blkid_topology_get_alignment_offset(blkid_topology tp)
+ __ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_minimum_io_size(blkid_topology tp)
+ __ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_optimal_io_size(blkid_topology tp)
+ __ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_logical_sector_size(blkid_topology tp)
+ __ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_physical_sector_size(blkid_topology tp)
+ __ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_dax(blkid_topology tp)
+ __ul_attribute__((nonnull));
+
+/*
+ * partitions probing
+ */
+extern int blkid_known_pttype(const char *pttype);
+extern int blkid_partitions_get_name(const size_t idx, const char **name);
+
+extern int blkid_probe_enable_partitions(blkid_probe pr, int enable)
+ __ul_attribute__((nonnull));
+
+extern int blkid_probe_reset_partitions_filter(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_invert_partitions_filter(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[])
+ __ul_attribute__((nonnull));
+
+/* partitions probing flags */
+#define BLKID_PARTS_FORCE_GPT (1 << 1)
+#define BLKID_PARTS_ENTRY_DETAILS (1 << 2)
+#define BLKID_PARTS_MAGIC (1 << 3)
+extern int blkid_probe_set_partitions_flags(blkid_probe pr, int flags)
+ __ul_attribute__((nonnull));
+
+/* binary interface */
+extern blkid_partlist blkid_probe_get_partitions(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern int blkid_partlist_numof_partitions(blkid_partlist ls)
+ __ul_attribute__((nonnull));
+extern blkid_parttable blkid_partlist_get_table(blkid_partlist ls)
+ __ul_attribute__((nonnull));
+extern blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n)
+ __ul_attribute__((nonnull));
+extern blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n)
+ __ul_attribute__((nonnull));
+extern blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno)
+ __ul_attribute__((nonnull));
+extern blkid_parttable blkid_partition_get_table(blkid_partition par)
+ __ul_attribute__((nonnull));
+
+extern const char *blkid_partition_get_name(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern const char *blkid_partition_get_uuid(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern int blkid_partition_get_partno(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern blkid_loff_t blkid_partition_get_start(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern blkid_loff_t blkid_partition_get_size(blkid_partition par)
+ __ul_attribute__((nonnull));
+
+extern int blkid_partition_get_type(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern const char *blkid_partition_get_type_string(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern unsigned long long blkid_partition_get_flags(blkid_partition par)
+ __ul_attribute__((nonnull));
+
+extern int blkid_partition_is_logical(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern int blkid_partition_is_extended(blkid_partition par)
+ __ul_attribute__((nonnull));
+extern int blkid_partition_is_primary(blkid_partition par)
+ __ul_attribute__((nonnull));
+
+extern const char *blkid_parttable_get_type(blkid_parttable tab)
+ __ul_attribute__((nonnull));
+extern const char *blkid_parttable_get_id(blkid_parttable tab)
+ __ul_attribute__((nonnull));
+extern blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab)
+ __ul_attribute__((nonnull));
+extern blkid_partition blkid_parttable_get_parent(blkid_parttable tab)
+ __ul_attribute__((nonnull));
+
+/*
+ * NAME=value low-level interface
+ */
+extern int blkid_do_probe(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_do_safeprobe(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_do_fullprobe(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+extern int blkid_probe_numof_values(blkid_probe pr)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_get_value(blkid_probe pr, int num, const char **name,
+ const char **data, size_t *len)
+ __ul_attribute__((nonnull(1)));
+extern int blkid_probe_lookup_value(blkid_probe pr, const char *name,
+ const char **data, size_t *len)
+ __ul_attribute__((nonnull(1, 2)));
+extern int blkid_probe_has_value(blkid_probe pr, const char *name)
+ __ul_attribute__((nonnull));
+extern int blkid_do_wipe(blkid_probe pr, int dryrun)
+ __ul_attribute__((nonnull));
+extern int blkid_probe_step_back(blkid_probe pr)
+ __ul_attribute__((nonnull));
+
+/*
+ * Deprecated functions/macros
+ */
+#ifndef BLKID_DISABLE_DEPRECATED
+
+#define BLKID_PROBREQ_LABEL BLKID_SUBLKS_LABEL
+#define BLKID_PROBREQ_LABELRAW BLKID_SUBLKS_LABELRAW
+#define BLKID_PROBREQ_UUID BLKID_SUBLKS_UUID
+#define BLKID_PROBREQ_UUIDRAW BLKID_SUBLKS_UUIDRAW
+#define BLKID_PROBREQ_TYPE BLKID_SUBLKS_TYPE
+#define BLKID_PROBREQ_SECTYPE BLKID_SUBLKS_SECTYPE
+#define BLKID_PROBREQ_USAGE BLKID_SUBLKS_USAGE
+#define BLKID_PROBREQ_VERSION BLKID_SUBLKS_VERSION
+
+extern int blkid_probe_set_request(blkid_probe pr, int flags)
+ __ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
+ __ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
+ __ul_attribute__((deprecated));
+
+extern int blkid_probe_invert_filter(blkid_probe pr)
+ __ul_attribute__((deprecated));
+
+extern int blkid_probe_reset_filter(blkid_probe pr)
+ __ul_attribute__((deprecated));
+
+#endif /* BLKID_DISABLE_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BLKID_BLKID_H */
diff --git a/libblkid/src/blkidP.h b/libblkid/src/blkidP.h
new file mode 100644
index 0000000..d0a2cfe
--- /dev/null
+++ b/libblkid/src/blkidP.h
@@ -0,0 +1,564 @@
+/*
+ * blkidP.h - Internal interfaces for libblkid
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifndef _BLKID_BLKIDP_H
+#define _BLKID_BLKIDP_H
+
+/* Always confirm that /dev/disk-by symlinks match with LABEL/UUID on device */
+/* #define CONFIG_BLKID_VERIFY_UDEV 1 */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#ifndef UUID_STR_LEN
+# define UUID_STR_LEN 37
+#endif
+
+#include "c.h"
+#include "bitops.h" /* $(top_srcdir)/include/ */
+#include "blkdev.h"
+
+#include "debug.h"
+#include "blkid.h"
+#include "list.h"
+#include "encode.h"
+
+/*
+ * This describes the attributes of a specific device.
+ * We can traverse all of the tags by bid_tags (linking to the tag bit_names).
+ * The bid_label and bid_uuid fields are shortcuts to the LABEL and UUID tag
+ * values, if they exist.
+ */
+struct blkid_struct_dev
+{
+ struct list_head bid_devs; /* All devices in the cache */
+ struct list_head bid_tags; /* All tags for this device */
+ blkid_cache bid_cache; /* Dev belongs to this cache */
+ char *bid_name; /* Device real path (as used in cache) */
+ char *bid_xname; /* Device path as used by application (maybe symlink..) */
+ char *bid_type; /* Preferred device TYPE */
+ int bid_pri; /* Device priority */
+ dev_t bid_devno; /* Device major/minor number */
+ time_t bid_time; /* Last update time of device */
+ suseconds_t bid_utime; /* Last update time (microseconds) */
+ unsigned int bid_flags; /* Device status bitflags */
+ char *bid_label; /* Shortcut to device LABEL */
+ char *bid_uuid; /* Shortcut to binary UUID */
+};
+
+#define BLKID_BID_FL_VERIFIED 0x0001 /* Device data validated from disk */
+#define BLKID_BID_FL_INVALID 0x0004 /* Device is invalid */
+#define BLKID_BID_FL_REMOVABLE 0x0008 /* Device added by blkid_probe_all_removable() */
+
+/*
+ * Each tag defines a NAME=value pair for a particular device. The tags
+ * are linked via bit_names for a single device, so that traversing the
+ * names list will get you a list of all tags associated with a device.
+ * They are also linked via bit_values for all devices, so one can easily
+ * search all tags with a given NAME for a specific value.
+ */
+struct blkid_struct_tag
+{
+ struct list_head bit_tags; /* All tags for this device */
+ struct list_head bit_names; /* All tags with given NAME */
+ char *bit_name; /* NAME of tag (shared) */
+ char *bit_val; /* value of tag */
+ blkid_dev bit_dev; /* pointer to device */
+};
+typedef struct blkid_struct_tag *blkid_tag;
+
+/*
+ * Chain IDs
+ */
+enum {
+ BLKID_CHAIN_SUBLKS, /* FS/RAID superblocks (enabled by default) */
+ BLKID_CHAIN_TOPLGY, /* Block device topology */
+ BLKID_CHAIN_PARTS, /* Partition tables */
+
+ BLKID_NCHAINS /* number of chains */
+};
+
+struct blkid_chain {
+ const struct blkid_chaindrv *driver; /* chain driver */
+
+ int enabled; /* boolean */
+ int flags; /* BLKID_<chain>_* */
+ int binary; /* boolean */
+ int idx; /* index of the current prober (or -1) */
+ unsigned long *fltr; /* filter or NULL */
+ void *data; /* private chain data or NULL */
+};
+
+/*
+ * Chain driver
+ */
+struct blkid_chaindrv {
+ const size_t id; /* BLKID_CHAIN_* */
+ const char *name; /* name of chain (for debug purpose) */
+ const int dflt_flags; /* default chain flags */
+ const int dflt_enabled; /* default enabled boolean */
+ int has_fltr; /* boolean */
+
+ const struct blkid_idinfo **idinfos; /* description of probing functions */
+ const size_t nidinfos; /* number of idinfos */
+
+ /* driver operations */
+ int (*probe)(blkid_probe, struct blkid_chain *);
+ int (*safeprobe)(blkid_probe, struct blkid_chain *);
+ void (*free_data)(blkid_probe, void *);
+};
+
+/* chains */
+extern const struct blkid_chaindrv superblocks_drv;
+extern const struct blkid_chaindrv topology_drv;
+extern const struct blkid_chaindrv partitions_drv;
+
+/*
+ * Low-level probe result
+ */
+struct blkid_prval
+{
+ const char *name; /* value name */
+ unsigned char *data; /* value data */
+ size_t len; /* length of value data */
+
+ struct blkid_chain *chain; /* owner */
+ struct list_head prvals; /* list of results */
+};
+
+/*
+ * Filesystem / Raid magic strings
+ */
+struct blkid_idmag
+{
+ const char *magic; /* magic string */
+ unsigned int len; /* length of magic */
+
+ const char *hoff; /* hint which contains byte offset to kboff */
+ long kboff; /* kilobyte offset of superblock */
+ unsigned int sboff; /* byte offset within superblock */
+
+ int is_zoned; /* indicate magic location is calculated based on zone position */
+ long zonenum; /* zone number which has superblock */
+ long kboff_inzone; /* kilobyte offset of superblock in a zone */
+};
+
+/*
+ * Filesystem / Raid description
+ */
+struct blkid_idinfo
+{
+ const char *name; /* fs, raid or partition table name */
+ int usage; /* BLKID_USAGE_* flag */
+ int flags; /* BLKID_IDINFO_* flags */
+ int minsz; /* minimal device size */
+
+ /* probe function */
+ int (*probefunc)(blkid_probe pr, const struct blkid_idmag *mag);
+
+ struct blkid_idmag magics[]; /* NULL or array with magic strings */
+};
+
+#define BLKID_NONE_MAGIC {{ NULL }}
+
+/*
+ * tolerant FS - can share the same device with more filesystems (e.g. typical
+ * on CD-ROMs). We need this flag to detect ambivalent results (e.g. valid fat
+ * and valid linux swap on the same device).
+ */
+#define BLKID_IDINFO_TOLERANT (1 << 1)
+
+struct blkid_bufinfo {
+ unsigned char *data;
+ uint64_t off;
+ uint64_t len;
+ struct list_head bufs; /* list of buffers */
+};
+
+/*
+ * Probing hint
+ */
+struct blkid_hint {
+ char *name;
+ uint64_t value;
+ struct list_head hints;
+};
+
+/*
+ * Low-level probing control struct
+ */
+struct blkid_struct_probe
+{
+ int fd; /* device file descriptor */
+ uint64_t off; /* begin of data on the device */
+ uint64_t size; /* end of data on the device */
+
+ dev_t devno; /* device number (st.st_rdev) */
+ dev_t disk_devno; /* devno of the whole-disk or 0 */
+ unsigned int blkssz; /* sector size (BLKSSZGET ioctl) */
+ mode_t mode; /* struct stat.sb_mode */
+ uint64_t zone_size; /* zone size (BLKGETZONESZ ioctl) */
+
+ int flags; /* private library flags */
+ int prob_flags; /* always zeroized by blkid_do_*() */
+
+ uint64_t wipe_off; /* begin of the wiped area */
+ uint64_t wipe_size; /* size of the wiped area */
+ struct blkid_chain *wipe_chain; /* superblock, partition, ... */
+
+ struct list_head buffers; /* list of buffers */
+ struct list_head hints;
+
+ struct blkid_chain chains[BLKID_NCHAINS]; /* array of chains */
+ struct blkid_chain *cur_chain; /* current chain */
+
+ struct list_head values; /* results */
+
+ struct blkid_struct_probe *parent; /* for clones */
+ struct blkid_struct_probe *disk_probe; /* whole-disk probing */
+};
+
+/* private flags library flags */
+#define BLKID_FL_PRIVATE_FD (1 << 1) /* see blkid_new_probe_from_filename() */
+#define BLKID_FL_TINY_DEV (1 << 2) /* <= 1.47MiB (floppy or so) */
+#define BLKID_FL_CDROM_DEV (1 << 3) /* is a CD/DVD drive */
+#define BLKID_FL_NOSCAN_DEV (1 << 4) /* do not scan this device */
+#define BLKID_FL_MODIF_BUFF (1 << 5) /* cached buffers has been modified */
+
+/* private per-probing flags */
+#define BLKID_PROBE_FL_IGNORE_PT (1 << 1) /* ignore partition table */
+
+extern blkid_probe blkid_clone_probe(blkid_probe parent);
+extern blkid_probe blkid_probe_get_wholedisk_probe(blkid_probe pr);
+
+/*
+ * Evaluation methods (for blkid_eval_* API)
+ */
+enum {
+ BLKID_EVAL_UDEV = 0,
+ BLKID_EVAL_SCAN,
+
+ __BLKID_EVAL_LAST
+};
+
+/*
+ * Library config options
+ */
+struct blkid_config {
+ int eval[__BLKID_EVAL_LAST]; /* array with EVALUATION=<udev,cache> options */
+ int nevals; /* number of elems in eval array */
+ int uevent; /* SEND_UEVENT=<yes|not> option */
+ char *cachefile; /* CACHE_FILE=<path> option */
+};
+
+extern struct blkid_config *blkid_read_config(const char *filename)
+ __ul_attribute__((warn_unused_result));
+extern void blkid_free_config(struct blkid_config *conf);
+
+/*
+ * Minimum number of seconds between device probes, even when reading
+ * from the cache. This is to avoid re-probing all devices which were
+ * just probed by another program that does not share the cache.
+ */
+#define BLKID_PROBE_MIN 2
+
+/*
+ * Time in seconds an entry remains verified in the in-memory cache
+ * before being reverified (in case of long-running processes that
+ * keep a cache in memory and continue to use it for a long time).
+ */
+#define BLKID_PROBE_INTERVAL 200
+
+/* This describes an entire blkid cache file and probed devices.
+ * We can traverse all of the found devices via bic_list.
+ * We can traverse all of the tag types by bic_tags, which hold empty tags
+ * for each tag type. Those tags can be used as list_heads for iterating
+ * through all devices with a specific tag type (e.g. LABEL).
+ */
+struct blkid_struct_cache
+{
+ struct list_head bic_devs; /* List head of all devices */
+ struct list_head bic_tags; /* List head of all tag types */
+ time_t bic_time; /* Last probe time */
+ time_t bic_ftime; /* Mod time of the cachefile */
+ unsigned int bic_flags; /* Status flags of the cache */
+ char *bic_filename; /* filename of cache */
+ blkid_probe probe; /* low-level probing stuff */
+};
+
+#define BLKID_BIC_FL_PROBED 0x0002 /* We probed /proc/partition devices */
+#define BLKID_BIC_FL_CHANGED 0x0004 /* Cache has changed from disk */
+
+/* config file */
+#define BLKID_CONFIG_FILE "/etc/blkid.conf"
+
+/* cache file on systemds with /run */
+#define BLKID_RUNTIME_TOPDIR "/run"
+#define BLKID_RUNTIME_DIR BLKID_RUNTIME_TOPDIR "/blkid"
+#define BLKID_CACHE_FILE BLKID_RUNTIME_DIR "/blkid.tab"
+
+/* old systems */
+#define BLKID_CACHE_FILE_OLD "/etc/blkid.tab"
+
+#define BLKID_PROBE_OK 0
+#define BLKID_PROBE_NONE 1
+
+#define BLKID_ERR_IO 5
+#define BLKID_ERR_SYSFS 9
+#define BLKID_ERR_MEM 12
+#define BLKID_ERR_CACHE 14
+#define BLKID_ERR_DEV 19
+#define BLKID_ERR_PARAM 22
+#define BLKID_ERR_BIG 27
+
+/*
+ * Priority settings for different types of devices
+ */
+#define BLKID_PRI_UBI 50
+#define BLKID_PRI_DM 40
+#define BLKID_PRI_EVMS 30
+#define BLKID_PRI_LVM 20
+#define BLKID_PRI_MD 10
+
+#define BLKID_DEBUG_HELP (1 << 0)
+#define BLKID_DEBUG_INIT (1 << 1)
+#define BLKID_DEBUG_CACHE (1 << 2)
+#define BLKID_DEBUG_CONFIG (1 << 3)
+#define BLKID_DEBUG_DEV (1 << 4)
+#define BLKID_DEBUG_DEVNAME (1 << 5)
+#define BLKID_DEBUG_DEVNO (1 << 6)
+#define BLKID_DEBUG_EVALUATE (1 << 7)
+#define BLKID_DEBUG_LOWPROBE (1 << 8)
+#define BLKID_DEBUG_PROBE (1 << 9)
+#define BLKID_DEBUG_READ (1 << 10)
+#define BLKID_DEBUG_SAVE (1 << 11)
+#define BLKID_DEBUG_TAG (1 << 12)
+#define BLKID_DEBUG_BUFFER (1 << 13)
+#define BLKID_DEBUG_ALL 0xFFFF /* (1 << 16) aka FFFF is expected by API */
+
+UL_DEBUG_DECLARE_MASK(libblkid);
+#define DBG(m, x) __UL_DBG(libblkid, BLKID_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(libblkid, BLKID_DEBUG_, m, x)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libblkid)
+#include "debugobj.h"
+
+extern void blkid_debug_dump_dev(blkid_dev dev);
+
+
+/* devno.c */
+struct dir_list {
+ char *name;
+ struct dir_list *next;
+};
+extern void blkid__scan_dir(char *, dev_t, struct dir_list **, char **)
+ __attribute__((nonnull(1,4)));
+extern int blkid_driver_has_major(const char *drvname, int drvmaj)
+ __attribute__((warn_unused_result));
+
+/* read.c */
+extern void blkid_read_cache(blkid_cache cache)
+ __attribute__((nonnull));
+
+/* save.c */
+extern int blkid_flush_cache(blkid_cache cache)
+ __attribute__((nonnull));
+
+/* cache */
+extern char *blkid_safe_getenv(const char *arg)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern char *blkid_get_cache_filename(struct blkid_config *conf)
+ __attribute__((warn_unused_result));
+/*
+ * Functions to create and find a specific tag type: tag.c
+ */
+extern void blkid_free_tag(blkid_tag tag);
+extern blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern int blkid_set_tag(blkid_dev dev, const char *name,
+ const char *value, const int vlength)
+ __attribute__((nonnull(1,2)));
+
+/*
+ * Functions to create and find a specific tag type: dev.c
+ */
+extern blkid_dev blkid_new_dev(void)
+ __attribute__((warn_unused_result));
+extern void blkid_free_dev(blkid_dev dev);
+
+/* probe.c */
+extern int blkid_probe_is_tiny(blkid_probe pr)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+extern int blkid_probe_is_cdrom(blkid_probe pr)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern unsigned char *blkid_probe_get_buffer(blkid_probe pr,
+ uint64_t off, uint64_t len)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern unsigned char *blkid_probe_get_sector(blkid_probe pr, unsigned int sector)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern int blkid_probe_get_dimension(blkid_probe pr,
+ uint64_t *off, uint64_t *size)
+ __attribute__((nonnull));
+
+extern int blkid_probe_set_dimension(blkid_probe pr,
+ uint64_t off, uint64_t size)
+ __attribute__((nonnull));
+
+extern int blkid_probe_get_idmag(blkid_probe pr, const struct blkid_idinfo *id,
+ uint64_t *offset, const struct blkid_idmag **res)
+ __attribute__((nonnull(1)));
+
+/* returns superblock according to 'struct blkid_idmag' */
+extern unsigned char *_blkid_probe_get_sb(blkid_probe pr, const struct blkid_idmag *mag, size_t size);
+#define blkid_probe_get_sb(_pr, _mag, type) \
+ ((type *) _blkid_probe_get_sb((_pr), _mag, sizeof(type)))
+
+extern blkid_partlist blkid_probe_get_partlist(blkid_probe pr)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern int blkid_probe_is_covered_by_pt(blkid_probe pr,
+ uint64_t offset, uint64_t size)
+ __attribute__((warn_unused_result));
+
+extern void blkid_probe_chain_reset_values(blkid_probe pr, struct blkid_chain *chn)
+ __attribute__((nonnull));
+extern int blkid_probe_chain_save_values(blkid_probe pr,
+ struct blkid_chain *chn,
+ struct list_head *vals)
+ __attribute__((nonnull));
+
+extern struct blkid_prval *blkid_probe_assign_value(blkid_probe pr,
+ const char *name)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern void blkid_probe_free_value(struct blkid_prval *v);
+
+
+extern void blkid_probe_append_values_list(blkid_probe pr,
+ struct list_head *vals)
+ __attribute__((nonnull));
+
+extern void blkid_probe_free_values_list(struct list_head *vals);
+
+extern struct blkid_chain *blkid_probe_get_chain(blkid_probe pr)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern struct blkid_prval *__blkid_probe_get_value(blkid_probe pr, int num)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern struct blkid_prval *__blkid_probe_lookup_value(blkid_probe pr, const char *name)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern unsigned long *blkid_probe_get_filter(blkid_probe pr, int chain, int create)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern int __blkid_probe_invert_filter(blkid_probe pr, int chain)
+ __attribute__((nonnull));
+extern int __blkid_probe_reset_filter(blkid_probe pr, int chain)
+ __attribute__((nonnull));
+extern int __blkid_probe_filter_types(blkid_probe pr, int chain, int flag, char *names[])
+ __attribute__((nonnull));
+
+extern void *blkid_probe_get_binary_data(blkid_probe pr, struct blkid_chain *chn)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+
+extern struct blkid_prval *blkid_probe_new_val(void)
+ __attribute__((warn_unused_result));
+extern int blkid_probe_set_value(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len)
+ __attribute__((nonnull));
+extern int blkid_probe_value_set_data(struct blkid_prval *v,
+ const unsigned char *data, size_t len)
+ __attribute__((nonnull));
+
+extern int blkid_probe_vsprintf_value(blkid_probe pr, const char *name,
+ const char *fmt, va_list ap)
+ __attribute__((nonnull));
+
+extern int blkid_probe_sprintf_value(blkid_probe pr, const char *name,
+ const char *fmt, ...)
+ __attribute__((nonnull))
+ __attribute__ ((__format__ (__printf__, 3, 4)));
+
+extern int blkid_probe_set_magic(blkid_probe pr, uint64_t offset,
+ size_t len, const unsigned char *magic)
+ __attribute__((nonnull));
+
+extern int blkid_probe_verify_csum(blkid_probe pr, uint64_t csum, uint64_t expected)
+ __attribute__((nonnull));
+
+extern void blkid_unparse_uuid(const unsigned char *uuid, char *str, size_t len)
+ __attribute__((nonnull));
+extern int blkid_uuid_is_empty(const unsigned char *buf, size_t len);
+
+extern size_t blkid_rtrim_whitespace(unsigned char *str)
+ __attribute__((nonnull));
+extern size_t blkid_ltrim_whitespace(unsigned char *str)
+ __attribute__((nonnull));
+
+extern void blkid_probe_set_wiper(blkid_probe pr, uint64_t off,
+ uint64_t size)
+ __attribute__((nonnull));
+extern int blkid_probe_is_wiped(blkid_probe pr, struct blkid_chain **chn,
+ uint64_t off, uint64_t size)
+ __attribute__((nonnull))
+ __attribute__((warn_unused_result));
+extern void blkid_probe_use_wiper(blkid_probe pr, uint64_t off, uint64_t size)
+ __attribute__((nonnull));
+
+extern int blkid_probe_get_hint(blkid_probe pr, const char *name, uint64_t *value)
+ __attribute__((nonnull(1,2)))
+ __attribute__((warn_unused_result));
+
+/* filter bitmap macros */
+#define blkid_bmp_wordsize (8 * sizeof(unsigned long))
+#define blkid_bmp_idx_bit(item) (1UL << ((item) % blkid_bmp_wordsize))
+#define blkid_bmp_idx_byte(item) ((item) / blkid_bmp_wordsize)
+
+#define blkid_bmp_set_item(bmp, item) \
+ ((bmp)[ blkid_bmp_idx_byte(item) ] |= blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_unset_item(bmp, item) \
+ ((bmp)[ blkid_bmp_idx_byte(item) ] &= ~blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_get_item(bmp, item) \
+ ((bmp)[ blkid_bmp_idx_byte(item) ] & blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_nwords(max_items) \
+ (((max_items) + blkid_bmp_wordsize) / blkid_bmp_wordsize)
+
+#define blkid_bmp_nbytes(max_items) \
+ (blkid_bmp_nwords(max_items) * sizeof(unsigned long))
+
+#endif /* _BLKID_BLKIDP_H */
diff --git a/libblkid/src/cache.c b/libblkid/src/cache.c
new file mode 100644
index 0000000..a7f2c95
--- /dev/null
+++ b/libblkid/src/cache.c
@@ -0,0 +1,225 @@
+/*
+ * cache.c - allocation/initialization/free routines for cache
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include "blkidP.h"
+#include "env.h"
+
+/**
+ * SECTION:cache
+ * @title: Cache
+ * @short_description: basic routines to work with libblkid cache
+ *
+ * Block device information is normally kept in a cache file blkid.tab and is
+ * verified to still be valid before being returned to the user (if the user has
+ * read permission on the raw block device, otherwise not). The cache file also
+ * allows unprivileged users (normally anyone other than root, or those not in the
+ * "disk" group) to locate devices by label/id. The standard location of the
+ * cache file can be overridden by the environment variable BLKID_FILE.
+ *
+ * In situations where one is getting information about a single known device, it
+ * does not impact performance whether the cache is used or not (unless you are
+ * not able to read the block device directly). If you are dealing with multiple
+ * devices, use of the cache is highly recommended (even if empty) as devices will
+ * be scanned at most one time and the on-disk cache will be updated if possible.
+ * There is rarely a reason not to use the cache.
+ *
+ * In some cases (modular kernels), block devices are not even visible until after
+ * they are accessed the first time, so it is critical that there is some way to
+ * locate these devices without enumerating only visible devices, so the use of
+ * the cache file is required in this situation.
+ */
+static const char *get_default_cache_filename(void)
+{
+ struct stat st;
+
+ if (stat(BLKID_RUNTIME_TOPDIR, &st) == 0 && S_ISDIR(st.st_mode))
+ return BLKID_CACHE_FILE; /* cache in /run */
+
+ return BLKID_CACHE_FILE_OLD; /* cache in /etc */
+}
+
+/* returns allocated path to cache */
+char *blkid_get_cache_filename(struct blkid_config *conf)
+{
+ char *filename;
+
+ filename = safe_getenv("BLKID_FILE");
+ if (filename)
+ filename = strdup(filename);
+ else if (conf)
+ filename = conf->cachefile ? strdup(conf->cachefile) : NULL;
+ else {
+ struct blkid_config *c = blkid_read_config(NULL);
+ if (!c)
+ filename = strdup(get_default_cache_filename());
+ else {
+ filename = c->cachefile; /* already allocated */
+ c->cachefile = NULL;
+ blkid_free_config(c);
+ }
+ }
+ return filename;
+}
+
+/**
+ * blkid_get_cache:
+ * @cache: pointer to return cache handler
+ * @filename: path to the cache file or NULL for the default path
+ *
+ * Allocates and initializes library cache handler.
+ *
+ * Returns: 0 on success or number less than zero in case of error.
+ */
+int blkid_get_cache(blkid_cache *ret_cache, const char *filename)
+{
+ blkid_cache cache;
+
+ if (!ret_cache)
+ return -BLKID_ERR_PARAM;
+
+ blkid_init_debug(0);
+
+ if (!(cache = calloc(1, sizeof(struct blkid_struct_cache))))
+ return -BLKID_ERR_MEM;
+
+ DBG(CACHE, ul_debugobj(cache, "alloc (from %s)", filename ? filename : "default cache"));
+ INIT_LIST_HEAD(&cache->bic_devs);
+ INIT_LIST_HEAD(&cache->bic_tags);
+
+ if (filename && !*filename)
+ filename = NULL;
+ if (filename)
+ cache->bic_filename = strdup(filename);
+ else
+ cache->bic_filename = blkid_get_cache_filename(NULL);
+
+ blkid_read_cache(cache);
+ *ret_cache = cache;
+ return 0;
+}
+
+/**
+ * blkid_put_cache:
+ * @cache: cache handler
+ *
+ * Saves changes to cache file.
+ */
+void blkid_put_cache(blkid_cache cache)
+{
+ if (!cache)
+ return;
+
+ (void) blkid_flush_cache(cache);
+
+ DBG(CACHE, ul_debugobj(cache, "freeing cache struct"));
+
+ /* DBG(CACHE, ul_debug_dump_cache(cache)); */
+
+ while (!list_empty(&cache->bic_devs)) {
+ blkid_dev dev = list_entry(cache->bic_devs.next,
+ struct blkid_struct_dev,
+ bid_devs);
+ blkid_free_dev(dev);
+ }
+
+ DBG(CACHE, ul_debugobj(cache, "freeing cache tag heads"));
+ while (!list_empty(&cache->bic_tags)) {
+ blkid_tag tag = list_entry(cache->bic_tags.next,
+ struct blkid_struct_tag,
+ bit_tags);
+
+ while (!list_empty(&tag->bit_names)) {
+ blkid_tag bad = list_entry(tag->bit_names.next,
+ struct blkid_struct_tag,
+ bit_names);
+
+ DBG(CACHE, ul_debugobj(cache, "warning: unfreed tag %s=%s",
+ bad->bit_name, bad->bit_val));
+ blkid_free_tag(bad);
+ }
+ blkid_free_tag(tag);
+ }
+
+ blkid_free_probe(cache->probe);
+
+ free(cache->bic_filename);
+ free(cache);
+}
+
+/**
+ * blkid_gc_cache:
+ * @cache: cache handler
+ *
+ * Removes garbage (non-existing devices) from the cache.
+ */
+void blkid_gc_cache(blkid_cache cache)
+{
+ struct list_head *p, *pnext;
+ struct stat st;
+
+ if (!cache)
+ return;
+
+ list_for_each_safe(p, pnext, &cache->bic_devs) {
+ blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs);
+ if (stat(dev->bid_name, &st) < 0) {
+ DBG(CACHE, ul_debugobj(cache, "freeing non-existing %s", dev->bid_name));
+ blkid_free_dev(dev);
+ cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+ } else {
+ DBG(CACHE, ul_debug("Device %s exists", dev->bid_name));
+ }
+ }
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char** argv)
+{
+ blkid_cache cache = NULL;
+ int ret;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+
+ if ((argc > 2)) {
+ fprintf(stderr, "Usage: %s [filename] \n", argv[0]);
+ exit(1);
+ }
+
+ if ((ret = blkid_get_cache(&cache, argv[1])) < 0) {
+ fprintf(stderr, "error %d parsing cache file %s\n", ret,
+ argv[1] ? argv[1] : blkid_get_cache_filename(NULL));
+ exit(1);
+ }
+ if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+ if ((ret = blkid_probe_all(cache)) < 0)
+ fprintf(stderr, "error probing devices\n");
+
+ blkid_put_cache(cache);
+
+ return ret;
+}
+#endif
diff --git a/libblkid/src/config.c b/libblkid/src/config.c
new file mode 100644
index 0000000..f229b3e
--- /dev/null
+++ b/libblkid/src/config.c
@@ -0,0 +1,201 @@
+/*
+ * config.c - blkid.conf routines
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "blkidP.h"
+#include "env.h"
+
+static int parse_evaluate(struct blkid_config *conf, char *s)
+{
+ while(s && *s) {
+ char *sep;
+
+ if (conf->nevals >= __BLKID_EVAL_LAST)
+ goto err;
+ sep = strchr(s, ',');
+ if (sep)
+ *sep = '\0';
+ if (strcmp(s, "udev") == 0)
+ conf->eval[conf->nevals] = BLKID_EVAL_UDEV;
+ else if (strcmp(s, "scan") == 0)
+ conf->eval[conf->nevals] = BLKID_EVAL_SCAN;
+ else
+ goto err;
+ conf->nevals++;
+ if (sep)
+ s = sep + 1;
+ else
+ break;
+ }
+ return 0;
+err:
+ DBG(CONFIG, ul_debug(
+ "config file: unknown evaluation method '%s'.", s));
+ return -1;
+}
+
+static int parse_next(FILE *fd, struct blkid_config *conf)
+{
+ char buf[BUFSIZ];
+ char *s;
+
+ /* read the next non-blank non-comment line */
+ do {
+ if (fgets (buf, sizeof(buf), fd) == NULL)
+ return feof(fd) ? 0 : -1;
+ s = strchr (buf, '\n');
+ if (!s) {
+ /* Missing final newline? Otherwise extremely */
+ /* long line - assume file was corrupted */
+ if (feof(fd))
+ s = strchr (buf, '\0');
+ else {
+ DBG(CONFIG, ul_debug(
+ "config file: missing newline at line '%s'.",
+ buf));
+ return -1;
+ }
+ }
+ *s = '\0';
+ if (--s >= buf && *s == '\r')
+ *s = '\0';
+
+ s = buf;
+ while (*s == ' ' || *s == '\t') /* skip space */
+ s++;
+
+ } while (*s == '\0' || *s == '#');
+
+ if (!strncmp(s, "SEND_UEVENT=", 12)) {
+ s += 12;
+ if (*s && !strcasecmp(s, "yes"))
+ conf->uevent = TRUE;
+ else if (*s)
+ conf->uevent = FALSE;
+ } else if (!strncmp(s, "CACHE_FILE=", 11)) {
+ s += 11;
+ free(conf->cachefile);
+ if (*s)
+ conf->cachefile = strdup(s);
+ else
+ conf->cachefile = NULL;
+ } else if (!strncmp(s, "EVALUATE=", 9)) {
+ s += 9;
+ if (*s && parse_evaluate(conf, s) == -1)
+ return -1;
+ } else {
+ DBG(CONFIG, ul_debug(
+ "config file: unknown option '%s'.", s));
+ return -1;
+ }
+ return 0;
+}
+
+/* return real config data or built-in default */
+struct blkid_config *blkid_read_config(const char *filename)
+{
+ struct blkid_config *conf;
+ FILE *f;
+
+ if (!filename)
+ filename = safe_getenv("BLKID_CONF");
+ if (!filename)
+ filename = BLKID_CONFIG_FILE;
+
+ conf = calloc(1, sizeof(*conf));
+ if (!conf)
+ return NULL;
+ conf->uevent = -1;
+
+ DBG(CONFIG, ul_debug("reading config file: %s.", filename));
+
+ f = fopen(filename, "r" UL_CLOEXECSTR);
+ if (!f) {
+ DBG(CONFIG, ul_debug("%s: does not exist, using built-in default", filename));
+ goto dflt;
+ }
+ while (!feof(f)) {
+ if (parse_next(f, conf)) {
+ DBG(CONFIG, ul_debug("%s: parse error", filename));
+ goto err;
+ }
+ }
+dflt:
+ if (!conf->nevals) {
+ conf->eval[0] = BLKID_EVAL_UDEV;
+ conf->eval[1] = BLKID_EVAL_SCAN;
+ conf->nevals = 2;
+ }
+ if (!conf->cachefile)
+ conf->cachefile = strdup(BLKID_CACHE_FILE);
+ if (conf->uevent == -1)
+ conf->uevent = TRUE;
+ if (f)
+ fclose(f);
+ return conf;
+err:
+ free(conf);
+ fclose(f);
+ return NULL;
+}
+
+void blkid_free_config(struct blkid_config *conf)
+{
+ if (!conf)
+ return;
+ free(conf->cachefile);
+ free(conf);
+}
+
+#ifdef TEST_PROGRAM
+/*
+ * usage: tst_config [<filename>]
+ */
+int main(int argc, char *argv[])
+{
+ int i;
+ struct blkid_config *conf;
+ char *filename = NULL;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+
+ if (argc == 2)
+ filename = argv[1];
+
+ conf = blkid_read_config(filename);
+ if (!conf)
+ return EXIT_FAILURE;
+
+ printf("EVALUATE: ");
+ for (i = 0; i < conf->nevals; i++)
+ printf("%s ", conf->eval[i] == BLKID_EVAL_UDEV ? "udev" : "scan");
+ printf("\n");
+
+ printf("SEND UEVENT: %s\n", conf->uevent ? "TRUE" : "FALSE");
+ printf("CACHE_FILE: %s\n", conf->cachefile);
+
+ blkid_free_config(conf);
+ return EXIT_SUCCESS;
+}
+#endif
diff --git a/libblkid/src/dev.c b/libblkid/src/dev.c
new file mode 100644
index 0000000..c38ec3d
--- /dev/null
+++ b/libblkid/src/dev.c
@@ -0,0 +1,279 @@
+/*
+ * dev.c - allocation/initialization/free routines for dev
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "blkidP.h"
+
+/*
+ * NOTE: reference manual is not structured as code. The following section is a generic
+ * section for all high-level cache search+iterate routines.
+ */
+
+/**
+ * SECTION:search
+ * @title: Search and iterate
+ * @short_description: search devices and iterate over devices in the cache.
+ *
+ * Note that high-level probing API provides information about superblocks
+ * (filesystems/raids) only. For partitions and topology is necessary to use
+ * the low-level API.
+ */
+
+blkid_dev blkid_new_dev(void)
+{
+ blkid_dev dev;
+
+ if (!(dev = calloc(1, sizeof(struct blkid_struct_dev))))
+ return NULL;
+
+ DBG(DEV, ul_debugobj(dev, "alloc"));
+ INIT_LIST_HEAD(&dev->bid_devs);
+ INIT_LIST_HEAD(&dev->bid_tags);
+
+ return dev;
+}
+
+void blkid_free_dev(blkid_dev dev)
+{
+ if (!dev)
+ return;
+
+ DBG(DEV, ul_debugobj(dev, "freeing (%s)", dev->bid_name));
+
+ list_del(&dev->bid_devs);
+ while (!list_empty(&dev->bid_tags)) {
+ blkid_tag tag = list_entry(dev->bid_tags.next,
+ struct blkid_struct_tag,
+ bit_tags);
+ blkid_free_tag(tag);
+ }
+ free(dev->bid_xname);
+ free(dev->bid_name);
+ free(dev);
+}
+
+/*
+ * Given a blkid device, return its name. The function returns the name
+ * previously used for blkid_get_dev(). This name does not have to be canonical
+ * (real path) name, but for example symlink.
+ */
+const char *blkid_dev_devname(blkid_dev dev)
+{
+ if (!dev)
+ return NULL;
+ if (dev->bid_xname)
+ return dev->bid_xname;
+ return dev->bid_name;
+}
+
+void blkid_debug_dump_dev(blkid_dev dev)
+{
+ struct list_head *p;
+
+ if (!dev) {
+ printf(" dev: NULL\n");
+ return;
+ }
+
+ fprintf(stderr, " dev: name = %s\n", dev->bid_name);
+ fprintf(stderr, " dev: DEVNO=\"0x%0lx\"\n", (unsigned long)dev->bid_devno);
+ fprintf(stderr, " dev: TIME=\"%lld.%lld\"\n", (long long)dev->bid_time, (long long)dev->bid_utime);
+ fprintf(stderr, " dev: PRI=\"%d\"\n", dev->bid_pri);
+ fprintf(stderr, " dev: flags = 0x%08X\n", dev->bid_flags);
+
+ list_for_each(p, &dev->bid_tags) {
+ blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
+ if (tag)
+ fprintf(stderr, " tag: %s=\"%s\"\n", tag->bit_name,
+ tag->bit_val);
+ else
+ fprintf(stderr, " tag: NULL\n");
+ }
+}
+
+/*
+ * dev iteration routines for the public libblkid interface.
+ *
+ * These routines do not expose the list.h implementation, which are a
+ * contamination of the namespace, and which force us to reveal far, far
+ * too much of our internal implementation. I'm not convinced I want
+ * to keep list.h in the long term, anyway. It's fine for kernel
+ * programming, but performance is not the #1 priority for this
+ * library, and I really don't like the trade-off of type-safety for
+ * performance for this application. [tytso:20030125.2007EST]
+ */
+
+/*
+ * This series of functions iterate over all devices in a blkid cache
+ */
+#define DEV_ITERATE_MAGIC 0x01a5284c
+
+struct blkid_struct_dev_iterate {
+ int magic;
+ blkid_cache cache;
+ char *search_type;
+ char *search_value;
+ struct list_head *p;
+};
+
+blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache)
+{
+ blkid_dev_iterate iter;
+
+ if (!cache) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ iter = malloc(sizeof(struct blkid_struct_dev_iterate));
+ if (iter) {
+ iter->magic = DEV_ITERATE_MAGIC;
+ iter->cache = cache;
+ iter->p = cache->bic_devs.next;
+ iter->search_type = NULL;
+ iter->search_value = NULL;
+ }
+ return iter;
+}
+
+int blkid_dev_set_search(blkid_dev_iterate iter,
+ const char *search_type, const char *search_value)
+{
+ char *new_type, *new_value;
+
+ if (!iter || iter->magic != DEV_ITERATE_MAGIC || !search_type ||
+ !search_value)
+ return -1;
+ new_type = malloc(strlen(search_type)+1);
+ new_value = malloc(strlen(search_value)+1);
+ if (!new_type || !new_value) {
+ free(new_type);
+ free(new_value);
+ return -1;
+ }
+ strcpy(new_type, search_type);
+ strcpy(new_value, search_value);
+ free(iter->search_type);
+ free(iter->search_value);
+ iter->search_type = new_type;
+ iter->search_value = new_value;
+ return 0;
+}
+
+/*
+ * Return 0 on success, -1 on error
+ */
+int blkid_dev_next(blkid_dev_iterate iter,
+ blkid_dev *ret_dev)
+{
+ blkid_dev dev;
+
+ if (!ret_dev || !iter || iter->magic != DEV_ITERATE_MAGIC)
+ return -1;
+ *ret_dev = NULL;
+ while (iter->p != &iter->cache->bic_devs) {
+ dev = list_entry(iter->p, struct blkid_struct_dev, bid_devs);
+ iter->p = iter->p->next;
+ if (iter->search_type &&
+ !blkid_dev_has_tag(dev, iter->search_type,
+ iter->search_value))
+ continue;
+ *ret_dev = dev;
+ return 0;
+ }
+ return -1;
+}
+
+void blkid_dev_iterate_end(blkid_dev_iterate iter)
+{
+ if (!iter || iter->magic != DEV_ITERATE_MAGIC)
+ return;
+ iter->magic = 0;
+ free(iter->search_type);
+ free(iter->search_value);
+ free(iter);
+}
+
+#ifdef TEST_PROGRAM
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+
+static void __attribute__((__noreturn__)) usage(char *prog)
+{
+ fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask]\n", prog);
+ fprintf(stderr, "\tList all devices and exit\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ blkid_dev_iterate iter;
+ blkid_cache cache = NULL;
+ blkid_dev dev;
+ int c, ret;
+ char *tmp;
+ char *file = NULL;
+ char *search_type = NULL;
+ char *search_value = NULL;
+
+ while ((c = getopt (argc, argv, "m:f:")) != EOF)
+ switch (c) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'm':
+ {
+ int mask = strtoul (optarg, &tmp, 0);
+ if (*tmp) {
+ fprintf(stderr, "Invalid debug mask: %s\n",
+ optarg);
+ exit(1);
+ }
+ blkid_init_debug(mask);
+ break;
+ }
+ case '?':
+ usage(argv[0]);
+ }
+ if (argc >= optind+2) {
+ search_type = argv[optind];
+ search_value = argv[optind+1];
+ optind += 2;
+ }
+ if (argc != optind)
+ usage(argv[0]);
+
+ if ((ret = blkid_get_cache(&cache, file)) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+
+ iter = blkid_dev_iterate_begin(cache);
+ if (search_type)
+ blkid_dev_set_search(iter, search_type, search_value);
+ while (blkid_dev_next(iter, &dev) == 0) {
+ printf("Device: %s\n", blkid_dev_devname(dev));
+ }
+ blkid_dev_iterate_end(iter);
+
+
+ blkid_put_cache(cache);
+ return (0);
+}
+#endif
diff --git a/libblkid/src/devname.c b/libblkid/src/devname.c
new file mode 100644
index 0000000..3f48032
--- /dev/null
+++ b/libblkid/src/devname.c
@@ -0,0 +1,647 @@
+/*
+ * devname.c - get a dev by its device inode name
+ *
+ * Copyright (C) Andries Brouwer
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003 Theodore Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#define _GNU_SOURCE 1
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <dirent.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <time.h>
+
+#include "blkidP.h"
+
+#include "canonicalize.h" /* $(top_srcdir)/include */
+#include "pathnames.h"
+#include "sysfs.h"
+#include "fileutils.h"
+
+/*
+ * Find a dev struct in the cache by device name, if available.
+ *
+ * If there is no entry with the specified device name, and the create
+ * flag is set, then create an empty device entry.
+ */
+blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags)
+{
+ blkid_dev dev = NULL, tmp;
+ struct list_head *p, *pnext;
+ char *cn = NULL;
+
+ if (!cache || !devname)
+ return NULL;
+
+ /* search by name */
+ list_for_each(p, &cache->bic_devs) {
+ tmp = list_entry(p, struct blkid_struct_dev, bid_devs);
+ if (strcmp(tmp->bid_name, devname) != 0)
+ continue;
+ dev = tmp;
+ break;
+ }
+
+ /* try canonicalize the name */
+ if (!dev && (cn = canonicalize_path(devname))) {
+ if (strcmp(cn, devname) != 0) {
+ DBG(DEVNAME, ul_debug("search canonical %s", cn));
+ list_for_each(p, &cache->bic_devs) {
+ tmp = list_entry(p, struct blkid_struct_dev, bid_devs);
+ if (strcmp(tmp->bid_name, cn) != 0)
+ continue;
+ dev = tmp;
+
+ /* update name returned by blkid_dev_devname() */
+ free(dev->bid_xname);
+ dev->bid_xname = strdup(devname);
+ break;
+ }
+ } else {
+ free(cn);
+ cn = NULL;
+ }
+ }
+
+ if (!dev && (flags & BLKID_DEV_CREATE)) {
+ if (access(devname, F_OK) < 0)
+ goto done;
+ dev = blkid_new_dev();
+ if (!dev)
+ goto done;
+ dev->bid_time = (uintmax_t)1 << (sizeof(time_t) * 8 - 1);
+ if (cn) {
+ dev->bid_name = cn;
+ dev->bid_xname = strdup(devname);
+ cn = NULL; /* see free() below */
+ } else
+ dev->bid_name = strdup(devname);
+
+ dev->bid_cache = cache;
+ list_add_tail(&dev->bid_devs, &cache->bic_devs);
+ cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+ }
+
+ if (flags & BLKID_DEV_VERIFY) {
+ dev = blkid_verify(cache, dev);
+ if (!dev || !(dev->bid_flags & BLKID_BID_FL_VERIFIED))
+ goto done;
+ /*
+ * If the device is verified, then search the blkid
+ * cache for any entries that match on the type, uuid,
+ * and label, and verify them; if a cache entry can
+ * not be verified, then it's stale and so we remove
+ * it.
+ */
+ list_for_each_safe(p, pnext, &cache->bic_devs) {
+ blkid_dev dev2 = list_entry(p, struct blkid_struct_dev, bid_devs);
+ if (dev2->bid_flags & BLKID_BID_FL_VERIFIED)
+ continue;
+ if (!dev->bid_type || !dev2->bid_type ||
+ strcmp(dev->bid_type, dev2->bid_type) != 0)
+ continue;
+ if (dev->bid_label && dev2->bid_label &&
+ strcmp(dev->bid_label, dev2->bid_label) != 0)
+ continue;
+ if (dev->bid_uuid && dev2->bid_uuid &&
+ strcmp(dev->bid_uuid, dev2->bid_uuid) != 0)
+ continue;
+ if ((dev->bid_label && !dev2->bid_label) ||
+ (!dev->bid_label && dev2->bid_label) ||
+ (dev->bid_uuid && !dev2->bid_uuid) ||
+ (!dev->bid_uuid && dev2->bid_uuid))
+ continue;
+ dev2 = blkid_verify(cache, dev2);
+ if (dev2 && !(dev2->bid_flags & BLKID_BID_FL_VERIFIED))
+ blkid_free_dev(dev2);
+ }
+ }
+done:
+ if (dev)
+ DBG(DEVNAME, ul_debug("%s requested, found %s in cache", devname, dev->bid_name));
+ free(cn);
+ return dev;
+}
+
+/* Directories where we will try to search for device names */
+static const char *dirlist[] = { "/dev", "/devfs", "/devices", NULL };
+
+/*
+ * Return 1 if the device is a device-mapper 'leaf' node
+ * not holding any other devices in its hierarchy.
+ */
+static int is_dm_leaf(const char *devname)
+{
+ DIR *dir;
+ char path[NAME_MAX + 18 + 1];
+ int ret;
+
+ snprintf(path, sizeof(path), "/sys/block/%s/holders", devname);
+ if ((dir = opendir(path)) == NULL)
+ return 0;
+
+ ret = xreaddir(dir) == NULL ? 1 : 0; /* 'leaf' has no entries */
+
+ closedir(dir);
+ return ret;
+}
+
+/*
+ * Probe a single block device to add to the device cache.
+ */
+static void probe_one(blkid_cache cache, const char *ptname,
+ dev_t devno, int pri, int only_if_new, int removable)
+{
+ blkid_dev dev = NULL;
+ struct list_head *p, *pnext;
+ const char **dir;
+ char *devname = NULL;
+
+ /* See if we already have this device number in the cache. */
+ list_for_each_safe(p, pnext, &cache->bic_devs) {
+ blkid_dev tmp = list_entry(p, struct blkid_struct_dev,
+ bid_devs);
+ if (tmp->bid_devno == devno) {
+ if (only_if_new && !access(tmp->bid_name, F_OK))
+ return;
+ dev = blkid_verify(cache, tmp);
+ if (dev && (dev->bid_flags & BLKID_BID_FL_VERIFIED))
+ break;
+ dev = NULL;
+ }
+ }
+ if (dev && dev->bid_devno == devno)
+ goto set_pri;
+
+ /* Try to translate private device-mapper dm-<N> names
+ * to standard /dev/mapper/<name>.
+ */
+ if (!strncmp(ptname, "dm-", 3) && isdigit(ptname[3])) {
+ devname = canonicalize_dm_name(ptname);
+ if (!devname)
+ blkid__scan_dir("/dev/mapper", devno, NULL, &devname);
+ if (devname)
+ goto get_dev;
+ }
+
+ /*
+ * Take a quick look at /dev/ptname for the device number. We check
+ * all of the likely device directories. If we don't find it, or if
+ * the stat information doesn't check out, use blkid_devno_to_devname()
+ * to find it via an exhaustive search for the device major/minor.
+ */
+ for (dir = dirlist; *dir; dir++) {
+ struct stat st;
+ char device[256];
+
+ snprintf(device, sizeof(device), "%s/%s", *dir, ptname);
+ if ((dev = blkid_get_dev(cache, device, BLKID_DEV_FIND)) &&
+ dev->bid_devno == devno)
+ goto set_pri;
+
+ if (stat(device, &st) == 0 &&
+ (S_ISBLK(st.st_mode) ||
+ (S_ISCHR(st.st_mode) && !strncmp(ptname, "ubi", 3))) &&
+ st.st_rdev == devno) {
+ devname = strdup(device);
+ goto get_dev;
+ }
+ }
+ /* Do a short-cut scan of /dev/mapper first */
+ if (!devname)
+ blkid__scan_dir("/dev/mapper", devno, NULL, &devname);
+ if (!devname) {
+ devname = blkid_devno_to_devname(devno);
+ if (!devname)
+ return;
+ }
+
+get_dev:
+ dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
+ free(devname);
+
+set_pri:
+ if (dev) {
+ if (pri)
+ dev->bid_pri = pri;
+ else if (!strncmp(dev->bid_name, "/dev/mapper/", 12)) {
+ dev->bid_pri = BLKID_PRI_DM;
+ if (is_dm_leaf(ptname))
+ dev->bid_pri += 5;
+ } else if (!strncmp(ptname, "md", 2))
+ dev->bid_pri = BLKID_PRI_MD;
+ if (removable)
+ dev->bid_flags |= BLKID_BID_FL_REMOVABLE;
+ }
+}
+
+#define PROC_PARTITIONS "/proc/partitions"
+#define VG_DIR "/proc/lvm/VGs"
+
+/*
+ * This function initializes the UUID cache with devices from the LVM
+ * proc hierarchy. We currently depend on the names of the LVM
+ * hierarchy giving us the device structure in /dev. (XXX is this a
+ * safe thing to do?)
+ */
+#ifdef VG_DIR
+static dev_t lvm_get_devno(const char *lvm_device)
+{
+ FILE *lvf;
+ char buf[1024];
+ int ma, mi;
+ dev_t ret = 0;
+
+ DBG(DEVNAME, ul_debug("opening %s", lvm_device));
+ if ((lvf = fopen(lvm_device, "r" UL_CLOEXECSTR)) == NULL) {
+ DBG(DEVNAME, ul_debug("%s: (%d) %m", lvm_device, errno));
+ return 0;
+ }
+
+ while (fgets(buf, sizeof(buf), lvf)) {
+ if (sscanf(buf, "device: %d:%d", &ma, &mi) == 2) {
+ ret = makedev(ma, mi);
+ break;
+ }
+ }
+ fclose(lvf);
+
+ return ret;
+}
+
+static void lvm_probe_all(blkid_cache cache, int only_if_new)
+{
+ DIR *vg_list;
+ struct dirent *vg_iter;
+ int vg_len = strlen(VG_DIR);
+ dev_t dev;
+
+ if ((vg_list = opendir(VG_DIR)) == NULL)
+ return;
+
+ DBG(DEVNAME, ul_debug("probing LVM devices under %s", VG_DIR));
+
+ while ((vg_iter = readdir(vg_list)) != NULL) {
+ DIR *lv_list;
+ char *vdirname;
+ char *vg_name;
+ struct dirent *lv_iter;
+ size_t len;
+
+ vg_name = vg_iter->d_name;
+ if (!strcmp(vg_name, ".") || !strcmp(vg_name, ".."))
+ continue;
+ len = vg_len + strlen(vg_name) + 8;
+ vdirname = malloc(len);
+ if (!vdirname)
+ goto exit;
+ snprintf(vdirname, len, "%s/%s/LVs", VG_DIR, vg_name);
+
+ lv_list = opendir(vdirname);
+ free(vdirname);
+ if (lv_list == NULL)
+ continue;
+
+ while ((lv_iter = readdir(lv_list)) != NULL) {
+ char *lv_name, *lvm_device;
+
+ lv_name = lv_iter->d_name;
+ if (!strcmp(lv_name, ".") || !strcmp(lv_name, ".."))
+ continue;
+
+ len = vg_len + strlen(vg_name) + strlen(lv_name) + 8;
+ lvm_device = malloc(len);
+ if (!lvm_device) {
+ closedir(lv_list);
+ goto exit;
+ }
+ snprintf(lvm_device, len, "%s/%s/LVs/%s", VG_DIR, vg_name,
+ lv_name);
+ dev = lvm_get_devno(lvm_device);
+ snprintf(lvm_device, len, "%s/%s", vg_name, lv_name);
+ DBG(DEVNAME, ul_debug("Probe LVM dev %s: devno 0x%04X",
+ lvm_device,
+ (unsigned int) dev));
+ probe_one(cache, lvm_device, dev, BLKID_PRI_LVM,
+ only_if_new, 0);
+ free(lvm_device);
+ }
+ closedir(lv_list);
+ }
+exit:
+ closedir(vg_list);
+}
+#endif
+
+static void
+ubi_probe_all(blkid_cache cache, int only_if_new)
+{
+ const char **dirname;
+
+ for (dirname = dirlist; *dirname; dirname++) {
+ DIR *dir;
+ struct dirent *iter;
+
+ DBG(DEVNAME, ul_debug("probing UBI volumes under %s",
+ *dirname));
+
+ dir = opendir(*dirname);
+ if (dir == NULL)
+ continue ;
+
+ while ((iter = readdir(dir)) != NULL) {
+ char *name;
+ struct stat st;
+ dev_t dev;
+
+ name = iter->d_name;
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (iter->d_type != DT_UNKNOWN &&
+ iter->d_type != DT_CHR && iter->d_type != DT_LNK)
+ continue;
+#endif
+ if (!strcmp(name, ".") || !strcmp(name, "..") ||
+ !strstr(name, "ubi"))
+ continue;
+ if (!strcmp(name, "ubi_ctrl"))
+ continue;
+ if (fstatat(dirfd(dir), name, &st, 0))
+ continue;
+
+ dev = st.st_rdev;
+
+ if (!S_ISCHR(st.st_mode) || !minor(dev))
+ continue;
+ DBG(DEVNAME, ul_debug("Probe UBI vol %s/%s: devno 0x%04X",
+ *dirname, name, (int) dev));
+ probe_one(cache, name, dev, BLKID_PRI_UBI, only_if_new, 0);
+ }
+ closedir(dir);
+ }
+}
+
+/*
+ * This function uses /sys to read all block devices in way compatible with
+ * /proc/partitions (like the original libblkid implementation)
+ */
+static int
+sysfs_probe_all(blkid_cache cache, int only_if_new, int only_removable)
+{
+ DIR *sysfs;
+ struct dirent *dev;
+
+ sysfs = opendir(_PATH_SYS_BLOCK);
+ if (!sysfs)
+ return -BLKID_ERR_SYSFS;
+
+ DBG(DEVNAME, ul_debug(" probe /sys/block"));
+
+ /* scan /sys/block */
+ while ((dev = xreaddir(sysfs))) {
+ DIR *dir = NULL;
+ dev_t devno;
+ size_t nparts = 0;
+ unsigned int maxparts = 0, removable = 0;
+ struct dirent *part;
+ struct path_cxt *pc = NULL;
+ uint64_t size = 0;
+
+ DBG(DEVNAME, ul_debug("checking %s", dev->d_name));
+
+ devno = sysfs_devname_to_devno(dev->d_name);
+ if (!devno)
+ goto next;
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc)
+ goto next;
+
+ if (ul_path_read_u64(pc, &size, "size") != 0)
+ size = 0;
+ if (ul_path_read_u32(pc, &removable, "removable") != 0)
+ removable = 0;
+
+ /* ignore empty devices */
+ if (!size)
+ goto next;
+
+ /* accept removable if only removable requested */
+ if (only_removable) {
+ if (!removable)
+ goto next;
+
+ /* emulate /proc/partitions
+ * -- ignore empty devices and non-partitionable removable devices */
+ } else {
+ if (ul_path_read_u32(pc, &maxparts, "ext_range") != 0)
+ maxparts = 0;
+ if (!maxparts && removable)
+ goto next;
+ }
+
+ DBG(DEVNAME, ul_debug("read device name %s", dev->d_name));
+
+ dir = ul_path_opendir(pc, NULL);
+ if (!dir)
+ goto next;
+
+ /* read /sys/block/<name>/ do get partitions */
+ while ((part = xreaddir(dir))) {
+ dev_t partno;
+
+ if (!sysfs_blkdev_is_partition_dirent(dir, part, dev->d_name))
+ continue;
+
+ /* ignore extended partitions
+ * -- recount size to blocks like /proc/partitions */
+ if (ul_path_readf_u64(pc, &size, "%s/size", part->d_name) == 0
+ && (size >> 1) == 1)
+ continue;
+ partno = __sysfs_devname_to_devno(NULL, part->d_name, dev->d_name);
+ if (!partno)
+ continue;
+
+ DBG(DEVNAME, ul_debug(" Probe partition dev %s, devno 0x%04X",
+ part->d_name, (unsigned int) partno));
+ nparts++;
+ probe_one(cache, part->d_name, partno, 0, only_if_new, 0);
+ }
+
+ if (!nparts) {
+ /* add non-partitioned whole disk to cache */
+ DBG(DEVNAME, ul_debug(" Probe whole dev %s, devno 0x%04X",
+ dev->d_name, (unsigned int) devno));
+ probe_one(cache, dev->d_name, devno, 0, only_if_new, 0);
+ } else {
+ /* remove partitioned whole-disk from cache */
+ struct list_head *p, *pnext;
+
+ list_for_each_safe(p, pnext, &cache->bic_devs) {
+ blkid_dev tmp = list_entry(p, struct blkid_struct_dev,
+ bid_devs);
+ if (tmp->bid_devno == devno) {
+ DBG(DEVNAME, ul_debug(" freeing %s", tmp->bid_name));
+ blkid_free_dev(tmp);
+ cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+ break;
+ }
+ }
+ }
+ next:
+ if (dir)
+ closedir(dir);
+ if (pc)
+ ul_unref_path(pc);
+ }
+
+ closedir(sysfs);
+ return 0;
+}
+
+/*
+ * Read the device data for all available block devices in the system.
+ */
+static int probe_all(blkid_cache cache, int only_if_new, int update_interval)
+{
+ int rc;
+
+ if (!cache)
+ return -BLKID_ERR_PARAM;
+
+ if (cache->bic_flags & BLKID_BIC_FL_PROBED &&
+ time(NULL) - cache->bic_time < BLKID_PROBE_INTERVAL) {
+ DBG(PROBE, ul_debug("don't re-probe [delay < %d]", BLKID_PROBE_INTERVAL));
+ return 0;
+ }
+
+ blkid_read_cache(cache);
+#ifdef VG_DIR
+ lvm_probe_all(cache, only_if_new);
+#endif
+ ubi_probe_all(cache, only_if_new);
+
+ rc = sysfs_probe_all(cache, only_if_new, 0);
+
+ /* Don't mark the change as "probed" if /sys not avalable */
+ if (update_interval && rc == 0) {
+ cache->bic_time = time(NULL);
+ cache->bic_flags |= BLKID_BIC_FL_PROBED;
+ }
+
+ blkid_flush_cache(cache);
+ return 0;
+}
+
+/**
+ * blkid_probe_all:
+ * @cache: cache handler
+ *
+ * Probes all block devices.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all(blkid_cache cache)
+{
+ int ret;
+
+ DBG(PROBE, ul_debug("Begin blkid_probe_all()"));
+ ret = probe_all(cache, 0, 1);
+ DBG(PROBE, ul_debug("End blkid_probe_all() [rc=%d]", ret));
+ return ret;
+}
+
+/**
+ * blkid_probe_all_new:
+ * @cache: cache handler
+ *
+ * Probes all new block devices.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all_new(blkid_cache cache)
+{
+ int ret;
+
+ DBG(PROBE, ul_debug("Begin blkid_probe_all_new()"));
+ ret = probe_all(cache, 1, 0);
+ DBG(PROBE, ul_debug("End blkid_probe_all_new() [rc=%d]", ret));
+ return ret;
+}
+
+/**
+ * blkid_probe_all_removable:
+ * @cache: cache handler
+ *
+ * The libblkid probing is based on devices from /proc/partitions by default.
+ * This file usually does not contain removable devices (e.g. CDROMs) and this kind
+ * of devices are invisible for libblkid.
+ *
+ * This function adds removable block devices to @cache (probing is based on
+ * information from the /sys directory). Don't forget that removable devices
+ * (floppies, CDROMs, ...) could be pretty slow. It's very bad idea to call
+ * this function by default.
+ *
+ * Note that devices which were detected by this function won't be written to
+ * blkid.tab cache file.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all_removable(blkid_cache cache)
+{
+ int ret;
+
+ DBG(PROBE, ul_debug("Begin blkid_probe_all_removable()"));
+ ret = sysfs_probe_all(cache, 0, 1);
+ DBG(PROBE, ul_debug("End blkid_probe_all_removable() [rc=%d]", ret));
+ return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+ blkid_cache cache = NULL;
+ int ret;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+ if (argc != 1) {
+ fprintf(stderr, "Usage: %s\n"
+ "Probe all devices and exit\n", argv[0]);
+ exit(1);
+ }
+ if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+ if (blkid_probe_all(cache) < 0)
+ printf("%s: error probing devices\n", argv[0]);
+
+ if (blkid_probe_all_removable(cache) < 0)
+ printf("%s: error probing removable devices\n", argv[0]);
+
+ blkid_put_cache(cache);
+ return (0);
+}
+#endif
diff --git a/libblkid/src/devno.c b/libblkid/src/devno.c
new file mode 100644
index 0000000..74a0d98
--- /dev/null
+++ b/libblkid/src/devno.c
@@ -0,0 +1,373 @@
+/*
+ * devno.c - find a particular device by its device number (major/minor)
+ *
+ * Copyright (C) 2000, 2001, 2003 Theodore Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <dirent.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include "blkidP.h"
+#include "pathnames.h"
+#include "sysfs.h"
+
+static char *blkid_strconcat(const char *a, const char *b, const char *c)
+{
+ char *res, *p;
+ size_t len, al, bl, cl;
+
+ al = a ? strlen(a) : 0;
+ bl = b ? strlen(b) : 0;
+ cl = c ? strlen(c) : 0;
+
+ len = al + bl + cl;
+ if (!len)
+ return NULL;
+ p = res = malloc(len + 1);
+ if (!res)
+ return NULL;
+ if (al) {
+ memcpy(p, a, al);
+ p += al;
+ }
+ if (bl) {
+ memcpy(p, b, bl);
+ p += bl;
+ }
+ if (cl) {
+ memcpy(p, c, cl);
+ p += cl;
+ }
+ *p = '\0';
+ return res;
+}
+
+/*
+ * This function adds an entry to the directory list
+ */
+static void add_to_dirlist(const char *dir, const char *subdir,
+ struct dir_list **list)
+{
+ struct dir_list *dp;
+
+ dp = malloc(sizeof(struct dir_list));
+ if (!dp)
+ return;
+ dp->name = subdir ? blkid_strconcat(dir, "/", subdir) :
+ dir ? strdup(dir) : NULL;
+
+ if (!dp->name) {
+ free(dp);
+ return;
+ }
+ dp->next = *list;
+ *list = dp;
+}
+
+/*
+ * This function frees a directory list
+ */
+static void free_dirlist(struct dir_list **list)
+{
+ struct dir_list *dp, *next;
+
+ for (dp = *list; dp; dp = next) {
+ next = dp->next;
+ free(dp->name);
+ free(dp);
+ }
+ *list = NULL;
+}
+
+void blkid__scan_dir(char *dirname, dev_t devno, struct dir_list **list,
+ char **devname)
+{
+ DIR *dir;
+ struct dirent *dp;
+ struct stat st;
+
+ if ((dir = opendir(dirname)) == NULL)
+ return;
+
+ while ((dp = readdir(dir)) != NULL) {
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_BLK &&
+ dp->d_type != DT_LNK && dp->d_type != DT_DIR)
+ continue;
+#endif
+ if (dp->d_name[0] == '.' &&
+ ((dp->d_name[1] == 0) ||
+ ((dp->d_name[1] == '.') && (dp->d_name[2] == 0))))
+ continue;
+
+ if (fstatat(dirfd(dir), dp->d_name, &st, 0))
+ continue;
+
+ if (S_ISBLK(st.st_mode) && st.st_rdev == devno) {
+ *devname = blkid_strconcat(dirname, "/", dp->d_name);
+ DBG(DEVNO, ul_debug("found 0x%llx at %s", (long long)devno,
+ *devname));
+ break;
+ }
+
+ if (!list || !S_ISDIR(st.st_mode))
+ continue;
+
+ /* add subdirectory (but not symlink) to the list */
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (dp->d_type == DT_LNK)
+ continue;
+ if (dp->d_type == DT_UNKNOWN)
+#endif
+ {
+ if (fstatat(dirfd(dir), dp->d_name, &st, AT_SYMLINK_NOFOLLOW) ||
+ !S_ISDIR(st.st_mode))
+ continue; /* symlink or fstatat() failed */
+ }
+
+ if (*dp->d_name == '.' || (
+#ifdef _DIRENT_HAVE_D_TYPE
+ dp->d_type == DT_DIR &&
+#endif
+ strcmp(dp->d_name, "shm") == 0))
+ /* ignore /dev/.{udev,mount,mdadm} and /dev/shm */
+ continue;
+
+ add_to_dirlist(dirname, dp->d_name, list);
+ }
+ closedir(dir);
+}
+
+/* Directories where we will try to search for device numbers */
+static const char *devdirs[] = { "/devices", "/devfs", "/dev", NULL };
+
+/**
+ * SECTION: misc
+ * @title: Miscellaneous utils
+ * @short_description: mix of various utils for low-level and high-level API
+ */
+
+
+
+static char *scandev_devno_to_devpath(dev_t devno)
+{
+ struct dir_list *list = NULL, *new_list = NULL;
+ char *devname = NULL;
+ const char **dir;
+
+ /*
+ * Add the starting directories to search in reverse order of
+ * importance, since we are using a stack...
+ */
+ for (dir = devdirs; *dir; dir++)
+ add_to_dirlist(*dir, NULL, &list);
+
+ while (list) {
+ struct dir_list *current = list;
+
+ list = list->next;
+ DBG(DEVNO, ul_debug("directory %s", current->name));
+ blkid__scan_dir(current->name, devno, &new_list, &devname);
+ free(current->name);
+ free(current);
+ if (devname)
+ break;
+ /*
+ * If we're done checking at this level, descend to
+ * the next level of subdirectories. (breadth-first)
+ */
+ if (list == NULL) {
+ list = new_list;
+ new_list = NULL;
+ }
+ }
+ free_dirlist(&list);
+ free_dirlist(&new_list);
+
+ return devname;
+}
+
+/**
+ * blkid_devno_to_devname:
+ * @devno: device number
+ *
+ * This function finds the pathname to a block device with a given
+ * device number.
+ *
+ * Returns: a pointer to allocated memory to the pathname on success,
+ * and NULL on failure.
+ */
+char *blkid_devno_to_devname(dev_t devno)
+{
+ char *path;
+ char buf[PATH_MAX];
+
+ path = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
+ if (path)
+ path = strdup(path);
+ if (!path)
+ path = scandev_devno_to_devpath(devno);
+
+ if (!path) {
+ DBG(DEVNO, ul_debug("blkid: couldn't find devno 0x%04lx",
+ (unsigned long) devno));
+ } else {
+ DBG(DEVNO, ul_debug("found devno 0x%04llx as %s", (long long)devno, path));
+ }
+
+ return path;
+}
+
+
+/**
+ * blkid_devno_to_wholedisk:
+ * @dev: device number
+ * @diskname: buffer to return diskname (or NULL)
+ * @len: diskname buffer size (or 0)
+ * @diskdevno: pointer to returns devno of entire disk (or NULL)
+ *
+ * This function uses sysfs to convert the @devno device number to the *name*
+ * of the whole disk. The function DOES NOT return full device name. The @dev
+ * argument could be partition or whole disk -- both is converted.
+ *
+ * For example: sda1, 0x0801 --> sda, 0x0800
+ *
+ * For conversion to the full disk *path* use blkid_devno_to_devname(), for
+ * example:
+ *
+ * <informalexample>
+ * <programlisting>
+ *
+ * dev_t dev = 0x0801, disk; // sda1 = 8:1
+ * char *diskpath, diskname[32];
+ *
+ * blkid_devno_to_wholedisk(dev, diskname, sizeof(diskname), &disk);
+ * diskpath = blkid_devno_to_devname(disk);
+ *
+ * // print "0x0801: sda, /dev/sda, 8:0
+ * printf("0x%x: %s, %s, %d:%d\n",
+ * dev, diskname, diskpath, major(disk), minor(disk));
+ *
+ * free(diskpath);
+ *
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: 0 on success or -1 in case of error.
+ */
+int blkid_devno_to_wholedisk(dev_t dev, char *diskname,
+ size_t len, dev_t *diskdevno)
+{
+ return sysfs_devno_to_wholedisk( dev, diskname, len, diskdevno);
+}
+
+/*
+ * Returns 1 if the @major number is associated with @drvname.
+ */
+int blkid_driver_has_major(const char *drvname, int drvmaj)
+{
+ FILE *f;
+ char buf[128];
+ int match = 0;
+
+ f = fopen(_PATH_PROC_DEVICES, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 0;
+
+ while (fgets(buf, sizeof(buf), f)) { /* skip to block dev section */
+ if (strncmp("Block devices:\n", buf, sizeof(buf)) == 0)
+ break;
+ }
+
+ while (fgets(buf, sizeof(buf), f)) {
+ int maj;
+ char name[64 + 1];
+
+ if (sscanf(buf, "%d %64[^\n ]", &maj, name) != 2)
+ continue;
+
+ if (maj == drvmaj && strcmp(name, drvname) == 0) {
+ match = 1;
+ break;
+ }
+ }
+
+ fclose(f);
+
+ DBG(DEVNO, ul_debug("major %d %s associated with '%s' driver",
+ drvmaj, match ? "is" : "is NOT", drvname));
+ return match;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char** argv)
+{
+ char *devname, *tmp;
+ char diskname[PATH_MAX];
+ int devmaj, devmin;
+ dev_t devno, disk_devno;
+ const char *errmsg = "Couldn't parse %s: %s\n";
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+ if ((argc != 2) && (argc != 3)) {
+ fprintf(stderr, "Usage:\t%s device_number\n\t%s major minor\n"
+ "Resolve a device number to a device name\n",
+ argv[0], argv[0]);
+ exit(1);
+ }
+ if (argc == 2) {
+ devno = strtoul(argv[1], &tmp, 0);
+ if (*tmp) {
+ fprintf(stderr, errmsg, "device number", argv[1]);
+ exit(1);
+ }
+ } else {
+ devmaj = strtoul(argv[1], &tmp, 0);
+ if (*tmp) {
+ fprintf(stderr, errmsg, "major number", argv[1]);
+ exit(1);
+ }
+ devmin = strtoul(argv[2], &tmp, 0);
+ if (*tmp) {
+ fprintf(stderr, errmsg, "minor number", argv[2]);
+ exit(1);
+ }
+ devno = makedev(devmaj, devmin);
+ }
+ printf("Looking for device 0x%04llx\n", (long long)devno);
+ devname = blkid_devno_to_devname(devno);
+ free(devname);
+
+ printf("Looking for whole-device for 0x%04llx\n", (long long)devno);
+ if (blkid_devno_to_wholedisk(devno, diskname,
+ sizeof(diskname), &disk_devno) == 0)
+ printf("found devno 0x%04llx as /dev/%s\n", (long long) disk_devno, diskname);
+
+ return 0;
+}
+#endif
diff --git a/libblkid/src/encode.c b/libblkid/src/encode.c
new file mode 100644
index 0000000..8213873
--- /dev/null
+++ b/libblkid/src/encode.c
@@ -0,0 +1,263 @@
+
+/*
+ * encode.c - string conversion routines (mostly for compatibility with
+ * udev/volume_id)
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "blkidP.h"
+#include "strutils.h"
+
+/**
+ * SECTION: encode
+ * @title: Encoding utils
+ * @short_description: encode strings to safe udev-compatible formats
+ *
+ */
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+ unsigned char c = (unsigned char)str[0];
+
+ if (c < 0x80)
+ return 1;
+ if ((c & 0xe0) == 0xc0)
+ return 2;
+ if ((c & 0xf0) == 0xe0)
+ return 3;
+ if ((c & 0xf8) == 0xf0)
+ return 4;
+ if ((c & 0xfc) == 0xf8)
+ return 5;
+ if ((c & 0xfe) == 0xfc)
+ return 6;
+ return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+ int unichar;
+ int len;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ switch (len) {
+ case 1:
+ return (int)str[0];
+ case 2:
+ unichar = str[0] & 0x1f;
+ break;
+ case 3:
+ unichar = (int)str[0] & 0x0f;
+ break;
+ case 4:
+ unichar = (int)str[0] & 0x07;
+ break;
+ case 5:
+ unichar = (int)str[0] & 0x03;
+ break;
+ case 6:
+ unichar = (int)str[0] & 0x01;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i = 1; i < len; i++) {
+ if (((int)str[i] & 0xc0) != 0x80)
+ return -1;
+ unichar <<= 6;
+ unichar |= (int)str[i] & 0x3f;
+ }
+
+ return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+ if (unichar < 0x80)
+ return 1;
+ if (unichar < 0x800)
+ return 2;
+ if (unichar < 0x10000)
+ return 3;
+ if (unichar < 0x200000)
+ return 4;
+ if (unichar < 0x4000000)
+ return 5;
+ return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+ if (unichar > 0x10ffff)
+ return 0;
+ if ((unichar & 0xfffff800) == 0xd800)
+ return 0;
+ if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+ return 0;
+ if ((unichar & 0xffff) == 0xffff)
+ return 0;
+ return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+ int len;
+ int unichar;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ if (len == 0)
+ return -1;
+
+ /* ascii is valid */
+ if (len == 1)
+ return 1;
+
+ /* check if expected encoded chars are available */
+ for (i = 0; i < len; i++)
+ if ((str[i] & 0x80) != 0x80)
+ return -1;
+
+ unichar = utf8_encoded_to_unichar(str);
+
+ /* check if encoded length matches encoded value */
+ if (utf8_unichar_to_encoded_len(unichar) != len)
+ return -1;
+
+ /* check if value has valid range */
+ if (!utf8_unichar_valid_range(unichar))
+ return -1;
+
+ return len;
+}
+
+static int is_whitelisted(char c, const char *white)
+{
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ strchr("#+-.:=@_", c) != NULL ||
+ (white != NULL && strchr(white, c) != NULL))
+ return 1;
+ return 0;
+}
+
+/**
+ * blkid_encode_string:
+ * @str: input string to be encoded
+ * @str_enc: output string to store the encoded input string
+ * @len: maximum size of the output string, which may be
+ * four times as long as the input string
+ *
+ * Encode all potentially unsafe characters of a string to the
+ * corresponding hex value prefixed by '\x'.
+ *
+ * Returns: 0 if the entire string was copied, non-zero otherwise.
+ **/
+int blkid_encode_string(const char *str, char *str_enc, size_t len)
+{
+ size_t i, j;
+
+ if (!str || !str_enc || !len)
+ return -1;
+
+ for (i = 0, j = 0; str[i] != '\0'; i++) {
+ int seqlen;
+
+ seqlen = utf8_encoded_valid_unichar(&str[i]);
+ if (seqlen > 1) {
+ if (len-j < (size_t)seqlen)
+ goto err;
+ memcpy(&str_enc[j], &str[i], seqlen);
+ j += seqlen;
+ i += (seqlen-1);
+ } else if (str[i] == '\\' || !is_whitelisted(str[i], NULL)) {
+ if (len-j < 4)
+ goto err;
+ sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]);
+ j += 4;
+ } else {
+ if (len-j < 1)
+ goto err;
+ str_enc[j] = str[i];
+ j++;
+ }
+ if (j+3 >= len)
+ goto err;
+ }
+ if (len-j < 1)
+ goto err;
+ str_enc[j] = '\0';
+ return 0;
+err:
+ return -1;
+}
+
+/**
+ * blkid_safe_string:
+ * @str: input string
+ * @str_safe: output string
+ * @len: size of output string
+ *
+ * Processing whitespace characters. Allows valid ascii,valid utf8.
+ * Replace everything else with'_'
+ *
+ * Returns: 0 on success or -1 in case of error.
+ */
+int blkid_safe_string(const char *str, char *str_safe, size_t len)
+{
+ size_t i = 0;
+
+ if (!str || !str_safe || !len)
+ return -1;
+
+ __normalize_whitespace(
+ (const unsigned char *) str, strnlen(str, len),
+ (unsigned char *) str_safe, len);
+
+ while (i < len && str_safe[i] != '\0') {
+ int seqsz;
+
+ /* accept ASCII from ' ' to '~' */
+ if (str_safe[i] > 0x20 && str_safe[i] <= 0x7E)
+ i++;
+
+ /* accept hex encoding */
+ else if (str_safe[i] == '\\' && str_safe[i+1] == 'x')
+ i += 2;
+
+ /* replace whitespace */
+ else if (isspace(str_safe[i]))
+ str_safe[i++] = '_';
+
+ /* accept valid utf8 */
+ else if ((seqsz = utf8_encoded_valid_unichar(&str_safe[i])) >= 1)
+ i += seqsz;
+
+ /* everything else is replaced with '_' */
+ else
+ str_safe[i++] = '_';
+ }
+
+ str_safe[len - 1] = '\0';
+ return 0;
+}
diff --git a/libblkid/src/evaluate.c b/libblkid/src/evaluate.c
new file mode 100644
index 0000000..8157a7c
--- /dev/null
+++ b/libblkid/src/evaluate.c
@@ -0,0 +1,330 @@
+/*
+ * evaluate.c - very high-level API to evaluate LABELs or UUIDs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "pathnames.h"
+#include "canonicalize.h"
+#include "closestream.h"
+
+#include "blkidP.h"
+
+/**
+ * SECTION:evaluate
+ * @title: Tags and Spec evaluation
+ * @short_description: top-level API for LABEL and UUID evaluation.
+ *
+ * This API provides very simple and portable way how evaluate LABEL and UUID
+ * tags. The blkid_evaluate_tag() and blkid_evaluate_spec() work on 2.4 and
+ * 2.6 systems and on systems with or without udev. Currently, the libblkid
+ * library supports "udev" and "scan" methods. The "udev" method uses udev
+ * /dev/disk/by-* symlinks and the "scan" method scans all block devices from
+ * the /proc/partitions file. The evaluation could be controlled by the
+ * /etc/blkid.conf config file. The default is to try "udev" and then "scan"
+ * method.
+ *
+ * The blkid_evaluate_tag() also automatically informs udevd when an obsolete
+ * /dev/disk/by-* symlink is detected.
+ *
+ * If you are not sure how translate LABEL or UUID to the device name use this
+ * API.
+ */
+
+#ifdef CONFIG_BLKID_VERIFY_UDEV
+/* returns zero when the device has NAME=value (LABEL/UUID) */
+static int verify_tag(const char *devname, const char *name, const char *value)
+{
+ blkid_probe pr;
+ int fd = -1, rc = -1;
+ size_t len;
+ const char *data;
+ int errsv = 0;
+
+ if (strcmp(token, "ID") == 0)
+ return 0; /* non-content tag */
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return -1;
+
+ blkid_probe_enable_superblocks(pr, TRUE);
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID);
+
+ blkid_probe_enable_partitions(pr, TRUE);
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+
+ fd = open(devname, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ errsv = errno;
+ goto done;
+ }
+ if (blkid_probe_set_device(pr, fd, 0, 0))
+ goto done;
+ rc = blkid_do_safeprobe(pr);
+ if (rc)
+ goto done;
+ rc = blkid_probe_lookup_value(pr, name, &data, &len);
+ if (!rc)
+ rc = memcmp(value, data, len);
+done:
+ DBG(EVALUATE, ul_debug("%s: %s verification %s",
+ devname, name, rc == 0 ? "PASS" : "FAILED"));
+ if (fd >= 0)
+ close(fd);
+ blkid_free_probe(pr);
+
+ /* for non-root users we use unverified udev links */
+ return errsv == EACCES ? 0 : rc;
+}
+#endif /* CONFIG_BLKID_VERIFY_UDEV*/
+
+/**
+ * blkid_send_uevent:
+ * @devname: absolute path to the device
+ * @action: event string
+ *
+ * Returns: -1 in case of failure, or 0 on success.
+ */
+int blkid_send_uevent(const char *devname, const char *action)
+{
+ char uevent[PATH_MAX];
+ struct stat st;
+ FILE *f;
+ int rc = -1;
+
+ DBG(EVALUATE, ul_debug("%s: uevent '%s' requested", devname, action));
+
+ if (!devname || !action)
+ return -1;
+ if (stat(devname, &st) || !S_ISBLK(st.st_mode))
+ return -1;
+
+ snprintf(uevent, sizeof(uevent), "/sys/dev/block/%d:%d/uevent",
+ major(st.st_rdev), minor(st.st_rdev));
+
+ f = fopen(uevent, "w" UL_CLOEXECSTR);
+ if (f) {
+ rc = 0;
+ if (fputs(action, f) >= 0)
+ rc = 0;
+ if (close_stream(f) != 0)
+ DBG(EVALUATE, ul_debug("write failed: %s", uevent));
+ }
+ DBG(EVALUATE, ul_debug("%s: send uevent %s",
+ uevent, rc == 0 ? "SUCCESS" : "FAILED"));
+ return rc;
+}
+
+static char *evaluate_by_udev(const char *token, const char *value, int uevent)
+{
+ char dev[PATH_MAX];
+ char *path = NULL;
+ size_t len;
+ struct stat st;
+
+ DBG(EVALUATE, ul_debug("evaluating by udev %s=%s", token, value));
+
+ if (!strcmp(token, "UUID"))
+ strcpy(dev, _PATH_DEV_BYUUID "/");
+ else if (!strcmp(token, "LABEL"))
+ strcpy(dev, _PATH_DEV_BYLABEL "/");
+ else if (!strcmp(token, "PARTLABEL"))
+ strcpy(dev, _PATH_DEV_BYPARTLABEL "/");
+ else if (!strcmp(token, "PARTUUID"))
+ strcpy(dev, _PATH_DEV_BYPARTUUID "/");
+ else if (!strcmp(token, "ID"))
+ strcpy(dev, _PATH_DEV_BYID "/");
+ else {
+ DBG(EVALUATE, ul_debug("unsupported token %s", token));
+ return NULL; /* unsupported tag */
+ }
+
+ len = strlen(dev);
+ if (blkid_encode_string(value, &dev[len], sizeof(dev) - len) != 0)
+ return NULL;
+
+ DBG(EVALUATE, ul_debug("expected udev link: %s", dev));
+
+ if (stat(dev, &st))
+ goto failed; /* link or device does not exist */
+
+ if (!S_ISBLK(st.st_mode))
+ return NULL;
+
+ path = canonicalize_path(dev);
+ if (!path)
+ return NULL;
+
+#ifdef CONFIG_BLKID_VERIFY_UDEV
+ if (verify_tag(path, token, value))
+ goto failed;
+#endif
+ return path;
+
+failed:
+ DBG(EVALUATE, ul_debug("failed to evaluate by udev"));
+
+ if (uevent && path)
+ blkid_send_uevent(path, "change");
+ free(path);
+ return NULL;
+}
+
+static char *evaluate_by_scan(const char *token, const char *value,
+ blkid_cache *cache, struct blkid_config *conf)
+{
+ blkid_cache c = cache ? *cache : NULL;
+ char *res;
+
+ DBG(EVALUATE, ul_debug("evaluating by blkid scan %s=%s", token, value));
+
+ if (!c) {
+ char *cachefile = blkid_get_cache_filename(conf);
+ int rc = blkid_get_cache(&c, cachefile);
+ free(cachefile);
+ if (rc < 0)
+ return NULL;
+ }
+ if (!c)
+ return NULL;
+
+ res = blkid_get_devname(c, token, value);
+
+ if (cache)
+ *cache = c;
+ else
+ blkid_put_cache(c);
+
+ return res;
+}
+
+/**
+ * blkid_evaluate_tag:
+ * @token: token name (e.g "LABEL" or "UUID") or unparsed tag (e.g. "LABEL=foo")
+ * @value: token data (e.g. "foo")
+ * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
+ *
+ * Returns: allocated string with a device name.
+ */
+char *blkid_evaluate_tag(const char *token, const char *value, blkid_cache *cache)
+{
+ struct blkid_config *conf = NULL;
+ char *t = NULL, *v = NULL;
+ char *ret = NULL;
+ int i;
+
+ if (!token)
+ return NULL;
+
+ if (!cache || !*cache)
+ blkid_init_debug(0);
+
+ DBG(EVALUATE, ul_debug("evaluating %s%s%s", token, value ? "=" : "",
+ value ? value : ""));
+
+ if (!value) {
+ if (!strchr(token, '=')) {
+ ret = strdup(token);
+ goto out;
+ }
+ if (blkid_parse_tag_string(token, &t, &v) != 0 || !t || !v)
+ goto out;
+ token = t;
+ value = v;
+ }
+
+ conf = blkid_read_config(NULL);
+ if (!conf)
+ goto out;
+
+ for (i = 0; i < conf->nevals; i++) {
+ if (conf->eval[i] == BLKID_EVAL_UDEV)
+ ret = evaluate_by_udev(token, value, conf->uevent);
+ else if (conf->eval[i] == BLKID_EVAL_SCAN)
+ ret = evaluate_by_scan(token, value, cache, conf);
+ if (ret)
+ break;
+ }
+
+ DBG(EVALUATE, ul_debug("%s=%s evaluated as %s", token, value, ret));
+out:
+ blkid_free_config(conf);
+ free(t);
+ free(v);
+ return ret;
+}
+
+/**
+ * blkid_evaluate_spec:
+ * @spec: unparsed tag (e.g. "LABEL=foo") or path (e.g. /dev/dm-0)
+ * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
+ *
+ * All returned paths are canonicalized, device-mapper paths are converted
+ * to the /dev/mapper/name format.
+ *
+ * Returns: allocated string with a device name.
+ */
+char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
+{
+ char *t = NULL, *v = NULL, *res;
+
+ if (!spec)
+ return NULL;
+
+ if (strchr(spec, '=') &&
+ blkid_parse_tag_string(spec, &t, &v) != 0) /* parse error */
+ return NULL;
+
+ if (v)
+ res = blkid_evaluate_tag(t, v, cache);
+ else
+ res = canonicalize_path(spec);
+
+ free(t);
+ free(v);
+ return res;
+}
+
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ blkid_cache cache = NULL;
+ char *res;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <tag> | <spec>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ blkid_init_debug(0);
+
+ res = blkid_evaluate_spec(argv[1], &cache);
+ if (res)
+ printf("%s\n", res);
+ if (cache)
+ blkid_put_cache(cache);
+
+ return res ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+#endif
diff --git a/libblkid/src/getsize.c b/libblkid/src/getsize.c
new file mode 100644
index 0000000..abe6ebc
--- /dev/null
+++ b/libblkid/src/getsize.c
@@ -0,0 +1,34 @@
+/*
+ * getsize.c --- get the size of a partition.
+ *
+ * Copyright (C) 1995, 1995 Theodore Ts'o.
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "blkidP.h"
+
+/**
+ * blkid_get_dev_size:
+ * @fd: file descriptor
+ *
+ * Returns: size (in bytes) of the block device or size of the regular file or 0.
+ */
+blkid_loff_t blkid_get_dev_size(int fd)
+{
+ unsigned long long bytes;
+
+ if (blkdev_get_size(fd, &bytes))
+ return 0;
+
+ return bytes;
+}
+
diff --git a/libblkid/src/init.c b/libblkid/src/init.c
new file mode 100644
index 0000000..6dc9ffd
--- /dev/null
+++ b/libblkid/src/init.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008-2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debugging
+ */
+
+#include <stdarg.h>
+
+#include "blkidP.h"
+
+UL_DEBUG_DEFINE_MASK(libblkid);
+UL_DEBUG_DEFINE_MASKNAMES(libblkid) =
+{
+ { "all", BLKID_DEBUG_ALL, "info about all subsystems" },
+ { "cache", BLKID_DEBUG_CACHE, "blkid tags cache" },
+ { "config", BLKID_DEBUG_CONFIG, "config file utils" },
+ { "dev", BLKID_DEBUG_DEV, "device utils" },
+ { "devname", BLKID_DEBUG_DEVNAME, "/proc/partitions evaluation" },
+ { "devno", BLKID_DEBUG_DEVNO, "conversions to device name" },
+ { "evaluate", BLKID_DEBUG_EVALUATE, "tags resolving" },
+ { "help", BLKID_DEBUG_HELP, "this help" },
+ { "lowprobe", BLKID_DEBUG_LOWPROBE, "superblock/raids/partitions probing" },
+ { "buffer", BLKID_DEBUG_BUFFER, "low-probing buffers" },
+ { "probe", BLKID_DEBUG_PROBE, "devices verification" },
+ { "read", BLKID_DEBUG_READ, "cache parsing" },
+ { "save", BLKID_DEBUG_SAVE, "cache writing" },
+ { "tag", BLKID_DEBUG_TAG, "tags utils" },
+ { NULL, 0, NULL }
+};
+
+/**
+ * blkid_init_debug:
+ * @mask: debug mask (0xffff to enable full debugging)
+ *
+ * If the @mask is not specified then this function reads
+ * LIBBLKID_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. It does not
+ * have effect to call this function twice.
+ */
+void blkid_init_debug(int mask)
+{
+ if (libblkid_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(libblkid, BLKID_DEBUG_, mask, LIBBLKID_DEBUG);
+
+ if (libblkid_debug_mask != BLKID_DEBUG_INIT
+ && libblkid_debug_mask != (BLKID_DEBUG_HELP|BLKID_DEBUG_INIT)) {
+ const char *ver = NULL;
+ const char *date = NULL;
+
+ blkid_get_library_version(&ver, &date);
+ DBG(INIT, ul_debug("library debug mask: 0x%04x", libblkid_debug_mask));
+ DBG(INIT, ul_debug("library version: %s [%s]", ver, date));
+
+ }
+ ON_DBG(HELP, ul_debug_print_masks("LIBBLKID_DEBUG",
+ UL_DEBUG_MASKNAMES(libblkid)));
+}
diff --git a/libblkid/src/libblkid.sym b/libblkid/src/libblkid.sym
new file mode 100644
index 0000000..366f2c0
--- /dev/null
+++ b/libblkid/src/libblkid.sym
@@ -0,0 +1,185 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol 'foo'
+ * can't run with old library.so not providing 'foo' - the global SONAME
+ * version info can't enforce this since we never change the SONAME.
+ *
+ * The original libblkid from e2fsprogs (<=1.41.4) does not to use
+ * symbol versioning -- all the original symbols are in BLKID_1.0 now.
+ *
+ * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com>
+ */
+BLKID_1.0 {
+global:
+ blkid_dev_devname;
+ blkid_dev_has_tag;
+ blkid_dev_iterate_begin;
+ blkid_dev_iterate_end;
+ blkid_dev_next;
+ blkid_devno_to_devname;
+ blkid_dev_set_search;
+ blkid_find_dev_with_tag;
+ blkid_gc_cache;
+ blkid_get_cache;
+ blkid_get_dev;
+ blkid_get_devname;
+ blkid_get_dev_size;
+ blkid_get_library_version;
+ blkid_get_tag_value;
+ blkid_known_fstype;
+ blkid_parse_tag_string;
+ blkid_parse_version_string;
+ blkid_probe_all;
+ blkid_probe_all_new;
+ blkid_put_cache;
+ blkid_tag_iterate_begin;
+ blkid_tag_iterate_end;
+ blkid_tag_next;
+ blkid_verify;
+local:
+ *;
+};
+
+
+/*
+ * symbols since util-linux 2.15
+ */
+BLKID_2.15 {
+global:
+ blkid_do_probe;
+ blkid_do_safeprobe;
+ blkid_encode_string;
+ blkid_evaluate_tag;
+ blkid_free_probe;
+ blkid_new_probe;
+ blkid_probe_filter_types;
+ blkid_probe_filter_usage;
+ blkid_probe_get_value;
+ blkid_probe_has_value;
+ blkid_probe_invert_filter;
+ blkid_probe_lookup_value;
+ blkid_probe_numof_values;
+ blkid_probe_reset_filter;
+ blkid_probe_set_device;
+ blkid_probe_set_request;
+ blkid_reset_probe;
+ blkid_safe_string;
+ blkid_send_uevent;
+} BLKID_1.0;
+
+/*
+ * symbols since util-linux 2.17
+ */
+BLKID_2.17 {
+global:
+ blkid_devno_to_wholedisk;
+ blkid_do_fullprobe;
+ blkid_known_pttype;
+ blkid_new_probe_from_filename;
+ blkid_partition_get_name;
+ blkid_partition_get_partno;
+ blkid_partition_get_size;
+ blkid_partition_get_start;
+ blkid_partition_get_table;
+ blkid_partition_get_type;
+ blkid_partition_get_type_string;
+ blkid_partition_get_uuid;
+ blkid_partition_is_extended;
+ blkid_partition_is_logical;
+ blkid_partition_is_primary;
+ blkid_partlist_get_partition;
+ blkid_partlist_numof_partitions;
+ blkid_parttable_get_offset;
+ blkid_parttable_get_parent;
+ blkid_parttable_get_type;
+ blkid_probe_enable_partitions;
+ blkid_probe_enable_superblocks;
+ blkid_probe_enable_topology;
+ blkid_probe_filter_partitions_type;
+ blkid_probe_filter_superblocks_type;
+ blkid_probe_filter_superblocks_usage;
+ blkid_probe_get_devno;
+ blkid_probe_get_partitions;
+ blkid_probe_get_sectorsize;
+ blkid_probe_get_sectors;
+ blkid_probe_get_size;
+ blkid_probe_get_topology;
+ blkid_probe_invert_partitions_filter;
+ blkid_probe_invert_superblocks_filter;
+ blkid_probe_reset_partitions_filter;
+ blkid_probe_reset_superblocks_filter;
+ blkid_probe_set_partitions_flags;
+ blkid_probe_set_superblocks_flags;
+ blkid_topology_get_alignment_offset;
+ blkid_topology_get_logical_sector_size;
+ blkid_topology_get_minimum_io_size;
+ blkid_topology_get_optimal_io_size;
+ blkid_topology_get_physical_sector_size;
+} BLKID_2.15;
+
+/*
+ * symbols since util-linux 2.18
+ */
+BLKID_2.18 {
+global:
+ blkid_partition_get_flags;
+ blkid_partlist_devno_to_partition;
+ blkid_partlist_get_table;
+ blkid_probe_all_removable;
+ blkid_probe_get_fd;
+ blkid_probe_get_offset;
+ blkid_probe_get_wholedisk_devno;
+ blkid_probe_is_wholedisk;
+} BLKID_2.17;
+
+/*
+ * symbols since util-linux 2.20
+ */
+BLKID_2.20 {
+global:
+ blkid_evaluate_spec;
+ blkid_superblocks_get_name;
+} BLKID_2.18;
+
+/*
+ * symbols since util-linux 2.21
+ */
+BLKID_2.21 {
+global:
+ blkid_do_wipe;
+} BLKID_2.20;
+
+/*
+ * symbols since util-linux 2.23
+ */
+BLKID_2.23 {
+global:
+ blkid_probe_step_back;
+ blkid_parttable_get_id;
+ blkid_init_debug;
+} BLKID_2.21;
+
+/*
+ * symbols since util-linux 2.25
+ */
+BLKID_2.25 {
+ blkid_partlist_get_partition_by_partno;
+} BLKID_2.23;
+
+BLKID_2.30 {
+ blkid_probe_set_sectorsize;
+ blkid_partitions_get_name;
+} BLKID_2.25;
+
+BLKID_2_31 {
+ blkid_probe_reset_buffers;
+ blkid_probe_hide_range;
+} BLKID_2.30;
+
+BLKID_2_36 {
+ blkid_topology_get_dax;
+} BLKID_2_31;
+
+BLKID_2_37 {
+ blkid_probe_set_hint;
+ blkid_probe_reset_hints;
+} BLKID_2_36;
diff --git a/libblkid/src/partitions/aix.c b/libblkid/src/partitions/aix.c
new file mode 100644
index 0000000..03a311a
--- /dev/null
+++ b/libblkid/src/partitions/aix.c
@@ -0,0 +1,57 @@
+/*
+ * aix partitions
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "aix.h"
+
+static int probe_aix_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ blkid_partlist ls;
+ blkid_parttable tab;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ return BLKID_PROBE_NONE;
+
+ tab = blkid_partlist_new_parttable(ls, "aix", 0);
+ if (!tab)
+ return -ENOMEM;
+
+ return BLKID_PROBE_OK;
+}
+
+/*
+ * We know nothing about AIX on-disk structures. Everything what we know is the
+ * magic number at begin of the disk.
+ *
+ * Note, Linux kernel is trying to be smart and AIX signature is ignored when
+ * there is a valid DOS partitions table. We don't support such behavior. All
+ * fdisk-like programs has to properly wipe the fist sector. Everything other
+ * is a bug.
+ */
+const struct blkid_idinfo aix_pt_idinfo =
+{
+ .name = "aix",
+ .probefunc = probe_aix_pt,
+ .magics =
+ {
+ { .magic = BLKID_AIX_MAGIC_STRING, .len = BLKID_AIX_MAGIC_STRLEN },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/aix.h b/libblkid/src/partitions/aix.h
new file mode 100644
index 0000000..f767c5a
--- /dev/null
+++ b/libblkid/src/partitions/aix.h
@@ -0,0 +1,7 @@
+#ifndef BLKID_PARTITIONS_AIX_H
+#define BLKID_PARTITIONS_AIX_H
+
+#define BLKID_AIX_MAGIC_STRING "\xC9\xC2\xD4\xC1"
+#define BLKID_AIX_MAGIC_STRLEN (sizeof(BLKID_AIX_MAGIC_STRING) - 1)
+
+#endif
diff --git a/libblkid/src/partitions/atari.c b/libblkid/src/partitions/atari.c
new file mode 100644
index 0000000..314f047
--- /dev/null
+++ b/libblkid/src/partitions/atari.c
@@ -0,0 +1,312 @@
+/*
+ * atari partitions parsing code
+ *
+ * Copyright (C) 2018 Vaclav Dolezal <vdolezal@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Based on Linux kernel implementation and atari-fdisk
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+struct atari_part_def {
+ /*
+ * flags:
+ * 0 (LSB): active
+ * 1-6: (reserved)
+ * 7 (MSB): bootable
+ */
+ unsigned char flags;
+ char id[3];
+ uint32_t start;
+ uint32_t size;
+} __attribute__((packed));
+
+struct atari_rootsector {
+ char unused0[0x156]; /* boot code */
+ struct atari_part_def icd_part[8]; /* ICD partition entries */
+ char unused1[0xc];
+ uint32_t hd_size;
+ struct atari_part_def part[4]; /* primary partition entries */
+ uint32_t bsl_start; /* bad sector list start */
+ uint32_t bsl_len; /* bad sector list length */
+ uint16_t checksum;
+} __attribute__((packed));
+
+
+/*
+ * Generated using linux kernel ctype.{c,h}
+ *
+ * Since kernel uses isalnum() to detect whether it is Atari PT, we need same
+ * definition of alnum character to be consistent with kernel.
+ */
+static const unsigned char _linux_isalnum[] = {
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
+0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
+0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1
+};
+
+static int linux_isalnum(unsigned char c) {
+ return _linux_isalnum[c];
+}
+
+#define isalnum linux_isalnum
+
+#define IS_ACTIVE(partdef) ((partdef).flags & 1)
+
+static int is_valid_dimension(uint32_t start, uint32_t size, uint32_t maxoff)
+{
+ uint64_t end = start + size;
+
+ return end >= start
+ && 0 < start && start <= maxoff
+ && 0 < size && size <= maxoff
+ && 0 < end && end <= maxoff;
+}
+
+static int is_valid_partition(struct atari_part_def *part, uint32_t maxoff)
+{
+ uint32_t start = be32_to_cpu(part->start),
+ size = be32_to_cpu(part->size);
+
+ return (part->flags & 1)
+ && isalnum(part->id[0])
+ && isalnum(part->id[1])
+ && isalnum(part->id[2])
+ && is_valid_dimension(start, size, maxoff);
+}
+
+static int is_id_common(char *id)
+{
+ const char *ids[] = {"GEM", "BGM", "LNX", "SWP", "RAW", };
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(ids); i++) {
+ if (!memcmp(ids[i], id, 3))
+ return 1;
+ }
+ return 0;
+}
+
+static int parse_partition(blkid_partlist ls, blkid_parttable tab,
+ struct atari_part_def *part, uint32_t offset)
+{
+ blkid_partition par;
+ uint32_t start;
+ uint32_t size;
+
+ start = be32_to_cpu(part->start) + offset;
+ size = be32_to_cpu(part->size);
+
+ if (blkid_partlist_get_partition_by_start(ls, start)) {
+ /* Don't increment partno for extended parts */
+ if (!offset)
+ blkid_partlist_increment_partno(ls);
+ return 0;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ return -ENOMEM;
+
+ blkid_partition_set_type_string(par, (unsigned char *) part->id,
+ sizeof(part->id));
+ return 1;
+}
+
+/*
+ * \return 1: OK, 0: bad format or -errno
+ */
+static int parse_extended(blkid_probe pr, blkid_partlist ls,
+ blkid_parttable tab, struct atari_part_def *part)
+{
+ uint32_t x0start, xstart;
+ unsigned ct = 0, i = 0;
+ int rc;
+
+ x0start = xstart = be32_to_cpu(part->start);
+ while (1) {
+ struct atari_rootsector *xrs;
+
+ if (++ct > 100)
+ break;
+
+ xrs = (struct atari_rootsector *) blkid_probe_get_sector(pr, xstart);
+ if (!xrs) {
+ if (errno)
+ return -errno;
+ return 0;
+ }
+
+ /*
+ * There must be data partition followed by reference to next
+ * XGM or inactive entry.
+ */
+ for (i=0; ; i++) {
+ if (i >= ARRAY_SIZE(xrs->part) - 1)
+ return 0;
+ if (IS_ACTIVE(xrs->part[i]))
+ break;
+ }
+
+ if (!memcmp(xrs->part[i].id, "XGM", 3))
+ return 0;
+
+ rc = parse_partition(ls, tab, &xrs->part[i], xstart);
+ if (rc <= 0)
+ return rc;
+
+ if (!IS_ACTIVE(xrs->part[i+1]))
+ break;
+
+ if (memcmp(xrs->part[i+1].id, "XGM", 3) != 0)
+ return 0;
+
+ xstart = x0start + be32_to_cpu(xrs->part[i+1].start);
+ }
+
+ return 1;
+}
+
+static int probe_atari_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct atari_rootsector *rs;
+
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+
+ unsigned i;
+ int has_xgm = 0;
+ int rc = 0;
+ uint32_t rssize; /* size in sectors from root sector */
+ uint64_t size; /* size in sectors from system */
+
+ /* Atari partition is not defined for other sector sizes */
+ if (blkid_probe_get_sectorsize(pr) != 512)
+ goto nothing;
+
+ size = blkid_probe_get_size(pr) / 512;
+
+ /* Atari is not well defined to support large disks */
+ if (size > INT32_MAX)
+ goto nothing;
+
+ /* read root sector */
+ rs = (struct atari_rootsector *) blkid_probe_get_sector(pr, 0);
+ if (!rs) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ rssize = be32_to_cpu(rs->hd_size);
+
+ /* check number of sectors stored in the root sector */
+ if (rssize < 2 || rssize > size)
+ goto nothing;
+
+ /* check list of bad blocks */
+ if ((rs->bsl_start || rs->bsl_len)
+ && !is_valid_dimension(be32_to_cpu(rs->bsl_start),
+ be32_to_cpu(rs->bsl_len),
+ rssize))
+ goto nothing;
+
+ /*
+ * At least one valid partition required
+ */
+ for (i = 0; i < 4; i++) {
+ if (is_valid_partition(&rs->part[i], rssize)) {
+ if (blkid_probe_set_magic(pr,
+ offsetof(struct atari_rootsector, part[i]),
+ sizeof(rs->part[i].flags) + sizeof(rs->part[i].id),
+ (unsigned char *) &rs->part[i]))
+ goto err;
+ break;
+ }
+ }
+
+ if (i == 4)
+ goto nothing;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "atari", 0);
+ if (!tab)
+ goto err;
+
+ for (i = 0; i < ARRAY_SIZE(rs->part); i++) {
+ struct atari_part_def *p = &rs->part[i];
+
+ if (!IS_ACTIVE(*p)) {
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+ if (!memcmp(p->id, "XGM", 3)) {
+ has_xgm = 1;
+ rc = parse_extended(pr, ls, tab, p);
+ } else {
+ rc = parse_partition(ls, tab, p, 0);
+ }
+ if (rc < 0)
+ return rc;
+ }
+
+ /* if there are no XGM partitions, we can try ICD format */
+ /* if first ICD partition ID is not valid, assume no ICD format */
+ if (!has_xgm && is_id_common(rs->icd_part[0].id)) {
+ for (i = 0; i < ARRAY_SIZE(rs->icd_part); i++) {
+ struct atari_part_def *p = &rs->icd_part[i];
+
+ if (!IS_ACTIVE(*p) || !is_id_common(p->id)) {
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+
+ rc = parse_partition(ls, tab, p, 0);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+const struct blkid_idinfo atari_pt_idinfo =
+{
+ .name = "atari",
+ .probefunc = probe_atari_pt,
+ .magics = BLKID_NONE_MAGIC
+};
diff --git a/libblkid/src/partitions/bsd.c b/libblkid/src/partitions/bsd.c
new file mode 100644
index 0000000..7a0b231
--- /dev/null
+++ b/libblkid/src/partitions/bsd.c
@@ -0,0 +1,188 @@
+/*
+ * BSD/OSF partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Inspired by fdisk, partx, Linux kernel, libparted and openbsd header files.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "pt-bsd.h"
+
+/* Returns 'blkid_idmag' in 512-sectors */
+#define BLKID_MAG_SECTOR(_mag) (((_mag)->kboff / 2) + ((_mag)->sboff >> 9))
+
+/* Returns 'blkid_idmag' in bytes */
+#define BLKID_MAG_OFFSET(_mag) ((_mag)->kboff << 10) + ((_mag)->sboff)
+
+/* Returns 'blkid_idmag' offset in bytes within the last sector */
+#define BLKID_MAG_LASTOFFSET(_mag) \
+ (BLKID_MAG_OFFSET(_mag) - (BLKID_MAG_SECTOR(_mag) << 9))
+
+static int probe_bsd_pt(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct bsd_disklabel *l;
+ struct bsd_partition *p;
+ const char *name = "bsd" ;
+ blkid_parttable tab = NULL;
+ blkid_partition parent;
+ blkid_partlist ls;
+ int i, nparts = BSD_MAXPARTITIONS;
+ unsigned char *data;
+ int rc = BLKID_PROBE_NONE;
+ uint32_t abs_offset = 0;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return rc;
+
+ data = blkid_probe_get_sector(pr, BLKID_MAG_SECTOR(mag));
+ if (!data) {
+ if (errno)
+ rc = -errno;
+ goto nothing;
+ }
+
+ l = (struct bsd_disklabel *) (data + BLKID_MAG_LASTOFFSET(mag));
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ /* try to determine the real type of BSD system according to
+ * (parental) primary partition */
+ parent = blkid_partlist_get_parent(ls);
+ if (parent) {
+ switch(blkid_partition_get_type(parent)) {
+ case MBR_FREEBSD_PARTITION:
+ name = "freebsd";
+ abs_offset = blkid_partition_get_start(parent);
+ break;
+ case MBR_NETBSD_PARTITION:
+ name = "netbsd";
+ break;
+ case MBR_OPENBSD_PARTITION:
+ name = "openbsd";
+ break;
+ default:
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: BSD label detected on unknown (0x%x) "
+ "primary partition",
+ blkid_partition_get_type(parent)));
+ break;
+ }
+ }
+
+ tab = blkid_partlist_new_parttable(ls, name, BLKID_MAG_OFFSET(mag));
+ if (!tab) {
+ rc = -ENOMEM;
+ goto nothing;
+ }
+
+ if (le16_to_cpu(l->d_npartitions) < BSD_MAXPARTITIONS)
+ nparts = le16_to_cpu(l->d_npartitions);
+
+ else if (le16_to_cpu(l->d_npartitions) > BSD_MAXPARTITIONS)
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: ignore %d more BSD partitions",
+ le16_to_cpu(l->d_npartitions) - BSD_MAXPARTITIONS));
+
+ for (i = 0, p = l->d_partitions; i < nparts; i++, p++) {
+ blkid_partition par;
+ uint32_t start, size;
+
+ if (p->p_fstype == BSD_FS_UNUSED)
+ continue;
+
+ start = le32_to_cpu(p->p_offset);
+ size = le32_to_cpu(p->p_size);
+
+ /* FreeBSD since version 10 uses relative offsets. We can use
+ * 3rd partition (special wholedisk partition) to detect this
+ * situation.
+ */
+ if (abs_offset && nparts >= 3
+ && le32_to_cpu(l->d_partitions[2].p_offset) == 0)
+ start += abs_offset;
+
+ if (parent && blkid_partition_get_start(parent) == start
+ && blkid_partition_get_size(parent) == size) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: BSD partition (%d) same like parent, "
+ "ignore", i));
+ continue;
+ }
+ if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: BSD partition (%d) overflow "
+ "detected, ignore", i));
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par) {
+ rc = -ENOMEM;
+ goto nothing;
+ }
+
+ blkid_partition_set_type(par, p->p_fstype);
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return rc;
+}
+
+
+/*
+ * All BSD variants use the same magic string (little-endian),
+ * and the same disklabel.
+ *
+ * The difference between {Free,Open,...}BSD is in the (parental)
+ * primary partition type.
+ *
+ * See also: http://en.wikipedia.org/wiki/BSD_disklabel
+ *
+ * The location of BSD disk label is architecture specific and in defined by
+ * LABELSECTOR and LABELOFFSET macros in the disklabel.h file. The location
+ * also depends on BSD variant, FreeBSD uses only one location, NetBSD and
+ * OpenBSD are more creative...
+ *
+ * The basic overview:
+ *
+ * arch | LABELSECTOR | LABELOFFSET
+ * ------------------------+-------------+------------
+ * amd64 arm hppa hppa64 | |
+ * i386, macppc, mvmeppc | 1 | 0
+ * sgi, aviion, sh, socppc | |
+ * ------------------------+-------------+------------
+ * alpha luna88k mac68k | 0 | 64
+ * sparc(OpenBSD) vax | |
+ * ------------------------+-------------+------------
+ * sparc64 sparc(NetBSD) | 0 | 128
+ * ------------------------+-------------+------------
+ *
+ * ...and more (see http://fxr.watson.org/fxr/ident?v=NETBSD;i=LABELSECTOR)
+ *
+ */
+const struct blkid_idinfo bsd_pt_idinfo =
+{
+ .name = "bsd",
+ .probefunc = probe_bsd_pt,
+ .magics =
+ {
+ { .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 512 },
+ { .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 64 },
+ { .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 128 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/dos.c b/libblkid/src/partitions/dos.c
new file mode 100644
index 0000000..6e758ec
--- /dev/null
+++ b/libblkid/src/partitions/dos.c
@@ -0,0 +1,373 @@
+/*
+ * MS-DOS partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Inspired by fdisk, partx, Linux kernel and libparted.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "superblocks/superblocks.h"
+#include "aix.h"
+
+/* see superblocks/vfat.c */
+extern int blkid_probe_is_vfat(blkid_probe pr);
+
+static const struct dos_subtypes {
+ unsigned char type;
+ const struct blkid_idinfo *id;
+} dos_nested[] = {
+ { MBR_FREEBSD_PARTITION, &bsd_pt_idinfo },
+ { MBR_NETBSD_PARTITION, &bsd_pt_idinfo },
+ { MBR_OPENBSD_PARTITION, &bsd_pt_idinfo },
+ { MBR_UNIXWARE_PARTITION, &unixware_pt_idinfo },
+ { MBR_SOLARIS_X86_PARTITION, &solaris_x86_pt_idinfo },
+ { MBR_MINIX_PARTITION, &minix_pt_idinfo }
+};
+
+static inline int is_extended(struct dos_partition *p)
+{
+ return (p->sys_ind == MBR_DOS_EXTENDED_PARTITION ||
+ p->sys_ind == MBR_W95_EXTENDED_PARTITION ||
+ p->sys_ind == MBR_LINUX_EXTENDED_PARTITION);
+}
+
+static int parse_dos_extended(blkid_probe pr, blkid_parttable tab,
+ uint32_t ex_start, uint32_t ex_size, int ssf)
+{
+ blkid_partlist ls = blkid_probe_get_partlist(pr);
+ uint32_t cur_start = ex_start, cur_size = ex_size;
+ unsigned char *data;
+ int ct_nodata = 0; /* count ext.partitions without data partitions */
+ int i;
+
+ DBG(LOWPROBE, ul_debug("parse EBR [start=%d, size=%d]", ex_start/ssf, ex_size/ssf));
+ if (ex_start == 0) {
+ DBG(LOWPROBE, ul_debug("Bad offset in primary extended partition -- ignore"));
+ return 0;
+ }
+
+ while (1) {
+ struct dos_partition *p, *p0;
+ uint32_t start = 0, size;
+
+ if (++ct_nodata > 100)
+ return BLKID_PROBE_OK;
+ data = blkid_probe_get_sector(pr, cur_start);
+ if (!data) {
+ if (errno)
+ return -errno;
+ goto leave; /* malformed partition? */
+ }
+
+ if (!mbr_is_valid_magic(data))
+ goto leave;
+
+ p0 = mbr_get_partition(data, 0);
+
+ /* Usually, the first entry is the real data partition,
+ * the 2nd entry is the next extended partition, or empty,
+ * and the 3rd and 4th entries are unused.
+ * However, DRDOS sometimes has the extended partition as
+ * the first entry (when the data partition is empty),
+ * and OS/2 seems to use all four entries.
+ * -- Linux kernel fs/partitions/dos.c
+ *
+ * See also http://en.wikipedia.org/wiki/Extended_boot_record
+ */
+
+ /* Parse data partition */
+ for (p = p0, i = 0; i < 4; i++, p++) {
+ uint32_t abs_start;
+ blkid_partition par;
+
+ /* the start is relative to the parental ext.partition */
+ start = dos_partition_get_start(p) * ssf;
+ size = dos_partition_get_size(p) * ssf;
+ abs_start = cur_start + start; /* absolute start */
+
+ if (!size || is_extended(p))
+ continue;
+ if (i >= 2) {
+ /* extra checks to detect real data on
+ * 3rd and 4th entries */
+ if (start + size > cur_size)
+ continue;
+ if (abs_start < ex_start)
+ continue;
+ if (abs_start + size > ex_start + ex_size)
+ continue;
+ }
+
+ /* Avoid recursive non-empty links, see ct_nodata counter */
+ if (blkid_partlist_get_partition_by_start(ls, abs_start)) {
+ DBG(LOWPROBE, ul_debug("#%d: EBR duplicate data partition [abs start=%u] -- ignore",
+ i + 1, abs_start));
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, abs_start, size);
+ if (!par)
+ return -ENOMEM;
+
+ blkid_partition_set_type(par, p->sys_ind);
+ blkid_partition_set_flags(par, p->boot_ind);
+ blkid_partition_gen_uuid(par);
+ ct_nodata = 0;
+ }
+ /* The first nested ext.partition should be a link to the next
+ * logical partition. Everything other (recursive ext.partitions)
+ * is junk.
+ */
+ for (p = p0, i = 0; i < 4; i++, p++) {
+ start = dos_partition_get_start(p) * ssf;
+ size = dos_partition_get_size(p) * ssf;
+
+ if (size && is_extended(p)) {
+ if (start == 0)
+ DBG(LOWPROBE, ul_debug("#%d: EBR link offset is zero -- ignore", i + 1));
+ else
+ break;
+ }
+ }
+ if (i == 4)
+ goto leave;
+
+ cur_start = ex_start + start;
+ cur_size = size;
+ }
+leave:
+ return BLKID_PROBE_OK;
+}
+
+static inline int is_lvm(blkid_probe pr)
+{
+ struct blkid_prval *v = __blkid_probe_lookup_value(pr, "TYPE");
+
+ return (v && v->data && strcmp((char *) v->data, "LVM2_member") == 0);
+}
+
+static inline int is_empty_mbr(unsigned char *mbr)
+{
+ struct dos_partition *p = mbr_get_partition(mbr, 0);
+ int i, nparts = 0;
+
+ for (i = 0; i < 4; i++) {
+ if (dos_partition_get_size(p) > 0)
+ nparts++;
+ p++;
+ }
+
+ return nparts == 0;
+}
+
+static int probe_dos_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int i;
+ int ssf;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ struct dos_partition *p0, *p;
+ unsigned char *data;
+ uint32_t start, size, id;
+ char idstr[UUID_STR_LEN];
+
+
+ data = blkid_probe_get_sector(pr, 0);
+ if (!data) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ /* ignore disks with AIX magic number -- for more details see aix.c */
+ if (memcmp(data, BLKID_AIX_MAGIC_STRING, BLKID_AIX_MAGIC_STRLEN) == 0)
+ goto nothing;
+
+ p0 = mbr_get_partition(data, 0);
+
+ /*
+ * Reject PT where boot indicator is not 0 or 0x80.
+ */
+ for (p = p0, i = 0; i < 4; i++, p++)
+ if (p->boot_ind != 0 && p->boot_ind != 0x80) {
+ DBG(LOWPROBE, ul_debug("missing boot indicator -- ignore"));
+ goto nothing;
+ }
+
+ /*
+ * GPT uses valid MBR
+ */
+ for (p = p0, i = 0; i < 4; i++, p++) {
+ if (p->sys_ind == MBR_GPT_PARTITION) {
+ DBG(LOWPROBE, ul_debug("probably GPT -- ignore"));
+ goto nothing;
+ }
+ }
+
+ /*
+ * Now that the 55aa signature is present, this is probably
+ * either the boot sector of a FAT filesystem or a DOS-type
+ * partition table.
+ */
+ if (blkid_probe_is_vfat(pr) == 1) {
+ DBG(LOWPROBE, ul_debug("probably FAT -- ignore"));
+ goto nothing;
+ }
+
+ /* Another false positive is NTFS */
+ if (blkid_probe_is_ntfs(pr) == 1) {
+ DBG(LOWPROBE, ul_debug("probably NTFS -- ignore"));
+ goto nothing;
+ }
+
+ /*
+ * Ugly exception, if the device contains a valid LVM physical volume
+ * and empty MBR (=no partition defined) then it's LVM and MBR should
+ * be ignored. Crazy people use it to boot from LVM devices.
+ */
+ if (is_lvm(pr) && is_empty_mbr(data)) {
+ DBG(LOWPROBE, ul_debug("empty MBR on LVM device -- ignore"));
+ goto nothing;
+ }
+
+ blkid_probe_use_wiper(pr, MBR_PT_OFFSET, 512 - MBR_PT_OFFSET);
+
+ id = mbr_get_id(data);
+ if (id)
+ snprintf(idstr, sizeof(idstr), "%08x", id);
+
+ /*
+ * Well, all checks pass, it's MS-DOS partition table
+ */
+ if (blkid_partitions_need_typeonly(pr)) {
+ /* Non-binary interface -- caller does not ask for details
+ * about partitions, just set generic variables only. */
+ if (id)
+ blkid_partitions_strcpy_ptuuid(pr, idstr);
+ return 0;
+ }
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ /* sector size factor (the start and size are in the real sectors, but
+ * we need to convert all sizes to 512 logical sectors
+ */
+ ssf = blkid_probe_get_sectorsize(pr) / 512;
+
+ /* allocate a new partition table */
+ tab = blkid_partlist_new_parttable(ls, "dos", MBR_PT_OFFSET);
+ if (!tab)
+ return -ENOMEM;
+
+ if (id)
+ blkid_parttable_set_id(tab, (unsigned char *) idstr);
+
+ /* Parse primary partitions */
+ for (p = p0, i = 0; i < 4; i++, p++) {
+ blkid_partition par;
+
+ start = dos_partition_get_start(p) * ssf;
+ size = dos_partition_get_size(p) * ssf;
+
+ if (!size) {
+ /* Linux kernel ignores empty partitions, but partno for
+ * the empty primary partitions is not reused */
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ return -ENOMEM;
+
+ blkid_partition_set_type(par, p->sys_ind);
+ blkid_partition_set_flags(par, p->boot_ind);
+ blkid_partition_gen_uuid(par);
+ }
+
+ /* Linux uses partition numbers greater than 4
+ * for all logical partition and all nested partition tables (bsd, ..)
+ */
+ blkid_partlist_set_partno(ls, 5);
+
+ /* Parse logical partitions */
+ for (p = p0, i = 0; i < 4; i++, p++) {
+ start = dos_partition_get_start(p) * ssf;
+ size = dos_partition_get_size(p) * ssf;
+
+ if (!size)
+ continue;
+ if (is_extended(p) &&
+ parse_dos_extended(pr, tab, start, size, ssf) == -1)
+ goto nothing;
+ }
+
+ /* Parse subtypes (nested partitions) on large disks */
+ if (!blkid_probe_is_tiny(pr)) {
+ int nparts = blkid_partlist_numof_partitions(ls);
+
+ DBG(LOWPROBE, ul_debug("checking for subtypes"));
+
+ for (i = 0; i < nparts; i++) {
+ size_t n;
+ int type;
+ blkid_partition pa = blkid_partlist_get_partition(ls, i);
+
+ if (pa == NULL
+ || blkid_partition_get_size(pa) == 0
+ || blkid_partition_is_extended(pa)
+ || blkid_partition_is_logical(pa))
+ continue;
+
+ type = blkid_partition_get_type(pa);
+
+ for (n = 0; n < ARRAY_SIZE(dos_nested); n++) {
+ int rc;
+
+ if (dos_nested[n].type != type)
+ continue;
+
+ rc = blkid_partitions_do_subprobe(pr, pa,
+ dos_nested[n].id);
+ if (rc < 0)
+ return rc;
+ break;
+ }
+ }
+ }
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+}
+
+
+const struct blkid_idinfo dos_pt_idinfo =
+{
+ .name = "dos",
+ .probefunc = probe_dos_pt,
+ .magics =
+ {
+ /* DOS master boot sector:
+ *
+ * 0 | Code Area
+ * 440 | Optional Disk signature
+ * 446 | Partition table
+ * 510 | 0x55
+ * 511 | 0xAA
+ */
+ { .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/gpt.c b/libblkid/src/partitions/gpt.c
new file mode 100644
index 0000000..af6257a
--- /dev/null
+++ b/libblkid/src/partitions/gpt.c
@@ -0,0 +1,473 @@
+/*
+ * EFI GPT partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * This code is not copy & past from any other implementation.
+ *
+ * For more information about GPT start your study at:
+ * http://en.wikipedia.org/wiki/GUID_Partition_Table
+ * http://technet.microsoft.com/en-us/library/cc739412(WS.10).aspx
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include "partitions.h"
+#include "crc32.h"
+
+#define GPT_PRIMARY_LBA 1
+
+/* Signature - “EFI PART” */
+#define GPT_HEADER_SIGNATURE 0x5452415020494645ULL
+#define GPT_HEADER_SIGNATURE_STR "EFI PART"
+
+/* basic types */
+typedef uint16_t efi_char16_t;
+
+/* UUID */
+typedef struct {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_hi;
+ uint8_t clock_seq_low;
+ uint8_t node[6];
+} efi_guid_t;
+
+
+#define GPT_UNUSED_ENTRY_GUID \
+ ((efi_guid_t) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
+struct gpt_header {
+ uint64_t signature; /* "EFI PART" */
+ uint32_t revision;
+ uint32_t header_size; /* usually 92 bytes */
+ uint32_t header_crc32; /* checksum of header with this
+ * field zeroed during calculation */
+ uint32_t reserved1;
+
+ uint64_t my_lba; /* location of this header copy */
+ uint64_t alternate_lba; /* location of the other header copy */
+ uint64_t first_usable_lba; /* first usable LBA for partitions */
+ uint64_t last_usable_lba; /* last usable LBA for partitions */
+
+ efi_guid_t disk_guid; /* disk UUID */
+
+ uint64_t partition_entries_lba; /* always 2 in primary header copy */
+ uint32_t num_partition_entries;
+ uint32_t sizeof_partition_entry;
+ uint32_t partition_entry_array_crc32;
+
+ /*
+ * The rest of the block is reserved by UEFI and must be zero. EFI
+ * standard handles this by:
+ *
+ * uint8_t reserved2[ BLKSSZGET - 92 ];
+ *
+ * This definition is useless in practice. It is necessary to read
+ * whole block from the device rather than sizeof(struct gpt_header)
+ * only.
+ */
+} __attribute__ ((packed));
+
+/*** not used
+struct gpt_entry_attributes {
+ uint64_t required_to_function:1;
+ uint64_t reserved:47;
+ uint64_t type_guid_specific:16;
+} __attribute__ ((packed));
+***/
+
+struct gpt_entry {
+ efi_guid_t partition_type_guid; /* type UUID */
+ efi_guid_t unique_partition_guid; /* partition UUID */
+ uint64_t starting_lba;
+ uint64_t ending_lba;
+
+ /*struct gpt_entry_attributes attributes;*/
+
+ uint64_t attributes;
+
+ efi_char16_t partition_name[72 / sizeof(efi_char16_t)]; /* UTF-16LE string*/
+} __attribute__ ((packed));
+
+
+/*
+ * EFI uses crc32 with ~0 seed and xor's with ~0 at the end.
+ */
+static inline uint32_t count_crc32(const unsigned char *buf, size_t len,
+ size_t exclude_off, size_t exclude_len)
+{
+ return (ul_crc32_exclude_offset(~0L, buf, len, exclude_off, exclude_len) ^ ~0L);
+}
+
+static inline unsigned char *get_lba_buffer(blkid_probe pr,
+ uint64_t lba, size_t bytes)
+{
+ return blkid_probe_get_buffer(pr,
+ blkid_probe_get_sectorsize(pr) * lba, bytes);
+}
+
+static inline int guidcmp(efi_guid_t left, efi_guid_t right)
+{
+ return memcmp(&left, &right, sizeof (efi_guid_t));
+}
+
+/*
+ * UUID is traditionally 16 byte big-endian array, except Intel EFI
+ * specification where the UUID is a structure of little-endian fields.
+ */
+static void swap_efi_guid(efi_guid_t *uid)
+{
+ uid->time_low = swab32(uid->time_low);
+ uid->time_mid = swab16(uid->time_mid);
+ uid->time_hi_and_version = swab16(uid->time_hi_and_version);
+}
+
+static int last_lba(blkid_probe pr, uint64_t *lba)
+{
+ uint64_t sz = blkid_probe_get_size(pr);
+ unsigned int ssz = blkid_probe_get_sectorsize(pr);
+
+ if (sz < ssz)
+ return -1;
+
+ *lba = (sz / ssz) - 1ULL;
+ return 0;
+}
+
+/*
+ * Protective (legacy) MBR.
+ *
+ * This MBR contains standard DOS partition table with a single partition, type
+ * of 0xEE. The partition usually encompassing the entire GPT drive - or 2TiB
+ * for large disks.
+ *
+ * Note that Apple uses GPT/MBR hybrid disks, where the DOS partition table is
+ * synchronized with GPT. This synchronization has many restriction of course
+ * (due DOS PT limitations).
+ *
+ * Note that the PMBR detection is optional (enabled by default) and could be
+ * disabled by BLKID_PARTS_FOPCE_GPT flag (see also blkid_partitions_set_flags()).
+ */
+static int is_pmbr_valid(blkid_probe pr, int *has)
+{
+ int flags = blkid_partitions_get_flags(pr);
+ unsigned char *data;
+ struct dos_partition *p;
+ int i;
+
+ if (has)
+ *has = 0;
+ else if (flags & BLKID_PARTS_FORCE_GPT)
+ return 1; /* skip PMBR check */
+
+ data = blkid_probe_get_sector(pr, 0);
+ if (!data) {
+ if (errno)
+ return -errno;
+ goto failed;
+ }
+
+ if (!mbr_is_valid_magic(data))
+ goto failed;
+
+ for (i = 0, p = mbr_get_partition(data, 0); i < 4; i++, p++) {
+ if (p->sys_ind == MBR_GPT_PARTITION) {
+ DBG(LOWPROBE, ul_debug(" #%d valid PMBR partition", i + 1));
+ goto ok;
+ }
+ }
+failed:
+ return 0;
+ok:
+ if (has)
+ *has = 1;
+ return 1;
+}
+
+/*
+ * Reads GPT header to @hdr and returns a pointer to @hdr or NULL in case of
+ * error. The function also returns GPT entries in @ents.
+ *
+ * Note, this function does not allocate any memory. The GPT header has fixed
+ * size so we use stack, and @ents returns memory from libblkid buffer (so the
+ * next blkid_probe_get_buffer() will overwrite this buffer).
+ *
+ * This function checks validity of header and entries array. A corrupted
+ * header is not returned.
+ */
+static struct gpt_header *get_gpt_header(
+ blkid_probe pr, struct gpt_header *hdr,
+ struct gpt_entry **ents, uint64_t lba,
+ uint64_t lastlba)
+{
+ struct gpt_header *h;
+ uint32_t crc;
+ uint64_t lu, fu;
+ uint64_t esz;
+ uint32_t hsz, ssz;
+
+ ssz = blkid_probe_get_sectorsize(pr);
+
+ DBG(LOWPROBE, ul_debug(" checking for GPT header at %"PRIu64, lba));
+
+ /* whole sector is allocated for GPT header */
+ h = (struct gpt_header *) get_lba_buffer(pr, lba, ssz);
+ if (!h)
+ return NULL;
+
+ if (le64_to_cpu(h->signature) != GPT_HEADER_SIGNATURE)
+ return NULL;
+
+ hsz = le32_to_cpu(h->header_size);
+
+ /* EFI: The HeaderSize must be greater than 92 and must be less
+ * than or equal to the logical block size.
+ */
+ if (hsz > ssz || hsz < sizeof(*h))
+ return NULL;
+
+ /* Header has to be verified when header_crc32 is zero */
+ crc = count_crc32((unsigned char *) h, hsz,
+ offsetof(struct gpt_header, header_crc32),
+ sizeof(h->header_crc32));
+
+ if (crc != le32_to_cpu(h->header_crc32)) {
+ DBG(LOWPROBE, ul_debug("GPT header corrupted"));
+ return NULL;
+ }
+
+ /* Valid header has to be at MyLBA */
+ if (le64_to_cpu(h->my_lba) != lba) {
+ DBG(LOWPROBE, ul_debug(
+ "GPT->MyLBA mismatch with real position"));
+ return NULL;
+ }
+
+ fu = le64_to_cpu(h->first_usable_lba);
+ lu = le64_to_cpu(h->last_usable_lba);
+
+ /* Check if First and Last usable LBA makes sense */
+ if (lu < fu || fu > lastlba || lu > lastlba) {
+ DBG(LOWPROBE, ul_debug(
+ "GPT->{First,Last}UsableLBA out of range"));
+ return NULL;
+ }
+
+ /* The header has to be outside usable range */
+ if (fu < lba && lba < lu) {
+ DBG(LOWPROBE, ul_debug("GPT header is inside usable area"));
+ return NULL;
+ }
+
+ /* Size of blocks with GPT entries */
+ esz = (uint64_t)le32_to_cpu(h->num_partition_entries) *
+ le32_to_cpu(h->sizeof_partition_entry);
+
+ if (esz == 0 || esz >= UINT32_MAX ||
+ le32_to_cpu(h->sizeof_partition_entry) != sizeof(struct gpt_entry)) {
+ DBG(LOWPROBE, ul_debug("GPT entries undefined"));
+ return NULL;
+ }
+
+ /* The header seems valid, save it
+ * (we don't care about zeros in hdr->reserved2 area) */
+ memcpy(hdr, h, sizeof(*h));
+ h = hdr;
+
+ /* Read GPT entries */
+ *ents = (struct gpt_entry *) get_lba_buffer(pr,
+ le64_to_cpu(h->partition_entries_lba), esz);
+ if (!*ents) {
+ DBG(LOWPROBE, ul_debug("GPT entries unreadable"));
+ return NULL;
+ }
+
+ /* Validate entries */
+ crc = count_crc32((unsigned char *) *ents, esz, 0, 0);
+ if (crc != le32_to_cpu(h->partition_entry_array_crc32)) {
+ DBG(LOWPROBE, ul_debug("GPT entries corrupted"));
+ return NULL;
+ }
+
+ return h;
+}
+
+static int probe_gpt_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t lastlba = 0, lba;
+ struct gpt_header hdr, *h;
+ struct gpt_entry *e;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ uint64_t fu, lu;
+ uint32_t ssf, i;
+ efi_guid_t guid;
+ int ret;
+
+ if (last_lba(pr, &lastlba))
+ goto nothing;
+
+ ret = is_pmbr_valid(pr, NULL);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ goto nothing;
+
+ errno = 0;
+ h = get_gpt_header(pr, &hdr, &e, (lba = GPT_PRIMARY_LBA), lastlba);
+ if (!h && !errno)
+ h = get_gpt_header(pr, &hdr, &e, (lba = lastlba), lastlba);
+
+ if (!h) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ blkid_probe_use_wiper(pr, lba * blkid_probe_get_size(pr), 8);
+
+ if (blkid_probe_set_magic(pr, blkid_probe_get_sectorsize(pr) * lba,
+ sizeof(GPT_HEADER_SIGNATURE_STR) - 1,
+ (unsigned char *) GPT_HEADER_SIGNATURE_STR))
+ goto err;
+
+ guid = h->disk_guid;
+ swap_efi_guid(&guid);
+
+ if (blkid_partitions_need_typeonly(pr)) {
+ /* Non-binary interface -- caller does not ask for details
+ * about partitions, just set generic variables only. */
+ blkid_partitions_set_ptuuid(pr, (unsigned char *) &guid);
+ return BLKID_PROBE_OK;
+ }
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "gpt",
+ blkid_probe_get_sectorsize(pr) * lba);
+ if (!tab)
+ goto err;
+
+ blkid_parttable_set_uuid(tab, (const unsigned char *) &guid);
+
+ ssf = blkid_probe_get_sectorsize(pr) / 512;
+
+ fu = le64_to_cpu(h->first_usable_lba);
+ lu = le64_to_cpu(h->last_usable_lba);
+
+ for (i = 0; i < le32_to_cpu(h->num_partition_entries); i++, e++) {
+
+ blkid_partition par;
+ uint64_t start = le64_to_cpu(e->starting_lba);
+ uint64_t size = le64_to_cpu(e->ending_lba) -
+ le64_to_cpu(e->starting_lba) + 1ULL;
+
+ /* 00000000-0000-0000-0000-000000000000 entry */
+ if (!guidcmp(e->partition_type_guid, GPT_UNUSED_ENTRY_GUID)) {
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+ /* the partition has to inside usable range */
+ if (start < fu || start + size - 1 > lu) {
+ DBG(LOWPROBE, ul_debug(
+ "GPT entry[%d] overflows usable area - ignore",
+ i));
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab,
+ start * ssf, size * ssf);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_utf8name(par,
+ (unsigned char *) e->partition_name,
+ sizeof(e->partition_name), UL_ENCODE_UTF16LE);
+
+ guid = e->unique_partition_guid;
+ swap_efi_guid(&guid);
+ blkid_partition_set_uuid(par, (const unsigned char *) &guid);
+
+ guid = e->partition_type_guid;
+ swap_efi_guid(&guid);
+ blkid_partition_set_type_uuid(par, (const unsigned char *) &guid);
+
+ blkid_partition_set_flags(par, le64_to_cpu(e->attributes));
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+
+err:
+ return -ENOMEM;
+}
+
+
+const struct blkid_idinfo gpt_pt_idinfo =
+{
+ .name = "gpt",
+ .probefunc = probe_gpt_pt,
+
+ /*
+ * It would be possible to check for DOS signature (0xAA55), but
+ * unfortunately almost all EFI GPT implementations allow to optionally
+ * skip the legacy MBR. We follows this behavior and MBR is optional.
+ * See is_valid_pmbr().
+ *
+ * It means we have to always call probe_gpt_pt().
+ */
+ .magics = BLKID_NONE_MAGIC
+};
+
+
+
+/* probe for *alone* protective MBR */
+static int probe_pmbr_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int has = 0;
+ struct gpt_entry *e;
+ uint64_t lastlba = 0;
+ struct gpt_header hdr;
+
+ if (last_lba(pr, &lastlba))
+ goto nothing;
+
+ is_pmbr_valid(pr, &has);
+ if (!has)
+ goto nothing;
+
+ if (!get_gpt_header(pr, &hdr, &e, GPT_PRIMARY_LBA, lastlba) &&
+ !get_gpt_header(pr, &hdr, &e, lastlba, lastlba))
+ return 0;
+nothing:
+ return 1;
+}
+
+const struct blkid_idinfo pmbr_pt_idinfo =
+{
+ .name = "PMBR",
+ .probefunc = probe_pmbr_pt,
+ .magics =
+ {
+ { .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/mac.c b/libblkid/src/partitions/mac.c
new file mode 100644
index 0000000..75a558b
--- /dev/null
+++ b/libblkid/src/partitions/mac.c
@@ -0,0 +1,200 @@
+/*
+ * mac partitions parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+#define MAC_PARTITION_MAGIC 0x504d
+#define MAC_PARTITION_MAGIC_OLD 0x5453
+
+/*
+ * Mac partition entry
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Devices/Devices-126.html
+ */
+struct mac_partition {
+ uint16_t signature; /* expected to be MAC_PARTITION_MAGIC */
+ uint16_t reserved; /* reserved */
+ uint32_t map_count; /* # blocks in partition map */
+ uint32_t start_block; /* absolute starting block # of partition */
+ uint32_t block_count; /* number of blocks in partition */
+ char name[32]; /* partition name */
+ char type[32]; /* string type description */
+ uint32_t data_start; /* rel block # of first data block */
+ uint32_t data_count; /* number of data blocks */
+ uint32_t status; /* partition status bits */
+ uint32_t boot_start; /* first logical block of boot code */
+ uint32_t boot_size; /* size of boot code, in bytes */
+ uint32_t boot_load; /* boot code load address */
+ uint32_t boot_load2; /* reserved */
+ uint32_t boot_entry; /* boot code entry point */
+ uint32_t boot_entry2; /* reserved */
+ uint32_t boot_cksum; /* boot code checksum */
+ char processor[16]; /* identifies ISA of boot */
+
+ /* there is more stuff after this that we don't need */
+} __attribute__((packed));
+
+/*
+ * Driver descriptor structure, in block 0
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Devices/Devices-121.html
+ */
+struct mac_driver_desc {
+ uint16_t signature; /* expected to be MAC_DRIVER_MAGIC */
+ uint16_t block_size; /* block size of the device */
+ uint32_t block_count; /* number of blocks on the device */
+
+ /* there is more stuff after this that we don't need */
+} __attribute__((packed));
+
+static inline unsigned char *get_mac_block(
+ blkid_probe pr,
+ uint16_t block_size,
+ uint32_t num)
+{
+ return blkid_probe_get_buffer(pr, (uint64_t) num * block_size, block_size);
+}
+
+static inline int has_part_signature(struct mac_partition *p)
+{
+ return be16_to_cpu(p->signature) == MAC_PARTITION_MAGIC ||
+ be16_to_cpu(p->signature) == MAC_PARTITION_MAGIC_OLD;
+}
+
+static int probe_mac_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct mac_driver_desc *md;
+ struct mac_partition *p;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ uint16_t block_size;
+ uint16_t ssf; /* sector size fragment */
+ uint32_t nblks, nprts, i;
+
+
+ /* The driver descriptor record is always located at physical block 0,
+ * the first block on the disk.
+ */
+ md = (struct mac_driver_desc *) blkid_probe_get_sector(pr, 0);
+ if (!md) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ block_size = be16_to_cpu(md->block_size);
+ if (block_size < sizeof(struct mac_partition))
+ goto nothing;
+
+ /* The partition map always begins at physical block 1,
+ * the second block on the disk.
+ */
+ p = (struct mac_partition *) get_mac_block(pr, block_size, 1);
+ if (!p) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ /* check the first partition signature */
+ if (!has_part_signature(p))
+ goto nothing;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return 0;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "mac", 0);
+ if (!tab)
+ goto err;
+
+ ssf = block_size / 512;
+ nblks = be32_to_cpu(p->map_count);
+ if (nblks > 256) {
+ nprts = 256;
+ DBG(LOWPROBE, ul_debug(
+ "mac: map_count too large, entry[0]: %u, "
+ "enforcing limit of %u", nblks, nprts));
+ } else
+ nprts = nblks;
+
+ for (i = 0; i < nprts; ++i) {
+ blkid_partition par;
+ uint32_t start;
+ uint32_t size;
+
+ p = (struct mac_partition *) get_mac_block(pr, block_size, i + 1);
+ if (!p) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+ if (!has_part_signature(p))
+ goto nothing;
+
+ if (be32_to_cpu(p->map_count) != nblks) {
+ DBG(LOWPROBE, ul_debug(
+ "mac: inconsistent map_count in partition map, "
+ "entry[0]: %u, entry[%u]: %u",
+ nblks, i,
+ be32_to_cpu(p->map_count)));
+ }
+
+ /*
+ * note that libparted ignores some mac partitions according to
+ * the partition name (e.g. "Apple_Free" or "Apple_Void"). We
+ * follows Linux kernel and all partitions are visible
+ */
+
+ start = be32_to_cpu(p->start_block) * ssf;
+ size = be32_to_cpu(p->block_count) * ssf;
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_name(par, (unsigned char *) p->name,
+ sizeof(p->name));
+
+ blkid_partition_set_type_string(par, (unsigned char *) p->type,
+ sizeof(p->type));
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+/*
+ * Mac disk always begin with "Driver Descriptor Record"
+ * (struct mac_driver_desc) and magic 0x4552.
+ */
+const struct blkid_idinfo mac_pt_idinfo =
+{
+ .name = "mac",
+ .probefunc = probe_mac_pt,
+ .magics =
+ {
+ /* big-endian magic string */
+ { .magic = "\x45\x52", .len = 2 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/minix.c b/libblkid/src/partitions/minix.c
new file mode 100644
index 0000000..43c9d9a
--- /dev/null
+++ b/libblkid/src/partitions/minix.c
@@ -0,0 +1,102 @@
+/*
+ * Minix partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "minix.h"
+
+static int probe_minix_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct dos_partition *p;
+ blkid_parttable tab = NULL;
+ blkid_partition parent;
+ blkid_partlist ls;
+ unsigned char *data;
+ int i;
+
+ data = blkid_probe_get_sector(pr, 0);
+ if (!data) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ /* Parent is required, because Minix uses the same PT as DOS and
+ * difference is only in primary partition (parent) type.
+ */
+ parent = blkid_partlist_get_parent(ls);
+ if (!parent)
+ goto nothing;
+
+ if (blkid_partition_get_type(parent) != MBR_MINIX_PARTITION)
+ goto nothing;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ tab = blkid_partlist_new_parttable(ls, "minix", MBR_PT_OFFSET);
+ if (!tab)
+ goto err;
+
+ for (i = 0, p = mbr_get_partition(data, 0);
+ i < MINIX_MAXPARTITIONS; i++, p++) {
+
+ uint32_t start, size;
+ blkid_partition par;
+
+ if (p->sys_ind != MBR_MINIX_PARTITION)
+ continue;
+
+ start = dos_partition_get_start(p);
+ size = dos_partition_get_size(p);
+
+ if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: minix partition (%d) overflow "
+ "detected, ignore", i));
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_type(par, p->sys_ind);
+ blkid_partition_set_flags(par, p->boot_ind);
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+/* same as DOS */
+const struct blkid_idinfo minix_pt_idinfo =
+{
+ .name = "minix",
+ .probefunc = probe_minix_pt,
+ .magics =
+ {
+ { .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/partitions.c b/libblkid/src/partitions/partitions.c
new file mode 100644
index 0000000..a34a446
--- /dev/null
+++ b/libblkid/src/partitions/partitions.c
@@ -0,0 +1,1526 @@
+/*
+ * partitions - partition tables parsing
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include "partitions.h"
+#include "sysfs.h"
+#include "strutils.h"
+
+/**
+ * SECTION: partitions
+ * @title: Partitions probing
+ * @short_description: partitions tables detection and parsing
+ *
+ * This chain supports binary and NAME=value interfaces, but complete PT
+ * description is provided by binary interface only. The libblkid prober is
+ * compatible with kernel partition tables parser. The parser does not return
+ * empty (size=0) partitions or special hidden partitions.
+ *
+ * NAME=value interface, supported tags:
+ *
+ * @PTTYPE: partition table type (dos, gpt, etc.).
+ *
+ * @PTUUID: partition table id (uuid for gpt, hex for dos).
+
+ * @PART_ENTRY_SCHEME: partition table type
+ *
+ * @PART_ENTRY_NAME: partition name (gpt and mac only)
+ *
+ * @PART_ENTRY_UUID: partition UUID (gpt, or pseudo IDs for MBR)
+ *
+ * @PART_ENTRY_TYPE: partition type, 0xNN (e.g. 0x82) or type UUID (gpt only) or type string (mac)
+ *
+ * @PART_ENTRY_FLAGS: partition flags (e.g. boot_ind) or attributes (e.g. gpt attributes)
+ *
+ * @PART_ENTRY_NUMBER: partition number
+ *
+ * @PART_ENTRY_OFFSET: the begin of the partition
+ *
+ * @PART_ENTRY_SIZE: size of the partition
+ *
+ * @PART_ENTRY_DISK: whole-disk maj:min
+ *
+ * Example:
+ *
+ * <informalexample>
+ * <programlisting>
+ * blkid_probe pr;
+ * const char *ptname;
+ *
+ * pr = blkid_new_probe_from_filename(devname);
+ * if (!pr)
+ * err("%s: failed to open device", devname);
+ *
+ * blkid_probe_enable_partitions(pr, TRUE);
+ * blkid_do_fullprobe(pr);
+ *
+ * blkid_probe_lookup_value(pr, "PTTYPE", &ptname, NULL);
+ * printf("%s partition type detected\n", pttype);
+ *
+ * blkid_free_probe(pr);
+ *
+ * // don't forget to check return codes in your code!
+ * </programlisting>
+ * </informalexample>
+ *
+ * Binary interface:
+ *
+ * <informalexample>
+ * <programlisting>
+ * blkid_probe pr;
+ * blkid_partlist ls;
+ * int nparts, i;
+ *
+ * pr = blkid_new_probe_from_filename(devname);
+ * if (!pr)
+ * err("%s: failed to open device", devname);
+ *
+ * ls = blkid_probe_get_partitions(pr);
+ * nparts = blkid_partlist_numof_partitions(ls);
+ *
+ * for (i = 0; i < nparts; i++) {
+ * blkid_partition par = blkid_partlist_get_partition(ls, i);
+ * printf("#%d: %llu %llu 0x%x",
+ * blkid_partition_get_partno(par),
+ * blkid_partition_get_start(par),
+ * blkid_partition_get_size(par),
+ * blkid_partition_get_type(par));
+ * }
+ *
+ * blkid_free_probe(pr);
+ *
+ * // don't forget to check return codes in your code!
+ * </programlisting>
+ * </informalexample>
+ */
+
+/*
+ * Chain driver function
+ */
+static int partitions_probe(blkid_probe pr, struct blkid_chain *chn);
+static void partitions_free_data(blkid_probe pr, void *data);
+
+/*
+ * Partitions chain probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+ &aix_pt_idinfo,
+ &sgi_pt_idinfo,
+ &sun_pt_idinfo,
+ &dos_pt_idinfo,
+ &gpt_pt_idinfo,
+ &pmbr_pt_idinfo, /* always after GPT */
+ &mac_pt_idinfo,
+ &ultrix_pt_idinfo,
+ &bsd_pt_idinfo,
+ &unixware_pt_idinfo,
+ &solaris_x86_pt_idinfo,
+ &minix_pt_idinfo,
+ &atari_pt_idinfo
+};
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv partitions_drv = {
+ .id = BLKID_CHAIN_PARTS,
+ .name = "partitions",
+ .dflt_enabled = FALSE,
+ .idinfos = idinfos,
+ .nidinfos = ARRAY_SIZE(idinfos),
+ .has_fltr = TRUE,
+ .probe = partitions_probe,
+ .safeprobe = partitions_probe,
+ .free_data = partitions_free_data
+};
+
+
+/*
+ * For compatibility with the rest of libblkid API (with the old high-level
+ * API) we use completely opaque typedefs for all structs. Don't forget that
+ * the final blkid_* types are pointers! See blkid.h.
+ *
+ * [Just for the record, I hate typedef for pointers --kzak]
+ */
+
+/* exported as opaque type "blkid_parttable" */
+struct blkid_struct_parttable {
+ const char *type; /* partition table type */
+ uint64_t offset; /* begin of the partition table (in bytes) */
+ int nparts; /* number of partitions */
+ blkid_partition parent; /* parent of nested partition table */
+ char id[UUID_STR_LEN]; /* PT identifier (e.g. UUID for GPT) */
+
+ struct list_head t_tabs; /* all tables */
+};
+
+/* exported as opaque type "blkid_partition" */
+struct blkid_struct_partition {
+ uint64_t start; /* begin of the partition (512-bytes sectors) */
+ uint64_t size; /* size of the partitions (512-bytes sectors) */
+
+ int type; /* partition type */
+ char typestr[UUID_STR_LEN]; /* partition type string (GPT and Mac) */
+
+ unsigned long long flags; /* partition flags / attributes */
+
+ int partno; /* partition number */
+ char uuid[UUID_STR_LEN]; /* UUID (when supported by PT), e.g. GPT */
+ unsigned char name[128]; /* Partition in UTF8 name (when supported by PT), e.g. Mac */
+
+ blkid_parttable tab; /* partition table */
+};
+
+/* exported as opaque type "blkid_partlist" */
+struct blkid_struct_partlist {
+ int next_partno; /* next partition number */
+ blkid_partition next_parent; /* next parent if parsing nested PT */
+
+ int nparts; /* number of partitions */
+ int nparts_max; /* max.number of partitions */
+ blkid_partition parts; /* array of partitions */
+
+ struct list_head l_tabs; /* list of partition tables */
+};
+
+static int blkid_partitions_probe_partition(blkid_probe pr);
+
+/**
+ * blkid_probe_enable_partitions:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the partitions probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_partitions(blkid_probe pr, int enable)
+{
+ pr->chains[BLKID_CHAIN_PARTS].enabled = enable;
+ return 0;
+}
+
+/**
+ * blkid_probe_set_partitions_flags:
+ * @pr: prober
+ * @flags: BLKID_PARTS_* flags
+ *
+ * Sets probing flags to the partitions prober. This function is optional.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_set_partitions_flags(blkid_probe pr, int flags)
+{
+ pr->chains[BLKID_CHAIN_PARTS].flags = flags;
+ return 0;
+}
+
+/**
+ * blkid_probe_reset_partitions_filter:
+ * @pr: prober
+ *
+ * Resets partitions probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_reset_partitions_filter(blkid_probe pr)
+{
+ return __blkid_probe_reset_filter(pr, BLKID_CHAIN_PARTS);
+}
+
+/**
+ * blkid_probe_invert_partitions_filter:
+ * @pr: prober
+ *
+ * Inverts partitions probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_invert_partitions_filter(blkid_probe pr)
+{
+ return __blkid_probe_invert_filter(pr, BLKID_CHAIN_PARTS);
+}
+
+/**
+ * blkid_probe_filter_partitions_type:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ * %BLKID_FLTR_NOTIN - probe for all items which are NOT IN @names
+ *
+ * %BLKID_FLTR_ONLYIN - probe for items which are IN @names
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[])
+{
+ return __blkid_probe_filter_types(pr, BLKID_CHAIN_PARTS, flag, names);
+}
+
+/**
+ * blkid_probe_get_partitions:
+ * @pr: probe
+ *
+ * This is a binary interface for partitions. See also blkid_partlist_*
+ * functions.
+ *
+ * This function is independent on blkid_do_[safe,full]probe() and
+ * blkid_probe_enable_partitions() calls.
+ *
+ * WARNING: the returned object will be overwritten by the next
+ * blkid_probe_get_partitions() call for the same @pr. If you want to
+ * use more blkid_partlist objects in the same time you have to create
+ * more blkid_probe handlers (see blkid_new_probe()).
+ *
+ * Returns: list of partitions, or NULL in case of error.
+ */
+blkid_partlist blkid_probe_get_partitions(blkid_probe pr)
+{
+ return (blkid_partlist) blkid_probe_get_binary_data(pr,
+ &pr->chains[BLKID_CHAIN_PARTS]);
+}
+
+/* for internal usage only */
+blkid_partlist blkid_probe_get_partlist(blkid_probe pr)
+{
+ return (blkid_partlist) pr->chains[BLKID_CHAIN_PARTS].data;
+}
+
+static void blkid_probe_set_partlist(blkid_probe pr, blkid_partlist ls)
+{
+ pr->chains[BLKID_CHAIN_PARTS].data = ls;
+}
+
+static void ref_parttable(blkid_parttable tab)
+{
+ if (tab)
+ tab->nparts++;
+}
+
+static void unref_parttable(blkid_parttable tab)
+{
+ if (!tab)
+ return;
+
+ tab->nparts--;
+ if (tab->nparts <= 0) {
+ list_del(&tab->t_tabs);
+ free(tab);
+ }
+}
+
+/* free all allocated parttables */
+static void free_parttables(blkid_partlist ls)
+{
+ if (!ls || !ls->l_tabs.next)
+ return;
+
+ /* remove unassigned partition tables */
+ while (!list_empty(&ls->l_tabs)) {
+ blkid_parttable tab = list_entry(ls->l_tabs.next,
+ struct blkid_struct_parttable, t_tabs);
+ unref_parttable(tab);
+ }
+}
+
+static void reset_partlist(blkid_partlist ls)
+{
+ if (!ls)
+ return;
+
+ free_parttables(ls);
+
+ if (ls->next_partno) {
+ /* already initialized - reset */
+ int tmp_nparts = ls->nparts_max;
+ blkid_partition tmp_parts = ls->parts;
+
+ memset(ls, 0, sizeof(struct blkid_struct_partlist));
+
+ ls->nparts_max = tmp_nparts;
+ ls->parts = tmp_parts;
+ }
+
+ ls->nparts = 0;
+ ls->next_partno = 1;
+ INIT_LIST_HEAD(&ls->l_tabs);
+
+ DBG(LOWPROBE, ul_debug("partlist reset"));
+}
+
+static blkid_partlist partitions_init_data(struct blkid_chain *chn)
+{
+ blkid_partlist ls;
+
+ if (chn->data)
+ ls = (blkid_partlist) chn->data;
+ else {
+ /* allocate the new list of partitions */
+ ls = calloc(1, sizeof(struct blkid_struct_partlist));
+ if (!ls)
+ return NULL;
+ chn->data = (void *) ls;
+ }
+
+ reset_partlist(ls);
+
+ DBG(LOWPROBE, ul_debug("parts: initialized partitions list (size=%d)", ls->nparts_max));
+ return ls;
+}
+
+static void partitions_free_data(blkid_probe pr __attribute__((__unused__)),
+ void *data)
+{
+ blkid_partlist ls = (blkid_partlist) data;
+
+ if (!ls)
+ return;
+
+ free_parttables(ls);
+
+ /* deallocate partitions and partlist */
+ free(ls->parts);
+ free(ls);
+}
+
+blkid_parttable blkid_partlist_new_parttable(blkid_partlist ls,
+ const char *type, uint64_t offset)
+{
+ blkid_parttable tab;
+
+ tab = calloc(1, sizeof(struct blkid_struct_parttable));
+ if (!tab)
+ return NULL;
+ tab->type = type;
+ tab->offset = offset;
+ tab->parent = ls->next_parent;
+
+ INIT_LIST_HEAD(&tab->t_tabs);
+ list_add_tail(&tab->t_tabs, &ls->l_tabs);
+
+ DBG(LOWPROBE, ul_debug("parts: create a new partition table "
+ "(type=%s, offset=%"PRId64")", type, offset));
+ return tab;
+}
+
+static blkid_partition new_partition(blkid_partlist ls, blkid_parttable tab)
+{
+ blkid_partition par;
+
+ if (ls->nparts + 1 > ls->nparts_max) {
+ /* Linux kernel has DISK_MAX_PARTS=256, but it's too much for
+ * generic Linux machine -- let start with 32 partitions.
+ */
+ void *tmp = realloc(ls->parts, (ls->nparts_max + 32) *
+ sizeof(struct blkid_struct_partition));
+ if (!tmp)
+ return NULL;
+ ls->parts = tmp;
+ ls->nparts_max += 32;
+ }
+
+ par = &ls->parts[ls->nparts++];
+ memset(par, 0, sizeof(struct blkid_struct_partition));
+
+ ref_parttable(tab);
+ par->tab = tab;
+ par->partno = blkid_partlist_increment_partno(ls);
+
+ return par;
+}
+
+blkid_partition blkid_partlist_add_partition(blkid_partlist ls,
+ blkid_parttable tab, uint64_t start, uint64_t size)
+{
+ blkid_partition par = new_partition(ls, tab);
+
+ if (!par)
+ return NULL;
+
+ par->start = start;
+ par->size = size;
+
+ DBG(LOWPROBE, ul_debug("parts: add partition (start=%"
+ PRIu64 ", size=%" PRIu64 ")",
+ par->start, par->size));
+ return par;
+}
+
+/* can be used to modify used partitions numbers (for example for logical partitions) */
+int blkid_partlist_set_partno(blkid_partlist ls, int partno)
+{
+ if (!ls)
+ return -1;
+ ls->next_partno = partno;
+ return 0;
+}
+
+int blkid_partlist_increment_partno(blkid_partlist ls)
+{
+ return ls ? ls->next_partno++ : -1;
+}
+
+/* can be used to set "parent" for the next nested partition */
+static int blkid_partlist_set_parent(blkid_partlist ls, blkid_partition par)
+{
+ if (!ls)
+ return -1;
+ ls->next_parent = par;
+ return 0;
+}
+
+blkid_partition blkid_partlist_get_parent(blkid_partlist ls)
+{
+ if (!ls)
+ return NULL;
+ return ls->next_parent;
+}
+
+int blkid_partitions_need_typeonly(blkid_probe pr)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ return chn && chn->data && chn->binary ? FALSE : TRUE;
+}
+
+/* get private chain flags */
+int blkid_partitions_get_flags(blkid_probe pr)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ return chn ? chn->flags : 0;
+}
+
+/* check if @start and @size are within @par partition */
+int blkid_is_nested_dimension(blkid_partition par,
+ uint64_t start, uint64_t size)
+{
+ uint64_t pstart;
+ uint64_t psize;
+
+ if (!par)
+ return 0;
+
+ pstart = blkid_partition_get_start(par);
+ psize = blkid_partition_get_size(par);
+
+ if (start < pstart || start + size > pstart + psize)
+ return 0;
+
+ return 1;
+}
+
+static int idinfo_probe(blkid_probe pr, const struct blkid_idinfo *id,
+ struct blkid_chain *chn)
+{
+ const struct blkid_idmag *mag = NULL;
+ uint64_t off;
+ int rc = BLKID_PROBE_NONE; /* default is nothing */
+
+ if (pr->size <= 0 || (id->minsz && (unsigned)id->minsz > pr->size))
+ goto nothing; /* the device is too small */
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ goto nothing;
+
+ rc = blkid_probe_get_idmag(pr, id, &off, &mag);
+ if (rc != BLKID_PROBE_OK)
+ goto nothing;
+
+ /* final check by probing function */
+ if (id->probefunc) {
+ DBG(LOWPROBE, ul_debug(
+ "%s: ---> call probefunc()", id->name));
+ rc = id->probefunc(pr, mag);
+ if (rc < 0) {
+ /* reset after error */
+ reset_partlist(blkid_probe_get_partlist(pr));
+ if (chn && !chn->binary)
+ blkid_probe_chain_reset_values(pr, chn);
+ DBG(LOWPROBE, ul_debug("%s probefunc failed, rc %d",
+ id->name, rc));
+ }
+ if (rc == BLKID_PROBE_OK && mag && chn && !chn->binary)
+ rc = blkid_probe_set_magic(pr, off, mag->len,
+ (const unsigned char *) mag->magic);
+
+ DBG(LOWPROBE, ul_debug("%s: <--- (rc = %d)", id->name, rc));
+ }
+
+ return rc;
+
+nothing:
+ return BLKID_PROBE_NONE;
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int partitions_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+ int rc = BLKID_PROBE_NONE;
+ size_t i;
+
+ if (!pr || chn->idx < -1)
+ return -EINVAL;
+
+ blkid_probe_chain_reset_values(pr, chn);
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return BLKID_PROBE_NONE;
+
+ if (chn->binary)
+ partitions_init_data(chn);
+
+ if (!pr->wipe_size && (pr->prob_flags & BLKID_PROBE_FL_IGNORE_PT))
+ goto details_only;
+
+ DBG(LOWPROBE, ul_debug("--> starting probing loop [PARTS idx=%d]",
+ chn->idx));
+
+ i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+ for ( ; i < ARRAY_SIZE(idinfos); i++) {
+ const char *name;
+
+ chn->idx = i;
+
+ /* apply filter */
+ if (chn->fltr && blkid_bmp_get_item(chn->fltr, i))
+ continue;
+
+ /* apply checks from idinfo */
+ rc = idinfo_probe(pr, idinfos[i], chn);
+ if (rc < 0)
+ break;
+ if (rc != BLKID_PROBE_OK)
+ continue;
+
+ name = idinfos[i]->name;
+
+ if (!chn->binary)
+ /*
+ * Non-binary interface, set generic variables. Note
+ * that the another variables could be set in prober
+ * functions.
+ */
+ blkid_probe_set_value(pr, "PTTYPE",
+ (const unsigned char *) name,
+ strlen(name) + 1);
+
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [PARTS idx=%d]",
+ name, chn->idx));
+ rc = BLKID_PROBE_OK;
+ break;
+ }
+
+ if (rc != BLKID_PROBE_OK) {
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed=%d) [PARTS idx=%d]",
+ rc, chn->idx));
+ }
+
+details_only:
+ /*
+ * Gather PART_ENTRY_* values if the current device is a partition.
+ */
+ if ((rc == BLKID_PROBE_OK || rc == BLKID_PROBE_NONE) && !chn->binary &&
+ (blkid_partitions_get_flags(pr) & BLKID_PARTS_ENTRY_DETAILS)) {
+
+ int xrc = blkid_partitions_probe_partition(pr);
+
+ /* partition entry probing is optional, and "not-found" from
+ * this sub-probing must not to overwrite previous success. */
+ if (xrc < 0)
+ rc = xrc; /* always propagate errors */
+ else if (rc == BLKID_PROBE_NONE)
+ rc = xrc;
+ }
+
+ DBG(LOWPROBE, ul_debug("partitions probe done [rc=%d]", rc));
+ return rc;
+}
+
+/* Probe for nested partition table within the parental partition */
+int blkid_partitions_do_subprobe(blkid_probe pr, blkid_partition parent,
+ const struct blkid_idinfo *id)
+{
+ blkid_probe prc;
+ int rc;
+ blkid_partlist ls;
+ uint64_t sz, off;
+
+ DBG(LOWPROBE, ul_debug(
+ "parts: ----> %s subprobe requested)",
+ id->name));
+
+ if (!pr || !parent || !parent->size)
+ return -EINVAL;
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return BLKID_PROBE_NONE;
+
+ /* range defined by parent */
+ sz = parent->size << 9;
+ off = parent->start << 9;
+
+ if (off < pr->off || pr->off + pr->size < off + sz) {
+ DBG(LOWPROBE, ul_debug(
+ "ERROR: parts: <---- '%s' subprobe: overflow detected.",
+ id->name));
+ return -ENOSPC;
+ }
+
+ /* create private prober */
+ prc = blkid_clone_probe(pr);
+ if (!prc)
+ return -ENOMEM;
+
+ blkid_probe_set_dimension(prc, off, sz);
+
+ /* clone is always with reset chain, fix it */
+ prc->cur_chain = blkid_probe_get_chain(pr);
+
+ /*
+ * Set 'parent' to the current list of the partitions and use the list
+ * in cloned prober (so the cloned prober will extend the current list
+ * of partitions rather than create a new).
+ */
+ ls = blkid_probe_get_partlist(pr);
+ blkid_partlist_set_parent(ls, parent);
+
+ blkid_probe_set_partlist(prc, ls);
+
+ rc = idinfo_probe(prc, id, blkid_probe_get_chain(pr));
+
+ blkid_probe_set_partlist(prc, NULL);
+ blkid_partlist_set_parent(ls, NULL);
+
+ blkid_free_probe(prc); /* free cloned prober */
+
+ DBG(LOWPROBE, ul_debug(
+ "parts: <---- %s subprobe done (rc=%d)",
+ id->name, rc));
+
+ return rc;
+}
+
+static int blkid_partitions_probe_partition(blkid_probe pr)
+{
+ blkid_probe disk_pr = NULL;
+ blkid_partlist ls;
+ blkid_partition par;
+ dev_t devno;
+
+ DBG(LOWPROBE, ul_debug("parts: start probing for partition entry"));
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ goto nothing;
+
+ devno = blkid_probe_get_devno(pr);
+ if (!devno)
+ goto nothing;
+
+ disk_pr = blkid_probe_get_wholedisk_probe(pr);
+ if (!disk_pr)
+ goto nothing;
+
+ /* parse PT */
+ ls = blkid_probe_get_partitions(disk_pr);
+ if (!ls)
+ goto nothing;
+
+ par = blkid_partlist_devno_to_partition(ls, devno);
+ if (!par)
+ goto nothing;
+ else {
+ const char *v;
+ blkid_parttable tab = blkid_partition_get_table(par);
+ dev_t disk = blkid_probe_get_devno(disk_pr);
+
+ if (tab) {
+ v = blkid_parttable_get_type(tab);
+ if (v)
+ blkid_probe_set_value(pr, "PART_ENTRY_SCHEME",
+ (const unsigned char *) v, strlen(v) + 1);
+ }
+
+ v = blkid_partition_get_name(par);
+ if (v)
+ blkid_probe_set_value(pr, "PART_ENTRY_NAME",
+ (const unsigned char *) v, strlen(v) + 1);
+
+ v = blkid_partition_get_uuid(par);
+ if (v)
+ blkid_probe_set_value(pr, "PART_ENTRY_UUID",
+ (const unsigned char *) v, strlen(v) + 1);
+
+ /* type */
+ v = blkid_partition_get_type_string(par);
+ if (v)
+ blkid_probe_set_value(pr, "PART_ENTRY_TYPE",
+ (const unsigned char *) v, strlen(v) + 1);
+ else
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_TYPE",
+ "0x%x", blkid_partition_get_type(par));
+
+ if (blkid_partition_get_flags(par))
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_FLAGS",
+ "0x%llx", blkid_partition_get_flags(par));
+
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_NUMBER",
+ "%d", blkid_partition_get_partno(par));
+
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_OFFSET", "%jd",
+ (intmax_t)blkid_partition_get_start(par));
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_SIZE", "%jd",
+ (intmax_t)blkid_partition_get_size(par));
+
+ blkid_probe_sprintf_value(pr, "PART_ENTRY_DISK", "%u:%u",
+ major(disk), minor(disk));
+ }
+
+ DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [success]"));
+ return BLKID_PROBE_OK;
+
+nothing:
+ DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [nothing]"));
+ return BLKID_PROBE_NONE;
+
+
+}
+
+/*
+ * Returns 1 if the device is whole-disk and the area specified by @offset and
+ * @size is covered by any partition.
+ */
+int blkid_probe_is_covered_by_pt(blkid_probe pr,
+ uint64_t offset, uint64_t size)
+{
+ blkid_probe prc = NULL;
+ blkid_partlist ls = NULL;
+ uint64_t start, end;
+ int nparts, i, rc = 0;
+
+ DBG(LOWPROBE, ul_debug(
+ "=> checking if off=%"PRIu64" size=%"PRIu64" covered by PT",
+ offset, size));
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ goto done;
+
+ prc = blkid_clone_probe(pr);
+ if (!prc)
+ goto done;
+
+ ls = blkid_probe_get_partitions(prc);
+ if (!ls)
+ goto done;
+
+ nparts = blkid_partlist_numof_partitions(ls);
+ if (!nparts)
+ goto done;
+
+ end = (offset + size) >> 9;
+ start = offset >> 9;
+
+ /* check if the partition table fits into the device */
+ for (i = 0; i < nparts; i++) {
+ blkid_partition par = &ls->parts[i];
+
+ if (par->start + par->size > (pr->size >> 9)) {
+ DBG(LOWPROBE, ul_debug("partition #%d overflows "
+ "device (off=%" PRId64 " size=%" PRId64 ")",
+ par->partno, par->start, par->size));
+ goto done;
+ }
+ }
+
+ /* check if the requested area is covered by PT */
+ for (i = 0; i < nparts; i++) {
+ blkid_partition par = &ls->parts[i];
+
+ if (start >= par->start && end <= par->start + par->size) {
+ rc = 1;
+ break;
+ }
+ }
+done:
+ blkid_free_probe(prc);
+
+ DBG(LOWPROBE, ul_debug("<= %s covered by PT", rc ? "IS" : "NOT"));
+ return rc;
+}
+
+/**
+ * blkid_known_pttype:
+ * @pttype: partition name
+ *
+ * Returns: 1 for known or 0 for unknown partition type.
+ */
+int blkid_known_pttype(const char *pttype)
+{
+ size_t i;
+
+ if (!pttype)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
+ const struct blkid_idinfo *id = idinfos[i];
+ if (strcmp(id->name, pttype) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * blkid_partitions_get_name:
+ * @idx: number >= 0
+ * @name: returns name of a supported partition
+ *
+ * Since: 2.30
+ *
+ * Returns: -1 if @idx is out of range, or 0 on success.
+ */
+int blkid_partitions_get_name(const size_t idx, const char **name)
+{
+ if (idx < ARRAY_SIZE(idinfos)) {
+ *name = idinfos[idx]->name;
+ return 0;
+ }
+ return -1;
+}
+
+/**
+ * blkid_partlist_numof_partitions:
+ * @ls: partitions list
+ *
+ * Returns: number of partitions in the list or -1 in case of error.
+ */
+int blkid_partlist_numof_partitions(blkid_partlist ls)
+{
+ return ls->nparts;
+}
+
+/**
+ * blkid_partlist_get_table:
+ * @ls: partitions list
+ *
+ * Returns: top-level partition table or NULL if there is not a partition table
+ * on the device.
+ */
+blkid_parttable blkid_partlist_get_table(blkid_partlist ls)
+{
+ if (list_empty(&ls->l_tabs))
+ return NULL;
+
+ return list_entry(ls->l_tabs.next,
+ struct blkid_struct_parttable, t_tabs);
+}
+
+
+/**
+ * blkid_partlist_get_partition:
+ * @ls: partitions list
+ * @n: partition number in range 0..N, where 'N' is blkid_partlist_numof_partitions().
+ *
+ * It's possible that the list of partitions is *empty*, but there is a valid
+ * partition table on the disk. This happen when on-disk details about
+ * partitions are unknown or the partition table is empty.
+ *
+ * See also blkid_partlist_get_table().
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n)
+{
+ if (n < 0 || n >= ls->nparts)
+ return NULL;
+
+ return &ls->parts[n];
+}
+
+blkid_partition blkid_partlist_get_partition_by_start(blkid_partlist ls, uint64_t start)
+{
+ int i, nparts;
+ blkid_partition par;
+
+ nparts = blkid_partlist_numof_partitions(ls);
+ for (i = 0; i < nparts; i++) {
+ par = blkid_partlist_get_partition(ls, i);
+ if ((uint64_t) blkid_partition_get_start(par) == start)
+ return par;
+ }
+ return NULL;
+}
+
+/**
+ * blkid_partlist_get_partition_by_partno
+ * @ls: partitions list
+ * @n: the partition number (e.g. 'N' from sda'N')
+ *
+ * This does not assume any order of the input blkid_partlist. And correctly
+ * handles "out of order" partition tables. partition N is located after
+ * partition N+1 on the disk.
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n)
+{
+ int i, nparts;
+ blkid_partition par;
+
+ nparts = blkid_partlist_numof_partitions(ls);
+ for (i = 0; i < nparts; i++) {
+ par = blkid_partlist_get_partition(ls, i);
+ if (n == blkid_partition_get_partno(par))
+ return par;
+ }
+ return NULL;
+}
+
+
+/**
+ * blkid_partlist_devno_to_partition:
+ * @ls: partitions list
+ * @devno: requested partition
+ *
+ * This function tries to get start and size for @devno from sysfs and
+ * returns a partition from @ls which matches with the values from sysfs.
+ *
+ * This function is necessary when you want to make a relation between an entry
+ * in the partition table (@ls) and block devices in your system.
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno)
+{
+ struct path_cxt *pc;
+ uint64_t start = 0, size;
+ int i, rc, partno = 0;
+
+ DBG(LOWPROBE, ul_debug("trying to convert devno 0x%llx to partition",
+ (long long) devno));
+
+
+ pc = ul_new_sysfs_path(devno, NULL, NULL);
+ if (!pc) {
+ DBG(LOWPROBE, ul_debug("failed t init sysfs context"));
+ return NULL;
+ }
+ rc = ul_path_read_u64(pc, &size, "size");
+ if (!rc) {
+ rc = ul_path_read_u64(pc, &start, "start");
+ if (rc) {
+ /* try to get partition number from DM uuid.
+ */
+ char *uuid = NULL, *tmp, *prefix;
+
+ ul_path_read_string(pc, &uuid, "dm/uuid");
+ tmp = uuid;
+ prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+ if (prefix && strncasecmp(prefix, "part", 4) == 0) {
+ char *end = NULL;
+
+ errno = 0;
+ partno = strtol(prefix + 4, &end, 10);
+ if (errno || prefix == end || (end && *end))
+ partno = 0;
+ else
+ rc = 0; /* success */
+ }
+ free(uuid);
+ }
+ }
+
+ ul_unref_path(pc);
+
+ if (rc)
+ return NULL;
+
+ if (partno) {
+ DBG(LOWPROBE, ul_debug("mapped by DM, using partno %d", partno));
+
+ /*
+ * Partition mapped by kpartx does not provide "start" offset
+ * in /sys, but if we know partno and size of the partition
+ * that we can probably make the relation between the device
+ * and an entry in partition table.
+ */
+ for (i = 0; i < ls->nparts; i++) {
+ blkid_partition par = &ls->parts[i];
+
+ if (partno != blkid_partition_get_partno(par))
+ continue;
+
+ if (size == (uint64_t)blkid_partition_get_size(par) ||
+ (blkid_partition_is_extended(par) && size <= 1024ULL))
+ return par;
+
+ }
+ return NULL;
+ }
+
+ DBG(LOWPROBE, ul_debug("searching by offset/size"));
+
+ for (i = 0; i < ls->nparts; i++) {
+ blkid_partition par = &ls->parts[i];
+
+ if ((uint64_t)blkid_partition_get_start(par) == start &&
+ (uint64_t)blkid_partition_get_size(par) == size)
+ return par;
+
+ /* exception for extended dos partitions */
+ if ((uint64_t)blkid_partition_get_start(par) == start &&
+ blkid_partition_is_extended(par) && size <= 1024ULL)
+ return par;
+
+ }
+
+ DBG(LOWPROBE, ul_debug("not found partition for device"));
+ return NULL;
+}
+
+
+int blkid_parttable_set_uuid(blkid_parttable tab, const unsigned char *id)
+{
+ if (!tab)
+ return -1;
+
+ blkid_unparse_uuid(id, tab->id, sizeof(tab->id));
+ return 0;
+}
+
+int blkid_parttable_set_id(blkid_parttable tab, const unsigned char *id)
+{
+ if (!tab)
+ return -1;
+
+ xstrncpy(tab->id, (const char *) id, sizeof(tab->id));
+ return 0;
+}
+
+/* set PTUUID variable for non-binary API */
+int blkid_partitions_set_ptuuid(blkid_probe pr, unsigned char *uuid)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+
+ if (chn->binary || blkid_uuid_is_empty(uuid, 16))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, "PTUUID");
+ if (!v)
+ return -ENOMEM;
+
+ v->len = UUID_STR_LEN;
+ v->data = calloc(1, v->len);
+ if (v->data) {
+ blkid_unparse_uuid(uuid, (char *) v->data, v->len);
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return -ENOMEM;
+}
+
+/* set PTUUID variable for non-binary API for tables where
+ * the ID is just a string */
+int blkid_partitions_strcpy_ptuuid(blkid_probe pr, char *str)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (chn->binary || !str || !*str)
+ return 0;
+
+ if (!blkid_probe_set_value(pr, "PTUUID", (unsigned char *) str, strlen(str) + 1))
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * blkid_parttable_get_id:
+ * @tab: partition table
+ *
+ * The ID is GPT disk UUID or DOS disk ID (in hex format).
+ *
+ * Returns: partition table ID (for example GPT disk UUID) or NULL
+ */
+const char *blkid_parttable_get_id(blkid_parttable tab)
+{
+ return *tab->id ? tab->id : NULL;
+}
+
+
+int blkid_partition_set_type(blkid_partition par, int type)
+{
+ par->type = type;
+ return 0;
+}
+
+/**
+ * blkid_parttable_get_type:
+ * @tab: partition table
+ *
+ * Returns: partition table type (type name, e.g. "dos", "gpt", ...)
+ */
+const char *blkid_parttable_get_type(blkid_parttable tab)
+{
+ return tab->type;
+}
+
+/**
+ * blkid_parttable_get_parent:
+ * @tab: partition table
+ *
+ * Returns: parent for nested partition tables or NULL.
+ */
+blkid_partition blkid_parttable_get_parent(blkid_parttable tab)
+{
+ return tab->parent;
+}
+
+/**
+ * blkid_parttable_get_offset:
+ * @tab: partition table
+ *
+ * Note the position is relative to begin of the device as defined by
+ * blkid_probe_set_device() for primary partition table, and relative
+ * to parental partition for nested partition tables.
+ *
+ * <informalexample>
+ * <programlisting>
+ * off_t offset;
+ * blkid_partition parent = blkid_parttable_get_parent(tab);
+ *
+ * offset = blkid_parttable_get_offset(tab);
+ *
+ * if (parent)
+ * / * 'tab' is nested partition table * /
+ * offset += blkid_partition_get_start(parent);
+ * </programlisting>
+ * </informalexample>
+
+ * Returns: position (in bytes) of the partition table or -1 in case of error.
+ *
+ */
+blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab)
+{
+ return (blkid_loff_t)tab->offset;
+}
+
+/**
+ * blkid_partition_get_table:
+ * @par: partition
+ *
+ * The "parttable" describes partition table. The table is usually the same for
+ * all partitions -- except nested partition tables.
+ *
+ * For example bsd, solaris, etc. use a nested partition table within
+ * standard primary dos partition:
+ *
+ * <informalexample>
+ * <programlisting>
+ *
+ * -- dos partition table
+ * 0: sda1 dos primary partition
+ * 1: sda2 dos primary partition
+ * -- bsd partition table (with in sda2)
+ * 2: sda5 bds partition
+ * 3: sda6 bds partition
+ *
+ * </programlisting>
+ * </informalexample>
+ *
+ * The library does not to use a separate partition table object for dos logical
+ * partitions (partitions within extended partition). It's possible to
+ * differentiate between logical, extended and primary partitions by
+ *
+ * blkid_partition_is_{extended,primary,logical}().
+ *
+ * Returns: partition table object or NULL in case of error.
+ */
+blkid_parttable blkid_partition_get_table(blkid_partition par)
+{
+ return par->tab;
+}
+
+static int partition_get_logical_type(blkid_partition par)
+{
+ blkid_parttable tab;
+
+ if (!par)
+ return -1;
+
+ tab = blkid_partition_get_table(par);
+ if (!tab || !tab->type)
+ return -1;
+
+ if (tab->parent)
+ return 'L'; /* report nested partitions as logical */
+
+ if (!strcmp(tab->type, "dos")) {
+ if (par->partno > 4)
+ return 'L'; /* logical */
+
+ if(par->type == MBR_DOS_EXTENDED_PARTITION ||
+ par->type == MBR_W95_EXTENDED_PARTITION ||
+ par->type == MBR_LINUX_EXTENDED_PARTITION)
+ return 'E';
+ }
+ return 'P';
+}
+
+/**
+ * blkid_partition_is_primary:
+ * @par: partition
+ *
+ * Note, this function returns FALSE for DOS extended partitions and
+ * all partitions in nested partition tables.
+ *
+ * Returns: 1 if the partitions is primary partition or 0 if not.
+ */
+int blkid_partition_is_primary(blkid_partition par)
+{
+ return partition_get_logical_type(par) == 'P' ? TRUE : FALSE;
+}
+
+/**
+ * blkid_partition_is_extended:
+ * @par: partition
+ *
+ * Returns: 1 if the partitions is extended (dos, windows or linux)
+ * partition or 0 if not.
+ */
+int blkid_partition_is_extended(blkid_partition par)
+{
+ return partition_get_logical_type(par) == 'E' ? TRUE : FALSE;
+}
+
+/**
+ * blkid_partition_is_logical:
+ * @par: partition
+ *
+ * Note that this function returns TRUE for all partitions in all
+ * nested partition tables (e.g. BSD labels).
+ *
+ * Returns: 1 if the partitions is logical partition or 0 if not.
+ */
+int blkid_partition_is_logical(blkid_partition par)
+{
+ return partition_get_logical_type(par) == 'L' ? TRUE : FALSE;
+}
+
+static void set_string(unsigned char *item, size_t max,
+ const unsigned char *data, size_t len)
+{
+ if (len >= max)
+ len = max - 1;
+
+ memcpy(item, data, len);
+ item[len] = '\0';
+
+ blkid_rtrim_whitespace(item);
+}
+
+int blkid_partition_set_name(blkid_partition par,
+ const unsigned char *name, size_t len)
+{
+ if (!par)
+ return -1;
+
+ set_string(par->name, sizeof(par->name), name, len);
+ return 0;
+}
+
+int blkid_partition_set_utf8name(blkid_partition par, const unsigned char *name,
+ size_t len, int enc)
+{
+ if (!par)
+ return -1;
+
+ ul_encode_to_utf8(enc, par->name, sizeof(par->name), name, len);
+ blkid_rtrim_whitespace(par->name);
+ return 0;
+}
+
+int blkid_partition_set_uuid(blkid_partition par, const unsigned char *uuid)
+{
+ if (!par)
+ return -1;
+
+ blkid_unparse_uuid(uuid, par->uuid, sizeof(par->uuid));
+ return 0;
+}
+
+int blkid_partition_gen_uuid(blkid_partition par)
+{
+ if (!par || !par->tab || !*par->tab->id)
+ return -1;
+
+ snprintf(par->uuid, sizeof(par->uuid), "%.33s-%02x",
+ par->tab->id, par->partno);
+ return 0;
+}
+
+/**
+ * blkid_partition_get_name:
+ * @par: partition
+ *
+ * Returns: partition name string if supported by PT (e.g. Mac) or NULL.
+ */
+const char *blkid_partition_get_name(blkid_partition par)
+{
+ return *par->name ? (char *) par->name : NULL;
+}
+
+/**
+ * blkid_partition_get_uuid:
+ * @par: partition
+ *
+ * Returns: partition UUID string if supported by PT (e.g. GPT) or NULL.
+ */
+const char *blkid_partition_get_uuid(blkid_partition par)
+{
+ return *par->uuid ? par->uuid : NULL;
+}
+
+/**
+ * blkid_partition_get_partno:
+ * @par: partition
+ *
+ * Returns: proposed partition number (e.g. 'N' from sda'N') or -1 in case of
+ * error. Note that the number is generated by library independently of your OS.
+ */
+int blkid_partition_get_partno(blkid_partition par)
+{
+ return par->partno;
+}
+
+/**
+ * blkid_partition_get_start:
+ * @par: partition
+ *
+ * Be careful if you _not_ probe whole disk:
+ *
+ * 1) the offset is usually relative to begin of the disk -- but if you probe a
+ * fragment of the disk only -- then the offset could be still relative to
+ * the begin of the disk rather that relative to the fragment.
+ *
+ * 2) the offset for nested partitions could be relative to parent (e.g. Solaris)
+ * _or_ relative to the begin of the whole disk (e.g. bsd).
+ *
+ * You don't have to care about such details if you probe whole disk. In such
+ * a case libblkid always returns the offset relative to the begin of the disk.
+ *
+ * Returns: start of the partition (in 512-sectors).
+ */
+blkid_loff_t blkid_partition_get_start(blkid_partition par)
+{
+ return (blkid_loff_t)par->start;
+}
+
+/**
+ * blkid_partition_get_size:
+ * @par: partition
+ *
+ * WARNING: be very careful when you work with MS-DOS extended partitions. The
+ * library always returns full size of the partition. If you want to
+ * add the partition to the Linux system (BLKPG_ADD_PARTITION ioctl)
+ * you need to reduce the size of the partition to 1 or 2 blocks. The
+ * rest of the partition has to be inaccessible for mkfs or mkswap
+ * programs, we need a small space for boot loaders only.
+ *
+ * For some unknown reason this (safe) practice is not to used for
+ * nested BSD, Solaris, ..., partition tables in Linux kernel.
+ *
+ * Returns: size of the partition (in 512-sectors).
+ */
+blkid_loff_t blkid_partition_get_size(blkid_partition par)
+{
+ return (blkid_loff_t)par->size;
+}
+
+/**
+ * blkid_partition_get_type:
+ * @par: partition
+ *
+ * Returns: partition type.
+ */
+int blkid_partition_get_type(blkid_partition par)
+{
+ return par->type;
+}
+
+/* Sets partition 'type' for PT where the type is defined by string rather
+ * than by number
+ */
+int blkid_partition_set_type_string(blkid_partition par,
+ const unsigned char *type, size_t len)
+{
+ set_string((unsigned char *) par->typestr,
+ sizeof(par->typestr), type, len);
+ return 0;
+}
+
+/* Sets partition 'type' for PT where the type is defined by UUID rather
+ * than by number
+ */
+int blkid_partition_set_type_uuid(blkid_partition par, const unsigned char *uuid)
+{
+ blkid_unparse_uuid(uuid, par->typestr, sizeof(par->typestr));
+ return 0;
+}
+
+/**
+ * blkid_partition_get_type_string:
+ * @par: partition
+ *
+ * The type string is supported by a small subset of partition tables (e.g. Mac
+ * and EFI GPT). Note that GPT uses type UUID and this function returns this
+ * UUID as string.
+ *
+ * Returns: partition type string or NULL.
+ */
+const char *blkid_partition_get_type_string(blkid_partition par)
+{
+ return *par->typestr ? par->typestr : NULL;
+}
+
+
+int blkid_partition_set_flags(blkid_partition par, unsigned long long flags)
+{
+ par->flags = flags;
+ return 0;
+}
+
+/**
+ * blkid_partition_get_flags
+ * @par: partition
+ *
+ * Returns: partition flags (or attributes for gpt).
+ */
+unsigned long long blkid_partition_get_flags(blkid_partition par)
+{
+ return par->flags;
+}
+
diff --git a/libblkid/src/partitions/partitions.h b/libblkid/src/partitions/partitions.h
new file mode 100644
index 0000000..4a718f4
--- /dev/null
+++ b/libblkid/src/partitions/partitions.h
@@ -0,0 +1,74 @@
+#ifndef BLKID_PARTITIONS_H
+#define BLKID_PARTITIONS_H
+
+#include "blkidP.h"
+#include "pt-mbr.h"
+
+extern int blkid_partitions_get_flags(blkid_probe pr);
+
+extern blkid_parttable blkid_partlist_new_parttable(blkid_partlist ls,
+ const char *type, uint64_t offset);
+
+extern int blkid_parttable_set_uuid(blkid_parttable tab, const unsigned char *id);
+extern int blkid_parttable_set_id(blkid_parttable tab, const unsigned char *id);
+
+extern blkid_partition blkid_partlist_add_partition(blkid_partlist ls,
+ blkid_parttable tab,
+ uint64_t start, uint64_t size);
+
+extern int blkid_partlist_set_partno(blkid_partlist ls, int partno);
+extern int blkid_partlist_increment_partno(blkid_partlist ls);
+
+extern blkid_partition blkid_partlist_get_parent(blkid_partlist ls);
+
+extern blkid_partition blkid_partlist_get_partition_by_start(blkid_partlist ls, uint64_t start);
+
+extern int blkid_partitions_do_subprobe(blkid_probe pr,
+ blkid_partition parent, const struct blkid_idinfo *id);
+
+extern int blkid_partitions_need_typeonly(blkid_probe pr);
+extern int blkid_partitions_set_ptuuid(blkid_probe pr, unsigned char *uuid);
+extern int blkid_partitions_strcpy_ptuuid(blkid_probe pr, char *str);
+
+
+extern int blkid_is_nested_dimension(blkid_partition par,
+ uint64_t start, uint64_t size);
+
+extern int blkid_partition_set_name(blkid_partition par,
+ const unsigned char *name, size_t len);
+
+extern int blkid_partition_set_utf8name(blkid_partition par,
+ const unsigned char *name, size_t len, int enc);
+
+extern int blkid_partition_set_uuid(blkid_partition par,
+ const unsigned char *uuid);
+extern int blkid_partition_gen_uuid(blkid_partition par);
+
+extern int blkid_partition_set_type(blkid_partition par, int type);
+
+extern int blkid_partition_set_type_string(blkid_partition par,
+ const unsigned char *type, size_t len);
+
+extern int blkid_partition_set_type_uuid(blkid_partition par,
+ const unsigned char *uuid);
+
+extern int blkid_partition_set_flags(blkid_partition par, unsigned long long flags);
+
+/*
+ * partition probers
+ */
+extern const struct blkid_idinfo aix_pt_idinfo;
+extern const struct blkid_idinfo bsd_pt_idinfo;
+extern const struct blkid_idinfo unixware_pt_idinfo;
+extern const struct blkid_idinfo solaris_x86_pt_idinfo;
+extern const struct blkid_idinfo sun_pt_idinfo;
+extern const struct blkid_idinfo sgi_pt_idinfo;
+extern const struct blkid_idinfo mac_pt_idinfo;
+extern const struct blkid_idinfo dos_pt_idinfo;
+extern const struct blkid_idinfo minix_pt_idinfo;
+extern const struct blkid_idinfo gpt_pt_idinfo;
+extern const struct blkid_idinfo pmbr_pt_idinfo;
+extern const struct blkid_idinfo ultrix_pt_idinfo;
+extern const struct blkid_idinfo atari_pt_idinfo;
+
+#endif /* BLKID_PARTITIONS_H */
diff --git a/libblkid/src/partitions/sgi.c b/libblkid/src/partitions/sgi.c
new file mode 100644
index 0000000..99c0bf1
--- /dev/null
+++ b/libblkid/src/partitions/sgi.c
@@ -0,0 +1,87 @@
+/*
+ * sgi partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "pt-sgi.h"
+
+static int probe_sgi_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct sgi_disklabel *l;
+ struct sgi_partition *p;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ int i;
+
+ l = (struct sgi_disklabel *) blkid_probe_get_sector(pr, 0);
+ if (!l) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ if (sgi_pt_checksum(l)) {
+ DBG(LOWPROBE, ul_debug(
+ "detected corrupted sgi disk label -- ignore"));
+ goto nothing;
+ }
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "sgi", 0);
+ if (!tab)
+ goto err;
+
+ for(i = 0, p = &l->partitions[0]; i < SGI_MAXPARTITIONS; i++, p++) {
+ uint32_t size = be32_to_cpu(p->num_blocks);
+ uint32_t start = be32_to_cpu(p->first_block);
+ uint32_t type = be32_to_cpu(p->type);
+ blkid_partition par;
+
+ if (!size) {
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_type(par, type);
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+const struct blkid_idinfo sgi_pt_idinfo =
+{
+ .name = "sgi",
+ .probefunc = probe_sgi_pt,
+ .magics =
+ {
+ { .magic = "\x0B\xE5\xA9\x41", .len = 4 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/solaris_x86.c b/libblkid/src/partitions/solaris_x86.c
new file mode 100644
index 0000000..df1def5
--- /dev/null
+++ b/libblkid/src/partitions/solaris_x86.c
@@ -0,0 +1,154 @@
+/*
+ * Solaris x86 partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+/*
+ * Solaris-x86 is always within primary dos partition (nested PT table). The
+ * solaris-x86 vtoc can be used to split the entire partition to "slices". The
+ * offset (start) of the slice is always relatively to the primary dos
+ * partition.
+ *
+ * Note that Solaris-SPARC uses entire disk with a different partitioning
+ * scheme.
+ */
+
+/* some other implementation than Linux kernel assume 8 partitions only */
+#define SOLARIS_MAXPARTITIONS 16
+
+/* disklabel (vtoc) location */
+#define SOLARIS_SECTOR 1 /* in 512-sectors */
+#define SOLARIS_OFFSET (SOLARIS_SECTOR << 9) /* in bytes */
+#define SOLARIS_MAGICOFFSET (SOLARIS_OFFSET + 12) /* v_sanity offset in bytes */
+
+/* slice tags */
+#define SOLARIS_TAG_WHOLEDISK 5
+
+struct solaris_slice {
+ uint16_t s_tag; /* ID tag of partition */
+ uint16_t s_flag; /* permission flags */
+ uint32_t s_start; /* start sector no of partition */
+ uint32_t s_size; /* # of blocks in partition */
+} __attribute__((packed));
+
+struct solaris_vtoc {
+ unsigned int v_bootinfo[3]; /* info needed by mboot (unsupported) */
+
+ uint32_t v_sanity; /* to verify vtoc sanity */
+ uint32_t v_version; /* layout version */
+ char v_volume[8]; /* volume name */
+ uint16_t v_sectorsz; /* sector size in bytes */
+ uint16_t v_nparts; /* number of partitions */
+ unsigned int v_reserved[10]; /* free space */
+
+ struct solaris_slice v_slice[SOLARIS_MAXPARTITIONS]; /* slices */
+
+ unsigned int timestamp[SOLARIS_MAXPARTITIONS]; /* timestamp (unsupported) */
+ char v_asciilabel[128]; /* for compatibility */
+} __attribute__((packed));
+
+static int probe_solaris_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct solaris_vtoc *l; /* disk label */
+ struct solaris_slice *p; /* partition */
+ blkid_parttable tab = NULL;
+ blkid_partition parent;
+ blkid_partlist ls;
+ int i;
+ uint16_t nparts;
+
+ l = (struct solaris_vtoc *) blkid_probe_get_sector(pr, SOLARIS_SECTOR);
+ if (!l) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ if (le32_to_cpu(l->v_version) != 1) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: unsupported solaris x86 version %d, ignore",
+ le32_to_cpu(l->v_version)));
+ goto nothing;
+ }
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ parent = blkid_partlist_get_parent(ls);
+
+ tab = blkid_partlist_new_parttable(ls, "solaris", SOLARIS_OFFSET);
+ if (!tab)
+ goto err;
+
+ nparts = le16_to_cpu(l->v_nparts);
+ if (nparts > SOLARIS_MAXPARTITIONS)
+ nparts = SOLARIS_MAXPARTITIONS;
+
+ for (i = 1, p = &l->v_slice[0]; i < nparts; i++, p++) {
+
+ uint32_t start = le32_to_cpu(p->s_start);
+ uint32_t size = le32_to_cpu(p->s_size);
+ blkid_partition par;
+
+ if (size == 0 || le16_to_cpu(p->s_tag) == SOLARIS_TAG_WHOLEDISK)
+ continue;
+
+ if (parent)
+ /* Solaris slices are relative to the parent (primary
+ * DOS partition) */
+ start += blkid_partition_get_start(parent);
+
+ if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: solaris partition (%d) overflow "
+ "detected, ignore", i));
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_type(par, le16_to_cpu(p->s_tag));
+ blkid_partition_set_flags(par, le16_to_cpu(p->s_flag));
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+const struct blkid_idinfo solaris_x86_pt_idinfo =
+{
+ .name = "solaris",
+ .probefunc = probe_solaris_pt,
+ .magics =
+ {
+ {
+ .magic = "\xEE\xDE\x0D\x60", /* little-endian magic string */
+ .len = 4, /* v_sanity size in bytes */
+ .sboff = SOLARIS_MAGICOFFSET /* offset of v_sanity */
+ },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/sun.c b/libblkid/src/partitions/sun.c
new file mode 100644
index 0000000..058a663
--- /dev/null
+++ b/libblkid/src/partitions/sun.c
@@ -0,0 +1,125 @@
+/*
+ * sun (solaris-sparc) partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "pt-sun.h"
+#include "partitions.h"
+
+static int probe_sun_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct sun_disklabel *l;
+ struct sun_partition *p;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ uint16_t nparts;
+ uint64_t spc;
+ int i, use_vtoc;
+
+ l = (struct sun_disklabel *) blkid_probe_get_sector(pr, 0);
+ if (!l) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ if (sun_pt_checksum(l)) {
+ DBG(LOWPROBE, ul_debug(
+ "detected corrupted sun disk label -- ignore"));
+ goto nothing;
+ }
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "sun", 0);
+ if (!tab)
+ goto err;
+
+ /* sectors per cylinder (partition offset is in cylinders...) */
+ spc = (uint64_t) be16_to_cpu(l->nhead) * be16_to_cpu(l->nsect);
+
+ DBG(LOWPROBE, ul_debug("Sun VTOC sanity=%u version=%u nparts=%u",
+ be32_to_cpu(l->vtoc.sanity),
+ be32_to_cpu(l->vtoc.version),
+ be16_to_cpu(l->vtoc.nparts)));
+
+ /* Check to see if we can use the VTOC table */
+ use_vtoc = ((be32_to_cpu(l->vtoc.sanity) == SUN_VTOC_SANITY) &&
+ (be32_to_cpu(l->vtoc.version) == SUN_VTOC_VERSION) &&
+ (be16_to_cpu(l->vtoc.nparts) <= SUN_MAXPARTITIONS));
+
+ /* Use 8 partition entries if not specified in validated VTOC */
+ nparts = use_vtoc ? be16_to_cpu(l->vtoc.nparts) : SUN_MAXPARTITIONS;
+
+ /*
+ * So that old Linux-Sun partitions continue to work,
+ * allow the VTOC to be used under the additional condition ...
+ */
+ use_vtoc = use_vtoc || !(l->vtoc.sanity || l->vtoc.version || l->vtoc.nparts);
+
+ for (i = 0, p = l->partitions; i < nparts; i++, p++) {
+
+ uint64_t start, size;
+ uint16_t type = 0, flags = 0;
+ blkid_partition par;
+
+ start = be32_to_cpu(p->start_cylinder) * spc;
+ size = be32_to_cpu(p->num_sectors);
+ if (use_vtoc) {
+ type = be16_to_cpu(l->vtoc.infos[i].id);
+ flags = be16_to_cpu(l->vtoc.infos[i].flags);
+ }
+
+ if (type == SUN_TAG_WHOLEDISK || !size) {
+ blkid_partlist_increment_partno(ls);
+ continue;
+ }
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ if (type)
+ blkid_partition_set_type(par, type);
+ if (flags)
+ blkid_partition_set_flags(par, flags);
+ }
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+
+const struct blkid_idinfo sun_pt_idinfo =
+{
+ .name = "sun",
+ .probefunc = probe_sun_pt,
+ .magics =
+ {
+ {
+ .magic = "\xDA\xBE", /* big-endian magic string */
+ .len = 2,
+ .sboff = offsetof(struct sun_disklabel, magic)
+ },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/partitions/ultrix.c b/libblkid/src/partitions/ultrix.c
new file mode 100644
index 0000000..9c060be
--- /dev/null
+++ b/libblkid/src/partitions/ultrix.c
@@ -0,0 +1,99 @@
+/*
+ * uktrix partition parsing code
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+#define ULTRIX_MAXPARTITIONS 8
+
+#define ULTRIX_MAGIC 0x032957
+#define ULTRIX_MAGIC_STR "\x02\x29\x57"
+
+/* sector with partition table */
+#define ULTRIX_SECTOR ((16384 - sizeof(struct ultrix_disklabel)) >> 9)
+/* position of partition table within ULTRIX_SECTOR */
+#define ULTRIX_OFFSET (512 - sizeof(struct ultrix_disklabel))
+
+struct ultrix_disklabel {
+ int32_t pt_magic; /* magic no. indicating part. info exits */
+ int32_t pt_valid; /* set by driver if pt is current */
+ struct pt_info {
+ int32_t pi_nblocks; /* no. of sectors */
+ uint32_t pi_blkoff; /* block offset for start */
+ } pt_part[ULTRIX_MAXPARTITIONS];
+} __attribute__((packed));
+
+
+static int probe_ultrix_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ unsigned char *data;
+ struct ultrix_disklabel *l;
+ blkid_parttable tab = NULL;
+ blkid_partlist ls;
+ int i;
+
+ data = blkid_probe_get_sector(pr, ULTRIX_SECTOR);
+ if (!data) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ l = (struct ultrix_disklabel *) (data + ULTRIX_OFFSET);
+
+ if (l->pt_magic != ULTRIX_MAGIC || l->pt_valid != 1)
+ goto nothing;
+
+ if (blkid_probe_set_magic(pr, (ULTRIX_SECTOR << 9) + ULTRIX_OFFSET,
+ sizeof(ULTRIX_MAGIC_STR) - 1,
+ (unsigned char *) ULTRIX_MAGIC_STR))
+ goto err;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ tab = blkid_partlist_new_parttable(ls, "ultrix", 0);
+ if (!tab)
+ goto err;
+
+ for (i = 0; i < ULTRIX_MAXPARTITIONS; i++) {
+ if (!l->pt_part[i].pi_nblocks)
+ blkid_partlist_increment_partno(ls);
+ else {
+ if (!blkid_partlist_add_partition(ls, tab,
+ l->pt_part[i].pi_blkoff,
+ l->pt_part[i].pi_nblocks))
+ goto err;
+ }
+ }
+
+ return BLKID_PROBE_OK;
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+const struct blkid_idinfo ultrix_pt_idinfo =
+{
+ .name = "ultrix",
+ .probefunc = probe_ultrix_pt,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/partitions/unixware.c b/libblkid/src/partitions/unixware.c
new file mode 100644
index 0000000..845924e
--- /dev/null
+++ b/libblkid/src/partitions/unixware.c
@@ -0,0 +1,197 @@
+/*
+ * unixware partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ *
+ * The interesting information about unixware PT:
+ * - Linux kernel / partx
+ * - vtoc(7) SCO UNIX command man page
+ * - evms source code (http://evms.sourceforge.net/)
+ * - vxtools source code (http://martin.hinner.info/fs/vxfs/)
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+/* disklabel location */
+#define UNIXWARE_SECTOR 29
+#define UNIXWARE_OFFSET (UNIXWARE_SECTOR << 9) /* offset in bytes */
+#define UNIXWARE_KBOFFSET (UNIXWARE_OFFSET >> 10) /* offset in 1024-blocks */
+
+/* disklabel->d_magic offset within the last 1024 block */
+#define UNIXWARE_MAGICOFFSET (UNIXWARE_OFFSET - UNIXWARE_KBOFFSET + 4)
+
+#define UNIXWARE_VTOCMAGIC 0x600DDEEEUL
+#define UNIXWARE_MAXPARTITIONS 16
+
+/* unixware_partition->s_label flags */
+#define UNIXWARE_TAG_UNUSED 0x0000 /* unused partition */
+#define UNIXWARE_TAG_BOOT 0x0001 /* boot fs */
+#define UNIXWARE_TAG_ROOT 0x0002 /* root fs */
+#define UNIXWARE_TAG_SWAP 0x0003 /* swap fs */
+#define UNIXWARE_TAG_USER 0x0004 /* user fs */
+#define UNIXWARE_TAG_ENTIRE_DISK 0x0005 /* whole disk */
+#define UNIXWARE_TAG_ALT_S 0x0006 /* alternate sector space */
+#define UNIXWARE_TAG_OTHER 0x0007 /* non unix */
+#define UNIXWARE_TAG_ALT_T 0x0008 /* alternate track space */
+#define UNIXWARE_TAG_STAND 0x0009 /* stand partition */
+#define UNIXWARE_TAG_VAR 0x000a /* var partition */
+#define UNIXWARE_TAG_HOME 0x000b /* home partition */
+#define UNIXWARE_TAG_DUMP 0x000c /* dump partition */
+#define UNIXWARE_TAG_ALT_ST 0x000d /* alternate sector track */
+#define UNIXWARE_TAG_VM_PUBLIC 0x000e /* volume mgt public partition */
+#define UNIXWARE_TAG_VM_PRIVATE 0x000f /* volume mgt private partition */
+
+
+/* unixware_partition->s_flags flags */
+#define UNIXWARE_FLAG_VALID 0x0200
+
+struct unixware_partition {
+ uint16_t s_label; /* partition label (tag) */
+ uint16_t s_flags; /* permission flags */
+ uint32_t start_sect; /* starting sector */
+ uint32_t nr_sects; /* number of sectors */
+} __attribute__((packed));
+
+struct unixware_disklabel {
+ uint32_t d_type; /* drive type */
+ uint32_t d_magic; /* the magic number */
+ uint32_t d_version; /* version number */
+ char d_serial[12]; /* serial number of the device */
+ uint32_t d_ncylinders; /* # of data cylinders per device */
+ uint32_t d_ntracks; /* # of tracks per cylinder */
+ uint32_t d_nsectors; /* # of data sectors per track */
+ uint32_t d_secsize; /* # of bytes per sector */
+ uint32_t d_part_start; /* # of first sector of this partition */
+ uint32_t d_unknown1[12]; /* ? */
+ uint32_t d_alt_tbl; /* byte offset of alternate table */
+ uint32_t d_alt_len; /* byte length of alternate table */
+ uint32_t d_phys_cyl; /* # of physical cylinders per device */
+ uint32_t d_phys_trk; /* # of physical tracks per cylinder */
+ uint32_t d_phys_sec; /* # of physical sectors per track */
+ uint32_t d_phys_bytes; /* # of physical bytes per sector */
+ uint32_t d_unknown2; /* ? */
+ uint32_t d_unknown3; /* ? */
+ uint32_t d_pad[8]; /* pad */
+
+ struct unixware_vtoc {
+ uint32_t v_magic; /* the magic number */
+ uint32_t v_version; /* version number */
+ char v_name[8]; /* volume name */
+ uint16_t v_nslices; /* # of partitions */
+ uint16_t v_unknown1; /* ? */
+ uint32_t v_reserved[10]; /* reserved */
+
+ struct unixware_partition
+ v_slice[UNIXWARE_MAXPARTITIONS]; /* partition */
+ } __attribute__((packed)) vtoc;
+};
+
+static int probe_unixware_pt(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct unixware_disklabel *l;
+ struct unixware_partition *p;
+ blkid_parttable tab = NULL;
+ blkid_partition parent;
+ blkid_partlist ls;
+ int i;
+
+ l = (struct unixware_disklabel *)
+ blkid_probe_get_sector(pr, UNIXWARE_SECTOR);
+ if (!l) {
+ if (errno)
+ return -errno;
+ goto nothing;
+ }
+
+ if (le32_to_cpu(l->vtoc.v_magic) != UNIXWARE_VTOCMAGIC)
+ goto nothing;
+
+ if (blkid_partitions_need_typeonly(pr))
+ /* caller does not ask for details about partitions */
+ return BLKID_PROBE_OK;
+
+ ls = blkid_probe_get_partlist(pr);
+ if (!ls)
+ goto nothing;
+
+ parent = blkid_partlist_get_parent(ls);
+
+ tab = blkid_partlist_new_parttable(ls, "unixware", UNIXWARE_OFFSET);
+ if (!tab)
+ goto err;
+
+ /* Skip the first partition that describe whole disk
+ */
+ for (i = 1, p = &l->vtoc.v_slice[1];
+ i < UNIXWARE_MAXPARTITIONS; i++, p++) {
+
+ uint32_t start, size;
+ uint16_t tag, flg;
+ blkid_partition par;
+
+ tag = le16_to_cpu(p->s_label);
+ flg = le16_to_cpu(p->s_flags);
+
+ if (tag == UNIXWARE_TAG_UNUSED ||
+ tag == UNIXWARE_TAG_ENTIRE_DISK ||
+ flg != UNIXWARE_FLAG_VALID)
+ continue;
+
+ start = le32_to_cpu(p->start_sect);
+ size = le32_to_cpu(p->nr_sects);
+
+ if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+ DBG(LOWPROBE, ul_debug(
+ "WARNING: unixware partition (%d) overflow "
+ "detected, ignore", i));
+ continue;
+ }
+
+ par = blkid_partlist_add_partition(ls, tab, start, size);
+ if (!par)
+ goto err;
+
+ blkid_partition_set_type(par, tag);
+ blkid_partition_set_flags(par, flg);
+ }
+
+ return BLKID_PROBE_OK;
+
+nothing:
+ return BLKID_PROBE_NONE;
+err:
+ return -ENOMEM;
+}
+
+
+/*
+ * The unixware partition table is within primary DOS partition. The PT is
+ * located on 29 sector, PT magic string is d_magic member of 'struct
+ * unixware_disklabel'.
+ */
+const struct blkid_idinfo unixware_pt_idinfo =
+{
+ .name = "unixware",
+ .probefunc = probe_unixware_pt,
+ .minsz = 1024 * 1440 + 1, /* ignore floppies */
+ .magics =
+ {
+ {
+ .magic = "\x0D\x60\xE5\xCA", /* little-endian magic string */
+ .len = 4, /* d_magic size in bytes */
+ .kboff = UNIXWARE_KBOFFSET,
+ .sboff = UNIXWARE_MAGICOFFSET
+ },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/probe.c b/libblkid/src/probe.c
new file mode 100644
index 0000000..5acd273
--- /dev/null
+++ b/libblkid/src/probe.c
@@ -0,0 +1,2356 @@
+/*
+ * Low-level libblkid probing API
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: lowprobe
+ * @title: Low-level probing
+ * @short_description: low-level prober initialization
+ *
+ * The low-level probing routines always and directly read information from
+ * the selected (see blkid_probe_set_device()) device.
+ *
+ * The probing routines are grouped together into separate chains. Currently,
+ * the library provides superblocks, partitions and topology chains.
+ *
+ * The probing routines is possible to filter (enable/disable) by type (e.g.
+ * fstype "vfat" or partype "gpt") or by usage flags (e.g. BLKID_USAGE_RAID).
+ * These filters are per-chain. Note that always when you touch the chain
+ * filter the current probing position is reset and probing starts from
+ * scratch. It means that the chain filter should not be modified during
+ * probing, for example in loop where you call blkid_do_probe().
+ *
+ * For more details see the chain specific documentation.
+ *
+ * The low-level API provides two ways how access to probing results.
+ *
+ * 1. The NAME=value (tag) interface. This interface is older and returns all data
+ * as strings. This interface is generic for all chains.
+ *
+ * 2. The binary interfaces. These interfaces return data in the native formats.
+ * The interface is always specific to the probing chain.
+ *
+ * Note that the previous probing result (binary or NAME=value) is always
+ * zeroized when a chain probing function is called. For example:
+ *
+ * <informalexample>
+ * <programlisting>
+ * blkid_probe_enable_partitions(pr, TRUE);
+ * blkid_probe_enable_superblocks(pr, FALSE);
+ *
+ * blkid_do_safeprobe(pr);
+ * </programlisting>
+ * </informalexample>
+ *
+ * overwrites the previous probing result for the partitions chain, the superblocks
+ * result is not modified.
+ */
+
+/**
+ * SECTION: lowprobe-tags
+ * @title: Low-level tags
+ * @short_description: generic NAME=value interface.
+ *
+ * The probing routines inside the chain are mutually exclusive by default --
+ * only few probing routines are marked as "tolerant". The "tolerant" probing
+ * routines are used for filesystem which can share the same device with any
+ * other filesystem. The blkid_do_safeprobe() checks for the "tolerant" flag.
+ *
+ * The SUPERBLOCKS chain is enabled by default. The all others chains is
+ * necessary to enable by blkid_probe_enable_'CHAINNAME'(). See chains specific
+ * documentation.
+ *
+ * The blkid_do_probe() function returns a result from only one probing
+ * routine, and the next call from the next probing routine. It means you need
+ * to call the function in loop, for example:
+ *
+ * <informalexample>
+ * <programlisting>
+ * while((blkid_do_probe(pr) == 0)
+ * ... use result ...
+ * </programlisting>
+ * </informalexample>
+ *
+ * The blkid_do_safeprobe() is the same as blkid_do_probe(), but returns only
+ * first probing result for every enabled chain. This function checks for
+ * ambivalent results (e.g. more "intolerant" filesystems superblocks on the
+ * device).
+ *
+ * The probing result is set of NAME=value pairs (the NAME is always unique).
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_LINUX_CDROM_H
+#include <linux/cdrom.h>
+#endif
+#ifdef HAVE_LINUX_BLKZONED_H
+#include <linux/blkzoned.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <limits.h>
+
+#include "blkidP.h"
+#include "all-io.h"
+#include "sysfs.h"
+#include "strutils.h"
+#include "list.h"
+#include "fileutils.h"
+
+/*
+ * All supported chains
+ */
+static const struct blkid_chaindrv *chains_drvs[] = {
+ [BLKID_CHAIN_SUBLKS] = &superblocks_drv,
+ [BLKID_CHAIN_TOPLGY] = &topology_drv,
+ [BLKID_CHAIN_PARTS] = &partitions_drv
+};
+
+static void blkid_probe_reset_values(blkid_probe pr);
+
+/**
+ * blkid_new_probe:
+ *
+ * Returns: a pointer to the newly allocated probe struct or NULL in case of error.
+ */
+blkid_probe blkid_new_probe(void)
+{
+ int i;
+ blkid_probe pr;
+
+ blkid_init_debug(0);
+ pr = calloc(1, sizeof(struct blkid_struct_probe));
+ if (!pr)
+ return NULL;
+
+ DBG(LOWPROBE, ul_debug("allocate a new probe"));
+
+ /* initialize chains */
+ for (i = 0; i < BLKID_NCHAINS; i++) {
+ pr->chains[i].driver = chains_drvs[i];
+ pr->chains[i].flags = chains_drvs[i]->dflt_flags;
+ pr->chains[i].enabled = chains_drvs[i]->dflt_enabled;
+ }
+ INIT_LIST_HEAD(&pr->buffers);
+ INIT_LIST_HEAD(&pr->values);
+ INIT_LIST_HEAD(&pr->hints);
+ return pr;
+}
+
+/*
+ * Clone @parent, the new clone shares all, but except:
+ *
+ * - probing result
+ * - buffers if another device (or offset) is set to the prober
+ */
+blkid_probe blkid_clone_probe(blkid_probe parent)
+{
+ blkid_probe pr;
+
+ if (!parent)
+ return NULL;
+
+ DBG(LOWPROBE, ul_debug("allocate a probe clone"));
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return NULL;
+
+ pr->fd = parent->fd;
+ pr->off = parent->off;
+ pr->size = parent->size;
+ pr->devno = parent->devno;
+ pr->disk_devno = parent->disk_devno;
+ pr->blkssz = parent->blkssz;
+ pr->flags = parent->flags;
+ pr->zone_size = parent->zone_size;
+ pr->parent = parent;
+
+ pr->flags &= ~BLKID_FL_PRIVATE_FD;
+
+ return pr;
+}
+
+
+
+/**
+ * blkid_new_probe_from_filename:
+ * @filename: device or regular file
+ *
+ * This function is same as call open(filename), blkid_new_probe() and
+ * blkid_probe_set_device(pr, fd, 0, 0).
+ *
+ * The @filename is closed by blkid_free_probe() or by the
+ * blkid_probe_set_device() call.
+ *
+ * Returns: a pointer to the newly allocated probe struct or NULL in case of
+ * error.
+ */
+blkid_probe blkid_new_probe_from_filename(const char *filename)
+{
+ int fd;
+ blkid_probe pr = NULL;
+
+ fd = open(filename, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0)
+ return NULL;
+
+ pr = blkid_new_probe();
+ if (!pr)
+ goto err;
+
+ if (blkid_probe_set_device(pr, fd, 0, 0))
+ goto err;
+
+ pr->flags |= BLKID_FL_PRIVATE_FD;
+ return pr;
+err:
+ close(fd);
+ blkid_free_probe(pr);
+ return NULL;
+}
+
+/**
+ * blkid_free_probe:
+ * @pr: probe
+ *
+ * Deallocates the probe struct, buffers and all allocated
+ * data that are associated with this probing control struct.
+ */
+void blkid_free_probe(blkid_probe pr)
+{
+ int i;
+
+ if (!pr)
+ return;
+
+ for (i = 0; i < BLKID_NCHAINS; i++) {
+ struct blkid_chain *ch = &pr->chains[i];
+
+ if (ch->driver->free_data)
+ ch->driver->free_data(pr, ch->data);
+ free(ch->fltr);
+ ch->fltr = NULL;
+ }
+
+ if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0)
+ close(pr->fd);
+ blkid_probe_reset_buffers(pr);
+ blkid_probe_reset_values(pr);
+ blkid_probe_reset_hints(pr);
+ blkid_free_probe(pr->disk_probe);
+
+ DBG(LOWPROBE, ul_debug("free probe"));
+ free(pr);
+}
+
+void blkid_probe_free_value(struct blkid_prval *v)
+{
+ if (!v)
+ return;
+
+ list_del(&v->prvals);
+ free(v->data);
+
+ DBG(LOWPROBE, ul_debug(" free value %s", v->name));
+ free(v);
+}
+
+/*
+ * Removes chain values from probing result.
+ */
+void blkid_probe_chain_reset_values(blkid_probe pr, struct blkid_chain *chn)
+{
+
+ struct list_head *p, *pnext;
+
+ if (list_empty(&pr->values))
+ return;
+
+ DBG(LOWPROBE, ul_debug("Resetting %s values", chn->driver->name));
+
+ list_for_each_safe(p, pnext, &pr->values) {
+ struct blkid_prval *v = list_entry(p,
+ struct blkid_prval, prvals);
+
+ if (v->chain == chn)
+ blkid_probe_free_value(v);
+ }
+}
+
+static void blkid_probe_chain_reset_position(struct blkid_chain *chn)
+{
+ chn->idx = -1;
+}
+
+/*
+ * Move chain values from probing result to @vals
+ */
+int blkid_probe_chain_save_values(blkid_probe pr, struct blkid_chain *chn,
+ struct list_head *vals)
+{
+ struct list_head *p, *pnext;
+ struct blkid_prval *v;
+
+ DBG(LOWPROBE, ul_debug("saving %s values", chn->driver->name));
+
+ list_for_each_safe(p, pnext, &pr->values) {
+
+ v = list_entry(p, struct blkid_prval, prvals);
+ if (v->chain != chn)
+ continue;
+
+ list_del_init(&v->prvals);
+ list_add_tail(&v->prvals, vals);
+ }
+ return 0;
+}
+
+/*
+ * Appends values from @vals to the probing result
+ */
+void blkid_probe_append_values_list(blkid_probe pr, struct list_head *vals)
+{
+ DBG(LOWPROBE, ul_debug("appending values"));
+
+ list_splice(vals, &pr->values);
+ INIT_LIST_HEAD(vals);
+}
+
+
+void blkid_probe_free_values_list(struct list_head *vals)
+{
+ if (!vals)
+ return;
+
+ DBG(LOWPROBE, ul_debug("freeing values list"));
+
+ while (!list_empty(vals)) {
+ struct blkid_prval *v = list_entry(vals->next, struct blkid_prval, prvals);
+ blkid_probe_free_value(v);
+ }
+}
+
+struct blkid_chain *blkid_probe_get_chain(blkid_probe pr)
+{
+ return pr->cur_chain;
+}
+
+static const char *blkid_probe_get_probername(blkid_probe pr)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (chn && chn->idx >= 0 && (unsigned)chn->idx < chn->driver->nidinfos)
+ return chn->driver->idinfos[chn->idx]->name;
+
+ return NULL;
+}
+
+void *blkid_probe_get_binary_data(blkid_probe pr, struct blkid_chain *chn)
+{
+ int rc, org_prob_flags;
+ struct blkid_chain *org_chn;
+
+ /* save the current setting -- the binary API has to be completely
+ * independent on the current probing status
+ */
+ org_chn = pr->cur_chain;
+ org_prob_flags = pr->prob_flags;
+
+ pr->cur_chain = chn;
+ pr->prob_flags = 0;
+ chn->binary = TRUE;
+ blkid_probe_chain_reset_position(chn);
+
+ rc = chn->driver->probe(pr, chn);
+
+ chn->binary = FALSE;
+ blkid_probe_chain_reset_position(chn);
+
+ /* restore the original setting
+ */
+ pr->cur_chain = org_chn;
+ pr->prob_flags = org_prob_flags;
+
+ if (rc != 0)
+ return NULL;
+
+ DBG(LOWPROBE, ul_debug("returning %s binary data", chn->driver->name));
+ return chn->data;
+}
+
+
+/**
+ * blkid_reset_probe:
+ * @pr: probe
+ *
+ * Zeroize probing results and resets the current probing (this has impact to
+ * blkid_do_probe() only). This function does not touch probing filters and
+ * keeps assigned device.
+ */
+void blkid_reset_probe(blkid_probe pr)
+{
+ int i;
+
+ blkid_probe_reset_values(pr);
+ blkid_probe_set_wiper(pr, 0, 0);
+
+ pr->cur_chain = NULL;
+
+ for (i = 0; i < BLKID_NCHAINS; i++)
+ blkid_probe_chain_reset_position(&pr->chains[i]);
+}
+
+/***
+static int blkid_probe_dump_filter(blkid_probe pr, int chain)
+{
+ struct blkid_chain *chn;
+ int i;
+
+ if (!pr || chain < 0 || chain >= BLKID_NCHAINS)
+ return -1;
+
+ chn = &pr->chains[chain];
+
+ if (!chn->fltr)
+ return -1;
+
+ for (i = 0; i < chn->driver->nidinfos; i++) {
+ const struct blkid_idinfo *id = chn->driver->idinfos[i];
+
+ DBG(LOWPROBE, ul_debug("%d: %s: %s",
+ i,
+ id->name,
+ blkid_bmp_get_item(chn->fltr, i)
+ ? "disabled" : "enabled <--"));
+ }
+ return 0;
+}
+***/
+
+/*
+ * Returns properly initialized chain filter
+ */
+unsigned long *blkid_probe_get_filter(blkid_probe pr, int chain, int create)
+{
+ struct blkid_chain *chn;
+
+ if (chain < 0 || chain >= BLKID_NCHAINS)
+ return NULL;
+
+ chn = &pr->chains[chain];
+
+ /* always when you touch the chain filter all indexes are reset and
+ * probing starts from scratch
+ */
+ blkid_probe_chain_reset_position(chn);
+ pr->cur_chain = NULL;
+
+ if (!chn->driver->has_fltr || (!chn->fltr && !create))
+ return NULL;
+
+ if (!chn->fltr)
+ chn->fltr = calloc(1, blkid_bmp_nbytes(chn->driver->nidinfos));
+ else
+ memset(chn->fltr, 0, blkid_bmp_nbytes(chn->driver->nidinfos));
+
+ /* blkid_probe_dump_filter(pr, chain); */
+ return chn->fltr;
+}
+
+/*
+ * Generic private functions for filter setting
+ */
+int __blkid_probe_invert_filter(blkid_probe pr, int chain)
+{
+ size_t i;
+ struct blkid_chain *chn;
+
+ chn = &pr->chains[chain];
+
+ if (!chn->driver->has_fltr || !chn->fltr)
+ return -1;
+
+ for (i = 0; i < blkid_bmp_nwords(chn->driver->nidinfos); i++)
+ chn->fltr[i] = ~chn->fltr[i];
+
+ DBG(LOWPROBE, ul_debug("probing filter inverted"));
+ /* blkid_probe_dump_filter(pr, chain); */
+ return 0;
+}
+
+int __blkid_probe_reset_filter(blkid_probe pr, int chain)
+{
+ return blkid_probe_get_filter(pr, chain, FALSE) ? 0 : -1;
+}
+
+int __blkid_probe_filter_types(blkid_probe pr, int chain, int flag, char *names[])
+{
+ unsigned long *fltr;
+ struct blkid_chain *chn;
+ size_t i;
+
+ fltr = blkid_probe_get_filter(pr, chain, TRUE);
+ if (!fltr)
+ return -1;
+
+ chn = &pr->chains[chain];
+
+ for (i = 0; i < chn->driver->nidinfos; i++) {
+ int has = 0;
+ const struct blkid_idinfo *id = chn->driver->idinfos[i];
+ char **n;
+
+ for (n = names; *n; n++) {
+ if (!strcmp(id->name, *n)) {
+ has = 1;
+ break;
+ }
+ }
+ if (has) {
+ if (flag & BLKID_FLTR_NOTIN)
+ blkid_bmp_set_item(fltr, i);
+ } else if (flag & BLKID_FLTR_ONLYIN)
+ blkid_bmp_set_item(fltr, i);
+ }
+
+ DBG(LOWPROBE, ul_debug("%s: a new probing type-filter initialized",
+ chn->driver->name));
+ /* blkid_probe_dump_filter(pr, chain); */
+ return 0;
+}
+
+static struct blkid_bufinfo *read_buffer(blkid_probe pr, uint64_t real_off, uint64_t len)
+{
+ ssize_t ret;
+ struct blkid_bufinfo *bf = NULL;
+
+ if (lseek(pr->fd, real_off, SEEK_SET) == (off_t) -1) {
+ errno = 0;
+ return NULL;
+ }
+
+ /* someone trying to overflow some buffers? */
+ if (len > ULONG_MAX - sizeof(struct blkid_bufinfo)) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* allocate info and space for data by one malloc call */
+ bf = calloc(1, sizeof(struct blkid_bufinfo) + len);
+ if (!bf) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ bf->data = ((unsigned char *) bf) + sizeof(struct blkid_bufinfo);
+ bf->len = len;
+ bf->off = real_off;
+ INIT_LIST_HEAD(&bf->bufs);
+
+ DBG(LOWPROBE, ul_debug("\tread: off=%"PRIu64" len=%"PRIu64"",
+ real_off, len));
+
+ ret = read(pr->fd, bf->data, len);
+ if (ret != (ssize_t) len) {
+ DBG(LOWPROBE, ul_debug("\tread failed: %m"));
+ free(bf);
+
+ /* I/O errors on CDROMs are non-fatal to work with hybrid
+ * audio+data disks */
+ if (ret >= 0 || blkid_probe_is_cdrom(pr))
+ errno = 0;
+ return NULL;
+ }
+
+ return bf;
+}
+
+/*
+ * Search in buffers we already have in memory
+ */
+static struct blkid_bufinfo *get_cached_buffer(blkid_probe pr, uint64_t off, uint64_t len)
+{
+ uint64_t real_off = pr->off + off;
+ struct list_head *p;
+
+ list_for_each(p, &pr->buffers) {
+ struct blkid_bufinfo *x =
+ list_entry(p, struct blkid_bufinfo, bufs);
+
+ if (real_off >= x->off && real_off + len <= x->off + x->len) {
+ DBG(BUFFER, ul_debug("\treuse: off=%"PRIu64" len=%"PRIu64" (for off=%"PRIu64" len=%"PRIu64")",
+ x->off, x->len, real_off, len));
+ return x;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Zeroize in-memory data in already read buffer. The next blkid_probe_get_buffer()
+ * will return modified buffer. This is usable when you want to call the same probing
+ * function more than once and hide previously detected magic strings.
+ *
+ * See blkid_probe_hide_range().
+ */
+static int hide_buffer(blkid_probe pr, uint64_t off, uint64_t len)
+{
+ uint64_t real_off = pr->off + off;
+ struct list_head *p;
+ int ct = 0;
+
+ if (UINT64_MAX - len < off) {
+ DBG(BUFFER, ul_debug("\t hide-buffer overflow (ignore)"));
+ return -EINVAL;
+ }
+
+ list_for_each(p, &pr->buffers) {
+ struct blkid_bufinfo *x =
+ list_entry(p, struct blkid_bufinfo, bufs);
+ unsigned char *data;
+
+ if (real_off >= x->off && real_off + len <= x->off + x->len) {
+
+ assert(x->off <= real_off);
+ assert(x->off + x->len >= real_off + len);
+
+ data = real_off ? x->data + (real_off - x->off) : x->data;
+
+ DBG(BUFFER, ul_debug("\thiding: off=%"PRIu64" len=%"PRIu64,
+ off, len));
+ memset(data, 0, len);
+ ct++;
+ }
+ }
+ return ct == 0 ? -EINVAL : 0;
+}
+
+
+/*
+ * Note that @off is offset within probing area, the probing area is defined by
+ * pr->off and pr->size.
+ */
+unsigned char *blkid_probe_get_buffer(blkid_probe pr, uint64_t off, uint64_t len)
+{
+ struct blkid_bufinfo *bf = NULL;
+ uint64_t real_off = pr->off + off;
+
+ /*
+ DBG(BUFFER, ul_debug("\t>>>> off=%ju, real-off=%ju (probe <%ju..%ju>, len=%ju",
+ off, real_off, pr->off, pr->off + pr->size, len));
+ */
+ if (pr->size == 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (UINT64_MAX - len < off || UINT64_MAX - len < real_off) {
+ DBG(BUFFER, ul_debug("\t read-buffer overflow (ignore)"));
+ return NULL;
+ }
+
+ if (len == 0
+ || (!S_ISCHR(pr->mode) && (pr->size < off || pr->size < len))
+ || (!S_ISCHR(pr->mode) && (pr->off + pr->size < real_off + len))) {
+ DBG(BUFFER, ul_debug("\t read-buffer out of probing area (ignore)"));
+ errno = 0;
+ return NULL;
+ }
+
+ if (pr->parent &&
+ pr->parent->devno == pr->devno &&
+ pr->parent->off <= pr->off &&
+ pr->parent->off + pr->parent->size >= pr->off + pr->size) {
+ /*
+ * This is a cloned prober and points to the same area as
+ * parent. Let's use parent's buffers.
+ *
+ * Note that pr->off (and pr->parent->off) is always from the
+ * begin of the device.
+ */
+ return blkid_probe_get_buffer(pr->parent,
+ pr->off + off - pr->parent->off, len);
+ }
+
+ /* try buffers we already have in memory or read from device */
+ bf = get_cached_buffer(pr, off, len);
+ if (!bf) {
+ bf = read_buffer(pr, real_off, len);
+ if (!bf)
+ return NULL;
+
+ list_add_tail(&bf->bufs, &pr->buffers);
+ }
+
+ assert(bf->off <= real_off);
+ assert(bf->off + bf->len >= real_off + len);
+
+ errno = 0;
+ return real_off ? bf->data + (real_off - bf->off) : bf->data;
+}
+
+/**
+ * blkid_probe_reset_buffers:
+ * @pr: prober
+ *
+ * libblkid reuse all already read buffers from the device. The buffers may be
+ * modified by blkid_probe_hide_range(). This function reset and free all
+ * cached buffers. The next blkid_do_probe() will read all data from the
+ * device.
+ *
+ * Since: 2.31
+ *
+ * Returns: <0 in case of failure, or 0 on success.
+ */
+int blkid_probe_reset_buffers(blkid_probe pr)
+{
+ uint64_t ct = 0, len = 0;
+
+ pr->flags &= ~BLKID_FL_MODIF_BUFF;
+
+ if (list_empty(&pr->buffers))
+ return 0;
+
+ DBG(BUFFER, ul_debug("Resetting probing buffers"));
+
+ while (!list_empty(&pr->buffers)) {
+ struct blkid_bufinfo *bf = list_entry(pr->buffers.next,
+ struct blkid_bufinfo, bufs);
+ ct++;
+ len += bf->len;
+ list_del(&bf->bufs);
+
+ DBG(BUFFER, ul_debug(" remove buffer: [off=%"PRIu64", len=%"PRIu64"]",
+ bf->off, bf->len));
+ free(bf);
+ }
+
+ DBG(LOWPROBE, ul_debug(" buffers summary: %"PRIu64" bytes by %"PRIu64" read() calls",
+ len, ct));
+
+ INIT_LIST_HEAD(&pr->buffers);
+
+ return 0;
+}
+
+/**
+ * blkid_probe_hide_range:
+ * @pr: prober
+ * @off: start of the range
+ * @len: size of the range
+ *
+ * This function modifies in-memory cached data from the device. The specified
+ * range is zeroized. This is usable together with blkid_probe_step_back().
+ * The next blkid_do_probe() will not see specified area.
+ *
+ * Note that this is usable for already (by library) read data, and this
+ * function is not a way how to hide any large areas on your device.
+ *
+ * The function blkid_probe_reset_buffers() reverts all.
+ *
+ * Since: 2.31
+ *
+ * Returns: <0 in case of failure, or 0 on success.
+ */
+int blkid_probe_hide_range(blkid_probe pr, uint64_t off, uint64_t len)
+{
+ int rc = hide_buffer(pr, off, len);
+
+ if (rc == 0)
+ pr->flags |= BLKID_FL_MODIF_BUFF;
+ return rc;
+}
+
+
+static void blkid_probe_reset_values(blkid_probe pr)
+{
+ if (list_empty(&pr->values))
+ return;
+
+ DBG(LOWPROBE, ul_debug("resetting results"));
+
+ while (!list_empty(&pr->values)) {
+ struct blkid_prval *v = list_entry(pr->values.next,
+ struct blkid_prval, prvals);
+ blkid_probe_free_value(v);
+ }
+
+ INIT_LIST_HEAD(&pr->values);
+}
+
+/*
+ * Small devices need a special care.
+ */
+int blkid_probe_is_tiny(blkid_probe pr)
+{
+ return (pr->flags & BLKID_FL_TINY_DEV);
+}
+
+/*
+ * CDROMs may fail when probed for RAID (last sector problem)
+ */
+int blkid_probe_is_cdrom(blkid_probe pr)
+{
+ return (pr->flags & BLKID_FL_CDROM_DEV);
+}
+
+#ifdef CDROM_GET_CAPABILITY
+
+static int is_sector_readable(int fd, uint64_t sector)
+{
+ char buf[512];
+ ssize_t sz;
+
+ if (lseek(fd, sector * 512, SEEK_SET) == (off_t) -1)
+ goto failed;
+
+ sz = read(fd, buf, sizeof(buf));
+ if (sz != (ssize_t) sizeof(buf))
+ goto failed;
+
+ return 1;
+failed:
+ DBG(LOWPROBE, ul_debug("CDROM: read sector %"PRIu64" failed %m", sector));
+ errno = 0;
+ return 0;
+}
+
+/*
+ * Linux kernel reports (BLKGETSIZE) cdrom device size greater than area
+ * readable by read(2). We have to reduce the probing area to avoid unwanted
+ * I/O errors in probing functions. It seems that unreadable are always last 2
+ * or 3 CD blocks (CD block size is 2048 bytes, it means 12 in 512-byte
+ * sectors). Linux kernel reports (CDROM_LAST_WRITTEN) also location of last
+ * written block, so we will reduce size based on it too.
+ */
+static void cdrom_size_correction(blkid_probe pr, uint64_t last_written)
+{
+ uint64_t n, nsectors = pr->size >> 9;
+
+ if (last_written && nsectors > ((last_written+1) << 2))
+ nsectors = (last_written+1) << 2;
+
+ for (n = nsectors - 12; n < nsectors; n++) {
+ if (!is_sector_readable(pr->fd, n))
+ goto failed;
+ }
+
+ DBG(LOWPROBE, ul_debug("CDROM: full size available"));
+ return;
+failed:
+ /* 'n' is the failed sector, reduce device size to n-1; */
+ DBG(LOWPROBE, ul_debug("CDROM: reduce size from %ju to %ju.",
+ (uintmax_t) pr->size,
+ (uintmax_t) n << 9));
+ pr->size = n << 9;
+}
+
+#endif
+
+/**
+ * blkid_probe_set_device:
+ * @pr: probe
+ * @fd: device file descriptor
+ * @off: begin of probing area
+ * @size: size of probing area (zero means whole device/file)
+ *
+ * Assigns the device to probe control struct, resets internal buffers, resets
+ * the current probing, and close previously associated device (if open by
+ * libblkid).
+ *
+ * If @fd is < 0 than only resets the prober and returns 1. Note that
+ * blkid_reset_probe() keeps the device associated with the prober, but
+ * blkid_probe_set_device() does complete reset.
+ *
+ * Returns: -1 in case of failure, 0 on success and 1 on reset.
+ */
+int blkid_probe_set_device(blkid_probe pr, int fd,
+ blkid_loff_t off, blkid_loff_t size)
+{
+ struct stat sb;
+ uint64_t devsiz = 0;
+ char *dm_uuid = NULL;
+ int is_floppy = 0;
+
+ blkid_reset_probe(pr);
+ blkid_probe_reset_buffers(pr);
+
+ if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0)
+ close(pr->fd);
+
+ if (pr->disk_probe) {
+ blkid_free_probe(pr->disk_probe);
+ pr->disk_probe = NULL;
+ }
+
+ pr->flags &= ~BLKID_FL_PRIVATE_FD;
+ pr->flags &= ~BLKID_FL_TINY_DEV;
+ pr->flags &= ~BLKID_FL_CDROM_DEV;
+ pr->prob_flags = 0;
+ pr->fd = fd;
+ pr->off = (uint64_t) off;
+ pr->size = 0;
+ pr->devno = 0;
+ pr->disk_devno = 0;
+ pr->mode = 0;
+ pr->blkssz = 0;
+ pr->wipe_off = 0;
+ pr->wipe_size = 0;
+ pr->wipe_chain = NULL;
+ pr->zone_size = 0;
+
+ if (fd < 0)
+ return 1;
+
+
+#if defined(POSIX_FADV_RANDOM) && defined(HAVE_POSIX_FADVISE)
+ /* Disable read-ahead */
+ posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
+#endif
+ if (fstat(fd, &sb))
+ goto err;
+
+ if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode) && !S_ISREG(sb.st_mode)) {
+ errno = EINVAL;
+ goto err;
+ }
+
+ pr->mode = sb.st_mode;
+ if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode))
+ pr->devno = sb.st_rdev;
+
+ if (S_ISBLK(sb.st_mode)) {
+ if (blkdev_get_size(fd, (unsigned long long *) &devsiz)) {
+ DBG(LOWPROBE, ul_debug("failed to get device size"));
+ goto err;
+ }
+ } else if (S_ISCHR(sb.st_mode)) {
+ char buf[PATH_MAX];
+
+ if (!sysfs_chrdev_devno_to_devname(sb.st_rdev, buf, sizeof(buf))
+ || strncmp(buf, "ubi", 3) != 0) {
+ DBG(LOWPROBE, ul_debug("no UBI char device"));
+ errno = EINVAL;
+ goto err;
+ }
+ devsiz = 1; /* UBI devices are char... */
+ } else if (S_ISREG(sb.st_mode))
+ devsiz = sb.st_size; /* regular file */
+
+ pr->size = size ? (uint64_t)size : devsiz;
+
+ if (off && size == 0)
+ /* only offset without size specified */
+ pr->size -= (uint64_t) off;
+
+ if (pr->off + pr->size > devsiz) {
+ DBG(LOWPROBE, ul_debug("area specified by offset and size is bigger than device"));
+ errno = EINVAL;
+ goto err;
+ }
+
+ if (pr->size <= 1440 * 1024 && !S_ISCHR(sb.st_mode))
+ pr->flags |= BLKID_FL_TINY_DEV;
+
+#ifdef FDGETFDCSTAT
+ if (S_ISBLK(sb.st_mode)) {
+ /*
+ * Re-open without O_NONBLOCK for floppy device.
+ *
+ * Since kernel commit c7e9d0020361f4308a70cdfd6d5335e273eb8717
+ * floppy drive works bad when opened with O_NONBLOCK.
+ */
+ struct floppy_fdc_state flst;
+
+ if (ioctl(fd, FDGETFDCSTAT, &flst) >= 0) {
+ int flags = fcntl(fd, F_GETFL, 0);
+
+ if (flags < 0)
+ goto err;
+ if (flags & O_NONBLOCK) {
+ flags &= ~O_NONBLOCK;
+
+ fd = ul_reopen(fd, flags | O_CLOEXEC);
+ if (fd < 0)
+ goto err;
+
+ pr->flags |= BLKID_FL_PRIVATE_FD;
+ pr->fd = fd;
+ }
+ is_floppy = 1;
+ }
+ errno = 0;
+ }
+#endif
+ if (S_ISBLK(sb.st_mode) &&
+ !is_floppy &&
+ sysfs_devno_is_dm_private(sb.st_rdev, &dm_uuid)) {
+ DBG(LOWPROBE, ul_debug("ignore private device mapper device"));
+ pr->flags |= BLKID_FL_NOSCAN_DEV;
+ }
+
+#ifdef CDROM_GET_CAPABILITY
+ else if (S_ISBLK(sb.st_mode) &&
+ !blkid_probe_is_tiny(pr) &&
+ !dm_uuid &&
+ !is_floppy &&
+ blkid_probe_is_wholedisk(pr)) {
+
+ long last_written = 0;
+
+ /*
+ * pktcdvd.ko accepts only these ioctls:
+ * CDROMEJECT CDROMMULTISESSION CDROMREADTOCENTRY
+ * CDROM_LAST_WRITTEN CDROM_SEND_PACKET SCSI_IOCTL_SEND_COMMAND
+ * So CDROM_GET_CAPABILITY cannot be used for detecting pktcdvd
+ * devices. But CDROM_GET_CAPABILITY and CDROM_DRIVE_STATUS are
+ * fast so use them for detecting if medium is present. In any
+ * case use last written block form CDROM_LAST_WRITTEN.
+ */
+
+ if (ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0) {
+# ifdef CDROM_DRIVE_STATUS
+ switch (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)) {
+ case CDS_TRAY_OPEN:
+ case CDS_NO_DISC:
+ errno = ENOMEDIUM;
+ goto err;
+ }
+# endif
+ pr->flags |= BLKID_FL_CDROM_DEV;
+ }
+
+# ifdef CDROM_LAST_WRITTEN
+ if (ioctl(fd, CDROM_LAST_WRITTEN, &last_written) == 0) {
+ pr->flags |= BLKID_FL_CDROM_DEV;
+ } else {
+ if (errno == ENOMEDIUM)
+ goto err;
+ }
+# endif
+
+ if (pr->flags & BLKID_FL_CDROM_DEV) {
+ cdrom_size_correction(pr, last_written);
+
+# ifdef CDROMMULTISESSION
+ if (!pr->off && blkid_probe_get_hint(pr, "session_offset", NULL) < 0) {
+ struct cdrom_multisession multisession = { .addr_format = CDROM_LBA };
+ if (ioctl(fd, CDROMMULTISESSION, &multisession) == 0 && multisession.xa_flag)
+ blkid_probe_set_hint(pr, "session_offset", (multisession.addr.lba << 11));
+ }
+# endif
+ }
+ }
+#endif
+ free(dm_uuid);
+
+# ifdef BLKGETZONESZ
+ if (S_ISBLK(sb.st_mode) && !is_floppy) {
+ uint32_t zone_size_sector;
+
+ if (!ioctl(pr->fd, BLKGETZONESZ, &zone_size_sector))
+ pr->zone_size = zone_size_sector << 9;
+ }
+# endif
+
+ DBG(LOWPROBE, ul_debug("ready for low-probing, offset=%"PRIu64", size=%"PRIu64", zonesize=%"PRIu64,
+ pr->off, pr->size, pr->zone_size));
+ DBG(LOWPROBE, ul_debug("whole-disk: %s, regfile: %s",
+ blkid_probe_is_wholedisk(pr) ?"YES" : "NO",
+ S_ISREG(pr->mode) ? "YES" : "NO"));
+
+ return 0;
+err:
+ DBG(LOWPROBE, ul_debug("failed to prepare a device for low-probing"));
+ return -1;
+
+}
+
+int blkid_probe_get_dimension(blkid_probe pr, uint64_t *off, uint64_t *size)
+{
+ *off = pr->off;
+ *size = pr->size;
+ return 0;
+}
+
+int blkid_probe_set_dimension(blkid_probe pr, uint64_t off, uint64_t size)
+{
+ DBG(LOWPROBE, ul_debug(
+ "changing probing area: size=%"PRIu64", off=%"PRIu64" "
+ "-to-> size=%"PRIu64", off=%"PRIu64"",
+ pr->size, pr->off, size, off));
+
+ pr->off = off;
+ pr->size = size;
+ pr->flags &= ~BLKID_FL_TINY_DEV;
+
+ if (pr->size <= 1440ULL * 1024ULL && !S_ISCHR(pr->mode))
+ pr->flags |= BLKID_FL_TINY_DEV;
+
+ blkid_probe_reset_buffers(pr);
+
+ return 0;
+}
+
+unsigned char *_blkid_probe_get_sb(blkid_probe pr, const struct blkid_idmag *mag, size_t size)
+{
+ uint64_t hint_offset;
+
+ if (!mag->hoff || blkid_probe_get_hint(pr, mag->hoff, &hint_offset) < 0)
+ hint_offset = 0;
+
+ return blkid_probe_get_buffer(pr, hint_offset + (mag->kboff << 10), size);
+}
+
+/*
+ * Check for matching magic value.
+ * Returns BLKID_PROBE_OK if found, BLKID_PROBE_NONE if not found
+ * or no magic present, or negative value on error.
+ */
+int blkid_probe_get_idmag(blkid_probe pr, const struct blkid_idinfo *id,
+ uint64_t *offset, const struct blkid_idmag **res)
+{
+ const struct blkid_idmag *mag = NULL;
+ uint64_t off = 0;
+
+ if (id)
+ mag = &id->magics[0];
+ if (res)
+ *res = NULL;
+
+ /* try to detect by magic string */
+ while(mag && mag->magic) {
+ unsigned char *buf;
+ uint64_t kboff;
+ uint64_t hint_offset;
+
+ if (!mag->hoff || blkid_probe_get_hint(pr, mag->hoff, &hint_offset) < 0)
+ hint_offset = 0;
+
+ /* If the magic is for zoned device, skip non-zoned device */
+ if (mag->is_zoned && !pr->zone_size) {
+ mag++;
+ continue;
+ }
+
+ if (!mag->is_zoned)
+ kboff = mag->kboff;
+ else
+ kboff = ((mag->zonenum * pr->zone_size) >> 10) + mag->kboff_inzone;
+
+ off = hint_offset + ((kboff + (mag->sboff >> 10)) << 10);
+ buf = blkid_probe_get_buffer(pr, off, 1024);
+
+ if (!buf && errno)
+ return -errno;
+
+ if (buf && !memcmp(mag->magic,
+ buf + (mag->sboff & 0x3ff), mag->len)) {
+
+ DBG(LOWPROBE, ul_debug("\tmagic sboff=%u, kboff=%" PRIu64,
+ mag->sboff, kboff));
+ if (offset)
+ *offset = off + (mag->sboff & 0x3ff);
+ if (res)
+ *res = mag;
+ return BLKID_PROBE_OK;
+ }
+ mag++;
+ }
+
+ if (id && id->magics[0].magic)
+ /* magic string(s) defined, but not found */
+ return BLKID_PROBE_NONE;
+
+ return BLKID_PROBE_OK;
+}
+
+static inline void blkid_probe_start(blkid_probe pr)
+{
+ DBG(LOWPROBE, ul_debug("start probe"));
+ pr->cur_chain = NULL;
+ pr->prob_flags = 0;
+ blkid_probe_set_wiper(pr, 0, 0);
+}
+
+static inline void blkid_probe_end(blkid_probe pr)
+{
+ DBG(LOWPROBE, ul_debug("end probe"));
+ pr->cur_chain = NULL;
+ pr->prob_flags = 0;
+ blkid_probe_set_wiper(pr, 0, 0);
+}
+
+/**
+ * blkid_do_probe:
+ * @pr: prober
+ *
+ * Calls probing functions in all enabled chains. The superblocks chain is
+ * enabled by default. The blkid_do_probe() stores result from only one
+ * probing function. It's necessary to call this routine in a loop to get
+ * results from all probing functions in all chains. The probing is reset
+ * by blkid_reset_probe() or by filter functions.
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * <example>
+ * <title>basic case - use the first result only</title>
+ * <programlisting>
+ * if (blkid_do_probe(pr) == 0) {
+ * int nvals = blkid_probe_numof_values(pr);
+ * for (n = 0; n < nvals; n++) {
+ * if (blkid_probe_get_value(pr, n, &name, &data, &len) == 0)
+ * printf("%s = %s\n", name, data);
+ * }
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * <example>
+ * <title>advanced case - probe for all signatures</title>
+ * <programlisting>
+ * while (blkid_do_probe(pr) == 0) {
+ * int nvals = blkid_probe_numof_values(pr);
+ * ...
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * See also blkid_reset_probe().
+ *
+ * Returns: 0 on success, 1 when probing is done and -1 in case of error.
+ */
+int blkid_do_probe(blkid_probe pr)
+{
+ int rc = 1;
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return 1;
+
+ do {
+ struct blkid_chain *chn = pr->cur_chain;
+
+ if (!chn) {
+ blkid_probe_start(pr);
+ chn = pr->cur_chain = &pr->chains[0];
+ }
+ /* we go to the next chain only when the previous probing
+ * result was nothing (rc == 1) and when the current chain is
+ * disabled or we are at end of the current chain (chain->idx +
+ * 1 == sizeof chain) or the current chain bailed out right at
+ * the start (chain->idx == -1)
+ */
+ else if (rc == 1 && (chn->enabled == FALSE ||
+ chn->idx + 1 == (int) chn->driver->nidinfos ||
+ chn->idx == -1)) {
+
+ size_t idx = chn->driver->id + 1;
+
+ if (idx < BLKID_NCHAINS)
+ chn = pr->cur_chain = &pr->chains[idx];
+ else {
+ blkid_probe_end(pr);
+ return 1; /* all chains already probed */
+ }
+ }
+
+ chn->binary = FALSE; /* for sure... */
+
+ DBG(LOWPROBE, ul_debug("chain probe %s %s (idx=%d)",
+ chn->driver->name,
+ chn->enabled? "ENABLED" : "DISABLED",
+ chn->idx));
+
+ if (!chn->enabled)
+ continue;
+
+ /* rc: -1 = error, 0 = success, 1 = no result */
+ rc = chn->driver->probe(pr, chn);
+
+ } while (rc == 1);
+
+ return rc;
+}
+
+#ifdef HAVE_LINUX_BLKZONED_H
+static int is_conventional(blkid_probe pr, uint64_t offset)
+{
+ struct blk_zone_report *rep = NULL;
+ int ret;
+ uint64_t zone_mask;
+
+ if (!pr->zone_size)
+ return 1;
+
+ zone_mask = ~(pr->zone_size - 1);
+ rep = blkdev_get_zonereport(blkid_probe_get_fd(pr),
+ (offset & zone_mask) >> 9, 1);
+ if (!rep)
+ return -1;
+
+ if (rep->zones[0].type == BLK_ZONE_TYPE_CONVENTIONAL)
+ ret = 1;
+ else
+ ret = 0;
+
+ free(rep);
+
+ return ret;
+}
+#else
+static inline int is_conventional(blkid_probe pr __attribute__((__unused__)),
+ uint64_t offset __attribute__((__unused__)))
+{
+ return 1;
+}
+#endif
+
+/**
+ * blkid_do_wipe:
+ * @pr: prober
+ * @dryrun: if TRUE then don't touch the device.
+ *
+ * This function erases the current signature detected by @pr. The @pr has to
+ * be open in O_RDWR mode, BLKID_SUBLKS_MAGIC or/and BLKID_PARTS_MAGIC flags
+ * has to be enabled (if you want to erase also superblock with broken check
+ * sums then use BLKID_SUBLKS_BADCSUM too).
+ *
+ * After successful signature removing the @pr prober will be moved one step
+ * back and the next blkid_do_probe() call will again call previously called
+ * probing function. All in-memory cached data from the device are always
+ * reset.
+ *
+ * <example>
+ * <title>wipe all filesystems or raids from the device</title>
+ * <programlisting>
+ * fd = open(devname, O_RDWR|O_CLOEXEC);
+ * blkid_probe_set_device(pr, fd, 0, 0);
+ *
+ * blkid_probe_enable_superblocks(pr, 1);
+ * blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC);
+ *
+ * while (blkid_do_probe(pr) == 0)
+ * blkid_do_wipe(pr, FALSE);
+ * </programlisting>
+ * </example>
+ *
+ * See also blkid_probe_step_back() if you cannot use this built-in wipe
+ * function, but you want to use libblkid probing as a source for wiping.
+ *
+ * Returns: 0 on success, and -1 in case of error.
+ */
+int blkid_do_wipe(blkid_probe pr, int dryrun)
+{
+ const char *off = NULL;
+ size_t len = 0;
+ uint64_t offset, magoff;
+ int conventional;
+ char buf[BUFSIZ];
+ int fd, rc = 0;
+ struct blkid_chain *chn;
+
+ chn = pr->cur_chain;
+ if (!chn)
+ return -1;
+
+ switch (chn->driver->id) {
+ case BLKID_CHAIN_SUBLKS:
+ rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL);
+ if (!rc)
+ rc = blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len);
+ break;
+ case BLKID_CHAIN_PARTS:
+ rc = blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &off, NULL);
+ if (!rc)
+ rc = blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len);
+ break;
+ default:
+ return 0;
+ }
+
+ if (rc || len == 0 || off == NULL)
+ return 0;
+
+ errno = 0;
+ magoff = strtoumax(off, NULL, 10);
+ if (errno)
+ return 0;
+
+ offset = magoff + pr->off;
+ fd = blkid_probe_get_fd(pr);
+ if (fd < 0)
+ return -1;
+
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ rc = is_conventional(pr, offset);
+ if (rc < 0)
+ return rc;
+ conventional = rc == 1;
+
+ DBG(LOWPROBE, ul_debug(
+ "do_wipe [offset=0x%"PRIx64" (%"PRIu64"), len=%zu, chain=%s, idx=%d, dryrun=%s]\n",
+ offset, offset, len, chn->driver->name, chn->idx, dryrun ? "yes" : "not"));
+
+ if (lseek(fd, offset, SEEK_SET) == (off_t) -1)
+ return -1;
+
+ if (!dryrun && len) {
+ if (conventional) {
+ memset(buf, 0, len);
+
+ /* wipen on device */
+ if (write_all(fd, buf, len))
+ return -1;
+ fsync(fd);
+ } else {
+#ifdef HAVE_LINUX_BLKZONED_H
+ uint64_t zone_mask = ~(pr->zone_size - 1);
+ struct blk_zone_range range = {
+ .sector = (offset & zone_mask) >> 9,
+ .nr_sectors = pr->zone_size >> 9,
+ };
+
+ rc = ioctl(fd, BLKRESETZONE, &range);
+ if (rc < 0)
+ return -1;
+#else
+ /* Should not reach here */
+ assert(0);
+#endif
+ }
+
+ pr->flags &= ~BLKID_FL_MODIF_BUFF; /* be paranoid */
+
+ return blkid_probe_step_back(pr);
+
+ }
+
+ if (dryrun) {
+ /* wipe in memory only */
+ blkid_probe_hide_range(pr, magoff, len);
+ return blkid_probe_step_back(pr);
+ }
+
+ return 0;
+}
+
+/**
+ * blkid_probe_step_back:
+ * @pr: prober
+ *
+ * This function move pointer to the probing chain one step back -- it means
+ * that the previously used probing function will be called again in the next
+ * blkid_do_probe() call.
+ *
+ * This is necessary for example if you erase or modify on-disk superblock
+ * according to the current libblkid probing result.
+ *
+ * Note that blkid_probe_hide_range() changes semantic of this function and
+ * cached buffers are not reset, but library uses in-memory modified
+ * buffers to call the next probing function.
+ *
+ * <example>
+ * <title>wipe all superblock, but use libblkid only for probing</title>
+ * <programlisting>
+ * pr = blkid_new_probe_from_filename(devname);
+ *
+ * blkid_probe_enable_superblocks(pr, 1);
+ * blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC);
+ *
+ * blkid_probe_enable_partitions(pr, 1);
+ * blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC);
+ *
+ * while (blkid_do_probe(pr) == 0) {
+ * const char *ostr = NULL;
+ * size_t len = 0;
+ *
+ * // superblocks
+ * if (blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &ostr, NULL) == 0)
+ * blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len);
+ *
+ * // partition tables
+ * if (len == 0 && blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &ostr, NULL) == 0)
+ * blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len);
+ *
+ * if (!len || !str)
+ * continue;
+ *
+ * // convert ostr to the real offset by off = strtoll(ostr, NULL, 10);
+ * // use your stuff to erase @len bytes at the @off
+ * ....
+ *
+ * // retry the last probing to check for backup superblocks ..etc.
+ * blkid_probe_step_back(pr);
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * Returns: 0 on success, and -1 in case of error.
+ */
+int blkid_probe_step_back(blkid_probe pr)
+{
+ struct blkid_chain *chn;
+
+ chn = pr->cur_chain;
+ if (!chn)
+ return -1;
+
+ if (!(pr->flags & BLKID_FL_MODIF_BUFF))
+ blkid_probe_reset_buffers(pr);
+
+ if (chn->idx >= 0) {
+ chn->idx--;
+ DBG(LOWPROBE, ul_debug("step back: moving %s chain index to %d",
+ chn->driver->name,
+ chn->idx));
+ }
+
+ if (chn->idx == -1) {
+ /* blkid_do_probe() goes to the next chain if the index
+ * of the current chain is -1, so we have to set the
+ * chain pointer to the previous chain.
+ */
+ size_t idx = chn->driver->id > 0 ? chn->driver->id - 1 : 0;
+
+ DBG(LOWPROBE, ul_debug("step back: moving to previous chain"));
+
+ if (idx > 0)
+ pr->cur_chain = &pr->chains[idx];
+ else if (idx == 0)
+ pr->cur_chain = NULL;
+ }
+
+ return 0;
+}
+
+/**
+ * blkid_do_safeprobe:
+ * @pr: prober
+ *
+ * This function gathers probing results from all enabled chains and checks
+ * for ambivalent results (e.g. more filesystems on the device).
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * Note about superblocks chain -- the function does not check for filesystems
+ * when a RAID signature is detected. The function also does not check for
+ * collision between RAIDs. The first detected RAID is returned. The function
+ * checks for collision between partition table and RAID signature -- it's
+ * recommended to enable partitions chain together with superblocks chain.
+ *
+ * Returns: 0 on success, 1 if nothing is detected, -2 if ambivalent result is
+ * detected and -1 on case of error.
+ */
+int blkid_do_safeprobe(blkid_probe pr)
+{
+ int i, count = 0, rc = 0;
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return 1;
+
+ blkid_probe_start(pr);
+
+ for (i = 0; i < BLKID_NCHAINS; i++) {
+ struct blkid_chain *chn;
+
+ chn = pr->cur_chain = &pr->chains[i];
+ chn->binary = FALSE; /* for sure... */
+
+ DBG(LOWPROBE, ul_debug("chain safeprobe %s %s",
+ chn->driver->name,
+ chn->enabled? "ENABLED" : "DISABLED"));
+
+ if (!chn->enabled)
+ continue;
+
+ blkid_probe_chain_reset_position(chn);
+
+ rc = chn->driver->safeprobe(pr, chn);
+
+ blkid_probe_chain_reset_position(chn);
+
+ /* rc: -2 ambivalent, -1 = error, 0 = success, 1 = no result */
+ if (rc < 0)
+ goto done; /* error */
+ if (rc == 0)
+ count++; /* success */
+ }
+
+done:
+ blkid_probe_end(pr);
+ if (rc < 0)
+ return rc;
+ return count ? 0 : 1;
+}
+
+/**
+ * blkid_do_fullprobe:
+ * @pr: prober
+ *
+ * This function gathers probing results from all enabled chains. Same as
+ * blkid_do_safeprobe() but does not check for collision between probing
+ * result.
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * Returns: 0 on success, 1 if nothing is detected or -1 on case of error.
+ */
+int blkid_do_fullprobe(blkid_probe pr)
+{
+ int i, count = 0, rc = 0;
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return 1;
+
+ blkid_probe_start(pr);
+
+ for (i = 0; i < BLKID_NCHAINS; i++) {
+ struct blkid_chain *chn;
+
+ chn = pr->cur_chain = &pr->chains[i];
+ chn->binary = FALSE; /* for sure... */
+
+ DBG(LOWPROBE, ul_debug("chain fullprobe %s: %s",
+ chn->driver->name,
+ chn->enabled? "ENABLED" : "DISABLED"));
+
+ if (!chn->enabled)
+ continue;
+
+ blkid_probe_chain_reset_position(chn);
+
+ rc = chn->driver->probe(pr, chn);
+
+ blkid_probe_chain_reset_position(chn);
+
+ /* rc: -1 = error, 0 = success, 1 = no result */
+ if (rc < 0)
+ goto done; /* error */
+ if (rc == 0)
+ count++; /* success */
+ }
+
+done:
+ blkid_probe_end(pr);
+ if (rc < 0)
+ return rc;
+ return count ? 0 : 1;
+}
+
+/* same sa blkid_probe_get_buffer() but works with 512-sectors */
+unsigned char *blkid_probe_get_sector(blkid_probe pr, unsigned int sector)
+{
+ return blkid_probe_get_buffer(pr, ((uint64_t) sector) << 9, 0x200);
+}
+
+struct blkid_prval *blkid_probe_assign_value(blkid_probe pr, const char *name)
+{
+ struct blkid_prval *v;
+
+ v = calloc(1, sizeof(struct blkid_prval));
+ if (!v)
+ return NULL;
+
+ INIT_LIST_HEAD(&v->prvals);
+ v->name = name;
+ v->chain = pr->cur_chain;
+ list_add_tail(&v->prvals, &pr->values);
+
+ DBG(LOWPROBE, ul_debug("assigning %s [%s]", name, v->chain->driver->name));
+ return v;
+}
+
+/* Note that value data is always terminated by zero to keep things robust,
+ * this extra zero is not count to the value length. It's caller responsibility
+ * to set proper value length (for strings we count terminator to the length,
+ * for binary data it's without terminator).
+ */
+int blkid_probe_value_set_data(struct blkid_prval *v,
+ const unsigned char *data, size_t len)
+{
+ v->data = calloc(1, len + 1); /* always terminate by \0 */
+
+ if (!v->data)
+ return -ENOMEM;
+ memcpy(v->data, data, len);
+ v->len = len;
+ return 0;
+}
+
+int blkid_probe_set_value(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len)
+{
+ struct blkid_prval *v;
+
+ v = blkid_probe_assign_value(pr, name);
+ if (!v)
+ return -1;
+
+ return blkid_probe_value_set_data(v, data, len);
+}
+
+int blkid_probe_vsprintf_value(blkid_probe pr, const char *name,
+ const char *fmt, va_list ap)
+{
+ struct blkid_prval *v;
+ ssize_t len;
+
+ v = blkid_probe_assign_value(pr, name);
+ if (!v)
+ return -ENOMEM;
+
+ len = vasprintf((char **) &v->data, fmt, ap);
+
+ if (len <= 0) {
+ blkid_probe_free_value(v);
+ return len == 0 ? -EINVAL : -ENOMEM;
+ }
+ v->len = len + 1;
+ return 0;
+}
+
+int blkid_probe_sprintf_value(blkid_probe pr, const char *name,
+ const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ va_start(ap, fmt);
+ rc = blkid_probe_vsprintf_value(pr, name, fmt, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+int blkid_probe_set_magic(blkid_probe pr, uint64_t offset,
+ size_t len, const unsigned char *magic)
+{
+ int rc = 0;
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (!chn || !len || chn->binary)
+ return 0;
+
+ switch (chn->driver->id) {
+ case BLKID_CHAIN_SUBLKS:
+ if (!(chn->flags & BLKID_SUBLKS_MAGIC))
+ return 0;
+ rc = blkid_probe_set_value(pr, "SBMAGIC", magic, len);
+ if (!rc)
+ rc = blkid_probe_sprintf_value(pr,
+ "SBMAGIC_OFFSET", "%llu", (unsigned long long)offset);
+ break;
+ case BLKID_CHAIN_PARTS:
+ if (!(chn->flags & BLKID_PARTS_MAGIC))
+ return 0;
+ rc = blkid_probe_set_value(pr, "PTMAGIC", magic, len);
+ if (!rc)
+ rc = blkid_probe_sprintf_value(pr,
+ "PTMAGIC_OFFSET", "%llu", (unsigned long long)offset);
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+int blkid_probe_verify_csum(blkid_probe pr, uint64_t csum, uint64_t expected)
+{
+ if (csum != expected) {
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ DBG(LOWPROBE, ul_debug(
+ "incorrect checksum for type %s,"
+ " got %"PRIX64", expected %"PRIX64"",
+ blkid_probe_get_probername(pr),
+ csum, expected));
+ /*
+ * Accept bad checksum if BLKID_SUBLKS_BADCSUM flags is set
+ */
+ if (chn->driver->id == BLKID_CHAIN_SUBLKS
+ && (chn->flags & BLKID_SUBLKS_BADCSUM)) {
+ blkid_probe_set_value(pr, "SBBADCSUM", (unsigned char *) "1", 2);
+ goto accept;
+ }
+ return 0; /* bad checksum */
+ }
+
+accept:
+ return 1;
+}
+
+/**
+ * blkid_probe_get_devno:
+ * @pr: probe
+ *
+ * Returns: block device number, or 0 for regular files.
+ */
+dev_t blkid_probe_get_devno(blkid_probe pr)
+{
+ return pr->devno;
+}
+
+/**
+ * blkid_probe_get_wholedisk_devno:
+ * @pr: probe
+ *
+ * Returns: device number of the wholedisk, or 0 for regular files.
+ */
+dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr)
+{
+ if (!pr->disk_devno) {
+ dev_t devno, disk_devno = 0;
+
+ devno = blkid_probe_get_devno(pr);
+ if (!devno)
+ return 0;
+
+ if (blkid_devno_to_wholedisk(devno, NULL, 0, &disk_devno) == 0)
+ pr->disk_devno = disk_devno;
+ }
+ return pr->disk_devno;
+}
+
+/**
+ * blkid_probe_is_wholedisk:
+ * @pr: probe
+ *
+ * Returns: 1 if the device is whole-disk or 0.
+ */
+int blkid_probe_is_wholedisk(blkid_probe pr)
+{
+ dev_t devno, disk_devno;
+
+ devno = blkid_probe_get_devno(pr);
+ if (!devno)
+ return 0;
+
+ disk_devno = blkid_probe_get_wholedisk_devno(pr);
+ if (!disk_devno)
+ return 0;
+
+ return devno == disk_devno;
+}
+
+blkid_probe blkid_probe_get_wholedisk_probe(blkid_probe pr)
+{
+ dev_t disk;
+
+ if (blkid_probe_is_wholedisk(pr))
+ return NULL; /* this is not partition */
+
+ if (pr->parent)
+ /* this is cloned blkid_probe, use parent's stuff */
+ return blkid_probe_get_wholedisk_probe(pr->parent);
+
+ disk = blkid_probe_get_wholedisk_devno(pr);
+
+ if (pr->disk_probe && pr->disk_probe->devno != disk) {
+ /* we have disk prober, but for another disk... close it */
+ blkid_free_probe(pr->disk_probe);
+ pr->disk_probe = NULL;
+ }
+
+ if (!pr->disk_probe) {
+ /* Open a new disk prober */
+ char *disk_path = blkid_devno_to_devname(disk);
+
+ if (!disk_path)
+ return NULL;
+
+ DBG(LOWPROBE, ul_debug("allocate a wholedisk probe"));
+
+ pr->disk_probe = blkid_new_probe_from_filename(disk_path);
+
+ free(disk_path);
+
+ if (!pr->disk_probe)
+ return NULL; /* ENOMEM? */
+ }
+
+ return pr->disk_probe;
+}
+
+/**
+ * blkid_probe_get_size:
+ * @pr: probe
+ *
+ * This function returns size of probing area as defined by blkid_probe_set_device().
+ * If the size of the probing area is unrestricted then this function returns
+ * the real size of device. See also blkid_get_dev_size().
+ *
+ * Returns: size in bytes or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_size(blkid_probe pr)
+{
+ return (blkid_loff_t) pr->size;
+}
+
+/**
+ * blkid_probe_get_offset:
+ * @pr: probe
+ *
+ * This function returns offset of probing area as defined by blkid_probe_set_device().
+ *
+ * Returns: offset in bytes or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_offset(blkid_probe pr)
+{
+ return (blkid_loff_t) pr->off;
+}
+
+/**
+ * blkid_probe_get_fd:
+ * @pr: probe
+ *
+ * Returns: file descriptor for assigned device/file or -1 in case of error.
+ */
+int blkid_probe_get_fd(blkid_probe pr)
+{
+ return pr->fd;
+}
+
+/**
+ * blkid_probe_get_sectorsize:
+ * @pr: probe or NULL (for NULL returns 512)
+ *
+ * Returns: block device logical sector size (BLKSSZGET ioctl, default 512).
+ */
+unsigned int blkid_probe_get_sectorsize(blkid_probe pr)
+{
+ if (pr->blkssz)
+ return pr->blkssz;
+
+ if (S_ISBLK(pr->mode) &&
+ blkdev_get_sector_size(pr->fd, (int *) &pr->blkssz) == 0)
+ return pr->blkssz;
+
+ pr->blkssz = DEFAULT_SECTOR_SIZE;
+ return pr->blkssz;
+}
+
+/**
+ * blkid_probe_set_sectorsize:
+ * @pr: probe
+ * @sz: new size (to overwrite system default)
+ *
+ * Note that blkid_probe_set_device() resets this setting. Use it after
+ * blkid_probe_set_device() and before any probing call.
+ *
+ * Since: 2.30
+ *
+ * Returns: 0 or <0 in case of error
+ */
+int blkid_probe_set_sectorsize(blkid_probe pr, unsigned int sz)
+{
+ pr->blkssz = sz;
+ return 0;
+}
+
+/**
+ * blkid_probe_get_sectors:
+ * @pr: probe
+ *
+ * Returns: 512-byte sector count or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_sectors(blkid_probe pr)
+{
+ return (blkid_loff_t) (pr->size >> 9);
+}
+
+/**
+ * blkid_probe_numof_values:
+ * @pr: probe
+ *
+ * Returns: number of values in probing result or -1 in case of error.
+ */
+int blkid_probe_numof_values(blkid_probe pr)
+{
+ int i = 0;
+ struct list_head *p;
+
+ list_for_each(p, &pr->values)
+ ++i;
+ return i;
+}
+
+/**
+ * blkid_probe_get_value:
+ * @pr: probe
+ * @num: wanted value in range 0..N, where N is blkid_probe_numof_values() - 1
+ * @name: pointer to return value name or NULL
+ * @data: pointer to return value data or NULL
+ * @len: pointer to return value length or NULL
+ *
+ * Note, the @len returns length of the @data, including the terminating
+ * '\0' character.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_get_value(blkid_probe pr, int num, const char **name,
+ const char **data, size_t *len)
+{
+ struct blkid_prval *v = __blkid_probe_get_value(pr, num);
+
+ if (!v)
+ return -1;
+ if (name)
+ *name = v->name;
+ if (data)
+ *data = (char *) v->data;
+ if (len)
+ *len = v->len;
+
+ DBG(LOWPROBE, ul_debug("returning %s value", v->name));
+ return 0;
+}
+
+/**
+ * blkid_probe_lookup_value:
+ * @pr: probe
+ * @name: name of value
+ * @data: pointer to return value data or NULL
+ * @len: pointer to return value length or NULL
+ *
+ * Note, the @len returns length of the @data, including the terminating
+ * '\0' character.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_lookup_value(blkid_probe pr, const char *name,
+ const char **data, size_t *len)
+{
+ struct blkid_prval *v = __blkid_probe_lookup_value(pr, name);
+
+ if (!v)
+ return -1;
+ if (data)
+ *data = (char *) v->data;
+ if (len)
+ *len = v->len;
+ return 0;
+}
+
+/**
+ * blkid_probe_has_value:
+ * @pr: probe
+ * @name: name of value
+ *
+ * Returns: 1 if value exist in probing result, otherwise 0.
+ */
+int blkid_probe_has_value(blkid_probe pr, const char *name)
+{
+ if (blkid_probe_lookup_value(pr, name, NULL, NULL) == 0)
+ return 1;
+ return 0;
+}
+
+struct blkid_prval *__blkid_probe_get_value(blkid_probe pr, int num)
+{
+ int i = 0;
+ struct list_head *p;
+
+ if (num < 0)
+ return NULL;
+
+ list_for_each(p, &pr->values) {
+ if (i++ != num)
+ continue;
+ return list_entry(p, struct blkid_prval, prvals);
+ }
+ return NULL;
+}
+
+struct blkid_prval *__blkid_probe_lookup_value(blkid_probe pr, const char *name)
+{
+ struct list_head *p;
+
+ if (list_empty(&pr->values))
+ return NULL;
+
+ list_for_each(p, &pr->values) {
+ struct blkid_prval *v = list_entry(p, struct blkid_prval,
+ prvals);
+
+ if (v->name && strcmp(name, v->name) == 0) {
+ DBG(LOWPROBE, ul_debug("returning %s value", v->name));
+ return v;
+ }
+ }
+ return NULL;
+}
+
+
+/* converts DCE UUID (uuid[16]) to human readable string
+ * - the @len should be always 37 */
+void blkid_unparse_uuid(const unsigned char *uuid, char *str, size_t len)
+{
+ snprintf(str, len,
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid[0], uuid[1], uuid[2], uuid[3],
+ uuid[4], uuid[5],
+ uuid[6], uuid[7],
+ uuid[8], uuid[9],
+ uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]);
+}
+
+/* like uuid_is_null() from libuuid, but works with arbitrary size of UUID */
+int blkid_uuid_is_empty(const unsigned char *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ if (buf[i])
+ return 0;
+ return 1;
+}
+
+/* Removes whitespace from the right-hand side of a string (trailing
+ * whitespace).
+ *
+ * Returns size of the new string (without \0).
+ */
+size_t blkid_rtrim_whitespace(unsigned char *str)
+{
+ return rtrim_whitespace(str);
+}
+
+/* Removes whitespace from the left-hand side of a string.
+ *
+ * Returns size of the new string (without \0).
+ */
+size_t blkid_ltrim_whitespace(unsigned char *str)
+{
+ return ltrim_whitespace(str);
+}
+
+/*
+ * Some mkfs-like utils wipe some parts (usually begin) of the device.
+ * For example LVM (pvcreate) or mkswap(8). This information could be used
+ * for later resolution to conflicts between superblocks.
+ *
+ * For example we found valid LVM superblock, LVM wipes 8KiB at the begin of
+ * the device. If we found another signature (for example MBR) within the
+ * wiped area then the signature has been added later and LVM superblock
+ * should be ignore.
+ *
+ * Note that this heuristic is not 100% reliable, for example "pvcreate --zero n"
+ * can be used to keep the begin of the device unmodified. It's probably better
+ * to use this heuristic for conflicts between superblocks and partition tables
+ * than for conflicts between filesystem superblocks -- existence of unwanted
+ * partition table is very unusual, because PT is pretty visible (parsed and
+ * interpreted by kernel).
+ *
+ * Note that we usually expect only one signature on the device, it means that
+ * we have to remember only one wiped area from previously successfully
+ * detected signature.
+ *
+ * blkid_probe_set_wiper() -- defines wiped area (e.g. LVM)
+ * blkid_probe_use_wiper() -- try to use area (e.g. MBR)
+ *
+ * Note that there is not relation between _wiper and blkid_to_wipe().
+ *
+ */
+void blkid_probe_set_wiper(blkid_probe pr, uint64_t off, uint64_t size)
+{
+ struct blkid_chain *chn;
+
+ if (!size) {
+ DBG(LOWPROBE, ul_debug("zeroize wiper"));
+ pr->wipe_size = pr->wipe_off = 0;
+ pr->wipe_chain = NULL;
+ return;
+ }
+
+ chn = pr->cur_chain;
+
+ if (!chn || !chn->driver ||
+ chn->idx < 0 || (size_t) chn->idx >= chn->driver->nidinfos)
+ return;
+
+ pr->wipe_size = size;
+ pr->wipe_off = off;
+ pr->wipe_chain = chn;
+
+ DBG(LOWPROBE,
+ ul_debug("wiper set to %s::%s off=%"PRIu64" size=%"PRIu64"",
+ chn->driver->name,
+ chn->driver->idinfos[chn->idx]->name,
+ pr->wipe_off, pr->wipe_size));
+}
+
+/*
+ * Returns 1 if the <@off,@size> area was wiped
+ */
+int blkid_probe_is_wiped(blkid_probe pr, struct blkid_chain **chn, uint64_t off, uint64_t size)
+{
+ if (!size)
+ return 0;
+
+ if (pr->wipe_off <= off && off + size <= pr->wipe_off + pr->wipe_size) {
+ *chn = pr->wipe_chain;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Try to use any area -- if the area has been previously wiped then the
+ * previous probing result should be ignored (reset).
+ */
+void blkid_probe_use_wiper(blkid_probe pr, uint64_t off, uint64_t size)
+{
+ struct blkid_chain *chn = NULL;
+
+ if (blkid_probe_is_wiped(pr, &chn, off, size) && chn) {
+ DBG(LOWPROBE, ul_debug("previously wiped area modified "
+ " -- ignore previous results"));
+ blkid_probe_set_wiper(pr, 0, 0);
+ blkid_probe_chain_reset_values(pr, chn);
+ }
+}
+
+static struct blkid_hint *get_hint(blkid_probe pr, const char *name)
+{
+ struct list_head *p;
+
+ if (list_empty(&pr->hints))
+ return NULL;
+
+ list_for_each(p, &pr->hints) {
+ struct blkid_hint *h = list_entry(p, struct blkid_hint, hints);
+
+ if (h->name && strcmp(name, h->name) == 0)
+ return h;
+ }
+ return NULL;
+}
+
+/**
+ * blkid_probe_set_hint:
+ * @pr: probe
+ * @name: hint name or NAME=value
+ * @value: offset or another number
+ *
+ * Sets extra hint for low-level prober. If the hint is set by NAME=value
+ * notation than @value is ignored. The functions blkid_probe_set_device()
+ * and blkid_reset_probe() resets all hints.
+ *
+ * The hints are optional way how to force libblkid probing functions to check
+ * for example another location.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_set_hint(blkid_probe pr, const char *name, uint64_t value)
+{
+ struct blkid_hint *hint = NULL;
+ char *n = NULL, *v = NULL;
+
+ if (strchr(name, '=')) {
+ char *end = NULL;
+
+ if (blkid_parse_tag_string(name, &n, &v) != 0)
+ goto done;
+
+ errno = 0;
+ value = strtoumax(v, &end, 10);
+
+ if (errno || v == end || (end && *end))
+ goto done;
+ }
+
+ hint = get_hint(pr, n ? n : name);
+ if (hint) {
+ /* alter old hint */
+ hint->value = value;
+ DBG(LOWPROBE,
+ ul_debug("updated hint '%s' to %"PRIu64"", hint->name, hint->value));
+ } else {
+ /* add a new hint */
+ if (!n) {
+ n = strdup(name);
+ if (!n)
+ goto done;
+ }
+ hint = malloc(sizeof(*hint));
+ if (!hint)
+ goto done;
+
+ hint->name = n;
+ hint->value = value;
+
+ INIT_LIST_HEAD(&hint->hints);
+ list_add_tail(&hint->hints, &pr->hints);
+
+ DBG(LOWPROBE,
+ ul_debug("new hint '%s' is %"PRIu64"", hint->name, hint->value));
+ n = NULL;
+ }
+done:
+ free(n);
+ free(v);
+
+ if (!hint)
+ return errno ? -errno : -EINVAL;
+ return 0;
+}
+
+int blkid_probe_get_hint(blkid_probe pr, const char *name, uint64_t *value)
+{
+ struct blkid_hint *h = get_hint(pr, name);
+
+ if (!h)
+ return -EINVAL;
+ if (value)
+ *value = h->value;
+ return 0;
+}
+
+/**
+ * blkid_probe_reset_hints:
+ * @pr: probe
+ *
+ * Removes all previously defined probinig hints. See also blkid_probe_set_hint().
+ */
+void blkid_probe_reset_hints(blkid_probe pr)
+{
+ if (list_empty(&pr->hints))
+ return;
+
+ DBG(LOWPROBE, ul_debug("resetting hints"));
+
+ while (!list_empty(&pr->hints)) {
+ struct blkid_hint *h = list_entry(pr->hints.next,
+ struct blkid_hint, hints);
+ list_del(&h->hints);
+ free(h->name);
+ free(h);
+ }
+
+ INIT_LIST_HEAD(&pr->hints);
+}
diff --git a/libblkid/src/read.c b/libblkid/src/read.c
new file mode 100644
index 0000000..d62edfa
--- /dev/null
+++ b/libblkid/src/read.c
@@ -0,0 +1,455 @@
+/*
+ * read.c - read the blkid cache from disk, to avoid scanning all devices
+ *
+ * Copyright (C) 2001, 2003 Theodore Y. Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "blkidP.h"
+
+#ifdef HAVE_STDLIB_H
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600 /* for inclusion of strtoull */
+# endif
+# include <stdlib.h>
+#endif
+
+/*
+ * File format:
+ *
+ * <device [<NAME="value"> ...]>device_name</device>
+ *
+ * The following tags are required for each entry:
+ * <ID="id"> unique (within this file) ID number of this device
+ * <TIME="sec.usec"> (time_t and suseconds_t) time this entry was last
+ * read from disk
+ * <TYPE="type"> (detected) type of filesystem/data for this partition
+ *
+ * The following tags may be present, depending on the device contents
+ * <LABEL="label"> (user supplied) label (volume name, etc)
+ * <UUID="uuid"> (generated) universally unique identifier (serial no)
+ */
+
+static char *skip_over_blank(char *cp)
+{
+ while (*cp && isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+ char ch;
+
+ while ((ch = *cp)) {
+ /* If we see a backslash, skip the next character */
+ if (ch == '\\') {
+ cp++;
+ if (*cp == '\0')
+ break;
+ cp++;
+ continue;
+ }
+ if (isspace(ch) || ch == '<' || ch == '>')
+ break;
+ cp++;
+ }
+ return cp;
+}
+
+static char *strip_line(char *line)
+{
+ char *p;
+
+ line = skip_over_blank(line);
+
+ p = line + strlen(line) - 1;
+
+ while (*line) {
+ if (isspace(*p))
+ *p-- = '\0';
+ else
+ break;
+ }
+
+ return line;
+}
+
+#if 0
+static char *parse_word(char **buf)
+{
+ char *word, *next;
+
+ word = *buf;
+ if (*word == '\0')
+ return NULL;
+
+ word = skip_over_blank(word);
+ next = skip_over_word(word);
+ if (*next) {
+ char *end = next - 1;
+ if (*end == '"' || *end == '\'')
+ *end = '\0';
+ *next++ = '\0';
+ }
+ *buf = next;
+
+ if (*word == '"' || *word == '\'')
+ word++;
+ return word;
+}
+#endif
+
+/*
+ * Start parsing a new line from the cache.
+ *
+ * line starts with "<device" return 1 -> continue parsing line
+ * line starts with "<foo", empty, or # return 0 -> skip line
+ * line starts with other, return -BLKID_ERR_CACHE -> error
+ */
+static int parse_start(char **cp)
+{
+ char *p;
+
+ p = strip_line(*cp);
+
+ /* Skip comment or blank lines. We can't just NUL the first '#' char,
+ * in case it is inside quotes, or escaped.
+ */
+ if (*p == '\0' || *p == '#')
+ return 0;
+
+ if (!strncmp(p, "<device", 7)) {
+ DBG(READ, ul_debug("found device header: %8s", p));
+ p += 7;
+
+ *cp = p;
+ return 1;
+ }
+
+ if (*p == '<')
+ return 0;
+
+ return -BLKID_ERR_CACHE;
+}
+
+/* Consume the remaining XML on the line (cosmetic only) */
+static int parse_end(char **cp)
+{
+ *cp = skip_over_blank(*cp);
+
+ if (!strncmp(*cp, "</device>", 9)) {
+ DBG(READ, ul_debug("found device trailer %9s", *cp));
+ *cp += 9;
+ return 0;
+ }
+
+ return -BLKID_ERR_CACHE;
+}
+
+/*
+ * Allocate a new device struct with device name filled in. Will handle
+ * finding the device on lines of the form:
+ * <device foo=bar>devname</device>
+ * <device>devname<foo>bar</foo></device>
+ */
+static int parse_dev(blkid_cache cache, blkid_dev *dev, char **cp)
+{
+ char *start, *tmp, *end, *name;
+ int ret;
+
+ if ((ret = parse_start(cp)) <= 0)
+ return ret;
+
+ start = tmp = strchr(*cp, '>');
+ if (!start) {
+ DBG(READ, ul_debug("blkid: short line parsing dev: %s", *cp));
+ return -BLKID_ERR_CACHE;
+ }
+ start = skip_over_blank(start + 1);
+ end = skip_over_word(start);
+
+ DBG(READ, ul_debug("device should be %*s",
+ (int)(end - start), start));
+
+ if (**cp == '>')
+ *cp = end;
+ else
+ (*cp)++;
+
+ *tmp = '\0';
+
+ if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) {
+ DBG(READ, ul_debug("blkid: missing </device> ending: %s", end));
+ } else if (tmp)
+ *tmp = '\0';
+
+ if (end - start <= 1) {
+ DBG(READ, ul_debug("blkid: empty device name: %s", *cp));
+ return -BLKID_ERR_CACHE;
+ }
+
+ name = strndup(start, end - start);
+ if (name == NULL)
+ return -BLKID_ERR_MEM;
+
+ DBG(READ, ul_debug("found dev %s", name));
+
+ if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) {
+ free(name);
+ return -BLKID_ERR_MEM;
+ }
+
+ free(name);
+ return 1;
+}
+
+/*
+ * Extract a tag of the form NAME="value" from the line.
+ */
+static int parse_token(char **name, char **value, char **cp)
+{
+ char *end;
+
+ if (!name || !value || !cp)
+ return -BLKID_ERR_PARAM;
+
+ if (!(*value = strchr(*cp, '=')))
+ return 0;
+
+ **value = '\0';
+ *name = strip_line(*cp);
+ *value = skip_over_blank(*value + 1);
+
+ if (**value == '"') {
+ char *p = end = *value + 1;
+
+ /* convert 'foo\"bar' to 'foo"bar' */
+ while (*p) {
+ if (*p == '\\') {
+ p++;
+ *end = *p;
+ } else {
+ *end = *p;
+ if (*p == '"')
+ break;
+ }
+ p++;
+ end++;
+ }
+
+ if (*end != '"') {
+ DBG(READ, ul_debug("unbalanced quotes at: %s", *value));
+ *cp = *value;
+ return -BLKID_ERR_CACHE;
+ }
+ (*value)++;
+ *end = '\0';
+ end = ++p;
+ } else {
+ end = skip_over_word(*value);
+ if (*end) {
+ *end = '\0';
+ end++;
+ }
+ }
+ *cp = end;
+
+ return 1;
+}
+
+/*
+ * Extract a tag from the line.
+ *
+ * Return 1 if a valid tag was found.
+ * Return 0 if no tag found.
+ * Return -ve error code.
+ */
+static int parse_tag(blkid_cache cache, blkid_dev dev, char **cp)
+{
+ char *name = NULL;
+ char *value = NULL;
+ int ret;
+
+ if (!cache || !dev)
+ return -BLKID_ERR_PARAM;
+
+ if ((ret = parse_token(&name, &value, cp)) <= 0)
+ return ret;
+
+ DBG(READ, ul_debug("tag: %s=\"%s\"", name, value));
+
+ errno = 0;
+
+ /* Some tags are stored directly in the device struct */
+ if (!strcmp(name, "DEVNO")) {
+ dev->bid_devno = strtoull(value, NULL, 0);
+ if (errno)
+ return -errno;
+ } else if (!strcmp(name, "PRI")) {
+ dev->bid_pri = strtol(value, NULL, 0);
+ if (errno)
+ return -errno;
+ } else if (!strcmp(name, "TIME")) {
+ char *end = NULL;
+
+ dev->bid_time = strtoull(value, &end, 0);
+ if (errno == 0 && end && *end == '.')
+ dev->bid_utime = strtoull(end + 1, NULL, 0);
+ if (errno)
+ return -errno;
+ } else
+ ret = blkid_set_tag(dev, name, value, strlen(value));
+
+ return ret < 0 ? ret : 1;
+}
+
+/*
+ * Parse a single line of data, and return a newly allocated dev struct.
+ * Add the new device to the cache struct, if one was read.
+ *
+ * Lines are of the form <device [TAG="value" ...]>/dev/foo</device>
+ *
+ * Returns -ve value on error.
+ * Returns 0 otherwise.
+ * If a valid device was read, *dev_p is non-NULL, otherwise it is NULL
+ * (e.g. comment lines, unknown XML content, etc).
+ */
+static int blkid_parse_line(blkid_cache cache, blkid_dev *dev_p, char *cp)
+{
+ blkid_dev dev;
+ int ret;
+
+ if (!cache || !dev_p)
+ return -BLKID_ERR_PARAM;
+
+ *dev_p = NULL;
+
+ DBG(READ, ul_debug("line: %s", cp));
+
+ if ((ret = parse_dev(cache, dev_p, &cp)) <= 0)
+ return ret;
+
+ dev = *dev_p;
+
+ while ((ret = parse_tag(cache, dev, &cp)) > 0) {
+ ;
+ }
+
+ if (dev->bid_type == NULL) {
+ DBG(READ, ul_debug("blkid: device %s has no TYPE",dev->bid_name));
+ blkid_free_dev(dev);
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
+/*
+ * Parse the specified filename, and return the data in the supplied or
+ * a newly allocated cache struct. If the file doesn't exist, return a
+ * new empty cache struct.
+ */
+void blkid_read_cache(blkid_cache cache)
+{
+ FILE *file;
+ char buf[4096];
+ int fd, lineno = 0;
+ struct stat st;
+
+ /*
+ * If the file doesn't exist, then we just return an empty
+ * struct so that the cache can be populated.
+ */
+ if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0)
+ return;
+ if (fstat(fd, &st) < 0)
+ goto errout;
+ if ((st.st_mtime == cache->bic_ftime) ||
+ (cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
+ DBG(CACHE, ul_debug("skipping re-read of %s",
+ cache->bic_filename));
+ goto errout;
+ }
+
+ DBG(CACHE, ul_debug("reading cache file %s",
+ cache->bic_filename));
+
+ file = fdopen(fd, "r" UL_CLOEXECSTR);
+ if (!file)
+ goto errout;
+
+ while (fgets(buf, sizeof(buf), file)) {
+ blkid_dev dev;
+ unsigned int end;
+
+ lineno++;
+ if (buf[0] == 0)
+ continue;
+ end = strlen(buf) - 1;
+ /* Continue reading next line if it ends with a backslash */
+ while (end < (sizeof(buf) - 2) && buf[end] == '\\' &&
+ fgets(buf + end, sizeof(buf) - end, file)) {
+ end = strlen(buf) - 1;
+ lineno++;
+ }
+
+ if (blkid_parse_line(cache, &dev, buf) < 0) {
+ DBG(READ, ul_debug("blkid: bad format on line %d", lineno));
+ continue;
+ }
+ }
+ fclose(file);
+
+ /*
+ * Initially we do not need to write out the cache file.
+ */
+ cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
+ cache->bic_ftime = st.st_mtime;
+
+ return;
+errout:
+ close(fd);
+}
+
+#ifdef TEST_PROGRAM
+
+int main(int argc, char**argv)
+{
+ blkid_cache cache = NULL;
+ int ret;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+ if (argc > 2) {
+ fprintf(stderr, "Usage: %s [filename]\n"
+ "Test parsing of the cache (filename)\n", argv[0]);
+ exit(1);
+ }
+ if ((ret = blkid_get_cache(&cache, argv[1])) < 0)
+ fprintf(stderr, "error %d reading cache file %s\n", ret,
+ argv[1] ? argv[1] : blkid_get_cache_filename(NULL));
+
+ blkid_put_cache(cache);
+
+ return ret;
+}
+#endif
diff --git a/libblkid/src/resolve.c b/libblkid/src/resolve.c
new file mode 100644
index 0000000..16653fa
--- /dev/null
+++ b/libblkid/src/resolve.c
@@ -0,0 +1,129 @@
+/*
+ * resolve.c - resolve names and tags into specific devices
+ *
+ * Copyright (C) 2001, 2003 Theodore Ts'o.
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "blkidP.h"
+
+/*
+ * Find a tagname (e.g. LABEL or UUID) on a specific device.
+ */
+char *blkid_get_tag_value(blkid_cache cache, const char *tagname,
+ const char *devname)
+{
+ blkid_tag found;
+ blkid_dev dev;
+ blkid_cache c = cache;
+ char *ret = NULL;
+
+ DBG(TAG, ul_debug("looking for tag %s on %s device", tagname, devname));
+
+ if (!devname)
+ return NULL;
+ if (!cache && blkid_get_cache(&c, NULL) < 0)
+ return NULL;
+
+ if ((dev = blkid_get_dev(c, devname, BLKID_DEV_NORMAL)) &&
+ (found = blkid_find_tag_dev(dev, tagname)))
+ ret = found->bit_val ? strdup(found->bit_val) : NULL;
+
+ if (!cache)
+ blkid_put_cache(c);
+
+ return ret;
+}
+
+/*
+ * Locate a device name from a token (NAME=value string), or (name, value)
+ * pair. In the case of a token, value is ignored. If the "token" is not
+ * of the form "NAME=value" and there is no value given, then it is assumed
+ * to be the actual devname and a copy is returned.
+ */
+char *blkid_get_devname(blkid_cache cache, const char *token,
+ const char *value)
+{
+ blkid_dev dev;
+ blkid_cache c = cache;
+ char *t = NULL, *v = NULL;
+ char *ret = NULL;
+
+ if (!token)
+ return NULL;
+ if (!cache && blkid_get_cache(&c, NULL) < 0)
+ return NULL;
+
+ DBG(TAG, ul_debug("looking for %s%s%s %s", token, value ? "=" : "",
+ value ? value : "", cache ? "in cache" : "from disk"));
+
+ if (!value) {
+ if (!strchr(token, '=')) {
+ ret = strdup(token);
+ goto out;
+ }
+ if (blkid_parse_tag_string(token, &t, &v) != 0 || !t || !v)
+ goto out;
+ token = t;
+ value = v;
+ }
+
+ dev = blkid_find_dev_with_tag(c, token, value);
+ if (!dev)
+ goto out;
+
+ ret = dev->bid_name ? strdup(dev->bid_name) : NULL;
+out:
+ free(t);
+ free(v);
+ if (!cache)
+ blkid_put_cache(c);
+ return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+ char *value;
+ blkid_cache cache;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+ if (argc != 2 && argc != 3) {
+ fprintf(stderr, "Usage:\t%s tagname=value\n"
+ "\t%s tagname devname\n"
+ "Find which device holds a given token or\n"
+ "Find what the value of a tag is in a device\n",
+ argv[0], argv[0]);
+ exit(1);
+ }
+ if (blkid_get_cache(&cache, "/dev/null") < 0) {
+ fprintf(stderr, "Couldn't get blkid cache\n");
+ exit(1);
+ }
+
+ if (argv[2]) {
+ value = blkid_get_tag_value(cache, argv[1], argv[2]);
+ printf("%s has tag %s=%s\n", argv[2], argv[1],
+ value ? value : "<missing>");
+ } else {
+ value = blkid_get_devname(cache, argv[1], NULL);
+ printf("%s has tag %s\n", value ? value : "<none>", argv[1]);
+ }
+ blkid_put_cache(cache);
+ return value ? 0 : 1;
+}
+#endif
diff --git a/libblkid/src/save.c b/libblkid/src/save.c
new file mode 100644
index 0000000..1a617c0
--- /dev/null
+++ b/libblkid/src/save.c
@@ -0,0 +1,244 @@
+/*
+ * save.c - write the cache struct to disk
+ *
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "closestream.h"
+#include "fileutils.h"
+
+#include "blkidP.h"
+
+
+static void save_quoted(const char *data, FILE *file)
+{
+ const char *p;
+
+ fputc('"', file);
+ for (p = data; p && *p; p++) {
+ if ((unsigned char) *p == 0x22 || /* " */
+ (unsigned char) *p == 0x5c) /* \ */
+ fputc('\\', file);
+
+ fputc(*p, file);
+ }
+ fputc('"', file);
+}
+static int save_dev(blkid_dev dev, FILE *file)
+{
+ struct list_head *p;
+
+ if (!dev || dev->bid_name[0] != '/')
+ return 0;
+
+ DBG(SAVE, ul_debug("device %s, type %s", dev->bid_name, dev->bid_type ?
+ dev->bid_type : "(null)"));
+
+ fprintf(file, "<device DEVNO=\"0x%04lx\" TIME=\"%lld.%lld\"",
+ (unsigned long) dev->bid_devno,
+ (long long) dev->bid_time,
+ (long long) dev->bid_utime);
+
+ if (dev->bid_pri)
+ fprintf(file, " PRI=\"%d\"", dev->bid_pri);
+
+ list_for_each(p, &dev->bid_tags) {
+ blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
+
+ fputc(' ', file); /* space between tags */
+ fputs(tag->bit_name, file); /* tag NAME */
+ fputc('=', file); /* separator between NAME and VALUE */
+ save_quoted(tag->bit_val, file); /* tag "VALUE" */
+ }
+ fprintf(file, ">%s</device>\n", dev->bid_name);
+
+ return 0;
+}
+
+/*
+ * Write out the cache struct to the cache file on disk.
+ */
+int blkid_flush_cache(blkid_cache cache)
+{
+ struct list_head *p;
+ char *tmp = NULL;
+ char *opened = NULL;
+ char *filename;
+ FILE *file = NULL;
+ int fd, ret = 0;
+ struct stat st;
+
+ if (list_empty(&cache->bic_devs) ||
+ !(cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
+ DBG(SAVE, ul_debug("skipping cache file write"));
+ return 0;
+ }
+
+ filename = cache->bic_filename ? cache->bic_filename :
+ blkid_get_cache_filename(NULL);
+ if (!filename)
+ return -BLKID_ERR_PARAM;
+
+ if (strncmp(filename,
+ BLKID_RUNTIME_DIR "/", sizeof(BLKID_RUNTIME_DIR)) == 0) {
+
+ /* default destination, create the directory if necessary */
+ if (stat(BLKID_RUNTIME_DIR, &st)
+ && errno == ENOENT
+ && mkdir(BLKID_RUNTIME_DIR, S_IWUSR|
+ S_IRUSR|S_IRGRP|S_IROTH|
+ S_IXUSR|S_IXGRP|S_IXOTH) != 0
+ && errno != EEXIST) {
+ DBG(SAVE, ul_debug("can't create %s directory for cache file",
+ BLKID_RUNTIME_DIR));
+ return 0;
+ }
+ }
+
+ /* If we can't write to the cache file, then don't even try */
+ if (((ret = stat(filename, &st)) < 0 && errno != ENOENT) ||
+ (ret == 0 && access(filename, W_OK) < 0)) {
+ DBG(SAVE, ul_debug("can't write to cache file %s", filename));
+ return 0;
+ }
+
+ /*
+ * Try and create a temporary file in the same directory so
+ * that in case of error we don't overwrite the cache file.
+ * If the cache file doesn't yet exist, it isn't a regular
+ * file (e.g. /dev/null or a socket), or we couldn't create
+ * a temporary file then we open it directly.
+ */
+ if (ret == 0 && S_ISREG(st.st_mode)) {
+ size_t len = strlen(filename) + 8;
+ tmp = malloc(len);
+ if (tmp) {
+ snprintf(tmp, len, "%s-XXXXXX", filename);
+ fd = mkstemp_cloexec(tmp);
+ if (fd >= 0) {
+ if (fchmod(fd, 0644) != 0)
+ DBG(SAVE, ul_debug("%s: fchmod failed", filename));
+ else if ((file = fdopen(fd, "w" UL_CLOEXECSTR)))
+ opened = tmp;
+ if (!file)
+ close(fd);
+ }
+ }
+ }
+
+ if (!file) {
+ file = fopen(filename, "w" UL_CLOEXECSTR);
+ opened = filename;
+ }
+
+ DBG(SAVE, ul_debug("writing cache file %s (really %s)",
+ filename, opened));
+
+ if (!file) {
+ ret = errno;
+ goto errout;
+ }
+
+ list_for_each(p, &cache->bic_devs) {
+ blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs);
+ if (!dev->bid_type || (dev->bid_flags & BLKID_BID_FL_REMOVABLE))
+ continue;
+ if ((ret = save_dev(dev, file)) < 0)
+ break;
+ }
+
+ if (ret >= 0) {
+ cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
+ ret = 1;
+ }
+
+ if (close_stream(file) != 0)
+ DBG(SAVE, ul_debug("write failed: %s", filename));
+
+ if (opened != filename) {
+ if (ret < 0) {
+ unlink(opened);
+ DBG(SAVE, ul_debug("unlinked temp cache %s", opened));
+ } else {
+ char *backup;
+ size_t len = strlen(filename) + 5;
+
+ backup = malloc(len);
+ if (backup) {
+ snprintf(backup, len, "%s.old", filename);
+ unlink(backup);
+ if (link(filename, backup)) {
+ DBG(SAVE, ul_debug("can't link %s to %s",
+ filename, backup));
+ }
+ free(backup);
+ }
+ if (rename(opened, filename)) {
+ ret = errno;
+ DBG(SAVE, ul_debug("can't rename %s to %s",
+ opened, filename));
+ } else {
+ DBG(SAVE, ul_debug("moved temp cache %s", opened));
+ }
+ }
+ }
+
+errout:
+ free(tmp);
+ if (filename != cache->bic_filename)
+ free(filename);
+ return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+ blkid_cache cache = NULL;
+ int ret;
+
+ blkid_init_debug(BLKID_DEBUG_ALL);
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s [filename]\n"
+ "Test loading/saving a cache (filename)\n", argv[0]);
+ exit(1);
+ }
+
+ if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+ if ((ret = blkid_probe_all(cache)) < 0) {
+ fprintf(stderr, "error (%d) probing devices\n", ret);
+ exit(1);
+ }
+ cache->bic_filename = strdup(argv[1]);
+
+ if ((ret = blkid_flush_cache(cache)) < 0) {
+ fprintf(stderr, "error (%d) saving cache\n", ret);
+ exit(1);
+ }
+
+ blkid_put_cache(cache);
+
+ return ret;
+}
+#endif
diff --git a/libblkid/src/superblocks/adaptec_raid.c b/libblkid/src/superblocks/adaptec_raid.c
new file mode 100644
index 0000000..5fc5fc4
--- /dev/null
+++ b/libblkid/src/superblocks/adaptec_raid.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct adaptec_metadata {
+
+ uint32_t b0idcode;
+ uint8_t lunsave[8];
+ uint16_t sdtype;
+ uint16_t ssavecyl;
+ uint8_t ssavehed;
+ uint8_t ssavesec;
+ uint8_t sb0flags;
+ uint8_t jbodEnable;
+ uint8_t lundsave;
+ uint8_t svpdirty;
+ uint16_t biosInfo;
+ uint16_t svwbskip;
+ uint16_t svwbcln;
+ uint16_t svwbmax;
+ uint16_t res3;
+ uint16_t svwbmin;
+ uint16_t res4;
+ uint16_t svrcacth;
+ uint16_t svwcacth;
+ uint16_t svwbdly;
+ uint8_t svsdtime;
+ uint8_t res5;
+ uint16_t firmval;
+ uint16_t firmbln;
+ uint32_t firmblk;
+ uint32_t fstrsvrb;
+ uint16_t svBlockStorageTid;
+ uint16_t svtid;
+ uint8_t svseccfl;
+ uint8_t res6;
+ uint8_t svhbanum;
+ uint8_t resver;
+ uint32_t drivemagic;
+ uint8_t reserved[20];
+ uint8_t testnum;
+ uint8_t testflags;
+ uint16_t maxErrorCount;
+ uint32_t count;
+ uint32_t startTime;
+ uint32_t interval;
+ uint8_t tstxt0;
+ uint8_t tstxt1;
+ uint8_t serNum[32];
+ uint8_t res8[102];
+ uint32_t fwTestMagic;
+ uint32_t fwTestSeqNum;
+ uint8_t fwTestRes[8];
+ uint32_t smagic;
+ uint32_t raidtbl;
+ uint16_t raidline;
+ uint8_t res9[0xF6];
+} __attribute__((packed));
+
+#define AD_SIGNATURE 0x4450544D /* "DPTM" */
+#define AD_MAGIC 0x37FC4D1E
+
+static int probe_adraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct adaptec_metadata *ad;
+
+ if (pr->size < 0x10000)
+ return BLKID_PROBE_NONE;
+
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return BLKID_PROBE_NONE;
+
+ off = ((pr->size / 0x200)-1) * 0x200;
+ ad = (struct adaptec_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct adaptec_metadata));
+ if (!ad)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (ad->smagic != be32_to_cpu(AD_SIGNATURE))
+ return BLKID_PROBE_NONE;
+ if (ad->b0idcode != be32_to_cpu(AD_MAGIC))
+ return BLKID_PROBE_NONE;
+ if (blkid_probe_sprintf_version(pr, "%u", ad->resver) != 0)
+ return BLKID_PROBE_NONE;
+ if (blkid_probe_set_magic(pr, off, sizeof(ad->b0idcode),
+ (unsigned char *) &ad->b0idcode))
+ return BLKID_PROBE_NONE;
+
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo adraid_idinfo = {
+ .name = "adaptec_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_adraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/apfs.c b/libblkid/src/superblocks/apfs.c
new file mode 100644
index 0000000..3332ef2
--- /dev/null
+++ b/libblkid/src/superblocks/apfs.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 Harry Mallon <hjmallon@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include "superblocks.h"
+
+#define APFS_CONTAINER_SUPERBLOCK_TYPE 1
+#define APFS_CONTAINER_SUPERBLOCK_SUBTYPE 0
+#define APFS_STANDARD_BLOCK_SIZE 4096
+
+/*
+ * This struct is much longer than this, but this seems
+ * to contain the useful bits (for now).
+ *
+ * All values are little-endian.
+ */
+struct apfs_super_block {
+ // Generic part to all APFS objects
+ uint64_t checksum;
+ uint64_t oid;
+ uint64_t xid;
+ uint16_t type;
+ uint16_t flags;
+ uint16_t subtype;
+ uint16_t pad;
+
+ // Specific to container header
+ uint32_t magic; // 'NXSB'
+ uint32_t block_size;
+ uint64_t block_count;
+ uint64_t features;
+ uint64_t read_only_features;
+ uint64_t incompatible_features;
+ uint8_t uuid[16];
+};
+
+static int probe_apfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct apfs_super_block *sb;
+
+ sb = blkid_probe_get_sb(pr, mag, struct apfs_super_block);
+ if (!sb)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (le16_to_cpu(sb->type) != APFS_CONTAINER_SUPERBLOCK_TYPE)
+ return BLKID_PROBE_NONE;
+
+ if (le16_to_cpu(sb->subtype) != APFS_CONTAINER_SUPERBLOCK_SUBTYPE)
+ return BLKID_PROBE_NONE;
+
+ if (le16_to_cpu(sb->pad) != 0)
+ return BLKID_PROBE_NONE;
+
+ /*
+ * This check is pretty draconian, but should avoid false
+ * positives. Can be improved as more APFS documentation
+ * is published.
+ */
+ if (le32_to_cpu(sb->block_size) != APFS_STANDARD_BLOCK_SIZE)
+ return BLKID_PROBE_NONE;
+
+ if (blkid_probe_set_uuid(pr, sb->uuid) < 0)
+ return BLKID_PROBE_NONE;
+
+ blkid_probe_set_block_size(pr, le32_to_cpu(sb->block_size));
+
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo apfs_idinfo =
+{
+ .name = "apfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_apfs,
+ .magics =
+ {
+ { .magic = "NXSB", .len = 4, .sboff = 32 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/bcache.c b/libblkid/src/superblocks/bcache.c
new file mode 100644
index 0000000..aa2dc04
--- /dev/null
+++ b/libblkid/src/superblocks/bcache.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 Rolf Fokkens <rolf@fokkens.nl>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Based on code fragments from bcache-tools by Kent Overstreet:
+ * http://evilpiepirate.org/git/bcache-tools.git
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include "superblocks.h"
+
+#define SB_LABEL_SIZE 32
+#define SB_JOURNAL_BUCKETS 256U
+
+#define node(i, j) ((i)->d + (j))
+#define end(i) node(i, le16_to_cpu((i)->keys))
+
+/*
+ * The bcache_super_block is heavily simplified version of struct cache_sb in kernel.
+ * https://github.com/torvalds/linux/blob/master/include/uapi/linux/bcache.h
+ */
+struct bcache_super_block {
+ uint64_t csum;
+ uint64_t offset; /* where this super block was written */
+ uint64_t version;
+ uint8_t magic[16]; /* bcache file system identifier */
+ uint8_t uuid[16]; /* device identifier */
+};
+
+/* magic string */
+#define BCACHE_SB_MAGIC "\xc6\x85\x73\xf6\x4e\x1a\x45\xca\x82\x65\xf5\x7f\x48\xba\x6d\x81"
+/* magic string len */
+#define BCACHE_SB_MAGIC_LEN (sizeof(BCACHE_SB_MAGIC) - 1)
+/* super block offset */
+#define BCACHE_SB_OFF 0x1000
+/* supper block offset in kB */
+#define BCACHE_SB_KBOFF (BCACHE_SB_OFF >> 10)
+/* magic string offset within super block */
+#define BCACHE_SB_MAGIC_OFF offsetof (struct bcache_super_block, magic)
+
+static int probe_bcache (blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct bcache_super_block *bcs;
+
+ bcs = blkid_probe_get_sb(pr, mag, struct bcache_super_block);
+ if (!bcs)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (le64_to_cpu(bcs->offset) != BCACHE_SB_OFF / 512)
+ return BLKID_PROBE_NONE;
+
+ if (blkid_probe_set_uuid(pr, bcs->uuid) < 0)
+ return BLKID_PROBE_NONE;
+
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo bcache_idinfo =
+{
+ .name = "bcache",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_bcache,
+ .minsz = 8192,
+ .magics =
+ {
+ {
+ .magic = BCACHE_SB_MAGIC,
+ .len = BCACHE_SB_MAGIC_LEN,
+ .kboff = BCACHE_SB_KBOFF,
+ .sboff = BCACHE_SB_MAGIC_OFF
+ },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/befs.c b/libblkid/src/superblocks/befs.c
new file mode 100644
index 0000000..211feae
--- /dev/null
+++ b/libblkid/src/superblocks/befs.c
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2010 Jeroen Oortwijn <oortwijn@gmail.com>
+ *
+ * Partly based on the Haiku BFS driver by
+ * Axel Dörfler <axeld@pinc-software.de>
+ *
+ * Also inspired by the Linux BeFS driver by
+ * Will Dyson <will_dyson@pobox.com>, et al.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+#define B_OS_NAME_LENGTH 0x20
+#define SUPER_BLOCK_MAGIC1 0x42465331 /* BFS1 */
+#define SUPER_BLOCK_MAGIC2 0xdd121031
+#define SUPER_BLOCK_MAGIC3 0x15b6830e
+#define SUPER_BLOCK_FS_ENDIAN 0x42494745 /* BIGE */
+#define INODE_MAGIC1 0x3bbe0ad9
+#define BPLUSTREE_MAGIC 0x69f6c2e8
+#define BPLUSTREE_NULL -1LL
+#define NUM_DIRECT_BLOCKS 12
+#define B_UINT64_TYPE 0x554c4c47 /* ULLG */
+#define KEY_NAME "be:volume_id"
+#define KEY_SIZE 8
+
+#define FS16_TO_CPU(value, fs_is_le) (fs_is_le ? le16_to_cpu(value) \
+ : be16_to_cpu(value))
+#define FS32_TO_CPU(value, fs_is_le) (fs_is_le ? le32_to_cpu(value) \
+ : be32_to_cpu(value))
+#define FS64_TO_CPU(value, fs_is_le) (fs_is_le ? le64_to_cpu(value) \
+ : be64_to_cpu(value))
+
+typedef struct block_run {
+ int32_t allocation_group;
+ uint16_t start;
+ uint16_t len;
+} __attribute__((packed)) block_run, inode_addr;
+
+struct befs_super_block {
+ char name[B_OS_NAME_LENGTH];
+ int32_t magic1;
+ int32_t fs_byte_order;
+ uint32_t block_size;
+ uint32_t block_shift;
+ int64_t num_blocks;
+ int64_t used_blocks;
+ int32_t inode_size;
+ int32_t magic2;
+ int32_t blocks_per_ag;
+ int32_t ag_shift;
+ int32_t num_ags;
+ int32_t flags;
+ block_run log_blocks;
+ int64_t log_start;
+ int64_t log_end;
+ int32_t magic3;
+ inode_addr root_dir;
+ inode_addr indices;
+ int32_t pad[8];
+} __attribute__((packed));
+
+typedef struct data_stream {
+ block_run direct[NUM_DIRECT_BLOCKS];
+ int64_t max_direct_range;
+ block_run indirect;
+ int64_t max_indirect_range;
+ block_run double_indirect;
+ int64_t max_double_indirect_range;
+ int64_t size;
+} __attribute__((packed)) data_stream;
+
+struct befs_inode {
+ int32_t magic1;
+ inode_addr inode_num;
+ int32_t uid;
+ int32_t gid;
+ int32_t mode;
+ int32_t flags;
+ int64_t create_time;
+ int64_t last_modified_time;
+ inode_addr parent;
+ inode_addr attributes;
+ uint32_t type;
+ int32_t inode_size;
+ uint32_t etc;
+ data_stream data;
+ int32_t pad[4];
+ int32_t small_data[0];
+} __attribute__((packed));
+
+struct small_data {
+ uint32_t type;
+ uint16_t name_size;
+ uint16_t data_size;
+ char name[0];
+} __attribute__((packed));
+
+struct bplustree_header {
+ uint32_t magic;
+ uint32_t node_size;
+ uint32_t max_number_of_levels;
+ uint32_t data_type;
+ int64_t root_node_pointer;
+ int64_t free_node_pointer;
+ int64_t maximum_size;
+} __attribute__((packed));
+
+struct bplustree_node {
+ int64_t left_link;
+ int64_t right_link;
+ int64_t overflow_link;
+ uint16_t all_key_count;
+ uint16_t all_key_length;
+ char name[0];
+} __attribute__((packed));
+
+static unsigned char *get_block_run(blkid_probe pr, const struct befs_super_block *bs,
+ const struct block_run *br, int fs_le)
+{
+ return blkid_probe_get_buffer(pr,
+ ((uint64_t) FS32_TO_CPU(br->allocation_group, fs_le)
+ << FS32_TO_CPU(bs->ag_shift, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ + ((uint64_t) FS16_TO_CPU(br->start, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le)),
+ (uint64_t) FS16_TO_CPU(br->len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le));
+}
+
+static unsigned char *get_custom_block_run(blkid_probe pr,
+ const struct befs_super_block *bs,
+ const struct block_run *br,
+ int64_t offset, uint32_t length, int fs_le)
+{
+ if (offset + length > (int64_t) FS16_TO_CPU(br->len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ return NULL;
+
+ return blkid_probe_get_buffer(pr,
+ ((uint64_t) FS32_TO_CPU(br->allocation_group, fs_le)
+ << FS32_TO_CPU(bs->ag_shift, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ + ((uint64_t) FS16_TO_CPU(br->start, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ + offset,
+ length);
+}
+
+static unsigned char *get_tree_node(blkid_probe pr, const struct befs_super_block *bs,
+ const struct data_stream *ds,
+ int64_t start, uint32_t length, int fs_le)
+{
+ if (start < (int64_t) FS64_TO_CPU(ds->max_direct_range, fs_le)) {
+ int64_t br_len;
+ size_t i;
+
+ for (i = 0; i < NUM_DIRECT_BLOCKS; i++) {
+ br_len = (int64_t) FS16_TO_CPU(ds->direct[i].len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le);
+ if (start < br_len)
+ return get_custom_block_run(pr, bs,
+ &ds->direct[i],
+ start, length, fs_le);
+ start -= br_len;
+ }
+ } else if (start < (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le)) {
+ struct block_run *br;
+ int64_t max_br, br_len, i;
+
+ start -= FS64_TO_CPU(ds->max_direct_range, fs_le);
+ max_br = ((int64_t) FS16_TO_CPU(ds->indirect.len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ / sizeof(struct block_run);
+
+ br = (struct block_run *) get_block_run(pr, bs, &ds->indirect,
+ fs_le);
+ if (!br)
+ return NULL;
+
+ for (i = 0; i < max_br; i++) {
+ br_len = (int64_t) FS16_TO_CPU(br[i].len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le);
+ if (start < br_len)
+ return get_custom_block_run(pr, bs, &br[i],
+ start, length, fs_le);
+ start -= br_len;
+ }
+ } else if (start < (int64_t) FS64_TO_CPU(ds->max_double_indirect_range, fs_le)) {
+ struct block_run *br;
+ int64_t max_br, di_br_size, br_per_di_br, di_index, i_index;
+
+ start -= (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le);
+
+ di_br_size = (int64_t) FS16_TO_CPU(ds->double_indirect.len,
+ fs_le) << FS32_TO_CPU(bs->block_shift, fs_le);
+ if (di_br_size == 0)
+ return NULL;
+
+ br_per_di_br = di_br_size / sizeof(struct block_run);
+ if (br_per_di_br == 0)
+ return NULL;
+
+ di_index = start / (br_per_di_br * di_br_size);
+ i_index = (start % (br_per_di_br * di_br_size)) / di_br_size;
+ start = (start % (br_per_di_br * di_br_size)) % di_br_size;
+
+ if (di_index >= br_per_di_br)
+ return NULL; /* Corrupt? */
+
+ br = (struct block_run *) get_block_run(pr, bs,
+ &ds->double_indirect, fs_le);
+ if (!br)
+ return NULL;
+
+ max_br = ((int64_t)FS16_TO_CPU(br[di_index].len, fs_le)
+ << FS32_TO_CPU(bs->block_shift, fs_le))
+ / sizeof(struct block_run);
+
+ if (i_index >= max_br)
+ return NULL; /* Corrupt? */
+
+ br = (struct block_run *) get_block_run(pr, bs, &br[di_index],
+ fs_le);
+ if (!br)
+ return NULL;
+
+ return get_custom_block_run(pr, bs, &br[i_index], start, length,
+ fs_le);
+ }
+ return NULL;
+}
+
+#define BAD_KEYS -2
+
+static int32_t compare_keys(const char keys1[], uint16_t keylengths1[],
+ int32_t index, const char *key2,
+ uint16_t keylength2, uint16_t all_key_length,
+ int fs_le)
+{
+ const char *key1;
+ uint16_t keylength1, keystart1;
+ int32_t result;
+
+ keystart1 = index == 0 ? 0 : FS16_TO_CPU(keylengths1[index - 1], fs_le);
+ keylength1 = FS16_TO_CPU(keylengths1[index], fs_le) - keystart1;
+
+ if (keystart1 + keylength1 > all_key_length)
+ return BAD_KEYS; /* Corrupt? */
+
+ key1 = &keys1[keystart1];
+
+ result = strncmp(key1, key2, min(keylength1, keylength2));
+
+ if (result == 0)
+ return keylength1 - keylength2;
+
+ if (result < 0) /* Don't clash with BAD_KEYS */
+ result = -1;
+
+ return result;
+}
+
+static int64_t get_key_value(blkid_probe pr, const struct befs_super_block *bs,
+ const struct befs_inode *bi, const char *key, int fs_le)
+{
+ struct bplustree_header *bh;
+ struct bplustree_node *bn;
+ uint16_t *keylengths;
+ int64_t *values;
+ int64_t node_pointer;
+ uint32_t bn_size, all_key_count, all_key_length;
+ uint32_t keylengths_offset, values_offset;
+ int32_t first, last, mid, cmp;
+ int loop_detect = 0;
+
+ errno = 0;
+ bh = (struct bplustree_header *) get_tree_node(pr, bs, &bi->data, 0,
+ sizeof(struct bplustree_header), fs_le);
+ if (!bh)
+ return errno ? -errno : -ENOENT;
+
+ if ((int32_t) FS32_TO_CPU(bh->magic, fs_le) != BPLUSTREE_MAGIC)
+ return -ENOENT;
+
+ node_pointer = FS64_TO_CPU(bh->root_node_pointer, fs_le);
+ bn_size = FS32_TO_CPU(bh->node_size, fs_le);
+
+ if (bn_size < sizeof(struct bplustree_node))
+ return -ENOENT; /* Corrupt? */
+
+ do {
+ errno = 0;
+
+ bn = (struct bplustree_node *) get_tree_node(pr, bs, &bi->data,
+ node_pointer, bn_size, fs_le);
+ if (!bn)
+ return errno ? -errno : -ENOENT;
+
+ all_key_count = FS16_TO_CPU(bn->all_key_count, fs_le);
+ all_key_length = FS16_TO_CPU(bn->all_key_length, fs_le);
+ keylengths_offset =
+ (sizeof(struct bplustree_node) + all_key_length
+ + sizeof(int64_t) - 1) & ~(sizeof(int64_t) - 1);
+ values_offset = keylengths_offset +
+ all_key_count * sizeof(uint16_t);
+
+ if (values_offset + all_key_count * sizeof(uint64_t) > bn_size)
+ return -ENOENT; /* Corrupt? */
+
+ keylengths = (uint16_t *) ((uint8_t *) bn + keylengths_offset);
+ values = (int64_t *) ((uint8_t *) bn + values_offset);
+
+ first = 0;
+ mid = 0;
+ last = all_key_count - 1;
+
+ cmp = compare_keys(bn->name, keylengths, last, key,
+ strlen(key), all_key_length, fs_le);
+ if (cmp == BAD_KEYS)
+ return -ENOENT;
+
+ if (cmp == 0) {
+ if ((int64_t) FS64_TO_CPU(bn->overflow_link, fs_le)
+ == BPLUSTREE_NULL)
+ return FS64_TO_CPU(values[last], fs_le);
+
+ node_pointer = FS64_TO_CPU(values[last], fs_le);
+ } else if (cmp < 0)
+ node_pointer = FS64_TO_CPU(bn->overflow_link, fs_le);
+ else {
+ while (first <= last) {
+ mid = (first + last) / 2;
+
+ cmp = compare_keys(bn->name, keylengths, mid,
+ key, strlen(key),
+ all_key_length, fs_le);
+ if (cmp == BAD_KEYS)
+ return -ENOENT;
+
+ if (cmp == 0) {
+ if ((int64_t) FS64_TO_CPU(bn->overflow_link,
+ fs_le) == BPLUSTREE_NULL)
+ return FS64_TO_CPU(values[mid],
+ fs_le);
+ break;
+ }
+
+ if (cmp < 0)
+ first = mid + 1;
+ else
+ last = mid - 1;
+ }
+ if (cmp < 0)
+ node_pointer = FS64_TO_CPU(values[mid + 1],
+ fs_le);
+ else
+ node_pointer = FS64_TO_CPU(values[mid], fs_le);
+ }
+ } while (++loop_detect < 100 &&
+ (int64_t) FS64_TO_CPU(bn->overflow_link, fs_le)
+ != BPLUSTREE_NULL);
+ return 0;
+}
+
+static int get_uuid(blkid_probe pr, const struct befs_super_block *bs,
+ uint64_t * const uuid, int fs_le)
+{
+ struct befs_inode *bi;
+ struct small_data *sd;
+ uint64_t bi_size, offset, sd_size, sd_total_size;
+
+ bi = (struct befs_inode *) get_block_run(pr, bs, &bs->root_dir, fs_le);
+ if (!bi)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+ return BLKID_PROBE_NONE;
+
+ bi_size = (uint64_t)FS16_TO_CPU(bs->root_dir.len, fs_le) <<
+ FS32_TO_CPU(bs->block_shift, fs_le);
+ sd_total_size = min(bi_size - sizeof(struct befs_inode),
+ (uint64_t)FS32_TO_CPU(bi->inode_size, fs_le));
+
+ offset = 0;
+
+ while (offset + sizeof(struct small_data) <= sd_total_size) {
+ sd = (struct small_data *) ((uint8_t *)bi->small_data + offset);
+ sd_size = sizeof(struct small_data)
+ + FS16_TO_CPU(sd->name_size, fs_le) + 3
+ + FS16_TO_CPU(sd->data_size, fs_le) + 1;
+
+ if (offset + sd_size > sd_total_size)
+ break;
+
+ if (FS32_TO_CPU(sd->type, fs_le) == B_UINT64_TYPE
+ && FS16_TO_CPU(sd->name_size, fs_le) == strlen(KEY_NAME)
+ && FS16_TO_CPU(sd->data_size, fs_le) == KEY_SIZE
+ && strcmp(sd->name, KEY_NAME) == 0) {
+
+ memcpy(uuid,
+ sd->name + FS16_TO_CPU(sd->name_size, fs_le) + 3,
+ sizeof(uint64_t));
+
+ break;
+ }
+
+ if (FS32_TO_CPU(sd->type, fs_le) == 0
+ && FS16_TO_CPU(sd->name_size, fs_le) == 0
+ && FS16_TO_CPU(sd->data_size, fs_le) == 0)
+ break;
+
+ offset += sd_size;
+ }
+
+ if (*uuid == 0
+ && (FS32_TO_CPU(bi->attributes.allocation_group, fs_le) != 0
+ || FS16_TO_CPU(bi->attributes.start, fs_le) != 0
+ || FS16_TO_CPU(bi->attributes.len, fs_le) != 0)) {
+ int64_t value;
+
+ bi = (struct befs_inode *) get_block_run(pr, bs,
+ &bi->attributes, fs_le);
+ if (!bi)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+ return BLKID_PROBE_NONE;
+
+ value = get_key_value(pr, bs, bi, KEY_NAME, fs_le);
+ if (value < 0)
+ return value == -ENOENT ? BLKID_PROBE_NONE : value;
+
+ if (value > 0) {
+ bi = (struct befs_inode *) blkid_probe_get_buffer(pr,
+ value << FS32_TO_CPU(bs->block_shift, fs_le),
+ FS32_TO_CPU(bs->block_size, fs_le));
+ if (!bi)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+ return 1;
+
+ if (FS32_TO_CPU(bi->type, fs_le) == B_UINT64_TYPE
+ && FS64_TO_CPU(bi->data.size, fs_le) == KEY_SIZE
+ && FS16_TO_CPU(bi->data.direct[0].len, fs_le)
+ == 1) {
+ uint64_t *attr_data;
+
+ attr_data = (uint64_t *) get_block_run(pr, bs,
+ &bi->data.direct[0], fs_le);
+ if (!attr_data)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ *uuid = *attr_data;
+ }
+ }
+ }
+ return 0;
+}
+
+static int probe_befs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct befs_super_block *bs;
+ const char *version = NULL;
+ uint64_t volume_id = 0;
+ uint32_t block_size, block_shift;
+ int fs_le, ret;
+
+ bs = (struct befs_super_block *) blkid_probe_get_buffer(pr,
+ mag->sboff - B_OS_NAME_LENGTH,
+ sizeof(struct befs_super_block));
+ if (!bs)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (le32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1
+ && le32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2
+ && le32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3
+ && le32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) {
+ fs_le = 1;
+ version = "little-endian";
+ } else if (be32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1
+ && be32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2
+ && be32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3
+ && be32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) {
+ fs_le = 0;
+ version = "big-endian";
+ } else
+ return BLKID_PROBE_NONE;
+
+ block_size = FS32_TO_CPU(bs->block_size, fs_le);
+ block_shift = FS32_TO_CPU(bs->block_shift, fs_le);
+
+ if (block_shift < 10 || block_shift > 13 ||
+ block_size != 1U << block_shift)
+ return BLKID_PROBE_NONE;
+
+ ret = get_uuid(pr, bs, &volume_id, fs_le);
+
+ if (ret != 0)
+ return ret;
+
+ /*
+ * all checks pass, set LABEL, VERSION and UUID
+ */
+ if (*bs->name != '\0')
+ blkid_probe_set_label(pr, (unsigned char *) bs->name,
+ sizeof(bs->name));
+ if (version)
+ blkid_probe_set_version(pr, version);
+
+ if (volume_id)
+ blkid_probe_sprintf_uuid(pr, (unsigned char *) &volume_id,
+ sizeof(volume_id), "%016" PRIx64,
+ FS64_TO_CPU(volume_id, fs_le));
+
+ blkid_probe_set_block_size(pr, block_size);
+
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo befs_idinfo =
+{
+ .name = "befs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_befs,
+ .minsz = 1024 * 1440,
+ .magics = {
+ { .magic = "BFS1", .len = 4, .sboff = B_OS_NAME_LENGTH },
+ { .magic = "1SFB", .len = 4, .sboff = B_OS_NAME_LENGTH },
+ { .magic = "BFS1", .len = 4, .sboff = 0x200 +
+ B_OS_NAME_LENGTH },
+ { .magic = "1SFB", .len = 4, .sboff = 0x200 +
+ B_OS_NAME_LENGTH },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/bfs.c b/libblkid/src/superblocks/bfs.c
new file mode 100644
index 0000000..8a34c58
--- /dev/null
+++ b/libblkid/src/superblocks/bfs.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include "superblocks.h"
+
+/*
+ * BFS actually has two different labels in the superblock, each
+ * of them only 6 bytes long. Until we find out what their use
+ * we just ignore them.
+ */
+const struct blkid_idinfo bfs_idinfo =
+{
+ .name = "bfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .magics = {
+ { .magic = "\xce\xfa\xad\x1b", .len = 4 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/bitlocker.c b/libblkid/src/superblocks/bitlocker.c
new file mode 100644
index 0000000..111edf3
--- /dev/null
+++ b/libblkid/src/superblocks/bitlocker.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define BDE_HDR_SIZE 512
+#define BDE_HDR_OFFSET 0
+
+struct bde_header_win7 {
+/* 0 */ unsigned char boot_entry_point[3];
+/* 3 */ unsigned char fs_signature[8];
+/* 11 */ unsigned char __dummy1[67 - 11];
+/* 67 */ uint32_t volume_serial; /* NTFS uses 64bit serial number */
+/* 71 */ unsigned char volume_label[11]; /* "NO NAME\x20\x20\x20\x20" only */
+/* 82 */ unsigned char __dummy2[160 - 82];
+/* 160 */ unsigned char guid[16]; /* BitLocker specific GUID */
+/* 176 */ uint64_t fve_metadata_offset;
+} __attribute__((packed));
+
+
+struct bde_header_togo {
+/* 0 */ unsigned char boot_entry_point[3];
+/* 3 */ unsigned char fs_signature[8];
+/* 11 */ unsigned char __dummy[424 - 11];
+/* 424 */ unsigned char guid[16];
+/* 440 */ uint64_t fve_metadata_offset;
+} __attribute__((packed));
+
+
+struct bde_fve_metadata {
+/* 0 */ unsigned char signature[8];
+/* 8 */ uint16_t size;
+/* 10 */ uint16_t version;
+};
+
+enum {
+ BDE_VERSION_VISTA = 0,
+ BDE_VERSION_WIN7,
+ BDE_VERSION_TOGO
+};
+
+#define BDE_MAGIC_VISTA "\xeb\x52\x90-FVE-FS-"
+#define BDE_MAGIC_WIN7 "\xeb\x58\x90-FVE-FS-"
+#define BDE_MAGIC_TOGO "\xeb\x58\x90MSWIN4.1"
+
+#define BDE_MAGIC_FVE "-FVE-FS-"
+
+static int get_bitlocker_type(const unsigned char *buf)
+{
+ size_t i;
+ static const char *map[] = {
+ [BDE_VERSION_VISTA] = BDE_MAGIC_VISTA,
+ [BDE_VERSION_WIN7] = BDE_MAGIC_WIN7,
+ [BDE_VERSION_TOGO] = BDE_MAGIC_TOGO
+ };
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ if (memcmp(buf, map[i], 11) == 0)
+ return (int) i;
+ }
+
+ return -1;
+}
+
+/* Returns: < 0 error, 1 nothing, 0 success
+ */
+static int get_bitlocker_headers(blkid_probe pr,
+ int *type,
+ const unsigned char **buf_hdr,
+ const unsigned char **buf_fve)
+{
+
+ const unsigned char *buf;
+ const struct bde_fve_metadata *fve;
+ uint64_t off = 0;
+ int kind;
+
+ if (buf_hdr)
+ *buf_hdr = NULL;
+ if (buf_fve)
+ *buf_fve = NULL;
+ if (type)
+ *type = -1;
+
+ buf = blkid_probe_get_buffer(pr, BDE_HDR_OFFSET, BDE_HDR_SIZE);
+ if (!buf)
+ return errno ? -errno : 1;
+
+ kind = get_bitlocker_type(buf);
+
+ /* Check BitLocker header */
+ switch (kind) {
+ case BDE_VERSION_WIN7:
+ off = le64_to_cpu(((const struct bde_header_win7 *) buf)->fve_metadata_offset);
+ break;
+ case BDE_VERSION_TOGO:
+ off = le64_to_cpu(((const struct bde_header_togo *) buf)->fve_metadata_offset);
+ break;
+ case BDE_VERSION_VISTA:
+ goto done;
+ default:
+ goto nothing;
+ }
+
+ if (!off)
+ goto nothing;
+ if (buf_hdr)
+ *buf_hdr = buf;
+
+ /* Check Bitlocker FVE metadata header */
+ buf = blkid_probe_get_buffer(pr, off, sizeof(struct bde_fve_metadata));
+ if (!buf)
+ return errno ? -errno : 1;
+
+ fve = (const struct bde_fve_metadata *) buf;
+ if (memcmp(fve->signature, BDE_MAGIC_FVE, sizeof(fve->signature)) != 0)
+ goto nothing;
+ if (buf_fve)
+ *buf_fve = buf;
+done:
+ if (type)
+ *type = kind;
+ return 0;
+nothing:
+ return 1;
+}
+
+/*
+ * This is used by vFAT and NTFS prober to avoid collisions with bitlocker.
+ */
+int blkid_probe_is_bitlocker(blkid_probe pr)
+{
+ return get_bitlocker_headers(pr, NULL, NULL, NULL) == 0;
+}
+
+static int probe_bitlocker(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ const unsigned char *buf_fve = NULL;
+ const unsigned char *buf_hdr = NULL;
+ int rc, kind;
+
+ rc = get_bitlocker_headers(pr, &kind, &buf_hdr, &buf_fve);
+ if (rc)
+ return rc;
+
+ if (kind == BDE_VERSION_WIN7) {
+ const struct bde_header_win7 *hdr = (const struct bde_header_win7 *) buf_hdr;
+
+ /* Unfortunately, it seems volume_serial is always zero */
+ blkid_probe_sprintf_uuid(pr,
+ (const unsigned char *) &hdr->volume_serial,
+ sizeof(hdr->volume_serial),
+ "%016d", le32_to_cpu(hdr->volume_serial));
+ }
+
+ if (buf_fve) {
+ const struct bde_fve_metadata *fve = (const struct bde_fve_metadata *) buf_fve;
+
+ blkid_probe_sprintf_version(pr, "%d", fve->version);
+ }
+ return 0;
+}
+
+/* See header details:
+ * https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc
+ */
+const struct blkid_idinfo bitlocker_idinfo =
+{
+ .name = "BitLocker",
+ .usage = BLKID_USAGE_CRYPTO,
+ .probefunc = probe_bitlocker,
+ .magics =
+ {
+ { .magic = BDE_MAGIC_VISTA, .len = 11 },
+ { .magic = BDE_MAGIC_WIN7, .len = 11 },
+ { .magic = BDE_MAGIC_TOGO, .len = 11 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/bluestore.c b/libblkid/src/superblocks/bluestore.c
new file mode 100644
index 0000000..2ff1f35
--- /dev/null
+++ b/libblkid/src/superblocks/bluestore.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 by Kenneth Van Alstyne <kvanals@kvanals.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ *
+ * Ceph BlueStore is one of the supported storage
+ * methods for Object Storage Devices (OSDs).
+ * This is used to detect the backing block devices
+ * used for these types of OSDs in a Ceph Cluster.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "bitops.h"
+#include "superblocks.h"
+
+#define BLUESTORE_MAGIC_L 22
+
+struct bluestore_phdr {
+ uint8_t magic[BLUESTORE_MAGIC_L];
+} __attribute__((packed));
+
+static int probe_bluestore(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct bluestore_phdr *header;
+
+ header = blkid_probe_get_sb(pr, mag, struct bluestore_phdr);
+ if (header == NULL)
+ return errno ? -errno : 1;
+
+ return 0;
+}
+
+const struct blkid_idinfo bluestore_idinfo =
+{
+ .name = "ceph_bluestore",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_bluestore,
+ .magics =
+ {
+ { .magic = "bluestore block device", .len = 22 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/btrfs.c b/libblkid/src/superblocks/btrfs.c
new file mode 100644
index 0000000..78d767d
--- /dev/null
+++ b/libblkid/src/superblocks/btrfs.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#ifdef HAVE_LINUX_BLKZONED_H
+#include <linux/blkzoned.h>
+#endif
+
+#include "superblocks.h"
+
+struct btrfs_super_block {
+ uint8_t csum[32];
+ uint8_t fsid[16];
+ uint64_t bytenr;
+ uint64_t flags;
+ uint8_t magic[8];
+ uint64_t generation;
+ uint64_t root;
+ uint64_t chunk_root;
+ uint64_t log_root;
+ uint64_t log_root_transid;
+ uint64_t total_bytes;
+ uint64_t bytes_used;
+ uint64_t root_dir_objectid;
+ uint64_t num_devices;
+ uint32_t sectorsize;
+ uint32_t nodesize;
+ uint32_t leafsize;
+ uint32_t stripesize;
+ uint32_t sys_chunk_array_size;
+ uint64_t chunk_root_generation;
+ uint64_t compat_flags;
+ uint64_t compat_ro_flags;
+ uint64_t incompat_flags;
+ uint16_t csum_type;
+ uint8_t root_level;
+ uint8_t chunk_root_level;
+ uint8_t log_root_level;
+ struct btrfs_dev_item {
+ uint64_t devid;
+ uint64_t total_bytes;
+ uint64_t bytes_used;
+ uint32_t io_align;
+ uint32_t io_width;
+ uint32_t sector_size;
+ uint64_t type;
+ uint64_t generation;
+ uint64_t start_offset;
+ uint32_t dev_group;
+ uint8_t seek_speed;
+ uint8_t bandwidth;
+ uint8_t uuid[16];
+ uint8_t fsid[16];
+ } __attribute__ ((__packed__)) dev_item;
+ uint8_t label[256];
+} __attribute__ ((__packed__));
+
+#define BTRFS_SUPER_INFO_SIZE 4096
+
+/* Number of superblock log zones */
+#define BTRFS_NR_SB_LOG_ZONES 2
+
+/* Introduce some macros and types to unify the code with kernel side */
+#define SECTOR_SHIFT 9
+
+typedef uint64_t sector_t;
+
+#ifdef HAVE_LINUX_BLKZONED_H
+static int sb_write_pointer(blkid_probe pr, struct blk_zone *zones, uint64_t *wp_ret)
+{
+ bool empty[BTRFS_NR_SB_LOG_ZONES];
+ bool full[BTRFS_NR_SB_LOG_ZONES];
+ sector_t sector;
+
+ assert(zones[0].type != BLK_ZONE_TYPE_CONVENTIONAL &&
+ zones[1].type != BLK_ZONE_TYPE_CONVENTIONAL);
+
+ empty[0] = zones[0].cond == BLK_ZONE_COND_EMPTY;
+ empty[1] = zones[1].cond == BLK_ZONE_COND_EMPTY;
+ full[0] = zones[0].cond == BLK_ZONE_COND_FULL;
+ full[1] = zones[1].cond == BLK_ZONE_COND_FULL;
+
+ /*
+ * Possible states of log buffer zones
+ *
+ * Empty[0] In use[0] Full[0]
+ * Empty[1] * x 0
+ * In use[1] 0 x 0
+ * Full[1] 1 1 C
+ *
+ * Log position:
+ * *: Special case, no superblock is written
+ * 0: Use write pointer of zones[0]
+ * 1: Use write pointer of zones[1]
+ * C: Compare super blocks from zones[0] and zones[1], use the latest
+ * one determined by generation
+ * x: Invalid state
+ */
+
+ if (empty[0] && empty[1]) {
+ /* Special case to distinguish no superblock to read */
+ *wp_ret = zones[0].start << SECTOR_SHIFT;
+ return -ENOENT;
+ } else if (full[0] && full[1]) {
+ /* Compare two super blocks */
+ struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES];
+ int i;
+
+ for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
+ uint64_t bytenr;
+
+ bytenr = ((zones[i].start + zones[i].len)
+ << SECTOR_SHIFT) - BTRFS_SUPER_INFO_SIZE;
+
+ super[i] = (struct btrfs_super_block *)
+ blkid_probe_get_buffer(pr, bytenr, BTRFS_SUPER_INFO_SIZE);
+ if (!super[i])
+ return -EIO;
+ DBG(LOWPROBE, ul_debug("(btrfs) checking #%d zone "
+ "[start=%" PRIu64", len=%" PRIu64", sb-offset=%" PRIu64"]",
+ i, (uint64_t) zones[i].start,
+ (uint64_t) zones[i].len, bytenr));
+ }
+
+ if (super[0]->generation > super[1]->generation)
+ sector = zones[1].start;
+ else
+ sector = zones[0].start;
+ } else if (!full[0] && (empty[1] || full[1])) {
+ sector = zones[0].wp;
+ } else if (full[0]) {
+ sector = zones[1].wp;
+ } else {
+ return -EUCLEAN;
+ }
+ *wp_ret = sector << SECTOR_SHIFT;
+
+ DBG(LOWPROBE, ul_debug("(btrfs) write pointer: %" PRIu64" sector", sector));
+ return 0;
+}
+
+static int sb_log_offset(blkid_probe pr, uint64_t *bytenr_ret)
+{
+ uint32_t zone_num = 0;
+ uint32_t zone_size_sector;
+ struct blk_zone_report *rep;
+ struct blk_zone *zones;
+ int ret;
+ int i;
+ uint64_t wp;
+
+
+ zone_size_sector = pr->zone_size >> SECTOR_SHIFT;
+ rep = blkdev_get_zonereport(pr->fd, zone_num * zone_size_sector, 2);
+ if (!rep) {
+ ret = -errno;
+ goto out;
+ }
+ zones = (struct blk_zone *)(rep + 1);
+
+ /*
+ * Use the head of the first conventional zone, if the zones
+ * contain one.
+ */
+ for (i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) {
+ if (zones[i].type == BLK_ZONE_TYPE_CONVENTIONAL) {
+ DBG(LOWPROBE, ul_debug("(btrfs) checking conventional zone"));
+ *bytenr_ret = zones[i].start << SECTOR_SHIFT;
+ ret = 0;
+ goto out;
+ }
+ }
+
+ ret = sb_write_pointer(pr, zones, &wp);
+ if (ret != -ENOENT && ret) {
+ ret = 1;
+ goto out;
+ }
+ if (ret != -ENOENT) {
+ if (wp == zones[0].start << SECTOR_SHIFT)
+ wp = (zones[1].start + zones[1].len) << SECTOR_SHIFT;
+ wp -= BTRFS_SUPER_INFO_SIZE;
+ }
+ *bytenr_ret = wp;
+
+ ret = 0;
+out:
+ free(rep);
+
+ return ret;
+}
+#endif
+
+static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct btrfs_super_block *bfs;
+
+ if (pr->zone_size) {
+#ifdef HAVE_LINUX_BLKZONED_H
+ uint64_t offset = 0;
+ int ret;
+
+ ret = sb_log_offset(pr, &offset);
+ if (ret)
+ return ret;
+ bfs = (struct btrfs_super_block *)
+ blkid_probe_get_buffer(pr, offset,
+ sizeof(struct btrfs_super_block));
+#else
+ /* Nothing can be done */
+ return 1;
+#endif
+ } else {
+ bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block);
+ }
+ if (!bfs)
+ return errno ? -errno : 1;
+
+ if (*bfs->label)
+ blkid_probe_set_label(pr,
+ (unsigned char *) bfs->label,
+ sizeof(bfs->label));
+
+ blkid_probe_set_uuid(pr, bfs->fsid);
+ blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB");
+ blkid_probe_set_block_size(pr, le32_to_cpu(bfs->sectorsize));
+
+ return 0;
+}
+
+const struct blkid_idinfo btrfs_idinfo =
+{
+ .name = "btrfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_btrfs,
+ .minsz = 1024 * 1024,
+ .magics =
+ {
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 },
+ /* For zoned btrfs */
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
+ .is_zoned = 1, .zonenum = 0, .kboff_inzone = 0 },
+ { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40,
+ .is_zoned = 1, .zonenum = 1, .kboff_inzone = 0 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/cramfs.c b/libblkid/src/superblocks/cramfs.c
new file mode 100644
index 0000000..2c41cc5
--- /dev/null
+++ b/libblkid/src/superblocks/cramfs.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct cramfs_super
+{
+ uint8_t magic[4];
+ uint32_t size;
+ uint32_t flags;
+ uint32_t future;
+ uint8_t signature[16];
+ struct cramfs_info
+ {
+ uint32_t crc;
+ uint32_t edition;
+ uint32_t blocks;
+ uint32_t files;
+ } __attribute__((packed)) info;
+ uint8_t name[16];
+} __attribute__((packed));
+
+static int probe_cramfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct cramfs_super *cs;
+
+ cs = blkid_probe_get_sb(pr, mag, struct cramfs_super);
+ if (!cs)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_label(pr, cs->name, sizeof(cs->name));
+ return 0;
+}
+
+const struct blkid_idinfo cramfs_idinfo =
+{
+ .name = "cramfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_cramfs,
+ .magics =
+ {
+ { .magic = "\x45\x3d\xcd\x28", .len = 4 },
+ { .magic = "\x28\xcd\x3d\x45", .len = 4 },
+ { NULL }
+ }
+};
+
+
diff --git a/libblkid/src/superblocks/ddf_raid.c b/libblkid/src/superblocks/ddf_raid.c
new file mode 100644
index 0000000..0b82e73
--- /dev/null
+++ b/libblkid/src/superblocks/ddf_raid.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* http://www.snia.org/standards/home */
+#define DDF_GUID_LENGTH 24
+#define DDF_REV_LENGTH 8
+#define DDF_MAGIC 0xDE11DE11
+
+
+struct ddf_header {
+ uint32_t signature;
+ uint32_t crc;
+ uint8_t guid[DDF_GUID_LENGTH];
+ char ddf_rev[8]; /* 01.02.00 */
+ uint32_t seq; /* starts at '1' */
+ uint32_t timestamp;
+ uint8_t openflag;
+ uint8_t foreignflag;
+ uint8_t enforcegroups;
+ uint8_t pad0; /* 0xff */
+ uint8_t pad1[12]; /* 12 * 0xff */
+ /* 64 bytes so far */
+ uint8_t header_ext[32]; /* reserved: fill with 0xff */
+ uint64_t primary_lba;
+ uint64_t secondary_lba;
+ uint8_t type;
+ uint8_t pad2[3]; /* 0xff */
+ uint32_t workspace_len; /* sectors for vendor space -
+ * at least 32768(sectors) */
+ uint64_t workspace_lba;
+ uint16_t max_pd_entries; /* one of 15, 63, 255, 1023, 4095 */
+ uint16_t max_vd_entries; /* 2^(4,6,8,10,12)-1 : i.e. as above */
+ uint16_t max_partitions; /* i.e. max num of configuration
+ record entries per disk */
+ uint16_t config_record_len; /* 1 +ROUNDUP(max_primary_element_entries
+ *12/512) */
+ uint16_t max_primary_element_entries; /* 16, 64, 256, 1024, or 4096 */
+ uint8_t pad3[54]; /* 0xff */
+ /* 192 bytes so far */
+ uint32_t controller_section_offset;
+ uint32_t controller_section_length;
+ uint32_t phys_section_offset;
+ uint32_t phys_section_length;
+ uint32_t virt_section_offset;
+ uint32_t virt_section_length;
+ uint32_t config_section_offset;
+ uint32_t config_section_length;
+ uint32_t data_section_offset;
+ uint32_t data_section_length;
+ uint32_t bbm_section_offset;
+ uint32_t bbm_section_length;
+ uint32_t diag_space_offset;
+ uint32_t diag_space_length;
+ uint32_t vendor_offset;
+ uint32_t vendor_length;
+ /* 256 bytes so far */
+ uint8_t pad4[256]; /* 0xff */
+} __attribute__((packed));
+
+static int probe_ddf(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int hdrs[] = { 1, 257 };
+ size_t i;
+ struct ddf_header *ddf = NULL;
+ char version[DDF_REV_LENGTH + 1];
+ uint64_t off = 0, lba;
+
+ if (pr->size < 0x30000)
+ return 1;
+
+ for (i = 0; i < ARRAY_SIZE(hdrs); i++) {
+ off = ((pr->size / 0x200) - hdrs[i]) * 0x200;
+
+ ddf = (struct ddf_header *) blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct ddf_header));
+ if (!ddf)
+ return errno ? -errno : 1;
+ if (ddf->signature == cpu_to_be32(DDF_MAGIC) ||
+ ddf->signature == cpu_to_le32(DDF_MAGIC))
+ break;
+ ddf = NULL;
+ }
+
+ if (!ddf)
+ return 1;
+
+ lba = ddf->signature == cpu_to_be32(DDF_MAGIC) ?
+ be64_to_cpu(ddf->primary_lba) :
+ le64_to_cpu(ddf->primary_lba);
+
+ if (lba > 0) {
+ /* check primary header */
+ unsigned char *buf;
+
+ buf = blkid_probe_get_buffer(pr,
+ lba << 9, sizeof(ddf->signature));
+ if (!buf)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf, &ddf->signature, 4) != 0)
+ return 1;
+ }
+
+ blkid_probe_strncpy_uuid(pr, ddf->guid, sizeof(ddf->guid));
+
+ memcpy(version, ddf->ddf_rev, sizeof(ddf->ddf_rev));
+ *(version + sizeof(ddf->ddf_rev)) = '\0';
+
+ if (blkid_probe_set_version(pr, version) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off,
+ sizeof(ddf->signature),
+ (unsigned char *) &ddf->signature))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo ddfraid_idinfo = {
+ .name = "ddf_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_ddf,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/drbd.c b/libblkid/src/superblocks/drbd.c
new file mode 100644
index 0000000..f360186
--- /dev/null
+++ b/libblkid/src/superblocks/drbd.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2009 by Bastian Friedrich <bastian.friedrich@collax.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * defines, structs taken from drbd source; file names represent drbd source
+ * files.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+/*
+ * drbd/linux/drbd.h
+ */
+#define DRBD_MAGIC 0x83740267
+
+/*
+ * user/drbdmeta.c
+ * We support v08 and v09
+ */
+#define DRBD_MD_MAGIC_08 (DRBD_MAGIC+4)
+#define DRBD_MD_MAGIC_84_UNCLEAN (DRBD_MAGIC+5)
+#define DRBD_MD_MAGIC_09 (DRBD_MAGIC+6)
+/* there is no DRBD_MD_MAGIC_09_UNCLEAN */
+
+/*
+ * drbd/linux/drbd.h
+ */
+enum drbd_uuid_index {
+ UI_CURRENT,
+ UI_BITMAP,
+ UI_HISTORY_START,
+ UI_HISTORY_END,
+ UI_SIZE, /* nl-packet: number of dirty bits */
+ UI_FLAGS, /* nl-packet: flags */
+ UI_EXTENDED_SIZE /* Everything. */
+};
+
+
+/*
+ * Used by libblkid to avoid unnecessary padding at the end of the structs and
+ * too large unused structs in memory.
+ */
+#define DRBD_MD_OFFSET 4096
+
+/*
+ * user/shared/drbdmeta.c
+ * Minor modifications wrt. types
+ */
+struct md_on_disk_08 {
+ uint64_t la_sect; /* last agreed size. */
+ uint64_t uuid[UI_SIZE]; /* UUIDs */
+ uint64_t device_uuid;
+ uint64_t reserved_u64_1;
+ uint32_t flags;
+ uint32_t magic;
+ uint32_t md_size_sect;
+ int32_t al_offset; /* signed sector offset to this block */
+ uint32_t al_nr_extents; /* important for restoring the AL */
+ int32_t bm_offset; /* signed sector offset to the bitmap, from here */
+ uint32_t bm_bytes_per_bit;
+ uint32_t reserved_u32[4];
+
+ /* Unnecessary for libblkid **
+ * char reserved[8 * 512 - (8*(UI_SIZE+3)+4*11)];
+ */
+};
+
+/*
+ * linux/drbd.h, v9 only
+ */
+#define DRBD_PEERS_MAX 32
+#define HISTORY_UUIDS DRBD_PEERS_MAX
+
+/*
+ * drbd-headers/drbd_meta_data.h
+ * Minor modifications wrt. types
+ */
+struct peer_dev_md_on_disk_9 {
+ uint64_t bitmap_uuid;
+ uint64_t bitmap_dagtag;
+ uint32_t flags;
+ int32_t bitmap_index;
+ uint32_t reserved_u32[2];
+} __attribute__((packed));
+
+struct meta_data_on_disk_9 {
+ uint64_t effective_size; /* last agreed size */
+ uint64_t current_uuid;
+ uint64_t reserved_u64[4]; /* to have the magic at the same position as in v07, and v08 */
+ uint64_t device_uuid;
+ uint32_t flags; /* MDF */
+ uint32_t magic;
+ uint32_t md_size_sect;
+ uint32_t al_offset; /* offset to this block */
+ uint32_t al_nr_extents; /* important for restoring the AL */
+ uint32_t bm_offset; /* offset to the bitmap, from here */
+ uint32_t bm_bytes_per_bit; /* BM_BLOCK_SIZE */
+ uint32_t la_peer_max_bio_size; /* last peer max_bio_size */
+ uint32_t bm_max_peers;
+ int32_t node_id;
+
+ /* see al_tr_number_to_on_disk_sector() */
+ uint32_t al_stripes;
+ uint32_t al_stripe_size_4k;
+
+ uint32_t reserved_u32[2];
+
+ struct peer_dev_md_on_disk_9 peers[DRBD_PEERS_MAX];
+ uint64_t history_uuids[HISTORY_UUIDS];
+
+ /* Unnecessary for libblkid **
+ * char padding[0] __attribute__((aligned(4096)));
+ */
+} __attribute__((packed));
+
+
+static int probe_drbd_84(blkid_probe pr)
+{
+ struct md_on_disk_08 *md;
+ off_t off;
+
+ off = pr->size - DRBD_MD_OFFSET;
+
+ /* Small devices cannot be drbd (?) */
+ if (pr->size < 0x10000)
+ return 1;
+
+ md = (struct md_on_disk_08 *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct md_on_disk_08));
+ if (!md)
+ return errno ? -errno : 1;
+
+ if (be32_to_cpu(md->magic) != DRBD_MD_MAGIC_08 &&
+ be32_to_cpu(md->magic) != DRBD_MD_MAGIC_84_UNCLEAN)
+ return 1;
+
+ /*
+ * DRBD does not have "real" uuids; the following resembles DRBD's
+ * notion of uuids (64 bit, see struct above)
+ */
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &md->device_uuid, sizeof(md->device_uuid),
+ "%" PRIx64, be64_to_cpu(md->device_uuid));
+
+ blkid_probe_set_version(pr, "v08");
+
+ if (blkid_probe_set_magic(pr,
+ off + offsetof(struct md_on_disk_08, magic),
+ sizeof(md->magic),
+ (unsigned char *) &md->magic))
+ return 1;
+
+ return 0;
+}
+
+static int probe_drbd_90(blkid_probe pr)
+{
+ struct meta_data_on_disk_9 *md;
+ off_t off;
+
+ off = pr->size - DRBD_MD_OFFSET;
+
+ /*
+ * Smaller ones are certainly not DRBD9 devices.
+ * Recent utils even refuse to generate larger ones,
+ * keep this as a sufficient lower bound.
+ */
+ if (pr->size < 0x10000)
+ return 1;
+
+ md = (struct meta_data_on_disk_9 *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct meta_data_on_disk_9));
+ if (!md)
+ return errno ? -errno : 1;
+
+ if (be32_to_cpu(md->magic) != DRBD_MD_MAGIC_09)
+ return 1;
+
+ /*
+ * DRBD does not have "real" uuids; the following resembles DRBD's
+ * notion of uuids (64 bit, see struct above)
+ */
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &md->device_uuid, sizeof(md->device_uuid),
+ "%" PRIx64, be64_to_cpu(md->device_uuid));
+
+ blkid_probe_set_version(pr, "v09");
+
+ if (blkid_probe_set_magic(pr,
+ off + offsetof(struct meta_data_on_disk_9, magic),
+ sizeof(md->magic),
+ (unsigned char *) &md->magic))
+ return 1;
+
+ return 0;
+}
+
+static int probe_drbd(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int ret;
+
+ ret = probe_drbd_84(pr);
+ if (ret <= 0) /* success or fatal (-errno) */
+ return ret;
+
+ return probe_drbd_90(pr);
+}
+
+const struct blkid_idinfo drbd_idinfo =
+{
+ .name = "drbd",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_drbd,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/drbdmanage.c b/libblkid/src/superblocks/drbdmanage.c
new file mode 100644
index 0000000..d56c414
--- /dev/null
+++ b/libblkid/src/superblocks/drbdmanage.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 by Philipp Marek <philipp.marek@linbit.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * DRBD is a blocklevel replication solution in the Linux kernel,
+ * upstream since 2.6.33. (See http://drbd.linbit.com/)
+ * DRBDmanage is a configuration frontend that assists in
+ * creating/deleting/modifying DRBD resources across multiple machines
+ * (a DRBDmanage "cluster"); this file detects its control volume,
+ * which is replicated (via DRBD 9) on some of the nodes.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "bitops.h"
+#include "superblocks.h"
+
+struct drbdmanage_hdr {
+ unsigned char magic[11];
+ unsigned char uuid[32];
+ unsigned char lf;
+} __attribute__ ((packed));
+
+struct drbdmanage_pers {
+ char magic[4];
+ uint32_t version_le;
+} __attribute__ ((packed));
+
+
+static const char persistence_magic[4] = { '\x1a', '\xdb', '\x98', '\xa2' };
+
+
+static int probe_drbdmanage(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct drbdmanage_hdr *hdr;
+ unsigned char *cp;
+ struct drbdmanage_pers *prs;
+
+ hdr = (struct drbdmanage_hdr*)
+ blkid_probe_get_buffer(pr, 0, sizeof(*hdr));
+ if (!hdr)
+ return errno ? -errno : 1;
+
+ for(cp=hdr->uuid; cp<&hdr->lf; cp++)
+ if (!isxdigit(*cp))
+ return 1;
+ if (hdr->lf != '\n')
+ return 1;
+
+ if (blkid_probe_strncpy_uuid(pr,
+ hdr->uuid, sizeof(hdr->uuid)))
+ return errno ? -errno : 1;
+
+ prs = (struct drbdmanage_pers*)
+ blkid_probe_get_buffer(pr, 0x1000, sizeof(*prs));
+ if (!prs)
+ return errno ? -errno : 1;
+
+ if (memcmp(prs->magic, persistence_magic, sizeof(prs->magic)) == 0 &&
+ blkid_probe_sprintf_version(pr, "%d", be32_to_cpu(prs->version_le)) != 0)
+ return errno ? -errno : 1;
+
+ return 0;
+}
+
+
+const struct blkid_idinfo drbdmanage_idinfo =
+{
+ .name = "drbdmanage_control_volume",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_drbdmanage,
+ .minsz = 64 * 1024,
+ .magics = {
+ { .magic = "$DRBDmgr=q", .len = 10, .sboff = 0 },
+ { NULL }
+ },
+};
+
diff --git a/libblkid/src/superblocks/drbdproxy_datalog.c b/libblkid/src/superblocks/drbdproxy_datalog.c
new file mode 100644
index 0000000..9a4c7db
--- /dev/null
+++ b/libblkid/src/superblocks/drbdproxy_datalog.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 by Philipp Marek <philipp.marek@linbit.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+
+struct log_header_t {
+ uint64_t magic;
+ uint64_t version;
+
+ unsigned char uuid[16];
+
+ uint64_t flags;
+} __attribute__((packed));
+
+
+static int probe_drbdproxy_datalog(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct log_header_t *lh;
+
+ lh = (struct log_header_t *) blkid_probe_get_buffer(pr, 0, sizeof(*lh));
+ if (!lh)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_uuid(pr, lh->uuid);
+ blkid_probe_sprintf_version(pr, "v%"PRIu64, le64_to_cpu(lh->version));
+
+ return 0;
+}
+
+const struct blkid_idinfo drbdproxy_datalog_idinfo =
+{
+ .name = "drbdproxy_datalog",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_drbdproxy_datalog,
+ .minsz = 16*1024,
+ .magics =
+ {
+ { .magic = "DRBDdlh*", .len = 8, .sboff = 0, .kboff = 0 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/erofs.c b/libblkid/src/superblocks/erofs.c
new file mode 100644
index 0000000..0e7b422
--- /dev/null
+++ b/libblkid/src/superblocks/erofs.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 Gao Xiang
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+
+#define EROFS_SUPER_OFFSET 1024
+#define EROFS_SB_KBOFF (EROFS_SUPER_OFFSET >> 10)
+
+#define EROFS_SUPER_MAGIC_V1 "\xe2\xe1\xf5\xe0"
+#define EROFS_MAGIC_OFF 0
+
+/* All in little-endian */
+struct erofs_super_block {
+ uint32_t magic;
+ uint32_t checksum;
+ uint32_t feature_compat;
+ uint8_t blkszbits;
+ uint8_t reserved;
+
+ uint16_t root_nid;
+ uint64_t inos;
+
+ uint64_t build_time;
+ uint32_t build_time_nsec;
+ uint32_t blocks;
+ uint32_t meta_blkaddr;
+ uint32_t xattr_blkaddr;
+ uint8_t uuid[16];
+ uint8_t volume_name[16];
+ uint32_t feature_incompat;
+ uint8_t reserved2[44];
+};
+
+static int probe_erofs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct erofs_super_block *sb;
+
+ sb = blkid_probe_get_sb(pr, mag, struct erofs_super_block);
+ if (!sb)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (sb->volume_name[0])
+ blkid_probe_set_label(pr, (unsigned char *)sb->volume_name,
+ sizeof(sb->volume_name));
+
+ blkid_probe_set_uuid(pr, sb->uuid);
+
+ if (sb->blkszbits < 32)
+ blkid_probe_set_block_size(pr, 1U << sb->blkszbits);
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo erofs_idinfo =
+{
+ .name = "erofs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_erofs,
+ .magics =
+ {
+ {
+ .magic = EROFS_SUPER_MAGIC_V1,
+ .len = 4,
+ .kboff = EROFS_SB_KBOFF,
+ .sboff = EROFS_MAGIC_OFF,
+ }, { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/exfat.c b/libblkid/src/superblocks/exfat.c
new file mode 100644
index 0000000..f586ec7
--- /dev/null
+++ b/libblkid/src/superblocks/exfat.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "superblocks.h"
+
+struct exfat_super_block {
+ uint8_t jump[3];
+ uint8_t oem_name[8];
+ uint8_t __unused1[53];
+ uint64_t block_start;
+ uint64_t block_count;
+ uint32_t fat_block_start;
+ uint32_t fat_block_count;
+ uint32_t cluster_block_start;
+ uint32_t cluster_count;
+ uint32_t rootdir_cluster;
+ uint8_t volume_serial[4];
+ struct {
+ uint8_t vermin;
+ uint8_t vermaj;
+ } version;
+ uint16_t volume_state;
+ uint8_t block_bits;
+ uint8_t bpc_bits;
+ uint8_t fat_count;
+ uint8_t drive_no;
+ uint8_t allocated_percent;
+} __attribute__((__packed__));
+
+struct exfat_entry_label {
+ uint8_t type;
+ uint8_t length;
+ uint8_t name[22];
+ uint8_t reserved[8];
+} __attribute__((__packed__));
+
+#define BLOCK_SIZE(sb) (1u << (sb)->block_bits)
+#define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb)->bpc_bits)
+#define EXFAT_FIRST_DATA_CLUSTER 2
+#define EXFAT_LAST_DATA_CLUSTER 0xffffff6
+#define EXFAT_ENTRY_SIZE 32
+
+#define EXFAT_ENTRY_EOD 0x00
+#define EXFAT_ENTRY_LABEL 0x83
+
+static uint64_t block_to_offset(const struct exfat_super_block *sb,
+ uint64_t block)
+{
+ return block << sb->block_bits;
+}
+
+static uint64_t cluster_to_block(const struct exfat_super_block *sb,
+ uint32_t cluster)
+{
+ return le32_to_cpu(sb->cluster_block_start) +
+ ((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER)
+ << sb->bpc_bits);
+}
+
+static uint64_t cluster_to_offset(const struct exfat_super_block *sb,
+ uint32_t cluster)
+{
+ return block_to_offset(sb, cluster_to_block(sb, cluster));
+}
+
+static uint32_t next_cluster(blkid_probe pr,
+ const struct exfat_super_block *sb, uint32_t cluster)
+{
+ uint32_t *next;
+ uint64_t fat_offset;
+
+ fat_offset = block_to_offset(sb, le32_to_cpu(sb->fat_block_start))
+ + (uint64_t) cluster * sizeof(cluster);
+ next = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset,
+ sizeof(uint32_t));
+ if (!next)
+ return 0;
+ return le32_to_cpu(*next);
+}
+
+static struct exfat_entry_label *find_label(blkid_probe pr,
+ const struct exfat_super_block *sb)
+{
+ uint32_t cluster = le32_to_cpu(sb->rootdir_cluster);
+ uint64_t offset = cluster_to_offset(sb, cluster);
+ uint8_t *entry;
+ const size_t max_iter = 10000;
+ size_t i = 0;
+
+ for (; i < max_iter; i++) {
+ entry = (uint8_t *) blkid_probe_get_buffer(pr, offset,
+ EXFAT_ENTRY_SIZE);
+ if (!entry)
+ return NULL;
+ if (entry[0] == EXFAT_ENTRY_EOD)
+ return NULL;
+ if (entry[0] == EXFAT_ENTRY_LABEL)
+ return (struct exfat_entry_label *) entry;
+
+ offset += EXFAT_ENTRY_SIZE;
+ if (offset % CLUSTER_SIZE(sb) == 0) {
+ cluster = next_cluster(pr, sb, cluster);
+ if (cluster < EXFAT_FIRST_DATA_CLUSTER)
+ return NULL;
+ if (cluster > EXFAT_LAST_DATA_CLUSTER)
+ return NULL;
+ offset = cluster_to_offset(sb, cluster);
+ }
+ }
+
+ return NULL;
+}
+
+static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct exfat_super_block *sb;
+ struct exfat_entry_label *label;
+
+ sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block);
+ if (!sb || !CLUSTER_SIZE(sb))
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ label = find_label(pr, sb);
+ if (label)
+ blkid_probe_set_utf8label(pr, label->name,
+ min((size_t) label->length * 2, sizeof(label->name)),
+ UL_ENCODE_UTF16LE);
+ else if (errno)
+ return -errno;
+
+ blkid_probe_sprintf_uuid(pr, sb->volume_serial, 4,
+ "%02hhX%02hhX-%02hhX%02hhX",
+ sb->volume_serial[3], sb->volume_serial[2],
+ sb->volume_serial[1], sb->volume_serial[0]);
+
+ blkid_probe_sprintf_version(pr, "%u.%u",
+ sb->version.vermaj, sb->version.vermin);
+
+ blkid_probe_set_block_size(pr, BLOCK_SIZE(sb));
+
+ return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo exfat_idinfo =
+{
+ .name = "exfat",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_exfat,
+ .magics =
+ {
+ { .magic = "EXFAT ", .len = 8, .sboff = 3 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/exfs.c b/libblkid/src/superblocks/exfs.c
new file mode 100644
index 0000000..e0eafaf
--- /dev/null
+++ b/libblkid/src/superblocks/exfs.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2013 Eric Sandeen <sandeen@redhat.com>
+ * Copyright (C) 2017 Hewlett Packard Enterprise Development LP
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct exfs_super_block {
+ uint32_t sb_magicnum; /* magic number == EXFS_SB_MAGIC */
+ uint32_t sb_blocksize; /* logical block size, bytes */
+ uint64_t sb_dblocks; /* number of data blocks */
+ uint64_t sb_rblocks; /* number of realtime blocks */
+ uint64_t sb_rextents; /* number of realtime extents */
+ unsigned char sb_uuid[16]; /* file system unique id */
+ uint64_t sb_logstart; /* starting block of log if internal */
+ uint64_t sb_rootino; /* root inode number */
+ uint64_t sb_rbmino; /* bitmap inode for realtime extents */
+ uint64_t sb_rsumino; /* summary inode for rt bitmap */
+ uint32_t sb_rextsize; /* realtime extent size, blocks */
+ uint32_t sb_agblocks; /* size of an allocation group */
+ uint32_t sb_agcount; /* number of allocation groups */
+ uint32_t sb_rbmblocks; /* number of rt bitmap blocks */
+ uint32_t sb_logblocks; /* number of log blocks */
+
+ uint16_t sb_versionnum; /* header version == EXFS_SB_VERSION */
+ uint16_t sb_sectsize; /* volume sector size, bytes */
+ uint16_t sb_inodesize; /* inode size, bytes */
+ uint16_t sb_inopblock; /* inodes per block */
+ char sb_fname[12]; /* file system name */
+ uint8_t sb_blocklog; /* log2 of sb_blocksize */
+ uint8_t sb_sectlog; /* log2 of sb_sectsize */
+ uint8_t sb_inodelog; /* log2 of sb_inodesize */
+ uint8_t sb_inopblog; /* log2 of sb_inopblock */
+ uint8_t sb_agblklog; /* log2 of sb_agblocks (rounded up) */
+ uint8_t sb_rextslog; /* log2 of sb_rextents */
+ uint8_t sb_inprogress; /* mkfs is in progress, don't mount */
+ uint8_t sb_imax_pct; /* max % of fs for inode space */
+ /* statistics */
+ uint64_t sb_icount; /* allocated inodes */
+ uint64_t sb_ifree; /* free inodes */
+ uint64_t sb_fdblocks; /* free data blocks */
+ uint64_t sb_frextents; /* free realtime extents */
+
+ /* this is not all... but enough for libblkid */
+
+} __attribute__((packed));
+
+#define EXFS_MIN_BLOCKSIZE_LOG 9 /* i.e. 512 bytes */
+#define EXFS_MAX_BLOCKSIZE_LOG 16 /* i.e. 65536 bytes */
+#define EXFS_MIN_BLOCKSIZE (1 << EXFS_MIN_BLOCKSIZE_LOG)
+#define EXFS_MAX_BLOCKSIZE (1 << EXFS_MAX_BLOCKSIZE_LOG)
+#define EXFS_MIN_SECTORSIZE_LOG 9 /* i.e. 512 bytes */
+#define EXFS_MAX_SECTORSIZE_LOG 15 /* i.e. 32768 bytes */
+#define EXFS_MIN_SECTORSIZE (1 << EXFS_MIN_SECTORSIZE_LOG)
+#define EXFS_MAX_SECTORSIZE (1 << EXFS_MAX_SECTORSIZE_LOG)
+
+#define EXFS_DINODE_MIN_LOG 8
+#define EXFS_DINODE_MAX_LOG 11
+#define EXFS_DINODE_MIN_SIZE (1 << EXFS_DINODE_MIN_LOG)
+#define EXFS_DINODE_MAX_SIZE (1 << EXFS_DINODE_MAX_LOG)
+
+#define EXFS_MAX_RTEXTSIZE (1024 * 1024 * 1024) /* 1GB */
+#define EXFS_DFL_RTEXTSIZE (64 * 1024) /* 64kB */
+#define EXFS_MIN_RTEXTSIZE (4 * 1024) /* 4kB */
+
+#define EXFS_MIN_AG_BLOCKS 64
+#define EXFS_MAX_DBLOCKS(s) ((uint64_t)(s)->sb_agcount * (s)->sb_agblocks)
+#define EXFS_MIN_DBLOCKS(s) ((uint64_t)((s)->sb_agcount - 1) * \
+ (s)->sb_agblocks + EXFS_MIN_AG_BLOCKS)
+
+
+static void sb_from_disk(struct exfs_super_block *from,
+ struct exfs_super_block *to)
+{
+
+ to->sb_magicnum = be32_to_cpu(from->sb_magicnum);
+ to->sb_blocksize = be32_to_cpu(from->sb_blocksize);
+ to->sb_dblocks = be64_to_cpu(from->sb_dblocks);
+ to->sb_rblocks = be64_to_cpu(from->sb_rblocks);
+ to->sb_rextents = be64_to_cpu(from->sb_rextents);
+ to->sb_logstart = be64_to_cpu(from->sb_logstart);
+ to->sb_rootino = be64_to_cpu(from->sb_rootino);
+ to->sb_rbmino = be64_to_cpu(from->sb_rbmino);
+ to->sb_rsumino = be64_to_cpu(from->sb_rsumino);
+ to->sb_rextsize = be32_to_cpu(from->sb_rextsize);
+ to->sb_agblocks = be32_to_cpu(from->sb_agblocks);
+ to->sb_agcount = be32_to_cpu(from->sb_agcount);
+ to->sb_rbmblocks = be32_to_cpu(from->sb_rbmblocks);
+ to->sb_logblocks = be32_to_cpu(from->sb_logblocks);
+ to->sb_versionnum = be16_to_cpu(from->sb_versionnum);
+ to->sb_sectsize = be16_to_cpu(from->sb_sectsize);
+ to->sb_inodesize = be16_to_cpu(from->sb_inodesize);
+ to->sb_inopblock = be16_to_cpu(from->sb_inopblock);
+ to->sb_blocklog = from->sb_blocklog;
+ to->sb_sectlog = from->sb_sectlog;
+ to->sb_inodelog = from->sb_inodelog;
+ to->sb_inopblog = from->sb_inopblog;
+ to->sb_agblklog = from->sb_agblklog;
+ to->sb_rextslog = from->sb_rextslog;
+ to->sb_inprogress = from->sb_inprogress;
+ to->sb_imax_pct = from->sb_imax_pct;
+ to->sb_icount = be64_to_cpu(from->sb_icount);
+ to->sb_ifree = be64_to_cpu(from->sb_ifree);
+ to->sb_fdblocks = be64_to_cpu(from->sb_fdblocks);
+ to->sb_frextents = be64_to_cpu(from->sb_frextents);
+}
+
+static int exfs_verify_sb(struct exfs_super_block *ondisk)
+{
+ struct exfs_super_block sb, *sbp = &sb;
+
+ /* beXX_to_cpu(), but don't convert UUID and fsname! */
+ sb_from_disk(ondisk, sbp);
+
+ /* sanity checks, we don't want to rely on magic string only */
+ if (sbp->sb_agcount <= 0 ||
+ sbp->sb_sectsize < EXFS_MIN_SECTORSIZE ||
+ sbp->sb_sectsize > EXFS_MAX_SECTORSIZE ||
+ sbp->sb_sectlog < EXFS_MIN_SECTORSIZE_LOG ||
+ sbp->sb_sectlog > EXFS_MAX_SECTORSIZE_LOG ||
+ sbp->sb_sectsize != (1 << sbp->sb_sectlog) ||
+ sbp->sb_blocksize < EXFS_MIN_BLOCKSIZE ||
+ sbp->sb_blocksize > EXFS_MAX_BLOCKSIZE ||
+ sbp->sb_blocklog < EXFS_MIN_BLOCKSIZE_LOG ||
+ sbp->sb_blocklog > EXFS_MAX_BLOCKSIZE_LOG ||
+ sbp->sb_blocksize != (1ULL << sbp->sb_blocklog) ||
+ sbp->sb_inodesize < EXFS_DINODE_MIN_SIZE ||
+ sbp->sb_inodesize > EXFS_DINODE_MAX_SIZE ||
+ sbp->sb_inodelog < EXFS_DINODE_MIN_LOG ||
+ sbp->sb_inodelog > EXFS_DINODE_MAX_LOG ||
+ sbp->sb_inodesize != (1 << sbp->sb_inodelog) ||
+ (sbp->sb_blocklog - sbp->sb_inodelog != sbp->sb_inopblog) ||
+ (sbp->sb_rextsize * sbp->sb_blocksize > EXFS_MAX_RTEXTSIZE) ||
+ (sbp->sb_rextsize * sbp->sb_blocksize < EXFS_MIN_RTEXTSIZE) ||
+ (sbp->sb_imax_pct > 100 /* zero sb_imax_pct is valid */) ||
+ sbp->sb_dblocks == 0 ||
+ sbp->sb_dblocks > EXFS_MAX_DBLOCKS(sbp) ||
+ sbp->sb_dblocks < EXFS_MIN_DBLOCKS(sbp))
+ return 0;
+ return 1;
+}
+
+static int probe_exfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct exfs_super_block *xs;
+
+ xs = blkid_probe_get_sb(pr, mag, struct exfs_super_block);
+ if (!xs)
+ return errno ? -errno : 1;
+
+ if (!exfs_verify_sb(xs))
+ return 1;
+
+ if (*xs->sb_fname != '\0')
+ blkid_probe_set_label(pr, (unsigned char *) xs->sb_fname,
+ sizeof(xs->sb_fname));
+
+ blkid_probe_set_uuid(pr, xs->sb_uuid);
+
+ blkid_probe_set_block_size(pr, be32_to_cpu(xs->sb_blocksize));
+
+ return 0;
+}
+
+const struct blkid_idinfo exfs_idinfo =
+{
+ .name = "exfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_exfs,
+ .magics =
+ {
+ { .magic = "EXFS", .len = 4 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/ext.c b/libblkid/src/superblocks/ext.c
new file mode 100644
index 0000000..3870522
--- /dev/null
+++ b/libblkid/src/superblocks/ext.c
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+#include <time.h>
+
+#include "superblocks.h"
+
+struct ext2_super_block {
+ uint32_t s_inodes_count;
+ uint32_t s_blocks_count;
+ uint32_t s_r_blocks_count;
+ uint32_t s_free_blocks_count;
+ uint32_t s_free_inodes_count;
+ uint32_t s_first_data_block;
+ uint32_t s_log_block_size;
+ uint32_t s_dummy3[7];
+ unsigned char s_magic[2];
+ uint16_t s_state;
+ uint16_t s_errors;
+ uint16_t s_minor_rev_level;
+ uint32_t s_lastcheck;
+ uint32_t s_checkinterval;
+ uint32_t s_creator_os;
+ uint32_t s_rev_level;
+ uint16_t s_def_resuid;
+ uint16_t s_def_resgid;
+ uint32_t s_first_ino;
+ uint16_t s_inode_size;
+ uint16_t s_block_group_nr;
+ uint32_t s_feature_compat;
+ uint32_t s_feature_incompat;
+ uint32_t s_feature_ro_compat;
+ unsigned char s_uuid[16];
+ char s_volume_name[16];
+ char s_last_mounted[64];
+ uint32_t s_algorithm_usage_bitmap;
+ uint8_t s_prealloc_blocks;
+ uint8_t s_prealloc_dir_blocks;
+ uint16_t s_reserved_gdt_blocks;
+ uint8_t s_journal_uuid[16];
+ uint32_t s_journal_inum;
+ uint32_t s_journal_dev;
+ uint32_t s_last_orphan;
+ uint32_t s_hash_seed[4];
+ uint8_t s_def_hash_version;
+ uint8_t s_jnl_backup_type;
+ uint16_t s_reserved_word_pad;
+ uint32_t s_default_mount_opts;
+ uint32_t s_first_meta_bg;
+ uint32_t s_mkfs_time;
+ uint32_t s_jnl_blocks[17];
+ uint32_t s_blocks_count_hi;
+ uint32_t s_r_blocks_count_hi;
+ uint32_t s_free_blocks_hi;
+ uint16_t s_min_extra_isize;
+ uint16_t s_want_extra_isize;
+ uint32_t s_flags;
+ uint16_t s_raid_stride;
+ uint16_t s_mmp_interval;
+ uint64_t s_mmp_block;
+ uint32_t s_raid_stripe_width;
+ uint32_t s_reserved[163];
+} __attribute__((packed));
+
+/* magic string */
+#define EXT_SB_MAGIC "\123\357"
+/* supper block offset */
+#define EXT_SB_OFF 0x400
+/* supper block offset in kB */
+#define EXT_SB_KBOFF (EXT_SB_OFF >> 10)
+/* magic string offset within super block */
+#define EXT_MAG_OFF 0x38
+
+
+
+/* for s_flags */
+#define EXT2_FLAGS_TEST_FILESYS 0x0004
+
+/* for s_feature_compat */
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004
+
+/* for s_feature_ro_compat */
+#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
+#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
+#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004
+#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008
+#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010
+#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020
+#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040
+
+/* for s_feature_incompat */
+#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002
+#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004
+#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008
+#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010
+#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040 /* extents support */
+#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080
+#define EXT4_FEATURE_INCOMPAT_MMP 0x0100
+#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200
+
+#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
+ EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
+#define EXT2_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE| \
+ EXT2_FEATURE_INCOMPAT_META_BG)
+#define EXT2_FEATURE_INCOMPAT_UNSUPPORTED ~EXT2_FEATURE_INCOMPAT_SUPP
+#define EXT2_FEATURE_RO_COMPAT_UNSUPPORTED ~EXT2_FEATURE_RO_COMPAT_SUPP
+
+#define EXT3_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
+ EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
+#define EXT3_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE| \
+ EXT3_FEATURE_INCOMPAT_RECOVER| \
+ EXT2_FEATURE_INCOMPAT_META_BG)
+#define EXT3_FEATURE_INCOMPAT_UNSUPPORTED ~EXT3_FEATURE_INCOMPAT_SUPP
+#define EXT3_FEATURE_RO_COMPAT_UNSUPPORTED ~EXT3_FEATURE_RO_COMPAT_SUPP
+
+/*
+ * Starting in 2.6.29, ext4 can be used to support filesystems
+ * without a journal.
+ */
+#define EXT4_SUPPORTS_EXT2 KERNEL_VERSION(2, 6, 29)
+
+/*
+ * reads superblock and returns:
+ * fc = feature_compat
+ * fi = feature_incompat
+ * frc = feature_ro_compat
+ */
+static struct ext2_super_block *ext_get_super(
+ blkid_probe pr, uint32_t *fc, uint32_t *fi, uint32_t *frc)
+{
+ struct ext2_super_block *es;
+
+ es = (struct ext2_super_block *)
+ blkid_probe_get_buffer(pr, EXT_SB_OFF, 0x200);
+ if (!es)
+ return NULL;
+ if (fc)
+ *fc = le32_to_cpu(es->s_feature_compat);
+ if (fi)
+ *fi = le32_to_cpu(es->s_feature_incompat);
+ if (frc)
+ *frc = le32_to_cpu(es->s_feature_ro_compat);
+
+ return es;
+}
+
+static void ext_get_info(blkid_probe pr, int ver, struct ext2_super_block *es)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ DBG(PROBE, ul_debug("ext2_sb.compat = %08X:%08X:%08X",
+ le32_to_cpu(es->s_feature_compat),
+ le32_to_cpu(es->s_feature_incompat),
+ le32_to_cpu(es->s_feature_ro_compat)));
+
+ if (*es->s_volume_name != '\0')
+ blkid_probe_set_label(pr, (unsigned char *) es->s_volume_name,
+ sizeof(es->s_volume_name));
+ blkid_probe_set_uuid(pr, es->s_uuid);
+
+ if (le32_to_cpu(es->s_feature_compat) & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
+ blkid_probe_set_uuid_as(pr, es->s_journal_uuid, "EXT_JOURNAL");
+
+ if (ver != 2 && (chn->flags & BLKID_SUBLKS_SECTYPE) &&
+ ((le32_to_cpu(es->s_feature_incompat) & EXT2_FEATURE_INCOMPAT_UNSUPPORTED) == 0))
+ blkid_probe_set_value(pr, "SEC_TYPE",
+ (unsigned char *) "ext2",
+ sizeof("ext2"));
+
+ blkid_probe_sprintf_version(pr, "%u.%u",
+ le32_to_cpu(es->s_rev_level),
+ le16_to_cpu(es->s_minor_rev_level));
+
+ if (le32_to_cpu(es->s_log_block_size) < 32)
+ blkid_probe_set_block_size(pr, 1024U << le32_to_cpu(es->s_log_block_size));
+}
+
+
+static int probe_jbd(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct ext2_super_block *es;
+ uint32_t fi;
+
+ es = ext_get_super(pr, NULL, &fi, NULL);
+ if (!es)
+ return errno ? -errno : 1;
+ if (!(fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV))
+ return 1;
+
+ ext_get_info(pr, 2, es);
+ blkid_probe_set_uuid_as(pr, es->s_uuid, "LOGUUID");
+
+ return 0;
+}
+
+static int probe_ext2(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct ext2_super_block *es;
+ uint32_t fc, frc, fi;
+
+ es = ext_get_super(pr, &fc, &fi, &frc);
+ if (!es)
+ return errno ? -errno : 1;
+
+ /* Distinguish between ext3 and ext2 */
+ if (fc & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
+ return 1;
+
+ /* Any features which ext2 doesn't understand */
+ if ((frc & EXT2_FEATURE_RO_COMPAT_UNSUPPORTED) ||
+ (fi & EXT2_FEATURE_INCOMPAT_UNSUPPORTED))
+ return 1;
+
+ ext_get_info(pr, 2, es);
+ return 0;
+}
+
+static int probe_ext3(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct ext2_super_block *es;
+ uint32_t fc, frc, fi;
+
+ es = ext_get_super(pr, &fc, &fi, &frc);
+ if (!es)
+ return errno ? -errno : 1;
+
+ /* ext3 requires journal */
+ if (!(fc & EXT3_FEATURE_COMPAT_HAS_JOURNAL))
+ return 1;
+
+ /* Any features which ext3 doesn't understand */
+ if ((frc & EXT3_FEATURE_RO_COMPAT_UNSUPPORTED) ||
+ (fi & EXT3_FEATURE_INCOMPAT_UNSUPPORTED))
+ return 1;
+
+ ext_get_info(pr, 3, es);
+ return 0;
+}
+
+
+static int probe_ext4dev(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct ext2_super_block *es;
+ uint32_t fc, frc, fi;
+
+ es = ext_get_super(pr, &fc, &fi, &frc);
+ if (!es)
+ return errno ? -errno : 1;
+
+ /* Distinguish from jbd */
+ if (fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)
+ return 1;
+
+ if (!(le32_to_cpu(es->s_flags) & EXT2_FLAGS_TEST_FILESYS))
+ return 1;
+
+ ext_get_info(pr, 4, es);
+ return 0;
+}
+
+static int probe_ext4(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct ext2_super_block *es;
+ uint32_t fc, frc, fi;
+
+ es = ext_get_super(pr, &fc, &fi, &frc);
+ if (!es)
+ return errno ? -errno : 1;
+
+ /* Distinguish from jbd */
+ if (fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)
+ return 1;
+
+ /* Ext4 has at least one feature which ext3 doesn't understand */
+ if (!(frc & EXT3_FEATURE_RO_COMPAT_UNSUPPORTED) &&
+ !(fi & EXT3_FEATURE_INCOMPAT_UNSUPPORTED))
+ return 1;
+
+ /*
+ * If the filesystem is a OK for use by in-development
+ * filesystem code, and ext4dev is supported or ext4 is not
+ * supported, then don't call ourselves ext4, so we can redo
+ * the detection and mark the filesystem as ext4dev.
+ *
+ * If the filesystem is marked as in use by production
+ * filesystem, then it can only be used by ext4 and NOT by
+ * ext4dev.
+ */
+ if (le32_to_cpu(es->s_flags) & EXT2_FLAGS_TEST_FILESYS)
+ return 1;
+
+ ext_get_info(pr, 4, es);
+ return 0;
+}
+
+#define BLKID_EXT_MAGICS \
+ { \
+ { \
+ .magic = EXT_SB_MAGIC, \
+ .len = sizeof(EXT_SB_MAGIC) - 1, \
+ .kboff = EXT_SB_KBOFF, \
+ .sboff = EXT_MAG_OFF \
+ }, \
+ { NULL } \
+ }
+
+const struct blkid_idinfo jbd_idinfo =
+{
+ .name = "jbd",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_jbd,
+ .magics = BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext2_idinfo =
+{
+ .name = "ext2",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ext2,
+ .magics = BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext3_idinfo =
+{
+ .name = "ext3",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ext3,
+ .magics = BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext4_idinfo =
+{
+ .name = "ext4",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ext4,
+ .magics = BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext4dev_idinfo =
+{
+ .name = "ext4dev",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ext4dev,
+ .magics = BLKID_EXT_MAGICS
+};
+
diff --git a/libblkid/src/superblocks/f2fs.c b/libblkid/src/superblocks/f2fs.c
new file mode 100644
index 0000000..aed93e2
--- /dev/null
+++ b/libblkid/src/superblocks/f2fs.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 Alejandro Martinez Ruiz <alex@nowcomputing.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+
+#define F2FS_MAGIC "\x10\x20\xF5\xF2"
+#define F2FS_MAGIC_OFF 0
+#define F2FS_UUID_SIZE 16
+#define F2FS_LABEL_SIZE 512
+#define F2FS_SB1_OFF 0x400
+#define F2FS_SB1_KBOFF (F2FS_SB1_OFF >> 10)
+#define F2FS_SB2_OFF 0x1400
+#define F2FS_SB2_KBOFF (F2FS_SB2_OFF >> 10)
+
+struct f2fs_super_block { /* According to version 1.1 */
+/* 0x00 */ uint32_t magic; /* Magic Number */
+/* 0x04 */ uint16_t major_ver; /* Major Version */
+/* 0x06 */ uint16_t minor_ver; /* Minor Version */
+/* 0x08 */ uint32_t log_sectorsize; /* log2 sector size in bytes */
+/* 0x0C */ uint32_t log_sectors_per_block; /* log2 # of sectors per block */
+/* 0x10 */ uint32_t log_blocksize; /* log2 block size in bytes */
+/* 0x14 */ uint32_t log_blocks_per_seg; /* log2 # of blocks per segment */
+/* 0x18 */ uint32_t segs_per_sec; /* # of segments per section */
+/* 0x1C */ uint32_t secs_per_zone; /* # of sections per zone */
+/* 0x20 */ uint32_t checksum_offset; /* checksum offset inside super block */
+/* 0x24 */ uint64_t block_count; /* total # of user blocks */
+/* 0x2C */ uint32_t section_count; /* total # of sections */
+/* 0x30 */ uint32_t segment_count; /* total # of segments */
+/* 0x34 */ uint32_t segment_count_ckpt; /* # of segments for checkpoint */
+/* 0x38 */ uint32_t segment_count_sit; /* # of segments for SIT */
+/* 0x3C */ uint32_t segment_count_nat; /* # of segments for NAT */
+/* 0x40 */ uint32_t segment_count_ssa; /* # of segments for SSA */
+/* 0x44 */ uint32_t segment_count_main; /* # of segments for main area */
+/* 0x48 */ uint32_t segment0_blkaddr; /* start block address of segment 0 */
+/* 0x4C */ uint32_t cp_blkaddr; /* start block address of checkpoint */
+/* 0x50 */ uint32_t sit_blkaddr; /* start block address of SIT */
+/* 0x54 */ uint32_t nat_blkaddr; /* start block address of NAT */
+/* 0x58 */ uint32_t ssa_blkaddr; /* start block address of SSA */
+/* 0x5C */ uint32_t main_blkaddr; /* start block address of main area */
+/* 0x60 */ uint32_t root_ino; /* root inode number */
+/* 0x64 */ uint32_t node_ino; /* node inode number */
+/* 0x68 */ uint32_t meta_ino; /* meta inode number */
+/* 0x6C */ uint8_t uuid[F2FS_UUID_SIZE]; /* 128-bit uuid for volume */
+/* 0x7C */ uint16_t volume_name[F2FS_LABEL_SIZE]; /* volume name */
+#if 0
+/* 0x47C */ uint32_t extension_count; /* # of extensions below */
+/* 0x480 */ uint8_t extension_list[64][8]; /* extension array */
+#endif
+} __attribute__((packed));
+
+static int probe_f2fs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct f2fs_super_block *sb;
+ uint16_t vermaj, vermin;
+
+ sb = blkid_probe_get_sb(pr, mag, struct f2fs_super_block);
+ if (!sb)
+ return errno ? -errno : 1;
+
+ vermaj = le16_to_cpu(sb->major_ver);
+ vermin = le16_to_cpu(sb->minor_ver);
+
+ /* For version 1.0 we cannot know the correct sb structure */
+ if (vermaj == 1 && vermin == 0)
+ return 0;
+
+ if (*((unsigned char *) sb->volume_name))
+ blkid_probe_set_utf8label(pr, (unsigned char *) sb->volume_name,
+ sizeof(sb->volume_name),
+ UL_ENCODE_UTF16LE);
+
+ blkid_probe_set_uuid(pr, sb->uuid);
+ blkid_probe_sprintf_version(pr, "%u.%u", vermaj, vermin);
+ if (le32_to_cpu(sb->log_blocksize) < 32)
+ blkid_probe_set_block_size(pr, 1U << le32_to_cpu(sb->log_blocksize));
+ return 0;
+}
+
+const struct blkid_idinfo f2fs_idinfo =
+{
+ .name = "f2fs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_f2fs,
+ .magics =
+ {
+ {
+ .magic = F2FS_MAGIC,
+ .len = 4,
+ .kboff = F2FS_SB1_KBOFF,
+ .sboff = F2FS_MAGIC_OFF
+ },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/gfs.c b/libblkid/src/superblocks/gfs.c
new file mode 100644
index 0000000..37a74b1
--- /dev/null
+++ b/libblkid/src/superblocks/gfs.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* Common gfs/gfs2 constants: */
+#define GFS_LOCKNAME_LEN 64
+
+/* gfs1 constants: */
+#define GFS_FORMAT_FS 1309
+#define GFS_FORMAT_MULTI 1401
+
+struct gfs2_meta_header {
+ uint32_t mh_magic;
+ uint32_t mh_type;
+ uint64_t __pad0; /* Was generation number in gfs1 */
+ uint32_t mh_format;
+ uint32_t __pad1; /* Was incarnation number in gfs1 */
+};
+
+struct gfs2_inum {
+ uint64_t no_formal_ino;
+ uint64_t no_addr;
+};
+
+struct gfs2_sb {
+ struct gfs2_meta_header sb_header;
+
+ uint32_t sb_fs_format;
+ uint32_t sb_multihost_format;
+ uint32_t __pad0; /* Was superblock flags in gfs1 */
+
+ uint32_t sb_bsize;
+ uint32_t sb_bsize_shift;
+ uint32_t __pad1; /* Was journal segment size in gfs1 */
+
+ struct gfs2_inum sb_master_dir; /* Was jindex dinode in gfs1 */
+ struct gfs2_inum __pad2; /* Was rindex dinode in gfs1 */
+ struct gfs2_inum sb_root_dir;
+
+ char sb_lockproto[GFS_LOCKNAME_LEN];
+ char sb_locktable[GFS_LOCKNAME_LEN];
+
+ struct gfs2_inum __pad3; /* Was quota inode in gfs1 */
+ struct gfs2_inum __pad4; /* Was license inode in gfs1 */
+ uint8_t sb_uuid[16]; /* The UUID maybe 0 for backwards compat */
+} __attribute__((packed));
+
+
+
+static int probe_gfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct gfs2_sb *sbd;
+
+ sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
+ if (!sbd)
+ return errno ? -errno : 1;
+
+ if (be32_to_cpu(sbd->sb_fs_format) == GFS_FORMAT_FS &&
+ be32_to_cpu(sbd->sb_multihost_format) == GFS_FORMAT_MULTI)
+ {
+ if (*sbd->sb_locktable)
+ blkid_probe_set_label(pr,
+ (unsigned char *) sbd->sb_locktable,
+ sizeof(sbd->sb_locktable));
+
+ blkid_probe_set_uuid(pr, sbd->sb_uuid);
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int gfs2_format_is_valid(uint32_t format)
+{
+ return (format >= 1800 && format < 1900);
+}
+static inline int gfs2_multiformat_is_valid(uint32_t multi)
+{
+ return (multi >= 1900 && multi < 2000);
+}
+
+static int probe_gfs2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct gfs2_sb *sbd;
+
+ sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
+ if (!sbd)
+ return errno ? -errno : 1;
+
+ if (gfs2_format_is_valid(be32_to_cpu(sbd->sb_fs_format)) &&
+ gfs2_multiformat_is_valid(be32_to_cpu(sbd->sb_multihost_format)))
+ {
+ if (*sbd->sb_locktable)
+ blkid_probe_set_label(pr,
+ (unsigned char *) sbd->sb_locktable,
+ sizeof(sbd->sb_locktable));
+ blkid_probe_set_uuid(pr, sbd->sb_uuid);
+ blkid_probe_set_version(pr, "1");
+ blkid_probe_set_block_size(pr, be32_to_cpu(sbd->sb_bsize));
+ return 0;
+ }
+ return 1;
+}
+
+const struct blkid_idinfo gfs_idinfo =
+{
+ .name = "gfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_gfs,
+ .minsz = 32 * 1024 * 1024, /* minimal size of GFS journal */
+ .magics =
+ {
+ { .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo gfs2_idinfo =
+{
+ .name = "gfs2",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_gfs2,
+ .minsz = 32 * 1024 * 1024, /* minimal size of GFS journal */
+ .magics =
+ {
+ { .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/hfs.c b/libblkid/src/superblocks/hfs.c
new file mode 100644
index 0000000..fceab95
--- /dev/null
+++ b/libblkid/src/superblocks/hfs.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+#include "md5.h"
+
+/* HFS / HFS+ */
+struct hfs_finder_info {
+ uint32_t boot_folder;
+ uint32_t start_app;
+ uint32_t open_folder;
+ uint32_t os9_folder;
+ uint32_t reserved;
+ uint32_t osx_folder;
+ uint8_t id[8];
+} __attribute__((packed));
+
+#define HFS_SECTOR_SIZE 512
+
+struct hfs_mdb {
+ uint8_t signature[2];
+ uint32_t cr_date;
+ uint32_t ls_Mod;
+ uint16_t atrb;
+ uint16_t nm_fls;
+ uint16_t vbm_st;
+ uint16_t alloc_ptr;
+ uint16_t nm_al_blks;
+ uint32_t al_blk_size;
+ uint32_t clp_size;
+ uint16_t al_bl_st;
+ uint32_t nxt_cnid;
+ uint16_t free_bks;
+ uint8_t label_len;
+ uint8_t label[27];
+ uint32_t vol_bkup;
+ uint16_t vol_seq_num;
+ uint32_t wr_cnt;
+ uint32_t xt_clump_size;
+ uint32_t ct_clump_size;
+ uint16_t num_root_dirs;
+ uint32_t file_count;
+ uint32_t dir_count;
+ struct hfs_finder_info finder_info;
+ uint8_t embed_sig[2];
+ uint16_t embed_startblock;
+ uint16_t embed_blockcount;
+} __attribute__((packed));
+
+
+#define HFS_NODE_LEAF 0xff
+#define HFSPLUS_POR_CNID 1
+
+struct hfsplus_bnode_descriptor {
+ uint32_t next;
+ uint32_t prev;
+ uint8_t type;
+ uint8_t height;
+ uint16_t num_recs;
+ uint16_t reserved;
+} __attribute__((packed));
+
+struct hfsplus_bheader_record {
+ uint16_t depth;
+ uint32_t root;
+ uint32_t leaf_count;
+ uint32_t leaf_head;
+ uint32_t leaf_tail;
+ uint16_t node_size;
+} __attribute__((packed));
+
+struct hfsplus_catalog_key {
+ uint16_t key_len;
+ uint32_t parent_id;
+ uint16_t unicode_len;
+ uint8_t unicode[255 * 2];
+} __attribute__((packed));
+
+struct hfsplus_extent {
+ uint32_t start_block;
+ uint32_t block_count;
+} __attribute__((packed));
+
+#define HFSPLUS_EXTENT_COUNT 8
+struct hfsplus_fork {
+ uint64_t total_size;
+ uint32_t clump_size;
+ uint32_t total_blocks;
+ struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+} __attribute__((packed));
+
+struct hfsplus_vol_header {
+ uint8_t signature[2];
+ uint16_t version;
+ uint32_t attributes;
+ uint32_t last_mount_vers;
+ uint32_t reserved;
+ uint32_t create_date;
+ uint32_t modify_date;
+ uint32_t backup_date;
+ uint32_t checked_date;
+ uint32_t file_count;
+ uint32_t folder_count;
+ uint32_t blocksize;
+ uint32_t total_blocks;
+ uint32_t free_blocks;
+ uint32_t next_alloc;
+ uint32_t rsrc_clump_sz;
+ uint32_t data_clump_sz;
+ uint32_t next_cnid;
+ uint32_t write_count;
+ uint64_t encodings_bmp;
+ struct hfs_finder_info finder_info;
+ struct hfsplus_fork alloc_file;
+ struct hfsplus_fork ext_file;
+ struct hfsplus_fork cat_file;
+ struct hfsplus_fork attr_file;
+ struct hfsplus_fork start_file;
+} __attribute__((packed));
+
+#define HFSPLUS_SECTOR_SIZE 512
+
+static int hfs_set_uuid(blkid_probe pr, unsigned char const *hfs_info, size_t len)
+{
+ static unsigned char const hash_init[UL_MD5LENGTH] = {
+ 0xb3, 0xe2, 0x0f, 0x39, 0xf2, 0x92, 0x11, 0xd6,
+ 0x97, 0xa4, 0x00, 0x30, 0x65, 0x43, 0xec, 0xac
+ };
+ unsigned char uuid[UL_MD5LENGTH];
+ struct UL_MD5Context md5c;
+
+ if (memcmp(hfs_info, "\0\0\0\0\0\0\0\0", len) == 0)
+ return -1;
+
+ ul_MD5Init(&md5c);
+ ul_MD5Update(&md5c, hash_init, UL_MD5LENGTH);
+ ul_MD5Update(&md5c, hfs_info, len);
+ ul_MD5Final(uuid, &md5c);
+
+ uuid[6] = 0x30 | (uuid[6] & 0x0f);
+ uuid[8] = 0x80 | (uuid[8] & 0x3f);
+ return blkid_probe_set_uuid(pr, uuid);
+}
+
+static int probe_hfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct hfs_mdb *hfs;
+ int size;
+
+ hfs = blkid_probe_get_sb(pr, mag, struct hfs_mdb);
+ if (!hfs)
+ return errno ? -errno : 1;
+
+ if ((memcmp(hfs->embed_sig, "H+", 2) == 0) ||
+ (memcmp(hfs->embed_sig, "HX", 2) == 0))
+ return 1; /* Not hfs, but an embedded HFS+ */
+
+ size = be32_to_cpu(hfs->al_blk_size);
+ if (!size || (size & (HFS_SECTOR_SIZE - 1))) {
+ DBG(LOWPROBE, ul_debug("\tbad allocation size - ignore"));
+ return 1;
+ }
+
+ hfs_set_uuid(pr, hfs->finder_info.id, sizeof(hfs->finder_info.id));
+
+ size = hfs->label_len;
+ if ((size_t) size > sizeof(hfs->label))
+ size = sizeof(hfs->label);
+ blkid_probe_set_label(pr, hfs->label, size);
+ return 0;
+}
+
+static int probe_hfsplus(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+ struct hfsplus_bnode_descriptor *descr;
+ struct hfsplus_bheader_record *bnode;
+ struct hfsplus_catalog_key *key;
+ struct hfsplus_vol_header *hfsplus;
+ struct hfs_mdb *sbd;
+ unsigned int alloc_block_size;
+ unsigned int alloc_first_block;
+ unsigned int embed_first_block;
+ unsigned int off = 0;
+ unsigned int blocksize;
+ unsigned int cat_block;
+ unsigned int ext_block_start = 0;
+ unsigned int ext_block_count;
+ unsigned int record_count;
+ unsigned int leaf_node_head;
+ unsigned int leaf_node_count;
+ unsigned int leaf_node_size;
+ unsigned int leaf_block;
+ int ext;
+ uint64_t leaf_off;
+ unsigned char *buf;
+
+ sbd = blkid_probe_get_sb(pr, mag, struct hfs_mdb);
+ if (!sbd)
+ return errno ? -errno : 1;
+
+ /* Check for a HFS+ volume embedded in a HFS volume */
+ if (memcmp(sbd->signature, "BD", 2) == 0) {
+ if ((memcmp(sbd->embed_sig, "H+", 2) != 0) &&
+ (memcmp(sbd->embed_sig, "HX", 2) != 0))
+ /* This must be an HFS volume, so fail */
+ return 1;
+
+ alloc_block_size = be32_to_cpu(sbd->al_blk_size);
+ alloc_first_block = be16_to_cpu(sbd->al_bl_st);
+ embed_first_block = be16_to_cpu(sbd->embed_startblock);
+ off = (alloc_first_block * 512) +
+ (embed_first_block * alloc_block_size);
+
+ buf = blkid_probe_get_buffer(pr,
+ off + (mag->kboff * 1024),
+ sizeof(struct hfsplus_vol_header));
+ hfsplus = (struct hfsplus_vol_header *) buf;
+
+ } else
+ hfsplus = blkid_probe_get_sb(pr, mag,
+ struct hfsplus_vol_header);
+
+ if (!hfsplus)
+ return errno ? -errno : 1;
+
+ if ((memcmp(hfsplus->signature, "H+", 2) != 0) &&
+ (memcmp(hfsplus->signature, "HX", 2) != 0))
+ return 1;
+
+ hfs_set_uuid(pr, hfsplus->finder_info.id, sizeof(hfsplus->finder_info.id));
+
+ blocksize = be32_to_cpu(hfsplus->blocksize);
+ if (blocksize < HFSPLUS_SECTOR_SIZE)
+ return 1;
+
+ blkid_probe_set_block_size(pr, blocksize);
+
+ memcpy(extents, hfsplus->cat_file.extents, sizeof(extents));
+ cat_block = be32_to_cpu(extents[0].start_block);
+
+ buf = blkid_probe_get_buffer(pr,
+ off + ((uint64_t) cat_block * blocksize), 0x2000);
+ if (!buf)
+ return errno ? -errno : 0;
+
+ bnode = (struct hfsplus_bheader_record *)
+ &buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+ leaf_node_head = be32_to_cpu(bnode->leaf_head);
+ leaf_node_size = be16_to_cpu(bnode->node_size);
+ leaf_node_count = be32_to_cpu(bnode->leaf_count);
+
+ if (leaf_node_size < sizeof(struct hfsplus_bnode_descriptor) +
+ sizeof(struct hfsplus_catalog_key) || leaf_node_count == 0)
+ return 0;
+
+ leaf_block = (leaf_node_head * leaf_node_size) / blocksize;
+
+ /* get physical location */
+ for (ext = 0; ext < HFSPLUS_EXTENT_COUNT; ext++) {
+ ext_block_start = be32_to_cpu(extents[ext].start_block);
+ ext_block_count = be32_to_cpu(extents[ext].block_count);
+ if (ext_block_count == 0)
+ return 0;
+
+ /* this is our extent */
+ if (leaf_block < ext_block_count)
+ break;
+
+ leaf_block -= ext_block_count;
+ }
+ if (ext == HFSPLUS_EXTENT_COUNT)
+ return 0;
+
+ leaf_off = ((uint64_t) ext_block_start + leaf_block) * blocksize;
+
+ buf = blkid_probe_get_buffer(pr,
+ (uint64_t) off + leaf_off,
+ leaf_node_size);
+ if (!buf)
+ return errno ? -errno : 0;
+
+ descr = (struct hfsplus_bnode_descriptor *) buf;
+ record_count = be16_to_cpu(descr->num_recs);
+ if (record_count == 0)
+ return 0;
+
+ if (descr->type != HFS_NODE_LEAF)
+ return 0;
+
+ key = (struct hfsplus_catalog_key *)
+ &buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+ if (be32_to_cpu(key->parent_id) != HFSPLUS_POR_CNID ||
+ be16_to_cpu(key->unicode_len) > 255)
+ return 0;
+
+ blkid_probe_set_utf8label(pr, key->unicode,
+ be16_to_cpu(key->unicode_len) * 2,
+ UL_ENCODE_UTF16BE);
+ return 0;
+}
+
+const struct blkid_idinfo hfs_idinfo =
+{
+ .name = "hfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_hfs,
+ .flags = BLKID_IDINFO_TOLERANT,
+ .magics =
+ {
+ { .magic = "BD", .len = 2, .kboff = 1 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo hfsplus_idinfo =
+{
+ .name = "hfsplus",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_hfsplus,
+ .magics =
+ {
+ { .magic = "BD", .len = 2, .kboff = 1 },
+ { .magic = "H+", .len = 2, .kboff = 1 },
+ { .magic = "HX", .len = 2, .kboff = 1 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/highpoint_raid.c b/libblkid/src/superblocks/highpoint_raid.c
new file mode 100644
index 0000000..2487930
--- /dev/null
+++ b/libblkid/src/superblocks/highpoint_raid.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct hpt45x_metadata {
+ uint32_t magic;
+};
+
+#define HPT45X_MAGIC_OK 0x5a7816f3
+#define HPT45X_MAGIC_BAD 0x5a7816fd
+
+static int probe_highpoint45x(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct hpt45x_metadata *hpt;
+ uint64_t off;
+ uint32_t magic;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200) - 11) * 0x200;
+ hpt = (struct hpt45x_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct hpt45x_metadata));
+ if (!hpt)
+ return errno ? -errno : 1;
+ magic = le32_to_cpu(hpt->magic);
+ if (magic != HPT45X_MAGIC_OK && magic != HPT45X_MAGIC_BAD)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(hpt->magic),
+ (unsigned char *) &hpt->magic))
+ return 1;
+ return 0;
+}
+
+static int probe_highpoint37x(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+ return 0;
+}
+
+
+const struct blkid_idinfo highpoint45x_idinfo = {
+ .name = "hpt45x_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_highpoint45x,
+ .magics = BLKID_NONE_MAGIC
+};
+
+const struct blkid_idinfo highpoint37x_idinfo = {
+ .name = "hpt37x_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_highpoint37x,
+ .magics = {
+ /*
+ * Superblock offset: 4608 bytes (9 sectors)
+ * Magic string offset within superblock: 32 bytes
+ *
+ * kboff = (4608 + 32) / 1024
+ * sboff = (4608 + 32) % kboff
+ */
+ { .magic = "\xf0\x16\x78\x5a", .len = 4, .kboff = 4, .sboff = 544 },
+ { .magic = "\xfd\x16\x78\x5a", .len = 4, .kboff = 4, .sboff = 544 },
+ { NULL }
+ }
+};
+
+
diff --git a/libblkid/src/superblocks/hpfs.c b/libblkid/src/superblocks/hpfs.c
new file mode 100644
index 0000000..dcf4520
--- /dev/null
+++ b/libblkid/src/superblocks/hpfs.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct hpfs_boot_block
+{
+ uint8_t jmp[3];
+ uint8_t oem_id[8];
+ uint8_t bytes_per_sector[2];
+ uint8_t sectors_per_cluster;
+ uint8_t n_reserved_sectors[2];
+ uint8_t n_fats;
+ uint8_t n_rootdir_entries[2];
+ uint8_t n_sectors_s[2];
+ uint8_t media_byte;
+ uint16_t sectors_per_fat;
+ uint16_t sectors_per_track;
+ uint16_t heads_per_cyl;
+ uint32_t n_hidden_sectors;
+ uint32_t n_sectors_l;
+ uint8_t drive_number;
+ uint8_t mbz;
+ uint8_t sig_28h;
+ uint8_t vol_serno[4];
+ uint8_t vol_label[11];
+ uint8_t sig_hpfs[8];
+ uint8_t pad[448];
+ uint8_t magic[2];
+} __attribute__((packed));
+
+struct hpfs_super_block
+{
+ uint8_t magic[4];
+ uint8_t magic1[4];
+ uint8_t version;
+} __attribute__((packed));
+
+struct hpfs_spare_super
+{
+ uint8_t magic[4];
+ uint8_t magic1[4];
+} __attribute__((packed));
+
+
+#define HPFS_SB_OFFSET 0x2000
+#define HPFS_SBSPARE_OFFSET 0x2200
+
+static int probe_hpfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct hpfs_super_block *hs;
+ struct hpfs_spare_super *hss;
+ struct hpfs_boot_block *hbb;
+ uint8_t version;
+
+ /* super block */
+ hs = blkid_probe_get_sb(pr, mag, struct hpfs_super_block);
+ if (!hs)
+ return errno ? -errno : 1;
+ version = hs->version;
+
+ /* spare super block */
+ hss = (struct hpfs_spare_super *)
+ blkid_probe_get_buffer(pr,
+ HPFS_SBSPARE_OFFSET,
+ sizeof(struct hpfs_spare_super));
+ if (!hss)
+ return errno ? -errno : 1;
+ if (memcmp(hss->magic, "\x49\x18\x91\xf9", 4) != 0)
+ return 1;
+
+ /* boot block (with UUID and LABEL) */
+ hbb = (struct hpfs_boot_block *)
+ blkid_probe_get_buffer(pr,
+ 0,
+ sizeof(struct hpfs_boot_block));
+ if (!hbb)
+ return errno ? -errno : 1;
+ if (memcmp(hbb->magic, "\x55\xaa", 2) == 0 &&
+ memcmp(hbb->sig_hpfs, "HPFS", 4) == 0 &&
+ hbb->sig_28h == 0x28) {
+ blkid_probe_set_label(pr, hbb->vol_label, sizeof(hbb->vol_label));
+ blkid_probe_sprintf_uuid(pr,
+ hbb->vol_serno, sizeof(hbb->vol_serno),
+ "%02X%02X-%02X%02X",
+ hbb->vol_serno[3], hbb->vol_serno[2],
+ hbb->vol_serno[1], hbb->vol_serno[0]);
+ }
+ blkid_probe_sprintf_version(pr, "%u", version);
+ blkid_probe_set_block_size(pr, 512);
+
+ return 0;
+}
+
+const struct blkid_idinfo hpfs_idinfo =
+{
+ .name = "hpfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_hpfs,
+ .magics =
+ {
+ {
+ .magic = "\x49\xe8\x95\xf9",
+ .len = 4,
+ .kboff = (HPFS_SB_OFFSET >> 10)
+ },
+ { NULL }
+ }
+};
+
+
diff --git a/libblkid/src/superblocks/iso9660.c b/libblkid/src/superblocks/iso9660.c
new file mode 100644
index 0000000..289a325
--- /dev/null
+++ b/libblkid/src/superblocks/iso9660.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired also by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include "superblocks.h"
+#include "cctype.h"
+
+struct iso9660_date {
+ unsigned char year[4];
+ unsigned char month[2];
+ unsigned char day[2];
+ unsigned char hour[2];
+ unsigned char minute[2];
+ unsigned char second[2];
+ unsigned char hundredth[2];
+ unsigned char offset;
+} __attribute__ ((packed));
+
+/* PVD - Primary volume descriptor */
+struct iso_volume_descriptor {
+ unsigned char vd_type;
+ unsigned char vd_id[5];
+ unsigned char vd_version;
+ unsigned char flags;
+ unsigned char system_id[32];
+ unsigned char volume_id[32];
+ unsigned char unused[8];
+ unsigned char space_size[8];
+ unsigned char escape_sequences[8];
+ unsigned char unused2[94];
+ unsigned char volume_set_id[128];
+ unsigned char publisher_id[128];
+ unsigned char data_preparer_id[128];
+ unsigned char application_id[128];
+ unsigned char unused3[111];
+ struct iso9660_date created;
+ struct iso9660_date modified;
+} __attribute__((packed));
+
+/* Boot Record */
+struct boot_record {
+ unsigned char vd_type;
+ unsigned char vd_id[5];
+ unsigned char vd_version;
+ unsigned char boot_system_id[32];
+ unsigned char boot_id[32];
+ unsigned char unused[1];
+} __attribute__((packed));
+
+#define ISO_SUPERBLOCK_OFFSET 0x8000
+#define ISO_SECTOR_SIZE 0x800
+#define ISO_VD_BOOT_RECORD 0x0
+#define ISO_VD_PRIMARY 0x1
+#define ISO_VD_SUPPLEMENTARY 0x2
+#define ISO_VD_END 0xff
+#define ISO_VD_MAX 16
+
+struct high_sierra_volume_descriptor {
+ unsigned char foo[8];
+ unsigned char type;
+ unsigned char id[5];
+ unsigned char version;
+ unsigned char unused1;
+ unsigned char system_id[32];
+ unsigned char volume_id[32];
+} __attribute__((packed));
+
+/* old High Sierra format */
+static int probe_iso9660_hsfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct high_sierra_volume_descriptor *iso;
+
+ iso = blkid_probe_get_sb(pr, mag, struct high_sierra_volume_descriptor);
+ if (!iso)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_block_size(pr, ISO_SECTOR_SIZE);
+ blkid_probe_set_version(pr, "High Sierra");
+ blkid_probe_set_label(pr, iso->volume_id, sizeof(iso->volume_id));
+ return 0;
+}
+
+static int probe_iso9660_set_uuid (blkid_probe pr, const struct iso9660_date *date)
+{
+ unsigned char buffer[16];
+ unsigned int i, zeros = 0;
+
+ buffer[0] = date->year[0];
+ buffer[1] = date->year[1];
+ buffer[2] = date->year[2];
+ buffer[3] = date->year[3];
+ buffer[4] = date->month[0];
+ buffer[5] = date->month[1];
+ buffer[6] = date->day[0];
+ buffer[7] = date->day[1];
+ buffer[8] = date->hour[0];
+ buffer[9] = date->hour[1];
+ buffer[10] = date->minute[0];
+ buffer[11] = date->minute[1];
+ buffer[12] = date->second[0];
+ buffer[13] = date->second[1];
+ buffer[14] = date->hundredth[0];
+ buffer[15] = date->hundredth[1];
+
+ /* count the number of zeros ('0') in the date buffer */
+ for (i = 0, zeros = 0; i < sizeof(buffer); i++)
+ if (buffer[i] == '0')
+ zeros++;
+
+ /* due to the iso9660 standard if all date fields are '0' and offset is 0, the date is unset */
+ if (zeros == sizeof(buffer) && date->offset == 0)
+ return 0;
+
+ /* generate an UUID using this date and return success */
+ blkid_probe_sprintf_uuid (pr, buffer, sizeof(buffer),
+ "%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
+ buffer[0], buffer[1], buffer[2], buffer[3],
+ buffer[4], buffer[5],
+ buffer[6], buffer[7],
+ buffer[8], buffer[9],
+ buffer[10], buffer[11],
+ buffer[12], buffer[13],
+ buffer[14], buffer[15]);
+
+ return 1;
+}
+
+static int is_str_empty(const unsigned char *str, size_t len)
+{
+ size_t i;
+
+ if (!str || !*str)
+ return 1;
+
+ for (i = 0; i < len; i++)
+ if (!isspace(str[i]))
+ return 0;
+ return 1;
+}
+
+static int is_utf16be_str_empty(unsigned char *utf16, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i += 2) {
+ if (utf16[i] != 0x0 || !isspace(utf16[i + 1]))
+ return 0;
+ }
+ return 1;
+}
+
+/* if @utf16 is prefix of @ascii (ignoring non-representable characters and upper-case conversion)
+ * then reconstruct prefix from @utf16 and @ascii, append suffix from @ascii, fill it into @out
+ * and returns length of bytes written into @out; otherwise returns zero */
+static size_t merge_utf16be_ascii(unsigned char *out, const unsigned char *utf16, const unsigned char *ascii, size_t len)
+{
+ size_t o, a, u;
+
+ for (o = 0, a = 0, u = 0; u + 1 < len && a < len; o += 2, a++, u += 2) {
+ /* Surrogate pair with code point above U+FFFF */
+ if (utf16[u] >= 0xD8 && utf16[u] <= 0xDB && u + 3 < len &&
+ utf16[u + 2] >= 0xDC && utf16[u + 2] <= 0xDF) {
+ out[o++] = utf16[u++];
+ out[o++] = utf16[u++];
+ }
+ /* Value '_' is replacement for non-representable character */
+ if (ascii[a] == '_') {
+ out[o] = utf16[u];
+ out[o + 1] = utf16[u + 1];
+ } else if (utf16[u] == 0x00 && utf16[u + 1] == '_') {
+ out[o] = 0x00;
+ out[o + 1] = ascii[a];
+ } else if (utf16[u] == 0x00 && c_toupper(ascii[a]) == c_toupper(utf16[u + 1])) {
+ out[o] = 0x00;
+ out[o + 1] = c_isupper(ascii[a]) ? utf16[u + 1] : ascii[a];
+ } else {
+ return 0;
+ }
+ }
+
+ for (; a < len; o += 2, a++) {
+ out[o] = 0x00;
+ out[o + 1] = ascii[a];
+ }
+
+ return o;
+}
+
+/* iso9660 [+ Microsoft Joliet Extension] */
+static int probe_iso9660(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct boot_record *boot = NULL;
+ struct iso_volume_descriptor *pvd = NULL;
+ struct iso_volume_descriptor *joliet = NULL;
+ unsigned char buf[256];
+ size_t len;
+ int is_unicode_empty;
+ int is_ascii_empty;
+ int i;
+ uint64_t off;
+
+ if (blkid_probe_get_hint(pr, mag->hoff, &off) < 0)
+ off = 0;
+
+ if (off % ISO_SECTOR_SIZE)
+ return 1;
+
+ if (strcmp(mag->magic, "CDROM") == 0)
+ return probe_iso9660_hsfs(pr, mag);
+
+ for (i = 0, off += ISO_SUPERBLOCK_OFFSET; i < ISO_VD_MAX && (!boot || !pvd || !joliet); i++, off += ISO_SECTOR_SIZE) {
+ unsigned char *desc =
+ blkid_probe_get_buffer(pr,
+ off,
+ max(sizeof(struct boot_record),
+ sizeof(struct iso_volume_descriptor)));
+
+ if (desc == NULL || desc[0] == ISO_VD_END)
+ break;
+ else if (!boot && desc[0] == ISO_VD_BOOT_RECORD)
+ boot = (struct boot_record *)desc;
+ else if (!pvd && desc[0] == ISO_VD_PRIMARY)
+ pvd = (struct iso_volume_descriptor *)desc;
+ else if (!joliet && desc[0] == ISO_VD_SUPPLEMENTARY) {
+ joliet = (struct iso_volume_descriptor *)desc;
+ if (memcmp(joliet->escape_sequences, "%/@", 3) != 0 &&
+ memcmp(joliet->escape_sequences, "%/C", 3) != 0 &&
+ memcmp(joliet->escape_sequences, "%/E", 3) != 0)
+ joliet = NULL;
+ }
+ }
+
+ if (!pvd)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_block_size(pr, ISO_SECTOR_SIZE);
+
+ if (joliet && (len = merge_utf16be_ascii(buf, joliet->system_id, pvd->system_id, sizeof(pvd->system_id))) != 0)
+ blkid_probe_set_utf8_id_label(pr, "SYSTEM_ID", buf, len, UL_ENCODE_UTF16BE);
+ else if (joliet)
+ blkid_probe_set_utf8_id_label(pr, "SYSTEM_ID", joliet->system_id, sizeof(joliet->system_id), UL_ENCODE_UTF16BE);
+ else
+ blkid_probe_set_id_label(pr, "SYSTEM_ID", pvd->system_id, sizeof(pvd->system_id));
+
+ if (joliet && (len = merge_utf16be_ascii(buf, joliet->volume_set_id, pvd->volume_set_id, sizeof(pvd->volume_set_id))) != 0)
+ blkid_probe_set_utf8_id_label(pr, "VOLUME_SET_ID", buf, len, UL_ENCODE_UTF16BE);
+ else if (joliet)
+ blkid_probe_set_utf8_id_label(pr, "VOLUME_SET_ID", joliet->volume_set_id, sizeof(joliet->volume_set_id), UL_ENCODE_UTF16BE);
+ else
+ blkid_probe_set_id_label(pr, "VOLUME_SET_ID", pvd->volume_set_id, sizeof(pvd->volume_set_id));
+
+ is_ascii_empty = (is_str_empty(pvd->publisher_id, sizeof(pvd->publisher_id)) || pvd->publisher_id[0] == '_');
+ is_unicode_empty = (!joliet || is_utf16be_str_empty(joliet->publisher_id, sizeof(joliet->publisher_id)) || (joliet->publisher_id[0] == 0x00 && joliet->publisher_id[1] == '_'));
+ if (!is_unicode_empty && !is_ascii_empty && (len = merge_utf16be_ascii(buf, joliet->publisher_id, pvd->publisher_id, sizeof(pvd->publisher_id))) != 0)
+ blkid_probe_set_utf8_id_label(pr, "PUBLISHER_ID", buf, len, UL_ENCODE_UTF16BE);
+ else if (!is_unicode_empty)
+ blkid_probe_set_utf8_id_label(pr, "PUBLISHER_ID", joliet->publisher_id, sizeof(joliet->publisher_id), UL_ENCODE_UTF16BE);
+ else if (!is_ascii_empty)
+ blkid_probe_set_id_label(pr, "PUBLISHER_ID", pvd->publisher_id, sizeof(pvd->publisher_id));
+
+ is_ascii_empty = (is_str_empty(pvd->data_preparer_id, sizeof(pvd->data_preparer_id)) || pvd->data_preparer_id[0] == '_');
+ is_unicode_empty = (!joliet || is_utf16be_str_empty(joliet->data_preparer_id, sizeof(joliet->data_preparer_id)) || (joliet->data_preparer_id[0] == 0x00 && joliet->data_preparer_id[1] == '_'));
+ if (!is_unicode_empty && !is_ascii_empty && (len = merge_utf16be_ascii(buf, joliet->data_preparer_id, pvd->data_preparer_id, sizeof(pvd->data_preparer_id))) != 0)
+ blkid_probe_set_utf8_id_label(pr, "DATA_PREPARER_ID", buf, len, UL_ENCODE_UTF16BE);
+ else if (!is_unicode_empty)
+ blkid_probe_set_utf8_id_label(pr, "DATA_PREPARER_ID", joliet->data_preparer_id, sizeof(joliet->data_preparer_id), UL_ENCODE_UTF16BE);
+ else if (!is_ascii_empty)
+ blkid_probe_set_id_label(pr, "DATA_PREPARER_ID", pvd->data_preparer_id, sizeof(pvd->data_preparer_id));
+
+ is_ascii_empty = (is_str_empty(pvd->application_id, sizeof(pvd->application_id)) || pvd->application_id[0] == '_');
+ is_unicode_empty = (!joliet || is_utf16be_str_empty(joliet->application_id, sizeof(joliet->application_id)) || (joliet->application_id[0] == 0x00 && joliet->application_id[1] == '_'));
+ if (!is_unicode_empty && !is_ascii_empty && (len = merge_utf16be_ascii(buf, joliet->application_id, pvd->application_id, sizeof(pvd->application_id))) != 0)
+ blkid_probe_set_utf8_id_label(pr, "APPLICATION_ID", buf, len, UL_ENCODE_UTF16BE);
+ else if (!is_unicode_empty)
+ blkid_probe_set_utf8_id_label(pr, "APPLICATION_ID", joliet->application_id, sizeof(joliet->application_id), UL_ENCODE_UTF16BE);
+ else if (!is_ascii_empty)
+ blkid_probe_set_id_label(pr, "APPLICATION_ID", pvd->application_id, sizeof(pvd->application_id));
+
+ /* create an UUID using the modified/created date */
+ if (! probe_iso9660_set_uuid(pr, &pvd->modified))
+ probe_iso9660_set_uuid(pr, &pvd->created);
+
+ if (boot)
+ blkid_probe_set_id_label(pr, "BOOT_SYSTEM_ID",
+ boot->boot_system_id,
+ sizeof(boot->boot_system_id));
+
+ if (joliet)
+ blkid_probe_set_version(pr, "Joliet Extension");
+
+ /* Label in Joliet is UNICODE (UTF16BE) but can contain only 16 characters. Label in PVD is
+ * subset of ASCII but can contain up to the 32 characters. Non-representable characters are
+ * stored as replacement character '_'. Label in Joliet is in most cases trimmed but UNICODE
+ * version of label in PVD. Based on these facts try to reconstruct original label if label
+ * in Joliet is prefix of the label in PVD (ignoring non-representable characters).
+ */
+ if (joliet && (len = merge_utf16be_ascii(buf, joliet->volume_id, pvd->volume_id, sizeof(pvd->volume_id))) != 0)
+ blkid_probe_set_utf8label(pr, buf, len, UL_ENCODE_UTF16BE);
+ else if (joliet)
+ blkid_probe_set_utf8label(pr, joliet->volume_id, sizeof(joliet->volume_id), UL_ENCODE_UTF16BE);
+ else
+ blkid_probe_set_label(pr, pvd->volume_id, sizeof(pvd->volume_id));
+
+ return 0;
+}
+
+const struct blkid_idinfo iso9660_idinfo =
+{
+ .name = "iso9660",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_iso9660,
+ .flags = BLKID_IDINFO_TOLERANT,
+ .magics =
+ {
+ { .magic = "CD001", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "CDROM", .len = 5, .kboff = 32, .sboff = 9, .hoff = "session_offset" },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/isw_raid.c b/libblkid/src/superblocks/isw_raid.c
new file mode 100644
index 0000000..81d53a1
--- /dev/null
+++ b/libblkid/src/superblocks/isw_raid.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct isw_metadata {
+ uint8_t sig[32];
+ uint32_t check_sum;
+ uint32_t mpb_size;
+ uint32_t family_num;
+ uint32_t generation_num;
+};
+
+#define ISW_SIGNATURE "Intel Raid ISM Cfg Sig. "
+
+static int probe_iswraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ unsigned int sector_size;
+ struct isw_metadata *isw;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ sector_size = blkid_probe_get_sectorsize(pr);
+ off = ((pr->size / sector_size) - 2) * sector_size;
+
+ isw = (struct isw_metadata *)blkid_probe_get_buffer(pr,
+ off, sizeof(struct isw_metadata));
+ if (!isw)
+ return errno ? -errno : 1;
+
+ if (memcmp(isw->sig, ISW_SIGNATURE, sizeof(ISW_SIGNATURE)-1) != 0)
+ return 1;
+
+ if (blkid_probe_sprintf_version(pr, "%6s",
+ &isw->sig[sizeof(ISW_SIGNATURE)-1]) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(isw->sig),
+ (unsigned char *) isw->sig))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo iswraid_idinfo = {
+ .name = "isw_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_iswraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/jfs.c b/libblkid/src/superblocks/jfs.c
new file mode 100644
index 0000000..3de8c2e
--- /dev/null
+++ b/libblkid/src/superblocks/jfs.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct jfs_super_block {
+ unsigned char js_magic[4];
+ uint32_t js_version;
+ uint64_t js_size;
+ uint32_t js_bsize; /* 4: aggregate block size in bytes */
+ uint16_t js_l2bsize; /* 2: log2 of s_bsize */
+ uint16_t js_l2bfactor; /* 2: log2(s_bsize/hardware block size) */
+ uint32_t js_pbsize; /* 4: hardware/LVM block size in bytes */
+ uint16_t js_l2pbsize; /* 2: log2 of s_pbsize */
+ uint16_t js_pad; /* 2: padding necessary for alignment */
+ uint32_t js_dummy2[26];
+ unsigned char js_uuid[16];
+ unsigned char js_label[16];
+ unsigned char js_loguuid[16];
+};
+
+static int probe_jfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct jfs_super_block *js;
+
+ js = blkid_probe_get_sb(pr, mag, struct jfs_super_block);
+ if (!js)
+ return errno ? -errno : 1;
+ if (le32_to_cpu(js->js_bsize) != (1U << le16_to_cpu(js->js_l2bsize)))
+ return 1;
+ if (le32_to_cpu(js->js_pbsize) != (1U << le16_to_cpu(js->js_l2pbsize)))
+ return 1;
+ if ((le16_to_cpu(js->js_l2bsize) - le16_to_cpu(js->js_l2pbsize)) !=
+ le16_to_cpu(js->js_l2bfactor))
+ return 1;
+
+ if (*((char *) js->js_label) != '\0')
+ blkid_probe_set_label(pr, js->js_label, sizeof(js->js_label));
+ blkid_probe_set_uuid(pr, js->js_uuid);
+ blkid_probe_set_block_size(pr, le32_to_cpu(js->js_bsize));
+ return 0;
+}
+
+
+const struct blkid_idinfo jfs_idinfo =
+{
+ .name = "jfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_jfs,
+ .minsz = 16 * 1024 * 1024,
+ .magics =
+ {
+ { .magic = "JFS1", .len = 4, .kboff = 32 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/jmicron_raid.c b/libblkid/src/superblocks/jmicron_raid.c
new file mode 100644
index 0000000..ca79867
--- /dev/null
+++ b/libblkid/src/superblocks/jmicron_raid.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct jm_metadata {
+ int8_t signature[2];
+ uint8_t minor_version;
+ uint8_t major_version;
+ uint16_t checksum;
+};
+
+#define JM_SIGNATURE "JM"
+
+static int probe_jmraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct jm_metadata *jm;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200) - 1) * 0x200;
+ jm = (struct jm_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct jm_metadata));
+ if (!jm)
+ return errno ? -errno : 1;
+
+ if (memcmp(jm->signature, JM_SIGNATURE, sizeof(JM_SIGNATURE) - 1) != 0)
+ return 1;
+ if (blkid_probe_sprintf_version(pr, "%u.%u",
+ jm->major_version, jm->minor_version) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(jm->signature),
+ (unsigned char *) jm->signature))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo jmraid_idinfo = {
+ .name = "jmicron_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_jmraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/linux_raid.c b/libblkid/src/superblocks/linux_raid.c
new file mode 100644
index 0000000..4388e56
--- /dev/null
+++ b/libblkid/src/superblocks/linux_raid.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct mdp0_super_block {
+ uint32_t md_magic;
+ uint32_t major_version;
+ uint32_t minor_version;
+ uint32_t patch_version;
+ uint32_t gvalid_words;
+ uint32_t set_uuid0;
+ uint32_t ctime;
+ uint32_t level;
+ uint32_t size;
+ uint32_t nr_disks;
+ uint32_t raid_disks;
+ uint32_t md_minor;
+ uint32_t not_persistent;
+ uint32_t set_uuid1;
+ uint32_t set_uuid2;
+ uint32_t set_uuid3;
+};
+
+/*
+ * Version-1, little-endian.
+ */
+struct mdp1_super_block {
+ /* constant array information - 128 bytes */
+ uint32_t magic; /* MD_SB_MAGIC: 0xa92b4efc - little endian */
+ uint32_t major_version; /* 1 */
+ uint32_t feature_map; /* 0 for now */
+ uint32_t pad0; /* always set to 0 when writing */
+
+ uint8_t set_uuid[16]; /* user-space generated. */
+ unsigned char set_name[32]; /* set and interpreted by user-space */
+
+ uint64_t ctime; /* lo 40 bits are seconds, top 24 are microseconds or 0*/
+ uint32_t level; /* -4 (multipath), -1 (linear), 0,1,4,5 */
+ uint32_t layout; /* only for raid5 currently */
+ uint64_t size; /* used size of component devices, in 512byte sectors */
+
+ uint32_t chunksize; /* in 512byte sectors */
+ uint32_t raid_disks;
+ uint32_t bitmap_offset; /* sectors after start of superblock that bitmap starts
+ * NOTE: signed, so bitmap can be before superblock
+ * only meaningful of feature_map[0] is set.
+ */
+
+ /* These are only valid with feature bit '4' */
+ uint32_t new_level; /* new level we are reshaping to */
+ uint64_t reshape_position; /* next address in array-space for reshape */
+ uint32_t delta_disks; /* change in number of raid_disks */
+ uint32_t new_layout; /* new layout */
+ uint32_t new_chunk; /* new chunk size (bytes) */
+ uint8_t pad1[128-124]; /* set to 0 when written */
+
+ /* constant this-device information - 64 bytes */
+ uint64_t data_offset; /* sector start of data, often 0 */
+ uint64_t data_size; /* sectors in this device that can be used for data */
+ uint64_t super_offset; /* sector start of this superblock */
+ uint64_t recovery_offset;/* sectors before this offset (from data_offset) have been recovered */
+ uint32_t dev_number; /* permanent identifier of this device - not role in raid */
+ uint32_t cnt_corrected_read; /* number of read errors that were corrected by re-writing */
+ uint8_t device_uuid[16]; /* user-space setable, ignored by kernel */
+ uint8_t devflags; /* per-device flags. Only one defined...*/
+ uint8_t pad2[64-57]; /* set to 0 when writing */
+
+ /* array state information - 64 bytes */
+ uint64_t utime; /* 40 bits second, 24 bits microseconds */
+ uint64_t events; /* incremented when superblock updated */
+ uint64_t resync_offset; /* data before this offset (from data_offset) known to be in sync */
+ uint32_t sb_csum; /* checksum up to dev_roles[max_dev] */
+ uint32_t max_dev; /* size of dev_roles[] array to consider */
+ uint8_t pad3[64-32]; /* set to 0 when writing */
+
+ /* device state information. Indexed by dev_number.
+ * 2 bytes per device
+ * Note there are no per-device state flags. State information is rolled
+ * into the 'roles' value. If a device is spare or faulty, then it doesn't
+ * have a meaningful role.
+ */
+ uint16_t dev_roles[0]; /* role in array, or 0xffff for a spare, or 0xfffe for faulty */
+};
+
+
+#define MD_RESERVED_BYTES 0x10000
+#define MD_SB_MAGIC 0xa92b4efc
+
+static int probe_raid0(blkid_probe pr, uint64_t off)
+{
+ struct mdp0_super_block *mdp0;
+ union {
+ uint32_t ints[4];
+ uint8_t bytes[16];
+ } uuid;
+ uint32_t ma, mi, pa;
+ uint64_t size;
+
+ if (pr->size < MD_RESERVED_BYTES)
+ return 1;
+ mdp0 = (struct mdp0_super_block *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct mdp0_super_block));
+ if (!mdp0)
+ return errno ? -errno : 1;
+
+ memset(uuid.ints, 0, sizeof(uuid.ints));
+
+ if (le32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
+ uuid.ints[0] = swab32(mdp0->set_uuid0);
+ if (le32_to_cpu(mdp0->minor_version) >= 90) {
+ uuid.ints[1] = swab32(mdp0->set_uuid1);
+ uuid.ints[2] = swab32(mdp0->set_uuid2);
+ uuid.ints[3] = swab32(mdp0->set_uuid3);
+ }
+ ma = le32_to_cpu(mdp0->major_version);
+ mi = le32_to_cpu(mdp0->minor_version);
+ pa = le32_to_cpu(mdp0->patch_version);
+ size = le32_to_cpu(mdp0->size);
+
+ } else if (be32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
+ uuid.ints[0] = mdp0->set_uuid0;
+ if (be32_to_cpu(mdp0->minor_version) >= 90) {
+ uuid.ints[1] = mdp0->set_uuid1;
+ uuid.ints[2] = mdp0->set_uuid2;
+ uuid.ints[3] = mdp0->set_uuid3;
+ }
+ ma = be32_to_cpu(mdp0->major_version);
+ mi = be32_to_cpu(mdp0->minor_version);
+ pa = be32_to_cpu(mdp0->patch_version);
+ size = be32_to_cpu(mdp0->size);
+ } else
+ return 1;
+
+ size <<= 10; /* convert KiB to bytes */
+
+ if (pr->size < size + MD_RESERVED_BYTES)
+ /* device is too small */
+ return 1;
+
+ if (off < size)
+ /* no space before superblock */
+ return 1;
+
+ /*
+ * Check for collisions between RAID and partition table
+ *
+ * For example the superblock is at the end of the last partition, it's
+ * the same position as at the end of the disk...
+ */
+ if ((S_ISREG(pr->mode) || blkid_probe_is_wholedisk(pr)) &&
+ blkid_probe_is_covered_by_pt(pr,
+ off - size, /* min. start */
+ size + MD_RESERVED_BYTES)) { /* min. length */
+
+ /* ignore this superblock, it's within any partition and
+ * we are working with whole-disk now */
+ return 1;
+ }
+
+ if (blkid_probe_sprintf_version(pr, "%u.%u.%u", ma, mi, pa) != 0)
+ return 1;
+ if (blkid_probe_set_uuid(pr, (unsigned char *) uuid.bytes) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(mdp0->md_magic),
+ (unsigned char *) &mdp0->md_magic))
+ return 1;
+ return 0;
+}
+
+static int probe_raid1(blkid_probe pr, off_t off)
+{
+ struct mdp1_super_block *mdp1;
+
+ mdp1 = (struct mdp1_super_block *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct mdp1_super_block));
+ if (!mdp1)
+ return errno ? -errno : 1;
+ if (le32_to_cpu(mdp1->magic) != MD_SB_MAGIC)
+ return 1;
+ if (le32_to_cpu(mdp1->major_version) != 1U)
+ return 1;
+ if (le64_to_cpu(mdp1->super_offset) != (uint64_t) off >> 9)
+ return 1;
+ if (blkid_probe_set_uuid(pr, (unsigned char *) mdp1->set_uuid) != 0)
+ return 1;
+ if (blkid_probe_set_uuid_as(pr,
+ (unsigned char *) mdp1->device_uuid, "UUID_SUB") != 0)
+ return 1;
+ if (blkid_probe_set_label(pr, mdp1->set_name,
+ sizeof(mdp1->set_name)) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(mdp1->magic),
+ (unsigned char *) &mdp1->magic))
+ return 1;
+ return 0;
+}
+
+static int probe_raid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ const char *ver = NULL;
+ int ret = BLKID_PROBE_NONE;
+
+ if (pr->size > MD_RESERVED_BYTES) {
+ /* version 0 at the end of the device */
+ uint64_t sboff = (pr->size & ~(MD_RESERVED_BYTES - 1))
+ - MD_RESERVED_BYTES;
+ ret = probe_raid0(pr, sboff);
+ if (ret < 1)
+ return ret; /* error */
+
+ /* version 1.0 at the end of the device */
+ sboff = (pr->size & ~(0x1000 - 1)) - 0x2000;
+ ret = probe_raid1(pr, sboff);
+ if (ret < 0)
+ return ret; /* error */
+ if (ret == 0)
+ ver = "1.0";
+ }
+
+ if (!ver) {
+ /* version 1.1 at the start of the device */
+ ret = probe_raid1(pr, 0);
+ if (ret == 0)
+ ver = "1.1";
+
+ /* version 1.2 at 4k offset from the start */
+ else if (ret == BLKID_PROBE_NONE) {
+ ret = probe_raid1(pr, 0x1000);
+ if (ret == 0)
+ ver = "1.2";
+ }
+ }
+
+ if (ver) {
+ blkid_probe_set_version(pr, ver);
+ return BLKID_PROBE_OK;
+ }
+ return ret;
+}
+
+
+const struct blkid_idinfo linuxraid_idinfo = {
+ .name = "linux_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_raid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/lsi_raid.c b/libblkid/src/superblocks/lsi_raid.c
new file mode 100644
index 0000000..697b0fe
--- /dev/null
+++ b/libblkid/src/superblocks/lsi_raid.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct lsi_metadata {
+ uint8_t sig[6];
+};
+
+
+#define LSI_SIGNATURE "$XIDE$"
+
+static int probe_lsiraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct lsi_metadata *lsi;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200) - 1) * 0x200;
+ lsi = (struct lsi_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct lsi_metadata));
+ if (!lsi)
+ return errno ? -errno : 1;
+
+ if (memcmp(lsi->sig, LSI_SIGNATURE, sizeof(LSI_SIGNATURE)-1) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(lsi->sig),
+ (unsigned char *) lsi->sig))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo lsiraid_idinfo = {
+ .name = "lsi_mega_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_lsiraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/luks.c b/libblkid/src/superblocks/luks.c
new file mode 100644
index 0000000..0230b34
--- /dev/null
+++ b/libblkid/src/superblocks/luks.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2018 Milan Broz <gmazyland@gmail.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define LUKS_CIPHERNAME_L 32
+#define LUKS_CIPHERMODE_L 32
+#define LUKS_HASHSPEC_L 32
+#define LUKS_DIGESTSIZE 20
+#define LUKS_SALTSIZE 32
+#define LUKS_MAGIC_L 6
+#define UUID_STRING_L 40
+#define LUKS2_LABEL_L 48
+#define LUKS2_SALT_L 64
+#define LUKS2_CHECKSUM_ALG_L 32
+#define LUKS2_CHECKSUM_L 64
+
+#define LUKS_MAGIC "LUKS\xba\xbe"
+#define LUKS_MAGIC_2 "SKUL\xba\xbe"
+
+/* Offsets for secondary header (for scan if primary header is corrupted). */
+#define LUKS2_HDR2_OFFSETS { 0x04000, 0x008000, 0x010000, 0x020000, \
+ 0x40000, 0x080000, 0x100000, 0x200000, 0x400000 }
+
+static const uint64_t secondary_offsets[] = LUKS2_HDR2_OFFSETS;
+
+struct luks_phdr {
+ uint8_t magic[LUKS_MAGIC_L];
+ uint16_t version;
+ uint8_t cipherName[LUKS_CIPHERNAME_L];
+ uint8_t cipherMode[LUKS_CIPHERMODE_L];
+ uint8_t hashSpec[LUKS_HASHSPEC_L];
+ uint32_t payloadOffset;
+ uint32_t keyBytes;
+ uint8_t mkDigest[LUKS_DIGESTSIZE];
+ uint8_t mkDigestSalt[LUKS_SALTSIZE];
+ uint32_t mkDigestIterations;
+ uint8_t uuid[UUID_STRING_L];
+} __attribute__((packed));
+
+struct luks2_phdr {
+ char magic[LUKS_MAGIC_L];
+ uint16_t version;
+ uint64_t hdr_size; /* in bytes, including JSON area */
+ uint64_t seqid; /* increased on every update */
+ char label[LUKS2_LABEL_L];
+ char checksum_alg[LUKS2_CHECKSUM_ALG_L];
+ uint8_t salt[LUKS2_SALT_L]; /* unique for every header/offset */
+ char uuid[UUID_STRING_L];
+ char subsystem[LUKS2_LABEL_L]; /* owner subsystem label */
+ uint64_t hdr_offset; /* offset from device start in bytes */
+ char _padding[184];
+ uint8_t csum[LUKS2_CHECKSUM_L];
+ /* Padding to 4k, then JSON area */
+} __attribute__ ((packed));
+
+static int luks_attributes(blkid_probe pr, struct luks2_phdr *header, uint64_t offset)
+{
+ int version;
+ struct luks_phdr *header_v1;
+
+ if (blkid_probe_set_magic(pr, offset, LUKS_MAGIC_L, (unsigned char *) &header->magic))
+ return BLKID_PROBE_NONE;
+
+ version = be16_to_cpu(header->version);
+ blkid_probe_sprintf_version(pr, "%u", version);
+
+ if (version == 1) {
+ header_v1 = (struct luks_phdr *)header;
+ blkid_probe_strncpy_uuid(pr,
+ (unsigned char *) header_v1->uuid, UUID_STRING_L);
+ } else if (version == 2) {
+ blkid_probe_strncpy_uuid(pr,
+ (unsigned char *) header->uuid, UUID_STRING_L);
+ blkid_probe_set_label(pr,
+ (unsigned char *) header->label, LUKS2_LABEL_L);
+ blkid_probe_set_id_label(pr, "SUBSYSTEM",
+ (unsigned char *) header->subsystem, LUKS2_LABEL_L);
+ }
+
+ return BLKID_PROBE_OK;
+}
+
+static int probe_luks(blkid_probe pr, const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct luks2_phdr *header;
+ size_t i;
+
+ header = (struct luks2_phdr *) blkid_probe_get_buffer(pr, 0, sizeof(struct luks2_phdr));
+ if (!header)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (!memcmp(header->magic, LUKS_MAGIC, LUKS_MAGIC_L)) {
+ /* LUKS primary header was found. */
+ return luks_attributes(pr, header, 0);
+ }
+
+ /* No primary header, scan for known offsets of LUKS2 secondary header. */
+ for (i = 0; i < ARRAY_SIZE(secondary_offsets); i++) {
+ header = (struct luks2_phdr *) blkid_probe_get_buffer(pr,
+ secondary_offsets[i], sizeof(struct luks2_phdr));
+
+ if (!header)
+ return errno ? -errno : BLKID_PROBE_NONE;
+
+ if (!memcmp(header->magic, LUKS_MAGIC_2, LUKS_MAGIC_L))
+ return luks_attributes(pr, header, secondary_offsets[i]);
+ }
+
+ return BLKID_PROBE_NONE;
+}
+
+const struct blkid_idinfo luks_idinfo =
+{
+ .name = "crypto_LUKS",
+ .usage = BLKID_USAGE_CRYPTO,
+ .probefunc = probe_luks,
+ .magics = BLKID_NONE_MAGIC
+};
diff --git a/libblkid/src/superblocks/lvm.c b/libblkid/src/superblocks/lvm.c
new file mode 100644
index 0000000..b078aba
--- /dev/null
+++ b/libblkid/src/superblocks/lvm.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Milan Broz <mbroz@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define LVM1_ID_LEN 128
+#define LVM2_ID_LEN 32
+
+struct lvm2_pv_label_header {
+ /* label_header */
+ uint8_t id[8]; /* LABELONE */
+ uint64_t sector_xl; /* Sector number of this label */
+ uint32_t crc_xl; /* From next field to end of sector */
+ uint32_t offset_xl; /* Offset from start of struct to contents */
+ uint8_t type[8]; /* LVM2 001 */
+ /* pv_header */
+ uint8_t pv_uuid[LVM2_ID_LEN];
+} __attribute__ ((packed));
+
+struct lvm1_pv_label_header {
+ uint8_t id[2]; /* HM */
+ uint16_t version; /* version 1 or 2 */
+ uint32_t _notused[10]; /* lvm1 internals */
+ uint8_t pv_uuid[LVM1_ID_LEN];
+} __attribute__ ((packed));
+
+#define LVM2_LABEL_SIZE 512
+static unsigned int lvm2_calc_crc(const void *buf, unsigned int size)
+{
+ static const unsigned int crctab[] = {
+ 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+ 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+ 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
+ };
+ unsigned int i, crc = 0xf597a6cf;
+ const uint8_t *data = (const uint8_t *) buf;
+
+ for (i = 0; i < size; i++) {
+ crc ^= *data++;
+ crc = (crc >> 4) ^ crctab[crc & 0xf];
+ crc = (crc >> 4) ^ crctab[crc & 0xf];
+ }
+ return crc;
+}
+
+/* Length of real UUID is always LVM2_ID_LEN */
+static void format_lvm_uuid(char *dst_uuid, char *src_uuid)
+{
+ unsigned int i, b;
+
+ for (i = 0, b = 1; i < LVM2_ID_LEN; i++, b <<= 1) {
+ if (b & 0x4444440)
+ *dst_uuid++ = '-';
+ *dst_uuid++ = *src_uuid++;
+ }
+ *dst_uuid = '\0';
+}
+
+static int probe_lvm2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ int sector = mag->kboff << 1;
+ struct lvm2_pv_label_header *label;
+ char uuid[LVM2_ID_LEN + 7];
+ unsigned char *buf;
+
+ buf = blkid_probe_get_buffer(pr,
+ mag->kboff << 10,
+ 512 + sizeof(struct lvm2_pv_label_header));
+ if (!buf)
+ return errno ? -errno : 1;
+
+ /* buf is at 0k or 1k offset; find label inside */
+ if (memcmp(buf, "LABELONE", 8) == 0) {
+ label = (struct lvm2_pv_label_header *) buf;
+ } else if (memcmp(buf + 512, "LABELONE", 8) == 0) {
+ label = (struct lvm2_pv_label_header *)(buf + 512);
+ sector++;
+ } else {
+ return 1;
+ }
+
+ if (le64_to_cpu(label->sector_xl) != (unsigned) sector)
+ return 1;
+
+ if (!blkid_probe_verify_csum(
+ pr, lvm2_calc_crc(
+ &label->offset_xl, LVM2_LABEL_SIZE -
+ ((char *) &label->offset_xl - (char *) label)),
+ le32_to_cpu(label->crc_xl)))
+ return 1;
+
+ format_lvm_uuid(uuid, (char *) label->pv_uuid);
+ blkid_probe_sprintf_uuid(pr, label->pv_uuid, sizeof(label->pv_uuid),
+ "%s", uuid);
+
+ /* the mag->magic is the same string as label->type,
+ * but zero terminated */
+ blkid_probe_set_version(pr, mag->magic);
+
+ /* LVM (pvcreate) wipes begin of the device -- let's remember this
+ * to resolve conflicts between LVM and partition tables, ...
+ */
+ blkid_probe_set_wiper(pr, 0, 8 * 1024);
+
+ return 0;
+}
+
+static int probe_lvm1(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct lvm1_pv_label_header *label;
+ char uuid[LVM2_ID_LEN + 7];
+ unsigned int version;
+
+ label = blkid_probe_get_sb(pr, mag, struct lvm1_pv_label_header);
+ if (!label)
+ return errno ? -errno : 1;
+
+ version = le16_to_cpu(label->version);
+ if (version != 1 && version != 2)
+ return 1;
+
+ format_lvm_uuid(uuid, (char *) label->pv_uuid);
+ blkid_probe_sprintf_uuid(pr, label->pv_uuid, sizeof(label->pv_uuid),
+ "%s", uuid);
+
+ return 0;
+}
+
+struct verity_sb {
+ uint8_t signature[8]; /* "verity\0\0" */
+ uint32_t version; /* superblock version */
+ uint32_t hash_type; /* 0 - Chrome OS, 1 - normal */
+ uint8_t uuid[16]; /* UUID of hash device */
+ uint8_t algorithm[32];/* hash algorithm name */
+ uint32_t data_block_size; /* data block in bytes */
+ uint32_t hash_block_size; /* hash block in bytes */
+ uint64_t data_blocks; /* number of data blocks */
+ uint16_t salt_size; /* salt size */
+ uint8_t _pad1[6];
+ uint8_t salt[256]; /* salt */
+ uint8_t _pad2[168];
+} __attribute__((packed));
+
+static int probe_verity(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct verity_sb *sb;
+ unsigned int version;
+
+ sb = blkid_probe_get_sb(pr, mag, struct verity_sb);
+ if (sb == NULL)
+ return errno ? -errno : 1;
+
+ version = le32_to_cpu(sb->version);
+ if (version != 1)
+ return 1;
+
+ blkid_probe_set_uuid(pr, sb->uuid);
+ blkid_probe_sprintf_version(pr, "%u", version);
+ return 0;
+}
+
+struct integrity_sb {
+ uint8_t magic[8];
+ uint8_t version;
+ int8_t log2_interleave_sectors;
+ uint16_t integrity_tag_size;
+ uint32_t journal_sections;
+ uint64_t provided_data_sectors;
+ uint32_t flags;
+ uint8_t log2_sectors_per_block;
+} __attribute__ ((packed));
+
+static int probe_integrity(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct integrity_sb *sb;
+
+ sb = blkid_probe_get_sb(pr, mag, struct integrity_sb);
+ if (sb == NULL)
+ return errno ? -errno : 1;
+
+ if (!sb->version)
+ return 1;
+
+ blkid_probe_sprintf_version(pr, "%u", sb->version);
+ return 0;
+}
+
+/* NOTE: the original libblkid uses "lvm2pv" as a name */
+const struct blkid_idinfo lvm2_idinfo =
+{
+ .name = "LVM2_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_lvm2,
+ .magics =
+ {
+ { .magic = "LVM2 001", .len = 8, .sboff = 0x218 },
+ { .magic = "LVM2 001", .len = 8, .sboff = 0x018 },
+ { .magic = "LVM2 001", .len = 8, .kboff = 1, .sboff = 0x018 },
+ { .magic = "LVM2 001", .len = 8, .kboff = 1, .sboff = 0x218 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo lvm1_idinfo =
+{
+ .name = "LVM1_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_lvm1,
+ .magics =
+ {
+ { .magic = "HM", .len = 2 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo snapcow_idinfo =
+{
+ .name = "DM_snapshot_cow",
+ .usage = BLKID_USAGE_OTHER,
+ .magics =
+ {
+ { .magic = "SnAp", .len = 4 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo verity_hash_idinfo =
+{
+ .name = "DM_verity_hash",
+ .usage = BLKID_USAGE_CRYPTO,
+ .probefunc = probe_verity,
+ .magics =
+ {
+ { .magic = "verity\0\0", .len = 8 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo integrity_idinfo =
+{
+ .name = "DM_integrity",
+ .usage = BLKID_USAGE_CRYPTO,
+ .probefunc = probe_integrity,
+ .magics =
+ {
+ { .magic = "integrt\0", .len = 8 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c
new file mode 100644
index 0000000..b521efb
--- /dev/null
+++ b/libblkid/src/superblocks/minix.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+#include "superblocks.h"
+#include "minix.h"
+
+#define minix_swab16(doit, num) ((uint16_t) (doit ? swab16(num) : num))
+#define minix_swab32(doit, num) ((uint32_t) (doit ? swab32(num) : num))
+
+static int get_minix_version(const unsigned char *data, int *other_endian)
+{
+ const struct minix_super_block *sb = (const struct minix_super_block *) data;
+ const struct minix3_super_block *sb3 = (const struct minix3_super_block *) data;
+ int version = 0;
+ char *endian;
+
+ *other_endian = 0;
+
+ switch (sb->s_magic) {
+ case MINIX_SUPER_MAGIC:
+ case MINIX_SUPER_MAGIC2:
+ version = 1;
+ break;
+ case MINIX2_SUPER_MAGIC:
+ case MINIX2_SUPER_MAGIC2:
+ version = 2;
+ break;
+ default:
+ if (sb3->s_magic == MINIX3_SUPER_MAGIC)
+ version = 3;
+ break;
+ }
+
+ if (!version) {
+ *other_endian = 1;
+
+ switch (swab16(sb->s_magic)) {
+ case MINIX_SUPER_MAGIC:
+ case MINIX_SUPER_MAGIC2:
+ version = 1;
+ break;
+ case MINIX2_SUPER_MAGIC:
+ case MINIX2_SUPER_MAGIC2:
+ version = 2;
+ break;
+ default:
+ if (sb3->s_magic == MINIX3_SUPER_MAGIC)
+ version = 3;
+ break;
+ }
+ }
+ if (!version)
+ return -1;
+
+#if defined(WORDS_BIGENDIAN)
+ endian = *other_endian ? "LE" : "BE";
+#else
+ endian = *other_endian ? "BE" : "LE";
+#endif
+ DBG(LOWPROBE, ul_debug("minix version %d detected [%s]", version,
+ endian));
+ return version;
+}
+
+static int probe_minix(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ unsigned char *ext;
+ const unsigned char *data;
+ int version = 0, swabme = 0;
+ unsigned long zones, ninodes, imaps, zmaps;
+ off_t firstz;
+ size_t zone_size;
+ unsigned block_size;
+
+ data = blkid_probe_get_buffer(pr, 1024,
+ max(sizeof(struct minix_super_block),
+ sizeof(struct minix3_super_block)));
+ if (!data)
+ return errno ? -errno : 1;
+ version = get_minix_version(data, &swabme);
+ switch (version) {
+ case 1:
+ case 2: {
+ const struct minix_super_block *sb = (const struct minix_super_block *) data;
+
+ uint16_t state = minix_swab16(swabme, sb->s_state);
+ if ((state & (MINIX_VALID_FS | MINIX_ERROR_FS)) != state)
+ return 1;
+
+ zones = version == 2 ? minix_swab32(swabme, sb->s_zones) :
+ minix_swab16(swabme, sb->s_nzones);
+ ninodes = minix_swab16(swabme, sb->s_ninodes);
+ imaps = minix_swab16(swabme, sb->s_imap_blocks);
+ zmaps = minix_swab16(swabme, sb->s_zmap_blocks);
+ firstz = minix_swab16(swabme, sb->s_firstdatazone);
+ zone_size = sb->s_log_zone_size;
+ block_size = 1024;
+ break;
+ }
+ case 3: {
+ const struct minix3_super_block *sb = (const struct minix3_super_block *) data;
+
+ zones = minix_swab32(swabme, sb->s_zones);
+ ninodes = minix_swab32(swabme, sb->s_ninodes);
+ imaps = minix_swab16(swabme, sb->s_imap_blocks);
+ zmaps = minix_swab16(swabme, sb->s_zmap_blocks);
+ firstz = minix_swab16(swabme, sb->s_firstdatazone);
+ zone_size = sb->s_log_zone_size;
+ block_size = minix_swab16(swabme, sb->s_blocksize);
+
+ break;
+ }
+ default:
+ return 1;
+ }
+
+ /* sanity checks to be sure that the FS is really minix.
+ * see disk-utils/fsck.minix.c read_superblock
+ */
+ if (zone_size != 0 || ninodes == 0 || ninodes == UINT32_MAX)
+ return 1;
+ if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1)
+ return 1;
+ if (firstz > (off_t) zones)
+ return 1;
+ if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1)
+ return 1;
+
+ /* unfortunately, some parts of ext3 is sometimes possible to
+ * interpreted as minix superblock. So check for extN magic
+ * string. (For extN magic string and offsets see ext.c.)
+ */
+ ext = blkid_probe_get_buffer(pr, 0x400 + 0x38, 2);
+ if (!ext)
+ return errno ? -errno : 1;
+
+ if (memcmp(ext, "\123\357", 2) == 0)
+ return 1;
+
+ blkid_probe_sprintf_version(pr, "%d", version);
+ blkid_probe_set_block_size(pr, block_size);
+ return 0;
+}
+
+const struct blkid_idinfo minix_idinfo =
+{
+ .name = "minix",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_minix,
+ .magics =
+ {
+ /* version 1 - LE */
+ { .magic = "\177\023", .len = 2, .kboff = 1, .sboff = 0x10 },
+ { .magic = "\217\023", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+ /* version 1 - BE */
+ { .magic = "\023\177", .len = 2, .kboff = 1, .sboff = 0x10 },
+ { .magic = "\023\217", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+ /* version 2 - LE */
+ { .magic = "\150\044", .len = 2, .kboff = 1, .sboff = 0x10 },
+ { .magic = "\170\044", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+ /* version 2 - BE */
+ { .magic = "\044\150", .len = 2, .kboff = 1, .sboff = 0x10 },
+ { .magic = "\044\170", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+ /* version 3 - LE */
+ { .magic = "\132\115", .len = 2, .kboff = 1, .sboff = 0x18 },
+
+ /* version 3 - BE */
+ { .magic = "\115\132", .len = 2, .kboff = 1, .sboff = 0x18 },
+
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/mpool.c b/libblkid/src/superblocks/mpool.c
new file mode 100644
index 0000000..b27569e
--- /dev/null
+++ b/libblkid/src/superblocks/mpool.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 Micron Technology, Inc.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include "crc32c.h"
+#include "superblocks.h"
+
+#define MAX_MPOOL_NAME_LEN 32
+
+struct omf_sb_descriptor {
+ uint64_t osb_magic;
+ uint8_t osb_name[MAX_MPOOL_NAME_LEN];
+ unsigned char osb_poolid[16]; /* UUID of pool this drive belongs to */
+ uint16_t osb_vers;
+ uint32_t osb_gen;
+ uint32_t osb_cksum1; /* crc32c of the preceding fields */
+} __attribute__((packed));
+
+static int probe_mpool(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct omf_sb_descriptor *osd;
+ uint32_t sb_crc;
+
+ osd = blkid_probe_get_sb(pr, mag, struct omf_sb_descriptor);
+ if (!osd)
+ return errno ? -errno : 1;
+
+ sb_crc = crc32c(~0L, (const void *)osd,
+ offsetof(struct omf_sb_descriptor, osb_cksum1));
+ sb_crc ^= ~0L;
+
+ if (!blkid_probe_verify_csum(pr, sb_crc, le32_to_cpu(osd->osb_cksum1)))
+ return 1;
+
+ blkid_probe_set_label(pr, osd->osb_name, sizeof(osd->osb_name));
+ blkid_probe_set_uuid(pr, osd->osb_poolid);
+
+ return 0;
+}
+
+/* "mpoolDev" in ASCII */
+#define MPOOL_SB_MAGIC "\x6D\x70\x6f\x6f\x6c\x44\x65\x76"
+
+const struct blkid_idinfo mpool_idinfo =
+{
+ .name = "mpool",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_mpool,
+ .magics =
+ {
+ { .magic = MPOOL_SB_MAGIC, .len = 8},
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/netware.c b/libblkid/src/superblocks/netware.c
new file mode 100644
index 0000000..af81cf5
--- /dev/null
+++ b/libblkid/src/superblocks/netware.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct netware_super_block {
+ uint8_t SBH_Signature[4];
+ uint16_t SBH_VersionMajor;
+ uint16_t SBH_VersionMinor;
+ uint16_t SBH_VersionMediaMajor;
+ uint16_t SBH_VersionMediaMinor;
+ uint32_t SBH_ItemsMoved;
+ uint8_t SBH_InternalID[16];
+ uint32_t SBH_PackedSize;
+ uint32_t SBH_Checksum;
+ uint32_t supersyncid;
+ int64_t superlocation[4];
+ uint32_t physSizeUsed;
+ uint32_t sizeUsed;
+ uint32_t superTimeStamp;
+ uint32_t reserved0[1];
+ int64_t SBH_LoggedPoolDataBlk;
+ int64_t SBH_PoolDataBlk;
+ uint8_t SBH_OldInternalID[16];
+ uint32_t SBH_PoolToLVStartUTC;
+ uint32_t SBH_PoolToLVEndUTC;
+ uint16_t SBH_VersionMediaMajorCreate;
+ uint16_t SBH_VersionMediaMinorCreate;
+ uint32_t SBH_BlocksMoved;
+ uint32_t SBH_TempBTSpBlk;
+ uint32_t SBH_TempFTSpBlk;
+ uint32_t SBH_TempFTSpBlk1;
+ uint32_t SBH_TempFTSpBlk2;
+ uint32_t nssMagicNumber;
+ uint32_t poolClassID;
+ uint32_t poolID;
+ uint32_t createTime;
+ int64_t SBH_LoggedVolumeDataBlk;
+ int64_t SBH_VolumeDataBlk;
+ int64_t SBH_SystemBeastBlkNum;
+ uint64_t totalblocks;
+ uint16_t SBH_Name[64];
+ uint8_t SBH_VolumeID[16];
+ uint8_t SBH_PoolID[16];
+ uint8_t SBH_PoolInternalID[16];
+ uint64_t SBH_Lsn;
+ uint32_t SBH_SS_Enabled;
+ uint32_t SBH_SS_CreateTime;
+ uint8_t SBH_SS_OriginalPoolID[16];
+ uint8_t SBH_SS_OriginalVolumeID[16];
+ uint8_t SBH_SS_Guid[16];
+ uint16_t SBH_SS_OriginalName[64];
+ uint32_t reserved2[64-(2+46)];
+} __attribute__((__packed__));
+
+static int probe_netware(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct netware_super_block *nw;
+
+ nw = blkid_probe_get_sb(pr, mag, struct netware_super_block);
+ if (!nw)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_uuid(pr, nw->SBH_PoolID);
+
+ blkid_probe_sprintf_version(pr, "%u.%02u",
+ le16_to_cpu(nw->SBH_VersionMediaMajor),
+ le16_to_cpu(nw->SBH_VersionMediaMinor));
+
+ return 0;
+}
+
+const struct blkid_idinfo netware_idinfo =
+{
+ .name = "nss",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_netware,
+ .magics =
+ {
+ { .magic = "SPB5", .len = 4, .kboff = 4 },
+ { NULL }
+ }
+};
+
+
diff --git a/libblkid/src/superblocks/nilfs.c b/libblkid/src/superblocks/nilfs.c
new file mode 100644
index 0000000..423bd1a
--- /dev/null
+++ b/libblkid/src/superblocks/nilfs.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2010 by Jiro SEKIBA <jir@unicus.jp>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+#include "crc32.h"
+
+struct nilfs_super_block {
+ uint32_t s_rev_level;
+ uint16_t s_minor_rev_level;
+ uint16_t s_magic;
+
+ uint16_t s_bytes;
+
+ uint16_t s_flags;
+ uint32_t s_crc_seed;
+ uint32_t s_sum;
+
+ uint32_t s_log_block_size;
+
+ uint64_t s_nsegments;
+ uint64_t s_dev_size;
+ uint64_t s_first_data_block;
+ uint32_t s_blocks_per_segment;
+ uint32_t s_r_segments_percentage;
+
+ uint64_t s_last_cno;
+ uint64_t s_last_pseg;
+ uint64_t s_last_seq;
+ uint64_t s_free_blocks_count;
+
+ uint64_t s_ctime;
+
+ uint64_t s_mtime;
+ uint64_t s_wtime;
+ uint16_t s_mnt_count;
+ uint16_t s_max_mnt_count;
+ uint16_t s_state;
+ uint16_t s_errors;
+ uint64_t s_lastcheck;
+
+ uint32_t s_checkinterval;
+ uint32_t s_creator_os;
+ uint16_t s_def_resuid;
+ uint16_t s_def_resgid;
+ uint32_t s_first_ino;
+
+ uint16_t s_inode_size;
+ uint16_t s_dat_entry_size;
+ uint16_t s_checkpoint_size;
+ uint16_t s_segment_usage_size;
+
+ uint8_t s_uuid[16];
+ char s_volume_name[80];
+
+ uint32_t s_c_interval;
+ uint32_t s_c_block_max;
+ uint32_t s_reserved[192];
+};
+
+#define NILFS_SB_MAGIC 0x3434
+#define NILFS_SB_OFFSET 0x400
+#define NILFS_SBB_OFFSET(_sz) ((((_sz) / 0x200) - 8) * 0x200)
+
+static int nilfs_valid_sb(blkid_probe pr, struct nilfs_super_block *sb, int is_bak)
+{
+ static unsigned char sum[4];
+ const int sumoff = offsetof(struct nilfs_super_block, s_sum);
+ size_t bytes;
+ const size_t crc_start = sumoff + 4;
+ uint32_t crc;
+
+ if (!sb || le16_to_cpu(sb->s_magic) != NILFS_SB_MAGIC)
+ return 0;
+
+ if (is_bak && blkid_probe_is_wholedisk(pr) &&
+ sb->s_dev_size != pr->size)
+ return 0;
+
+ bytes = le16_to_cpu(sb->s_bytes);
+ /* ensure that no underrun can happen in the length parameter
+ * of the crc32 call or more data are processed than read into
+ * sb */
+ if (bytes < crc_start || bytes > sizeof(struct nilfs_super_block))
+ return 0;
+
+ crc = ul_crc32(le32_to_cpu(sb->s_crc_seed), (unsigned char *)sb, sumoff);
+ crc = ul_crc32(crc, sum, 4);
+ crc = ul_crc32(crc, (unsigned char *)sb + crc_start, bytes - crc_start);
+
+ return blkid_probe_verify_csum(pr, crc, le32_to_cpu(sb->s_sum));
+}
+
+static int probe_nilfs2(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct nilfs_super_block *sb, *sbp, *sbb;
+ int valid[2], swp = 0;
+ uint64_t magoff;
+
+ /* primary */
+ sbp = (struct nilfs_super_block *) blkid_probe_get_buffer(
+ pr, NILFS_SB_OFFSET, sizeof(struct nilfs_super_block));
+ if (!sbp)
+ return errno ? -errno : 1;
+
+ valid[0] = nilfs_valid_sb(pr, sbp, 0);
+
+
+ /* backup */
+ sbb = (struct nilfs_super_block *) blkid_probe_get_buffer(
+ pr, NILFS_SBB_OFFSET(pr->size), sizeof(struct nilfs_super_block));
+ if (!sbb) {
+ valid[1] = 0;
+
+ /* If the primary block is valid then continue and ignore also
+ * I/O errors for backup block. Note the this is probably CD
+ * where I/O errors and the end of the disk/session are "normal".
+ */
+ if (!valid[0])
+ return errno ? -errno : 1;
+ } else
+ valid[1] = nilfs_valid_sb(pr, sbb, 1);
+
+
+ /*
+ * Compare two super blocks and set 1 in swp if the secondary
+ * super block is valid and newer. Otherwise, set 0 in swp.
+ */
+ if (!valid[0] && !valid[1])
+ return 1;
+
+ swp = valid[1] && (!valid[0] ||
+ le64_to_cpu(sbp->s_last_cno) >
+ le64_to_cpu(sbb->s_last_cno));
+ sb = swp ? sbb : sbp;
+
+ DBG(LOWPROBE, ul_debug("nilfs2: primary=%d, backup=%d, swap=%d",
+ valid[0], valid[1], swp));
+
+ if (*(sb->s_volume_name) != '\0')
+ blkid_probe_set_label(pr, (unsigned char *) sb->s_volume_name,
+ sizeof(sb->s_volume_name));
+
+ blkid_probe_set_uuid(pr, sb->s_uuid);
+ blkid_probe_sprintf_version(pr, "%u", le32_to_cpu(sb->s_rev_level));
+
+ magoff = swp ? NILFS_SBB_OFFSET(pr->size) : NILFS_SB_OFFSET;
+ magoff += offsetof(struct nilfs_super_block, s_magic);
+
+ if (blkid_probe_set_magic(pr, magoff, sizeof(sb->s_magic),
+ (unsigned char *) &sb->s_magic))
+ return 1;
+
+ if (le32_to_cpu(sb->s_log_block_size) < 32)
+ blkid_probe_set_block_size(pr, 1024U << le32_to_cpu(sb->s_log_block_size));
+
+ return 0;
+}
+
+const struct blkid_idinfo nilfs2_idinfo =
+{
+ .name = "nilfs2",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_nilfs2,
+ /* default min.size is 128MiB, but 1MiB for "mkfs.nilfs2 -b 1024 -B 16" */
+ .minsz = (1024 * 1024),
+ .magics = BLKID_NONE_MAGIC
+};
diff --git a/libblkid/src/superblocks/ntfs.c b/libblkid/src/superblocks/ntfs.c
new file mode 100644
index 0000000..dced699
--- /dev/null
+++ b/libblkid/src/superblocks/ntfs.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+struct ntfs_bios_parameters {
+ uint16_t sector_size; /* Size of a sector in bytes. */
+ uint8_t sectors_per_cluster; /* Size of a cluster in sectors. */
+ uint16_t reserved_sectors; /* zero */
+ uint8_t fats; /* zero */
+ uint16_t root_entries; /* zero */
+ uint16_t sectors; /* zero */
+ uint8_t media_type; /* 0xf8 = hard disk */
+ uint16_t sectors_per_fat; /* zero */
+ uint16_t sectors_per_track; /* irrelevant */
+ uint16_t heads; /* irrelevant */
+ uint32_t hidden_sectors; /* zero */
+ uint32_t large_sectors; /* zero */
+} __attribute__ ((__packed__));
+
+struct ntfs_super_block {
+ uint8_t jump[3];
+ uint8_t oem_id[8]; /* magic string */
+
+ struct ntfs_bios_parameters bpb;
+
+ uint16_t unused[2];
+ uint64_t number_of_sectors;
+ uint64_t mft_cluster_location;
+ uint64_t mft_mirror_cluster_location;
+ int8_t clusters_per_mft_record;
+ uint8_t reserved1[3];
+ int8_t cluster_per_index_record;
+ uint8_t reserved2[3];
+ uint64_t volume_serial;
+ uint32_t checksum;
+} __attribute__((packed));
+
+struct master_file_table_record {
+ uint32_t magic;
+ uint16_t usa_ofs;
+ uint16_t usa_count;
+ uint64_t lsn;
+ uint16_t sequence_number;
+ uint16_t link_count;
+ uint16_t attrs_offset;
+ uint16_t flags;
+ uint32_t bytes_in_use;
+ uint32_t bytes_allocated;
+} __attribute__((__packed__));
+
+struct file_attribute {
+ uint32_t type;
+ uint32_t len;
+ uint8_t non_resident;
+ uint8_t name_len;
+ uint16_t name_offset;
+ uint16_t flags;
+ uint16_t instance;
+ uint32_t value_len;
+ uint16_t value_offset;
+} __attribute__((__packed__));
+
+#define MFT_RECORD_VOLUME 3
+/* Windows 10 Creators edition has extended the cluster size limit to 2MB */
+#define NTFS_MAX_CLUSTER_SIZE (2 * 1024 * 1024)
+
+#define MFT_RECORD_ATTR_VOLUME_NAME 0x60
+#define MFT_RECORD_ATTR_END 0xffffffff
+
+static int __probe_ntfs(blkid_probe pr, const struct blkid_idmag *mag, int save_info)
+{
+ struct ntfs_super_block *ns;
+ struct master_file_table_record *mft;
+
+ uint32_t sectors_per_cluster, mft_record_size;
+ uint16_t sector_size;
+ uint64_t nr_clusters, off, attr_off;
+ unsigned char *buf_mft;
+
+ ns = blkid_probe_get_sb(pr, mag, struct ntfs_super_block);
+ if (!ns)
+ return errno ? -errno : 1;
+
+ /*
+ * Check bios parameters block
+ */
+ sector_size = le16_to_cpu(ns->bpb.sector_size);
+
+ if (sector_size < 256 || sector_size > 4096)
+ return 1;
+
+ switch (ns->bpb.sectors_per_cluster) {
+ case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:
+ sectors_per_cluster = ns->bpb.sectors_per_cluster;
+ break;
+ default:
+ if ((ns->bpb.sectors_per_cluster < 240)
+ || (ns->bpb.sectors_per_cluster > 249))
+ return 1;
+ sectors_per_cluster = 1 << (256 - ns->bpb.sectors_per_cluster);
+ }
+
+ if ((uint16_t) le16_to_cpu(ns->bpb.sector_size) *
+ sectors_per_cluster > NTFS_MAX_CLUSTER_SIZE)
+ return 1;
+
+ /* Unused fields must be zero */
+ if (le16_to_cpu(ns->bpb.reserved_sectors)
+ || le16_to_cpu(ns->bpb.root_entries)
+ || le16_to_cpu(ns->bpb.sectors)
+ || le16_to_cpu(ns->bpb.sectors_per_fat)
+ || le32_to_cpu(ns->bpb.large_sectors)
+ || ns->bpb.fats)
+ return 1;
+
+ if ((uint8_t) ns->clusters_per_mft_record < 0xe1
+ || (uint8_t) ns->clusters_per_mft_record > 0xf7) {
+
+ switch (ns->clusters_per_mft_record) {
+ case 1: case 2: case 4: case 8: case 16: case 32: case 64:
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (ns->clusters_per_mft_record > 0)
+ mft_record_size = ns->clusters_per_mft_record *
+ sectors_per_cluster * sector_size;
+ else
+ mft_record_size = 1 << (0 - ns->clusters_per_mft_record);
+
+ nr_clusters = le64_to_cpu(ns->number_of_sectors) / sectors_per_cluster;
+
+ if ((le64_to_cpu(ns->mft_cluster_location) > nr_clusters) ||
+ (le64_to_cpu(ns->mft_mirror_cluster_location) > nr_clusters))
+ return 1;
+
+
+ off = le64_to_cpu(ns->mft_cluster_location) * sector_size *
+ sectors_per_cluster;
+
+ DBG(LOWPROBE, ul_debug("NTFS: sector_size=%"PRIu16", mft_record_size=%"PRIu32", "
+ "sectors_per_cluster=%"PRIu32", nr_clusters=%"PRIu64" "
+ "cluster_offset=%"PRIu64"",
+ sector_size, mft_record_size,
+ sectors_per_cluster, nr_clusters,
+ off));
+
+ if (mft_record_size < 4)
+ return 1;
+
+ buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+ if (!buf_mft)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf_mft, "FILE", 4) != 0)
+ return 1;
+
+ off += MFT_RECORD_VOLUME * mft_record_size;
+
+ buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+ if (!buf_mft)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf_mft, "FILE", 4) != 0)
+ return 1;
+
+ /* return if caller does not care about UUID and LABEL */
+ if (!save_info)
+ return 0;
+
+ mft = (struct master_file_table_record *) buf_mft;
+ attr_off = le16_to_cpu(mft->attrs_offset);
+
+ while (attr_off + sizeof(struct file_attribute) <= mft_record_size &&
+ attr_off <= le32_to_cpu(mft->bytes_allocated)) {
+
+ uint32_t attr_len;
+ struct file_attribute *attr;
+
+ attr = (struct file_attribute *) (buf_mft + attr_off);
+ attr_len = le32_to_cpu(attr->len);
+ if (!attr_len)
+ break;
+
+ if (le32_to_cpu(attr->type) == (uint32_t) MFT_RECORD_ATTR_END)
+ break;
+ if (le32_to_cpu(attr->type) == (uint32_t) MFT_RECORD_ATTR_VOLUME_NAME) {
+ unsigned int val_off = le16_to_cpu(attr->value_offset);
+ unsigned int val_len = le32_to_cpu(attr->value_len);
+ unsigned char *val = ((uint8_t *) attr) + val_off;
+
+ if (attr_off + val_off + val_len <= mft_record_size)
+ blkid_probe_set_utf8label(pr, val, val_len,
+ UL_ENCODE_UTF16LE);
+ break;
+ }
+
+ attr_off += attr_len;
+ }
+
+ blkid_probe_set_block_size(pr, sector_size);
+
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &ns->volume_serial,
+ sizeof(ns->volume_serial),
+ "%016" PRIX64, le64_to_cpu(ns->volume_serial));
+ return 0;
+}
+
+static int probe_ntfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ return __probe_ntfs(pr, mag, 1);
+}
+
+int blkid_probe_is_ntfs(blkid_probe pr)
+{
+ const struct blkid_idmag *mag = NULL;
+ int rc;
+
+ rc = blkid_probe_get_idmag(pr, &ntfs_idinfo, NULL, &mag);
+ if (rc < 0)
+ return rc; /* error */
+ if (rc != BLKID_PROBE_OK || !mag)
+ return 0;
+
+ return __probe_ntfs(pr, mag, 0) == 0 ? 1 : 0;
+}
+
+const struct blkid_idinfo ntfs_idinfo =
+{
+ .name = "ntfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ntfs,
+ .magics =
+ {
+ { .magic = "NTFS ", .len = 8, .sboff = 3 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/nvidia_raid.c b/libblkid/src/superblocks/nvidia_raid.c
new file mode 100644
index 0000000..5db8ec2
--- /dev/null
+++ b/libblkid/src/superblocks/nvidia_raid.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct nv_metadata {
+ uint8_t vendor[8];
+ uint32_t size;
+ uint32_t chksum;
+ uint16_t version;
+} __attribute__((packed));
+
+#define NVIDIA_SIGNATURE "NVIDIA"
+
+static int probe_nvraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct nv_metadata *nv;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200) - 2) * 0x200;
+ nv = (struct nv_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct nv_metadata));
+ if (!nv)
+ return errno ? -errno : 1;
+
+ if (memcmp(nv->vendor, NVIDIA_SIGNATURE, sizeof(NVIDIA_SIGNATURE)-1) != 0)
+ return 1;
+ if (blkid_probe_sprintf_version(pr, "%u", le16_to_cpu(nv->version)) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off, sizeof(nv->vendor),
+ (unsigned char *) nv->vendor))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo nvraid_idinfo = {
+ .name = "nvidia_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_nvraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/ocfs.c b/libblkid/src/superblocks/ocfs.c
new file mode 100644
index 0000000..463ed7b
--- /dev/null
+++ b/libblkid/src/superblocks/ocfs.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct ocfs_volume_header {
+ unsigned char minor_version[4];
+ unsigned char major_version[4];
+ unsigned char signature[128];
+ char mount[128];
+ unsigned char mount_len[2];
+} __attribute__((packed));
+
+struct ocfs_volume_label {
+ unsigned char disk_lock[48];
+ char label[64];
+ unsigned char label_len[2];
+ unsigned char vol_id[16];
+ unsigned char vol_id_len[2];
+} __attribute__((packed));
+
+#define ocfsmajor(o) ( (uint32_t) o.major_version[0] \
+ + (((uint32_t) o.major_version[1]) << 8) \
+ + (((uint32_t) o.major_version[2]) << 16) \
+ + (((uint32_t) o.major_version[3]) << 24))
+
+#define ocfsminor(o) ( (uint32_t) o.minor_version[0] \
+ + (((uint32_t) o.minor_version[1]) << 8) \
+ + (((uint32_t) o.minor_version[2]) << 16) \
+ + (((uint32_t) o.minor_version[3]) << 24))
+
+#define ocfslabellen(o) ((uint32_t)o.label_len[0] + (((uint32_t) o.label_len[1]) << 8))
+#define ocfsmountlen(o) ((uint32_t)o.mount_len[0] + (((uint32_t) o.mount_len[1]) << 8))
+
+struct ocfs2_super_block {
+ uint8_t i_signature[8];
+ uint32_t i_generation;
+ int16_t i_suballoc_slot;
+ uint16_t i_suballoc_bit;
+ uint32_t i_reserved0;
+ uint32_t i_clusters;
+ uint32_t i_uid;
+ uint32_t i_gid;
+ uint64_t i_size;
+ uint16_t i_mode;
+ uint16_t i_links_count;
+ uint32_t i_flags;
+ uint64_t i_atime;
+ uint64_t i_ctime;
+ uint64_t i_mtime;
+ uint64_t i_dtime;
+ uint64_t i_blkno;
+ uint64_t i_last_eb_blk;
+ uint32_t i_fs_generation;
+ uint32_t i_atime_nsec;
+ uint32_t i_ctime_nsec;
+ uint32_t i_mtime_nsec;
+ uint64_t i_reserved1[9];
+ uint64_t i_pad1;
+ uint16_t s_major_rev_level;
+ uint16_t s_minor_rev_level;
+ uint16_t s_mnt_count;
+ int16_t s_max_mnt_count;
+ uint16_t s_state;
+ uint16_t s_errors;
+ uint32_t s_checkinterval;
+ uint64_t s_lastcheck;
+ uint32_t s_creator_os;
+ uint32_t s_feature_compat;
+ uint32_t s_feature_incompat;
+ uint32_t s_feature_ro_compat;
+ uint64_t s_root_blkno;
+ uint64_t s_system_dir_blkno;
+ uint32_t s_blocksize_bits;
+ uint32_t s_clustersize_bits;
+ uint16_t s_max_slots;
+ uint16_t s_reserved1;
+ uint32_t s_reserved2;
+ uint64_t s_first_cluster_group;
+ uint8_t s_label[64];
+ uint8_t s_uuid[16];
+} __attribute__((packed));
+
+struct oracle_asm_disk_label {
+ char dummy[32];
+ char dl_tag[8];
+ char dl_id[24];
+} __attribute__((packed));
+
+static int probe_ocfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ unsigned char *buf;
+ struct ocfs_volume_header ovh;
+ struct ocfs_volume_label ovl;
+ uint32_t maj, min;
+
+ /* header */
+ buf = blkid_probe_get_buffer(pr, mag->kboff << 10,
+ sizeof(struct ocfs_volume_header));
+ if (!buf)
+ return errno ? -errno : 1;
+ memcpy(&ovh, buf, sizeof(ovh));
+
+ /* label */
+ buf = blkid_probe_get_buffer(pr, (mag->kboff << 10) + 512,
+ sizeof(struct ocfs_volume_label));
+ if (!buf)
+ return errno ? -errno : 1;
+ memcpy(&ovl, buf, sizeof(ovl));
+
+ maj = ocfsmajor(ovh);
+ min = ocfsminor(ovh);
+
+ if (maj == 1)
+ blkid_probe_set_value(pr, "SEC_TYPE",
+ (unsigned char *) "ocfs1", sizeof("ocfs1"));
+ else if (maj >= 9)
+ blkid_probe_set_value(pr, "SEC_TYPE",
+ (unsigned char *) "ntocfs", sizeof("ntocfs"));
+
+ blkid_probe_set_label(pr, (unsigned char *) ovl.label,
+ ocfslabellen(ovl));
+ blkid_probe_set_value(pr, "MOUNT", (unsigned char *) ovh.mount,
+ ocfsmountlen(ovh));
+ blkid_probe_set_uuid(pr, ovl.vol_id);
+ blkid_probe_sprintf_version(pr, "%u.%u", maj, min);
+ return 0;
+}
+
+static int probe_ocfs2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct ocfs2_super_block *osb;
+
+ osb = blkid_probe_get_sb(pr, mag, struct ocfs2_super_block);
+ if (!osb)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_label(pr, (unsigned char *) osb->s_label, sizeof(osb->s_label));
+ blkid_probe_set_uuid(pr, osb->s_uuid);
+
+ blkid_probe_sprintf_version(pr, "%u.%u",
+ le16_to_cpu(osb->s_major_rev_level),
+ le16_to_cpu(osb->s_minor_rev_level));
+
+ if (le32_to_cpu(osb->s_blocksize_bits) < 32)
+ blkid_probe_set_block_size(pr, 1U << le32_to_cpu(osb->s_blocksize_bits));
+
+ return 0;
+}
+
+static int probe_oracleasm(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct oracle_asm_disk_label *dl;
+
+ dl = blkid_probe_get_sb(pr, mag, struct oracle_asm_disk_label);
+ if (!dl)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_label(pr, (unsigned char *) dl->dl_id, sizeof(dl->dl_id));
+ return 0;
+}
+
+
+const struct blkid_idinfo ocfs_idinfo =
+{
+ .name = "ocfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ocfs,
+ .minsz = 14000 * 1024,
+ .magics =
+ {
+ { .magic = "OracleCFS", .len = 9, .kboff = 8 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo ocfs2_idinfo =
+{
+ .name = "ocfs2",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ocfs2,
+ .minsz = 14000 * 1024,
+ .magics =
+ {
+ { .magic = "OCFSV2", .len = 6, .kboff = 1 },
+ { .magic = "OCFSV2", .len = 6, .kboff = 2 },
+ { .magic = "OCFSV2", .len = 6, .kboff = 4 },
+ { .magic = "OCFSV2", .len = 6, .kboff = 8 },
+ { NULL }
+ }
+};
+
+/* Oracle ASM (Automatic Storage Management) */
+const struct blkid_idinfo oracleasm_idinfo =
+{
+ .name = "oracleasm",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_oracleasm,
+ .magics =
+ {
+ { .magic = "ORCLDISK", .len = 8, .sboff = 32 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/promise_raid.c b/libblkid/src/superblocks/promise_raid.c
new file mode 100644
index 0000000..75f3a20
--- /dev/null
+++ b/libblkid/src/superblocks/promise_raid.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct promise_metadata {
+ uint8_t sig[24];
+};
+
+#define PDC_CONFIG_OFF 0x1200
+#define PDC_SIGNATURE "Promise Technology, Inc."
+
+static int probe_pdcraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ size_t i;
+ static unsigned int sectors[] = {
+ 63, 255, 256, 16, 399, 591, 675, 735, 911, 974, 991, 951, 3087
+ };
+ uint64_t nsectors;
+
+ if (pr->size < 0x40000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ nsectors = pr->size >> 9;
+
+ for (i = 0; i < ARRAY_SIZE(sectors); i++) {
+ uint64_t off;
+ struct promise_metadata *pdc;
+
+ if (nsectors < sectors[i])
+ return 1;
+
+ off = (nsectors - sectors[i]) << 9;
+ pdc = (struct promise_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct promise_metadata));
+ if (!pdc)
+ return errno ? -errno : 1;
+
+ if (memcmp(pdc->sig, PDC_SIGNATURE,
+ sizeof(PDC_SIGNATURE) - 1) == 0) {
+
+ if (blkid_probe_set_magic(pr, off, sizeof(pdc->sig),
+ (unsigned char *) pdc->sig))
+ return 1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+const struct blkid_idinfo pdcraid_idinfo = {
+ .name = "promise_fasttrack_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_pdcraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/refs.c b/libblkid/src/superblocks/refs.c
new file mode 100644
index 0000000..ea81f20
--- /dev/null
+++ b/libblkid/src/superblocks/refs.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+
+const struct blkid_idinfo refs_idinfo =
+{
+ .name = "ReFS",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .magics =
+ {
+ { .magic = "\000\000\000ReFS\000", .len = 8 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/reiserfs.c b/libblkid/src/superblocks/reiserfs.c
new file mode 100644
index 0000000..6c5e5b0
--- /dev/null
+++ b/libblkid/src/superblocks/reiserfs.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct reiserfs_super_block {
+ uint32_t rs_blocks_count;
+ uint32_t rs_free_blocks;
+ uint32_t rs_root_block;
+ uint32_t rs_journal_block;
+ uint32_t rs_journal_dev;
+ uint32_t rs_orig_journal_size;
+ uint32_t rs_dummy2[5];
+ uint16_t rs_blocksize;
+ uint16_t rs_dummy3[3];
+ unsigned char rs_magic[12];
+ uint32_t rs_dummy4[5];
+ unsigned char rs_uuid[16];
+ char rs_label[16];
+} __attribute__((packed));
+
+struct reiser4_super_block {
+ unsigned char rs4_magic[16];
+ uint8_t rs4_dummy[3];
+ uint8_t rs4_blocksize;
+ unsigned char rs4_uuid[16];
+ unsigned char rs4_label[16];
+ uint64_t rs4_dummy2;
+} __attribute__((packed));
+
+static int probe_reiser(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct reiserfs_super_block *rs;
+ unsigned int blocksize;
+
+ rs = blkid_probe_get_sb(pr, mag, struct reiserfs_super_block);
+ if (!rs)
+ return errno ? -errno : 1;
+
+ blocksize = le16_to_cpu(rs->rs_blocksize);
+
+ /* The blocksize must be at least 512B */
+ if ((blocksize >> 9) == 0)
+ return 1;
+
+ /* If the superblock is inside the journal, we have the wrong one */
+ if (mag->kboff / (blocksize >> 9) > le32_to_cpu(rs->rs_journal_block) / 2)
+ return 1;
+
+ /* LABEL/UUID are only valid for later versions of Reiserfs v3.6. */
+ if (mag->magic[6] == '2' || mag->magic[6] == '3') {
+ if (*rs->rs_label)
+ blkid_probe_set_label(pr,
+ (unsigned char *) rs->rs_label,
+ sizeof(rs->rs_label));
+ blkid_probe_set_uuid(pr, rs->rs_uuid);
+ }
+
+ if (mag->magic[6] == '3')
+ blkid_probe_set_version(pr, "JR");
+ else if (mag->magic[6] == '2')
+ blkid_probe_set_version(pr, "3.6");
+ else
+ blkid_probe_set_version(pr, "3.5");
+
+ blkid_probe_set_block_size(pr, blocksize);
+
+ return 0;
+}
+
+static int probe_reiser4(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct reiser4_super_block *rs4;
+ unsigned int blocksize;
+
+ rs4 = blkid_probe_get_sb(pr, mag, struct reiser4_super_block);
+ if (!rs4)
+ return errno ? -errno : 1;
+
+ blocksize = rs4->rs4_blocksize * 256;
+
+ if (*rs4->rs4_label)
+ blkid_probe_set_label(pr, rs4->rs4_label, sizeof(rs4->rs4_label));
+ blkid_probe_set_uuid(pr, rs4->rs4_uuid);
+ blkid_probe_set_version(pr, "4");
+
+ blkid_probe_set_block_size(pr, blocksize);
+
+ return 0;
+}
+
+
+const struct blkid_idinfo reiser_idinfo =
+{
+ .name = "reiserfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_reiser,
+ .minsz = 128 * 1024,
+ .magics =
+ {
+ { .magic = "ReIsErFs", .len = 8, .kboff = 8, .sboff = 0x34 },
+ { .magic = "ReIsEr2Fs", .len = 9, .kboff = 64, .sboff = 0x34 },
+ { .magic = "ReIsEr3Fs", .len = 9, .kboff = 64, .sboff = 0x34 },
+ { .magic = "ReIsErFs", .len = 8, .kboff = 64, .sboff = 0x34 },
+ { .magic = "ReIsErFs", .len = 8, .kboff = 8, .sboff = 20 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo reiser4_idinfo =
+{
+ .name = "reiser4",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_reiser4,
+ .minsz = 128 * 1024,
+ .magics =
+ {
+ { .magic = "ReIsEr4", .len = 7, .kboff = 64 },
+ { NULL }
+ }
+};
+
+
+
+
diff --git a/libblkid/src/superblocks/romfs.c b/libblkid/src/superblocks/romfs.c
new file mode 100644
index 0000000..1c2ac43
--- /dev/null
+++ b/libblkid/src/superblocks/romfs.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct romfs_super_block {
+ unsigned char ros_magic[8];
+ uint32_t ros_dummy1[2];
+ unsigned char ros_volume[16];
+} __attribute__((packed));
+
+static int probe_romfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct romfs_super_block *ros;
+
+ ros = blkid_probe_get_sb(pr, mag, struct romfs_super_block);
+ if (!ros)
+ return errno ? -errno : 1;
+
+ if (*((char *) ros->ros_volume) != '\0')
+ blkid_probe_set_label(pr, ros->ros_volume,
+ sizeof(ros->ros_volume));
+
+ blkid_probe_set_block_size(pr, 1024);
+
+ return 0;
+}
+
+const struct blkid_idinfo romfs_idinfo =
+{
+ .name = "romfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_romfs,
+ .magics =
+ {
+ { .magic = "-rom1fs-", .len = 8 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/silicon_raid.c b/libblkid/src/superblocks/silicon_raid.c
new file mode 100644
index 0000000..399eea8
--- /dev/null
+++ b/libblkid/src/superblocks/silicon_raid.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+struct silicon_metadata {
+ uint8_t unknown0[0x2E];
+ uint8_t ascii_version[0x36 - 0x2E];
+ int8_t diskname[0x56 - 0x36];
+ int8_t unknown1[0x60 - 0x56];
+ uint32_t magic;
+ int8_t unknown1a[0x6C - 0x64];
+ uint32_t array_sectors_low;
+ uint32_t array_sectors_high;
+ int8_t unknown2[0x78 - 0x74];
+ uint32_t thisdisk_sectors;
+ int8_t unknown3[0x100 - 0x7C];
+ int8_t unknown4[0x104 - 0x100];
+ uint16_t product_id;
+ uint16_t vendor_id;
+ uint16_t minor_ver;
+ uint16_t major_ver;
+ uint8_t seconds;
+ uint8_t minutes;
+ uint8_t hour;
+ uint8_t day;
+ uint8_t month;
+ uint8_t year;
+ uint16_t raid0_stride;
+ int8_t unknown6[0x116 - 0x114];
+ uint8_t disk_number;
+ uint8_t type; /* SILICON_TYPE_* */
+ int8_t drives_per_striped_set;
+ int8_t striped_set_number;
+ int8_t drives_per_mirrored_set;
+ int8_t mirrored_set_number;
+ uint32_t rebuild_ptr_low;
+ uint32_t rebuild_ptr_high;
+ uint32_t incarnation_no;
+ uint8_t member_status;
+ uint8_t mirrored_set_state; /* SILICON_MIRROR_* */
+ uint8_t reported_device_location;
+ uint8_t idechannel;
+ uint8_t auto_rebuild;
+ uint8_t unknown8;
+ uint8_t text_type[0x13E - 0x12E];
+ uint16_t checksum1;
+ int8_t assumed_zeros[0x1FE - 0x140];
+ uint16_t checksum2;
+} __attribute__((packed));
+
+#define SILICON_MAGIC 0x2F000000
+
+static uint16_t silraid_checksum(struct silicon_metadata *sil)
+{
+ int sum = 0;
+ unsigned short count = offsetof(struct silicon_metadata, checksum1) / 2;
+ unsigned char *ptr = (unsigned char *) sil;
+
+ while (count--) {
+ uint16_t val;
+
+ memcpy(&val, ptr, sizeof(uint16_t));
+ sum += le16_to_cpu(val);
+
+ ptr += sizeof(uint16_t);
+ }
+
+ return (-sum & 0xFFFF);
+}
+
+static int probe_silraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct silicon_metadata *sil;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200) - 1) * 0x200;
+
+ sil = (struct silicon_metadata *)
+ blkid_probe_get_buffer(pr, off,
+ sizeof(struct silicon_metadata));
+ if (!sil)
+ return errno ? -errno : 1;
+
+ if (le32_to_cpu(sil->magic) != SILICON_MAGIC)
+ return 1;
+ if (sil->disk_number >= 8)
+ return 1;
+ if (!blkid_probe_verify_csum(pr, silraid_checksum(sil), le16_to_cpu(sil->checksum1)))
+ return 1;
+
+ if (blkid_probe_sprintf_version(pr, "%u.%u",
+ le16_to_cpu(sil->major_ver),
+ le16_to_cpu(sil->minor_ver)) != 0)
+ return 1;
+
+ if (blkid_probe_set_magic(pr,
+ off + offsetof(struct silicon_metadata, magic),
+ sizeof(sil->magic),
+ (unsigned char *) &sil->magic))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo silraid_idinfo = {
+ .name = "silicon_medley_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_silraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/squashfs.c b/libblkid/src/superblocks/squashfs.c
new file mode 100644
index 0000000..4db8424
--- /dev/null
+++ b/libblkid/src/superblocks/squashfs.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "bitops.h" /* swab16() */
+#include "superblocks.h"
+
+struct sqsh_super_block {
+ uint32_t s_magic;
+ uint32_t inodes;
+ uint32_t bytes_used_2;
+ uint32_t uid_start_2;
+ uint32_t guid_start_2;
+ uint32_t inode_table_start_2;
+ uint32_t directory_table_start_2;
+ uint16_t s_major;
+ uint16_t s_minor;
+} __attribute__((packed));
+
+static int probe_squashfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct sqsh_super_block *sq;
+ uint16_t vermaj;
+ uint16_t vermin;
+
+ sq = blkid_probe_get_sb(pr, mag, struct sqsh_super_block);
+ if (!sq)
+ return errno ? -errno : 1;
+
+ vermaj = le16_to_cpu(sq->s_major);
+ vermin = le16_to_cpu(sq->s_minor);
+ if (vermaj < 4)
+ return 1;
+
+ blkid_probe_sprintf_version(pr, "%u.%u", vermaj, vermin);
+
+ return 0;
+}
+
+static int probe_squashfs3(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct sqsh_super_block *sq;
+ uint16_t vermaj;
+ uint16_t vermin;
+
+ sq = blkid_probe_get_sb(pr, mag, struct sqsh_super_block);
+ if (!sq)
+ return errno ? -errno : 1;
+
+ if (strcmp(mag->magic, "sqsh") == 0) {
+ vermaj = be16_to_cpu(sq->s_major);
+ vermin = be16_to_cpu(sq->s_minor);
+ } else {
+ vermaj = le16_to_cpu(sq->s_major);
+ vermin = le16_to_cpu(sq->s_minor);
+ }
+
+ if (vermaj > 3)
+ return 1;
+
+ blkid_probe_sprintf_version(pr, "%u.%u", vermaj, vermin);
+
+ blkid_probe_set_block_size(pr, 1024);
+
+ return 0;
+}
+
+const struct blkid_idinfo squashfs_idinfo =
+{
+ .name = "squashfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_squashfs,
+ .magics =
+ {
+ { .magic = "hsqs", .len = 4 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo squashfs3_idinfo =
+{
+ .name = "squashfs3",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_squashfs3,
+ .magics =
+ {
+ { .magic = "sqsh", .len = 4 }, /* big endian */
+ { .magic = "hsqs", .len = 4 }, /* little endian */
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/stratis.c b/libblkid/src/superblocks/stratis.c
new file mode 100644
index 0000000..fe4a452
--- /dev/null
+++ b/libblkid/src/superblocks/stratis.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 Tony Asleson <tasleson@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/*
+ * Specification for on disk format
+ * https://stratis-storage.github.io/StratisSoftwareDesign.pdf
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+#include "crc32c.h"
+
+/* Macro to avoid error in struct declaration. */
+#define STRATIS_UUID_LEN 32
+/* Contains 4 hyphens and trailing null byte. */
+const int STRATIS_UUID_STR_LEN = 37;
+
+struct stratis_sb {
+ uint32_t crc32;
+ uint8_t magic[16];
+ uint64_t sectors;
+ uint8_t reserved[4];
+ uint8_t pool_uuid[STRATIS_UUID_LEN];
+ uint8_t dev_uuid[STRATIS_UUID_LEN];
+ uint64_t mda_size;
+ uint64_t reserved_size;
+ uint64_t flags;
+ uint64_t initialization_time;
+} __attribute__ ((__packed__));
+
+#define BS 512
+#define FIRST_COPY_OFFSET BS
+#define SECOND_COPY_OFFSET (BS * 9)
+#define SB_AREA_SIZE (BS * 16)
+
+const char STRATIS_MAGIC[] = "!Stra0tis\x86\xff\x02^\x41rh";
+#define MAGIC_LEN (sizeof(STRATIS_MAGIC) - 1)
+
+#define _MAGIC_OFFSET (offsetof(const struct stratis_sb, magic))
+#define MAGIC_OFFSET_COPY_1 (FIRST_COPY_OFFSET + _MAGIC_OFFSET)
+#define MAGIC_OFFSET_COPY_2 (SECOND_COPY_OFFSET + _MAGIC_OFFSET)
+
+static int stratis_valid_sb(uint8_t *p)
+{
+ const struct stratis_sb *stratis = (const struct stratis_sb *)p;
+ uint32_t crc = 0;
+
+ /* generate CRC from byte position 4 for length 508 == 512 byte sector */
+ crc = crc32c(~0L, p + sizeof(stratis->crc32),
+ BS - sizeof(stratis->crc32));
+ crc ^= ~0L;
+
+ return crc == le32_to_cpu(stratis->crc32);
+}
+
+/*
+ * Format UUID string representation to include hyphens given that Stratis stores
+ * UUIDs without hyphens in the superblock to keep the UUID length a power of 2.
+ */
+static void stratis_format_uuid(const unsigned char *src_uuid,
+ unsigned char *dst_uuid)
+{
+ int i;
+
+ for (i = 0; i < STRATIS_UUID_LEN; i++) {
+ *dst_uuid++ = *src_uuid++;
+ if (i == 7 || i == 11 || i == 15 || i == 19)
+ *dst_uuid ++ = '-';
+ }
+ *dst_uuid = '\0';
+}
+
+static int probe_stratis(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ const struct stratis_sb *stratis = NULL;
+ uint8_t *buf = blkid_probe_get_buffer(pr, 0, SB_AREA_SIZE);
+ unsigned char uuid[STRATIS_UUID_STR_LEN];
+
+ if (!buf)
+ return errno ? -errno : 1;
+
+ if (stratis_valid_sb(buf + FIRST_COPY_OFFSET)) {
+ stratis = (const struct stratis_sb *)(buf + FIRST_COPY_OFFSET);
+ } else {
+ if (!stratis_valid_sb(buf + SECOND_COPY_OFFSET))
+ return 1;
+
+ stratis = (const struct stratis_sb *)
+ (buf + SECOND_COPY_OFFSET);
+ }
+
+ stratis_format_uuid(stratis->dev_uuid, uuid);
+ blkid_probe_strncpy_uuid(pr, uuid, sizeof(uuid));
+
+ stratis_format_uuid(stratis->pool_uuid, uuid);
+ blkid_probe_set_value(pr, "POOL_UUID", uuid, sizeof(uuid));
+
+ blkid_probe_sprintf_value(pr, "BLOCKDEV_SECTORS", "%" PRIu64,
+ le64_to_cpu(stratis->sectors));
+ blkid_probe_sprintf_value(pr, "BLOCKDEV_INITTIME", "%" PRIu64,
+ le64_to_cpu(stratis->initialization_time));
+ return 0;
+}
+
+const struct blkid_idinfo stratis_idinfo = {
+ .name = "stratis",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_stratis,
+ .minsz = SB_AREA_SIZE,
+ .magics = {
+ { .magic = STRATIS_MAGIC, .len = MAGIC_LEN,
+ .sboff = MAGIC_OFFSET_COPY_1},
+ { .magic = STRATIS_MAGIC, .len = MAGIC_LEN,
+ .sboff = MAGIC_OFFSET_COPY_2},
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/superblocks.c b/libblkid/src/superblocks/superblocks.c
new file mode 100644
index 0000000..b87e01e
--- /dev/null
+++ b/libblkid/src/superblocks/superblocks.c
@@ -0,0 +1,882 @@
+/*
+ * superblocks.c - reads information from filesystem and raid superblocks
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "superblocks.h"
+
+/**
+ * SECTION:superblocks
+ * @title: Superblocks probing
+ * @short_description: filesystems and raids superblocks probing.
+ *
+ * The library API has been originally designed for superblocks probing only.
+ * This is reason why some *deprecated* superblock specific functions don't use
+ * '_superblocks_' namespace in the function name. Please, don't use these
+ * functions in new code.
+ *
+ * The 'superblocks' probers support NAME=value (tags) interface only. The
+ * superblocks probing is enabled by default (and controlled by
+ * blkid_probe_enable_superblocks()).
+ *
+ * Currently supported tags:
+ *
+ * @TYPE: filesystem type
+ *
+ * @SEC_TYPE: secondary filesystem type
+ *
+ * @LABEL: filesystem label
+ *
+ * @LABEL_RAW: raw label from FS superblock
+ *
+ * @UUID: filesystem UUID (lower case)
+ *
+ * @UUID_SUB: subvolume uuid (e.g. btrfs)
+ *
+ * @LOGUUID: external log UUID (e.g. xfs)
+ *
+ * @UUID_RAW: raw UUID from FS superblock
+ *
+ * @EXT_JOURNAL: external journal UUID
+ *
+ * @USAGE: usage string: "raid", "filesystem", ...
+ *
+ * @VERSION: filesystem version
+ *
+ * @MOUNT: cluster mount name (?) -- ocfs only
+ *
+ * @SBMAGIC: super block magic string
+ *
+ * @SBMAGIC_OFFSET: offset of SBMAGIC
+ *
+ * @FSSIZE: size of filesystem [not-implemented yet]
+ *
+ * @SYSTEM_ID: ISO9660 system identifier
+ *
+ * @PUBLISHER_ID: ISO9660 publisher identifier
+ *
+ * @APPLICATION_ID: ISO9660 application identifier
+ *
+ * @BOOT_SYSTEM_ID: ISO9660 boot system identifier
+ *
+ * @BLOCK_SIZE: minimal block size accessible by file system
+ */
+
+static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn);
+static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn);
+
+static int blkid_probe_set_usage(blkid_probe pr, int usage);
+
+
+/*
+ * Superblocks chains probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+ /* RAIDs */
+ &linuxraid_idinfo,
+ &ddfraid_idinfo,
+ &iswraid_idinfo,
+ &lsiraid_idinfo,
+ &viaraid_idinfo,
+ &silraid_idinfo,
+ &nvraid_idinfo,
+ &pdcraid_idinfo,
+ &highpoint45x_idinfo,
+ &highpoint37x_idinfo,
+ &adraid_idinfo,
+ &jmraid_idinfo,
+
+ &bcache_idinfo,
+ &bluestore_idinfo,
+ &drbd_idinfo,
+ &drbdmanage_idinfo,
+ &drbdproxy_datalog_idinfo,
+ &lvm2_idinfo,
+ &lvm1_idinfo,
+ &snapcow_idinfo,
+ &verity_hash_idinfo,
+ &integrity_idinfo,
+ &luks_idinfo,
+ &vmfs_volume_idinfo,
+ &ubi_idinfo,
+ &vdo_idinfo,
+ &stratis_idinfo,
+ &bitlocker_idinfo,
+
+ /* Filesystems */
+ &vfat_idinfo,
+ &swsuspend_idinfo,
+ &swap_idinfo,
+ &xfs_idinfo,
+ &xfs_log_idinfo,
+ &exfs_idinfo,
+ &ext4dev_idinfo,
+ &ext4_idinfo,
+ &ext3_idinfo,
+ &ext2_idinfo,
+ &jbd_idinfo,
+ &reiser_idinfo,
+ &reiser4_idinfo,
+ &jfs_idinfo,
+ &udf_idinfo,
+ &iso9660_idinfo,
+ &zfs_idinfo,
+ &hfsplus_idinfo,
+ &hfs_idinfo,
+ &ufs_idinfo,
+ &hpfs_idinfo,
+ &sysv_idinfo,
+ &xenix_idinfo,
+ &ntfs_idinfo,
+ &refs_idinfo,
+ &cramfs_idinfo,
+ &romfs_idinfo,
+ &minix_idinfo,
+ &gfs_idinfo,
+ &gfs2_idinfo,
+ &ocfs_idinfo,
+ &ocfs2_idinfo,
+ &oracleasm_idinfo,
+ &vxfs_idinfo,
+ &squashfs_idinfo,
+ &squashfs3_idinfo,
+ &netware_idinfo,
+ &btrfs_idinfo,
+ &ubifs_idinfo,
+ &bfs_idinfo,
+ &vmfs_fs_idinfo,
+ &befs_idinfo,
+ &nilfs2_idinfo,
+ &exfat_idinfo,
+ &f2fs_idinfo,
+ &mpool_idinfo,
+ &apfs_idinfo,
+ &zonefs_idinfo,
+ &erofs_idinfo
+};
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv superblocks_drv = {
+ .id = BLKID_CHAIN_SUBLKS,
+ .name = "superblocks",
+ .dflt_enabled = TRUE,
+ .dflt_flags = BLKID_SUBLKS_DEFAULT,
+ .idinfos = idinfos,
+ .nidinfos = ARRAY_SIZE(idinfos),
+ .has_fltr = TRUE,
+ .probe = superblocks_probe,
+ .safeprobe = superblocks_safeprobe,
+};
+
+/**
+ * blkid_probe_enable_superblocks:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the superblocks probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_superblocks(blkid_probe pr, int enable)
+{
+ pr->chains[BLKID_CHAIN_SUBLKS].enabled = enable;
+ return 0;
+}
+
+/**
+ * blkid_probe_set_superblocks_flags:
+ * @pr: prober
+ * @flags: BLKID_SUBLKS_* flags
+ *
+ * Sets probing flags to the superblocks prober. This function is optional, the
+ * default are BLKID_SUBLKS_DEFAULTS flags.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags)
+{
+ pr->chains[BLKID_CHAIN_SUBLKS].flags = flags;
+ return 0;
+}
+
+/**
+ * blkid_probe_reset_superblocks_filter:
+ * @pr: prober
+ *
+ * Resets superblocks probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_reset_superblocks_filter(blkid_probe pr)
+{
+ return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_invert_superblocks_filter:
+ * @pr: prober
+ *
+ * Inverts superblocks probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_invert_superblocks_filter(blkid_probe pr)
+{
+ return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_filter_superblocks_type:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ * %BLKID_FLTR_NOTIN - probe for all items which are NOT IN @names;
+ *
+ * %BLKID_FLTR_ONLYIN - probe for items which are IN @names
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[])
+{
+ return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
+}
+
+/**
+ * blkid_probe_filter_superblocks_usage:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @usage: BLKID_USAGE_* flags
+ *
+ * %BLKID_FLTR_NOTIN - probe for all items which are NOT IN @usage;
+ *
+ * %BLKID_FLTR_ONLYIN - probe for items which are IN @usage
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage)
+{
+ unsigned long *fltr;
+ struct blkid_chain *chn;
+ size_t i;
+
+ fltr = blkid_probe_get_filter(pr, BLKID_CHAIN_SUBLKS, TRUE);
+ if (!fltr)
+ return -1;
+
+ chn = &pr->chains[BLKID_CHAIN_SUBLKS];
+
+ for (i = 0; i < chn->driver->nidinfos; i++) {
+ const struct blkid_idinfo *id = chn->driver->idinfos[i];
+
+ if (id->usage & usage) {
+ if (flag & BLKID_FLTR_NOTIN)
+ blkid_bmp_set_item(chn->fltr, i);
+ } else if (flag & BLKID_FLTR_ONLYIN)
+ blkid_bmp_set_item(chn->fltr, i);
+ }
+ DBG(LOWPROBE, ul_debug("a new probing usage-filter initialized"));
+ return 0;
+}
+
+/**
+ * blkid_known_fstype:
+ * @fstype: filesystem name
+ *
+ * Returns: 1 for known filesystems, or 0 for unknown filesystem.
+ */
+int blkid_known_fstype(const char *fstype)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
+ const struct blkid_idinfo *id = idinfos[i];
+ if (strcmp(id->name, fstype) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * blkid_superblocks_get_name:
+ * @idx: number >= 0
+ * @name: returns name of supported filesystem/raid (optional)
+ * @usage: returns BLKID_USAGE_* flags, (optional)
+ *
+ * Returns: -1 if @idx is out of range, or 0 on success.
+ */
+int blkid_superblocks_get_name(size_t idx, const char **name, int *usage)
+{
+ if (idx < ARRAY_SIZE(idinfos)) {
+ if (name)
+ *name = idinfos[idx]->name;
+ if (usage)
+ *usage = idinfos[idx]->usage;
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+ size_t i;
+ int rc = BLKID_PROBE_NONE;
+
+ if (chn->idx < -1)
+ return -EINVAL;
+
+ blkid_probe_chain_reset_values(pr, chn);
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV) {
+ DBG(LOWPROBE, ul_debug("*** ignore (noscan flag)"));
+ return BLKID_PROBE_NONE;
+ }
+
+ if (pr->size <= 0 || (pr->size <= 1024 && !S_ISCHR(pr->mode))) {
+ /* Ignore very very small block devices or regular files (e.g.
+ * extended partitions). Note that size of the UBI char devices
+ * is 1 byte */
+ DBG(LOWPROBE, ul_debug("*** ignore (size <= 1024)"));
+ return BLKID_PROBE_NONE;
+ }
+
+ DBG(LOWPROBE, ul_debug("--> starting probing loop [SUBLKS idx=%d]",
+ chn->idx));
+
+ i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+ for ( ; i < ARRAY_SIZE(idinfos); i++) {
+ const struct blkid_idinfo *id;
+ const struct blkid_idmag *mag = NULL;
+ uint64_t off = 0;
+
+ chn->idx = i;
+ id = idinfos[i];
+
+ if (chn->fltr && blkid_bmp_get_item(chn->fltr, i)) {
+ DBG(LOWPROBE, ul_debug("filter out: %s", id->name));
+ rc = BLKID_PROBE_NONE;
+ continue;
+ }
+
+ if (id->minsz && (unsigned)id->minsz > pr->size) {
+ rc = BLKID_PROBE_NONE;
+ continue; /* the device is too small */
+ }
+
+ /* don't probe for RAIDs, swap or journal on CD/DVDs */
+ if ((id->usage & (BLKID_USAGE_RAID | BLKID_USAGE_OTHER)) &&
+ blkid_probe_is_cdrom(pr)) {
+ rc = BLKID_PROBE_NONE;
+ continue;
+ }
+
+ /* don't probe for RAIDs on floppies */
+ if ((id->usage & BLKID_USAGE_RAID) && blkid_probe_is_tiny(pr)) {
+ rc = BLKID_PROBE_NONE;
+ continue;
+ }
+
+ DBG(LOWPROBE, ul_debug("[%zd] %s:", i, id->name));
+
+ rc = blkid_probe_get_idmag(pr, id, &off, &mag);
+ if (rc < 0)
+ break;
+ if (rc != BLKID_PROBE_OK)
+ continue;
+
+ /* final check by probing function */
+ if (id->probefunc) {
+ DBG(LOWPROBE, ul_debug("\tcall probefunc()"));
+ rc = id->probefunc(pr, mag);
+ if (rc != BLKID_PROBE_OK) {
+ blkid_probe_chain_reset_values(pr, chn);
+ if (rc < 0)
+ break;
+ continue;
+ }
+ }
+
+ /* all checks passed */
+ if (chn->flags & BLKID_SUBLKS_TYPE)
+ rc = blkid_probe_set_value(pr, "TYPE",
+ (const unsigned char *) id->name,
+ strlen(id->name) + 1);
+
+ if (!rc)
+ rc = blkid_probe_set_usage(pr, id->usage);
+
+ if (!rc && mag)
+ rc = blkid_probe_set_magic(pr, off, mag->len,
+ (const unsigned char *) mag->magic);
+ if (rc) {
+ blkid_probe_chain_reset_values(pr, chn);
+ DBG(LOWPROBE, ul_debug("failed to set result -- ignore"));
+ continue;
+ }
+
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [SUBLKS idx=%d]",
+ id->name, chn->idx));
+ return BLKID_PROBE_OK;
+ }
+
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed=%d) [SUBLKS idx=%d]",
+ rc, chn->idx));
+ return rc;
+}
+
+/*
+ * This is the same function as blkid_do_probe(), but returns only one result
+ * (cannot be used in while()) and checks for ambivalent results (more
+ * filesystems on the device) -- in such case returns -2.
+ *
+ * The function does not check for filesystems when a RAID or crypto signature
+ * is detected. The function also does not check for collision between RAIDs
+ * and crypto devices. The first detected RAID or crypto device is returned.
+ *
+ * The function does not probe for ambivalent results on very small devices
+ * (e.g. floppies), on small devices the first detected filesystem is returned.
+ */
+static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn)
+{
+ struct list_head vals;
+ int idx = -1;
+ int count = 0;
+ int intol = 0;
+ int rc;
+
+ INIT_LIST_HEAD(&vals);
+
+ if (pr->flags & BLKID_FL_NOSCAN_DEV)
+ return BLKID_PROBE_NONE;
+
+ while ((rc = superblocks_probe(pr, chn)) == 0) {
+
+ if (blkid_probe_is_tiny(pr) && !count)
+ return BLKID_PROBE_OK; /* floppy or so -- returns the first result. */
+
+ count++;
+
+ if (chn->idx >= 0 &&
+ idinfos[chn->idx]->usage & (BLKID_USAGE_RAID | BLKID_USAGE_CRYPTO))
+ break;
+
+ if (chn->idx >= 0 &&
+ !(idinfos[chn->idx]->flags & BLKID_IDINFO_TOLERANT))
+ intol++;
+
+ if (count == 1) {
+ /* save the first result */
+ blkid_probe_chain_save_values(pr, chn, &vals);
+ idx = chn->idx;
+ }
+ }
+
+ if (rc < 0)
+ goto done; /* error */
+
+ if (count > 1 && intol) {
+ DBG(LOWPROBE, ul_debug("ERROR: superblocks chain: "
+ "ambivalent result detected (%d filesystems)!",
+ count));
+ rc = -2; /* error, ambivalent result (more FS) */
+ goto done;
+ }
+ if (!count) {
+ rc = BLKID_PROBE_NONE;
+ goto done;
+ }
+
+ if (idx != -1) {
+ /* restore the first result */
+ blkid_probe_chain_reset_values(pr, chn);
+ blkid_probe_append_values_list(pr, &vals);
+ chn->idx = idx;
+ }
+
+ /*
+ * The RAID device could be partitioned. The problem are RAID1 devices
+ * where the partition table is visible from underlying devices. We
+ * have to ignore such partition tables.
+ */
+ if (chn->idx >= 0 && idinfos[chn->idx]->usage & BLKID_USAGE_RAID)
+ pr->prob_flags |= BLKID_PROBE_FL_IGNORE_PT;
+
+ rc = BLKID_PROBE_OK;
+done:
+ blkid_probe_free_values_list(&vals);
+ return rc;
+}
+
+int blkid_probe_set_version(blkid_probe pr, const char *version)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (chn->flags & BLKID_SUBLKS_VERSION)
+ return blkid_probe_set_value(pr, "VERSION",
+ (const unsigned char *) version,
+ strlen(version) + 1);
+ return 0;
+}
+
+
+int blkid_probe_sprintf_version(blkid_probe pr, const char *fmt, ...)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ int rc = 0;
+
+ if (chn->flags & BLKID_SUBLKS_VERSION) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ rc = blkid_probe_vsprintf_value(pr, "VERSION", fmt, ap);
+ va_end(ap);
+ }
+ return rc;
+}
+
+int blkid_probe_set_block_size(blkid_probe pr, unsigned block_size)
+{
+ return blkid_probe_sprintf_value(pr, "BLOCK_SIZE", "%u", block_size);
+}
+
+static int blkid_probe_set_usage(blkid_probe pr, int usage)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ char *u = NULL;
+
+ if (!(chn->flags & BLKID_SUBLKS_USAGE))
+ return 0;
+
+ if (usage & BLKID_USAGE_FILESYSTEM)
+ u = "filesystem";
+ else if (usage & BLKID_USAGE_RAID)
+ u = "raid";
+ else if (usage & BLKID_USAGE_CRYPTO)
+ u = "crypto";
+ else if (usage & BLKID_USAGE_OTHER)
+ u = "other";
+ else
+ u = "unknown";
+
+ return blkid_probe_set_value(pr, "USAGE", (unsigned char *) u, strlen(u) + 1);
+}
+
+int blkid_probe_set_id_label(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if (!(chn->flags & BLKID_SUBLKS_LABEL))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, name);
+ if (!v)
+ return -ENOMEM;
+
+ rc = blkid_probe_value_set_data(v, data, len);
+ if (!rc) {
+ /* remove white spaces */
+ v->len = blkid_rtrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ v->len = blkid_ltrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+
+}
+
+int blkid_probe_set_utf8_id_label(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len, int enc)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if (!(chn->flags & BLKID_SUBLKS_LABEL))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, name);
+ if (!v)
+ return -ENOMEM;
+
+ v->len = (len * 3) + 1;
+ v->data = calloc(1, v->len);
+ if (!v->data)
+ rc = -ENOMEM;
+
+ if (!rc) {
+ ul_encode_to_utf8(enc, v->data, v->len, data, len);
+ v->len = blkid_rtrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ v->len = blkid_ltrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+}
+
+int blkid_probe_set_label(blkid_probe pr, const unsigned char *label, size_t len)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
+ (rc = blkid_probe_set_value(pr, "LABEL_RAW", label, len)) < 0)
+ return rc;
+
+ if (!(chn->flags & BLKID_SUBLKS_LABEL))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, "LABEL");
+ if (!v)
+ return -ENOMEM;
+
+ rc = blkid_probe_value_set_data(v, label, len);
+ if (!rc) {
+ v->len = blkid_rtrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+}
+
+int blkid_probe_set_utf8label(blkid_probe pr, const unsigned char *label,
+ size_t len, int enc)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
+ (rc = blkid_probe_set_value(pr, "LABEL_RAW", label, len)) < 0)
+ return rc;
+
+ if (!(chn->flags & BLKID_SUBLKS_LABEL))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, "LABEL");
+ if (!v)
+ return -ENOMEM;
+
+ v->len = (len * 3) + 1;
+ v->data = calloc(1, v->len);
+ if (!v->data)
+ rc = -ENOMEM;
+ if (!rc) {
+ ul_encode_to_utf8(enc, v->data, v->len, label, len);
+ v->len = blkid_rtrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+}
+
+int blkid_probe_sprintf_uuid(blkid_probe pr, const unsigned char *uuid,
+ size_t len, const char *fmt, ...)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ va_list ap;
+ int rc = 0;
+
+ if (blkid_uuid_is_empty(uuid, len))
+ return 0;
+
+ if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+ (rc = blkid_probe_set_value(pr, "UUID_RAW", uuid, len)) < 0)
+ return rc;
+
+ if (!(chn->flags & BLKID_SUBLKS_UUID))
+ return 0;
+
+ va_start(ap, fmt);
+ rc = blkid_probe_vsprintf_value(pr, "UUID", fmt, ap);
+ va_end(ap);
+
+ return rc;
+}
+
+/* function to set UUIDs that are in superblocks stored as strings */
+int blkid_probe_strncpy_uuid(blkid_probe pr, const unsigned char *str, size_t len)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if (str == NULL || *str == '\0')
+ return -EINVAL;
+
+ if (!len)
+ len = strlen((const char *) str);
+
+ if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+ (rc = blkid_probe_set_value(pr, "UUID_RAW", str, len)) < 0)
+ return rc;
+
+ if (!(chn->flags & BLKID_SUBLKS_UUID))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, "UUID");
+ if (!v)
+ rc= -ENOMEM;
+ if (!rc)
+ rc = blkid_probe_value_set_data(v, str, len);
+ if (!rc) {
+ v->len = blkid_rtrim_whitespace(v->data) + 1;
+ if (v->len > 1)
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+}
+
+/* default _set_uuid function to set DCE UUIDs */
+int blkid_probe_set_uuid_as(blkid_probe pr, const unsigned char *uuid, const char *name)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+ struct blkid_prval *v;
+ int rc = 0;
+
+ if (blkid_uuid_is_empty(uuid, 16))
+ return 0;
+
+ if (!name) {
+ if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+ (rc = blkid_probe_set_value(pr, "UUID_RAW", uuid, 16)) < 0)
+ return rc;
+
+ if (!(chn->flags & BLKID_SUBLKS_UUID))
+ return 0;
+
+ v = blkid_probe_assign_value(pr, "UUID");
+ } else
+ v = blkid_probe_assign_value(pr, name);
+
+ if (!v)
+ return -ENOMEM;
+
+ v->len = UUID_STR_LEN;
+ v->data = calloc(1, v->len);
+ if (!v->data)
+ rc = -ENOMEM;
+
+ if (!rc) {
+ blkid_unparse_uuid(uuid, (char *) v->data, v->len);
+ return 0;
+ }
+
+ blkid_probe_free_value(v);
+ return rc;
+}
+
+int blkid_probe_set_uuid(blkid_probe pr, const unsigned char *uuid)
+{
+ return blkid_probe_set_uuid_as(pr, uuid, NULL);
+}
+
+/**
+ * blkid_probe_set_request:
+ * @pr: probe
+ * @flags: BLKID_PROBREQ_* (deprecated) or BLKID_SUBLKS_* flags
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_set_superblocks_flags().
+ */
+int blkid_probe_set_request(blkid_probe pr, int flags)
+{
+ return blkid_probe_set_superblocks_flags(pr, flags);
+}
+
+/**
+ * blkid_probe_reset_filter:
+ * @pr: prober
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_reset_superblocks_filter().
+ */
+int blkid_probe_reset_filter(blkid_probe pr)
+{
+ return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_invert_filter:
+ * @pr: prober
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_invert_superblocks_filter().
+ */
+int blkid_probe_invert_filter(blkid_probe pr)
+{
+ return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_filter_types
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_filter_superblocks_type().
+ */
+int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
+{
+ return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
+}
+
+/**
+ * blkid_probe_filter_usage
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @usage: BLKID_USAGE_* flags
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_filter_superblocks_usage().
+ */
+int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
+{
+ return blkid_probe_filter_superblocks_usage(pr, flag, usage);
+}
+
+
diff --git a/libblkid/src/superblocks/superblocks.h b/libblkid/src/superblocks/superblocks.h
new file mode 100644
index 0000000..9c489c4
--- /dev/null
+++ b/libblkid/src/superblocks/superblocks.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef _BLKID_SUPERBLOCKS_H
+#define _BLKID_SUPERBLOCKS_H
+
+#include "blkidP.h"
+
+extern const struct blkid_idinfo cramfs_idinfo;
+extern const struct blkid_idinfo swap_idinfo;
+extern const struct blkid_idinfo swsuspend_idinfo;
+extern const struct blkid_idinfo adraid_idinfo;
+extern const struct blkid_idinfo ddfraid_idinfo;
+extern const struct blkid_idinfo iswraid_idinfo;
+extern const struct blkid_idinfo jmraid_idinfo;
+extern const struct blkid_idinfo lsiraid_idinfo;
+extern const struct blkid_idinfo nvraid_idinfo;
+extern const struct blkid_idinfo pdcraid_idinfo;
+extern const struct blkid_idinfo silraid_idinfo;
+extern const struct blkid_idinfo viaraid_idinfo;
+extern const struct blkid_idinfo linuxraid_idinfo;
+extern const struct blkid_idinfo ext4dev_idinfo;
+extern const struct blkid_idinfo ext4_idinfo;
+extern const struct blkid_idinfo ext3_idinfo;
+extern const struct blkid_idinfo ext2_idinfo;
+extern const struct blkid_idinfo jbd_idinfo;
+extern const struct blkid_idinfo jfs_idinfo;
+extern const struct blkid_idinfo xfs_idinfo;
+extern const struct blkid_idinfo xfs_log_idinfo;
+extern const struct blkid_idinfo exfs_idinfo;
+extern const struct blkid_idinfo gfs_idinfo;
+extern const struct blkid_idinfo gfs2_idinfo;
+extern const struct blkid_idinfo romfs_idinfo;
+extern const struct blkid_idinfo ocfs_idinfo;
+extern const struct blkid_idinfo ocfs2_idinfo;
+extern const struct blkid_idinfo oracleasm_idinfo;
+extern const struct blkid_idinfo reiser_idinfo;
+extern const struct blkid_idinfo reiser4_idinfo;
+extern const struct blkid_idinfo hfs_idinfo;
+extern const struct blkid_idinfo hfsplus_idinfo;
+extern const struct blkid_idinfo ntfs_idinfo;
+extern const struct blkid_idinfo refs_idinfo;
+extern const struct blkid_idinfo iso9660_idinfo;
+extern const struct blkid_idinfo udf_idinfo;
+extern const struct blkid_idinfo vxfs_idinfo;
+extern const struct blkid_idinfo minix_idinfo;
+extern const struct blkid_idinfo vfat_idinfo;
+extern const struct blkid_idinfo ufs_idinfo;
+extern const struct blkid_idinfo hpfs_idinfo;
+extern const struct blkid_idinfo lvm2_idinfo;
+extern const struct blkid_idinfo lvm1_idinfo;
+extern const struct blkid_idinfo snapcow_idinfo;
+extern const struct blkid_idinfo verity_hash_idinfo;
+extern const struct blkid_idinfo integrity_idinfo;
+extern const struct blkid_idinfo luks_idinfo;
+extern const struct blkid_idinfo highpoint37x_idinfo;
+extern const struct blkid_idinfo highpoint45x_idinfo;
+extern const struct blkid_idinfo squashfs_idinfo;
+extern const struct blkid_idinfo squashfs3_idinfo;
+extern const struct blkid_idinfo netware_idinfo;
+extern const struct blkid_idinfo sysv_idinfo;
+extern const struct blkid_idinfo xenix_idinfo;
+extern const struct blkid_idinfo btrfs_idinfo;
+extern const struct blkid_idinfo ubi_idinfo;
+extern const struct blkid_idinfo ubifs_idinfo;
+extern const struct blkid_idinfo zfs_idinfo;
+extern const struct blkid_idinfo bfs_idinfo;
+extern const struct blkid_idinfo vmfs_volume_idinfo;
+extern const struct blkid_idinfo vmfs_fs_idinfo;
+extern const struct blkid_idinfo bluestore_idinfo;
+extern const struct blkid_idinfo drbd_idinfo;
+extern const struct blkid_idinfo drbdmanage_idinfo;
+extern const struct blkid_idinfo drbdproxy_datalog_idinfo;
+extern const struct blkid_idinfo befs_idinfo;
+extern const struct blkid_idinfo nilfs2_idinfo;
+extern const struct blkid_idinfo exfat_idinfo;
+extern const struct blkid_idinfo f2fs_idinfo;
+extern const struct blkid_idinfo bcache_idinfo;
+extern const struct blkid_idinfo mpool_idinfo;
+extern const struct blkid_idinfo vdo_idinfo;
+extern const struct blkid_idinfo stratis_idinfo;
+extern const struct blkid_idinfo bitlocker_idinfo;
+extern const struct blkid_idinfo apfs_idinfo;
+extern const struct blkid_idinfo zonefs_idinfo;
+extern const struct blkid_idinfo erofs_idinfo;
+
+/*
+ * superblock functions
+ */
+extern int blkid_probe_set_version(blkid_probe pr, const char *version);
+extern int blkid_probe_sprintf_version(blkid_probe pr, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+
+extern int blkid_probe_set_label(blkid_probe pr, const unsigned char *label, size_t len);
+extern int blkid_probe_set_utf8label(blkid_probe pr, const unsigned char *label,
+ size_t len, int enc);
+extern int blkid_probe_sprintf_uuid(blkid_probe pr, const unsigned char *uuid,
+ size_t len, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 4, 5)));
+extern int blkid_probe_strncpy_uuid(blkid_probe pr, const unsigned char *str, size_t len);
+
+extern int blkid_probe_set_uuid(blkid_probe pr, const unsigned char *uuid);
+extern int blkid_probe_set_uuid_as(blkid_probe pr, const unsigned char *uuid, const char *name);
+
+extern int blkid_probe_set_id_label(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len);
+extern int blkid_probe_set_utf8_id_label(blkid_probe pr, const char *name,
+ const unsigned char *data, size_t len, int enc);
+
+int blkid_probe_set_block_size(blkid_probe pr, unsigned block_size);
+
+extern int blkid_probe_is_bitlocker(blkid_probe pr);
+extern int blkid_probe_is_ntfs(blkid_probe pr);
+
+#endif /* _BLKID_SUPERBLOCKS_H */
diff --git a/libblkid/src/superblocks/swap.c b/libblkid/src/superblocks/swap.c
new file mode 100644
index 0000000..fcdb13b
--- /dev/null
+++ b/libblkid/src/superblocks/swap.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* linux-2.6/include/linux/swap.h */
+struct swap_header_v1_2 {
+ /* char bootbits[1024]; */ /* Space for disklabel etc. */
+ uint32_t version;
+ uint32_t lastpage;
+ uint32_t nr_badpages;
+ unsigned char uuid[16];
+ unsigned char volume[16];
+ uint32_t padding[117];
+ uint32_t badpages[1];
+} __attribute__((packed));
+
+#define PAGESIZE_MIN 0xff6 /* 4086 (arm, i386, ...) */
+#define PAGESIZE_MAX 0xfff6 /* 65526 (ia64) */
+
+#define TOI_MAGIC_STRING "\xed\xc3\x02\xe9\x98\x56\xe5\x0c"
+#define TOI_MAGIC_STRLEN (sizeof(TOI_MAGIC_STRING) - 1)
+
+static int swap_set_info(blkid_probe pr, const char *version)
+{
+ struct swap_header_v1_2 *hdr;
+
+ /* Swap header always located at offset of 1024 bytes */
+ hdr = (struct swap_header_v1_2 *) blkid_probe_get_buffer(pr, 1024,
+ sizeof(struct swap_header_v1_2));
+ if (!hdr)
+ return errno ? -errno : 1;
+
+ /* SWAPSPACE2 - check for wrong version or zeroed pagecount */
+ if (strcmp(version, "1") == 0) {
+ if (hdr->version != 1 && swab32(hdr->version) != 1) {
+ DBG(LOWPROBE, ul_debug("incorrect swap version"));
+ return 1;
+ }
+ if (hdr->lastpage == 0) {
+ DBG(LOWPROBE, ul_debug("not set last swap page"));
+ return 1;
+ }
+ }
+
+ /* arbitrary sanity check.. is there any garbage down there? */
+ if (hdr->padding[32] == 0 && hdr->padding[33] == 0) {
+ if (hdr->volume[0] && blkid_probe_set_label(pr, hdr->volume,
+ sizeof(hdr->volume)) < 0)
+ return 1;
+ if (blkid_probe_set_uuid(pr, hdr->uuid) < 0)
+ return 1;
+ }
+
+ blkid_probe_set_version(pr, version);
+ return 0;
+}
+
+static int probe_swap(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ unsigned char *buf;
+
+ if (!mag)
+ return 1;
+
+ /* TuxOnIce keeps valid swap header at the end of the 1st page */
+ buf = blkid_probe_get_buffer(pr, 0, TOI_MAGIC_STRLEN);
+ if (!buf)
+ return errno ? -errno : 1;
+
+ if (memcmp(buf, TOI_MAGIC_STRING, TOI_MAGIC_STRLEN) == 0)
+ return 1; /* Ignore swap signature, it's TuxOnIce */
+
+ if (!memcmp(mag->magic, "SWAP-SPACE", mag->len)) {
+ /* swap v0 doesn't support LABEL or UUID */
+ blkid_probe_set_version(pr, "0");
+ return 0;
+
+ }
+
+ if (!memcmp(mag->magic, "SWAPSPACE2", mag->len))
+ return swap_set_info(pr, "1");
+
+ return 1;
+}
+
+static int probe_swsuspend(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ if (!mag)
+ return 1;
+ if (!memcmp(mag->magic, "S1SUSPEND", mag->len))
+ return swap_set_info(pr, "s1suspend");
+ if (!memcmp(mag->magic, "S2SUSPEND", mag->len))
+ return swap_set_info(pr, "s2suspend");
+ if (!memcmp(mag->magic, "ULSUSPEND", mag->len))
+ return swap_set_info(pr, "ulsuspend");
+ if (!memcmp(mag->magic, TOI_MAGIC_STRING, TOI_MAGIC_STRLEN))
+ return swap_set_info(pr, "tuxonice");
+ if (!memcmp(mag->magic, "LINHIB0001", mag->len))
+ return swap_set_info(pr, "linhib0001");
+
+ return 1; /* no signature detected */
+}
+
+const struct blkid_idinfo swap_idinfo =
+{
+ .name = "swap",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_swap,
+ .minsz = 10 * 4096, /* 10 pages */
+ .magics =
+ {
+ { .magic = "SWAP-SPACE", .len = 10, .sboff = 0xff6 },
+ { .magic = "SWAPSPACE2", .len = 10, .sboff = 0xff6 },
+ { .magic = "SWAP-SPACE", .len = 10, .sboff = 0x1ff6 },
+ { .magic = "SWAPSPACE2", .len = 10, .sboff = 0x1ff6 },
+ { .magic = "SWAP-SPACE", .len = 10, .sboff = 0x3ff6 },
+ { .magic = "SWAPSPACE2", .len = 10, .sboff = 0x3ff6 },
+ { .magic = "SWAP-SPACE", .len = 10, .sboff = 0x7ff6 },
+ { .magic = "SWAPSPACE2", .len = 10, .sboff = 0x7ff6 },
+ { .magic = "SWAP-SPACE", .len = 10, .sboff = 0xfff6 },
+ { .magic = "SWAPSPACE2", .len = 10, .sboff = 0xfff6 },
+ { NULL }
+ }
+};
+
+
+const struct blkid_idinfo swsuspend_idinfo =
+{
+ .name = "swsuspend",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_swsuspend,
+ .minsz = 10 * 4096, /* 10 pages */
+ .magics =
+ {
+ { .magic = TOI_MAGIC_STRING, .len = TOI_MAGIC_STRLEN },
+ { .magic = "S1SUSPEND", .len = 9, .sboff = 0xff6 },
+ { .magic = "S2SUSPEND", .len = 9, .sboff = 0xff6 },
+ { .magic = "ULSUSPEND", .len = 9, .sboff = 0xff6 },
+ { .magic = "LINHIB0001", .len = 10, .sboff = 0xff6 },
+
+ { .magic = "S1SUSPEND", .len = 9, .sboff = 0x1ff6 },
+ { .magic = "S2SUSPEND", .len = 9, .sboff = 0x1ff6 },
+ { .magic = "ULSUSPEND", .len = 9, .sboff = 0x1ff6 },
+ { .magic = "LINHIB0001", .len = 10, .sboff = 0x1ff6 },
+
+ { .magic = "S1SUSPEND", .len = 9, .sboff = 0x3ff6 },
+ { .magic = "S2SUSPEND", .len = 9, .sboff = 0x3ff6 },
+ { .magic = "ULSUSPEND", .len = 9, .sboff = 0x3ff6 },
+ { .magic = "LINHIB0001", .len = 10, .sboff = 0x3ff6 },
+
+ { .magic = "S1SUSPEND", .len = 9, .sboff = 0x7ff6 },
+ { .magic = "S2SUSPEND", .len = 9, .sboff = 0x7ff6 },
+ { .magic = "ULSUSPEND", .len = 9, .sboff = 0x7ff6 },
+ { .magic = "LINHIB0001", .len = 10, .sboff = 0x7ff6 },
+
+ { .magic = "S1SUSPEND", .len = 9, .sboff = 0xfff6 },
+ { .magic = "S2SUSPEND", .len = 9, .sboff = 0xfff6 },
+ { .magic = "ULSUSPEND", .len = 9, .sboff = 0xfff6 },
+ { .magic = "LINHIB0001", .len = 10, .sboff = 0xfff6 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/sysv.c b/libblkid/src/superblocks/sysv.c
new file mode 100644
index 0000000..421660e
--- /dev/null
+++ b/libblkid/src/superblocks/sysv.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This is written from scratch according to Linux kernel fs/sysv/super.c file.
+ * It seems that sysv probing code in libvolume_id and also in the original
+ * blkid is useless.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+#define XENIX_NICINOD 100
+#define XENIX_NICFREE 100
+
+struct xenix_super_block {
+ uint16_t s_isize;
+ uint32_t s_fsize;
+ uint16_t s_nfree;
+ uint32_t s_free[XENIX_NICFREE];
+ uint16_t s_ninode;
+ uint16_t s_inode[XENIX_NICINOD];
+ uint8_t s_flock;
+ uint8_t s_ilock;
+ uint8_t s_fmod;
+ uint8_t s_ronly;
+ uint32_t s_time;
+ uint32_t s_tfree;
+ uint16_t s_tinode;
+ uint16_t s_dinfo[4];
+ uint8_t s_fname[6];
+ uint8_t s_fpack[6];
+ uint8_t s_clean;
+ uint8_t s_fill[371];
+ uint32_t s_magic;
+ uint32_t s_type;
+} __attribute__((packed));
+
+
+#define SYSV_NICINOD 100
+#define SYSV_NICFREE 50
+
+struct sysv_super_block
+{
+ uint16_t s_isize;
+ uint16_t s_pad0;
+ uint32_t s_fsize;
+ uint16_t s_nfree;
+ uint16_t s_pad1;
+ uint32_t s_free[SYSV_NICFREE];
+ uint16_t s_ninode;
+ uint16_t s_pad2;
+ uint16_t s_inode[SYSV_NICINOD];
+ uint8_t s_flock;
+ uint8_t s_ilock;
+ uint8_t s_fmod;
+ uint8_t s_ronly;
+ uint32_t s_time;
+ uint16_t s_dinfo[4];
+ uint32_t s_tfree;
+ uint16_t s_tinode;
+ uint16_t s_pad3;
+ uint8_t s_fname[6];
+ uint8_t s_fpack[6];
+ uint32_t s_fill[12];
+ uint32_t s_state;
+ uint32_t s_magic;
+ uint32_t s_type;
+};
+
+static int probe_xenix(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct xenix_super_block *sb;
+
+ sb = blkid_probe_get_sb(pr, mag, struct xenix_super_block);
+ if (!sb)
+ return errno ? -errno : 1;
+ blkid_probe_set_label(pr, sb->s_fname, sizeof(sb->s_fname));
+ return 0;
+}
+
+#define SYSV_BLOCK_SIZE 1024
+
+/* Note that we don't probe for Coherent FS, this FS does not have
+ * magic string. (It requires to probe fname/fpack field..)
+ */
+static int probe_sysv(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct sysv_super_block *sb;
+ int blocks[] = {0, 9, 15, 18};
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(blocks); i++) {
+ int off = blocks[i] * SYSV_BLOCK_SIZE + SYSV_BLOCK_SIZE/2;
+
+ sb = (struct sysv_super_block *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct sysv_super_block));
+ if (!sb)
+ return errno ? -errno : 1;
+
+ if (sb->s_magic == cpu_to_le32(0xfd187e20) ||
+ sb->s_magic == cpu_to_be32(0xfd187e20)) {
+
+ if (blkid_probe_set_label(pr, sb->s_fname,
+ sizeof(sb->s_fname)))
+ return 1;
+
+ if (blkid_probe_set_magic(pr,
+ off + offsetof(struct sysv_super_block,
+ s_magic),
+ sizeof(sb->s_magic),
+ (unsigned char *) &sb->s_magic))
+ return 1;
+
+ return 0;
+ }
+ }
+ return 1;
+}
+
+const struct blkid_idinfo xenix_idinfo =
+{
+ .name = "xenix",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_xenix,
+ .magics =
+ {
+ { .magic = "\x2b\x55\x44", .len = 3, .kboff = 1, .sboff = 0x400 },
+ { .magic = "\x44\x55\x2b", .len = 3, .kboff = 1, .sboff = 0x400 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo sysv_idinfo =
+{
+ .name = "sysv",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_sysv,
+
+ /* SYSV is BE and LE and superblock could be on four positions. It's
+ * simpler to probe for the magic string by .probefunc().
+ */
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/ubi.c b/libblkid/src/superblocks/ubi.c
new file mode 100644
index 0000000..0739c32
--- /dev/null
+++ b/libblkid/src/superblocks/ubi.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 Rafał Miłecki <rafal@milecki.pl>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct ubi_ec_hdr {
+ uint32_t magic;
+ uint8_t version;
+ uint8_t padding1[3];
+ uint64_t ec;
+ uint32_t vid_hdr_offset;
+ uint32_t data_offset;
+ uint32_t image_seq;
+ uint8_t padding2[32];
+ uint32_t hdr_crc;
+} __attribute__((packed));
+
+static int probe_ubi(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct ubi_ec_hdr *hdr;
+
+ hdr = blkid_probe_get_sb(pr, mag, struct ubi_ec_hdr);
+ if (!hdr)
+ return -1;
+
+ blkid_probe_sprintf_version(pr, "%u", hdr->version);
+ blkid_probe_sprintf_uuid(pr, (unsigned char *)&hdr->image_seq, 4, "%u",
+ be32_to_cpu(hdr->image_seq));
+ return 0;
+}
+
+const struct blkid_idinfo ubi_idinfo =
+{
+ .name = "ubi",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_ubi,
+ .magics =
+ {
+ { .magic = "UBI#", .len = 4 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/ubifs.c b/libblkid/src/superblocks/ubifs.c
new file mode 100644
index 0000000..dc84260
--- /dev/null
+++ b/libblkid/src/superblocks/ubifs.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 Corentin Chary <corentincj@iksaif.net>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/*
+ * struct ubifs_ch - common header node.
+ * @magic: UBIFS node magic number (%UBIFS_NODE_MAGIC)
+ * @crc: CRC-32 checksum of the node header
+ * @sqnum: sequence number
+ * @len: full node length
+ * @node_type: node type
+ * @group_type: node group type
+ * @padding: reserved for future, zeroes
+ *
+ * Every UBIFS node starts with this common part. If the node has a key, the
+ * key always goes next.
+ */
+struct ubifs_ch {
+ uint32_t magic;
+ uint32_t crc;
+ uint64_t sqnum;
+ uint32_t len;
+ uint8_t node_type;
+ uint8_t group_type;
+ uint8_t padding[2];
+} __attribute__ ((packed));
+
+/*
+ * struct ubifs_sb_node - superblock node.
+ * @ch: common header
+ * @padding: reserved for future, zeroes
+ * @key_hash: type of hash function used in keys
+ * @key_fmt: format of the key
+ * @flags: file-system flags (%UBIFS_FLG_BIGLPT, etc)
+ * @min_io_size: minimal input/output unit size
+ * @leb_size: logical eraseblock size in bytes
+ * @leb_cnt: count of LEBs used by file-system
+ * @max_leb_cnt: maximum count of LEBs used by file-system
+ * @max_bud_bytes: maximum amount of data stored in buds
+ * @log_lebs: log size in logical eraseblocks
+ * @lpt_lebs: number of LEBs used for lprops table
+ * @orph_lebs: number of LEBs used for recording orphans
+ * @jhead_cnt: count of journal heads
+ * @fanout: tree fanout (max. number of links per indexing node)
+ * @lsave_cnt: number of LEB numbers in LPT's save table
+ * @fmt_version: UBIFS on-flash format version
+ * @default_compr: default compression algorithm (%UBIFS_COMPR_LZO, etc)
+ * @padding1: reserved for future, zeroes
+ * @rp_uid: reserve pool UID
+ * @rp_gid: reserve pool GID
+ * @rp_size: size of the reserved pool in bytes
+ * @padding2: reserved for future, zeroes
+ * @time_gran: time granularity in nanoseconds
+ * @uuid: UUID generated when the file system image was created
+ * @ro_compat_version: UBIFS R/O compatibility version
+ */
+struct ubifs_sb_node {
+ struct ubifs_ch ch;
+ uint8_t padding[2];
+ uint8_t key_hash;
+ uint8_t key_fmt;
+ uint32_t flags;
+ uint32_t min_io_size;
+ uint32_t leb_size;
+ uint32_t leb_cnt;
+ uint32_t max_leb_cnt;
+ uint64_t max_bud_bytes;
+ uint32_t log_lebs;
+ uint32_t lpt_lebs;
+ uint32_t orph_lebs;
+ uint32_t jhead_cnt;
+ uint32_t fanout;
+ uint32_t lsave_cnt;
+ uint32_t fmt_version;
+ uint16_t default_compr;
+ uint8_t padding1[2];
+ uint32_t rp_uid;
+ uint32_t rp_gid;
+ uint64_t rp_size;
+ uint32_t time_gran;
+ uint8_t uuid[16];
+ uint32_t ro_compat_version;
+ uint8_t padding2[3968];
+} __attribute__ ((packed));
+
+static int probe_ubifs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct ubifs_sb_node *sb;
+
+ sb = blkid_probe_get_sb(pr, mag, struct ubifs_sb_node);
+ if (!sb)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_uuid(pr, sb->uuid);
+ blkid_probe_sprintf_version(pr, "w%dr%d",
+ le32_to_cpu(sb->fmt_version),
+ le32_to_cpu(sb->ro_compat_version));
+ return 0;
+}
+
+const struct blkid_idinfo ubifs_idinfo =
+{
+ .name = "ubifs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ubifs,
+ .magics =
+ {
+ { .magic = "\x31\x18\x10\x06", .len = 4 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/udf.c b/libblkid/src/superblocks/udf.c
new file mode 100644
index 0000000..9ed089e
--- /dev/null
+++ b/libblkid/src/superblocks/udf.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2014-2017 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define is_charset_udf(charspec) ((charspec).type == 0 && strncmp((charspec).info, "OSTA Compressed Unicode", sizeof((charspec).info)) == 0)
+
+#define udf_cid_to_enc(cid) ((cid) == 8 ? UL_ENCODE_LATIN1 : (cid) == 16 ? UL_ENCODE_UTF16BE : -1)
+
+struct charspec {
+ uint8_t type;
+ char info[63];
+} __attribute__((packed));
+
+struct dstring128 {
+ uint8_t cid;
+ uint8_t c[126];
+ uint8_t clen;
+} __attribute__((packed));
+
+struct dstring32 {
+ uint8_t cid;
+ uint8_t c[30];
+ uint8_t clen;
+} __attribute__((packed));
+
+struct dstring36 {
+ uint8_t cid;
+ uint8_t c[34];
+ uint8_t clen;
+} __attribute__((packed));
+
+struct volume_descriptor {
+ struct descriptor_tag {
+ uint16_t id;
+ uint16_t version;
+ uint8_t checksum;
+ uint8_t reserved;
+ uint16_t serial;
+ uint16_t crc;
+ uint16_t crc_len;
+ uint32_t location;
+ } __attribute__((packed)) tag;
+
+ union {
+ struct anchor_descriptor {
+ uint32_t length;
+ uint32_t location;
+ } __attribute__((packed)) anchor;
+
+ struct primary_descriptor {
+ uint32_t seq_num;
+ uint32_t desc_num;
+ struct dstring32 ident;
+ uint16_t vds_num;
+ uint16_t max_vol_seq;
+ uint16_t ichg_lvl;
+ uint16_t max_ichg_lvl;
+ uint32_t charset_list;
+ uint32_t max_charset_list;
+ struct dstring128 volset_id;
+ struct charspec desc_charset;
+ struct charspec exp_charset;
+ uint32_t vol_abstract[2];
+ uint32_t vol_copyright[2];
+ uint8_t app_id_flags;
+ char app_id[23];
+ uint8_t app_id_reserved[8];
+ uint8_t recording_date[12];
+ uint8_t imp_id_flags;
+ char imp_id[23];
+ uint8_t imp_id_os_class;
+ uint8_t imp_id_os_id;
+ uint8_t imp_id_reserved[6];
+ } __attribute__((packed)) primary;
+
+ struct logical_descriptor {
+ uint32_t seq_num;
+ struct charspec desc_charset;
+ struct dstring128 logvol_id;
+ uint32_t logical_blocksize;
+ uint8_t domain_id_flags;
+ char domain_id[23];
+ uint16_t udf_rev;
+ uint8_t domain_suffix_flags;
+ uint8_t reserved[5];
+ uint8_t logical_contents_use[16];
+ uint32_t map_table_length;
+ uint32_t num_partition_maps;
+ uint8_t imp_id[32];
+ uint8_t imp_use[128];
+ uint32_t lvid_length;
+ uint32_t lvid_location;
+ } __attribute__((packed)) logical;
+
+ struct logical_vol_integ_descriptor {
+ uint8_t recording_date[12];
+ uint32_t type;
+ uint32_t next_lvid_length;
+ uint32_t next_lvid_location;
+ uint8_t logical_contents_use[32];
+ uint32_t num_partitions;
+ uint32_t imp_use_length;
+ } __attribute__((packed)) logical_vol_integ;
+
+ struct imp_use_volume_descriptor {
+ uint32_t seq_num;
+ uint8_t lvi_id_flags;
+ char lvi_id[23];
+ uint16_t lvi_id_udf_rev;
+ uint8_t lvi_id_os_class;
+ uint8_t lvi_id_os_id;
+ uint8_t lvi_id_reserved[4];
+ struct charspec lvi_charset;
+ struct dstring128 logvol_id;
+ struct dstring36 lvinfo1;
+ struct dstring36 lvinfo2;
+ struct dstring36 lvinfo3;
+ } __attribute__((packed)) imp_use_volume;
+ } __attribute__((packed)) type;
+
+} __attribute__((packed));
+
+#define TAG_ID_PVD 1
+#define TAG_ID_AVDP 2
+#define TAG_ID_IUVD 4
+#define TAG_ID_LVD 6
+#define TAG_ID_TD 8
+#define TAG_ID_LVID 9
+
+struct volume_structure_descriptor {
+ uint8_t type;
+ uint8_t id[5];
+ uint8_t version;
+} __attribute__((packed));
+
+#define UDF_VSD_OFFSET 0x8000LL
+
+struct logical_vol_integ_descriptor_imp_use
+{
+ uint8_t imp_id[32];
+ uint32_t num_files;
+ uint32_t num_dirs;
+ uint16_t min_udf_read_rev;
+ uint16_t min_udf_write_rev;
+ uint16_t max_udf_write_rev;
+} __attribute__ ((packed));
+
+#define UDF_LVIDIU_OFFSET(vd) (sizeof((vd).tag) + sizeof((vd).type.logical_vol_integ) + 2 * 4 * le32_to_cpu((vd).type.logical_vol_integ.num_partitions))
+#define UDF_LVIDIU_LENGTH(vd) (le32_to_cpu((vd).type.logical_vol_integ.imp_use_length))
+
+static inline int gen_uuid_from_volset_id(unsigned char uuid[17], struct dstring128 *volset_id)
+{
+ int enc;
+ size_t i;
+ size_t len;
+ size_t clen;
+ size_t nonhexpos;
+ unsigned char buf[17];
+
+ memset(buf, 0, sizeof(buf));
+
+ clen = volset_id->clen;
+ if (clen > 0)
+ --clen;
+ if (clen > sizeof(volset_id->c))
+ clen = sizeof(volset_id->c);
+
+ enc = udf_cid_to_enc(volset_id->cid);
+ if (enc == -1)
+ return -1;
+
+ len = ul_encode_to_utf8(enc, buf, sizeof(buf), volset_id->c, clen);
+ if (len < 8)
+ return -1;
+
+ nonhexpos = 16;
+ for (i = 0; i < 16; ++i) {
+ if (!isxdigit(buf[i])) {
+ nonhexpos = i;
+ break;
+ }
+ }
+
+ if (nonhexpos < 8) {
+ snprintf((char *) uuid, 17, "%02x%02x%02x%02x%02x%02x%02x%02x",
+ buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7]);
+ } else if (nonhexpos < 16) {
+ for (i = 0; i < 8; ++i)
+ uuid[i] = tolower(buf[i]);
+ snprintf((char *) uuid + 8, 9, "%02x%02x%02x%02x",
+ buf[8], buf[9], buf[10], buf[11]);
+ } else {
+ for (i = 0; i < 16; ++i)
+ uuid[i] = tolower(buf[i]);
+ uuid[16] = 0;
+ }
+
+ return 0;
+}
+
+static int probe_udf(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct volume_descriptor *vd;
+ struct volume_structure_descriptor *vsd;
+ struct logical_vol_integ_descriptor_imp_use *lvidiu;
+ uint32_t lvid_len = 0;
+ uint32_t lvid_loc = 0;
+ uint64_t s_off;
+ uint32_t bs;
+ uint32_t b;
+ uint16_t type;
+ uint32_t count;
+ uint32_t loc;
+ size_t i;
+ uint32_t vsd_len;
+ uint16_t udf_rev = 0;
+ int vsd_2048_valid = -1;
+ int have_label = 0;
+ int have_uuid = 0;
+ int have_logvolid = 0;
+ int have_volid = 0;
+ int have_volsetid = 0;
+ int have_applicationid = 0;
+ int have_publisherid = 0;
+
+ /* Session offset */
+ if (blkid_probe_get_hint(pr, "session_offset", &s_off) < 0)
+ s_off = 0;
+
+ /* The block size of a UDF filesystem is that of the underlying
+ * storage; we check later on for the special case of image files,
+ * which may have any block size valid for UDF filesystem */
+ uint32_t pbs[] = { 0, 512, 1024, 2048, 4096 };
+ pbs[0] = blkid_probe_get_sectorsize(pr);
+
+ for (i = 0; i < ARRAY_SIZE(pbs); i++) {
+ /* Do not try with block size same as sector size two times */
+ if (i != 0 && pbs[0] == pbs[i])
+ continue;
+
+ /* Do not try with block size which is not divisor of session offset */
+ if (s_off % pbs[i])
+ continue;
+
+ /* ECMA-167 2/8.4, 2/9.1: Each VSD is either 2048 bytes long or
+ * its size is same as blocksize (for blocksize > 2048 bytes)
+ * plus padded with zeros */
+ vsd_len = pbs[i] > 2048 ? pbs[i] : 2048;
+
+ /* Process 2048 bytes long VSD on first session only once
+ * as its location is same for any blocksize */
+ if (s_off == 0 && vsd_len == 2048) {
+ if (vsd_2048_valid == 0)
+ continue;
+ if (vsd_2048_valid == 1)
+ goto anchor;
+ }
+
+ /* Check for a Volume Structure Descriptor (VSD) */
+ for (b = 0; b < 64; b++) {
+ vsd = (struct volume_structure_descriptor *)
+ blkid_probe_get_buffer(pr,
+ s_off + UDF_VSD_OFFSET + b * vsd_len,
+ sizeof(*vsd));
+ if (!vsd)
+ return errno ? -errno : 1;
+ if (vsd->id[0] == '\0')
+ break;
+ if (memcmp(vsd->id, "NSR02", 5) == 0 ||
+ memcmp(vsd->id, "NSR03", 5) == 0)
+ goto anchor;
+ else if (memcmp(vsd->id, "BEA01", 5) != 0 &&
+ memcmp(vsd->id, "BOOT2", 5) != 0 &&
+ memcmp(vsd->id, "CD001", 5) != 0 &&
+ memcmp(vsd->id, "CDW02", 5) != 0 &&
+ memcmp(vsd->id, "TEA01", 5) != 0)
+ /* ECMA-167 2/8.3.1: The volume recognition sequence is
+ * terminated by the first sector which is not a valid
+ * descriptor.
+ * UDF-2.60 2.1.7: UDF 2.00 and lower revisions do not
+ * have requirement that NSR descriptor is in Extended Area
+ * (between BEA01 and TEA01) and that there is only one
+ * Extended Area. So do not stop scanning after TEA01. */
+ break;
+ }
+
+ if (s_off == 0 && vsd_len == 2048)
+ vsd_2048_valid = 0;
+
+ /* NSR was not found, try with next block size */
+ continue;
+
+anchor:
+ if (s_off == 0 && vsd_len == 2048)
+ vsd_2048_valid = 1;
+
+ /* Read Anchor Volume Descriptor (AVDP), detect block size */
+ vd = (struct volume_descriptor *)
+ blkid_probe_get_buffer(pr, s_off + 256 * pbs[i], sizeof(*vd));
+ if (!vd)
+ return errno ? -errno : 1;
+
+ /* Check that we read correct sector and detected correct block size */
+ if (le32_to_cpu(vd->tag.location) == s_off / pbs[i] + 256) {
+ type = le16_to_cpu(vd->tag.id);
+ if (type == TAG_ID_AVDP)
+ goto real_blksz;
+ }
+
+ /* UDF-2.60: 2.2.3: Unclosed sequential Write-Once media may
+ * have a single AVDP present at either sector 256 or 512. */
+ vd = (struct volume_descriptor *)
+ blkid_probe_get_buffer(pr, s_off + 512 * pbs[i], sizeof(*vd));
+ if (!vd)
+ return errno ? -errno : 1;
+
+ if (le32_to_cpu(vd->tag.location) == s_off / pbs[i] + 512) {
+ type = le16_to_cpu(vd->tag.id);
+ if (type == TAG_ID_AVDP)
+ goto real_blksz;
+ }
+
+ }
+ return 1;
+
+real_blksz:
+ /* Use the actual block size from here on out */
+ bs = pbs[i];
+
+ /* get descriptor list address and block count */
+ count = le32_to_cpu(vd->type.anchor.length) / bs;
+ loc = le32_to_cpu(vd->type.anchor.location);
+
+ /* pick the primary descriptor from the list and read UDF identifiers */
+ for (b = 0; b < count; b++) {
+ vd = (struct volume_descriptor *)
+ blkid_probe_get_buffer(pr,
+ (uint64_t) (loc + b) * bs,
+ sizeof(*vd));
+ if (!vd)
+ return errno ? -errno : 1;
+ type = le16_to_cpu(vd->tag.id);
+ if (type == 0)
+ break;
+ if (le32_to_cpu(vd->tag.location) != loc + b)
+ break;
+ if (type == TAG_ID_TD)
+ break;
+ if (type == TAG_ID_PVD) {
+ if (!have_volid && is_charset_udf(vd->type.primary.desc_charset)) {
+ int enc = udf_cid_to_enc(vd->type.primary.ident.cid);
+ uint8_t clen = vd->type.primary.ident.clen;
+ if (clen > 0)
+ --clen;
+ if (clen > sizeof(vd->type.primary.ident.c))
+ clen = sizeof(vd->type.primary.ident.c);
+ if (enc != -1)
+ have_volid = !blkid_probe_set_utf8_id_label(pr, "VOLUME_ID",
+ vd->type.primary.ident.c, clen, enc);
+ }
+ if (!have_uuid && is_charset_udf(vd->type.primary.desc_charset)) {
+ /* VolumeSetIdentifier in UDF 2.01 specification:
+ * =================================================================================
+ * 2.2.2.5 dstring VolumeSetIdentifier
+ *
+ * Interpreted as specifying the identifier for the volume set.
+ *
+ * The first 16 characters of this field should be set to a unique value. The
+ * remainder of the field may be set to any allowed value. Specifically, software
+ * generating volumes conforming to this specification shall not set this field to a
+ * fixed or trivial value. Duplicate disks which are intended to be identical may
+ * contain the same value in this field.
+ *
+ * NOTE: The intended purpose of this is to guarantee Volume Sets with unique
+ * identifiers. The first 8 characters of the unique part should come from a CS0
+ * hexadecimal representation of a 32-bit time value. The remaining 8 characters
+ * are free for implementation use.
+ * =================================================================================
+ *
+ * Implementation in libblkid:
+ * The first 16 (Unicode) characters of VolumeSetIdentifier are encoded to UTF-8
+ * and then first 16 UTF-8 bytes are used to generate UUID. If all 16 bytes are
+ * hexadecimal digits then their lowercase variants are used as UUID. If one of
+ * the first 8 bytes (time value) is not hexadecimal digit then first 8 bytes are
+ * encoded to their hexadecimal representations, resulting in 16 characters and
+ * set as UUID. If all first 8 bytes (time value) are hexadecimal digits but some
+ * remaining not then lowercase variant of the first 8 bytes are used as first
+ * part of UUID and next 4 bytes encoded in hexadecimal representations (resulting
+ * in 8 characters) are used as second part of UUID string.
+ */
+ unsigned char uuid[17];
+ if (gen_uuid_from_volset_id(uuid, &vd->type.primary.volset_id) == 0)
+ have_uuid = !blkid_probe_strncpy_uuid(pr, uuid, sizeof(uuid));
+ }
+ if (!have_volsetid && is_charset_udf(vd->type.primary.desc_charset)) {
+ int enc = udf_cid_to_enc(vd->type.primary.volset_id.cid);
+ uint8_t clen = vd->type.primary.volset_id.clen;
+ if (clen > 0)
+ --clen;
+ if (clen > sizeof(vd->type.primary.volset_id.c))
+ clen = sizeof(vd->type.primary.volset_id.c);
+ if (enc != -1)
+ have_volsetid = !blkid_probe_set_utf8_id_label(pr, "VOLUME_SET_ID",
+ vd->type.primary.volset_id.c, clen, enc);
+ }
+ if (!have_applicationid) {
+ /* UDF-2.60: 2.2.2.9: This field specifies a valid Entity Identifier identifying the application that last wrote this field */
+ const unsigned char *app_id = (const unsigned char *)vd->type.primary.app_id;
+ size_t app_id_len = strnlen(vd->type.primary.app_id, sizeof(vd->type.primary.app_id));
+ if (app_id_len > 0 && app_id[0] == '*') {
+ app_id++;
+ app_id_len--;
+ }
+ /* When Application Identifier is not set then use Developer ID from Implementation Identifier */
+ if (app_id_len == 0) {
+ /* UDF-2.60: 2.1.5.2: "*Developer ID" refers to an Entity Identifier that uniquely identifies the current implementation */
+ app_id = (const unsigned char *)vd->type.primary.imp_id;
+ app_id_len = strnlen(vd->type.primary.imp_id, sizeof(vd->type.primary.imp_id));
+ if (app_id_len > 0 && app_id[0] == '*') {
+ app_id++;
+ app_id_len--;
+ }
+ }
+ if (app_id_len > 0) {
+ /* UDF-2.60: 2.1.5.2: Values used by UDF for this field are specified in terms of ASCII character strings */
+ have_applicationid = !blkid_probe_set_id_label(pr, "APPLICATION_ID", app_id, app_id_len);
+ }
+ }
+ } else if (type == TAG_ID_LVD) {
+ if (!lvid_len || !lvid_loc) {
+ uint32_t num_partition_maps = le32_to_cpu(vd->type.logical.num_partition_maps);
+ /* ECMA-167 3/10.6.12: If num_partition_maps is 0, then no LVID is specified */
+ if (num_partition_maps) {
+ lvid_len = le32_to_cpu(vd->type.logical.lvid_length);
+ lvid_loc = le32_to_cpu(vd->type.logical.lvid_location);
+ }
+ }
+ if (!udf_rev) {
+ /* UDF-2.60: 2.1.5.3: UDF revision field shall indicate revision of UDF document
+ * We use maximal value from this field and from LVIDIU fields for ID_FS_VERSION */
+ if (strncmp(vd->type.logical.domain_id, "*OSTA UDF Compliant", sizeof(vd->type.logical.domain_id)) == 0)
+ udf_rev = le16_to_cpu(vd->type.logical.udf_rev);
+ }
+ if ((!have_logvolid || !have_label) && is_charset_udf(vd->type.logical.desc_charset)) {
+ /* LogicalVolumeIdentifier in UDF 2.01 specification:
+ * ===============================================================
+ * 2. Basic Restrictions & Requirements
+ *
+ * Logical Volume Descriptor
+ *
+ * There shall be exactly one prevailing Logical Volume
+ * Descriptor recorded per Volume Set.
+ *
+ * The LogicalVolumeIdentifier field shall not be null and
+ * should contain an identifier that aids in the identification of
+ * the logical volume. Specifically, software generating
+ * volumes conforming to this specification shall not set this
+ * field to a fixed or trivial value. Duplicate disks, which are
+ * intended to be identical, may contain the same value in this
+ * field. This field is extremely important in logical volume
+ * identification when multiple media are present within a
+ * jukebox. This name is typically what is displayed to the user.
+ * ===============================================================
+ *
+ * Implementation in libblkid:
+ * The LogicalVolumeIdentifier field is used for LABEL. MS Windows
+ * read Volume Label also from LogicalVolumeIdentifier. Grub2 read
+ * LABEL also from this field. Program newfs_udf (from UDFclient)
+ * when formatting disk set this field from user option Disc Name.
+ */
+ int enc = udf_cid_to_enc(vd->type.logical.logvol_id.cid);
+ uint8_t clen = vd->type.logical.logvol_id.clen;
+ if (clen > 0)
+ --clen;
+ if (clen > sizeof(vd->type.logical.logvol_id.c))
+ clen = sizeof(vd->type.logical.logvol_id.c);
+ if (enc != -1) {
+ if (!have_label)
+ have_label = !blkid_probe_set_utf8label(pr,
+ vd->type.logical.logvol_id.c, clen, enc);
+ if (!have_logvolid)
+ have_logvolid = !blkid_probe_set_utf8_id_label(pr, "LOGICAL_VOLUME_ID",
+ vd->type.logical.logvol_id.c, clen, enc);
+ }
+ }
+ } else if (type == TAG_ID_IUVD) {
+ if (!have_publisherid && strncmp(vd->type.imp_use_volume.lvi_id, "*UDF LV Info", sizeof(vd->type.imp_use_volume.lvi_id)) == 0 && is_charset_udf(vd->type.imp_use_volume.lvi_charset)) {
+ /* UDF-2.60: 2.2.7.2.3: Field LVInfo1 could contain information such as Owner Name
+ * More UDF generating tools set this field to person who creating the filesystem
+ * therefore its meaning is similar to ISO9660 Publisher Identifier. So for
+ * compatibility with iso9660 superblock code export this field via PUBLISHER_ID.
+ */
+ int enc = udf_cid_to_enc(vd->type.imp_use_volume.lvinfo1.cid);
+ uint8_t clen = vd->type.imp_use_volume.lvinfo1.clen;
+ if (clen > 0)
+ --clen;
+ if (clen > sizeof(vd->type.imp_use_volume.lvinfo1.c))
+ clen = sizeof(vd->type.imp_use_volume.lvinfo1.c);
+ if (enc != -1)
+ have_publisherid = !blkid_probe_set_utf8_id_label(pr, "PUBLISHER_ID",
+ vd->type.imp_use_volume.lvinfo1.c, clen, enc);
+ }
+ }
+ if (have_volid && have_uuid && have_volsetid && have_logvolid && have_label && lvid_len && lvid_loc && have_applicationid && have_publisherid)
+ break;
+ }
+
+ /* Pick the first logical volume integrity descriptor and read UDF revision */
+ if (lvid_loc && lvid_len >= sizeof(*vd)) {
+ vd = (struct volume_descriptor *)
+ blkid_probe_get_buffer(pr,
+ (uint64_t) lvid_loc * bs,
+ sizeof(*vd));
+ if (!vd)
+ return errno ? -errno : 1;
+ type = le16_to_cpu(vd->tag.id);
+ if (type == TAG_ID_LVID &&
+ le32_to_cpu(vd->tag.location) == lvid_loc &&
+ UDF_LVIDIU_LENGTH(*vd) >= sizeof(*lvidiu)) {
+ /* ECMA-167 3/8.8.2: There is stored sequence of LVIDs and valid is just last
+ * one. So correctly we should jump to next_lvid_location and read next LVID
+ * until we find last one. This could be time consuming process and could
+ * lead to scanning lot of disk blocks. Because we use LVID only for UDF
+ * version, in the worst case we would report only wrong ID_FS_VERSION. */
+ uint16_t lvidiu_udf_rev;
+ lvidiu = (struct logical_vol_integ_descriptor_imp_use *)
+ blkid_probe_get_buffer(pr,
+ (uint64_t) lvid_loc * bs + UDF_LVIDIU_OFFSET(*vd),
+ sizeof(*lvidiu));
+ if (!lvidiu)
+ return errno ? -errno : 1;
+ /* UDF-2.60: 2. Basic Restrictions & Requirements:
+ * The Minimum UDF Read Revision value shall be at most #0250
+ * for all media with a UDF 2.60 file system.
+ * Because some 2.60 implementations put 2.50 into both LVIDIU
+ * fields and 2.60 into LVD, use maximal value from LVD,
+ * Minimum UDF Read Revision and Minimum UDF Write Revision for
+ * ID_FS_VERSION to distinguish between UDF 2.50 and UDF 2.60 discs. */
+ lvidiu_udf_rev = le16_to_cpu(lvidiu->min_udf_read_rev);
+ if (lvidiu_udf_rev && udf_rev < lvidiu_udf_rev)
+ udf_rev = lvidiu_udf_rev;
+ lvidiu_udf_rev = le16_to_cpu(lvidiu->min_udf_write_rev);
+ if (lvidiu_udf_rev && udf_rev < lvidiu_udf_rev)
+ udf_rev = lvidiu_udf_rev;
+ }
+ }
+
+ if (udf_rev)
+ /* UDF revision is stored as decimal number in hexadecimal format.
+ * E.g. number 0x0150 is revision 1.50, number 0x0201 is revision 2.01. */
+ blkid_probe_sprintf_version(pr, "%x.%02x", (unsigned int)(udf_rev >> 8), (unsigned int)(udf_rev & 0xFF));
+
+ blkid_probe_set_block_size(pr, bs);
+
+ return 0;
+}
+
+
+const struct blkid_idinfo udf_idinfo =
+{
+ .name = "udf",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_udf,
+ .flags = BLKID_IDINFO_TOLERANT,
+ .magics =
+ {
+ { .magic = "BEA01", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "BOOT2", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "CD001", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "CDW02", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "NSR02", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "NSR03", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { .magic = "TEA01", .len = 5, .kboff = 32, .sboff = 1, .hoff = "session_offset" },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/ufs.c b/libblkid/src/superblocks/ufs.c
new file mode 100644
index 0000000..7a8396c
--- /dev/null
+++ b/libblkid/src/superblocks/ufs.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+struct ufs_super_block {
+ uint32_t fs_link;
+ uint32_t fs_rlink;
+ uint32_t fs_sblkno;
+ uint32_t fs_cblkno;
+ uint32_t fs_iblkno;
+ uint32_t fs_dblkno;
+ uint32_t fs_cgoffset;
+ uint32_t fs_cgmask;
+ uint32_t fs_time;
+ uint32_t fs_size;
+ uint32_t fs_dsize;
+ uint32_t fs_ncg;
+ uint32_t fs_bsize;
+ uint32_t fs_fsize;
+ uint32_t fs_frag;
+ uint32_t fs_minfree;
+ uint32_t fs_rotdelay;
+ uint32_t fs_rps;
+ uint32_t fs_bmask;
+ uint32_t fs_fmask;
+ uint32_t fs_bshift;
+ uint32_t fs_fshift;
+ uint32_t fs_maxcontig;
+ uint32_t fs_maxbpg;
+ uint32_t fs_fragshift;
+ uint32_t fs_fsbtodb;
+ uint32_t fs_sbsize;
+ uint32_t fs_csmask;
+ uint32_t fs_csshift;
+ uint32_t fs_nindir;
+ uint32_t fs_inopb;
+ uint32_t fs_nspf;
+ uint32_t fs_optim;
+ uint32_t fs_npsect_state;
+ uint32_t fs_interleave;
+ uint32_t fs_trackskew;
+ uint32_t fs_id[2];
+ uint32_t fs_csaddr;
+ uint32_t fs_cssize;
+ uint32_t fs_cgsize;
+ uint32_t fs_ntrak;
+ uint32_t fs_nsect;
+ uint32_t fs_spc;
+ uint32_t fs_ncyl;
+ uint32_t fs_cpg;
+ uint32_t fs_ipg;
+ uint32_t fs_fpg;
+ struct ufs_csum {
+ uint32_t cs_ndir;
+ uint32_t cs_nbfree;
+ uint32_t cs_nifree;
+ uint32_t cs_nffree;
+ } fs_cstotal;
+ int8_t fs_fmod;
+ int8_t fs_clean;
+ int8_t fs_ronly;
+ int8_t fs_flags;
+ union {
+ struct {
+ int8_t fs_fsmnt[512];
+ uint32_t fs_cgrotor;
+ uint32_t fs_csp[31];
+ uint32_t fs_maxcluster;
+ uint32_t fs_cpc;
+ uint16_t fs_opostbl[16][8];
+ } fs_u1;
+ struct {
+ int8_t fs_fsmnt[468];
+ uint8_t fs_volname[32];
+ uint64_t fs_swuid;
+ int32_t fs_pad;
+ uint32_t fs_cgrotor;
+ uint32_t fs_ocsp[28];
+ uint32_t fs_contigdirs;
+ uint32_t fs_csp;
+ uint32_t fs_maxcluster;
+ uint32_t fs_active;
+ int32_t fs_old_cpc;
+ int32_t fs_maxbsize;
+ int64_t fs_sparecon64[17];
+ int64_t fs_sblockloc;
+ struct ufs2_csum_total {
+ uint64_t cs_ndir;
+ uint64_t cs_nbfree;
+ uint64_t cs_nifree;
+ uint64_t cs_nffree;
+ uint64_t cs_numclusters;
+ uint64_t cs_spare[3];
+ } fs_cstotal;
+ struct ufs_timeval {
+ int32_t tv_sec;
+ int32_t tv_usec;
+ } fs_time;
+ int64_t fs_size;
+ int64_t fs_dsize;
+ uint64_t fs_csaddr;
+ int64_t fs_pendingblocks;
+ int32_t fs_pendinginodes;
+ } __attribute__((packed)) fs_u2;
+ } fs_u11;
+ union {
+ struct {
+ int32_t fs_sparecon[53];
+ int32_t fs_reclaim;
+ int32_t fs_sparecon2[1];
+ int32_t fs_state;
+ uint32_t fs_qbmask[2];
+ uint32_t fs_qfmask[2];
+ } fs_sun;
+ struct {
+ int32_t fs_sparecon[53];
+ int32_t fs_reclaim;
+ int32_t fs_sparecon2[1];
+ uint32_t fs_npsect;
+ uint32_t fs_qbmask[2];
+ uint32_t fs_qfmask[2];
+ } fs_sunx86;
+ struct {
+ int32_t fs_sparecon[50];
+ int32_t fs_contigsumsize;
+ int32_t fs_maxsymlinklen;
+ int32_t fs_inodefmt;
+ uint32_t fs_maxfilesize[2];
+ uint32_t fs_qbmask[2];
+ uint32_t fs_qfmask[2];
+ int32_t fs_state;
+ } fs_44;
+ } fs_u2;
+ int32_t fs_postblformat;
+ int32_t fs_nrpos;
+ int32_t fs_postbloff;
+ int32_t fs_rotbloff;
+ uint32_t fs_magic;
+ uint8_t fs_space[1];
+} __attribute__((packed));
+
+#define UFS_MAGIC 0x00011954
+#define UFS2_MAGIC 0x19540119
+#define UFS_MAGIC_FEA 0x00195612
+#define UFS_MAGIC_LFN 0x00095014
+#define UFS_MAGIC_SEC 0x00612195
+#define UFS_MAGIC_4GB 0x05231994
+
+static int probe_ufs(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int offsets[] = { 0, 8, 64, 256 };
+ uint32_t mags[] = {
+ UFS2_MAGIC, UFS_MAGIC, UFS_MAGIC_FEA, UFS_MAGIC_LFN,
+ UFS_MAGIC_SEC, UFS_MAGIC_4GB
+ };
+ size_t i;
+ uint32_t magic;
+ struct ufs_super_block *ufs;
+ int is_be;
+
+ for (i = 0; i < ARRAY_SIZE(offsets); i++) {
+ uint32_t magLE, magBE;
+ size_t y;
+
+ ufs = (struct ufs_super_block *)
+ blkid_probe_get_buffer(pr,
+ offsets[i] * 1024,
+ sizeof(struct ufs_super_block));
+ if (!ufs)
+ return errno ? -errno : 1;
+
+ magBE = be32_to_cpu(ufs->fs_magic);
+ magLE = le32_to_cpu(ufs->fs_magic);
+
+ for (y = 0; y < ARRAY_SIZE(mags); y++) {
+ if (magLE == mags[y] || magBE == mags[y]) {
+ magic = mags[y];
+ is_be = (magBE == mags[y]);
+ goto found;
+ }
+ }
+ }
+
+ return 1;
+
+found:
+ if (magic == UFS2_MAGIC) {
+ blkid_probe_set_version(pr, "2");
+ blkid_probe_set_label(pr, ufs->fs_u11.fs_u2.fs_volname,
+ sizeof(ufs->fs_u11.fs_u2.fs_volname));
+ } else
+ blkid_probe_set_version(pr, "1");
+ if (ufs->fs_id[0] || ufs->fs_id[1])
+ {
+ if (is_be)
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &ufs->fs_id,
+ sizeof(ufs->fs_id),
+ "%08x%08x",
+ be32_to_cpu(ufs->fs_id[0]),
+ be32_to_cpu(ufs->fs_id[1]));
+ else
+ blkid_probe_sprintf_uuid(pr,
+ (unsigned char *) &ufs->fs_id,
+ sizeof(ufs->fs_id),
+ "%08x%08x",
+ le32_to_cpu(ufs->fs_id[0]),
+ le32_to_cpu(ufs->fs_id[1]));
+ }
+
+ if (blkid_probe_set_magic(pr,
+ (offsets[i] * 1024) +
+ offsetof(struct ufs_super_block, fs_magic),
+ sizeof(ufs->fs_magic),
+ (unsigned char *) &ufs->fs_magic))
+ return 1;
+
+ if (!is_be)
+ blkid_probe_set_block_size(pr, le32_to_cpu(ufs->fs_fsize));
+ else
+ blkid_probe_set_block_size(pr, be32_to_cpu(ufs->fs_fsize));
+
+ return 0;
+}
+
+/*
+ * According to libvolume_id the UFS superblock could be on four positions.
+ * The original libblkid has checked one position (.kboff=8) only.
+ *
+ * We know four UFS magic strings and UFS could be both little-endian and
+ * big-endian. ... so we have:
+ *
+ * 4 position * 4 string * 2 version = 32 magic strings
+ *
+ * It seems simpler to check for these string in probing function that hardcode
+ * all in the .magic array.
+ */
+const struct blkid_idinfo ufs_idinfo =
+{
+ .name = "ufs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_ufs,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/vdo.c b/libblkid/src/superblocks/vdo.c
new file mode 100644
index 0000000..bec686f
--- /dev/null
+++ b/libblkid/src/superblocks/vdo.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct vdo_super_block {
+ char magic[8]; /* magic number 'dmvdo001'*/
+ char unused[32]; /* 32 bytes of unimportant space */
+ unsigned char sb_uuid[16]; /* vdo unique id */
+
+ /* this is not all... but enough for libblkid */
+} __attribute__((packed));
+
+static int probe_vdo(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct vdo_super_block *vsb;
+
+ vsb = blkid_probe_get_sb(pr, mag, struct vdo_super_block);
+ if (!vsb)
+ return errno ? -errno : 1;
+
+ blkid_probe_set_uuid(pr, vsb->sb_uuid);
+ return 0;
+}
+
+const struct blkid_idinfo vdo_idinfo =
+{
+ .name = "vdo",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_vdo,
+ .magics =
+ {
+ { .magic = "dmvdo001", .len = 8 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/vfat.c b/libblkid/src/superblocks/vfat.c
new file mode 100644
index 0000000..7181348
--- /dev/null
+++ b/libblkid/src/superblocks/vfat.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "pt-mbr.h"
+#include "superblocks.h"
+
+/* Yucky misaligned values */
+struct vfat_super_block {
+/* 00*/ unsigned char vs_ignored[3];
+/* 03*/ unsigned char vs_sysid[8];
+/* 0b*/ unsigned char vs_sector_size[2];
+/* 0d*/ uint8_t vs_cluster_size;
+/* 0e*/ uint16_t vs_reserved;
+/* 10*/ uint8_t vs_fats;
+/* 11*/ unsigned char vs_dir_entries[2];
+/* 13*/ unsigned char vs_sectors[2];
+/* 15*/ unsigned char vs_media;
+/* 16*/ uint16_t vs_fat_length;
+/* 18*/ uint16_t vs_secs_track;
+/* 1a*/ uint16_t vs_heads;
+/* 1c*/ uint32_t vs_hidden;
+/* 20*/ uint32_t vs_total_sect;
+/* 24*/ uint32_t vs_fat32_length;
+/* 28*/ uint16_t vs_flags;
+/* 2a*/ uint8_t vs_version[2];
+/* 2c*/ uint32_t vs_root_cluster;
+/* 30*/ uint16_t vs_fsinfo_sector;
+/* 32*/ uint16_t vs_backup_boot;
+/* 34*/ uint16_t vs_reserved2[6];
+/* 40*/ unsigned char vs_drive_number;
+/* 41*/ unsigned char vs_boot_flags;
+/* 42*/ unsigned char vs_ext_boot_sign; /* 0x28 - without vs_label/vs_magic; 0x29 - with */
+/* 43*/ unsigned char vs_serno[4];
+/* 47*/ unsigned char vs_label[11];
+/* 52*/ unsigned char vs_magic[8];
+/* 5a*/ unsigned char vs_dummy2[0x1fe - 0x5a];
+/*1fe*/ unsigned char vs_pmagic[2];
+} __attribute__((packed));
+
+/* Yucky misaligned values */
+struct msdos_super_block {
+/* DOS 2.0 BPB */
+/* 00*/ unsigned char ms_ignored[3];
+/* 03*/ unsigned char ms_sysid[8];
+/* 0b*/ unsigned char ms_sector_size[2];
+/* 0d*/ uint8_t ms_cluster_size;
+/* 0e*/ uint16_t ms_reserved;
+/* 10*/ uint8_t ms_fats;
+/* 11*/ unsigned char ms_dir_entries[2];
+/* 13*/ unsigned char ms_sectors[2]; /* =0 iff V3 or later */
+/* 15*/ unsigned char ms_media;
+/* 16*/ uint16_t ms_fat_length; /* Sectors per FAT */
+/* DOS 3.0 BPB */
+/* 18*/ uint16_t ms_secs_track;
+/* 1a*/ uint16_t ms_heads;
+/* 1c*/ uint32_t ms_hidden;
+/* DOS 3.31 BPB */
+/* 20*/ uint32_t ms_total_sect; /* iff ms_sectors == 0 */
+/* DOS 3.4 EBPB */
+/* 24*/ unsigned char ms_drive_number;
+/* 25*/ unsigned char ms_boot_flags;
+/* 26*/ unsigned char ms_ext_boot_sign; /* 0x28 - DOS 3.4 EBPB; 0x29 - DOS 4.0 EBPB */
+/* 27*/ unsigned char ms_serno[4];
+/* DOS 4.0 EBPB */
+/* 2b*/ unsigned char ms_label[11];
+/* 36*/ unsigned char ms_magic[8];
+/* padding */
+/* 3e*/ unsigned char ms_dummy2[0x1fe - 0x3e];
+/*1fe*/ unsigned char ms_pmagic[2];
+} __attribute__((packed));
+
+struct vfat_dir_entry {
+ uint8_t name[11];
+ uint8_t attr;
+ uint16_t time_creat;
+ uint16_t date_creat;
+ uint16_t time_acc;
+ uint16_t date_acc;
+ uint16_t cluster_high;
+ uint16_t time_write;
+ uint16_t date_write;
+ uint16_t cluster_low;
+ uint32_t size;
+} __attribute__((packed));
+
+struct fat32_fsinfo {
+ uint8_t signature1[4];
+ uint32_t reserved1[120];
+ uint8_t signature2[4];
+ uint32_t free_clusters;
+ uint32_t next_cluster;
+ uint32_t reserved2[4];
+} __attribute__((packed));
+
+/* maximum number of clusters */
+#define FAT12_MAX 0xFF4
+#define FAT16_MAX 0xFFF4
+#define FAT32_MAX 0x0FFFFFF6
+
+#define FAT_ATTR_VOLUME_ID 0x08
+#define FAT_ATTR_DIR 0x10
+#define FAT_ATTR_LONG_NAME 0x0f
+#define FAT_ATTR_MASK 0x3f
+#define FAT_ENTRY_FREE 0xe5
+
+static const char *no_name = "NO NAME ";
+
+#define unaligned_le16(x) \
+ (((unsigned char *) x)[0] + (((unsigned char *) x)[1] << 8))
+
+/*
+ * Look for LABEL (name) in the FAT root directory.
+ */
+static unsigned char *search_fat_label(blkid_probe pr,
+ uint64_t offset, uint32_t entries)
+{
+ struct vfat_dir_entry *ent, *dir = NULL;
+ uint32_t i;
+
+ DBG(LOWPROBE, ul_debug("\tlook for label in root-dir "
+ "(entries: %"PRIu32", offset: %"PRIu64")", entries, offset));
+
+ if (!blkid_probe_is_tiny(pr)) {
+ /* large disk, read whole root directory */
+ dir = (struct vfat_dir_entry *)
+ blkid_probe_get_buffer(pr,
+ offset,
+ (uint64_t) entries *
+ sizeof(struct vfat_dir_entry));
+ if (!dir)
+ return NULL;
+ }
+
+ for (i = 0; i < entries; i++) {
+ /*
+ * The root directory could be relatively large (4-16kB).
+ * Fortunately, the LABEL is usually the first entry in the
+ * directory. On tiny disks we call read() per entry.
+ */
+ if (!dir)
+ ent = (struct vfat_dir_entry *)
+ blkid_probe_get_buffer(pr,
+ (uint64_t) offset + (i *
+ sizeof(struct vfat_dir_entry)),
+ sizeof(struct vfat_dir_entry));
+ else
+ ent = &dir[i];
+
+ if (!ent || ent->name[0] == 0x00)
+ break;
+
+ if ((ent->name[0] == FAT_ENTRY_FREE) ||
+ (ent->cluster_high != 0 || ent->cluster_low != 0) ||
+ ((ent->attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME))
+ continue;
+
+ if ((ent->attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) ==
+ FAT_ATTR_VOLUME_ID) {
+ DBG(LOWPROBE, ul_debug("\tfound fs LABEL at entry %d", i));
+ if (ent->name[0] == 0x05)
+ ent->name[0] = 0xE5;
+ return ent->name;
+ }
+ }
+ return NULL;
+}
+
+static int fat_valid_superblock(blkid_probe pr,
+ const struct blkid_idmag *mag,
+ struct msdos_super_block *ms,
+ struct vfat_super_block *vs,
+ uint32_t *cluster_count, uint32_t *fat_size)
+{
+ uint16_t sector_size, dir_entries, reserved;
+ uint32_t sect_count, __fat_size, dir_size, __cluster_count, fat_length;
+ uint32_t max_count;
+
+ /* extra check for FATs without magic strings */
+ if (mag->len <= 2) {
+ /* Old floppies have a valid MBR signature */
+ if (ms->ms_pmagic[0] != 0x55 || ms->ms_pmagic[1] != 0xAA)
+ return 0;
+
+ /*
+ * OS/2 and apparently DFSee will place a FAT12/16-like
+ * pseudo-superblock in the first 512 bytes of non-FAT
+ * filesystems --- at least JFS and HPFS, and possibly others.
+ * So we explicitly check for those filesystems at the
+ * FAT12/16 filesystem magic field identifier, and if they are
+ * present, we rule this out as a FAT filesystem, despite the
+ * FAT-like pseudo-header.
+ */
+ if ((memcmp(ms->ms_magic, "JFS ", 8) == 0) ||
+ (memcmp(ms->ms_magic, "HPFS ", 8) == 0)) {
+ DBG(LOWPROBE, ul_debug("\tJFS/HPFS detected"));
+ return 0;
+ }
+ }
+
+ /* fat counts(Linux kernel expects at least 1 FAT table) */
+ if (!ms->ms_fats)
+ return 0;
+ if (!ms->ms_reserved)
+ return 0;
+ if (!(0xf8 <= ms->ms_media || ms->ms_media == 0xf0))
+ return 0;
+ if (!is_power_of_2(ms->ms_cluster_size))
+ return 0;
+
+ sector_size = unaligned_le16(&ms->ms_sector_size);
+ if (!is_power_of_2(sector_size) ||
+ sector_size < 512 || sector_size > 4096)
+ return 0;
+
+ dir_entries = unaligned_le16(&ms->ms_dir_entries);
+ reserved = le16_to_cpu(ms->ms_reserved);
+ sect_count = unaligned_le16(&ms->ms_sectors);
+
+ if (sect_count == 0)
+ sect_count = le32_to_cpu(ms->ms_total_sect);
+
+ fat_length = le16_to_cpu(ms->ms_fat_length);
+ if (fat_length == 0)
+ fat_length = le32_to_cpu(vs->vs_fat32_length);
+
+ __fat_size = fat_length * ms->ms_fats;
+ dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
+ (sector_size-1)) / sector_size;
+
+ __cluster_count = (sect_count - (reserved + __fat_size + dir_size)) /
+ ms->ms_cluster_size;
+ if (!ms->ms_fat_length && vs->vs_fat32_length)
+ max_count = FAT32_MAX;
+ else
+ max_count = __cluster_count > FAT12_MAX ? FAT16_MAX : FAT12_MAX;
+
+ if (__cluster_count > max_count)
+ return 0;
+
+ if (fat_size)
+ *fat_size = __fat_size;
+ if (cluster_count)
+ *cluster_count = __cluster_count;
+
+ if (blkid_probe_is_bitlocker(pr))
+ return 0;
+
+ return 1; /* valid */
+}
+
+/* function prototype to avoid warnings (duplicate in partitions/dos.c) */
+extern int blkid_probe_is_vfat(blkid_probe pr);
+
+/*
+ * This function is used by MBR partition table parser to avoid
+ * misinterpretation of FAT filesystem.
+ */
+int blkid_probe_is_vfat(blkid_probe pr)
+{
+ struct vfat_super_block *vs;
+ struct msdos_super_block *ms;
+ const struct blkid_idmag *mag = NULL;
+ int rc;
+
+ rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag);
+ if (rc < 0)
+ return rc; /* error */
+ if (rc != BLKID_PROBE_OK || !mag)
+ return 0;
+
+ ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
+ if (!ms)
+ return errno ? -errno : 0;
+ vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
+ if (!vs)
+ return errno ? -errno : 0;
+
+ return fat_valid_superblock(pr, mag, ms, vs, NULL, NULL);
+}
+
+/* FAT label extraction from the root directory taken from Kay
+ * Sievers's volume_id library */
+static int probe_vfat(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct vfat_super_block *vs;
+ struct msdos_super_block *ms;
+ const unsigned char *vol_label = NULL;
+ const unsigned char *boot_label = NULL;
+ unsigned char *vol_serno = NULL, vol_label_buf[11];
+ uint16_t sector_size = 0, reserved;
+ uint32_t cluster_count, fat_size;
+ const char *version = NULL;
+
+ ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
+ if (!ms)
+ return errno ? -errno : 1;
+
+ vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
+ if (!vs)
+ return errno ? -errno : 1;
+
+ if (!fat_valid_superblock(pr, mag, ms, vs, &cluster_count, &fat_size))
+ return 1;
+
+ sector_size = unaligned_le16(&ms->ms_sector_size);
+ reserved = le16_to_cpu(ms->ms_reserved);
+
+ if (ms->ms_fat_length) {
+ /* the label may be an attribute in the root directory */
+ uint32_t root_start = (reserved + fat_size) * sector_size;
+ uint32_t root_dir_entries = unaligned_le16(&vs->vs_dir_entries);
+
+ vol_label = search_fat_label(pr, root_start, root_dir_entries);
+ if (vol_label) {
+ memcpy(vol_label_buf, vol_label, 11);
+ vol_label = vol_label_buf;
+ }
+
+ if (ms->ms_ext_boot_sign == 0x29)
+ boot_label = ms->ms_label;
+
+ if (ms->ms_ext_boot_sign == 0x28 || ms->ms_ext_boot_sign == 0x29)
+ vol_serno = ms->ms_serno;
+
+ blkid_probe_set_value(pr, "SEC_TYPE", (unsigned char *) "msdos",
+ sizeof("msdos"));
+
+ if (cluster_count < FAT12_MAX)
+ version = "FAT12";
+ else if (cluster_count < FAT16_MAX)
+ version = "FAT16";
+
+ } else if (vs->vs_fat32_length) {
+ unsigned char *buf;
+ uint16_t fsinfo_sect;
+ int maxloop = 100;
+
+ /* Search the FAT32 root dir for the label attribute */
+ uint32_t buf_size = vs->vs_cluster_size * sector_size;
+ uint32_t start_data_sect = reserved + fat_size;
+ uint32_t entries = ((uint64_t) le32_to_cpu(vs->vs_fat32_length)
+ * sector_size) / sizeof(uint32_t);
+ uint32_t next = le32_to_cpu(vs->vs_root_cluster);
+
+ while (next && next < entries && --maxloop) {
+ uint32_t next_sect_off;
+ uint64_t next_off, fat_entry_off;
+ int count;
+
+ next_sect_off = (next - 2) * vs->vs_cluster_size;
+ next_off = (uint64_t)(start_data_sect + next_sect_off) *
+ sector_size;
+
+ count = buf_size / sizeof(struct vfat_dir_entry);
+
+ vol_label = search_fat_label(pr, next_off, count);
+ if (vol_label) {
+ memcpy(vol_label_buf, vol_label, 11);
+ vol_label = vol_label_buf;
+ break;
+ }
+
+ /* get FAT entry */
+ fat_entry_off = ((uint64_t) reserved * sector_size) +
+ (next * sizeof(uint32_t));
+ buf = blkid_probe_get_buffer(pr, fat_entry_off, buf_size);
+ if (buf == NULL)
+ break;
+
+ /* set next cluster */
+ next = le32_to_cpu(*((uint32_t *) buf)) & 0x0fffffff;
+ }
+
+ version = "FAT32";
+
+ if (vs->vs_ext_boot_sign == 0x29)
+ boot_label = vs->vs_label;
+
+ vol_serno = vs->vs_serno;
+
+ /*
+ * FAT32 should have a valid signature in the fsinfo block,
+ * but also allow all bytes set to '\0', because some volumes
+ * do not set the signature at all.
+ */
+ fsinfo_sect = le16_to_cpu(vs->vs_fsinfo_sector);
+ if (fsinfo_sect) {
+ struct fat32_fsinfo *fsinfo;
+
+ buf = blkid_probe_get_buffer(pr,
+ (uint64_t) fsinfo_sect * sector_size,
+ sizeof(struct fat32_fsinfo));
+ if (buf == NULL)
+ return errno ? -errno : 1;
+
+ fsinfo = (struct fat32_fsinfo *) buf;
+ if (memcmp(fsinfo->signature1, "\x52\x52\x61\x41", 4) != 0 &&
+ memcmp(fsinfo->signature1, "\x52\x52\x64\x41", 4) != 0 &&
+ memcmp(fsinfo->signature1, "\x00\x00\x00\x00", 4) != 0)
+ return 1;
+ if (memcmp(fsinfo->signature2, "\x72\x72\x41\x61", 4) != 0 &&
+ memcmp(fsinfo->signature2, "\x00\x00\x00\x00", 4) != 0)
+ return 1;
+ }
+ }
+
+ if (boot_label && memcmp(boot_label, no_name, 11) != 0)
+ blkid_probe_set_id_label(pr, "LABEL_FATBOOT", boot_label, 11);
+
+ if (vol_label)
+ blkid_probe_set_label(pr, vol_label, 11);
+
+ /* We can't just print them as %04X, because they are unaligned */
+ if (vol_serno)
+ blkid_probe_sprintf_uuid(pr, vol_serno, 4, "%02X%02X-%02X%02X",
+ vol_serno[3], vol_serno[2], vol_serno[1], vol_serno[0]);
+ if (version)
+ blkid_probe_set_version(pr, version);
+
+ blkid_probe_set_block_size(pr, sector_size);
+
+ return 0;
+}
+
+
+const struct blkid_idinfo vfat_idinfo =
+{
+ .name = "vfat",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_vfat,
+ .magics =
+ {
+ { .magic = "MSWIN", .len = 5, .sboff = 0x52 },
+ { .magic = "FAT32 ", .len = 8, .sboff = 0x52 },
+ { .magic = "MSDOS", .len = 5, .sboff = 0x36 },
+ { .magic = "FAT16 ", .len = 8, .sboff = 0x36 },
+ { .magic = "FAT12 ", .len = 8, .sboff = 0x36 },
+ { .magic = "FAT ", .len = 8, .sboff = 0x36 },
+ { .magic = "\353", .len = 1, },
+ { .magic = "\351", .len = 1, },
+ { .magic = "\125\252", .len = 2, .sboff = 0x1fe },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/via_raid.c b/libblkid/src/superblocks/via_raid.c
new file mode 100644
index 0000000..ee3ab65
--- /dev/null
+++ b/libblkid/src/superblocks/via_raid.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ * Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct via_metadata {
+ uint16_t signature;
+ uint8_t version_number;
+ struct via_array {
+ uint16_t disk_bit_mask;
+ uint8_t disk_array_ex;
+ uint32_t capacity_low;
+ uint32_t capacity_high;
+ uint32_t serial_checksum;
+ } __attribute__((packed)) array;
+ uint32_t serial_checksum[8];
+ uint8_t checksum;
+} __attribute__((packed));
+
+#define VIA_SIGNATURE 0xAA55
+
+/* 8 bit checksum on first 50 bytes of metadata. */
+static uint8_t via_checksum(struct via_metadata *v)
+{
+ uint8_t i = 50, cs = 0;
+
+ while (i--)
+ cs += ((uint8_t*) v)[i];
+
+ return cs;
+}
+
+static int probe_viaraid(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ uint64_t off;
+ struct via_metadata *v;
+
+ if (pr->size < 0x10000)
+ return 1;
+ if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+ return 1;
+
+ off = ((pr->size / 0x200)-1) * 0x200;
+
+ v = (struct via_metadata *)
+ blkid_probe_get_buffer(pr,
+ off,
+ sizeof(struct via_metadata));
+ if (!v)
+ return errno ? -errno : 1;
+
+ if (le16_to_cpu(v->signature) != VIA_SIGNATURE)
+ return 1;
+ if (v->version_number > 2)
+ return 1;
+ if (!blkid_probe_verify_csum(pr, via_checksum(v), v->checksum))
+ return 1;
+
+ if (blkid_probe_sprintf_version(pr, "%u", v->version_number) != 0)
+ return 1;
+ if (blkid_probe_set_magic(pr, off,
+ sizeof(v->signature),
+ (unsigned char *) &v->signature))
+ return 1;
+ return 0;
+}
+
+const struct blkid_idinfo viaraid_idinfo = {
+ .name = "via_raid_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_viaraid,
+ .magics = BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/vmfs.c b/libblkid/src/superblocks/vmfs.c
new file mode 100644
index 0000000..fac87ab
--- /dev/null
+++ b/libblkid/src/superblocks/vmfs.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 Mike Hommey <mh@glandium.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "superblocks.h"
+
+struct vmfs_fs_info {
+ uint32_t magic;
+ uint32_t volume_version;
+ uint8_t version;
+ uint8_t uuid[16];
+ uint32_t mode;
+ char label[128];
+} __attribute__ ((__packed__));
+
+struct vmfs_volume_info {
+ uint32_t magic;
+ uint32_t ver;
+ uint8_t irrelevant[122];
+ uint8_t uuid[16];
+} __attribute__ ((__packed__));
+
+static int probe_vmfs_fs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct vmfs_fs_info *header;
+
+ header = blkid_probe_get_sb(pr, mag, struct vmfs_fs_info);
+ if (header == NULL)
+ return errno ? -errno : 1;
+
+ blkid_probe_sprintf_uuid(pr, (unsigned char *) header->uuid, 16,
+ "%02x%02x%02x%02x-%02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x%02x%02x%02x%02x",
+ header->uuid[3], header->uuid[2], header->uuid[1],
+ header->uuid[0], header->uuid[7], header->uuid[6],
+ header->uuid[5], header->uuid[4], header->uuid[9],
+ header->uuid[8], header->uuid[10], header->uuid[11],
+ header->uuid[12], header->uuid[13], header->uuid[14],
+ header->uuid[15]);
+
+ blkid_probe_set_label(pr, (unsigned char *) header->label,
+ sizeof(header->label));
+ blkid_probe_sprintf_version(pr, "%u", header->version);
+ return 0;
+}
+
+static int probe_vmfs_volume(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct vmfs_volume_info *header;
+ unsigned char *lvm_uuid;
+
+ header = blkid_probe_get_sb(pr, mag, struct vmfs_volume_info);
+ if (header == NULL)
+ return errno ? -errno : 1;
+
+ blkid_probe_sprintf_value(pr, "UUID_SUB",
+ "%02x%02x%02x%02x-%02x%02x%02x%02x-"
+ "%02x%02x-%02x%02x%02x%02x%02x%02x",
+ header->uuid[3], header->uuid[2], header->uuid[1],
+ header->uuid[0], header->uuid[7], header->uuid[6],
+ header->uuid[5], header->uuid[4], header->uuid[9],
+ header->uuid[8], header->uuid[10], header->uuid[11],
+ header->uuid[12], header->uuid[13], header->uuid[14],
+ header->uuid[15]);
+ blkid_probe_sprintf_version(pr, "%u", le32_to_cpu(header->ver));
+
+ lvm_uuid = blkid_probe_get_buffer(pr,
+ 1024 * 1024 /* Start of the volume info */
+ + 512 /* Offset to lvm info */
+ + 20 /* Offset in lvm info */, 35);
+ if (lvm_uuid)
+ blkid_probe_strncpy_uuid(pr, lvm_uuid, 35);
+
+ return 0;
+}
+
+const struct blkid_idinfo vmfs_fs_idinfo =
+{
+ .name = "VMFS",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_vmfs_fs,
+ .magics =
+ {
+ { .magic = "\x5e\xf1\xab\x2f", .len = 4, .kboff = 2048 },
+ { NULL }
+ }
+};
+
+const struct blkid_idinfo vmfs_volume_idinfo =
+{
+ .name = "VMFS_volume_member",
+ .usage = BLKID_USAGE_RAID,
+ .probefunc = probe_vmfs_volume,
+ .magics =
+ {
+ { .magic = "\x0d\xd0\x01\xc0", .len = 4, .kboff = 1024 },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/superblocks/vxfs.c b/libblkid/src/superblocks/vxfs.c
new file mode 100644
index 0000000..d9d26ad
--- /dev/null
+++ b/libblkid/src/superblocks/vxfs.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+
+#include "superblocks.h"
+
+struct vxfs_super_block {
+ uint32_t vs_magic;
+ int32_t vs_version;
+ uint32_t vs_ctime;
+ uint32_t vs_cutime;
+ uint32_t __unused1;
+ uint32_t __unused2;
+ uint32_t vs_old_logstart;
+ uint32_t vs_old_logend;
+ uint32_t vs_bsize;
+ uint32_t vs_size;
+ uint32_t vs_dsize;
+};
+
+static int probe_vxfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct vxfs_super_block *vxs;
+
+ vxs = blkid_probe_get_sb(pr, mag, struct vxfs_super_block);
+ if (!vxs)
+ return errno ? -errno : 1;
+
+ if (le32_to_cpu(vxs->vs_magic) == 0xa501fcf5) {
+ blkid_probe_sprintf_version(pr, "%u", (unsigned int)le32_to_cpu(vxs->vs_version));
+ blkid_probe_set_block_size(pr, le32_to_cpu(vxs->vs_bsize));
+ } else if (be32_to_cpu(vxs->vs_magic) == 0xa501fcf5) {
+ blkid_probe_sprintf_version(pr, "%u", (unsigned int)be32_to_cpu(vxs->vs_version));
+ blkid_probe_set_block_size(pr, be32_to_cpu(vxs->vs_bsize));
+ }
+ return 0;
+}
+
+
+const struct blkid_idinfo vxfs_idinfo =
+{
+ .name = "vxfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_vxfs,
+ .magics =
+ {
+ { .magic = "\365\374\001\245", .len = 4, .kboff = 1 },
+ { .magic = "\245\001\374\365", .len = 4, .kboff = 8 },
+ { NULL }
+ }
+};
+
diff --git a/libblkid/src/superblocks/xfs.c b/libblkid/src/superblocks/xfs.c
new file mode 100644
index 0000000..d8c6fb6
--- /dev/null
+++ b/libblkid/src/superblocks/xfs.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2013 Eric Sandeen <sandeen@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct xfs_super_block {
+ uint32_t sb_magicnum; /* magic number == XFS_SB_MAGIC */
+ uint32_t sb_blocksize; /* logical block size, bytes */
+ uint64_t sb_dblocks; /* number of data blocks */
+ uint64_t sb_rblocks; /* number of realtime blocks */
+ uint64_t sb_rextents; /* number of realtime extents */
+ unsigned char sb_uuid[16]; /* file system unique id */
+ uint64_t sb_logstart; /* starting block of log if internal */
+ uint64_t sb_rootino; /* root inode number */
+ uint64_t sb_rbmino; /* bitmap inode for realtime extents */
+ uint64_t sb_rsumino; /* summary inode for rt bitmap */
+ uint32_t sb_rextsize; /* realtime extent size, blocks */
+ uint32_t sb_agblocks; /* size of an allocation group */
+ uint32_t sb_agcount; /* number of allocation groups */
+ uint32_t sb_rbmblocks; /* number of rt bitmap blocks */
+ uint32_t sb_logblocks; /* number of log blocks */
+
+ uint16_t sb_versionnum; /* header version == XFS_SB_VERSION */
+ uint16_t sb_sectsize; /* volume sector size, bytes */
+ uint16_t sb_inodesize; /* inode size, bytes */
+ uint16_t sb_inopblock; /* inodes per block */
+ char sb_fname[12]; /* file system name */
+ uint8_t sb_blocklog; /* log2 of sb_blocksize */
+ uint8_t sb_sectlog; /* log2 of sb_sectsize */
+ uint8_t sb_inodelog; /* log2 of sb_inodesize */
+ uint8_t sb_inopblog; /* log2 of sb_inopblock */
+ uint8_t sb_agblklog; /* log2 of sb_agblocks (rounded up) */
+ uint8_t sb_rextslog; /* log2 of sb_rextents */
+ uint8_t sb_inprogress; /* mkfs is in progress, don't mount */
+ uint8_t sb_imax_pct; /* max % of fs for inode space */
+ /* statistics */
+ uint64_t sb_icount; /* allocated inodes */
+ uint64_t sb_ifree; /* free inodes */
+ uint64_t sb_fdblocks; /* free data blocks */
+ uint64_t sb_frextents; /* free realtime extents */
+
+ /* this is not all... but enough for libblkid */
+
+} __attribute__((packed));
+
+#define XFS_MIN_BLOCKSIZE_LOG 9 /* i.e. 512 bytes */
+#define XFS_MAX_BLOCKSIZE_LOG 16 /* i.e. 65536 bytes */
+#define XFS_MIN_BLOCKSIZE (1 << XFS_MIN_BLOCKSIZE_LOG)
+#define XFS_MAX_BLOCKSIZE (1 << XFS_MAX_BLOCKSIZE_LOG)
+#define XFS_MIN_SECTORSIZE_LOG 9 /* i.e. 512 bytes */
+#define XFS_MAX_SECTORSIZE_LOG 15 /* i.e. 32768 bytes */
+#define XFS_MIN_SECTORSIZE (1 << XFS_MIN_SECTORSIZE_LOG)
+#define XFS_MAX_SECTORSIZE (1 << XFS_MAX_SECTORSIZE_LOG)
+
+#define XFS_DINODE_MIN_LOG 8
+#define XFS_DINODE_MAX_LOG 11
+#define XFS_DINODE_MIN_SIZE (1 << XFS_DINODE_MIN_LOG)
+#define XFS_DINODE_MAX_SIZE (1 << XFS_DINODE_MAX_LOG)
+
+#define XFS_MAX_RTEXTSIZE (1024 * 1024 * 1024) /* 1GB */
+#define XFS_DFL_RTEXTSIZE (64 * 1024) /* 64kB */
+#define XFS_MIN_RTEXTSIZE (4 * 1024) /* 4kB */
+
+#define XFS_MIN_AG_BLOCKS 64
+#define XFS_MAX_DBLOCKS(s) ((uint64_t)(s)->sb_agcount * (s)->sb_agblocks)
+#define XFS_MIN_DBLOCKS(s) ((uint64_t)((s)->sb_agcount - 1) * \
+ (s)->sb_agblocks + XFS_MIN_AG_BLOCKS)
+
+
+static void sb_from_disk(struct xfs_super_block *from,
+ struct xfs_super_block *to)
+{
+
+ to->sb_magicnum = be32_to_cpu(from->sb_magicnum);
+ to->sb_blocksize = be32_to_cpu(from->sb_blocksize);
+ to->sb_dblocks = be64_to_cpu(from->sb_dblocks);
+ to->sb_rblocks = be64_to_cpu(from->sb_rblocks);
+ to->sb_rextents = be64_to_cpu(from->sb_rextents);
+ to->sb_logstart = be64_to_cpu(from->sb_logstart);
+ to->sb_rootino = be64_to_cpu(from->sb_rootino);
+ to->sb_rbmino = be64_to_cpu(from->sb_rbmino);
+ to->sb_rsumino = be64_to_cpu(from->sb_rsumino);
+ to->sb_rextsize = be32_to_cpu(from->sb_rextsize);
+ to->sb_agblocks = be32_to_cpu(from->sb_agblocks);
+ to->sb_agcount = be32_to_cpu(from->sb_agcount);
+ to->sb_rbmblocks = be32_to_cpu(from->sb_rbmblocks);
+ to->sb_logblocks = be32_to_cpu(from->sb_logblocks);
+ to->sb_versionnum = be16_to_cpu(from->sb_versionnum);
+ to->sb_sectsize = be16_to_cpu(from->sb_sectsize);
+ to->sb_inodesize = be16_to_cpu(from->sb_inodesize);
+ to->sb_inopblock = be16_to_cpu(from->sb_inopblock);
+ to->sb_blocklog = from->sb_blocklog;
+ to->sb_sectlog = from->sb_sectlog;
+ to->sb_inodelog = from->sb_inodelog;
+ to->sb_inopblog = from->sb_inopblog;
+ to->sb_agblklog = from->sb_agblklog;
+ to->sb_rextslog = from->sb_rextslog;
+ to->sb_inprogress = from->sb_inprogress;
+ to->sb_imax_pct = from->sb_imax_pct;
+ to->sb_icount = be64_to_cpu(from->sb_icount);
+ to->sb_ifree = be64_to_cpu(from->sb_ifree);
+ to->sb_fdblocks = be64_to_cpu(from->sb_fdblocks);
+ to->sb_frextents = be64_to_cpu(from->sb_frextents);
+}
+
+static int xfs_verify_sb(struct xfs_super_block *ondisk)
+{
+ struct xfs_super_block sb, *sbp = &sb;
+
+ /* beXX_to_cpu(), but don't convert UUID and fsname! */
+ sb_from_disk(ondisk, sbp);
+
+ /* sanity checks, we don't want to rely on magic string only */
+ if (sbp->sb_agcount <= 0 ||
+ sbp->sb_sectsize < XFS_MIN_SECTORSIZE ||
+ sbp->sb_sectsize > XFS_MAX_SECTORSIZE ||
+ sbp->sb_sectlog < XFS_MIN_SECTORSIZE_LOG ||
+ sbp->sb_sectlog > XFS_MAX_SECTORSIZE_LOG ||
+ sbp->sb_sectsize != (1 << sbp->sb_sectlog) ||
+ sbp->sb_blocksize < XFS_MIN_BLOCKSIZE ||
+ sbp->sb_blocksize > XFS_MAX_BLOCKSIZE ||
+ sbp->sb_blocklog < XFS_MIN_BLOCKSIZE_LOG ||
+ sbp->sb_blocklog > XFS_MAX_BLOCKSIZE_LOG ||
+ sbp->sb_blocksize != (1ULL << sbp->sb_blocklog) ||
+ sbp->sb_inodesize < XFS_DINODE_MIN_SIZE ||
+ sbp->sb_inodesize > XFS_DINODE_MAX_SIZE ||
+ sbp->sb_inodelog < XFS_DINODE_MIN_LOG ||
+ sbp->sb_inodelog > XFS_DINODE_MAX_LOG ||
+ sbp->sb_inodesize != (1 << sbp->sb_inodelog) ||
+ (sbp->sb_blocklog - sbp->sb_inodelog != sbp->sb_inopblog) ||
+ (sbp->sb_rextsize * sbp->sb_blocksize > XFS_MAX_RTEXTSIZE) ||
+ (sbp->sb_rextsize * sbp->sb_blocksize < XFS_MIN_RTEXTSIZE) ||
+ (sbp->sb_imax_pct > 100 /* zero sb_imax_pct is valid */) ||
+ sbp->sb_dblocks == 0 ||
+ sbp->sb_dblocks > XFS_MAX_DBLOCKS(sbp) ||
+ sbp->sb_dblocks < XFS_MIN_DBLOCKS(sbp))
+ return 0;
+
+ /* TODO: version 5 has also checksum CRC32, maybe we can check it too */
+
+ return 1;
+}
+
+static int probe_xfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+ struct xfs_super_block *xs;
+
+ xs = blkid_probe_get_sb(pr, mag, struct xfs_super_block);
+ if (!xs)
+ return errno ? -errno : 1;
+
+ if (!xfs_verify_sb(xs))
+ return 1;
+
+ if (*xs->sb_fname != '\0')
+ blkid_probe_set_label(pr, (unsigned char *) xs->sb_fname,
+ sizeof(xs->sb_fname));
+ blkid_probe_set_uuid(pr, xs->sb_uuid);
+ blkid_probe_set_block_size(pr, be16_to_cpu(xs->sb_sectsize));
+ return 0;
+}
+
+const struct blkid_idinfo xfs_idinfo =
+{
+ .name = "xfs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_xfs,
+ .magics =
+ {
+ { .magic = "XFSB", .len = 4 },
+ { NULL }
+ }
+};
+
+struct xlog_rec_header {
+ uint32_t h_magicno;
+ uint32_t h_dummy1[1];
+ uint32_t h_version;
+ uint32_t h_len;
+ uint32_t h_dummy2[71];
+ uint32_t h_fmt;
+ unsigned char h_uuid[16];
+} __attribute__((packed));
+
+#define XLOG_HEADER_MAGIC_NUM 0xFEEDbabe
+
+/*
+ * For very small filesystems, the minimum log size
+ * can be smaller, but that seems vanishingly unlikely
+ * when used with an external log (which is used for
+ * performance reasons; tiny conflicts with that goal).
+ */
+#define XFS_MIN_LOG_BYTES (10 * 1024 * 1024)
+
+#define XLOG_FMT_LINUX_LE 1
+#define XLOG_FMT_LINUX_BE 2
+#define XLOG_FMT_IRIX_BE 3
+
+#define XLOG_VERSION_1 1
+#define XLOG_VERSION_2 2 /* Large IClogs, Log sunit */
+#define XLOG_VERSION_OKBITS (XLOG_VERSION_1 | XLOG_VERSION_2)
+
+static int xlog_valid_rec_header(struct xlog_rec_header *rhead)
+{
+ uint32_t hlen;
+
+ if (rhead->h_magicno != cpu_to_be32(XLOG_HEADER_MAGIC_NUM))
+ return 0;
+
+ if (!rhead->h_version ||
+ (be32_to_cpu(rhead->h_version) & (~XLOG_VERSION_OKBITS)))
+ return 0;
+
+ /* LR body must have data or it wouldn't have been written */
+ hlen = be32_to_cpu(rhead->h_len);
+ if (hlen <= 0 || hlen > INT_MAX)
+ return 0;
+
+ if (rhead->h_fmt != cpu_to_be32(XLOG_FMT_LINUX_LE) &&
+ rhead->h_fmt != cpu_to_be32(XLOG_FMT_LINUX_BE) &&
+ rhead->h_fmt != cpu_to_be32(XLOG_FMT_IRIX_BE))
+ return 0;
+
+ return 1;
+}
+
+/* xlog record header will be in some sector in the first 256k */
+static int probe_xfs_log(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int i;
+ struct xlog_rec_header *rhead;
+ unsigned char *buf;
+
+ buf = blkid_probe_get_buffer(pr, 0, 256*1024);
+ if (!buf)
+ return errno ? -errno : 1;
+
+ /* check the first 512 512-byte sectors */
+ for (i = 0; i < 512; i++) {
+ /* this is regular XFS (maybe with some sectors shift), ignore */
+ if (memcmp(&buf[i*512], "XFSB", 4) == 0)
+ return 1;
+
+ rhead = (struct xlog_rec_header *)&buf[i*512];
+
+ if (xlog_valid_rec_header(rhead)) {
+ blkid_probe_set_uuid_as(pr, rhead->h_uuid, "LOGUUID");
+
+ if (blkid_probe_set_magic(pr, i * 512,
+ sizeof(rhead->h_magicno),
+ (unsigned char *) &rhead->h_magicno))
+ return 1;
+
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+const struct blkid_idinfo xfs_log_idinfo =
+{
+ .name = "xfs_external_log",
+ .usage = BLKID_USAGE_OTHER,
+ .probefunc = probe_xfs_log,
+ .magics = BLKID_NONE_MAGIC,
+ .minsz = XFS_MIN_LOG_BYTES,
+};
diff --git a/libblkid/src/superblocks/zfs.c b/libblkid/src/superblocks/zfs.c
new file mode 100644
index 0000000..774a199
--- /dev/null
+++ b/libblkid/src/superblocks/zfs.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2009-2010 by Andreas Dilger <adilger@sun.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include "superblocks.h"
+
+#define VDEV_LABEL_UBERBLOCK (128 * 1024ULL)
+#define VDEV_LABEL_NVPAIR ( 16 * 1024ULL)
+#define VDEV_LABEL_SIZE (256 * 1024ULL)
+#define UBERBLOCK_SIZE 1024ULL
+#define UBERBLOCKS_COUNT 128
+
+/* #include <sys/uberblock_impl.h> */
+#define UBERBLOCK_MAGIC 0x00bab10c /* oo-ba-bloc! */
+struct zfs_uberblock {
+ uint64_t ub_magic; /* UBERBLOCK_MAGIC */
+ uint64_t ub_version; /* SPA_VERSION */
+ uint64_t ub_txg; /* txg of last sync */
+ uint64_t ub_guid_sum; /* sum of all vdev guids */
+ uint64_t ub_timestamp; /* UTC time of last sync */
+ char ub_rootbp; /* MOS objset_phys_t */
+} __attribute__((packed));
+
+#define ZFS_WANT 4
+
+#define DATA_TYPE_UINT64 8
+#define DATA_TYPE_STRING 9
+#define DATA_TYPE_DIRECTORY 19
+
+struct nvpair {
+ uint32_t nvp_size;
+ uint32_t nvp_unkown;
+ uint32_t nvp_namelen;
+ char nvp_name[0]; /* aligned to 4 bytes */
+ /* aligned ptr array for string arrays */
+ /* aligned array of data for value */
+};
+
+struct nvstring {
+ uint32_t nvs_type;
+ uint32_t nvs_elem;
+ uint32_t nvs_strlen;
+ unsigned char nvs_string[0];
+};
+
+struct nvuint64 {
+ uint32_t nvu_type;
+ uint32_t nvu_elem;
+ uint64_t nvu_value;
+} __attribute__((packed));
+
+struct nvdirectory {
+ uint32_t nvd_type;
+ uint32_t nvd_unknown[3];
+};
+
+struct nvlist {
+ uint32_t nvl_unknown[3];
+ struct nvpair nvl_nvpair;
+};
+
+static void zfs_process_value(blkid_probe pr, char *name, size_t namelen,
+ void *value, size_t max_value_size, unsigned directory_level)
+{
+ if (strncmp(name, "name", namelen) == 0 &&
+ sizeof(struct nvstring) <= max_value_size &&
+ !directory_level) {
+ struct nvstring *nvs = value;
+ uint32_t nvs_type = be32_to_cpu(nvs->nvs_type);
+ uint32_t nvs_strlen = be32_to_cpu(nvs->nvs_strlen);
+
+ if (nvs_type != DATA_TYPE_STRING ||
+ (uint64_t)nvs_strlen + sizeof(*nvs) > max_value_size)
+ return;
+
+ DBG(LOWPROBE, ul_debug("nvstring: type %u string %*s\n",
+ nvs_type, nvs_strlen, nvs->nvs_string));
+
+ blkid_probe_set_label(pr, nvs->nvs_string, nvs_strlen);
+ } else if (strncmp(name, "guid", namelen) == 0 &&
+ sizeof(struct nvuint64) <= max_value_size &&
+ !directory_level) {
+ struct nvuint64 *nvu = value;
+ uint32_t nvu_type = be32_to_cpu(nvu->nvu_type);
+ uint64_t nvu_value;
+
+ memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
+ nvu_value = be64_to_cpu(nvu_value);
+
+ if (nvu_type != DATA_TYPE_UINT64)
+ return;
+
+ DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n",
+ nvu_type, nvu_value));
+
+ blkid_probe_sprintf_value(pr, "UUID_SUB",
+ "%"PRIu64, nvu_value);
+ } else if (strncmp(name, "pool_guid", namelen) == 0 &&
+ sizeof(struct nvuint64) <= max_value_size &&
+ !directory_level) {
+ struct nvuint64 *nvu = value;
+ uint32_t nvu_type = be32_to_cpu(nvu->nvu_type);
+ uint64_t nvu_value;
+
+ memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
+ nvu_value = be64_to_cpu(nvu_value);
+
+ if (nvu_type != DATA_TYPE_UINT64)
+ return;
+
+ DBG(LOWPROBE, ul_debug("nvuint64: type %u value %"PRIu64"\n",
+ nvu_type, nvu_value));
+
+ blkid_probe_sprintf_uuid(pr, (unsigned char *) &nvu_value,
+ sizeof(nvu_value),
+ "%"PRIu64, nvu_value);
+ } else if (strncmp(name, "ashift", namelen) == 0 &&
+ sizeof(struct nvuint64) <= max_value_size) {
+ struct nvuint64 *nvu = value;
+ uint32_t nvu_type = be32_to_cpu(nvu->nvu_type);
+ uint64_t nvu_value;
+
+ memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
+ nvu_value = be64_to_cpu(nvu_value);
+
+ if (nvu_type != DATA_TYPE_UINT64)
+ return;
+
+ if (nvu_value < 32)
+ blkid_probe_set_block_size(pr, 1U << nvu_value);
+ }
+}
+
+static void zfs_extract_guid_name(blkid_probe pr, loff_t offset)
+{
+ unsigned char *p;
+ struct nvlist *nvl;
+ struct nvpair *nvp;
+ size_t left = 4096;
+ unsigned directory_level = 0;
+
+ offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR;
+
+ /* Note that we currently assume that the desired fields are within
+ * the first 4k (left) of the nvlist. This is true for all pools
+ * I've seen, and simplifies this code somewhat, because we don't
+ * have to handle an nvpair crossing a buffer boundary. */
+ p = blkid_probe_get_buffer(pr, offset, left);
+ if (!p)
+ return;
+
+ DBG(LOWPROBE, ul_debug("zfs_extract: nvlist offset %jd\n",
+ (intmax_t)offset));
+
+ nvl = (struct nvlist *) p;
+ nvp = &nvl->nvl_nvpair;
+ left -= (unsigned char *)nvp - p; /* Already used up 12 bytes */
+
+ while (left > sizeof(*nvp)) {
+ uint32_t nvp_size = be32_to_cpu(nvp->nvp_size);
+ uint32_t nvp_namelen = be32_to_cpu(nvp->nvp_namelen);
+ uint64_t namesize = ((uint64_t)nvp_namelen + 3) & ~3;
+ size_t max_value_size;
+ void *value;
+
+ if (!nvp->nvp_size) {
+ if (!directory_level)
+ break;
+ directory_level--;
+ nvp_size = 8;
+ goto cont;
+ }
+
+ DBG(LOWPROBE, ul_debug("left %zd nvp_size %u\n",
+ left, nvp_size));
+
+ /* nvpair fits in buffer and name fits in nvpair? */
+ if (nvp_size > left || namesize + sizeof(*nvp) > nvp_size)
+ break;
+
+ DBG(LOWPROBE,
+ ul_debug("nvlist: size %u, namelen %u, name %*s\n",
+ nvp_size, nvp_namelen, nvp_namelen,
+ nvp->nvp_name));
+
+ max_value_size = nvp_size - (namesize + sizeof(*nvp));
+ value = nvp->nvp_name + namesize;
+
+ if (sizeof(struct nvdirectory) <= max_value_size) {
+ struct nvdirectory *nvu = value;
+ if (be32_to_cpu(nvu->nvd_type) == DATA_TYPE_DIRECTORY) {
+ nvp_size = sizeof(*nvp) + namesize + sizeof(*nvu);
+ directory_level++;
+ goto cont;
+ }
+ }
+
+ zfs_process_value(pr, nvp->nvp_name, nvp_namelen,
+ value, max_value_size, directory_level);
+
+cont:
+ if (nvp_size > left)
+ break;
+ left -= nvp_size;
+
+ nvp = (struct nvpair *)((char *)nvp + nvp_size);
+ }
+}
+
+static int find_uberblocks(const void *label, loff_t *ub_offset, int *swap_endian)
+{
+ uint64_t swab_magic = swab64((uint64_t)UBERBLOCK_MAGIC);
+ const struct zfs_uberblock *ub;
+ int i, found = 0;
+ loff_t offset = VDEV_LABEL_UBERBLOCK;
+
+ for (i = 0; i < UBERBLOCKS_COUNT; i++, offset += UBERBLOCK_SIZE) {
+ ub = (const struct zfs_uberblock *)((const char *) label + offset);
+
+ if (ub->ub_magic == UBERBLOCK_MAGIC) {
+ *ub_offset = offset;
+ *swap_endian = 0;
+ found++;
+ DBG(LOWPROBE, ul_debug("probe_zfs: found little-endian uberblock at %jd\n", (intmax_t)offset >> 10));
+ }
+
+ if (ub->ub_magic == swab_magic) {
+ *ub_offset = offset;
+ *swap_endian = 1;
+ found++;
+ DBG(LOWPROBE, ul_debug("probe_zfs: found big-endian uberblock at %jd\n", (intmax_t)offset >> 10));
+ }
+ }
+
+ return found;
+}
+
+/* ZFS has 128x1kB host-endian root blocks, stored in 2 areas at the start
+ * of the disk, and 2 areas at the end of the disk. Check only some of them...
+ * #4 (@ 132kB) is the first one written on a new filesystem. */
+static int probe_zfs(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int swab_endian = 0;
+ struct zfs_uberblock *ub = NULL;
+ loff_t offset = 0, ub_offset = 0;
+ int label_no, found = 0, found_in_label;
+ void *label;
+ loff_t blk_align = (pr->size % (256 * 1024ULL));
+
+ DBG(PROBE, ul_debug("probe_zfs\n"));
+ /* Look for at least 4 uberblocks to ensure a positive match */
+ for (label_no = 0; label_no < 4; label_no++) {
+ switch(label_no) {
+ case 0: // jump to L0
+ offset = 0;
+ break;
+ case 1: // jump to L1
+ offset = VDEV_LABEL_SIZE;
+ break;
+ case 2: // jump to L2
+ offset = pr->size - 2 * VDEV_LABEL_SIZE - blk_align;
+ break;
+ case 3: // jump to L3
+ offset = pr->size - VDEV_LABEL_SIZE - blk_align;
+ break;
+ }
+
+ if ((S_ISREG(pr->mode) || blkid_probe_is_wholedisk(pr)) &&
+ blkid_probe_is_covered_by_pt(pr, offset, VDEV_LABEL_SIZE))
+ /* ignore this area, it's within any partition and
+ * we are working with whole-disk now */
+ continue;
+
+ label = blkid_probe_get_buffer(pr, offset, VDEV_LABEL_SIZE);
+ if (label == NULL)
+ return errno ? -errno : 1;
+
+ found_in_label = find_uberblocks(label, &ub_offset, &swab_endian);
+
+ if (found_in_label > 0) {
+ found+= found_in_label;
+ ub = (struct zfs_uberblock *)((char *) label + ub_offset);
+ ub_offset += offset;
+
+ if (found >= ZFS_WANT)
+ break;
+ }
+ }
+
+ if (found < ZFS_WANT)
+ return 1;
+
+ /* If we found the 4th uberblock, then we will have exited from the
+ * scanning loop immediately, and ub will be a valid uberblock. */
+ blkid_probe_sprintf_version(pr, "%" PRIu64, swab_endian ?
+ swab64(ub->ub_version) : ub->ub_version);
+
+ zfs_extract_guid_name(pr, offset);
+
+ if (blkid_probe_set_magic(pr, ub_offset,
+ sizeof(ub->ub_magic),
+ (unsigned char *) &ub->ub_magic))
+ return 1;
+
+ return 0;
+}
+
+const struct blkid_idinfo zfs_idinfo =
+{
+ .name = "zfs_member",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_zfs,
+ .minsz = 64 * 1024 * 1024,
+ .magics = BLKID_NONE_MAGIC
+};
diff --git a/libblkid/src/superblocks/zonefs.c b/libblkid/src/superblocks/zonefs.c
new file mode 100644
index 0000000..4c826ec
--- /dev/null
+++ b/libblkid/src/superblocks/zonefs.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+
+#define ZONEFS_MAGIC "SFOZ" /* 0x5a4f4653 'Z' 'O' 'F' 'S' */
+#define ZONEFS_MAGIC_SIZE 4
+#define ZONEFS_MAGIC_OFST 0
+#define ZONEFS_UUID_SIZE 16
+#define ZONEFS_LABEL_SIZE 32
+#define ZONEFS_SB_OFST 0
+
+#define ZONEFS_BLOCK_SIZE 4096U
+
+/* All in little-endian */
+struct zonefs_super {
+
+ /* Magic number */
+ int32_t s_magic;
+
+ /* Checksum */
+ int32_t s_crc;
+
+ /* Volume label */
+ char s_label[ZONEFS_LABEL_SIZE];
+
+ /* 128-bit uuid */
+ uint8_t s_uuid[ZONEFS_UUID_SIZE];
+
+ /* Features */
+ int64_t s_features;
+
+ /* UID/GID to use for files */
+ int32_t s_uid;
+ int32_t s_gid;
+
+ /* File permissions */
+ int32_t s_perm;
+
+ /* Padding to 4096 bytes */
+ /* uint8_t s_reserved[4020]; */
+
+} __attribute__ ((packed));
+
+static int probe_zonefs(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct zonefs_super *sb;
+
+ sb = (struct zonefs_super *)
+ blkid_probe_get_buffer(pr, ZONEFS_SB_OFST,
+ sizeof(struct zonefs_super));
+ if (!sb)
+ return errno ? -errno : 1;
+
+ if (sb->s_label[0])
+ blkid_probe_set_label(pr, (unsigned char *) sb->s_label,
+ sizeof(sb->s_label));
+
+ blkid_probe_set_uuid(pr, sb->s_uuid);
+ blkid_probe_set_block_size(pr, ZONEFS_BLOCK_SIZE);
+
+ return 0;
+}
+
+const struct blkid_idinfo zonefs_idinfo =
+{
+ .name = "zonefs",
+ .usage = BLKID_USAGE_FILESYSTEM,
+ .probefunc = probe_zonefs,
+ .magics =
+ {
+ {
+ .magic = (char *)ZONEFS_MAGIC,
+ .len = ZONEFS_MAGIC_SIZE,
+ .kboff = ZONEFS_SB_OFST,
+ .sboff = ZONEFS_MAGIC_OFST,
+ },
+ { NULL }
+ }
+};
diff --git a/libblkid/src/tag.c b/libblkid/src/tag.c
new file mode 100644
index 0000000..1783365
--- /dev/null
+++ b/libblkid/src/tag.c
@@ -0,0 +1,468 @@
+/*
+ * tag.c - allocation/initialization/free routines for tag structs
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blkidP.h"
+
+static blkid_tag blkid_new_tag(void)
+{
+ blkid_tag tag;
+
+ if (!(tag = calloc(1, sizeof(struct blkid_struct_tag))))
+ return NULL;
+
+ DBG(TAG, ul_debugobj(tag, "alloc"));
+ INIT_LIST_HEAD(&tag->bit_tags);
+ INIT_LIST_HEAD(&tag->bit_names);
+
+ return tag;
+}
+
+void blkid_free_tag(blkid_tag tag)
+{
+ if (!tag)
+ return;
+
+ DBG(TAG, ul_debugobj(tag, "freeing tag %s (%s)", tag->bit_name, tag->bit_val));
+
+ list_del(&tag->bit_tags); /* list of tags for this device */
+ list_del(&tag->bit_names); /* list of tags with this type */
+
+ free(tag->bit_name);
+ free(tag->bit_val);
+
+ free(tag);
+}
+
+/*
+ * Find the desired tag on a device. If value is NULL, then the
+ * first such tag is returned, otherwise return only exact tag if found.
+ */
+blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type)
+{
+ struct list_head *p;
+
+ list_for_each(p, &dev->bid_tags) {
+ blkid_tag tmp = list_entry(p, struct blkid_struct_tag,
+ bit_tags);
+
+ if (!strcmp(tmp->bit_name, type))
+ return tmp;
+ }
+ return NULL;
+}
+
+int blkid_dev_has_tag(blkid_dev dev, const char *type,
+ const char *value)
+{
+ blkid_tag tag;
+
+ tag = blkid_find_tag_dev(dev, type);
+ if (!value)
+ return (tag != NULL);
+ if (!tag || strcmp(tag->bit_val, value) != 0)
+ return 0;
+ return 1;
+}
+
+/*
+ * Find the desired tag type in the cache.
+ * We return the head tag for this tag type.
+ */
+static blkid_tag blkid_find_head_cache(blkid_cache cache, const char *type)
+{
+ blkid_tag head = NULL, tmp;
+ struct list_head *p;
+
+ if (!cache || !type)
+ return NULL;
+
+ list_for_each(p, &cache->bic_tags) {
+ tmp = list_entry(p, struct blkid_struct_tag, bit_tags);
+ if (!strcmp(tmp->bit_name, type)) {
+ DBG(TAG, ul_debug("found cache tag head %s", type));
+ head = tmp;
+ break;
+ }
+ }
+ return head;
+}
+
+/*
+ * Set a tag on an existing device.
+ *
+ * If value is NULL, then delete the tags from the device.
+ */
+int blkid_set_tag(blkid_dev dev, const char *name,
+ const char *value, const int vlength)
+{
+ blkid_tag t = NULL, head = NULL;
+ char *val = NULL;
+ char **dev_var = NULL;
+
+ if (value && !(val = strndup(value, vlength)))
+ return -BLKID_ERR_MEM;
+
+ /*
+ * Certain common tags are linked directly to the device struct
+ * We need to know what they are before we do anything else because
+ * the function name parameter might get freed later on.
+ */
+ if (!strcmp(name, "TYPE"))
+ dev_var = &dev->bid_type;
+ else if (!strcmp(name, "LABEL"))
+ dev_var = &dev->bid_label;
+ else if (!strcmp(name, "UUID"))
+ dev_var = &dev->bid_uuid;
+
+ t = blkid_find_tag_dev(dev, name);
+ if (!value) {
+ if (t)
+ blkid_free_tag(t);
+ } else if (t) {
+ if (!strcmp(t->bit_val, val)) {
+ /* Same thing, exit */
+ free(val);
+ return 0;
+ }
+ DBG(TAG, ul_debugobj(t, "update (%s) '%s' -> '%s'", t->bit_name, t->bit_val, val));
+ free(t->bit_val);
+ t->bit_val = val;
+ } else {
+ /* Existing tag not present, add to device */
+ if (!(t = blkid_new_tag()))
+ goto errout;
+ t->bit_name = strdup(name);
+ t->bit_val = val;
+ t->bit_dev = dev;
+
+ DBG(TAG, ul_debugobj(t, "setting (%s) '%s'", t->bit_name, t->bit_val));
+ list_add_tail(&t->bit_tags, &dev->bid_tags);
+
+ if (dev->bid_cache) {
+ head = blkid_find_head_cache(dev->bid_cache,
+ t->bit_name);
+ if (!head) {
+ head = blkid_new_tag();
+ if (!head)
+ goto errout;
+
+ DBG(TAG, ul_debugobj(head, "creating new cache tag head %s", name));
+ head->bit_name = strdup(name);
+ if (!head->bit_name)
+ goto errout;
+ list_add_tail(&head->bit_tags,
+ &dev->bid_cache->bic_tags);
+ }
+ list_add_tail(&t->bit_names, &head->bit_names);
+ }
+ }
+
+ /* Link common tags directly to the device struct */
+ if (dev_var)
+ *dev_var = val;
+
+ if (dev->bid_cache)
+ dev->bid_cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+ return 0;
+
+errout:
+ if (t)
+ blkid_free_tag(t);
+ else
+ free(val);
+ if (head)
+ blkid_free_tag(head);
+ return -BLKID_ERR_MEM;
+}
+
+
+/*
+ * Parse a "NAME=value" string. This is slightly different than
+ * parse_token, because that will end an unquoted value at a space, while
+ * this will assume that an unquoted value is the rest of the token (e.g.
+ * if we are passed an already quoted string from the command-line we don't
+ * have to both quote and escape quote so that the quotes make it to
+ * us).
+ *
+ * Returns 0 on success, and -1 on failure.
+ */
+int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val)
+{
+ char *name, *value, *cp;
+
+ DBG(TAG, ul_debug("trying to parse '%s' as a tag", token));
+
+ if (!token || !(cp = strchr(token, '=')))
+ return -1;
+
+ name = strdup(token);
+ if (!name)
+ return -1;
+ value = name + (cp - token);
+ *value++ = '\0';
+ if (*value == '"' || *value == '\'') {
+ char c = *value++;
+ if (!(cp = strrchr(value, c)))
+ goto errout; /* missing closing quote */
+ *cp = '\0';
+ }
+
+ if (ret_val) {
+ value = *value ? strdup(value) : NULL;
+ if (!value)
+ goto errout;
+ *ret_val = value;
+ }
+
+ if (ret_type)
+ *ret_type = name;
+ else
+ free(name);
+
+ return 0;
+
+errout:
+ DBG(TAG, ul_debug("parse error: '%s'", token));
+ free(name);
+ return -1;
+}
+
+/*
+ * Tag iteration routines for the public libblkid interface.
+ *
+ * These routines do not expose the list.h implementation, which are a
+ * contamination of the namespace, and which force us to reveal far, far
+ * too much of our internal implementation. I'm not convinced I want
+ * to keep list.h in the long term, anyway. It's fine for kernel
+ * programming, but performance is not the #1 priority for this
+ * library, and I really don't like the trade-off of type-safety for
+ * performance for this application. [tytso:20030125.2007EST]
+ */
+
+/*
+ * This series of functions iterate over all tags in a device
+ */
+#define TAG_ITERATE_MAGIC 0x01a5284c
+
+struct blkid_struct_tag_iterate {
+ int magic;
+ blkid_dev dev;
+ struct list_head *p;
+};
+
+blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev)
+{
+ blkid_tag_iterate iter;
+
+ if (!dev) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ iter = malloc(sizeof(struct blkid_struct_tag_iterate));
+ if (iter) {
+ iter->magic = TAG_ITERATE_MAGIC;
+ iter->dev = dev;
+ iter->p = dev->bid_tags.next;
+ }
+ return (iter);
+}
+
+/*
+ * Return 0 on success, -1 on error
+ */
+int blkid_tag_next(blkid_tag_iterate iter,
+ const char **type, const char **value)
+{
+ blkid_tag tag;
+
+ if (!type || !value ||
+ !iter || iter->magic != TAG_ITERATE_MAGIC ||
+ iter->p == &iter->dev->bid_tags)
+ return -1;
+
+ *type = NULL;
+ *value = NULL;
+ tag = list_entry(iter->p, struct blkid_struct_tag, bit_tags);
+ *type = tag->bit_name;
+ *value = tag->bit_val;
+ iter->p = iter->p->next;
+ return 0;
+}
+
+void blkid_tag_iterate_end(blkid_tag_iterate iter)
+{
+ if (!iter || iter->magic != TAG_ITERATE_MAGIC)
+ return;
+ iter->magic = 0;
+ free(iter);
+}
+
+/*
+ * This function returns a device which matches a particular
+ * type/value pair. If there is more than one device that matches the
+ * search specification, it returns the one with the highest priority
+ * value. This allows us to give preference to EVMS or LVM devices.
+ */
+blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
+ const char *type,
+ const char *value)
+{
+ blkid_tag head;
+ blkid_dev dev;
+ int pri;
+ struct list_head *p;
+ int probe_new = 0, probe_all = 0;
+
+ if (!cache || !type || !value)
+ return NULL;
+
+ blkid_read_cache(cache);
+
+ DBG(TAG, ul_debug("looking for tag %s=%s in cache", type, value));
+
+try_again:
+ pri = -1;
+ dev = NULL;
+ head = blkid_find_head_cache(cache, type);
+
+ if (head) {
+ list_for_each(p, &head->bit_names) {
+ blkid_tag tmp = list_entry(p, struct blkid_struct_tag,
+ bit_names);
+
+ if (!strcmp(tmp->bit_val, value) &&
+ (tmp->bit_dev->bid_pri > pri) &&
+ !access(tmp->bit_dev->bid_name, F_OK)) {
+ dev = tmp->bit_dev;
+ pri = dev->bid_pri;
+ }
+ }
+ }
+ if (dev && !(dev->bid_flags & BLKID_BID_FL_VERIFIED)) {
+ dev = blkid_verify(cache, dev);
+ if (!dev || dev->bid_flags & BLKID_BID_FL_VERIFIED)
+ goto try_again;
+ }
+
+ if (!dev && !probe_new) {
+ if (blkid_probe_all_new(cache) < 0)
+ return NULL;
+ probe_new++;
+ goto try_again;
+ }
+
+ if (!dev && !probe_all
+ && !(cache->bic_flags & BLKID_BIC_FL_PROBED)) {
+ if (blkid_probe_all(cache) < 0)
+ return NULL;
+ probe_all++;
+ goto try_again;
+ }
+ return dev;
+}
+
+#ifdef TEST_PROGRAM
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+
+static void __attribute__((__noreturn__)) usage(char *prog)
+{
+ fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask] device "
+ "[type value]\n",
+ prog);
+ fprintf(stderr, "\tList all tags for a device and exit\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ blkid_tag_iterate iter;
+ blkid_cache cache = NULL;
+ blkid_dev dev;
+ int c, ret, found;
+ int flags = BLKID_DEV_FIND;
+ char *tmp;
+ char *file = NULL;
+ char *devname = NULL;
+ char *search_type = NULL;
+ char *search_value = NULL;
+ const char *type, *value;
+
+ while ((c = getopt (argc, argv, "m:f:")) != EOF)
+ switch (c) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'm':
+ {
+ int mask = strtoul (optarg, &tmp, 0);
+ if (*tmp) {
+ fprintf(stderr, "Invalid debug mask: %s\n",
+ optarg);
+ exit(1);
+ }
+ blkid_init_debug(mask);
+ break;
+ }
+ case '?':
+ usage(argv[0]);
+ }
+ if (argc > optind)
+ devname = argv[optind++];
+ if (argc > optind)
+ search_type = argv[optind++];
+ if (argc > optind)
+ search_value = argv[optind++];
+ if (!devname || (argc != optind))
+ usage(argv[0]);
+
+ if ((ret = blkid_get_cache(&cache, file)) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+
+ dev = blkid_get_dev(cache, devname, flags);
+ if (!dev) {
+ fprintf(stderr, "%s: cannot find device in blkid cache\n",
+ devname);
+ exit(1);
+ }
+ if (search_type) {
+ found = blkid_dev_has_tag(dev, search_type, search_value);
+ printf("Device %s: (%s, %s) %s\n", blkid_dev_devname(dev),
+ search_type, search_value ? search_value : "NULL",
+ found ? "FOUND" : "NOT FOUND");
+ return(!found);
+ }
+ printf("Device %s...\n", blkid_dev_devname(dev));
+
+ iter = blkid_tag_iterate_begin(dev);
+ while (blkid_tag_next(iter, &type, &value) == 0) {
+ printf("\tTag %s has value %s\n", type, value);
+ }
+ blkid_tag_iterate_end(iter);
+
+ blkid_put_cache(cache);
+ return (0);
+}
+#endif
diff --git a/libblkid/src/topology/dm.c b/libblkid/src/topology/dm.c
new file mode 100644
index 0000000..b210a80
--- /dev/null
+++ b/libblkid/src/topology/dm.c
@@ -0,0 +1,133 @@
+/*
+ * device-mapper (dm) topology
+ * -- this is fallback for old systems where the topology information is not
+ * exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+static int is_dm_device(dev_t devno)
+{
+ return blkid_driver_has_major("device-mapper", major(devno));
+}
+
+static int probe_dm_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ const char *paths[] = {
+ "/usr/local/sbin/dmsetup",
+ "/usr/sbin/dmsetup",
+ "/sbin/dmsetup"
+ };
+ int dmpipe[] = { -1, -1 }, stripes, stripesize;
+ const char *cmd = NULL;
+ FILE *stream = NULL;
+ long long offset, size;
+ size_t i;
+ dev_t devno = blkid_probe_get_devno(pr);
+
+ if (!devno)
+ goto nothing; /* probably not a block device */
+ if (!is_dm_device(devno))
+ goto nothing;
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++) {
+ struct stat sb;
+ if (stat(paths[i], &sb) == 0) {
+ cmd = paths[i];
+ break;
+ }
+ }
+
+ if (!cmd)
+ goto nothing;
+ if (pipe(dmpipe) < 0) {
+ DBG(LOWPROBE, ul_debug("Failed to open pipe: errno=%d", errno));
+ goto nothing;
+ }
+
+ switch (fork()) {
+ case 0:
+ {
+ const char *dmargv[7];
+ char maj[16], min[16];
+
+ /* Plumbing */
+ close(dmpipe[0]);
+
+ if (dmpipe[1] != STDOUT_FILENO)
+ dup2(dmpipe[1], STDOUT_FILENO);
+
+ if (drop_permissions() != 0)
+ exit(1);
+
+ snprintf(maj, sizeof(maj), "%d", major(devno));
+ snprintf(min, sizeof(min), "%d", minor(devno));
+
+ dmargv[0] = cmd;
+ dmargv[1] = "table";
+ dmargv[2] = "-j";
+ dmargv[3] = maj;
+ dmargv[4] = "-m";
+ dmargv[5] = min;
+ dmargv[6] = NULL;
+
+ execv(dmargv[0], (char * const *) dmargv);
+
+ DBG(LOWPROBE, ul_debug("Failed to execute %s: errno=%d", cmd, errno));
+ exit(1);
+ }
+ case -1:
+ DBG(LOWPROBE, ul_debug("Failed to forking: errno=%d", errno));
+ goto nothing;
+ default:
+ break;
+ }
+
+ stream = fdopen(dmpipe[0], "r" UL_CLOEXECSTR);
+ if (!stream)
+ goto nothing;
+
+ if (fscanf(stream, "%lld %lld striped %d %d ",
+ &offset, &size, &stripes, &stripesize) != 0)
+ goto nothing;
+
+ blkid_topology_set_minimum_io_size(pr, stripesize << 9);
+ blkid_topology_set_optimal_io_size(pr, (stripes * stripesize) << 9);
+
+ fclose(stream);
+ close(dmpipe[1]);
+ return 0;
+
+nothing:
+ if (stream)
+ fclose(stream);
+ else if (dmpipe[0] != -1)
+ close(dmpipe[0]);
+ if (dmpipe[1] != -1)
+ close(dmpipe[1]);
+ return 1;
+}
+
+const struct blkid_idinfo dm_tp_idinfo =
+{
+ .name = "dm",
+ .probefunc = probe_dm_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/evms.c b/libblkid/src/topology/evms.c
new file mode 100644
index 0000000..1aa32f9
--- /dev/null
+++ b/libblkid/src/topology/evms.c
@@ -0,0 +1,77 @@
+/*
+ * Evms topology
+ * -- this is fallback for old systems where the topology information is not
+ * exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.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 "topology.h"
+
+#define EVMS_MAJOR 117
+
+#ifndef _IOT__IOTBASE_u_int32_t
+#define _IOT__IOTBASE_u_int32_t IOT_SIMPLE(uint32_t)
+#endif
+#define _IOT_evms_stripe_info _IOT (_IOTS(uint32_t), 2, 0, 0, 0, 0)
+#define EVMS_GET_STRIPE_INFO _IOR(EVMS_MAJOR, 0xF0, struct evms_stripe_info)
+
+struct evms_stripe_info {
+ uint32_t size; /* stripe unit 512-byte blocks */
+ uint32_t width; /* the number of stripe members or RAID data disks */
+};
+
+static int is_evms_device(dev_t devno)
+{
+ if (major(devno) == EVMS_MAJOR)
+ return 1;
+ return blkid_driver_has_major("evms", major(devno));
+}
+
+static int probe_evms_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ struct evms_stripe_info evms;
+ dev_t devno = blkid_probe_get_devno(pr);
+
+ if (!devno)
+ goto nothing; /* probably not a block device */
+
+ if (!is_evms_device(devno))
+ goto nothing;
+
+ memset(&evms, 0, sizeof(evms));
+
+ if (ioctl(pr->fd, EVMS_GET_STRIPE_INFO, &evms))
+ goto nothing;
+
+ blkid_topology_set_minimum_io_size(pr, evms.size << 9);
+ blkid_topology_set_optimal_io_size(pr, (evms.size * evms.width) << 9);
+
+ return 0;
+
+nothing:
+ return 1;
+}
+
+const struct blkid_idinfo evms_tp_idinfo =
+{
+ .name = "evms",
+ .probefunc = probe_evms_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/ioctl.c b/libblkid/src/topology/ioctl.c
new file mode 100644
index 0000000..3aba09e
--- /dev/null
+++ b/libblkid/src/topology/ioctl.c
@@ -0,0 +1,74 @@
+/*
+ * ioctl based topology -- gathers topology information
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "topology.h"
+
+/*
+ * ioctl topology values
+ */
+static struct topology_val {
+
+ long ioc;
+
+ /* functions to set probing result */
+ int (*set_ulong)(blkid_probe, unsigned long);
+ int (*set_int)(blkid_probe, int);
+
+} topology_vals[] = {
+ { BLKALIGNOFF, NULL, blkid_topology_set_alignment_offset },
+ { BLKIOMIN, blkid_topology_set_minimum_io_size },
+ { BLKIOOPT, blkid_topology_set_optimal_io_size },
+ { BLKPBSZGET, blkid_topology_set_physical_sector_size }
+ /* we read BLKSSZGET in topology.c */
+};
+
+static int probe_ioctl_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(topology_vals); i++) {
+ struct topology_val *val = &topology_vals[i];
+ int rc = 1;
+ unsigned int data;
+
+ if (ioctl(pr->fd, val->ioc, &data) == -1)
+ goto nothing;
+
+ if (val->set_int)
+ rc = val->set_int(pr, (int) data);
+ else
+ rc = val->set_ulong(pr, (unsigned long) data);
+ if (rc)
+ goto err;
+ }
+
+ return 0;
+nothing:
+ return 1;
+err:
+ return -1;
+}
+
+const struct blkid_idinfo ioctl_tp_idinfo =
+{
+ .name = "ioctl",
+ .probefunc = probe_ioctl_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/lvm.c b/libblkid/src/topology/lvm.c
new file mode 100644
index 0000000..6ab7a50
--- /dev/null
+++ b/libblkid/src/topology/lvm.c
@@ -0,0 +1,144 @@
+/*
+ * lvm topology
+ * -- this is fallback for old systems where the topology information is not
+ * exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+#ifndef LVM_BLK_MAJOR
+# define LVM_BLK_MAJOR 58
+#endif
+
+static int is_lvm_device(dev_t devno)
+{
+ if (major(devno) == LVM_BLK_MAJOR)
+ return 1;
+ return blkid_driver_has_major("lvm", major(devno));
+}
+
+static int probe_lvm_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ const char *paths[] = {
+ "/usr/local/sbin/lvdisplay",
+ "/usr/sbin/lvdisplay",
+ "/sbin/lvdisplay"
+ };
+ int lvpipe[] = { -1, -1 }, stripes = 0, stripesize = 0;
+ FILE *stream = NULL;
+ char *cmd = NULL, *devname = NULL, buf[1024];
+ size_t i;
+ dev_t devno = blkid_probe_get_devno(pr);
+
+ if (!devno)
+ goto nothing; /* probably not a block device */
+ if (!is_lvm_device(devno))
+ goto nothing;
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++) {
+ struct stat sb;
+ if (stat(paths[i], &sb) == 0) {
+ cmd = (char *) paths[i];
+ break;
+ }
+ }
+
+ if (!cmd)
+ goto nothing;
+
+ devname = blkid_devno_to_devname(devno);
+ if (!devname)
+ goto nothing;
+
+ if (pipe(lvpipe) < 0) {
+ DBG(LOWPROBE, ul_debug("Failed to open pipe: errno=%d", errno));
+ goto nothing;
+ }
+
+ switch (fork()) {
+ case 0:
+ {
+ char *lvargv[3];
+
+ /* Plumbing */
+ close(lvpipe[0]);
+
+ if (lvpipe[1] != STDOUT_FILENO)
+ dup2(lvpipe[1], STDOUT_FILENO);
+
+ if (drop_permissions() != 0)
+ exit(1);
+
+ lvargv[0] = cmd;
+ lvargv[1] = devname;
+ lvargv[2] = NULL;
+
+ execv(lvargv[0], lvargv);
+
+ DBG(LOWPROBE, ul_debug("Failed to execute %s: errno=%d", cmd, errno));
+ exit(1);
+ }
+ case -1:
+ DBG(LOWPROBE, ul_debug("Failed to forking: errno=%d", errno));
+ goto nothing;
+ default:
+ break;
+ }
+
+ stream = fdopen(lvpipe[0], "r" UL_CLOEXECSTR);
+ if (!stream)
+ goto nothing;
+
+ while (fgets(buf, sizeof(buf), stream) != NULL) {
+ if (!strncmp(buf, "Stripes", 7))
+ ignore_result( sscanf(buf, "Stripes %d", &stripes) );
+
+ if (!strncmp(buf, "Stripe size", 11))
+ ignore_result( sscanf(buf, "Stripe size (KByte) %d", &stripesize) );
+ }
+
+ if (!stripes)
+ goto nothing;
+
+ blkid_topology_set_minimum_io_size(pr, stripesize << 10);
+ blkid_topology_set_optimal_io_size(pr, (stripes * stripesize) << 10);
+
+ free(devname);
+ fclose(stream);
+ close(lvpipe[1]);
+ return 0;
+
+nothing:
+ free(devname);
+ if (stream)
+ fclose(stream);
+ else if (lvpipe[0] != -1)
+ close(lvpipe[0]);
+ if (lvpipe[1] != -1)
+ close(lvpipe[1]);
+ return 1;
+}
+
+const struct blkid_idinfo lvm_tp_idinfo =
+{
+ .name = "lvm",
+ .probefunc = probe_lvm_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/md.c b/libblkid/src/topology/md.c
new file mode 100644
index 0000000..02f27a8
--- /dev/null
+++ b/libblkid/src/topology/md.c
@@ -0,0 +1,154 @@
+/*
+ * Linux Software RAID (md) topology
+ * -- this is fallback for old systems where the topology information is not
+ * exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.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 "topology.h"
+
+#ifndef MD_MAJOR
+#define MD_MAJOR 9
+#endif
+
+#ifndef _IOT__IOTBASE_uint32_t
+#define _IOT__IOTBASE_uint32_t IOT_SIMPLE(uint32_t)
+#endif
+#define _IOT_md_array_info _IOT (_IOTS(uint32_t), 18, 0, 0, 0, 0)
+#define GET_ARRAY_INFO _IOR (MD_MAJOR, 0x11, struct md_array_info)
+
+struct md_array_info {
+ /*
+ * Generic constant information
+ */
+ uint32_t major_version;
+ uint32_t minor_version;
+ uint32_t patch_version;
+ uint32_t ctime;
+ uint32_t level;
+ uint32_t size;
+ uint32_t nr_disks;
+ uint32_t raid_disks;
+ uint32_t md_minor;
+ uint32_t not_persistent;
+
+ /*
+ * Generic state information
+ */
+ uint32_t utime; /* 0 Superblock update time */
+ uint32_t state; /* 1 State bits (clean, ...) */
+ uint32_t active_disks; /* 2 Number of currently active disks */
+ uint32_t working_disks; /* 3 Number of working disks */
+ uint32_t failed_disks; /* 4 Number of failed disks */
+ uint32_t spare_disks; /* 5 Number of spare disks */
+
+ /*
+ * Personality information
+ */
+ uint32_t layout; /* 0 the array's physical layout */
+ uint32_t chunk_size; /* 1 chunk size in bytes */
+
+};
+
+static int is_md_device(dev_t devno)
+{
+ if (major(devno) == MD_MAJOR)
+ return 1;
+ return blkid_driver_has_major("md", major(devno));
+}
+
+static int probe_md_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ int fd = -1;
+ dev_t disk = 0;
+ dev_t devno = blkid_probe_get_devno(pr);
+ struct md_array_info md;
+
+ if (!devno)
+ goto nothing; /* probably not a block device */
+
+ if (!is_md_device(devno))
+ goto nothing;
+
+ if (blkid_devno_to_wholedisk(devno, NULL, 0, &disk))
+ goto nothing;
+
+ if (disk == devno)
+ fd = pr->fd;
+ else {
+ char *diskpath = blkid_devno_to_devname(disk);
+
+ if (!diskpath)
+ goto nothing;
+
+ fd = open(diskpath, O_RDONLY|O_CLOEXEC);
+ free(diskpath);
+
+ if (fd == -1)
+ goto nothing;
+ }
+
+ memset(&md, 0, sizeof(md));
+
+ if (ioctl(fd, GET_ARRAY_INFO, &md))
+ goto nothing;
+
+ if (fd >= 0 && fd != pr->fd) {
+ close(fd);
+ fd = -1;
+ }
+
+ /*
+ * Ignore levels we don't want aligned (e.g. linear)
+ * and deduct disk(s) from stripe width on RAID4/5/6
+ */
+ switch (md.level) {
+ case 6:
+ md.raid_disks--;
+ /* fallthrough */
+ case 5:
+ case 4:
+ md.raid_disks--;
+ /* fallthrough */
+ case 1:
+ case 0:
+ case 10:
+ break;
+ default:
+ goto nothing;
+ }
+
+ blkid_topology_set_minimum_io_size(pr, md.chunk_size);
+ blkid_topology_set_optimal_io_size(pr, (unsigned long) md.chunk_size * md.raid_disks);
+
+ return 0;
+
+nothing:
+ if (fd >= 0 && fd != pr->fd)
+ close(fd);
+ return 1;
+}
+
+const struct blkid_idinfo md_tp_idinfo =
+{
+ .name = "md",
+ .probefunc = probe_md_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/sysfs.c b/libblkid/src/topology/sysfs.c
new file mode 100644
index 0000000..745cd11
--- /dev/null
+++ b/libblkid/src/topology/sysfs.c
@@ -0,0 +1,124 @@
+/*
+ * sysfs based topology -- gathers topology information from Linux sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * For more information see Linux kernel Documentation/ABI/testing/sysfs-block.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "sysfs.h"
+#include "topology.h"
+
+/*
+ * Sysfs topology values (since 2.6.31, May 2009).
+ */
+static struct topology_val {
+
+ /* /sys/dev/block/<maj>:<min>/<ATTR> */
+ const char *attr;
+
+ /* functions to set probing result */
+ int (*set_ulong)(blkid_probe, unsigned long);
+ int (*set_int)(blkid_probe, int);
+
+} topology_vals[] = {
+ { "alignment_offset", NULL, blkid_topology_set_alignment_offset },
+ { "queue/minimum_io_size", blkid_topology_set_minimum_io_size },
+ { "queue/optimal_io_size", blkid_topology_set_optimal_io_size },
+ { "queue/physical_block_size", blkid_topology_set_physical_sector_size },
+ { "queue/dax", blkid_topology_set_dax },
+};
+
+static int probe_sysfs_tp(blkid_probe pr,
+ const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+ dev_t dev;
+ int rc, set_parent = 1;
+ struct path_cxt *pc;
+ size_t i, count = 0;
+
+ dev = blkid_probe_get_devno(pr);
+ if (!dev)
+ return 1;
+ pc = ul_new_sysfs_path(dev, NULL, NULL);
+ if (!pc)
+ return 1;
+
+ rc = 1; /* nothing (default) */
+
+ for (i = 0; i < ARRAY_SIZE(topology_vals); i++) {
+ struct topology_val *val = &topology_vals[i];
+ int ok = ul_path_access(pc, F_OK, val->attr) == 0;
+
+ rc = 1; /* nothing */
+
+ if (!ok && set_parent) {
+ dev_t disk = blkid_probe_get_wholedisk_devno(pr);
+ set_parent = 0;
+
+ /*
+ * Read attributes from "disk" if the current device is
+ * a partition. Note that sysfs ul_path_* API is able
+ * to redirect requests to attributes if parent is set.
+ */
+ if (disk && disk != dev) {
+ struct path_cxt *parent = ul_new_sysfs_path(disk, NULL, NULL);
+ if (!parent)
+ goto done;
+
+ sysfs_blkdev_set_parent(pc, parent);
+ ul_unref_path(parent);
+
+ /* try it again */
+ ok = ul_path_access(pc, F_OK, val->attr) == 0;
+ }
+ }
+ if (!ok)
+ continue; /* attribute does not exist */
+
+ if (val->set_ulong) {
+ uint64_t data;
+
+ if (ul_path_read_u64(pc, &data, val->attr) != 0)
+ continue;
+ rc = val->set_ulong(pr, (unsigned long) data);
+
+ } else if (val->set_int) {
+ int64_t data;
+
+ if (ul_path_read_s64(pc, &data, val->attr) != 0)
+ continue;
+ rc = val->set_int(pr, (int) data);
+ }
+
+ if (rc < 0)
+ goto done; /* error */
+ if (rc == 0)
+ count++;
+ }
+
+done:
+ ul_unref_path(pc); /* unref pc and parent */
+ if (count)
+ return 0; /* success */
+ return rc; /* error or nothing */
+}
+
+const struct blkid_idinfo sysfs_tp_idinfo =
+{
+ .name = "sysfs",
+ .probefunc = probe_sysfs_tp,
+ .magics = BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/topology.c b/libblkid/src/topology/topology.c
new file mode 100644
index 0000000..53007d1
--- /dev/null
+++ b/libblkid/src/topology/topology.c
@@ -0,0 +1,381 @@
+/*
+ * topology - gathers information about device topology
+ *
+ * Copyright 2009 Red Hat, Inc. All rights reserved.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "topology.h"
+
+/**
+ * SECTION:topology
+ * @title: Topology information
+ * @short_description: block device topology information.
+ *
+ * The topology chain provides details about Linux block devices, for more
+ * information see:
+ *
+ * Linux kernel Documentation/ABI/testing/sysfs-block
+ *
+ * NAME=value (tags) interface is enabled by blkid_probe_enable_topology(),
+ * and provides:
+ *
+ * @LOGICAL_SECTOR_SIZE: this is the smallest unit the storage device can
+ * address. It is typically 512 bytes.
+ *
+ * @PHYSICAL_SECTOR_SIZE: this is the smallest unit a physical storage device
+ * can write atomically. It is usually the same as the
+ * logical sector size but may be bigger.
+ *
+ * @MINIMUM_IO_SIZE: minimum size which is the device's preferred unit of I/O.
+ * For RAID arrays it is often the stripe chunk size.
+ *
+ * @OPTIMAL_IO_SIZE: usually the stripe width for RAID or zero. For RAID arrays
+ * it is usually the stripe width or the internal track size.
+ *
+ * @ALIGNMENT_OFFSET: indicates how many bytes the beginning of the device is
+ * offset from the disk's natural alignment.
+ *
+ * The NAME=value tags are not defined when the corresponding topology value
+ * is zero. The MINIMUM_IO_SIZE should be always defined if kernel provides
+ * topology information.
+ *
+ * Binary interface:
+ *
+ * blkid_probe_get_topology()
+ *
+ * blkid_topology_get_'VALUENAME'()
+ */
+static int topology_probe(blkid_probe pr, struct blkid_chain *chn);
+static void topology_free(blkid_probe pr, void *data);
+static int topology_is_complete(blkid_probe pr);
+static int topology_set_logical_sector_size(blkid_probe pr);
+
+/*
+ * Binary interface
+ */
+struct blkid_struct_topology {
+ unsigned long alignment_offset;
+ unsigned long minimum_io_size;
+ unsigned long optimal_io_size;
+ unsigned long logical_sector_size;
+ unsigned long physical_sector_size;
+ unsigned long dax;
+};
+
+/*
+ * Topology chain probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+#ifdef __linux__
+ &sysfs_tp_idinfo,
+ &ioctl_tp_idinfo,
+ &md_tp_idinfo,
+ &dm_tp_idinfo,
+ &lvm_tp_idinfo,
+ &evms_tp_idinfo
+#endif
+};
+
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv topology_drv = {
+ .id = BLKID_CHAIN_TOPLGY,
+ .name = "topology",
+ .dflt_enabled = FALSE,
+ .idinfos = idinfos,
+ .nidinfos = ARRAY_SIZE(idinfos),
+ .probe = topology_probe,
+ .safeprobe = topology_probe,
+ .free_data = topology_free
+};
+
+/**
+ * blkid_probe_enable_topology:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the topology probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_topology(blkid_probe pr, int enable)
+{
+ pr->chains[BLKID_CHAIN_TOPLGY].enabled = enable;
+ return 0;
+}
+
+/**
+ * blkid_probe_get_topology:
+ * @pr: probe
+ *
+ * This is a binary interface for topology values. See also blkid_topology_*
+ * functions.
+ *
+ * This function is independent on blkid_do_[safe,full]probe() and
+ * blkid_probe_enable_topology() calls.
+ *
+ * WARNING: the returned object will be overwritten by the next
+ * blkid_probe_get_topology() call for the same @pr. If you want to
+ * use more blkid_topology objects in the same time you have to create
+ * more blkid_probe handlers (see blkid_new_probe()).
+ *
+ * Returns: blkid_topology, or NULL in case of error.
+ */
+blkid_topology blkid_probe_get_topology(blkid_probe pr)
+{
+ return (blkid_topology) blkid_probe_get_binary_data(pr,
+ &pr->chains[BLKID_CHAIN_TOPLGY]);
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int topology_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+ size_t i;
+
+ if (chn->idx < -1)
+ return -1;
+
+ if (!S_ISBLK(pr->mode))
+ return -EINVAL; /* nothing, works with block devices only */
+
+ if (chn->binary) {
+ DBG(LOWPROBE, ul_debug("initialize topology binary data"));
+
+ if (chn->data)
+ /* reset binary data */
+ memset(chn->data, 0,
+ sizeof(struct blkid_struct_topology));
+ else {
+ chn->data = calloc(1,
+ sizeof(struct blkid_struct_topology));
+ if (!chn->data)
+ return -ENOMEM;
+ }
+ }
+
+ blkid_probe_chain_reset_values(pr, chn);
+
+ DBG(LOWPROBE, ul_debug("--> starting probing loop [TOPOLOGY idx=%d]",
+ chn->idx));
+
+ i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+ for ( ; i < ARRAY_SIZE(idinfos); i++) {
+ const struct blkid_idinfo *id = idinfos[i];
+
+ chn->idx = i;
+
+ if (id->probefunc) {
+ DBG(LOWPROBE, ul_debug("%s: call probefunc()", id->name));
+ if (id->probefunc(pr, NULL) != 0)
+ continue;
+ }
+
+ if (!topology_is_complete(pr))
+ continue;
+
+ /* generic for all probing drivers */
+ topology_set_logical_sector_size(pr);
+
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [TOPOLOGY idx=%d]",
+ id->name, chn->idx));
+ return BLKID_PROBE_OK;
+ }
+
+ DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed) [TOPOLOGY idx=%d]",
+ chn->idx));
+ return BLKID_PROBE_NONE;
+}
+
+static void topology_free(blkid_probe pr __attribute__((__unused__)),
+ void *data)
+{
+ free(data);
+}
+
+static int topology_set_value(blkid_probe pr, const char *name,
+ size_t structoff, unsigned long data)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (!chn)
+ return -1;
+ if (!data)
+ return 0; /* ignore zeros */
+
+ if (chn->binary) {
+ memcpy((char *) chn->data + structoff, &data, sizeof(data));
+ return 0;
+ }
+ return blkid_probe_sprintf_value(pr, name, "%lu", data);
+}
+
+
+/* the topology info is complete when we have at least "minimum_io_size" which
+ * is provided by all blkid topology drivers */
+static int topology_is_complete(blkid_probe pr)
+{
+ struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+ if (!chn)
+ return FALSE;
+
+ if (chn->binary && chn->data) {
+ blkid_topology tp = (blkid_topology) chn->data;
+ if (tp->minimum_io_size)
+ return TRUE;
+ }
+
+ return __blkid_probe_lookup_value(pr, "MINIMUM_IO_SIZE") ? TRUE : FALSE;
+}
+
+int blkid_topology_set_alignment_offset(blkid_probe pr, int val)
+{
+ unsigned long xval;
+
+ /* Welcome to Hell. The kernel is able to return -1 as an
+ * alignment_offset if no compatible sizes and alignments
+ * exist for stacked devices.
+ *
+ * There is no way how libblkid caller can respond to the value -1, so
+ * we will hide this corner case...
+ *
+ * (TODO: maybe we can export an extra boolean value 'misaligned' rather
+ * then complete hide this problem.)
+ */
+ xval = val < 0 ? 0 : val;
+
+ return topology_set_value(pr,
+ "ALIGNMENT_OFFSET",
+ offsetof(struct blkid_struct_topology, alignment_offset),
+ xval);
+}
+
+int blkid_topology_set_minimum_io_size(blkid_probe pr, unsigned long val)
+{
+ return topology_set_value(pr,
+ "MINIMUM_IO_SIZE",
+ offsetof(struct blkid_struct_topology, minimum_io_size),
+ val);
+}
+
+int blkid_topology_set_optimal_io_size(blkid_probe pr, unsigned long val)
+{
+ return topology_set_value(pr,
+ "OPTIMAL_IO_SIZE",
+ offsetof(struct blkid_struct_topology, optimal_io_size),
+ val);
+}
+
+/* BLKSSZGET is provided on all systems since 2.3.3 -- so we don't have to
+ * waste time with sysfs.
+ */
+static int topology_set_logical_sector_size(blkid_probe pr)
+{
+ unsigned long val = blkid_probe_get_sectorsize(pr);
+
+ if (!val)
+ return -1;
+
+ return topology_set_value(pr,
+ "LOGICAL_SECTOR_SIZE",
+ offsetof(struct blkid_struct_topology, logical_sector_size),
+ val);
+}
+
+int blkid_topology_set_physical_sector_size(blkid_probe pr, unsigned long val)
+{
+ return topology_set_value(pr,
+ "PHYSICAL_SECTOR_SIZE",
+ offsetof(struct blkid_struct_topology, physical_sector_size),
+ val);
+}
+
+int blkid_topology_set_dax(blkid_probe pr, unsigned long val)
+{
+ return topology_set_value(pr,
+ "DAX",
+ offsetof(struct blkid_struct_topology, dax),
+ val);
+}
+
+/**
+ * blkid_topology_get_alignment_offset:
+ * @tp: topology
+ *
+ * Returns: alignment offset in bytes or 0.
+ */
+unsigned long blkid_topology_get_alignment_offset(blkid_topology tp)
+{
+ return tp->alignment_offset;
+}
+
+/**
+ * blkid_topology_get_minimum_io_size:
+ * @tp: topology
+ *
+ * Returns: minimum io size in bytes or 0.
+ */
+unsigned long blkid_topology_get_minimum_io_size(blkid_topology tp)
+{
+ return tp->minimum_io_size;
+}
+
+/**
+ * blkid_topology_get_optimal_io_size
+ * @tp: topology
+ *
+ * Returns: optimal io size in bytes or 0.
+ */
+unsigned long blkid_topology_get_optimal_io_size(blkid_topology tp)
+{
+ return tp->optimal_io_size;
+}
+
+/**
+ * blkid_topology_get_logical_sector_size
+ * @tp: topology
+ *
+ * Returns: logical sector size (BLKSSZGET ioctl) in bytes or 0.
+ */
+unsigned long blkid_topology_get_logical_sector_size(blkid_topology tp)
+{
+ return tp->logical_sector_size;
+}
+
+/**
+ * blkid_topology_get_physical_sector_size
+ * @tp: topology
+ *
+ * Returns: logical sector size (BLKSSZGET ioctl) in bytes or 0.
+ */
+unsigned long blkid_topology_get_physical_sector_size(blkid_topology tp)
+{
+ return tp->physical_sector_size;
+}
+
+/**
+ * blkid_topology_get_dax
+ * @tp: topology
+ *
+ * Returns: 1 if dax is supported, 0 otherwise.
+ *
+ * Since: 2.36
+ */
+unsigned long blkid_topology_get_dax(blkid_topology tp)
+{
+ return tp->dax;
+}
diff --git a/libblkid/src/topology/topology.h b/libblkid/src/topology/topology.h
new file mode 100644
index 0000000..3e46af9
--- /dev/null
+++ b/libblkid/src/topology/topology.h
@@ -0,0 +1,25 @@
+#ifndef BLKID_TOPOLOGY_H
+#define BLKID_TOPOLOGY_H
+
+#include "blkidP.h"
+
+extern int blkid_topology_set_alignment_offset(blkid_probe pr, int val);
+extern int blkid_topology_set_minimum_io_size(blkid_probe pr, unsigned long val);
+extern int blkid_topology_set_optimal_io_size(blkid_probe pr, unsigned long val);
+extern int blkid_topology_set_physical_sector_size(blkid_probe pr, unsigned long val);
+extern int blkid_topology_set_dax(blkid_probe pr, unsigned long val);
+
+/*
+ * topology probers
+ */
+#ifdef __linux__
+extern const struct blkid_idinfo ioctl_tp_idinfo;
+extern const struct blkid_idinfo md_tp_idinfo;
+extern const struct blkid_idinfo evms_tp_idinfo;
+extern const struct blkid_idinfo sysfs_tp_idinfo;
+extern const struct blkid_idinfo dm_tp_idinfo;
+extern const struct blkid_idinfo lvm_tp_idinfo;
+#endif
+
+#endif /* BLKID_TOPOLOGY_H */
+
diff --git a/libblkid/src/verify.c b/libblkid/src/verify.c
new file mode 100644
index 0000000..3b9754f
--- /dev/null
+++ b/libblkid/src/verify.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "blkidP.h"
+#include "sysfs.h"
+
+static void blkid_probe_to_tags(blkid_probe pr, blkid_dev dev)
+{
+ const char *data;
+ const char *name;
+ int nvals, n;
+ size_t len;
+
+ nvals = blkid_probe_numof_values(pr);
+
+ for (n = 0; n < nvals; n++) {
+ if (blkid_probe_get_value(pr, n, &name, &data, &len) != 0)
+ continue;
+ if (strncmp(name, "PART_ENTRY_", 11) == 0) {
+ if (strcmp(name, "PART_ENTRY_UUID") == 0)
+ blkid_set_tag(dev, "PARTUUID", data, len);
+ else if (strcmp(name, "PART_ENTRY_NAME") == 0)
+ blkid_set_tag(dev, "PARTLABEL", data, len);
+
+ } else if (!strstr(name, "_ID")) {
+ /* superblock UUID, LABEL, ...
+ * but not {SYSTEM,APPLICATION,..._ID} */
+ blkid_set_tag(dev, name, data, len);
+ }
+ }
+}
+
+/*
+ * Verify that the data in dev is consistent with what is on the actual
+ * block device (using the devname field only). Normally this will be
+ * called when finding items in the cache, but for long running processes
+ * is also desirable to revalidate an item before use.
+ *
+ * If we are unable to revalidate the data, we return the old data and
+ * do not set the BLKID_BID_FL_VERIFIED flag on it.
+ */
+blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev)
+{
+ blkid_tag_iterate iter;
+ const char *type, *value;
+ struct stat st;
+ time_t diff, now;
+ int fd;
+
+ if (!dev || !cache)
+ return NULL;
+
+ now = time(NULL);
+ diff = (uintmax_t)now - dev->bid_time;
+
+ if (stat(dev->bid_name, &st) < 0) {
+ DBG(PROBE, ul_debug("blkid_verify: error %s (%d) while "
+ "trying to stat %s", strerror(errno), errno,
+ dev->bid_name));
+ open_err:
+ if ((errno == EPERM) || (errno == EACCES) || (errno == ENOENT)) {
+ /* We don't have read permission, just return cache data. */
+ DBG(PROBE, ul_debug("returning unverified data for %s",
+ dev->bid_name));
+ return dev;
+ }
+ blkid_free_dev(dev);
+ return NULL;
+ }
+
+ if (now >= dev->bid_time &&
+#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+ (st.st_mtime < dev->bid_time ||
+ (st.st_mtime == dev->bid_time &&
+ st.st_mtim.tv_nsec / 1000 <= dev->bid_utime)) &&
+#else
+ st.st_mtime <= dev->bid_time &&
+#endif
+ diff >= 0 && diff < BLKID_PROBE_MIN) {
+ dev->bid_flags |= BLKID_BID_FL_VERIFIED;
+ return dev;
+ }
+
+#ifndef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+ DBG(PROBE, ul_debug("need to revalidate %s (cache time %lld, stat time %lld,\t"
+ "time since last check %lld)",
+ dev->bid_name, (long long)dev->bid_time,
+ (long long)st.st_mtime, (long long)diff));
+#else
+ DBG(PROBE, ul_debug("need to revalidate %s (cache time %lld.%lld, stat time %lld.%lld,\t"
+ "time since last check %lld)",
+ dev->bid_name,
+ (long long)dev->bid_time, (long long)dev->bid_utime,
+ (long long)st.st_mtime, (long long)st.st_mtim.tv_nsec / 1000,
+ (long long)diff));
+#endif
+
+ if (sysfs_devno_is_dm_private(st.st_rdev, NULL)) {
+ blkid_free_dev(dev);
+ return NULL;
+ }
+ if (!cache->probe) {
+ cache->probe = blkid_new_probe();
+ if (!cache->probe) {
+ blkid_free_dev(dev);
+ return NULL;
+ }
+ }
+
+ fd = open(dev->bid_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ DBG(PROBE, ul_debug("blkid_verify: error %s (%d) while "
+ "opening %s", strerror(errno), errno,
+ dev->bid_name));
+ goto open_err;
+ }
+
+ if (blkid_probe_set_device(cache->probe, fd, 0, 0)) {
+ /* failed to read the device */
+ close(fd);
+ blkid_free_dev(dev);
+ return NULL;
+ }
+
+ /* remove old cache info */
+ iter = blkid_tag_iterate_begin(dev);
+ while (blkid_tag_next(iter, &type, &value) == 0)
+ blkid_set_tag(dev, type, NULL, 0);
+ blkid_tag_iterate_end(iter);
+
+ /* enable superblocks probing */
+ blkid_probe_enable_superblocks(cache->probe, TRUE);
+ blkid_probe_set_superblocks_flags(cache->probe,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE);
+
+ /* enable partitions probing */
+ blkid_probe_enable_partitions(cache->probe, TRUE);
+ blkid_probe_set_partitions_flags(cache->probe, BLKID_PARTS_ENTRY_DETAILS);
+
+ /* probe */
+ if (blkid_do_safeprobe(cache->probe)) {
+ /* found nothing or error */
+ blkid_free_dev(dev);
+ dev = NULL;
+ }
+
+ if (dev) {
+#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+ struct timeval tv;
+ if (!gettimeofday(&tv, NULL)) {
+ dev->bid_time = tv.tv_sec;
+ dev->bid_utime = tv.tv_usec;
+ } else
+#endif
+ dev->bid_time = time(NULL);
+
+ dev->bid_devno = st.st_rdev;
+ dev->bid_flags |= BLKID_BID_FL_VERIFIED;
+ cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+
+ blkid_probe_to_tags(cache->probe, dev);
+
+ DBG(PROBE, ul_debug("%s: devno 0x%04llx, type %s",
+ dev->bid_name, (long long)st.st_rdev, dev->bid_type));
+ }
+
+ /* reset prober */
+ blkid_probe_reset_superblocks_filter(cache->probe);
+ blkid_probe_set_device(cache->probe, -1, 0, 0);
+ close(fd);
+
+ return dev;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+ blkid_dev dev;
+ blkid_cache cache;
+ int ret;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s device\n"
+ "Probe a single device to determine type\n", argv[0]);
+ exit(1);
+ }
+ if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+ fprintf(stderr, "%s: error creating cache (%d)\n",
+ argv[0], ret);
+ exit(1);
+ }
+ dev = blkid_get_dev(cache, argv[1], BLKID_DEV_NORMAL);
+ if (!dev) {
+ printf("%s: %s has an unsupported type\n", argv[0], argv[1]);
+ return (1);
+ }
+ printf("TYPE='%s'\n", dev->bid_type ? dev->bid_type : "(null)");
+ if (dev->bid_label)
+ printf("LABEL='%s'\n", dev->bid_label);
+ if (dev->bid_uuid)
+ printf("UUID='%s'\n", dev->bid_uuid);
+
+ blkid_free_dev(dev);
+ return (0);
+}
+#endif
diff --git a/libblkid/src/version.c b/libblkid/src/version.c
new file mode 100644
index 0000000..ed92db9
--- /dev/null
+++ b/libblkid/src/version.c
@@ -0,0 +1,62 @@
+/*
+ * version.c --- Return the version of the blkid library
+ *
+ * Copyright (C) 2004 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Lesser General
+ * Public License.
+ * %End-Header%
+ */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "blkid.h"
+
+/* LIBBLKID_* defined in the global config.h */
+static const char *lib_version = LIBBLKID_VERSION; /* release version */
+static const char *lib_date = LIBBLKID_DATE;
+
+/**
+ * blkid_parse_version_string:
+ * @ver_string: version string (e.g. "2.16.0")
+ *
+ * Returns: release version code.
+ */
+int blkid_parse_version_string(const char *ver_string)
+{
+ const char *cp;
+ int version = 0;
+
+ for (cp = ver_string; *cp; cp++) {
+ if (*cp == '.')
+ continue;
+ if (!isdigit(*cp))
+ break;
+ version = (version * 10) + (*cp - '0');
+ }
+ return version;
+}
+
+/**
+ * blkid_get_library_version:
+ * @ver_string: returns release version (!= SONAME version)
+ * @date_string: returns date
+ *
+ * Returns: release version code.
+ */
+int blkid_get_library_version(const char **ver_string,
+ const char **date_string)
+{
+ if (ver_string)
+ *ver_string = lib_version;
+ if (date_string)
+ *date_string = lib_date;
+
+ return blkid_parse_version_string(lib_version);
+}
diff --git a/libfdisk/COPYING b/libfdisk/COPYING
new file mode 100644
index 0000000..1c4252a
--- /dev/null
+++ b/libfdisk/COPYING
@@ -0,0 +1,8 @@
+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.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPL-2.1-or-later file.
diff --git a/libfdisk/Makemodule.am b/libfdisk/Makemodule.am
new file mode 100644
index 0000000..0c23eee
--- /dev/null
+++ b/libfdisk/Makemodule.am
@@ -0,0 +1,15 @@
+if BUILD_LIBFDISK
+
+include libfdisk/src/Makemodule.am
+include libfdisk/samples/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libfdisk/docs
+endif
+
+pkgconfig_DATA += libfdisk/fdisk.pc
+PATHFILES += libfdisk/fdisk.pc
+EXTRA_DIST += libfdisk/COPYING
+
+endif # BUILD_LIBFDISK
diff --git a/libfdisk/docs/Makefile.am b/libfdisk/docs/Makefile.am
new file mode 100644
index 0000000..f91cc88
--- /dev/null
+++ b/libfdisk/docs/Makefile.am
@@ -0,0 +1,93 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libfdisk
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space fdisk
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libfdisk/src/libfdisk.h
+CFILE_GLOB=$(top_srcdir)/libfdisk/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=fdiskP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
diff --git a/libfdisk/docs/Makefile.in b/libfdisk/docs/Makefile.in
new file mode 100644
index 0000000..37c3cfe
--- /dev/null
+++ b/libfdisk/docs/Makefile.in
@@ -0,0 +1,894 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# WARNING: this is not gtk-doc.make file from gtk-doc project. This
+# file has been modified to match with util-linux requirements:
+#
+# * install files to $datadir
+# * don't maintain generated files in git repository
+# * don't distribute the final html files
+# * don't require --enable-gtk-doc for "make dist"
+# * support out-of-tree build ($srcdir != $builddir)
+#
+# -- kzak, Nov 2009
+#
+
+####################################
+# Everything below here is generic #
+####################################
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = libfdisk/docs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_vscript.m4 \
+ $(top_srcdir)/m4/compiler.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/tls.m4 \
+ $(top_srcdir)/m4/ul.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = version.xml
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/version.xml.in \
+ $(top_srcdir)/config/gtk-doc.make
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ADJTIME_PATH = @ADJTIME_PATH@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+ASCIIDOCTOR = @ASCIIDOCTOR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BSD_WARN_CFLAGS = @BSD_WARN_CFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTSETUP_CFLAGS = @CRYPTSETUP_CFLAGS@
+CRYPTSETUP_LIBS = @CRYPTSETUP_LIBS@
+CRYPTSETUP_LIBS_STATIC = @CRYPTSETUP_LIBS_STATIC@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DAEMON_CFLAGS = @DAEMON_CFLAGS@
+DAEMON_LDFLAGS = @DAEMON_LDFLAGS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+ECONF_CFLAGS = @ECONF_CFLAGS@
+ECONF_LIBS = @ECONF_LIBS@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZING_ENGINE_LDFLAGS = @FUZZING_ENGINE_LDFLAGS@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBBLKID_DATE = @LIBBLKID_DATE@
+LIBBLKID_VERSION = @LIBBLKID_VERSION@
+LIBBLKID_VERSION_INFO = @LIBBLKID_VERSION_INFO@
+LIBFDISK_MAJOR_VERSION = @LIBFDISK_MAJOR_VERSION@
+LIBFDISK_MINOR_VERSION = @LIBFDISK_MINOR_VERSION@
+LIBFDISK_PATCH_VERSION = @LIBFDISK_PATCH_VERSION@
+LIBFDISK_PC_REQUIRES = @LIBFDISK_PC_REQUIRES@
+LIBFDISK_VERSION = @LIBFDISK_VERSION@
+LIBFDISK_VERSION_INFO = @LIBFDISK_VERSION_INFO@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMOUNT_MAJOR_VERSION = @LIBMOUNT_MAJOR_VERSION@
+LIBMOUNT_MINOR_VERSION = @LIBMOUNT_MINOR_VERSION@
+LIBMOUNT_PATCH_VERSION = @LIBMOUNT_PATCH_VERSION@
+LIBMOUNT_VERSION = @LIBMOUNT_VERSION@
+LIBMOUNT_VERSION_INFO = @LIBMOUNT_VERSION_INFO@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSMARTCOLS_VERSION = @LIBSMARTCOLS_VERSION@
+LIBSMARTCOLS_VERSION_INFO = @LIBSMARTCOLS_VERSION_INFO@
+LIBTOOL = @LIBTOOL@
+LIBUSER_CFLAGS = @LIBUSER_CFLAGS@
+LIBUSER_LIBS = @LIBUSER_LIBS@
+LIBUUID_VERSION = @LIBUUID_VERSION@
+LIBUUID_VERSION_INFO = @LIBUUID_VERSION_INFO@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAGIC_LIBS = @MAGIC_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MATH_LIBS = @MATH_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NCURSES5_CONFIG = @NCURSES5_CONFIG@
+NCURSES6_CONFIG = @NCURSES6_CONFIG@
+NCURSESW5_CONFIG = @NCURSESW5_CONFIG@
+NCURSESW6_CONFIG = @NCURSESW6_CONFIG@
+NCURSESW_CFLAGS = @NCURSESW_CFLAGS@
+NCURSESW_LIBS = @NCURSESW_LIBS@
+NCURSES_CFLAGS = @NCURSES_CFLAGS@
+NCURSES_LIBS = @NCURSES_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NO_UNUSED_WARN_CFLAGS = @NO_UNUSED_WARN_CFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+PYTHON_WARN_CFLAGS = @PYTHON_WARN_CFLAGS@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+READLINE_LIBS_STATIC = @READLINE_LIBS_STATIC@
+REALTIME_LIBS = @REALTIME_LIBS@
+RTAS_LIBS = @RTAS_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SELINUX_LIBS_STATIC = @SELINUX_LIBS_STATIC@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SOLIB_CFLAGS = @SOLIB_CFLAGS@
+SOLIB_LDFLAGS = @SOLIB_LDFLAGS@
+STRIP = @STRIP@
+SUID_CFLAGS = @SUID_CFLAGS@
+SUID_LDFLAGS = @SUID_LDFLAGS@
+SYSCONFSTATICDIR = @SYSCONFSTATICDIR@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@
+SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+TINFOW_CFLAGS = @TINFOW_CFLAGS@
+TINFOW_LIBS = @TINFOW_LIBS@
+TINFO_CFLAGS = @TINFO_CFLAGS@
+TINFO_LIBS = @TINFO_LIBS@
+TINFO_LIBS_STATIC = @TINFO_LIBS_STATIC@
+UBSAN_LDFLAGS = @UBSAN_LDFLAGS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+VSCRIPT_LDFLAGS = @VSCRIPT_LDFLAGS@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XSLTPROC = @XSLTPROC@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bashcompletiondir = @bashcompletiondir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+sysconfstaticdir = @sysconfstaticdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+usrbin_execdir = @usrbin_execdir@
+usrlib_execdir = @usrlib_execdir@
+usrsbin_execdir = @usrsbin_execdir@
+vendordir = @vendordir@
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE = libfdisk
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR = ../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS =
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space fdisk
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS =
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS =
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB = $(top_builddir)/libfdisk/src/libfdisk.h
+CFILE_GLOB = $(top_srcdir)/libfdisk/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES =
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES = fdiskP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES =
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files =
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS =
+GTKDOC_LIBS =
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_CC = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_LD = $(LIBTOOL) --tag=CC --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_RUN =
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_RUN = $(LIBTOOL) --mode=execute
+
+# We set GPATH here; this gives us semantics for GNU make
+# which are more like other make's VPATH, when it comes to
+# whether a source that is a target of one rule is then
+# searched for in VPATH/GPATH.
+#
+GPATH = $(srcdir)
+TARGET_DIR = $(docdir)/$(DOC_MODULE)
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES = version.xml
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST = $(content_files) $(HTML_IMAGES) $(DOC_MAIN_SGML_FILE) \
+ $(DOC_MODULE)-sections.txt version.xml.in
+# $(DOC_MODULE)-overrides.txt
+DOC_STAMPS = scan-build.stamp sgml-build.stamp html-build.stamp \
+ $(srcdir)/setup.stamp $(srcdir)/sgml.stamp \
+ $(srcdir)/html.stamp
+
+SCANOBJ_FILES = \
+ $(DOC_MODULE).args \
+ $(DOC_MODULE).hierarchy \
+ $(DOC_MODULE).interfaces \
+ $(DOC_MODULE).prerequisites \
+ $(DOC_MODULE).signals \
+ $(DOC_MODULE).types # util-linux: we don't use types
+
+REPORT_FILES = \
+ $(DOC_MODULE)-undocumented.txt \
+ $(DOC_MODULE)-undeclared.txt \
+ $(DOC_MODULE)-unused.txt
+
+CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/config/gtk-doc.make $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libfdisk/docs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign libfdisk/docs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/config/gtk-doc.make $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+version.xml: $(top_builddir)/config.status $(srcdir)/version.xml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile all-local
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-local
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am all-local check check-am clean clean-generic \
+ clean-libtool clean-local cscopelist-am ctags-am distclean \
+ distclean-generic distclean-libtool distclean-local distdir \
+ dvi dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-data-local install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am uninstall-local
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_GTK_DOC_TRUE@all-local: html-build.stamp
+@ENABLE_GTK_DOC_FALSE@all-local:
+
+docs: html-build.stamp
+
+$(REPORT_FILES): sgml-build.stamp
+
+#### setup ####
+
+setup-build.stamp:
+ -@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
+ echo 'gtk-doc: Preparing build'; \
+ files=`echo $(EXTRA_DIST) $(expand_content_files) $(srcdir)/$(DOC_MODULE).types`; \
+ if test "x$$files" != "x" ; then \
+ for file in $$files ; do \
+ test -f $(abs_srcdir)/$$file && \
+ cp -p $(abs_srcdir)/$$file $(abs_builddir)/; \
+ done \
+ fi \
+ fi
+ @touch setup-build.stamp
+
+setup.stamp: setup-build.stamp
+ @true
+
+#### scan ####
+
+scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB) $(srcdir)/$(DOC_MODULE)-*.txt $(content_files)
+
+ @test -f $(DOC_MODULE)-sections.txt || \
+ cp $(srcdir)/$(DOC_MODULE)-sections.txt $(builddir)
+
+ $(AM_V_GEN)gtkdoc-scan --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --ignore-decorators="__ul_attribute__\(.*\)" \
+ --ignore-headers="$(IGNORE_HFILES)" \
+ --output-dir=$(builddir) \
+ $(SCAN_OPTIONS) $(EXTRA_HFILES)
+
+ @ if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \
+ CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" RUN="$(GTKDOC_RUN)" \
+ CFLAGS="$(GTKDOC_CFLAGS) $(CFLAGS)" LDFLAGS="$(GTKDOC_LIBS) \
+ $(LDFLAGS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) \
+ --module=$(DOC_MODULE) --output-dir=$(builddir) ; \
+ else \
+ for i in $(SCANOBJ_FILES) ; do \
+ test -f $$i || touch $$i ; \
+ done \
+ fi
+ @ touch scan-build.stamp
+
+$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp
+ @true
+
+#### templates ####
+#
+#tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(srcdir)/$(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt
+# @echo 'gtk-doc: Rebuilding template files'
+# test -z $(builddir)/tmpl || $(MKDIR_P) $(builddir)/tmpl
+# gtkdoc-mktmpl --module=$(DOC_MODULE) \
+# $(MKTMPL_OPTIONS)
+# touch tmpl-build.stamp
+#
+#tmpl.stamp: tmpl-build.stamp
+# @true
+#
+#tmpl/*.sgml:
+# @true
+#
+
+#### xml ####
+
+sgml-build.stamp: setup.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-decl.txt $(DOC_MODULE)-sections.txt $(expand_content_files)
+ $(AM_V_GEN)gtkdoc-mkdb --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --output-format=xml \
+ --ignore-files="$(IGNORE_HFILES)" \
+ --expand-content-files="$(expand_content_files)" \
+ --main-sgml-file=$(srcdir)/$(DOC_MAIN_SGML_FILE) \
+ $(MKDB_OPTIONS)
+ @touch sgml-build.stamp
+
+sgml.stamp: sgml-build.stamp
+ @true
+
+#### html ####
+
+html-build.stamp: sgml.stamp $(srcdir)/$(DOC_MAIN_SGML_FILE) $(content_files)
+ @rm -rf $(builddir)/html
+ @$(MKDIR_P) $(builddir)/html
+ $(AM_V_GEN)cd $(builddir)/html && \
+ gtkdoc-mkhtml --path="$(abs_builddir):$(abs_builddir)/xml:$(abs_srcdir)" \
+ $(MKHTML_OPTIONS) \
+ $(DOC_MODULE) \
+ $(abs_srcdir)/$(DOC_MAIN_SGML_FILE)
+
+ @test "x$(HTML_IMAGES)" = "x" || \
+ ( cd $(srcdir) && cp $(HTML_IMAGES) $(abs_builddir)/html )
+
+ $(AM_V_GEN)gtkdoc-fixxref --module-dir=html \
+ --module=$(DOC_MODULE) \
+ --html-dir=$(HTML_DIR) \
+ $(FIXXREF_OPTIONS)
+ @touch html-build.stamp
+
+##############
+
+clean-local:
+ rm -f *~ *.bak
+ rm -rf .libs
+
+distclean-local:
+ rm -rf xml html $(REPORT_FILES) *.stamp \
+ $(DOC_MODULE)-overrides.txt \
+ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
+ test $(abs_builddir) == $(abs_srcdir) || \
+ rm -f $(DOC_MODULE)-*.txt $(DOC_MODULE)-*.xml *.xml.in
+
+install-data-local:
+ installfiles=`echo $(builddir)/html/*`; \
+ if test "$$installfiles" = '$(builddir)/html/*'; \
+ then echo '-- Nothing to install' ; \
+ else \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ $(mkinstalldirs) $${installdir} ; \
+ for i in $$installfiles; do \
+ echo '-- Installing '$$i ; \
+ $(INSTALL_DATA) $$i $${installdir}; \
+ done; \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp2 \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp2; \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp; \
+ fi; \
+ ! which gtkdoc-rebase >/dev/null 2>&1 || \
+ gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$${installdir} ; \
+ fi
+
+uninstall-local:
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ rm -rf $${installdir}
+
+#
+# Require gtk-doc when making dist
+#
+@ENABLE_GTK_DOC_TRUE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@ @echo "*** gtk-doc must be installed and enabled in order to make dist"
+@ENABLE_GTK_DOC_FALSE@ @false
+
+#dist-hook: dist-check-gtkdoc dist-hook-local sgml.stamp html-build.stamp
+# mkdir $(distdir)/tmpl
+# mkdir $(distdir)/xml
+# mkdir $(distdir)/html
+# -cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl
+# -cp $(srcdir)/xml/*.xml $(distdir)/xml
+# cp $(srcdir)/html/* $(distdir)/html
+# -cp $(srcdir)/$(DOC_MODULE).types $(distdir)/
+# -cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/
+# cd $(distdir) && rm -f $(DISTCLEANFILES)
+# ! which gtkdoc-rebase >/dev/null 2>&1 || \
+# gtkdoc-rebase --online --relative --html-dir=$(distdir)/html
+#
+#.PHONY : dist-hook-local docs
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/libfdisk/docs/libfdisk-docs.xml b/libfdisk/docs/libfdisk-docs.xml
new file mode 100644
index 0000000..f215065
--- /dev/null
+++ b/libfdisk/docs/libfdisk-docs.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>libfdisk Reference Manual</title>
+ <releaseinfo>for libfdisk version &version;</releaseinfo>
+ <copyright>
+ <year>2014-2022</year>
+ <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+ </copyright>
+ </bookinfo>
+
+ <part id="over">
+ <title>libfdisk Overview</title>
+ <partintro>
+ <para>
+The libfdisk library is used for manipulating with partition tables.
+ </para>
+ <para>
+The library is part of the util-linux package since version 2.26 and is
+available from https://www.kernel.org/pub/linux/utils/util-linux/.
+ </para>
+ </partintro>
+ </part>
+
+ <part>
+ <title>Basic handlers and setting</title>
+ <xi:include href="xml/context.xml"/>
+ <xi:include href="xml/ask.xml"/>
+ <xi:include href="xml/alignment.xml"/>
+ <xi:include href="xml/script.xml"/>
+ </part>
+ <part>
+ <title>Partitioning</title>
+ <xi:include href="xml/label.xml"/>
+ <xi:include href="xml/partition.xml"/>
+ <xi:include href="xml/table.xml"/>
+ <xi:include href="xml/parttype.xml"/>
+ <xi:include href="xml/item.xml"/>
+ <xi:include href="xml/field.xml"/>
+ </part>
+ <part>
+ <title>Label specific functions</title>
+ <xi:include href="xml/dos.xml"/>
+ <xi:include href="xml/gpt.xml"/>
+ <xi:include href="xml/sun.xml"/>
+ <xi:include href="xml/sgi.xml"/>
+ <xi:include href="xml/bsd.xml"/>
+ </part>
+ <part>
+ <title>Misc</title>
+ <xi:include href="xml/iter.xml"/>
+ <xi:include href="xml/utils.xml"/>
+ <xi:include href="xml/init.xml"/>
+ <xi:include href="xml/version-utils.xml"/>
+
+ </part>
+ <index id="api-index">
+ <title>API Index</title>
+ <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.29">
+ <title>Index of new symbols in 2.29</title>
+ <xi:include href="xml/api-index-2.29.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.30">
+ <title>Index of new symbols in 2.30</title>
+ <xi:include href="xml/api-index-2.30.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.31">
+ <title>Index of new symbols in 2.31</title>
+ <xi:include href="xml/api-index-2.31.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.32">
+ <title>Index of new symbols in 2.32</title>
+ <xi:include href="xml/api-index-2.32.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.33">
+ <title>Index of new symbols in 2.33</title>
+ <xi:include href="xml/api-index-2.33.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.35">
+ <title>Index of new symbols in 2.35</title>
+ <xi:include href="xml/api-index-2.35.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.36">
+ <title>Index of new symbols in 2.36</title>
+ <xi:include href="xml/api-index-2.36.xml"><xi:fallback /></xi:include>
+ </index>
+</book>
diff --git a/libfdisk/docs/libfdisk-sections.txt b/libfdisk/docs/libfdisk-sections.txt
new file mode 100644
index 0000000..1589413
--- /dev/null
+++ b/libfdisk/docs/libfdisk-sections.txt
@@ -0,0 +1,400 @@
+<SECTION>
+<FILE>init</FILE>
+fdisk_init_debug
+</SECTION>
+
+<SECTION>
+<FILE>version-utils</FILE>
+LIBFDISK_MAJOR_VERSION
+LIBFDISK_MINOR_VERSION
+LIBFDISK_PATCH_VERSION
+fdisk_parse_version_string
+fdisk_get_library_version
+fdisk_get_library_features
+LIBFDISK_VERSION
+</SECTION>
+
+<SECTION>
+<FILE>ask</FILE>
+fdisk_info
+fdisk_warn
+fdisk_warnx
+fdisk_set_ask
+<SUBSECTION>
+fdisk_ask
+fdisk_is_ask
+fdisk_ask_get_query
+fdisk_ask_get_type
+fdisk_ask_menu_get_default
+fdisk_ask_menu_get_item
+fdisk_ask_menu_get_nitems
+fdisk_ask_menu_get_result
+fdisk_ask_menu_set_result
+fdisk_ask_number
+fdisk_ask_number_get_base
+fdisk_ask_number_get_default
+fdisk_ask_number_get_high
+fdisk_ask_number_get_low
+fdisk_ask_number_get_range
+fdisk_ask_number_get_result
+fdisk_ask_number_get_unit
+fdisk_ask_number_inchars
+fdisk_ask_number_is_wrap_negative
+fdisk_ask_number_set_relative
+fdisk_ask_number_set_result
+fdisk_ask_partnum
+fdisk_ask_print_get_errno
+fdisk_ask_print_get_mesg
+fdisk_ask_string
+fdisk_ask_string_get_result
+fdisk_ask_string_set_result
+fdisk_ask_yesno
+fdisk_ask_yesno_get_result
+fdisk_ask_yesno_set_result
+fdisk_asktype
+fdisk_ref_ask
+fdisk_unref_ask
+</SECTION>
+
+<SECTION>
+<FILE>alignment</FILE>
+FDISK_ALIGN_DOWN
+FDISK_ALIGN_NEAREST
+FDISK_ALIGN_UP
+fdisk_sector_t
+fdisk_align_lba
+fdisk_align_lba_in_range
+fdisk_has_user_device_properties
+fdisk_lba_is_phy_aligned
+fdisk_override_geometry
+fdisk_reset_alignment
+fdisk_reset_device_properties
+fdisk_save_user_geometry
+fdisk_save_user_grain
+fdisk_save_user_sector_size
+</SECTION>
+
+<SECTION>
+<FILE>label</FILE>
+fdisk_create_disklabel
+fdisk_list_disklabel
+fdisk_locate_disklabel
+fdisk_reorder_partitions
+fdisk_set_disklabel_id
+fdisk_set_disklabel_id_from_string
+fdisk_set_partition_type
+fdisk_toggle_partition_flag
+fdisk_verify_disklabel
+fdisk_write_disklabel
+<SUBSECTION>
+fdisk_get_disklabel_id
+fdisk_get_label
+fdisk_get_nlabels
+fdisk_next_label
+fdisk_get_npartitions
+<SUBSECTION>
+fdisk_label
+fdisk_is_label
+fdisk_label_advparse_parttype
+fdisk_label_get_field
+fdisk_label_get_field_by_name
+fdisk_label_get_fields_ids
+fdisk_label_get_fields_ids_all
+fdisk_label_get_geomrange_cylinders
+fdisk_label_get_geomrange_heads
+fdisk_label_get_geomrange_sectors
+fdisk_label_get_name
+fdisk_label_get_nparttypes
+fdisk_label_get_parttype
+fdisk_label_get_parttype_from_code
+fdisk_label_get_parttype_from_string
+fdisk_label_get_parttype_shortcut
+fdisk_label_get_type
+fdisk_label_has_code_parttypes
+fdisk_label_has_parttypes_shortcuts
+fdisk_label_is_changed
+fdisk_label_is_disabled
+fdisk_label_parse_parttype
+fdisk_label_require_geometry
+fdisk_label_set_changed
+fdisk_label_set_disabled
+fdisk_labeltype
+</SECTION>
+
+<SECTION>
+<FILE>script</FILE>
+fdisk_set_script
+fdisk_get_script
+<SUBSECTION>
+fdisk_apply_script
+fdisk_apply_script_headers
+<SUBSECTION>
+fdisk_script
+fdisk_new_script
+fdisk_new_script_from_file
+fdisk_ref_script
+fdisk_script_enable_json
+fdisk_script_get_header
+fdisk_script_get_nlines
+fdisk_script_set_table
+fdisk_script_get_table
+fdisk_script_has_force_label
+fdisk_script_read_context
+fdisk_script_read_file
+fdisk_script_read_line
+fdisk_script_set_header
+fdisk_script_set_fgets
+fdisk_script_write_file
+fdisk_script_set_userdata
+fdisk_script_get_userdata
+fdisk_unref_script
+</SECTION>
+
+<SECTION>
+<FILE>bsd</FILE>
+fdisk_bsd_edit_disklabel
+fdisk_bsd_link_partition
+fdisk_bsd_write_bootstrap
+</SECTION>
+
+<SECTION>
+<FILE>partition</FILE>
+fdisk_add_partition
+fdisk_delete_all_partitions
+fdisk_delete_partition
+fdisk_get_partition
+fdisk_is_partition_used
+fdisk_set_partition
+fdisk_wipe_partition
+<SUBSECTION>
+fdisk_partition
+fdisk_new_partition
+fdisk_partition_cmp_partno
+fdisk_partition_cmp_start
+fdisk_partition_end_follow_default
+fdisk_partition_end_is_default
+fdisk_partition_get_attrs
+fdisk_partition_get_end
+fdisk_partition_get_name
+fdisk_partition_get_parent
+fdisk_partition_get_partno
+fdisk_partition_get_size
+fdisk_partition_get_start
+fdisk_partition_get_type
+fdisk_partition_get_uuid
+fdisk_partition_has_end
+fdisk_partition_has_partno
+fdisk_partition_has_size
+fdisk_partition_has_start
+fdisk_partition_has_wipe
+fdisk_partition_is_bootable
+fdisk_partition_is_container
+fdisk_partition_is_freespace
+fdisk_partition_is_nested
+fdisk_partition_is_used
+fdisk_partition_is_wholedisk
+fdisk_partition_next_partno
+fdisk_partition_partno_follow_default
+fdisk_partition_set_attrs
+fdisk_partition_set_name
+fdisk_partition_set_partno
+fdisk_partition_set_size
+fdisk_partition_set_start
+fdisk_partition_set_type
+fdisk_partition_set_uuid
+fdisk_partition_size_explicit
+fdisk_partition_start_follow_default
+fdisk_partition_start_is_default
+fdisk_partition_to_string
+fdisk_partition_unset_partno
+fdisk_partition_unset_size
+fdisk_partition_unset_start
+fdisk_ref_partition
+fdisk_reset_partition
+fdisk_unref_partition
+</SECTION>
+
+<SECTION>
+<FILE>dos</FILE>
+DOS_FLAG_ACTIVE
+fdisk_dos_enable_compatible
+fdisk_dos_is_compatible
+fdisk_dos_move_begin
+fdisk_dos_fix_chs
+</SECTION>
+
+<SECTION>
+<FILE>sgi</FILE>
+SGI_FLAG_BOOT
+SGI_FLAG_SWAP
+fdisk_sgi_create_info
+fdisk_sgi_set_bootfile
+</SECTION>
+
+<SECTION>
+<FILE>gpt</FILE>
+fdisk_gpt_is_hybrid
+fdisk_gpt_get_partition_attrs
+fdisk_gpt_set_partition_attrs
+fdisk_gpt_set_npartitions
+fdisk_gpt_disable_relocation
+fdisk_gpt_enable_minimize
+GPT_FLAG_REQUIRED
+GPT_FLAG_NOBLOCK
+GPT_FLAG_LEGACYBOOT
+GPT_FLAG_GUIDSPECIFIC
+</SECTION>
+
+<SECTION>
+<FILE>sun</FILE>
+fdisk_sun_set_alt_cyl
+fdisk_sun_set_ilfact
+fdisk_sun_set_pcylcount
+fdisk_sun_set_rspeed
+fdisk_sun_set_xcyl
+</SECTION>
+
+<SECTION>
+<FILE>parttype</FILE>
+fdisk_parttype
+fdisk_copy_parttype
+fdisk_new_parttype
+fdisk_new_unknown_parttype
+fdisk_parttype_get_code
+fdisk_parttype_get_name
+fdisk_parttype_get_string
+fdisk_parttype_is_unknown
+fdisk_parttype_parser_flags
+fdisk_parttype_set_code
+fdisk_parttype_set_name
+fdisk_parttype_set_typestr
+fdisk_ref_parttype
+fdisk_unref_parttype
+</SECTION>
+
+<SECTION>
+<FILE>table</FILE>
+fdisk_get_freespaces
+fdisk_get_partitions
+<SUBSECTION>
+fdisk_table
+fdisk_apply_table
+fdisk_new_table
+fdisk_ref_table
+fdisk_reset_table
+fdisk_table_add_partition
+fdisk_table_get_nents
+fdisk_table_get_partition
+fdisk_table_get_partition_by_partno
+fdisk_table_is_empty
+fdisk_table_next_partition
+fdisk_table_remove_partition
+fdisk_table_sort_partitions
+fdisk_table_wrong_order
+fdisk_unref_table
+</SECTION>
+
+
+<SECTION>
+<FILE>context</FILE>
+fdisk_context
+fdisk_assign_device
+fdisk_assign_device_by_fd
+fdisk_deassign_device
+fdisk_reassign_device
+fdisk_device_is_used
+fdisk_enable_bootbits_protection
+fdisk_enable_details
+fdisk_enable_listonly
+fdisk_enable_wipe
+fdisk_disable_dialogs
+fdisk_get_alignment_offset
+fdisk_get_collision
+fdisk_get_devfd
+fdisk_get_devmodel
+fdisk_get_devname
+fdisk_get_devno
+fdisk_get_disklabel_item
+fdisk_get_first_lba
+fdisk_get_geom_cylinders
+fdisk_get_geom_heads
+fdisk_get_geom_sectors
+fdisk_get_grain_size
+fdisk_get_last_lba
+fdisk_get_minimal_iosize
+fdisk_get_nsectors
+fdisk_get_optimal_iosize
+fdisk_get_parent
+fdisk_get_physector_size
+fdisk_get_sector_size
+fdisk_get_size_unit
+fdisk_get_unit
+fdisk_get_units_per_sector
+fdisk_has_dialogs
+fdisk_has_label
+fdisk_has_protected_bootbits
+fdisk_has_wipe
+fdisk_is_details
+fdisk_is_labeltype
+fdisk_is_listonly
+fdisk_is_ptcollision
+fdisk_is_readonly
+fdisk_is_regfile
+fdisk_new_context
+fdisk_new_nested_context
+FDISK_PLURAL
+fdisk_ref_context
+fdisk_reread_changes
+fdisk_reread_partition_table
+fdisk_set_first_lba
+fdisk_set_last_lba
+fdisk_set_size_unit
+fdisk_set_unit
+FDISK_SINGULAR
+fdisk_unref_context
+fdisk_use_cylinders
+</SECTION>
+
+<SECTION>
+<FILE>utils</FILE>
+fdisk_partname
+</SECTION>
+
+<SECTION>
+<FILE>iter</FILE>
+fdisk_iter
+fdisk_free_iter
+fdisk_iter_get_direction
+fdisk_new_iter
+fdisk_reset_iter
+</SECTION>
+
+<SECTION>
+<FILE>item</FILE>
+fdisk_new_labelitem
+fdisk_ref_labelitem
+fdisk_reset_labelitem
+fdisk_unref_labelitem
+fdisk_labelitem_get_name
+fdisk_labelitem_get_id
+fdisk_labelitem_get_data_u64
+fdisk_labelitem_get_data_string
+fdisk_labelitem_is_string
+fdisk_labelitem_is_number
+fdisk_labelitem
+fdisk_labelitem_bsd
+fdisk_labelitem_gen
+fdisk_labelitem_gpt
+fdisk_labelitem_sgi
+fdisk_labelitem_sun
+</SECTION>
+
+<SECTION>
+<FILE>field</FILE>
+fdisk_field
+fdisk_field_get_id
+fdisk_field_get_name
+fdisk_field_get_width
+fdisk_field_is_number
+fdisk_fieldtype
+</SECTION>
diff --git a/libfdisk/docs/version.xml b/libfdisk/docs/version.xml
new file mode 100644
index 0000000..d8c7561
--- /dev/null
+++ b/libfdisk/docs/version.xml
@@ -0,0 +1 @@
+2.38.1
diff --git a/libfdisk/docs/version.xml.in b/libfdisk/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libfdisk/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libfdisk/fdisk.pc.in b/libfdisk/fdisk.pc.in
new file mode 100644
index 0000000..bf81df0
--- /dev/null
+++ b/libfdisk/fdisk.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: fdisk
+Description: fdisk library
+Version: @LIBFDISK_VERSION@
+Requires.private: @LIBFDISK_PC_REQUIRES@
+Cflags: -I${includedir}/libfdisk
+Libs: -L${libdir} -lfdisk
diff --git a/libfdisk/meson.build b/libfdisk/meson.build
new file mode 100644
index 0000000..1debb21
--- /dev/null
+++ b/libfdisk/meson.build
@@ -0,0 +1,83 @@
+dir_libfdisk = include_directories('.', 'src')
+
+defs = configuration_data()
+defs.set('LIBFDISK_VERSION', pc_version)
+defs.set('LIBFDISK_MAJOR_VERSION', pc_version.split('.')[0])
+defs.set('LIBFDISK_MINOR_VERSION', pc_version.split('.')[1])
+defs.set('LIBFDISK_PATCH_VERSION', pc_version.split('.')[2])
+
+libfdisk_h = configure_file(
+ input : 'src/libfdisk.h.in',
+ output : 'libfdisk.h',
+ configuration : defs,
+ install : build_libfdisk,
+ install_dir : join_paths(get_option('includedir'), 'libfdisk'),
+)
+
+lib_fdisk_sources = '''
+ src/fdiskP.h
+ src/init.c
+ src/field.c
+ src/item.c
+ src/test.c
+ src/ask.c
+ src/alignment.c
+ src/label.c
+ src/utils.c
+ src/context.c
+ src/parttype.c
+ src/partition.c
+ src/table.c
+ src/iter.c
+ src/script.c
+ src/version.c
+ src/wipe.c
+
+ src/sun.c
+ src/sgi.c
+ src/dos.c
+ src/bsd.c
+ src/gpt.c
+'''.split() + [
+ list_h,
+]
+
+libfdisk_sym = 'src/libfdisk.sym'
+libfdisk_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libfdisk_sym)
+
+lib_fdisk_includes = [dir_include,
+ dir_libblkid,
+ dir_libfdisk,
+ dir_libuuid] # XXX: should this be declared along with the lib?
+
+lib__fdisk = static_library(
+ '_fdisk',
+ lib_fdisk_sources,
+ include_directories : lib_fdisk_includes,
+ dependencies : build_libfdisk ? [] : disabler())
+
+lib_fdisk_static = static_library(
+ 'fdisk',
+ link_whole : lib__fdisk,
+ link_with : [lib_common,
+ lib_blkid.get_static_lib(),
+ lib_uuid.get_static_lib()],
+ install : false)
+
+lib_fdisk = library(
+ 'fdisk',
+ link_whole : lib__fdisk,
+ link_depends : libfdisk_sym,
+ version : libfdisk_version,
+ link_args : ['-Wl,--version-script=@0@'.format(libfdisk_sym_path)],
+ link_with : [lib_common,
+ lib_blkid,
+ lib_uuid],
+ install : build_libfdisk)
+
+if build_libfdisk
+ pkgconfig.generate(lib_fdisk,
+ description : 'fdisk library',
+ subdirs : 'libfdisk',
+ version : pc_version)
+endif
diff --git a/libfdisk/samples/Makemodule.am b/libfdisk/samples/Makemodule.am
new file mode 100644
index 0000000..b67b121
--- /dev/null
+++ b/libfdisk/samples/Makemodule.am
@@ -0,0 +1,16 @@
+
+check_PROGRAMS += \
+ sample-fdisk-mkpart \
+ sample-fdisk-mkpart-fullspec
+
+sample_fdisk_cflags = $(AM_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) \
+ -I$(ul_libfdisk_incdir)
+sample_fdisk_ldadd = $(LDADD) libfdisk.la
+
+sample_fdisk_mkpart_SOURCES = libfdisk/samples/mkpart.c
+sample_fdisk_mkpart_LDADD = $(sample_fdisk_ldadd) libcommon.la
+sample_fdisk_mkpart_CFLAGS = $(sample_fdisk_cflags)
+
+sample_fdisk_mkpart_fullspec_SOURCES = libfdisk/samples/mkpart-fullspec.c
+sample_fdisk_mkpart_fullspec_LDADD = $(sample_fdisk_ldadd) libcommon.la
+sample_fdisk_mkpart_fullspec_CFLAGS = $(sample_fdisk_cflags)
diff --git a/libfdisk/samples/mkpart-fullspec.c b/libfdisk/samples/mkpart-fullspec.c
new file mode 100644
index 0000000..821a688
--- /dev/null
+++ b/libfdisk/samples/mkpart-fullspec.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ *
+ * Libfdisk sample to create partitions by specify all required partition
+ * properties (partno, start and size). The default is only partition type
+ * (except MBR where 4th partition is extended).
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libfdisk.h"
+
+static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)),
+ struct fdisk_ask *ask,
+ void *data)
+{
+ switch(fdisk_ask_get_type(ask)) {
+ case FDISK_ASKTYPE_INFO:
+ fputs(fdisk_ask_print_get_mesg(ask), stdout);
+ fputc('\n', stdout);
+ break;
+ case FDISK_ASKTYPE_WARNX:
+ fflush(stdout);
+ fputs(fdisk_ask_print_get_mesg(ask), stderr);
+ fputc('\n', stderr);
+ break;
+ case FDISK_ASKTYPE_WARN:
+ fflush(stdout);
+ fputs(fdisk_ask_print_get_mesg(ask), stderr);
+ errno = fdisk_ask_print_get_errno(ask);
+ fprintf(stderr, ": %m\n");
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_context *cxt;
+ struct fdisk_partition *pa;
+ const char *label = NULL, *device = NULL;
+ int c;
+ size_t n = 1;
+
+ static const struct option longopts[] = {
+ { "label", required_argument, NULL, 'x' },
+ { "device", required_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ fdisk_init_debug(0);
+
+ while((c = getopt_long(argc, argv, "x:d:h", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'x':
+ label = optarg;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 'h':
+ printf("%s [options] -- <partno,start,size> ...", program_invocation_short_name);
+ fputs(USAGE_SEPARATOR, stdout);
+ puts("Make disklabel and partitions.");
+ puts(" <partno> 1..n (4th is extended for MBR), or '-' for default");
+ puts(" <start> partition start offset in sectors");
+ puts(" <size> partition size in sectors");
+ fputs(USAGE_OPTIONS, stdout);
+ puts(" -x, --label <dos,gpt,...> disk label type (default MBR)");
+ puts(" -d, --device <path> block device");
+ puts(" -h, --help this help");
+ fputs(USAGE_SEPARATOR, stdout);
+ return EXIT_SUCCESS;
+ }
+ }
+
+ if (!device)
+ errx(EXIT_FAILURE, "no device specified");
+ if (!label)
+ label = "dos";
+
+ cxt = fdisk_new_context();
+ if (!cxt)
+ err_oom();
+ fdisk_set_ask(cxt, ask_callback, NULL);
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ err_oom();
+
+ if (fdisk_assign_device(cxt, device, 0))
+ err(EXIT_FAILURE, "failed to assign device");
+ if (fdisk_create_disklabel(cxt, label))
+ err(EXIT_FAILURE, "failed to create disk label");
+
+ fdisk_disable_dialogs(cxt, 1);
+
+ while (optind < argc) {
+ int rc;
+ unsigned int partno = 0;
+ uint64_t start = 0, size = 0;
+ const char *str = argv[optind];
+
+ fdisk_reset_partition(pa);
+ fdisk_partition_end_follow_default(pa, 0);
+
+ if (*str == '-') {
+ /* partno unspecified */
+ if (sscanf(str, "-,%"SCNu64",%"SCNu64"", &start, &size) != 2)
+ errx(EXIT_FAILURE, "failed to parse %s", str);
+ fdisk_partition_partno_follow_default(pa, 1);
+ fdisk_partition_unset_partno(pa);
+ } else {
+ /* partno specified */
+ if (sscanf(str, "%u,%"SCNu64",%"SCNu64"", &partno, &start, &size) != 3)
+ errx(EXIT_FAILURE, "failed to parse %s", str);
+
+ fdisk_partition_partno_follow_default(pa, 0);
+ fdisk_partition_set_partno(pa, partno - 1); /* library uses 0..n */
+ }
+
+ fdisk_partition_set_start(pa, start);
+ fdisk_partition_set_size(pa, size);
+
+ if (fdisk_partition_has_partno(pa))
+ fprintf(stdout, "Requested partition: <partno=%zu,start=%ju,size=%ju>\n",
+ fdisk_partition_get_partno(pa),
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_size(pa));
+ else
+ fprintf(stdout, "Requested partition: <partno=<default>,start=%ju,size=%ju>\n",
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_size(pa));
+
+ if (fdisk_is_label(cxt, DOS)) {
+ /* Make sure last primary partition is extended if user
+ * wants more than 4 partitions.
+ */
+ if ((partno == 4 || (n == 4 && !fdisk_partition_has_partno(pa)))
+ && optind + 1 < argc) {
+ struct fdisk_parttype *type =
+ fdisk_label_parse_parttype(
+ fdisk_get_label(cxt, NULL), "05");
+ if (!type)
+ err_oom();
+ fdisk_partition_set_type(pa, type);
+ fdisk_unref_parttype(type);
+ }
+ }
+
+ rc = fdisk_add_partition(cxt, pa, NULL);
+ if (rc) {
+ errno = -rc;
+ errx(EXIT_FAILURE, "failed to add #%zu partition",
+ fdisk_partition_has_partno(pa) ?
+ fdisk_partition_get_partno(pa) + 1: n);
+ }
+
+ fdisk_reset_partition(pa);
+ optind++;
+ n++;
+ }
+
+ if (fdisk_write_disklabel(cxt))
+ err(EXIT_FAILURE, "failed to write disk label");
+
+ fdisk_deassign_device(cxt, 1);
+ fdisk_unref_context(cxt);
+ fdisk_unref_partition(pa);
+
+ return EXIT_SUCCESS;
+}
diff --git a/libfdisk/samples/mkpart.c b/libfdisk/samples/mkpart.c
new file mode 100644
index 0000000..1e5fd99
--- /dev/null
+++ b/libfdisk/samples/mkpart.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ *
+ * Libfdisk sample to create partitions by specify size, for example:
+ *
+ * mkpart --label dos --device /dev/sdc 2M 2M 2M 10M 1M -
+ *
+ * creates 6 partitions:
+ * - 3 primary (3x 2M)
+ * - 1 extended (1x 10M)
+ * - 2 logical (1x 1M, 1x remaining-space-in-extended-partition)
+ *
+ * Notes:
+ * The sample specifies size and partno for MBR, and size only for another
+ * labels (e.g. GPT).
+ *
+ * The Ask-API does not use anything else than warning/info. The
+ * partitionning has to be done non-interactive.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libfdisk.h"
+
+static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)),
+ struct fdisk_ask *ask,
+ void *data)
+{
+ switch(fdisk_ask_get_type(ask)) {
+ case FDISK_ASKTYPE_INFO:
+ fputs(fdisk_ask_print_get_mesg(ask), stdout);
+ fputc('\n', stdout);
+ break;
+ case FDISK_ASKTYPE_WARNX:
+ fflush(stdout);
+ fputs(fdisk_ask_print_get_mesg(ask), stderr);
+ fputc('\n', stderr);
+ break;
+ case FDISK_ASKTYPE_WARN:
+ fflush(stdout);
+ fputs(fdisk_ask_print_get_mesg(ask), stderr);
+ errno = fdisk_ask_print_get_errno(ask);
+ fprintf(stderr, ": %m\n");
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_context *cxt;
+ struct fdisk_partition *pa;
+ const char *label = NULL, *device = NULL;
+ int n = 0, c, nopartno = 0;
+ unsigned int sectorsize;
+ uint64_t grain = 0;
+
+ static const struct option longopts[] = {
+ { "label", required_argument, NULL, 'x' },
+ { "device", required_argument, NULL, 'd' },
+ { "nopartno", no_argument, NULL, 'p' },
+ { "grain", required_argument, NULL, 'g' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ fdisk_init_debug(0);
+
+ while((c = getopt_long(argc, argv, "g:x:d:h", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'x':
+ label = optarg;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 'p':
+ nopartno = 1;
+ break;
+ case 'g':
+ grain = strtosize_or_err(optarg, "failed to parse grain");
+ break;
+ case 'h':
+ printf("%s [options] <size> ...", program_invocation_short_name);
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs("Make disklabel and partitions.\n", stdout);
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(" -x, --label <dos,gpt,...> disk label type\n", stdout);
+ fputs(" -d, --device <path> block device\n", stdout);
+ fputs(" -p, --nopartno don't set partno (use default)\n", stdout);
+ fputs(" -h, --help this help\n", stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+ return EXIT_SUCCESS;
+ }
+ }
+
+ if (!device)
+ errx(EXIT_FAILURE, "no device specified");
+ if (!label)
+ label = "dos";
+
+ cxt = fdisk_new_context();
+ if (!cxt)
+ err_oom();
+ fdisk_set_ask(cxt, ask_callback, NULL);
+
+ if (grain)
+ fdisk_save_user_grain(cxt, grain);
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ err_oom();
+
+ if (fdisk_assign_device(cxt, device, 0))
+ err(EXIT_FAILURE, "failed to assign device");
+ if (fdisk_create_disklabel(cxt, label))
+ err(EXIT_FAILURE, "failed to create disk label");
+
+ sectorsize = fdisk_get_sector_size(cxt);
+
+ fdisk_disable_dialogs(cxt, 1);
+
+ while (optind < argc) {
+ int rc;
+ uint64_t size;
+ const char *str = argv[optind];
+
+ /* defaults */
+ fdisk_partition_start_follow_default(pa, 1);
+ fdisk_partition_end_follow_default(pa, 1);
+ fdisk_partition_partno_follow_default(pa, 1);
+
+ /* set size */
+ if (isdigit(*str)) {
+ size = strtosize_or_err(argv[optind], "failed to parse partition size");
+ fdisk_partition_set_size(pa, size / sectorsize);
+ fdisk_partition_end_follow_default(pa, 0);
+
+ } else if (*str == '-') {
+ fdisk_partition_end_follow_default(pa, 1);
+ }
+
+ if (fdisk_is_label(cxt, DOS)) {
+ /* For MBR we want to avoid primary/logical dialog.
+ * This is possible by explicitly specified partition
+ * number, <4 means primary, >=4 means logical.
+ */
+ if (!nopartno) {
+ fdisk_partition_partno_follow_default(pa, 0);
+ fdisk_partition_set_partno(pa, n);
+ }
+
+ /* Make sure last primary partition is extended if user
+ * wants more than 4 partitions.
+ */
+ if (n == 3 && optind + 1 < argc) {
+ struct fdisk_parttype *type =
+ fdisk_label_parse_parttype(
+ fdisk_get_label(cxt, NULL), "05");
+ if (!type)
+ err_oom();
+ fdisk_partition_set_type(pa, type);
+ fdisk_unref_parttype(type);
+ }
+ }
+
+ rc = fdisk_add_partition(cxt, pa, NULL);
+ if (rc) {
+ errno = -rc;
+ errx(EXIT_FAILURE, "failed to add #%d partition", n + 1);
+ }
+
+ fdisk_reset_partition(pa);
+ optind++;
+ n++;
+ }
+
+ if (fdisk_write_disklabel(cxt))
+ err(EXIT_FAILURE, "failed to write disk label");
+
+ fdisk_deassign_device(cxt, 1);
+ fdisk_unref_context(cxt);
+ fdisk_unref_partition(pa);
+
+ return EXIT_SUCCESS;
+}
diff --git a/libfdisk/src/Makemodule.am b/libfdisk/src/Makemodule.am
new file mode 100644
index 0000000..9bd64c1
--- /dev/null
+++ b/libfdisk/src/Makemodule.am
@@ -0,0 +1,140 @@
+
+# libfdisk.h is generated, so it's stored in builddir!
+fdiskincdir = $(includedir)/libfdisk
+nodist_fdiskinc_HEADERS = libfdisk/src/libfdisk.h
+
+usrlib_exec_LTLIBRARIES += libfdisk.la
+libfdisk_la_SOURCES = \
+ include/list.h \
+ \
+ libfdisk/src/fdiskP.h \
+ libfdisk/src/init.c \
+ libfdisk/src/field.c \
+ libfdisk/src/item.c \
+ libfdisk/src/test.c \
+ libfdisk/src/ask.c \
+ libfdisk/src/alignment.c \
+ libfdisk/src/label.c \
+ libfdisk/src/utils.c \
+ libfdisk/src/context.c \
+ libfdisk/src/parttype.c \
+ libfdisk/src/partition.c \
+ libfdisk/src/table.c \
+ libfdisk/src/iter.c \
+ libfdisk/src/script.c \
+ libfdisk/src/version.c \
+ libfdisk/src/wipe.c \
+ \
+ libfdisk/src/sun.c \
+ libfdisk/src/sgi.c \
+ libfdisk/src/dos.c \
+ libfdisk/src/bsd.c \
+ libfdisk/src/gpt.c
+
+libfdisk_la_LIBADD = libcommon.la libuuid.la
+
+libfdisk_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_libuuid_incdir) \
+ -I$(ul_libfdisk_incdir) \
+ -I$(top_srcdir)/libfdisk/src
+
+EXTRA_libfdisk_la_DEPENDENCIES = \
+ libfdisk/src/libfdisk.sym
+
+libfdisk_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libfdisk_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libfdisk/src/libfdisk.sym
+endif
+libfdisk_la_LDFLAGS += -version-info $(LIBFDISK_VERSION_INFO)
+
+
+if BUILD_LIBBLKID
+libfdisk_la_LIBADD += libblkid.la
+libfdisk_la_CFLAGS += -I$(ul_libblkid_incdir)
+endif
+
+EXTRA_DIST += \
+ libfdisk/src/libfdisk.sym
+
+if BUILD_LIBFDISK_TESTS
+check_PROGRAMS += \
+ test_fdisk_ask \
+ test_fdisk_gpt \
+ test_fdisk_script \
+ test_fdisk_utils \
+ test_fdisk_version \
+ test_fdisk_item
+
+libfdisk_tests_cflags = -DTEST_PROGRAM $(libfdisk_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+libfdisk_tests_ldflags = libuuid.la -static
+libfdisk_tests_ldadd = libfdisk.la $(LDADD)
+
+if BUILD_LIBBLKID
+libfdisk_tests_ldflags += libblkid.la
+endif
+
+test_fdisk_ask_SOURCES = libfdisk/src/ask.c
+test_fdisk_ask_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_ask_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_ask_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_gpt_SOURCES = libfdisk/src/gpt.c
+test_fdisk_gpt_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_gpt_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_gpt_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_utils_SOURCES = libfdisk/src/utils.c
+test_fdisk_utils_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_utils_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_utils_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_script_SOURCES = libfdisk/src/script.c
+test_fdisk_script_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_script_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_script_LDADD = $(libfdisk_tests_ldadd)
+
+if FUZZING_ENGINE
+check_PROGRAMS += test_fdisk_script_fuzz
+
+# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements
+nodist_EXTRA_test_fdisk_script_fuzz_SOURCES = dummy.cxx
+
+test_fdisk_script_fuzz_SOURCES = libfdisk/src/script.c
+test_fdisk_script_fuzz_CFLAGS = -DFUZZ_TARGET $(libfdisk_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+test_fdisk_script_fuzz_LDFLAGS = $(libfdisk_tests_ldflags) -lpthread
+test_fdisk_script_fuzz_LDADD = $(libfdisk_tests_ldadd) $(LIB_FUZZING_ENGINE)
+endif
+
+test_fdisk_version_SOURCES = libfdisk/src/version.c
+test_fdisk_version_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_version_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_version_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_item_SOURCES = libfdisk/src/item.c
+test_fdisk_item_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_item_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_item_LDADD = $(libfdisk_tests_ldadd)
+
+endif # BUILD_LIBFDISK_TESTS
+
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libfdisk:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libfdisk.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libfdisk.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libfdisk.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libfdisk.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libfdisk.so); \
+ fi
+
+uninstall-hook-libfdisk:
+ rm -f $(DESTDIR)$(libdir)/libfdisk.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libfdisk
+UNINSTALL_HOOKS += uninstall-hook-libfdisk
+
diff --git a/libfdisk/src/alignment.c b/libfdisk/src/alignment.c
new file mode 100644
index 0000000..3ae7219
--- /dev/null
+++ b/libfdisk/src/alignment.c
@@ -0,0 +1,721 @@
+
+#ifdef HAVE_LIBBLKID
+#include <blkid.h>
+#endif
+#include "blkdev.h"
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: alignment
+ * @title: Alignment
+ * @short_description: functions to align partitions and work with disk topology and geometry
+ *
+ * The libfdisk aligns the end of the partitions to make it possible to align
+ * the next partition to the "grain" (see fdisk_get_grain_size()). The grain is
+ * usually 1MiB (or more for devices where optimal I/O is greater than 1MiB).
+ *
+ * It means that the library does not align strictly to physical sector size
+ * (or minimal or optimal I/O), but it uses greater granularity. It makes
+ * partition tables more portable. If you copy disk layout from 512-sector to
+ * 4K-sector device, all partitions are still aligned to physical sectors.
+ *
+ * This unified concept also makes partition tables more user friendly, all
+ * tables look same, LBA of the first partition is 2048 sectors everywhere, etc.
+ *
+ * It's recommended to not change any alignment or device properties. All is
+ * initialized by default by fdisk_assign_device().
+ *
+ * Note that terminology used by libfdisk is:
+ * - device properties: I/O limits (topology), geometry, sector size, ...
+ * - alignment: first, last LBA, grain, ...
+ *
+ * The alignment setting may be modified by disk label driver.
+ */
+
+/*
+ * Alignment according to logical granularity (usually 1MiB)
+ */
+static int lba_is_aligned(struct fdisk_context *cxt, uintmax_t lba)
+{
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+ uintmax_t offset;
+
+ if (cxt->grain > granularity)
+ granularity = cxt->grain;
+
+ offset = (lba * cxt->sector_size) % granularity;
+
+ return !((granularity + cxt->alignment_offset - offset) % granularity);
+}
+
+/*
+ * Alignment according to physical device topology (usually minimal i/o size)
+ */
+static int lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+ uintmax_t offset = (lba * cxt->sector_size) % granularity;
+
+ return !((granularity + cxt->alignment_offset - offset) % granularity);
+}
+
+/**
+ * fdisk_align_lba:
+ * @cxt: context
+ * @lba: address to align
+ * @direction: FDISK_ALIGN_{UP,DOWN,NEAREST}
+ *
+ * This function aligns @lba to the "grain" (see fdisk_get_grain_size()). If the
+ * device uses alignment offset then the result is moved according the offset
+ * to be on the physical boundary.
+ *
+ * Returns: alignment LBA.
+ */
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction)
+{
+ fdisk_sector_t res;
+
+ if (lba_is_aligned(cxt, lba))
+ res = lba;
+ else {
+ fdisk_sector_t sects_in_phy = cxt->grain / cxt->sector_size;
+
+ if (lba < cxt->first_lba)
+ res = cxt->first_lba;
+
+ else if (direction == FDISK_ALIGN_UP)
+ res = ((lba + sects_in_phy) / sects_in_phy) * sects_in_phy;
+
+ else if (direction == FDISK_ALIGN_DOWN)
+ res = (lba / sects_in_phy) * sects_in_phy;
+
+ else /* FDISK_ALIGN_NEAREST */
+ res = ((lba + sects_in_phy / 2) / sects_in_phy) * sects_in_phy;
+
+ if (cxt->alignment_offset && !lba_is_aligned(cxt, res) &&
+ res > cxt->alignment_offset / cxt->sector_size) {
+ /*
+ * apply alignment_offset
+ *
+ * On disk with alignment compensation physical blocks starts
+ * at LBA < 0 (usually LBA -1). It means we have to move LBA
+ * according the offset to be on the physical boundary.
+ */
+ /* fprintf(stderr, "LBA: %llu apply alignment_offset\n", res); */
+ res -= (max(cxt->phy_sector_size, cxt->min_io_size) -
+ cxt->alignment_offset) / cxt->sector_size;
+
+ if (direction == FDISK_ALIGN_UP && res < lba)
+ res += sects_in_phy;
+ }
+ }
+/*
+ if (lba != res)
+ DBG(CXT, ul_debugobj(cxt, "LBA %12ju aligned-%s %12ju [grain=%lus]",
+ (uintmax_t) lba,
+ direction == FDISK_ALIGN_UP ? "up " :
+ direction == FDISK_ALIGN_DOWN ? "down" : "near",
+ (uintmax_t) res,
+ cxt->grain / cxt->sector_size));
+ else
+ DBG(CXT, ul_debugobj(cxt, "LBA %12ju already aligned", (uintmax_t)lba));
+*/
+ return res;
+}
+
+/**
+ * fdisk_align_lba_in_range:
+ * @cxt: context
+ * @lba: LBA
+ * @start: range start
+ * @stop: range stop
+ *
+ * Align @lba, the result has to be between @start and @stop
+ *
+ * Returns: aligned LBA
+ */
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+ fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop)
+{
+ fdisk_sector_t res;
+
+ /*DBG(CXT, ul_debugobj(cxt, "LBA: align in range <%ju..%ju>", (uintmax_t) start, (uintmax_t) stop));*/
+
+ if (start + (cxt->grain / cxt->sector_size) <= stop) {
+ start = fdisk_align_lba(cxt, start, FDISK_ALIGN_UP);
+ stop = fdisk_align_lba(cxt, stop, FDISK_ALIGN_DOWN);
+ }
+
+ if (start + (cxt->grain / cxt->sector_size) > stop) {
+ DBG(CXT, ul_debugobj(cxt, "LBA: area smaller than grain, don't align"));
+ res = lba;
+ goto done;
+ }
+
+ lba = fdisk_align_lba(cxt, lba, FDISK_ALIGN_NEAREST);
+
+ if (lba < start)
+ res = start;
+ else if (lba > stop)
+ res = stop;
+ else
+ res = lba;
+done:
+ DBG(CXT, ul_debugobj(cxt, "%ju in range <%ju..%ju> aligned to %ju",
+ (uintmax_t) lba,
+ (uintmax_t) start,
+ (uintmax_t) stop,
+ (uintmax_t) res));
+ return res;
+}
+
+/**
+ * fdisk_lba_is_phy_aligned:
+ * @cxt: context
+ * @lba: LBA to check
+ *
+ * Check if the @lba is aligned to physical sector boundary.
+ *
+ * Returns: 1 if aligned.
+ */
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ return lba_is_phy_aligned(cxt, lba);
+}
+
+static unsigned long get_sector_size(struct fdisk_context *cxt)
+{
+ int sect_sz;
+
+ if (!fdisk_is_regfile(cxt) &&
+ !blkdev_get_sector_size(cxt->dev_fd, &sect_sz))
+ return (unsigned long) sect_sz;
+
+ return DEFAULT_SECTOR_SIZE;
+}
+
+static void recount_geometry(struct fdisk_context *cxt)
+{
+ if (!cxt->geom.heads)
+ cxt->geom.heads = 255;
+ if (!cxt->geom.sectors)
+ cxt->geom.sectors = 63;
+
+ cxt->geom.cylinders = cxt->total_sectors /
+ (cxt->geom.heads * cxt->geom.sectors);
+}
+
+/**
+ * fdisk_override_geometry:
+ * @cxt: fdisk context
+ * @cylinders: user specified cylinders
+ * @heads: user specified heads
+ * @sectors: user specified sectors
+ *
+ * Overrides auto-discovery. The function fdisk_reset_device_properties()
+ * restores the original setting.
+ *
+ * The difference between fdisk_override_geometry() and fdisk_save_user_geometry()
+ * is that saved user geometry is persistent setting and it's applied always
+ * when device is assigned to the context or device properties are reset.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_override_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors)
+{
+ if (!cxt)
+ return -EINVAL;
+ if (heads)
+ cxt->geom.heads = heads;
+ if (sectors)
+ cxt->geom.sectors = sectors;
+
+ if (cylinders)
+ cxt->geom.cylinders = cylinders;
+ else
+ recount_geometry(cxt);
+
+ fdisk_reset_alignment(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "override C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_geometry:
+ * @cxt: context
+ * @cylinders: C
+ * @heads: H
+ * @sectors: S
+ *
+ * Save user defined geometry to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ if (heads)
+ cxt->user_geom.heads = heads > 256 ? 0 : heads;
+ if (sectors)
+ cxt->user_geom.sectors = sectors >= 64 ? 0 : sectors;
+ if (cylinders)
+ cxt->user_geom.cylinders = cylinders;
+
+ DBG(CXT, ul_debugobj(cxt, "user C/H/S: %u/%u/%u",
+ (unsigned) cxt->user_geom.cylinders,
+ (unsigned) cxt->user_geom.heads,
+ (unsigned) cxt->user_geom.sectors));
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_sector_size:
+ * @cxt: context
+ * @phy: physical sector size
+ * @log: logical sector size
+ *
+ * Save user defined sector sizes to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+ unsigned int phy,
+ unsigned int log)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "user phy/log sector size: %u/%u", phy, log));
+
+ cxt->user_pyh_sector = phy;
+ cxt->user_log_sector = log;
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_grain:
+ * @cxt: context
+ * @grain: size in bytes (>= 512, multiple of 512)
+ *
+ * Save user define grain size. The size is used to align partitions.
+ *
+ * The default is 1MiB (or optimal I/O size if greater than 1MiB). It's strongly
+ * recommended to use the default.
+ *
+ * The smallest possible granularity for partitioning is physical sector size
+ * (or minimal I/O size; the bigger number win). If the user's @grain size is
+ * too small then the smallest possible granularity is used. It means
+ * fdisk_save_user_grain(cxt, 512) forces libfdisk to use grain as small as
+ * possible.
+ *
+ * The setting is applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_grain(struct fdisk_context *cxt, unsigned long grain)
+{
+ if (!cxt || grain % 512)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "user grain size: %lu", grain));
+ cxt->user_grain = grain;
+ return 0;
+}
+
+/**
+ * fdisk_has_user_device_properties:
+ * @cxt: context
+ *
+ * Returns: 1 if user specified any properties
+ */
+int fdisk_has_user_device_properties(struct fdisk_context *cxt)
+{
+ return (cxt->user_pyh_sector || cxt->user_log_sector ||
+ cxt->user_grain ||
+ fdisk_has_user_device_geometry(cxt));
+}
+
+int fdisk_has_user_device_geometry(struct fdisk_context *cxt)
+{
+ return (cxt->user_geom.heads || cxt->user_geom.sectors || cxt->user_geom.cylinders);
+}
+
+int fdisk_apply_user_device_properties(struct fdisk_context *cxt)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "applying user device properties"));
+
+ if (cxt->user_pyh_sector)
+ cxt->phy_sector_size = cxt->user_pyh_sector;
+ if (cxt->user_log_sector) {
+ uint64_t old_total = cxt->total_sectors;
+ uint64_t old_secsz = cxt->sector_size;
+
+ cxt->sector_size = cxt->min_io_size =
+ cxt->io_size = cxt->user_log_sector;
+
+ if (cxt->sector_size != old_secsz) {
+ cxt->total_sectors = (old_total * (old_secsz/512)) / (cxt->sector_size >> 9);
+ DBG(CXT, ul_debugobj(cxt, "new total sectors: %ju", (uintmax_t)cxt->total_sectors));
+ }
+ }
+
+ if (cxt->user_geom.heads)
+ cxt->geom.heads = cxt->user_geom.heads;
+ if (cxt->user_geom.sectors)
+ cxt->geom.sectors = cxt->user_geom.sectors;
+
+ if (cxt->user_geom.cylinders)
+ cxt->geom.cylinders = cxt->user_geom.cylinders;
+ else if (cxt->user_geom.heads || cxt->user_geom.sectors)
+ recount_geometry(cxt);
+
+ fdisk_reset_alignment(cxt);
+
+ if (cxt->user_grain) {
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+
+ cxt->grain = cxt->user_grain < granularity ? granularity : cxt->user_grain;
+ DBG(CXT, ul_debugobj(cxt, "new grain: %lu", cxt->grain));
+ }
+
+ if (cxt->firstsector_bufsz != cxt->sector_size)
+ fdisk_read_firstsector(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "new C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+ DBG(CXT, ul_debugobj(cxt, "new log/phy sector size: %u/%u",
+ (unsigned) cxt->sector_size,
+ (unsigned) cxt->phy_sector_size));
+
+ return 0;
+}
+
+void fdisk_zeroize_device_properties(struct fdisk_context *cxt)
+{
+ assert(cxt);
+
+ cxt->io_size = 0;
+ cxt->optimal_io_size = 0;
+ cxt->min_io_size = 0;
+ cxt->phy_sector_size = 0;
+ cxt->sector_size = 0;
+ cxt->alignment_offset = 0;
+ cxt->grain = 0;
+ cxt->first_lba = 0;
+ cxt->last_lba = 0;
+ cxt->total_sectors = 0;
+
+ memset(&cxt->geom, 0, sizeof(struct fdisk_geometry));
+}
+
+/**
+ * fdisk_reset_device_properties:
+ * @cxt: context
+ *
+ * Resets and discovery topology (I/O limits), geometry, re-read the first
+ * rector on the device if necessary and apply user device setting (geometry
+ * and sector size), then initialize alignment according to label driver (see
+ * fdisk_reset_alignment()).
+ *
+ * You don't have to use this function by default, fdisk_assign_device() is
+ * smart enough to initialize all necessary setting.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_reset_device_properties(struct fdisk_context *cxt)
+{
+ int rc;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "*** resetting device properties"));
+
+ fdisk_zeroize_device_properties(cxt);
+ fdisk_discover_topology(cxt);
+ fdisk_discover_geometry(cxt);
+
+ rc = fdisk_read_firstsector(cxt);
+ if (rc)
+ return rc;
+
+ fdisk_apply_user_device_properties(cxt);
+ return 0;
+}
+
+/*
+ * Generic (label independent) geometry
+ */
+int fdisk_discover_geometry(struct fdisk_context *cxt)
+{
+ fdisk_sector_t nsects = 0;
+ unsigned int h = 0, s = 0;
+
+ assert(cxt);
+ assert(cxt->geom.heads == 0);
+
+ DBG(CXT, ul_debugobj(cxt, "%s: discovering geometry...", cxt->dev_path));
+
+ if (fdisk_is_regfile(cxt))
+ cxt->total_sectors = cxt->dev_st.st_size / cxt->sector_size;
+ else {
+ /* get number of 512-byte sectors, and convert it the real sectors */
+ if (!blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &nsects))
+ cxt->total_sectors = (nsects / (cxt->sector_size >> 9));
+
+ /* what the kernel/bios thinks the geometry is */
+ blkdev_get_geometry(cxt->dev_fd, &h, &s);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "total sectors: %ju (ioctl=%ju)",
+ (uintmax_t) cxt->total_sectors,
+ (uintmax_t) nsects));
+
+ cxt->geom.cylinders = 0;
+ cxt->geom.heads = h;
+ cxt->geom.sectors = s;
+
+ /* obtained heads and sectors */
+ recount_geometry(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "result: C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+ return 0;
+}
+
+int fdisk_discover_topology(struct fdisk_context *cxt)
+{
+#ifdef HAVE_LIBBLKID
+ blkid_probe pr;
+#endif
+ assert(cxt);
+ assert(cxt->sector_size == 0);
+
+ DBG(CXT, ul_debugobj(cxt, "%s: discovering topology...", cxt->dev_path));
+#ifdef HAVE_LIBBLKID
+ DBG(CXT, ul_debugobj(cxt, "initialize libblkid prober"));
+
+ pr = blkid_new_probe();
+ if (pr && blkid_probe_set_device(pr, cxt->dev_fd, 0, 0) == 0) {
+ blkid_topology tp = blkid_probe_get_topology(pr);
+
+ if (tp) {
+ cxt->min_io_size = blkid_topology_get_minimum_io_size(tp);
+ cxt->optimal_io_size = blkid_topology_get_optimal_io_size(tp);
+ cxt->phy_sector_size = blkid_topology_get_physical_sector_size(tp);
+ cxt->alignment_offset = blkid_topology_get_alignment_offset(tp);
+
+ /* I/O size used by fdisk */
+ cxt->io_size = cxt->optimal_io_size;
+ if (!cxt->io_size)
+ /* optimal I/O is optional, default to minimum IO */
+ cxt->io_size = cxt->min_io_size;
+
+ if (cxt->io_size && cxt->phy_sector_size) {
+ if (cxt->io_size == 33553920) {
+ /* 33553920 (32 MiB - 512) is always a controller error */
+ DBG(CXT, ul_debugobj(cxt, "ignore bad I/O size 33553920"));
+ cxt->io_size = cxt->phy_sector_size;
+ } else if ((cxt->io_size % cxt->phy_sector_size) != 0) {
+ /* ignore optimal I/O if not aligned to phy.sector size */
+ DBG(CXT, ul_debugobj(cxt, "ignore misaligned I/O size"));
+ cxt->io_size = cxt->phy_sector_size;
+ }
+ }
+
+ }
+ }
+ blkid_free_probe(pr);
+#endif
+
+ cxt->sector_size = get_sector_size(cxt);
+ if (!cxt->phy_sector_size) /* could not discover physical size */
+ cxt->phy_sector_size = cxt->sector_size;
+
+ /* no blkid or error, use default values */
+ if (!cxt->min_io_size)
+ cxt->min_io_size = cxt->sector_size;
+ if (!cxt->io_size)
+ cxt->io_size = cxt->sector_size;
+
+ DBG(CXT, ul_debugobj(cxt, "result: log/phy sector size: %ld/%ld",
+ cxt->sector_size, cxt->phy_sector_size));
+ DBG(CXT, ul_debugobj(cxt, "result: fdisk/optimal/minimal io: %ld/%ld/%ld",
+ cxt->io_size, cxt->optimal_io_size, cxt->min_io_size));
+ return 0;
+}
+
+static int has_topology(struct fdisk_context *cxt)
+{
+ /*
+ * Assume that the device provides topology info if
+ * optimal_io_size is set or alignment_offset is set or
+ * minimum_io_size is not power of 2.
+ */
+ if (cxt &&
+ (cxt->optimal_io_size ||
+ cxt->alignment_offset ||
+ !is_power_of_2(cxt->min_io_size)))
+ return 1;
+ return 0;
+}
+
+/*
+ * The LBA of the first partition is based on the device geometry and topology.
+ * This offset is generic (and recommended) for all labels.
+ *
+ * Returns: 0 on error or number of logical sectors.
+ */
+static fdisk_sector_t topology_get_first_lba(struct fdisk_context *cxt)
+{
+ fdisk_sector_t x = 0, res;
+
+ if (!cxt)
+ return 0;
+
+ if (!cxt->io_size)
+ fdisk_discover_topology(cxt);
+
+ /*
+ * Align the begin of partitions to:
+ *
+ * a) topology
+ * a2) alignment offset
+ * a1) or physical sector (minimal_io_size, aka "grain")
+ *
+ * b) or default to 1MiB (2048 sectors, Windows Vista default)
+ *
+ * c) or for very small devices use 1 phy.sector
+ */
+ if (has_topology(cxt)) {
+ if (cxt->alignment_offset)
+ x = cxt->alignment_offset;
+ else if (cxt->io_size > 2048 * 512)
+ x = cxt->io_size;
+ }
+ /* default to 1MiB */
+ if (!x)
+ x = 2048 * 512;
+
+ res = x / cxt->sector_size;
+
+ /* don't use huge offset on small devices */
+ if (cxt->total_sectors <= res * 4)
+ res = cxt->phy_sector_size / cxt->sector_size;
+
+ return res;
+}
+
+static unsigned long topology_get_grain(struct fdisk_context *cxt)
+{
+ unsigned long res;
+
+ if (!cxt)
+ return 0;
+
+ if (!cxt->io_size)
+ fdisk_discover_topology(cxt);
+
+ res = cxt->io_size;
+
+ /* use 1MiB grain always when possible */
+ if (res < 2048 * 512)
+ res = 2048 * 512;
+
+ /* don't use huge grain on small devices */
+ if (cxt->total_sectors <= (res * 4 / cxt->sector_size))
+ res = cxt->phy_sector_size;
+
+ return res;
+}
+
+/* apply label alignment setting to the context -- if not sure use
+ * fdisk_reset_alignment()
+ */
+int fdisk_apply_label_device_properties(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (cxt->label && cxt->label->op->reset_alignment) {
+ DBG(CXT, ul_debugobj(cxt, "applying label device properties..."));
+ rc = cxt->label->op->reset_alignment(cxt);
+ }
+ return rc;
+}
+
+/**
+ * fdisk_reset_alignment:
+ * @cxt: fdisk context
+ *
+ * Resets alignment setting to the default and label specific values. This
+ * function does not change device properties (I/O limits, geometry etc.).
+ *
+ * Returns: 0 on success, < 0 in case of error.
+ */
+int fdisk_reset_alignment(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "resetting alignment..."));
+
+ /* default */
+ cxt->grain = topology_get_grain(cxt);
+ cxt->first_lba = topology_get_first_lba(cxt);
+ cxt->last_lba = cxt->total_sectors - 1;
+
+ /* overwrite default by label stuff */
+ rc = fdisk_apply_label_device_properties(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "alignment reset to: "
+ "first LBA=%ju, last LBA=%ju, grain=%lu [rc=%d]",
+ (uintmax_t) cxt->first_lba, (uintmax_t) cxt->last_lba,
+ cxt->grain, rc));
+ return rc;
+}
+
+
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+ fdisk_sector_t un = fdisk_get_units_per_sector(cxt);
+ return (num + un - 1) / un;
+}
+
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+ return fdisk_use_cylinders(cxt) ?
+ (num / fdisk_get_units_per_sector(cxt)) + 1 : num;
+}
+
diff --git a/libfdisk/src/ask.c b/libfdisk/src/ask.c
new file mode 100644
index 0000000..274f6ba
--- /dev/null
+++ b/libfdisk/src/ask.c
@@ -0,0 +1,1077 @@
+
+#include "strutils.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: ask
+ * @title: Ask
+ * @short_description: interface for dialog driven partitioning, warning and info messages
+ *
+ */
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask);
+
+
+/**
+ * fdisk_set_ask:
+ * @cxt: context
+ * @ask_cb: callback
+ * @data: callback data
+ *
+ * Set callback for dialog driven partitioning and library warnings/errors.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_ask(struct fdisk_context *cxt,
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+ void *data)
+{
+ assert(cxt);
+
+ cxt->ask_cb = ask_cb;
+ cxt->ask_data = data;
+ return 0;
+}
+
+struct fdisk_ask *fdisk_new_ask(void)
+{
+ struct fdisk_ask *ask = calloc(1, sizeof(struct fdisk_ask));
+
+ if (!ask)
+ return NULL;
+
+ DBG(ASK, ul_debugobj(ask, "alloc"));
+ ask->refcount = 1;
+ return ask;
+}
+
+void fdisk_reset_ask(struct fdisk_ask *ask)
+{
+ int refcount;
+
+ assert(ask);
+ free(ask->query);
+
+ DBG(ASK, ul_debugobj(ask, "reset"));
+ refcount = ask->refcount;
+
+ if (fdisk_is_ask(ask, MENU))
+ fdisk_ask_menu_reset_items(ask);
+
+ memset(ask, 0, sizeof(*ask));
+ ask->refcount = refcount;
+}
+
+/**
+ * fdisk_ref_ask:
+ * @ask: ask instance
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_ask(struct fdisk_ask *ask)
+{
+ if (ask)
+ ask->refcount++;
+}
+
+
+/**
+ * fdisk_unref_ask:
+ * @ask: ask instance
+ *
+ * Decrements reference counter, on zero the @ask is automatically
+ * deallocated.
+ */
+void fdisk_unref_ask(struct fdisk_ask *ask)
+{
+ if (!ask)
+ return;
+ ask->refcount--;
+
+ if (ask->refcount <= 0) {
+ fdisk_reset_ask(ask);
+ DBG(ASK, ul_debugobj(ask, "free"));
+ free(ask);
+ }
+}
+
+/**
+ * fdisk_ask_get_query:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog string.
+ */
+const char *fdisk_ask_get_query(struct fdisk_ask *ask)
+{
+ assert(ask);
+ return ask->query;
+}
+
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str)
+{
+ assert(ask);
+ return strdup_to_struct_member(ask, query, str);
+}
+
+/**
+ * fdisk_ask_get_type:
+ * @ask: ask instance
+ *
+ * Returns: FDISK_ASKTYPE_*
+ */
+int fdisk_ask_get_type(struct fdisk_ask *ask)
+{
+ assert(ask);
+ return ask->type;
+}
+
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type)
+{
+ assert(ask);
+ ask->type = type;
+ return 0;
+}
+
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask)
+{
+ int rc;
+
+ assert(ask);
+ assert(cxt);
+
+ DBG(ASK, ul_debugobj(ask, "do_ask for '%s'",
+ ask->query ? ask->query :
+ ask->type == FDISK_ASKTYPE_INFO ? "info" :
+ ask->type == FDISK_ASKTYPE_WARNX ? "warnx" :
+ ask->type == FDISK_ASKTYPE_WARN ? "warn" :
+ "?nothing?"));
+
+ if (!fdisk_has_dialogs(cxt) &&
+ !(ask->type == FDISK_ASKTYPE_INFO ||
+ ask->type == FDISK_ASKTYPE_WARNX ||
+ ask->type == FDISK_ASKTYPE_WARN)) {
+ DBG(ASK, ul_debugobj(ask, "dialogs disabled"));
+ return -EINVAL;
+ }
+
+ if (!cxt->ask_cb) {
+ DBG(ASK, ul_debugobj(ask, "no ask callback specified!"));
+ return -EINVAL;
+ }
+
+ rc = cxt->ask_cb(cxt, ask, cxt->ask_data);
+
+ DBG(ASK, ul_debugobj(ask, "do_ask done [rc=%d]", rc));
+ return rc;
+}
+
+#define is_number_ask(a) (fdisk_is_ask(a, NUMBER) || fdisk_is_ask(a, OFFSET))
+
+/**
+ * fdisk_ask_number_get_range:
+ * @ask: ask instance
+ *
+ * Returns: string with range (e.g. "1,3,5-10")
+ */
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.range;
+}
+
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ ask->data.num.range = range;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default number
+ *
+ */
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.dfl;
+}
+
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt)
+{
+ assert(ask);
+ ask->data.num.dfl = dflt;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_low:
+ * @ask: ask instance
+ *
+ * Returns: minimal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.low;
+}
+
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low)
+{
+ assert(ask);
+ ask->data.num.low = low;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_high:
+ * @ask: ask instance
+ *
+ * Returns: maximal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.hig;
+}
+
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high)
+{
+ assert(ask);
+ ask->data.num.hig = high;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_result:
+ * @ask: ask instance
+ *
+ * Returns: result
+ */
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.result;
+}
+
+/**
+ * fdisk_ask_number_set_result:
+ * @ask: ask instance
+ * @result: dialog result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result)
+{
+ assert(ask);
+ ask->data.num.result = result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_base:
+ * @ask: ask instance
+ *
+ * Returns: base when user specify number in relative notation (+size)
+ */
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.base;
+}
+
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base)
+{
+ assert(ask);
+ ask->data.num.base = base;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_unit:
+ * @ask: ask instance
+ *
+ * Returns: number of bytes per the unit
+ */
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.unit;
+}
+
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit)
+{
+ assert(ask);
+ ask->data.num.unit = unit;
+ return 0;
+}
+
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.relative;
+}
+
+/**
+ * fdisk_ask_number_is_wrap_negative:
+ * @ask: ask instance
+ *
+ * The wrap-negative flag can be used to accept negative number from user. In this
+ * case the dialog result is calculated as "high - num" (-N from high limit).
+ *
+ * Returns: 1 or 0.
+ *
+ * Since: 2.33
+ */
+int fdisk_ask_number_is_wrap_negative(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.wrap_negative;
+}
+
+/**
+ * fdisk_ask_number_set_relative
+ * @ask: ask instance
+ * @relative: 0 or 1
+ *
+ * Inform libfdisk that user can specify the number in relative notation rather than
+ * by explicit number. This is useful for some optimization (e.g.
+ * align end of partition, etc.)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative)
+{
+ assert(ask);
+ ask->data.num.relative = relative ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_inchars:
+ * @ask: ask instance
+ *
+ * For example for BSD is normal to address partition by chars rather than by
+ * number (first partition is 'a').
+ *
+ * Returns: 1 if number should be presented as chars
+ *
+ */
+int fdisk_ask_number_inchars(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.inchars;
+}
+
+int fdisk_ask_number_set_wrap_negative(struct fdisk_ask *ask, int wrap_negative)
+{
+ assert(ask);
+ ask->data.num.wrap_negative = wrap_negative ? 1 : 0;
+ return 0;
+}
+
+/*
+ * Generates string with list ranges (e.g. 1,2,5-8) for the 'cur'
+ */
+#define tochar(num) ((int) ('a' + num - 1))
+static char *mk_string_list(char *ptr, size_t *len, size_t *begin,
+ size_t *run, ssize_t cur, int inchar)
+{
+ int rlen;
+
+ if (cur != -1) {
+ if (!*begin) { /* begin of the list */
+ *begin = cur + 1;
+ return ptr;
+ }
+
+ if (*begin + *run == (size_t)cur) { /* no gap, continue */
+ (*run)++;
+ return ptr;
+ }
+ } else if (!*begin) {
+ *ptr = '\0';
+ return ptr; /* end of empty list */
+ }
+
+ /* add to the list */
+ if (!*run)
+ rlen = inchar ? snprintf(ptr, *len, "%c,", tochar(*begin)) :
+ snprintf(ptr, *len, "%zu,", *begin);
+ else if (*run == 1)
+ rlen = inchar ?
+ snprintf(ptr, *len, "%c,%c,", tochar(*begin), tochar(*begin + 1)) :
+ snprintf(ptr, *len, "%zu,%zu,", *begin, *begin + 1);
+ else
+ rlen = inchar ?
+ snprintf(ptr, *len, "%c-%c,", tochar(*begin), tochar(*begin + *run)) :
+ snprintf(ptr, *len, "%zu-%zu,", *begin, *begin + *run);
+
+ if (rlen < 0 || (size_t) rlen >= *len)
+ return NULL;
+
+ ptr += rlen;
+ *len -= rlen;
+
+ if (cur == -1 && *begin) {
+ /* end of the list */
+ *(ptr - 1) = '\0'; /* remove tailing ',' from the list */
+ return ptr;
+ }
+
+ *begin = cur + 1;
+ *run = 0;
+
+ return ptr;
+}
+
+/**
+ * fdisk_ask_partnum:
+ * @cxt: context
+ * @partnum: returns partition number
+ * @wantnew: 0|1
+ *
+ * High-level API to ask for used or unused partition number.
+ *
+ * Returns: 0 on success, < 0 on error, 1 if no free/used partition
+ */
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew)
+{
+ int rc = 0, inchar = 0;
+ char range[BUFSIZ], *ptr = range;
+ size_t i, len = sizeof(range), begin = 0, run = 0;
+ struct fdisk_ask *ask = NULL;
+ __typeof__(ask->data.num) *num;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(partnum);
+
+ if (cxt->label && cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+ inchar = 1;
+
+ DBG(ASK, ul_debug("%s: asking for %s partition number "
+ "(max: %zu, inchar: %s)",
+ cxt->label ? cxt->label->name : "???",
+ wantnew ? "new" : "used",
+ cxt->label ? cxt->label->nparts_max : 0,
+ inchar ? "yes" : "not"));
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ num = &ask->data.num;
+
+ ask->data.num.inchars = inchar ? 1 : 0;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ int used = fdisk_is_partition_used(cxt, i);
+
+ if (wantnew && !used) {
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+ if (!ptr) {
+ rc = -EINVAL;
+ break;
+ }
+ if (!num->low)
+ num->dfl = num->low = i + 1;
+ num->hig = i + 1;
+ } else if (!wantnew && used) {
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+ if (!num->low)
+ num->low = i + 1;
+ num->dfl = num->hig = i + 1;
+ }
+ }
+
+ DBG(ASK, ul_debugobj(ask, "ask limits: low: %"PRIu64", high: %"PRIu64", default: %"PRIu64"",
+ num->low, num->hig, num->dfl));
+
+ if (!rc && !wantnew && num->low == num->hig) {
+ if (num->low > 0) {
+ /* only one existing partition, don't ask, return the number */
+ fdisk_ask_number_set_result(ask, num->low);
+ fdisk_info(cxt, _("Selected partition %ju"), num->low);
+
+ } else if (num->low == 0) {
+ fdisk_warnx(cxt, _("No partition is defined yet!"));
+ rc = 1;
+ }
+ goto dont_ask;
+ }
+ if (!rc && wantnew && num->low == num->hig) {
+ if (num->low > 0) {
+ /* only one free partition, don't ask, return the number */
+ fdisk_ask_number_set_result(ask, num->low);
+ fdisk_info(cxt, _("Selected partition %ju"), num->low);
+ }
+ if (num->low == 0) {
+ fdisk_warnx(cxt, _("No free partition available!"));
+ rc = 1;
+ }
+ goto dont_ask;
+ }
+ if (!rc) {
+ mk_string_list(ptr, &len, &begin, &run, -1, inchar); /* terminate the list */
+ rc = fdisk_ask_number_set_range(ask, range);
+ }
+ if (!rc)
+ rc = fdisk_ask_set_query(ask, _("Partition number"));
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+
+dont_ask:
+ if (!rc) {
+ *partnum = fdisk_ask_number_get_result(ask);
+ if (*partnum)
+ *partnum -= 1;
+ }
+ DBG(ASK, ul_debugobj(ask, "result: %"PRIu64" [rc=%d]\n", fdisk_ask_number_get_result(ask), rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_number:
+ * @cxt: context
+ * @low: minimal possible number
+ * @dflt: default suggestion
+ * @high: maximal possible number
+ * @query: question string
+ * @result: returns result
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_number(struct fdisk_context *cxt,
+ uintmax_t low,
+ uintmax_t dflt,
+ uintmax_t high,
+ const char *query,
+ uintmax_t *result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ if (!rc)
+ fdisk_ask_number_set_low(ask, low);
+ if (!rc)
+ fdisk_ask_number_set_default(ask, dflt);
+ if (!rc)
+ fdisk_ask_number_set_high(ask, high);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_number_get_result(ask);
+
+ DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_string_get_result:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog result
+ */
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, STRING));
+ return ask->data.str.result;
+}
+
+/**
+ * fdisk_ask_string_set_result:
+ * @ask: ask instance
+ * @result: pointer to allocated buffer with string
+ *
+ * You don't have to care about the @result deallocation, libfdisk is going to
+ * deallocate the result when destroy @ask instance.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result)
+{
+ assert(ask);
+ ask->data.str.result = result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_string:
+ * @cxt: context:
+ * @query: question string
+ * @result: returns allocated buffer
+ *
+ * High-level API to ask for strings. Don't forget to deallocate the @result.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_string(struct fdisk_context *cxt,
+ const char *query,
+ char **result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_STRING);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_string_get_result(ask);
+
+ DBG(ASK, ul_debugobj(ask, "result: %s [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_yesno:
+ * @cxt: context
+ * @query: question string
+ * @result: returns 0 (no) or 1 (yes)
+ *
+ * High-level API to ask Yes/No questions
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+ const char *query,
+ int *result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_YESNO);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_yesno_get_result(ask) == 1 ? 1 : 0;
+
+ DBG(ASK, ul_debugobj(ask, "result: %d [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_yesno_get_result:
+ * @ask: ask instance
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, YESNO));
+ return ask->data.yesno.result;
+}
+
+/**
+ * fdisk_ask_yesno_set_result:
+ * @ask: ask instance
+ * @result: 1 or 0
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result)
+{
+ assert(ask);
+ ask->data.yesno.result = result;
+ return 0;
+}
+
+/*
+ * menu
+ */
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ ask->data.menu.dfl = dfl;
+ return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default menu item key
+ */
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ return ask->data.menu.dfl;
+}
+
+/**
+ * fdisk_ask_menu_set_result:
+ * @ask: ask instance
+ * @key: result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ ask->data.menu.result = key;
+ DBG(ASK, ul_debugobj(ask, "menu result: %c\n", key));
+ return 0;
+
+}
+
+/**
+ * fdisk_ask_menu_get_result:
+ * @ask: ask instance
+ * @key: returns selected menu item key
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ if (key)
+ *key = ask->data.menu.result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_item:
+ * @ask: ask menu instance
+ * @idx: wanted menu item index
+ * @key: returns key of the menu item
+ * @name: returns name of the menu item
+ * @desc: returns description of the menu item
+ *
+ * Returns: 0 on success, <0 on error, >0 if idx out-of-range
+ */
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+ const char **name, const char **desc)
+{
+ size_t i;
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (i = 0, mi = ask->data.menu.first; mi; mi = mi->next, i++) {
+ if (i == idx)
+ break;
+ }
+
+ if (!mi)
+ return 1; /* no more items */
+ if (key)
+ *key = mi->key;
+ if (name)
+ *name = mi->name;
+ if (desc)
+ *desc = mi->desc;
+ return 0;
+}
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask)
+{
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (mi = ask->data.menu.first; mi; ) {
+ struct ask_menuitem *next = mi->next;
+ free(mi);
+ mi = next;
+ }
+}
+
+/**
+ * fdisk_ask_menu_get_nitems:
+ * @ask: ask instance
+ *
+ * Returns: number of menu items
+ */
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask)
+{
+ struct ask_menuitem *mi;
+ size_t n;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (n = 0, mi = ask->data.menu.first; mi; mi = mi->next, n++);
+
+ return n;
+}
+
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+ const char *name, const char *desc)
+{
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ mi = calloc(1, sizeof(*mi));
+ if (!mi)
+ return -ENOMEM;
+ mi->key = key;
+ mi->name = name;
+ mi->desc = desc;
+
+ if (!ask->data.menu.first)
+ ask->data.menu.first = mi;
+ else {
+ struct ask_menuitem *last = ask->data.menu.first;
+
+ while (last->next)
+ last = last->next;
+ last->next = mi;
+ }
+
+ DBG(ASK, ul_debugobj(ask, "new menu item: %c, \"%s\" (%s)\n", mi->key, mi->name, mi->desc));
+ return 0;
+}
+
+
+/*
+ * print-like
+ */
+
+#define is_print_ask(a) (fdisk_is_ask(a, WARN) || fdisk_is_ask(a, WARNX) || fdisk_is_ask(a, INFO))
+
+/**
+ * fdisk_ask_print_get_errno:
+ * @ask: ask instance
+ *
+ * Returns: error number for warning/error messages
+ */
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_print_ask(ask));
+ return ask->data.print.errnum;
+}
+
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum)
+{
+ assert(ask);
+ ask->data.print.errnum = errnum;
+ return 0;
+}
+
+/**
+ * fdisk_ask_print_get_mesg:
+ * @ask: ask instance
+ *
+ * Returns: pointer to message
+ */
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_print_ask(ask));
+ return ask->data.print.mesg;
+}
+
+/* does not reallocate the message! */
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg)
+{
+ assert(ask);
+ ask->data.print.mesg = mesg;
+ return 0;
+}
+
+static int do_vprint(struct fdisk_context *cxt, int errnum, int type,
+ const char *fmt, va_list va)
+{
+ struct fdisk_ask *ask;
+ int rc;
+ char *mesg;
+
+ assert(cxt);
+
+ if (vasprintf(&mesg, fmt, va) < 0)
+ return -ENOMEM;
+
+ ask = fdisk_new_ask();
+ if (!ask) {
+ free(mesg);
+ return -ENOMEM;
+ }
+
+ fdisk_ask_set_type(ask, type);
+ fdisk_ask_print_set_mesg(ask, mesg);
+ if (errnum >= 0)
+ fdisk_ask_print_set_errno(ask, errnum);
+ rc = fdisk_do_ask(cxt, ask);
+
+ fdisk_unref_ask(ask);
+ free(mesg);
+ return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parameters
+ *
+ * High-level API to print info messages,
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, -1, FDISK_ASKTYPE_INFO, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parameters
+ *
+ * High-level API to print warning message (errno expected)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, errno, FDISK_ASKTYPE_WARN, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+/**
+ * fdisk_warnx:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable options
+ *
+ * High-level API to print warning message
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, -1, FDISK_ASKTYPE_WARNX, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+int fdisk_info_new_partition(
+ struct fdisk_context *cxt,
+ int num, fdisk_sector_t start, fdisk_sector_t stop,
+ struct fdisk_parttype *t)
+{
+ int rc;
+ char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+ (uint64_t)(stop - start + 1) * cxt->sector_size);
+
+ rc = fdisk_info(cxt,
+ _("Created a new partition %d of type '%s' and of size %s."),
+ num, t ? t->name : _("Unknown"), str);
+ free(str);
+ return rc;
+}
+
+#ifdef TEST_PROGRAM
+static int test_ranges(struct fdisk_test *ts, int argc, char *argv[])
+{
+ /* 1 - 3, 6, 8, 9, 11 13 */
+ size_t nums[] = { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1 };
+ size_t numx[] = { 0, 0, 0 };
+ char range[BUFSIZ], *ptr = range;
+ size_t i, len = sizeof(range), begin = 0, run = 0;
+
+ for (i = 0; i < ARRAY_SIZE(nums); i++) {
+ if (!nums[i])
+ continue;
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+ }
+ mk_string_list(ptr, &len, &begin, &run, -1, 0);
+ printf("list: '%s'\n", range);
+
+ ptr = range;
+ len = sizeof(range), begin = 0, run = 0;
+ for (i = 0; i < ARRAY_SIZE(numx); i++) {
+ if (!numx[i])
+ continue;
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+ }
+ mk_string_list(ptr, &len, &begin, &run, -1, 0);
+ printf("empty list: '%s'\n", range);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--ranges", test_ranges, "generates ranges" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/bsd.c b/libfdisk/src/bsd.c
new file mode 100644
index 0000000..313ae5a
--- /dev/null
+++ b/libfdisk/src/bsd.c
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on the original code from fdisk
+ * written by Bernhard Fastenrath (fasten@informatik.uni-bonn.de)
+ * with code from the NetBSD disklabel command.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, March 1999
+ * David Huggins-Daines <dhuggins@linuxcare.com>, January 2000
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/param.h>
+
+#include "blkdev.h"
+#include "fdiskP.h"
+#include "pt-mbr.h"
+#include "pt-bsd.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: bsd
+ * @title: BSD
+ * @short_description: disk label specific functions
+ *
+ */
+
+static const char *bsd_dktypenames[] = {
+ "unknown",
+ "SMD",
+ "MSCP",
+ "old DEC",
+ "SCSI",
+ "ESDI",
+ "ST506",
+ "HP-IB",
+ "HP-FL",
+ "type 9",
+ "floppy",
+ NULL
+};
+#define BSD_DKMAXTYPES (ARRAY_SIZE(bsd_dktypenames) - 1)
+
+static struct fdisk_parttype bsd_fstypes[] = {
+ {BSD_FS_UNUSED, "unused"},
+ {BSD_FS_SWAP, "swap"},
+ {BSD_FS_V6, "Version 6"},
+ {BSD_FS_V7, "Version 7"},
+ {BSD_FS_SYSV, "System V"},
+ {BSD_FS_V71K, "4.1BSD"},
+ {BSD_FS_V8, "Eighth Edition"},
+ {BSD_FS_BSDFFS, "4.2BSD"},
+#ifdef __alpha__
+ {BSD_FS_EXT2, "ext2"},
+#else
+ {BSD_FS_MSDOS, "MS-DOS"},
+#endif
+ {BSD_FS_BSDLFS, "4.4LFS"},
+ {BSD_FS_OTHER, "unknown"},
+ {BSD_FS_HPFS, "HPFS"},
+ {BSD_FS_ISO9660,"ISO-9660"},
+ {BSD_FS_BOOT, "boot"},
+ {BSD_FS_ADOS, "ADOS"},
+ {BSD_FS_HFS, "HFS"},
+ {BSD_FS_ADVFS, "AdvFS"},
+ { 0, NULL }
+};
+#define BSD_FSMAXTYPES (ARRAY_SIZE(bsd_fstypes)-1)
+
+/*
+ * in-memory fdisk BSD stuff
+ */
+struct fdisk_bsd_label {
+ struct fdisk_label head; /* generic part */
+
+ struct dos_partition *dos_part; /* parent */
+ struct bsd_disklabel bsd; /* on disk label */
+#if defined (__alpha__)
+ /* We access this through a u_int64_t * when checksumming */
+ char bsdbuffer[BSD_BBSIZE] __attribute__((aligned(8)));
+#else
+ char bsdbuffer[BSD_BBSIZE];
+#endif
+};
+
+static int bsd_initlabel(struct fdisk_context *cxt);
+static int bsd_readlabel(struct fdisk_context *cxt);
+static void sync_disks(struct fdisk_context *cxt);
+
+static inline struct fdisk_bsd_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ return (struct fdisk_bsd_label *) cxt->label;
+}
+
+static inline struct bsd_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ return &((struct fdisk_bsd_label *) cxt->label)->bsd;
+}
+
+static struct fdisk_parttype *bsd_partition_parttype(
+ struct fdisk_context *cxt,
+ struct bsd_partition *p)
+{
+ struct fdisk_parttype *t
+ = fdisk_label_get_parttype_from_code(cxt->label, p->p_fstype);
+ return t ? : fdisk_new_unknown_parttype(p->p_fstype, NULL);
+}
+
+
+#if defined (__alpha__)
+static void alpha_bootblock_checksum (char *boot)
+{
+ uint64_t *dp = (uint64_t *) boot, sum = 0;
+ int i;
+
+ for (i = 0; i < 63; i++)
+ sum += dp[i];
+ dp[63] = sum;
+}
+#endif /* __alpha__ */
+
+#define HIDDEN_MASK 0x10
+
+static int is_bsd_partition_type(int type)
+{
+ return (type == MBR_FREEBSD_PARTITION ||
+ type == (MBR_FREEBSD_PARTITION ^ HIDDEN_MASK) ||
+ type == MBR_NETBSD_PARTITION ||
+ type == (MBR_NETBSD_PARTITION ^ HIDDEN_MASK) ||
+ type == MBR_OPENBSD_PARTITION ||
+ type == (MBR_OPENBSD_PARTITION ^ HIDDEN_MASK));
+}
+
+/*
+ * look for DOS partition usable for nested BSD partition table
+ */
+static int bsd_assign_dos_partition(struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < 4; i++) {
+ fdisk_sector_t ss;
+
+ l->dos_part = fdisk_dos_get_partition(cxt->parent, i);
+
+ if (!l->dos_part || !is_bsd_partition_type(l->dos_part->sys_ind))
+ continue;
+
+ ss = dos_partition_get_start(l->dos_part);
+ if (!ss) {
+ fdisk_warnx(cxt, _("Partition %zd: has invalid starting "
+ "sector 0."), i + 1);
+ return -1;
+ }
+
+ if (cxt->parent->dev_path) {
+ free(cxt->dev_path);
+ cxt->dev_path = fdisk_partname(
+ cxt->parent->dev_path, i + 1);
+ }
+
+ DBG(LABEL, ul_debug("partition %zu assigned to BSD", i + 1));
+ return 0;
+ }
+
+ fdisk_warnx(cxt, _("There is no *BSD partition on %s."),
+ cxt->parent->dev_path);
+ free(cxt->dev_path);
+ cxt->dev_path = NULL;
+ l->dos_part = NULL;
+ return 1;
+}
+
+static int bsd_probe_label(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (cxt->parent)
+ rc = bsd_assign_dos_partition(cxt); /* nested BSD partition table */
+ if (!rc)
+ rc = bsd_readlabel(cxt);
+ if (!rc)
+ return 1; /* found BSD */
+ return 0; /* not found */
+}
+
+static int set_parttype(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ struct fdisk_parttype *t)
+{
+ struct bsd_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (partnum >= d->d_npartitions || !t || t->code > UINT8_MAX)
+ return -EINVAL;
+
+ p = &d->d_partitions[partnum];
+ if (t->code == p->p_fstype)
+ return 0;
+
+ p->p_fstype = t->code;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int bsd_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ size_t i;
+ unsigned int begin = 0, end;
+ int rc = 0;
+
+ rc = fdisk_partition_next_partno(pa, cxt, &i);
+ if (rc)
+ return rc;
+ if (i >= BSD_MAXPARTITIONS)
+ return -ERANGE;
+ if (l->dos_part) {
+ begin = dos_partition_get_start(l->dos_part);
+ end = begin + dos_partition_get_size(l->dos_part) - 1;
+ } else
+ end = d->d_secperunit - 1;
+
+ /*
+ * First sector
+ */
+ if (pa && pa->start_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ if (pa->start < begin || pa->start > end)
+ return -ERANGE;
+ begin = pa->start;
+ } else {
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_query(ask,
+ fdisk_use_cylinders(cxt) ?
+ _("First cylinder") : _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+
+ rc = fdisk_do_ask(cxt, ask);
+ begin = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ begin = (begin - 1) * d->d_secpercyl;
+ }
+
+ /*
+ * Last sector
+ */
+ if (pa && pa->end_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ if (begin + pa->size > end)
+ return -ERANGE;
+ end = begin + pa->size - 1ULL;
+ } else {
+ /* ask user by dialog */
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (fdisk_use_cylinders(cxt)) {
+ fdisk_ask_set_query(ask, _("Last cylinder, +/-cylinders or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ } else {
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+ }
+
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, end));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+ fdisk_ask_number_set_base(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ end = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ end = end * d->d_secpercyl - 1;
+ }
+
+ d->d_partitions[i].p_size = end - begin + 1;
+ d->d_partitions[i].p_offset = begin;
+ d->d_partitions[i].p_fstype = BSD_FS_UNUSED;
+
+ if (i >= d->d_npartitions)
+ d->d_npartitions = i + 1;
+ cxt->label->nparts_cur = d->d_npartitions;
+
+ if (pa && pa->type)
+ set_parttype(cxt, i, pa->type);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ if (partno)
+ *partno = i;
+ return 0;
+}
+
+static int bsd_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct bsd_partition *p;
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (n >= d->d_npartitions)
+ return -EINVAL;
+
+ p = &d->d_partitions[n];
+
+ /* we have to stay within parental DOS partition */
+ if (l->dos_part && (fdisk_partition_has_start(pa) ||
+ fdisk_partition_has_size(pa))) {
+
+ fdisk_sector_t dosbegin = dos_partition_get_start(l->dos_part);
+ fdisk_sector_t dosend = dosbegin + dos_partition_get_size(l->dos_part) - 1;
+ fdisk_sector_t begin = fdisk_partition_has_start(pa) ? pa->start : p->p_offset;
+ fdisk_sector_t end = begin + (fdisk_partition_has_size(pa) ? pa->size : p->p_size) - 1;
+
+ if (begin < dosbegin || begin > dosend)
+ return -ERANGE;
+ if (end < dosbegin || end > dosend)
+ return -ERANGE;
+ }
+
+ if (pa->type) {
+ int rc = set_parttype(cxt, n, pa->type);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_partition_has_start(pa))
+ d->d_partitions[n].p_offset = pa->start;
+ if (fdisk_partition_has_size(pa))
+ d->d_partitions[n].p_size = pa->size;
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+/* Returns 0 on success, < 0 on error. */
+static int bsd_create_disklabel(struct fdisk_context *cxt)
+{
+ int rc, yes = 0;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ fdisk_info(cxt, _("The device %s does not contain BSD disklabel."), cxt->dev_path);
+ rc = fdisk_ask_yesno(cxt,
+ _("Do you want to create a BSD disklabel?"),
+ &yes);
+ if (rc)
+ return rc;
+ if (!yes)
+ return 1;
+ if (cxt->parent) {
+ rc = bsd_assign_dos_partition(cxt);
+ if (rc == 1)
+ /* not found DOS partition usable for BSD label */
+ rc = -EINVAL;
+ }
+ if (rc)
+ return rc;
+
+ rc = bsd_initlabel(cxt);
+ if (!rc) {
+ cxt->label->nparts_cur = d->d_npartitions;
+ cxt->label->nparts_max = BSD_MAXPARTITIONS;
+ }
+
+ return rc;
+}
+
+static int bsd_delete_part(
+ struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ d->d_partitions[partnum].p_size = 0;
+ d->d_partitions[partnum].p_offset = 0;
+ d->d_partitions[partnum].p_fstype = BSD_FS_UNUSED;
+
+ if (d->d_npartitions == partnum + 1)
+ while (!d->d_partitions[d->d_npartitions - 1].p_size)
+ d->d_npartitions--;
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int bsd_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct bsd_disklabel *d;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ d = self_disklabel(cxt);
+
+ switch (item->id) {
+ case BSD_LABELITEM_TYPE:
+ item->name = _("Type");
+ item->type = 's';
+ if ((unsigned) d->d_type < BSD_DKMAXTYPES) {
+ item->data.str = strdup(bsd_dktypenames[d->d_type]);
+ if (!item->data.str)
+ rc = -ENOMEM;
+ } else if (asprintf(&item->data.str, "%d", d->d_type) < 0)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_DISK:
+ item->name = _("Disk");
+ item->type = 's';
+ item->data.str = strndup(d->d_typename, sizeof(d->d_typename));
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_PACKNAME:
+ item->name = _("Packname");
+ item->type = 's';
+ item->data.str = strndup(d->d_packname, sizeof(d->d_packname));
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_FLAGS:
+ item->name = _("Flags");
+ item->type = 's';
+ item->data.str = strdup(
+ d->d_flags & BSD_D_REMOVABLE ? _(" removable") :
+ d->d_flags & BSD_D_ECC ? _(" ecc") :
+ d->d_flags & BSD_D_BADSECT ? _(" badsect") : "");
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+
+ /* On various machines the fields of *lp are short/int/long */
+ /* In order to avoid problems, we cast them all uint64. */
+ case BSD_LABELITEM_SECSIZE:
+ item->name = _("Bytes/Sector");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) d->d_secsize;
+ break;
+ case BSD_LABELITEM_NTRACKS:
+ item->name = _("Tracks/Cylinder");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) d->d_ntracks;
+ break;
+ case BSD_LABELITEM_SECPERCYL:
+ item->name = _("Sectors/Cylinder");
+ item->data.num64 = (uint64_t) d->d_secpercyl;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_CYLINDERS:
+ item->name = _("Cylinders");
+ item->data.num64 = (uint64_t) d->d_ncylinders;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_RPM:
+ item->name = _("Rpm");
+ item->data.num64 = (uint64_t) d->d_rpm;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_INTERLEAVE:
+ item->name = _("Interleave");
+ item->data.num64 = (uint64_t) d->d_interleave;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_TRACKSKEW:
+ item->name = _("Trackskew");
+ item->data.num64 = (uint64_t) d->d_trackskew;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_CYLINDERSKEW:
+ item->name = _("Cylinderskew");
+ item->data.num64 = (uint64_t) d->d_cylskew;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_HEADSWITCH:
+ item->name = _("Headswitch");
+ item->data.num64 = (uint64_t) d->d_headswitch;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_TRKSEEK:
+ item->name = _("Track-to-track seek");
+ item->data.num64 = (uint64_t) d->d_trkseek;
+ item->type = 'j';
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static int bsd_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct bsd_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ if (n >= d->d_npartitions)
+ return -EINVAL;
+
+ p = &d->d_partitions[n];
+
+ pa->used = p->p_size ? 1 : 0;
+ if (!pa->used)
+ return 0;
+
+ if (fdisk_use_cylinders(cxt) && d->d_secpercyl) {
+ pa->start_post = p->p_offset % d->d_secpercyl ? '*' : ' ';
+ pa->end_post = (p->p_offset + p->p_size) % d->d_secpercyl ? '*' : ' ';
+ }
+
+ pa->start = p->p_offset;
+ pa->size = p->p_size;
+ pa->type = bsd_partition_parttype(cxt, p);
+
+ if (p->p_fstype == BSD_FS_UNUSED || p->p_fstype == BSD_FS_BSDFFS) {
+ pa->fsize = p->p_fsize;
+ pa->bsize = p->p_fsize * p->p_frag;
+ }
+ if (p->p_fstype == BSD_FS_BSDFFS)
+ pa->cpg = p->p_cpg;
+
+ return 0;
+}
+
+static uint32_t ask_uint32(struct fdisk_context *cxt,
+ uint32_t dflt, char *mesg)
+{
+ uintmax_t res;
+
+ if (fdisk_ask_number(cxt, min(dflt, (uint32_t) 1), dflt,
+ UINT32_MAX, mesg, &res) == 0)
+ return res;
+ return dflt;
+}
+
+static uint16_t ask_uint16(struct fdisk_context *cxt,
+ uint16_t dflt, char *mesg)
+{
+ uintmax_t res;
+
+ if (fdisk_ask_number(cxt, min(dflt, (uint16_t) 1),
+ dflt, UINT16_MAX, mesg, &res) == 0)
+ return res;
+ return dflt;
+}
+
+/**
+ * fdisk_bsd_edit_disklabel:
+ * @cxt: context
+ *
+ * Edits fields in BSD disk label.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ uintmax_t res;
+
+#if defined (__alpha__) || defined (__ia64__)
+ if (fdisk_ask_number(cxt, DEFAULT_SECTOR_SIZE, d->d_secsize,
+ UINT32_MAX, _("bytes/sector"), &res) == 0)
+ d->d_secsize = res;
+
+ d->d_nsectors = ask_uint32(cxt, d->d_nsectors, _("sectors/track"));
+ d->d_ntracks = ask_uint32(cxt, d->d_ntracks, _("tracks/cylinder"));
+ d->d_ncylinders = ask_uint32(cxt, d->d_ncylinders ,_("cylinders"));
+#endif
+ if (fdisk_ask_number(cxt, 1,
+ (uintmax_t) d->d_nsectors * d->d_ntracks,
+ (uintmax_t) d->d_nsectors * d->d_ntracks,
+ _("sectors/cylinder"), &res) == 0)
+ d->d_secpercyl = res;
+
+ d->d_rpm = ask_uint16(cxt, d->d_rpm, _("rpm"));
+ d->d_interleave = ask_uint16(cxt, d->d_interleave, _("interleave"));
+ d->d_trackskew = ask_uint16(cxt, d->d_trackskew, _("trackskew"));
+ d->d_cylskew = ask_uint16(cxt, d->d_cylskew, _("cylinderskew"));
+
+ d->d_headswitch = ask_uint32(cxt, d->d_headswitch, _("headswitch"));
+ d->d_trkseek = ask_uint32(cxt, d->d_trkseek, _("track-to-track seek"));
+
+ d->d_secperunit = d->d_secpercyl * d->d_ncylinders;
+ return 0;
+}
+
+static int bsd_get_bootstrap(struct fdisk_context *cxt,
+ char *path, void *ptr, int size)
+{
+ int fd;
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ fdisk_warn(cxt, _("cannot open %s"), path);
+ return -errno;
+ }
+
+ if (read_all(fd, ptr, size) != size) {
+ fdisk_warn(cxt, _("cannot read %s"), path);
+ close(fd);
+ return -errno;
+ }
+
+ fdisk_info(cxt, _("The bootstrap file %s successfully loaded."), path);
+ close (fd);
+ return 0;
+}
+
+/**
+ * fdisk_bsd_write_bootstrap:
+ * @cxt: context
+ *
+ * Install bootstrap file to the BSD device
+ */
+int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt)
+{
+ struct bsd_disklabel dl, *d = self_disklabel(cxt);
+ struct fdisk_bsd_label *l = self_label(cxt);
+ char *name = d->d_type == BSD_DTYPE_SCSI ? "sd" : "wd";
+ char buf[BUFSIZ];
+ char *res, *dp, *p;
+ int rc;
+ fdisk_sector_t sector;
+
+ snprintf(buf, sizeof(buf),
+ _("Bootstrap: %1$sboot -> boot%1$s (default %1$s)"),
+ name);
+ rc = fdisk_ask_string(cxt, buf, &res);
+ if (rc)
+ goto done;
+ if (res && *res)
+ name = res;
+
+ snprintf(buf, sizeof(buf), "%s/%sboot", BSD_LINUX_BOOTDIR, name);
+ rc = bsd_get_bootstrap(cxt, buf, l->bsdbuffer, (int) d->d_secsize);
+ if (rc)
+ goto done;
+
+ /* We need a backup of the disklabel (might have changed). */
+ dp = &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE];
+ memmove(&dl, dp, sizeof(struct bsd_disklabel));
+
+ /* The disklabel will be overwritten by 0's from bootxx anyway */
+ memset(dp, 0, sizeof(struct bsd_disklabel));
+
+ snprintf(buf, sizeof(buf), "%s/boot%s", BSD_LINUX_BOOTDIR, name);
+ rc = bsd_get_bootstrap(cxt, buf,
+ &l->bsdbuffer[d->d_secsize],
+ (int) d->d_bbsize - d->d_secsize);
+ if (rc)
+ goto done;
+
+ /* check end of the bootstrap */
+ for (p = dp; p < dp + sizeof(struct bsd_disklabel); p++) {
+ if (!*p)
+ continue;
+ fdisk_warnx(cxt, _("Bootstrap overlaps with disklabel!"));
+ return -EINVAL;
+ }
+
+ /* move disklabel back */
+ memmove(dp, &dl, sizeof(struct bsd_disklabel));
+
+ sector = 0;
+ if (l->dos_part)
+ sector = dos_partition_get_start(l->dos_part);
+#if defined (__alpha__)
+ alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+ if (lseek(cxt->dev_fd, (off_t) sector * DEFAULT_SECTOR_SIZE, SEEK_SET) == -1) {
+ fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+ rc = -errno;
+ goto done;
+ }
+ if (write_all(cxt->dev_fd, l->bsdbuffer, BSD_BBSIZE)) {
+ fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+ rc = -errno;
+ goto done;
+ }
+
+ fdisk_info(cxt, _("Bootstrap installed on %s."), cxt->dev_path);
+ sync_disks(cxt);
+
+ rc = 0;
+done:
+ free(res);
+ return rc;
+}
+
+static unsigned short bsd_dkcksum (struct bsd_disklabel *lp)
+{
+ unsigned char *ptr, *end;
+ unsigned short sum = 0;
+
+ ptr = (unsigned char *) lp;
+ end = (unsigned char *) &lp->d_partitions[lp->d_npartitions];
+
+ while (ptr < end) {
+ unsigned short val;
+
+ memcpy(&val, ptr, sizeof(unsigned short));
+ sum ^= val;
+
+ ptr += sizeof(unsigned short);
+ }
+ return sum;
+}
+
+static int bsd_initlabel (struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ struct bsd_partition *pp;
+
+ memset (d, 0, sizeof (struct bsd_disklabel));
+
+ d -> d_magic = BSD_DISKMAGIC;
+
+ if (strncmp (cxt->dev_path, "/dev/sd", 7) == 0)
+ d -> d_type = BSD_DTYPE_SCSI;
+ else
+ d -> d_type = BSD_DTYPE_ST506;
+
+#if !defined (__alpha__)
+ d -> d_flags = BSD_D_DOSPART;
+#else
+ d -> d_flags = 0;
+#endif
+ d -> d_secsize = DEFAULT_SECTOR_SIZE; /* bytes/sector */
+ d -> d_nsectors = cxt->geom.sectors; /* sectors/track */
+ d -> d_ntracks = cxt->geom.heads; /* tracks/cylinder (heads) */
+ d -> d_ncylinders = cxt->geom.cylinders;
+ d -> d_secpercyl = cxt->geom.sectors * cxt->geom.heads;/* sectors/cylinder */
+ if (d -> d_secpercyl == 0)
+ d -> d_secpercyl = 1; /* avoid segfaults */
+ d -> d_secperunit = d -> d_secpercyl * d -> d_ncylinders;
+
+ d -> d_rpm = 3600;
+ d -> d_interleave = 1;
+ d -> d_trackskew = 0;
+ d -> d_cylskew = 0;
+ d -> d_headswitch = 0;
+ d -> d_trkseek = 0;
+
+ d -> d_magic2 = BSD_DISKMAGIC;
+ d -> d_bbsize = BSD_BBSIZE;
+ d -> d_sbsize = BSD_SBSIZE;
+
+ if (l->dos_part) {
+ d->d_npartitions = 4;
+
+ pp = &d->d_partitions[2]; /* Partition C should be the NetBSD partition */
+ pp->p_offset = dos_partition_get_start(l->dos_part);
+ pp->p_size = dos_partition_get_size(l->dos_part);
+ pp->p_fstype = BSD_FS_UNUSED;
+
+ pp = &d -> d_partitions[3]; /* Partition D should be the whole disk */
+ pp->p_offset = 0;
+ pp->p_size = d->d_secperunit;
+ pp->p_fstype = BSD_FS_UNUSED;
+ } else {
+ d->d_npartitions = 3;
+
+ pp = &d->d_partitions[2]; /* Partition C should be the whole disk */
+ pp->p_offset = 0;
+ pp->p_size = d->d_secperunit;
+ pp->p_fstype = BSD_FS_UNUSED;
+ }
+
+ return 0;
+}
+
+/*
+ * Read a bsd_disklabel from sector 0 or from the starting sector of p.
+ * If it has the right magic, return 0.
+ */
+static int bsd_readlabel(struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l;
+ struct bsd_disklabel *d;
+ int t;
+ off_t offset = 0;
+
+ l = self_label(cxt);
+ d = self_disklabel(cxt);
+
+ if (l->dos_part)
+ /* BSD is nested within DOS partition, get the begin of the
+ * partition. Note that DOS uses native sector size. */
+ offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1)
+ return -1;
+ if (read_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer)) < 0)
+ return errno ? -errno : -1;
+
+ /* The offset to begin of the disk label. Note that BSD uses
+ * 512-byte (default) sectors. */
+ memmove(d, &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+ + BSD_LABELOFFSET], sizeof(*d));
+
+ if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) {
+ DBG(LABEL, ul_debug("not found magic"));
+ return -1;
+ }
+
+ for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) {
+ d->d_partitions[t].p_size = 0;
+ d->d_partitions[t].p_offset = 0;
+ d->d_partitions[t].p_fstype = BSD_FS_UNUSED;
+ }
+
+ if (d->d_npartitions > BSD_MAXPARTITIONS)
+ fdisk_warnx(cxt, ("Too many partitions (%d, maximum is %d)."),
+ d->d_npartitions, BSD_MAXPARTITIONS);
+
+ /* let's follow in-PT geometry */
+ cxt->geom.sectors = d->d_nsectors;
+ cxt->geom.heads = d->d_ntracks;
+ cxt->geom.cylinders = d->d_ncylinders;
+
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ cxt->label->nparts_max = BSD_MAXPARTITIONS;
+ DBG(LABEL, ul_debug("read BSD label"));
+ return 0;
+}
+
+static int bsd_write_disklabel(struct fdisk_context *cxt)
+{
+ off_t offset = 0;
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+
+ if (l->dos_part)
+ offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+ d->d_checksum = 0;
+ d->d_checksum = bsd_dkcksum(d);
+
+ /* Update label within boot block. */
+ memmove(&l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+ + BSD_LABELOFFSET], d, sizeof(*d));
+
+#if defined (__alpha__) && BSD_LABELSECTOR == 0
+ /* Write the checksum to the end of the first sector. */
+ alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1) {
+ fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+ return -errno;
+ }
+ if (write_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer))) {
+ fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+ return -errno;
+ }
+ sync_disks(cxt);
+
+ if (cxt->parent && fdisk_label_is_changed(cxt->parent->label))
+ fdisk_info(cxt, _("Disklabel written to %s. (Don't forget to write the %s disklabel too.)"),
+ cxt->dev_path, cxt->parent->dev_path);
+ else
+ fdisk_info(cxt, _("Disklabel written to %s."), cxt->dev_path);
+ return 0;
+}
+
+static void sync_disks(struct fdisk_context *cxt)
+{
+ fdisk_info(cxt, _("Syncing disks."));
+ sync();
+}
+
+static int bsd_translate_fstype (int linux_type)
+{
+ switch (linux_type) {
+ case 0x01: /* DOS 12-bit FAT */
+ case 0x04: /* DOS 16-bit <32M */
+ case 0x06: /* DOS 16-bit >=32M */
+ case 0xe1: /* DOS access */
+ case 0xe3: /* DOS R/O */
+#if !defined (__alpha__)
+ case 0xf2: /* DOS secondary */
+ return BSD_FS_MSDOS;
+#endif
+ case 0x07: /* OS/2 HPFS */
+ return BSD_FS_HPFS;
+ default:
+ break;
+ }
+
+ return BSD_FS_OTHER;
+}
+
+/**
+ * fdisk_bsd_link_partition:
+ * @cxt: context
+ *
+ * Links partition from parent (DOS) to nested BSD partition table.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_link_partition(struct fdisk_context *cxt)
+{
+ size_t k, i;
+ int rc;
+ struct dos_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (!cxt->parent || !fdisk_is_label(cxt->parent, DOS)) {
+ fdisk_warnx(cxt, _("BSD label is not nested within a DOS partition."));
+ return -EINVAL;
+ }
+
+ /* ask for DOS partition */
+ rc = fdisk_ask_partnum(cxt->parent, &k, FALSE);
+ if (rc)
+ return rc;
+ /* ask for BSD partition */
+ rc = fdisk_ask_partnum(cxt, &i, TRUE);
+ if (rc)
+ return rc;
+
+ if (i >= BSD_MAXPARTITIONS)
+ return -EINVAL;
+
+ p = fdisk_dos_get_partition(cxt->parent, k);
+
+ d->d_partitions[i].p_size = dos_partition_get_size(p);
+ d->d_partitions[i].p_offset = dos_partition_get_start(p);
+ d->d_partitions[i].p_fstype = bsd_translate_fstype(p->sys_ind);
+
+ if (i >= d->d_npartitions)
+ d->d_npartitions = i + 1;
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info(cxt, _("BSD partition '%c' linked to DOS partition %zu."),
+ 'a' + (int) i, k + 1);
+ return 0;
+}
+
+
+static int bsd_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (partnum >= BSD_MAXPARTITIONS)
+ return 0;
+
+ return d->d_partitions[partnum].p_size ? 1 : 0;
+}
+
+
+static const struct fdisk_label_operations bsd_operations =
+{
+ .probe = bsd_probe_label,
+ .get_item = bsd_get_disklabel_item,
+ .write = bsd_write_disklabel,
+ .create = bsd_create_disklabel,
+
+ .del_part = bsd_delete_part,
+ .get_part = bsd_get_partition,
+ .set_part = bsd_set_partition,
+ .add_part = bsd_add_partition,
+
+ .part_is_used = bsd_partition_is_used,
+};
+
+static const struct fdisk_field bsd_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Slice"), 1, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 8, 0 },
+ { FDISK_FIELD_FSIZE, N_("Fsize"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_BSIZE, N_("Bsize"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CPG, N_("Cpg"), 5, FDISK_FIELDFL_NUMBER }
+};
+
+/*
+ * allocates BSD label driver
+ */
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_bsd_label *bsd;
+
+ bsd = calloc(1, sizeof(*bsd));
+ if (!bsd)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) bsd;
+ lb->name = "bsd";
+ lb->id = FDISK_DISKLABEL_BSD;
+ lb->op = &bsd_operations;
+ lb->parttypes = bsd_fstypes;
+ lb->nparttypes = ARRAY_SIZE(bsd_fstypes) - 1;
+
+ lb->fields = bsd_fields;
+ lb->nfields = ARRAY_SIZE(bsd_fields);
+
+ lb->flags |= FDISK_LABEL_FL_INCHARS_PARTNO;
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) bsd;
+}
diff --git a/libfdisk/src/context.c b/libfdisk/src/context.c
new file mode 100644
index 0000000..083b255
--- /dev/null
+++ b/libfdisk/src/context.c
@@ -0,0 +1,1554 @@
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "blkdev.h"
+#ifdef __linux__
+# include "partx.h"
+#endif
+#include "loopdev.h"
+#include "fdiskP.h"
+
+#include "strutils.h"
+
+/**
+ * SECTION: context
+ * @title: Context
+ * @short_description: stores info about device, labels etc.
+ *
+ * The library distinguish between three types of partitioning objects.
+ *
+ * on-disk label data
+ * - disk label specific
+ * - probed and read by disklabel drivers when assign device to the context
+ * or when switch to another disk label type
+ * - only fdisk_write_disklabel() modify on-disk data
+ *
+ * in-memory label data
+ * - generic data and disklabel specific data stored in struct fdisk_label
+ * - all partitioning operations are based on in-memory data only
+ *
+ * struct fdisk_partition
+ * - provides abstraction to present partitions to users
+ * - fdisk_partition is possible to gather to fdisk_table container
+ * - used as unified template for new partitions
+ * - used (with fdisk_table) in fdisk scripts
+ * - the struct fdisk_partition is always completely independent object and
+ * any change to the object has no effect to in-memory (or on-disk) label data
+ *
+ * Don't forget to inform kernel about changes by fdisk_reread_partition_table()
+ * or more smart fdisk_reread_changes().
+ */
+
+/**
+ * fdisk_new_context:
+ *
+ * Returns: newly allocated libfdisk handler
+ */
+struct fdisk_context *fdisk_new_context(void)
+{
+ struct fdisk_context *cxt;
+
+ cxt = calloc(1, sizeof(*cxt));
+ if (!cxt)
+ return NULL;
+
+ DBG(CXT, ul_debugobj(cxt, "alloc"));
+ cxt->dev_fd = -1;
+ cxt->refcount = 1;
+
+ INIT_LIST_HEAD(&cxt->wipes);
+
+ /*
+ * Allocate label specific structs.
+ *
+ * This is necessary (for example) to store label specific
+ * context setting.
+ */
+ cxt->labels[ cxt->nlabels++ ] = fdisk_new_gpt_label(cxt);
+ cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt);
+ cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt);
+ cxt->labels[ cxt->nlabels++ ] = fdisk_new_sgi_label(cxt);
+ cxt->labels[ cxt->nlabels++ ] = fdisk_new_sun_label(cxt);
+
+ bindtextdomain(LIBFDISK_TEXTDOMAIN, LOCALEDIR);
+
+ return cxt;
+}
+
+static int init_nested_from_parent(struct fdisk_context *cxt, int isnew)
+{
+ struct fdisk_context *parent;
+
+ assert(cxt);
+ assert(cxt->parent);
+
+ parent = cxt->parent;
+
+ INIT_LIST_HEAD(&cxt->wipes);
+
+ cxt->alignment_offset = parent->alignment_offset;
+ cxt->ask_cb = parent->ask_cb;
+ cxt->ask_data = parent->ask_data;
+ cxt->dev_fd = parent->dev_fd;
+ cxt->first_lba = parent->first_lba;
+ cxt->firstsector_bufsz = parent->firstsector_bufsz;
+ cxt->firstsector = parent->firstsector;
+ cxt->geom = parent->geom;
+ cxt->grain = parent->grain;
+ cxt->io_size = parent->io_size;
+ cxt->last_lba = parent->last_lba;
+ cxt->min_io_size = parent->min_io_size;
+ cxt->optimal_io_size = parent->optimal_io_size;
+ cxt->phy_sector_size = parent->phy_sector_size;
+ cxt->readonly = parent->readonly;
+ cxt->script = parent->script;
+ fdisk_ref_script(cxt->script);
+ cxt->sector_size = parent->sector_size;
+ cxt->total_sectors = parent->total_sectors;
+ cxt->user_geom = parent->user_geom;
+ cxt->user_log_sector = parent->user_log_sector;
+ cxt->user_pyh_sector = parent->user_pyh_sector;
+
+ /* parent <--> nested independent setting, initialize for new nested
+ * contexts only */
+ if (isnew) {
+ cxt->listonly = parent->listonly;
+ cxt->display_details = parent->display_details;
+ cxt->display_in_cyl_units = parent->display_in_cyl_units;
+ cxt->protect_bootbits = parent->protect_bootbits;
+ }
+
+ free(cxt->dev_model);
+ cxt->dev_model = NULL;
+ cxt->dev_model_probed = 0;
+
+ return strdup_between_structs(cxt, parent, dev_path);
+}
+
+/**
+ * fdisk_new_nested_context:
+ * @parent: parental context
+ * @name: optional label name (e.g. "bsd")
+ *
+ * Create a new nested fdisk context for nested disk labels (e.g. BSD or PMBR).
+ * The function also probes for the nested label on the device if device is
+ * already assigned to parent.
+ *
+ * The new context is initialized according to @parent and both context shares
+ * some settings and file descriptor to the device. The child propagate some
+ * changes (like fdisk_assign_device()) to parent, but it does not work
+ * vice-versa. The behavior is undefined if you assign another device to
+ * parent.
+ *
+ * Returns: new context for nested partition table.
+ */
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent,
+ const char *name)
+{
+ struct fdisk_context *cxt;
+ struct fdisk_label *lb = NULL;
+
+ assert(parent);
+
+ cxt = calloc(1, sizeof(*cxt));
+ if (!cxt)
+ return NULL;
+
+ DBG(CXT, ul_debugobj(parent, "alloc nested [%p] [name=%s]", cxt, name));
+ cxt->refcount = 1;
+
+ fdisk_ref_context(parent);
+ cxt->parent = parent;
+
+ if (init_nested_from_parent(cxt, 1) != 0) {
+ cxt->parent = NULL;
+ fdisk_unref_context(cxt);
+ return NULL;
+ }
+
+ if (name) {
+ if (strcasecmp(name, "bsd") == 0)
+ lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt);
+ else if (strcasecmp(name, "dos") == 0 || strcasecmp(name, "mbr") == 0)
+ lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt);
+ }
+
+ if (lb && parent->dev_fd >= 0) {
+ DBG(CXT, ul_debugobj(cxt, "probing for nested %s", lb->name));
+
+ cxt->label = lb;
+
+ if (lb->op->probe(cxt) == 1)
+ __fdisk_switch_label(cxt, lb);
+ else {
+ DBG(CXT, ul_debugobj(cxt, "not found %s label", lb->name));
+ if (lb->op->deinit)
+ lb->op->deinit(lb);
+ cxt->label = NULL;
+ }
+ }
+
+ return cxt;
+}
+
+
+/**
+ * fdisk_ref_context:
+ * @cxt: context pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_context(struct fdisk_context *cxt)
+{
+ if (cxt)
+ cxt->refcount++;
+}
+
+/**
+ * fdisk_get_label:
+ * @cxt: context instance
+ * @name: label name (e.g. "gpt")
+ *
+ * If no @name specified then returns the current context label.
+ *
+ * The label is allocated and maintained within the context #cxt. There is
+ * nothing like reference counting for labels, you cannot deallocate the
+ * label.
+ *
+ * Returns: label struct or NULL in case of error.
+ */
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name)
+{
+ size_t i;
+
+ assert(cxt);
+
+ if (!name)
+ return cxt->label;
+
+ if (strcasecmp(name, "mbr") == 0)
+ name = "dos";
+
+ for (i = 0; i < cxt->nlabels; i++)
+ if (cxt->labels[i]
+ && strcasecmp(cxt->labels[i]->name, name) == 0)
+ return cxt->labels[i];
+
+ DBG(CXT, ul_debugobj(cxt, "failed to found %s label driver", name));
+ return NULL;
+}
+
+/**
+ * fdisk_next_label:
+ * @cxt: context instance
+ * @lb: returns pointer to the next label
+ *
+ * <informalexample>
+ * <programlisting>
+ * // print all supported labels
+ * struct fdisk_context *cxt = fdisk_new_context();
+ * struct fdisk_label *lb = NULL;
+ *
+ * while (fdisk_next_label(cxt, &lb) == 0)
+ * print("label name: %s\n", fdisk_label_get_name(lb));
+ * fdisk_unref_context(cxt);
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: <0 in case of error, 0 on success, 1 at the end.
+ */
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb)
+{
+ size_t i;
+ struct fdisk_label *res = NULL;
+
+ if (!lb || !cxt)
+ return -EINVAL;
+
+ if (!*lb)
+ res = cxt->labels[0];
+ else {
+ for (i = 1; i < cxt->nlabels; i++) {
+ if (*lb == cxt->labels[i - 1]) {
+ res = cxt->labels[i];
+ break;
+ }
+ }
+ }
+
+ *lb = res;
+ return res ? 0 : 1;
+}
+
+/**
+ * fdisk_get_nlabels:
+ * @cxt: context
+ *
+ * Returns: number of supported label types
+ */
+size_t fdisk_get_nlabels(struct fdisk_context *cxt)
+{
+ return cxt ? cxt->nlabels : 0;
+}
+
+int __fdisk_switch_label(struct fdisk_context *cxt, struct fdisk_label *lb)
+{
+ if (!lb || !cxt)
+ return -EINVAL;
+ if (lb->disabled) {
+ DBG(CXT, ul_debugobj(cxt, "*** attempt to switch to disabled label %s -- ignore!", lb->name));
+ return -EINVAL;
+ }
+ cxt->label = lb;
+ DBG(CXT, ul_debugobj(cxt, "--> switching context to %s!", lb->name));
+
+ fdisk_apply_label_device_properties(cxt);
+ return 0;
+}
+
+/**
+ * fdisk_has_label:
+ * @cxt: fdisk context
+ *
+ * Returns: return 1 if there is label on the device.
+ */
+int fdisk_has_label(struct fdisk_context *cxt)
+{
+ return cxt && cxt->label;
+}
+
+/**
+ * fdisk_has_protected_bootbits:
+ * @cxt: fdisk context
+ *
+ * Returns: return 1 if boot bits protection enabled.
+ */
+int fdisk_has_protected_bootbits(struct fdisk_context *cxt)
+{
+ return cxt && cxt->protect_bootbits;
+}
+
+/**
+ * fdisk_enable_bootbits_protection:
+ * @cxt: fdisk context
+ * @enable: 1 or 0
+ *
+ * The library zeroizes all the first sector when create a new disk label by
+ * default. This function can be used to control this behavior. For now it's
+ * supported for MBR and GPT.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_bootbits_protection(struct fdisk_context *cxt, int enable)
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->protect_bootbits = enable ? 1 : 0;
+ return 0;
+}
+/**
+ * fdisk_disable_dialogs
+ * @cxt: fdisk context
+ * @disable: 1 or 0
+ *
+ * The library uses dialog driven partitioning by default.
+ *
+ * Returns: 0 on success, < 0 on error.
+ *
+ * Since: 2.31
+ */
+int fdisk_disable_dialogs(struct fdisk_context *cxt, int disable)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ cxt->no_disalogs = disable;
+ return 0;
+}
+
+/**
+ * fdisk_has_dialogs
+ * @cxt: fdisk context
+ *
+ * See fdisk_disable_dialogs()
+ *
+ * Returns: 1 if dialog driven partitioning enabled (default), or 0.
+ *
+ * Since: 2.31
+ */
+int fdisk_has_dialogs(struct fdisk_context *cxt)
+{
+ return cxt->no_disalogs == 0;
+}
+
+/**
+ * fdisk_enable_wipe
+ * @cxt: fdisk context
+ * @enable: 1 or 0
+ *
+ * The library removes all PT/filesystem/RAID signatures before it writes
+ * partition table. The probing area where it looks for signatures is from
+ * the begin of the disk. The device is wiped by libblkid.
+ *
+ * See also fdisk_wipe_partition().
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_wipe(struct fdisk_context *cxt, int enable)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ fdisk_set_wipe_area(cxt, 0, cxt->total_sectors, enable);
+ return 0;
+}
+
+/**
+ * fdisk_has_wipe
+ * @cxt: fdisk context
+ *
+ * Returns the current wipe setting. See fdisk_enable_wipe().
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_has_wipe(struct fdisk_context *cxt)
+{
+ if (!cxt)
+ return 0;
+
+ return fdisk_has_wipe_area(cxt, 0, cxt->total_sectors);
+}
+
+
+/**
+ * fdisk_get_collision
+ * @cxt: fdisk context
+ *
+ * Returns: name of the filesystem or RAID detected on the device or NULL.
+ */
+const char *fdisk_get_collision(struct fdisk_context *cxt)
+{
+ return cxt->collision;
+}
+
+/**
+ * fdisk_is_ptcollision:
+ * @cxt: fdisk context
+ *
+ * The collision detected by libblkid (usually another partition table). Note
+ * that libfdisk does not support all partitions tables, so fdisk_has_label()
+ * may return false, but fdisk_is_ptcollision() may return true.
+ *
+ * Since: 2.30
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_is_ptcollision(struct fdisk_context *cxt)
+{
+ return cxt->pt_collision;
+}
+
+/**
+ * fdisk_get_npartitions:
+ * @cxt: context
+ *
+ * The maximal number of the partitions depends on disklabel and does not
+ * have to describe the real limit of PT.
+ *
+ * For example the limit for MBR without extend partition is 4, with extended
+ * partition it's unlimited (so the function returns the current number of all
+ * partitions in this case).
+ *
+ * And for example for GPT it depends on space allocated on disk for array of
+ * entry records (usually 128).
+ *
+ * It's fine to use fdisk_get_npartitions() in loops, but don't forget that
+ * partition may be unused (see fdisk_is_partition_used()).
+ *
+ * <informalexample>
+ * <programlisting>
+ * struct fdisk_partition *pa = NULL;
+ * size_t i, nmax = fdisk_get_npartitions(cxt);
+ *
+ * for (i = 0; i < nmax; i++) {
+ * if (!fdisk_is_partition_used(cxt, i))
+ * continue;
+ * ... do something ...
+ * }
+ * </programlisting>
+ * </informalexample>
+ *
+ * Note that the recommended way to list partitions is to use
+ * fdisk_get_partitions() and struct fdisk_table then ask disk driver for each
+ * individual partitions.
+ *
+ * Returns: maximal number of partitions for the current label.
+ */
+size_t fdisk_get_npartitions(struct fdisk_context *cxt)
+{
+ return cxt && cxt->label ? cxt->label->nparts_max : 0;
+}
+
+/**
+ * fdisk_is_labeltype:
+ * @cxt: fdisk context
+ * @id: FDISK_DISKLABEL_*
+ *
+ * See also fdisk_is_label() macro in libfdisk.h.
+ *
+ * Returns: return 1 if the current label is @id
+ */
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id)
+{
+ assert(cxt);
+
+ return cxt->label && (unsigned)fdisk_label_get_type(cxt->label) == id;
+}
+
+/**
+ * fdisk_get_parent:
+ * @cxt: nested fdisk context
+ *
+ * Returns: pointer to parental context, or NULL
+ */
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->parent;
+}
+
+static void reset_context(struct fdisk_context *cxt)
+{
+ size_t i;
+
+ DBG(CXT, ul_debugobj(cxt, "*** resetting context"));
+
+ /* reset drives' private data */
+ for (i = 0; i < cxt->nlabels; i++)
+ fdisk_deinit_label(cxt->labels[i]);
+
+ if (cxt->parent) {
+ /* the first sector may be independent on parent */
+ if (cxt->parent->firstsector != cxt->firstsector) {
+ DBG(CXT, ul_debugobj(cxt, " firstsector independent on parent (freeing)"));
+ free(cxt->firstsector);
+ }
+ } else {
+ /* we close device only in primary context */
+ if (cxt->dev_fd > -1 && cxt->is_priv)
+ close(cxt->dev_fd);
+ DBG(CXT, ul_debugobj(cxt, " freeing firstsector"));
+ free(cxt->firstsector);
+ }
+
+ free(cxt->dev_path);
+ cxt->dev_path = NULL;
+
+ free(cxt->dev_model);
+ cxt->dev_model = NULL;
+ cxt->dev_model_probed = 0;
+
+ free(cxt->collision);
+ cxt->collision = NULL;
+
+ memset(&cxt->dev_st, 0, sizeof(cxt->dev_st));
+
+ cxt->dev_fd = -1;
+ cxt->is_priv = 0;
+ cxt->is_excl = 0;
+ cxt->firstsector = NULL;
+ cxt->firstsector_bufsz = 0;
+
+ fdisk_zeroize_device_properties(cxt);
+
+ fdisk_unref_script(cxt->script);
+ cxt->script = NULL;
+
+ cxt->label = NULL;
+
+ fdisk_free_wipe_areas(cxt);
+}
+
+/* fdisk_assign_device() body */
+static int fdisk_assign_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly,
+ int priv, int excl)
+{
+ assert(cxt);
+ assert(fd >= 0);
+
+ errno = 0;
+
+ /* redirect request to parent */
+ if (cxt->parent) {
+ int rc, org = fdisk_is_listonly(cxt->parent);
+
+ /* assign_device() is sensitive to "listonly" mode, so let's
+ * follow the current context setting for the parent to avoid
+ * unwanted extra warnings. */
+ fdisk_enable_listonly(cxt->parent, fdisk_is_listonly(cxt));
+
+ rc = fdisk_assign_fd(cxt->parent, fd, fname, readonly, priv, excl);
+ fdisk_enable_listonly(cxt->parent, org);
+
+ if (!rc)
+ rc = init_nested_from_parent(cxt, 0);
+ if (!rc)
+ fdisk_probe_labels(cxt);
+ return rc;
+ }
+
+ reset_context(cxt);
+
+ if (fstat(fd, &cxt->dev_st) != 0)
+ goto fail;
+
+ cxt->readonly = readonly ? 1 : 0;
+ cxt->dev_fd = fd;
+ cxt->is_priv = priv ? 1 : 0;
+ cxt->is_excl = excl ? 1 : 0;
+
+ cxt->dev_path = fname ? strdup(fname) : NULL;
+ if (!cxt->dev_path)
+ goto fail;
+
+ fdisk_discover_topology(cxt);
+ fdisk_discover_geometry(cxt);
+
+ fdisk_apply_user_device_properties(cxt);
+
+ if (fdisk_read_firstsector(cxt) < 0)
+ goto fail;
+
+ /* warn about obsolete stuff on the device if we aren't in list-only */
+ if (!fdisk_is_listonly(cxt) && fdisk_check_collisions(cxt) < 0)
+ goto fail;
+
+ fdisk_probe_labels(cxt);
+ fdisk_apply_label_device_properties(cxt);
+
+ /* Don't report collision if there is already a valid partition table.
+ * The bootbits are wiped when we create a *new* partition table only. */
+ if (fdisk_is_ptcollision(cxt) && fdisk_has_label(cxt)) {
+ cxt->pt_collision = 0;
+ free(cxt->collision);
+ cxt->collision = NULL;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "initialized for %s [%s %s %s]",
+ fname,
+ cxt->readonly ? "READ-ONLY" : "READ-WRITE",
+ cxt->is_excl ? "EXCL" : "",
+ cxt->is_priv ? "PRIV" : ""));
+ return 0;
+fail:
+ {
+ int rc = errno ? -errno : -EINVAL;
+ cxt->dev_fd = -1;
+ DBG(CXT, ul_debugobj(cxt, "failed to assign device [rc=%d]", rc));
+ return rc;
+ }
+}
+
+/**
+ * fdisk_assign_device:
+ * @cxt: context
+ * @fname: path to the device to be handled
+ * @readonly: how to open the device
+ *
+ * Open the device, discovery topology, geometry, detect disklabel, check for
+ * collisions and switch the current label driver to reflect the probing
+ * result.
+ *
+ * If in standard mode (!= non-listonly mode) then also detects for collisions.
+ * The result is accessible by fdisk_get_collision() and
+ * fdisk_is_ptcollision(). The collision (e.g. old obsolete PT) may be removed
+ * by fdisk_enable_wipe(). Note that new PT and old PT may be on different
+ * locations.
+ *
+ * Note that this function resets all generic setting in context.
+ *
+ * If the @cxt is nested context (necessary for example to edit BSD or PMBR)
+ * then the device is assigned to the parental context and necessary properties
+ * are copied to the @cxt. The change is propagated in child->parent direction
+ * only. It's impossible to use a different device for primary and nested
+ * contexts.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_assign_device(struct fdisk_context *cxt,
+ const char *fname, int readonly)
+{
+ int fd, rc, flags = O_CLOEXEC;
+
+ DBG(CXT, ul_debugobj(cxt, "assigning device %s", fname));
+ assert(cxt);
+
+ if (readonly)
+ flags |= O_RDONLY;
+ else
+ flags |= (O_RDWR | O_EXCL);
+
+ errno = 0;
+ fd = open(fname,flags);
+ if (fd < 0 && errno == EBUSY && (flags & O_EXCL)) {
+ flags &= ~O_EXCL;
+ errno = 0;
+ fd = open(fname, flags);
+ }
+
+ if (fd < 0) {
+ rc = -errno;
+ DBG(CXT, ul_debugobj(cxt, "failed to assign device [rc=%d]", rc));
+ return rc;
+ }
+
+ rc = fdisk_assign_fd(cxt, fd, fname, readonly, 1, flags & O_EXCL);
+ if (rc)
+ close(fd);
+ return rc;
+}
+
+/**
+ * fdisk_assign_device_by_fd:
+ * @cxt: context
+ * @fd: device file descriptor
+ * @fname: path to the device (used for dialogs, debugging, partition names, ...)
+ * @readonly: how to use the device
+ *
+ * Like fdisk_assign_device(), but caller is responsible to open and close the
+ * device. The library only fsync() the device on fdisk_deassign_device().
+ *
+ * The device has to be open O_RDWR on @readonly=0.
+ *
+ * Returns: 0 on success, < 0 on error.
+ *
+ * Since: 2.35
+ */
+int fdisk_assign_device_by_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly)
+{
+ DBG(CXT, ul_debugobj(cxt, "assign by fd"));
+ return fdisk_assign_fd(cxt, fd, fname, readonly, 0, 0);
+}
+
+/**
+ * fdisk_deassign_device:
+ * @cxt: context
+ * @nosync: disable sync() after close().
+ *
+ * Call fsync(), close() and than sync(), but for read-only handler close the
+ * device only. If the @cxt is nested context then the request is redirected to
+ * the parent.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync)
+{
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ if (cxt->parent) {
+ int rc = fdisk_deassign_device(cxt->parent, nosync);
+
+ if (!rc)
+ rc = init_nested_from_parent(cxt, 0);
+ return rc;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "de-assigning device %s", cxt->dev_path));
+
+ if (cxt->readonly && cxt->is_priv)
+ close(cxt->dev_fd);
+ else {
+ if (fsync(cxt->dev_fd)) {
+ fdisk_warn(cxt, _("%s: fsync device failed"),
+ cxt->dev_path);
+ return -errno;
+ }
+ if (cxt->is_priv && close(cxt->dev_fd)) {
+ fdisk_warn(cxt, _("%s: close device failed"),
+ cxt->dev_path);
+ return -errno;
+ }
+ if (!nosync) {
+ fdisk_info(cxt, _("Syncing disks."));
+ sync();
+ }
+ }
+
+ free(cxt->dev_path);
+ cxt->dev_path = NULL;
+ cxt->dev_fd = -1;
+ cxt->is_priv = 0;
+ cxt->is_excl = 0;
+
+ return 0;
+}
+
+/**
+ * fdisk_reassign_device:
+ * @cxt: context
+ *
+ * This function is "hard reset" of the context and it does not write anything
+ * to the device. All in-memory changes associated with the context will be
+ * lost. It's recommended to use this function after some fatal problem when the
+ * context (and label specific driver) is in an undefined state.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_reassign_device(struct fdisk_context *cxt)
+{
+ char *devname;
+ int rdonly, rc, fd, priv, excl;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ DBG(CXT, ul_debugobj(cxt, "re-assigning device %s", cxt->dev_path));
+
+ devname = strdup(cxt->dev_path);
+ if (!devname)
+ return -ENOMEM;
+
+ rdonly = cxt->readonly;
+ fd = cxt->dev_fd;
+ priv = cxt->is_priv;
+ excl = cxt->is_excl;
+
+ fdisk_deassign_device(cxt, 1);
+
+ if (priv)
+ /* reopen and assign */
+ rc = fdisk_assign_device(cxt, devname, rdonly);
+ else
+ /* assign only */
+ rc = fdisk_assign_fd(cxt, fd, devname, rdonly, priv, excl);
+
+ free(devname);
+ return rc;
+}
+
+/**
+ * fdisk_reread_partition_table:
+ * @cxt: context
+ *
+ * Force *kernel* to re-read partition table on block devices.
+ *
+ * Returns: 0 on success, < 0 in case of error.
+ */
+int fdisk_reread_partition_table(struct fdisk_context *cxt)
+{
+ int i = 0;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ if (!S_ISBLK(cxt->dev_st.st_mode))
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "calling re-read ioctl"));
+ sync();
+#ifdef BLKRRPART
+ fdisk_info(cxt, _("Calling ioctl() to re-read partition table."));
+ i = ioctl(cxt->dev_fd, BLKRRPART);
+#else
+ errno = ENOSYS;
+ i = 1;
+#endif
+
+ if (i) {
+ fdisk_warn(cxt, _("Re-reading the partition table failed."));
+ fdisk_info(cxt, _(
+ "The kernel still uses the old table. The "
+ "new table will be used at the next reboot "
+ "or after you run partprobe(8) or partx(8)."));
+ return -errno;
+ }
+
+ return 0;
+}
+
+#ifdef __linux__
+static inline int add_to_partitions_array(
+ struct fdisk_partition ***ary,
+ struct fdisk_partition *pa,
+ size_t *n, size_t nmax)
+{
+ if (!*ary) {
+ *ary = calloc(nmax, sizeof(struct fdisk_partition *));
+ if (!*ary)
+ return -ENOMEM;
+ }
+ (*ary)[*n] = pa;
+ (*n)++;
+ return 0;
+}
+#endif
+
+/**
+ * fdisk_reread_changes:
+ * @cxt: context
+ * @org: original layout (on disk)
+ *
+ * Like fdisk_reread_partition_table() but don't forces kernel re-read all
+ * partition table. The BLKPG_* ioctls are used for individual partitions. The
+ * advantage is that unmodified partitions maybe mounted.
+ *
+ * The function behaves like fdisk_reread_partition_table() on systems where
+ * are no available BLKPG_* ioctls.
+ *
+ * Returns: <0 on error, or 0.
+ */
+#ifdef __linux__
+int fdisk_reread_changes(struct fdisk_context *cxt, struct fdisk_table *org)
+{
+ struct fdisk_table *tb = NULL;
+ struct fdisk_iter itr;
+ struct fdisk_partition *pa;
+ struct fdisk_partition **rem = NULL, **add = NULL, **upd = NULL;
+ int change, rc = 0, err = 0;
+ size_t nparts, i, nadds = 0, nupds = 0, nrems = 0;
+ unsigned int ssf;
+
+ DBG(CXT, ul_debugobj(cxt, "rereading changes"));
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ /* the current layout */
+ fdisk_get_partitions(cxt, &tb);
+ /* maximal number of partitions */
+ nparts = max(fdisk_table_get_nents(tb), fdisk_table_get_nents(org));
+
+ while (fdisk_diff_tables(org, tb, &itr, &pa, &change) == 0) {
+ if (change == FDISK_DIFF_UNCHANGED)
+ continue;
+ switch (change) {
+ case FDISK_DIFF_REMOVED:
+ rc = add_to_partitions_array(&rem, pa, &nrems, nparts);
+ break;
+ case FDISK_DIFF_ADDED:
+ rc = add_to_partitions_array(&add, pa, &nadds, nparts);
+ break;
+ case FDISK_DIFF_RESIZED:
+ rc = add_to_partitions_array(&upd, pa, &nupds, nparts);
+ break;
+ case FDISK_DIFF_MOVED:
+ rc = add_to_partitions_array(&rem, pa, &nrems, nparts);
+ if (!rc)
+ rc = add_to_partitions_array(&add, pa, &nadds, nparts);
+ break;
+ }
+ if (rc != 0)
+ goto done;
+ }
+
+ /* sector size factor -- used to recount from real to 512-byte sectors */
+ ssf = cxt->sector_size / 512;
+
+ for (i = 0; i < nrems; i++) {
+ pa = rem[i];
+ DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_DEL_PARTITION", pa->partno));
+ if (partx_del_partition(cxt->dev_fd, pa->partno + 1) != 0) {
+ fdisk_warn(cxt, _("Failed to remove partition %zu from system"), pa->partno + 1);
+ err++;
+ }
+ }
+ for (i = 0; i < nupds; i++) {
+ pa = upd[i];
+ DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_RESIZE_PARTITION", pa->partno));
+ if (partx_resize_partition(cxt->dev_fd, pa->partno + 1,
+ pa->start * ssf, pa->size * ssf) != 0) {
+ fdisk_warn(cxt, _("Failed to update system information about partition %zu"), pa->partno + 1);
+ err++;
+ }
+ }
+ for (i = 0; i < nadds; i++) {
+ uint64_t sz;
+
+ pa = add[i];
+ sz = pa->size * ssf;
+
+ DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_ADD_PARTITION", pa->partno));
+
+ if (fdisk_is_label(cxt, DOS) && fdisk_partition_is_container(pa))
+ /* Let's follow the Linux kernel and reduce
+ * DOS extended partition to 1 or 2 sectors.
+ */
+ sz = min(sz, (uint64_t) 2);
+
+ if (partx_add_partition(cxt->dev_fd, pa->partno + 1,
+ pa->start * ssf, sz) != 0) {
+ fdisk_warn(cxt, _("Failed to add partition %zu to system"), pa->partno + 1);
+ err++;
+ }
+ }
+ if (err)
+ fdisk_info(cxt, _(
+ "The kernel still uses the old partitions. The new "
+ "table will be used at the next reboot. "));
+done:
+ free(rem);
+ free(add);
+ free(upd);
+ fdisk_unref_table(tb);
+ return rc;
+}
+#else
+int fdisk_reread_changes(struct fdisk_context *cxt,
+ struct fdisk_table *org __attribute__((__unused__))) {
+ return fdisk_reread_partition_table(cxt);
+}
+#endif
+
+/**
+ * fdisk_device_is_used:
+ * @cxt: context
+ *
+ * The function returns always 0 if the device has not been opened by
+ * fdisk_assign_device() or if open read-only.
+ *
+ * Returns: 1 if the device assigned to the context is used by system, or 0.
+ */
+int fdisk_device_is_used(struct fdisk_context *cxt)
+{
+ int rc;
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ rc = cxt->readonly ? 0 :
+ cxt->is_excl ? 0 :
+ cxt->is_priv ? 1 : 0;
+
+ DBG(CXT, ul_debugobj(cxt, "device used: %s [read-only=%d, excl=%d, priv:%d]",
+ rc ? "TRUE" : "FALSE", cxt->readonly,
+ cxt->is_excl, cxt->is_priv));
+ return rc;
+}
+
+/**
+ * fdisk_is_readonly:
+ * @cxt: context
+ *
+ * Returns: 1 if device open readonly
+ */
+int fdisk_is_readonly(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->readonly;
+}
+
+/**
+ * fdisk_is_regfile:
+ * @cxt: context
+ *
+ * Since: 2.30
+ *
+ * Returns: 1 if open file descriptor is regular file rather than a block device.
+ */
+int fdisk_is_regfile(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return S_ISREG(cxt->dev_st.st_mode);
+}
+
+/**
+ * fdisk_unref_context:
+ * @cxt: fdisk context
+ *
+ * Deallocates context struct.
+ */
+void fdisk_unref_context(struct fdisk_context *cxt)
+{
+ unsigned i;
+
+ if (!cxt)
+ return;
+
+ cxt->refcount--;
+ if (cxt->refcount <= 0) {
+ DBG(CXT, ul_debugobj(cxt, "freeing context %p for %s", cxt, cxt->dev_path));
+
+ reset_context(cxt); /* this is sensitive to parent<->child relationship! */
+
+ /* deallocate label's private stuff */
+ for (i = 0; i < cxt->nlabels; i++) {
+ if (!cxt->labels[i])
+ continue;
+ if (cxt->labels[i]->op->free)
+ cxt->labels[i]->op->free(cxt->labels[i]);
+ else
+ free(cxt->labels[i]);
+ cxt->labels[i] = NULL;
+ }
+
+ fdisk_unref_context(cxt->parent);
+ cxt->parent = NULL;
+
+ free(cxt);
+ }
+}
+
+
+/**
+ * fdisk_enable_details:
+ * @cxt: context
+ * @enable: true/false
+ *
+ * Enables or disables "details" display mode. This function has effect to
+ * fdisk_partition_to_string() function.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_details(struct fdisk_context *cxt, int enable)
+{
+ assert(cxt);
+ cxt->display_details = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_is_details:
+ * @cxt: context
+ *
+ * Returns: 1 if details are enabled
+ */
+int fdisk_is_details(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->display_details == 1;
+}
+
+/**
+ * fdisk_enable_listonly:
+ * @cxt: context
+ * @enable: true/false
+ *
+ * Just list partition only, don't care about another details, mistakes, ...
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable)
+{
+ assert(cxt);
+ cxt->listonly = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_is_listonly:
+ * @cxt: context
+ *
+ * Returns: 1 if list-only mode enabled
+ */
+int fdisk_is_listonly(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->listonly == 1;
+}
+
+
+/**
+ * fdisk_set_unit:
+ * @cxt: context
+ * @str: "cylinder" or "sector".
+ *
+ * This is pure shit, unfortunately for example Sun addresses begin of the
+ * partition by cylinders...
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str)
+{
+ assert(cxt);
+
+ cxt->display_in_cyl_units = 0;
+
+ if (!str)
+ return 0;
+
+ if (strcmp(str, "cylinder") == 0 || strcmp(str, "cylinders") == 0)
+ cxt->display_in_cyl_units = 1;
+
+ else if (strcmp(str, "sector") == 0 || strcmp(str, "sectors") == 0)
+ cxt->display_in_cyl_units = 0;
+
+ DBG(CXT, ul_debugobj(cxt, "display unit: %s", fdisk_get_unit(cxt, 0)));
+ return 0;
+}
+
+/**
+ * fdisk_get_unit:
+ * @cxt: context
+ * @n: FDISK_PLURAL or FDISK_SINGULAR
+ *
+ * Returns: unit name.
+ */
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n)
+{
+ assert(cxt);
+
+ if (fdisk_use_cylinders(cxt))
+ return P_("cylinder", "cylinders", n);
+ return P_("sector", "sectors", n);
+}
+
+/**
+ * fdisk_use_cylinders:
+ * @cxt: context
+ *
+ * Returns: 1 if user wants to display in cylinders.
+ */
+int fdisk_use_cylinders(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->display_in_cyl_units == 1;
+}
+
+/**
+ * fdisk_get_units_per_sector:
+ * @cxt: context
+ *
+ * This is necessary only for brain dead situations when we use "cylinders";
+ *
+ * Returns: number of "units" per sector, default is 1 if display unit is sector.
+ */
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt)
+{
+ assert(cxt);
+
+ if (fdisk_use_cylinders(cxt)) {
+ assert(cxt->geom.heads);
+ return cxt->geom.heads * cxt->geom.sectors;
+ }
+ return 1;
+}
+
+/**
+ * fdisk_get_optimal_iosize:
+ * @cxt: context
+ *
+ * The optimal I/O is optional and does not have to be provided by device,
+ * anyway libfdisk never returns zero. If the optimal I/O size is not provided
+ * then libfdisk returns minimal I/O size or sector size.
+ *
+ * Returns: optimal I/O size in bytes.
+ */
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->optimal_io_size ? cxt->optimal_io_size : cxt->io_size;
+}
+
+/**
+ * fdisk_get_minimal_iosize:
+ * @cxt: context
+ *
+ * Returns: minimal I/O size in bytes
+ */
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->min_io_size;
+}
+
+/**
+ * fdisk_get_physector_size:
+ * @cxt: context
+ *
+ * Returns: physical sector size in bytes
+ */
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->phy_sector_size;
+}
+
+/**
+ * fdisk_get_sector_size:
+ * @cxt: context
+ *
+ * Returns: logical sector size in bytes
+ */
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->sector_size;
+}
+
+/**
+ * fdisk_get_alignment_offset
+ * @cxt: context
+ *
+ * The alignment offset is offset between logical and physical sectors. For
+ * backward compatibility the first logical sector on 4K disks does no have to
+ * start on the same place like physical sectors.
+ *
+ * Returns: alignment offset in bytes
+ */
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->alignment_offset;
+}
+
+/**
+ * fdisk_get_grain_size:
+ * @cxt: context
+ *
+ * Returns: grain in bytes used to align partitions (usually 1MiB)
+ */
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->grain;
+}
+
+/**
+ * fdisk_get_first_lba:
+ * @cxt: context
+ *
+ * Returns: first possible LBA on disk for data partitions.
+ */
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->first_lba;
+}
+
+/**
+ * fdisk_set_first_lba:
+ * @cxt: fdisk context
+ * @lba: first possible logical sector for data
+ *
+ * It's strongly recommended to use the default library setting. The first LBA
+ * is always reset by fdisk_assign_device(), fdisk_override_geometry()
+ * and fdisk_reset_alignment(). This is very low level function and library
+ * does not check if your setting makes any sense.
+ *
+ * This function is necessary only when you want to work with very unusual
+ * partition tables like GPT protective MBR or hybrid partition tables on
+ * bootable media where the first partition may start on very crazy offsets.
+ *
+ * Note that this function changes only runtime information. It does not update
+ * any range in on-disk partition table. For example GPT Header contains First
+ * and Last usable LBA fields. These fields are not updated by this function.
+ * Be careful.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ assert(cxt);
+ DBG(CXT, ul_debugobj(cxt, "setting first LBA from %ju to %ju",
+ (uintmax_t) cxt->first_lba, (uintmax_t) lba));
+ cxt->first_lba = lba;
+ return 0;
+}
+
+/**
+ * fdisk_get_last_lba:
+ * @cxt: fdisk context
+ *
+ * Note that the device has to be already assigned.
+ *
+ * Returns: last possible LBA on device
+ */
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt)
+{
+ return cxt->last_lba;
+}
+
+/**
+ * fdisk_set_last_lba:
+ * @cxt: fdisk context
+ * @lba: last possible logical sector
+ *
+ * It's strongly recommended to use the default library setting. The last LBA
+ * is always reset by fdisk_assign_device(), fdisk_override_geometry() and
+ * fdisk_reset_alignment().
+ *
+ * The default is number of sectors on the device, but maybe modified by the
+ * current disklabel driver (for example GPT uses the end of disk for backup
+ * header, so last_lba is smaller than total number of sectors).
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ assert(cxt);
+
+ if (lba > cxt->total_sectors - 1 || lba < 1)
+ return -ERANGE;
+ cxt->last_lba = lba;
+ return 0;
+}
+
+/**
+ * fdisk_set_size_unit:
+ * @cxt: fdisk context
+ * @unit: FDISK_SIZEUNIT_*
+ *
+ * Sets unit for SIZE output field (see fdisk_partition_to_string()).
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_set_size_unit(struct fdisk_context *cxt, int unit)
+{
+ assert(cxt);
+ cxt->sizeunit = unit;
+ return 0;
+}
+
+/**
+ * fdisk_get_size_unit:
+ * @cxt: fdisk context
+ *
+ * Gets unit for SIZE output field (see fdisk_partition_to_string()).
+ *
+ * Returns: unit
+ */
+int fdisk_get_size_unit(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->sizeunit;
+}
+
+/**
+ * fdisk_get_nsectors:
+ * @cxt: context
+ *
+ * Returns: size of the device in logical sectors.
+ */
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->total_sectors;
+}
+
+/**
+ * fdisk_get_devname:
+ * @cxt: context
+ *
+ * Returns: device name.
+ */
+const char *fdisk_get_devname(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->dev_path;
+}
+
+/**
+ * fdisk_get_devno:
+ * @cxt: context
+ *
+ * Returns: device number or zero for non-block devices
+ *
+ * Since: 2.33
+ */
+dev_t fdisk_get_devno(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return S_ISBLK(cxt->dev_st.st_mode) ? cxt->dev_st.st_rdev : 0;
+}
+
+/**
+ * fdisk_get_devmodel:
+ * @cxt: context
+ *
+ * Returns: device model string or NULL.
+ *
+ * Since: 2.33
+ */
+#ifdef __linux__
+const char *fdisk_get_devmodel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+
+ if (cxt->dev_model_probed)
+ return cxt->dev_model;
+
+ if (fdisk_get_devno(cxt)) {
+ struct path_cxt *pc = ul_new_sysfs_path(fdisk_get_devno(cxt), NULL, NULL);
+
+ if (pc) {
+ ul_path_read_string(pc, &cxt->dev_model, "device/model");
+ ul_unref_path(pc);
+ }
+ }
+ cxt->dev_model_probed = 1;
+ return cxt->dev_model;
+}
+#else
+const char *fdisk_get_devmodel(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return NULL;
+}
+#endif
+
+/**
+ * fdisk_get_devfd:
+ * @cxt: context
+ *
+ * Returns: device file descriptor.
+ */
+int fdisk_get_devfd(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->dev_fd;
+}
+
+/**
+ * fdisk_get_geom_heads:
+ * @cxt: context
+ *
+ * Returns: number of geometry heads.
+ */
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->geom.heads;
+}
+/**
+ * fdisk_get_geom_sectors:
+ * @cxt: context
+ *
+ * Returns: number of geometry sectors.
+ */
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->geom.sectors;
+
+}
+
+/**
+ * fdisk_get_geom_cylinders:
+ * @cxt: context
+ *
+ * Returns: number of geometry cylinders
+ */
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->geom.cylinders;
+}
+
+int fdisk_missing_geometry(struct fdisk_context *cxt)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return 0;
+
+ rc = (fdisk_label_require_geometry(cxt->label) &&
+ (!cxt->geom.heads || !cxt->geom.sectors
+ || !cxt->geom.cylinders));
+
+ if (rc && !fdisk_is_listonly(cxt))
+ fdisk_warnx(cxt, _("Incomplete geometry setting."));
+
+ return rc;
+}
+
diff --git a/libfdisk/src/dos.c b/libfdisk/src/dos.c
new file mode 100644
index 0000000..98bc614
--- /dev/null
+++ b/libfdisk/src/dos.c
@@ -0,0 +1,2912 @@
+/*
+ *
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ * 2012 Davidlohr Bueso <dave@gnu.org>
+ * 2021 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This is re-written version for libfdisk, the original was fdiskdoslabel.c
+ * from util-linux fdisk.
+ */
+#include "c.h"
+#include "randutils.h"
+#include "pt-mbr.h"
+#include "strutils.h"
+
+#include "fdiskP.h"
+
+#include <ctype.h>
+
+#define MAXIMUM_PARTS 60
+#define ACTIVE_FLAG 0x80
+
+/**
+ * SECTION: dos
+ * @title: DOS
+ * @short_description: disk label specific functions
+ *
+ */
+
+
+#define IS_EXTENDED(i) \
+ ((i) == MBR_DOS_EXTENDED_PARTITION \
+ || (i) == MBR_W95_EXTENDED_PARTITION \
+ || (i) == MBR_LINUX_EXTENDED_PARTITION)
+
+/*
+ * per partition table entry data
+ *
+ * The four primary partitions have the same sectorbuffer
+ * and have NULL ex_entry.
+ *
+ * Each logical partition table entry has two pointers, one for the
+ * partition and one link to the next one.
+ */
+struct pte {
+ struct dos_partition *pt_entry; /* on-disk MBR entry */
+ struct dos_partition *ex_entry; /* on-disk EBR entry */
+ fdisk_sector_t offset; /* disk sector number */
+ unsigned char *sectorbuffer; /* disk sector contents */
+
+ unsigned int changed : 1,
+ private_sectorbuffer : 1;
+};
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_dos_label {
+ struct fdisk_label head; /* generic part */
+
+ struct pte ptes[MAXIMUM_PARTS]; /* partition */
+ fdisk_sector_t ext_offset; /* start of the ext.partition */
+ size_t ext_index; /* ext.partition index (if ext_offset is set) */
+ unsigned int compatible : 1, /* is DOS compatible? */
+ non_pt_changed : 1; /* MBR, but no PT changed */
+};
+
+/*
+ * Partition types
+ */
+static struct fdisk_parttype dos_parttypes[] = {
+ #include "pt-mbr-partnames.h"
+};
+
+static const struct fdisk_shortcut dos_parttype_cuts[] =
+{
+ { .shortcut = "L", .alias = "linux", .data = "83" },
+ { .shortcut = "S", .alias = "swap", .data = "82" },
+ { .shortcut = "E", .alias = "extended", .data = "05", .deprecated = 1 }, /* collision with 0x0e type */
+ { .shortcut = "Ex",.alias = "extended", .data = "05" }, /* MBR extended */
+ { .shortcut = "U", .alias = "uefi", .data = "EF" }, /* UEFI system */
+ { .shortcut = "R", .alias = "raid", .data = "FD" }, /* Linux RAID */
+ { .shortcut = "V", .alias = "lvm", .data = "8E" }, /* LVM */
+ { .shortcut = "X", .alias = "linuxex", .data = "85" } /* Linux extended */
+};
+
+
+#define sector(s) ((s) & 0x3f)
+#define cylinder(s, c) ((c) | (((s) & 0xc0) << 2))
+
+#define alignment_required(_x) ((_x)->grain != (_x)->sector_size)
+
+#define is_dos_compatible(_x) \
+ (fdisk_is_label(_x, DOS) && \
+ fdisk_dos_is_compatible(fdisk_get_label(_x, NULL)))
+
+#define cround(c, n) fdisk_cround(c, n)
+
+
+static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ return (struct fdisk_dos_label *) cxt->label;
+}
+
+static inline struct pte *self_pte(struct fdisk_context *cxt, size_t i)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ if (i >= ARRAY_SIZE(l->ptes))
+ return NULL;
+
+ return &l->ptes[i];
+}
+
+static inline struct dos_partition *self_partition(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct pte *pe = self_pte(cxt, i);
+ return pe ? pe->pt_entry : NULL;
+}
+
+struct dos_partition *fdisk_dos_get_partition(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ return self_partition(cxt, i);
+}
+
+static struct fdisk_parttype *dos_partition_parttype(
+ struct fdisk_context *cxt,
+ struct dos_partition *p)
+{
+ struct fdisk_parttype *t
+ = fdisk_label_get_parttype_from_code(cxt->label, p->sys_ind);
+ return t ? : fdisk_new_unknown_parttype(p->sys_ind, NULL);
+}
+
+/*
+ * Linux kernel cares about partition size only. Things like
+ * partition type or so are completely irrelevant -- kzak Nov-2013
+ */
+static int is_used_partition(struct dos_partition *p)
+{
+ return p && dos_partition_get_size(p) != 0;
+}
+
+static void partition_set_changed(
+ struct fdisk_context *cxt,
+ size_t i,
+ int changed)
+{
+ struct pte *pe = self_pte(cxt, i);
+
+ if (!pe)
+ return;
+
+ DBG(LABEL, ul_debug("DOS: setting %zu partition to %s", i,
+ changed ? "changed" : "unchanged"));
+
+ pe->changed = changed ? 1 : 0;
+ if (changed)
+ fdisk_label_set_changed(cxt->label, 1);
+}
+
+static fdisk_sector_t get_abs_partition_start(struct pte *pe)
+{
+ assert(pe);
+ assert(pe->pt_entry);
+
+ return pe->offset + dos_partition_get_start(pe->pt_entry);
+}
+
+static fdisk_sector_t get_abs_partition_end(struct pte *pe)
+{
+ fdisk_sector_t size;
+
+ assert(pe);
+ assert(pe->pt_entry);
+
+ size = dos_partition_get_size(pe->pt_entry);
+ return get_abs_partition_start(pe) + size - (size ? 1 : 0);
+}
+
+static int is_cleared_partition(struct dos_partition *p)
+{
+ return !(!p || p->boot_ind || p->bh || p->bs || p->bc ||
+ p->sys_ind || p->eh || p->es || p->ec ||
+ dos_partition_get_start(p) || dos_partition_get_size(p));
+}
+
+static int get_partition_unused_primary(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ size_t org, n;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(partno);
+
+ org = cxt->label->nparts_max;
+
+ cxt->label->nparts_max = 4;
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ cxt->label->nparts_max = org;
+
+ if (rc == 1) {
+ fdisk_info(cxt, _("All primary partitions have been defined already."));
+ rc = -1;
+ } else if (rc == -ERANGE) {
+ fdisk_warnx(cxt, _("Primary partition not available."));
+ } else if (rc == 0)
+ *partno = n;
+
+ return rc;
+}
+
+static int seek_sector(struct fdisk_context *cxt, fdisk_sector_t secno)
+{
+ off_t offset = (off_t) secno * cxt->sector_size;
+
+ return lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1 ? -errno : 0;
+}
+
+static int read_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+ unsigned char *buf)
+{
+ int rc = seek_sector(cxt, secno);
+ ssize_t r;
+
+ if (rc < 0)
+ return rc;
+
+ r = read(cxt->dev_fd, buf, cxt->sector_size);
+ if (r == (ssize_t) cxt->sector_size)
+ return 0;
+ if (r < 0)
+ return -errno;
+ return -1;
+}
+
+/* Allocate a buffer and read a partition table sector */
+static int read_pte(struct fdisk_context *cxt, size_t pno, fdisk_sector_t offset)
+{
+ int rc;
+ unsigned char *buf;
+ struct pte *pe = self_pte(cxt, pno);
+
+ if (!pe)
+ return -EINVAL;
+
+ buf = calloc(1, cxt->sector_size);
+ if (!buf)
+ return -ENOMEM;
+
+ DBG(LABEL, ul_debug("DOS: reading EBR %zu: offset=%ju, buffer=%p",
+ pno, (uintmax_t) offset, buf));
+
+ pe->offset = offset;
+ pe->sectorbuffer = buf;
+ pe->private_sectorbuffer = 1;
+
+ rc = read_sector(cxt, offset, pe->sectorbuffer);
+ if (rc) {
+ fdisk_warn(cxt, _("Failed to read extended partition table "
+ "(offset=%ju)"), (uintmax_t) offset);
+ return rc;
+ }
+
+ pe->changed = 0;
+ pe->pt_entry = pe->ex_entry = NULL;
+ return 0;
+}
+
+
+static void clear_partition(struct dos_partition *p)
+{
+ if (!p)
+ return;
+ p->boot_ind = 0;
+ p->bh = 0;
+ p->bs = 0;
+ p->bc = 0;
+ p->sys_ind = 0;
+ p->eh = 0;
+ p->es = 0;
+ p->ec = 0;
+ dos_partition_set_start(p,0);
+ dos_partition_set_size(p,0);
+}
+
+static void dos_init(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ size_t i;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: initialize, first sector buffer %p", cxt->firstsector));
+
+ cxt->label->nparts_max = 4; /* default, unlimited number of logical */
+
+ l->ext_index = 0;
+ l->ext_offset = 0;
+ l->non_pt_changed = 0;
+
+ memset(l->ptes, 0, sizeof(l->ptes));
+
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ pe->pt_entry = mbr_get_partition(cxt->firstsector, i);
+ pe->ex_entry = NULL;
+ pe->offset = 0;
+ pe->sectorbuffer = cxt->firstsector;
+ pe->private_sectorbuffer = 0;
+ pe->changed = 0;
+
+ DBG(LABEL, ul_debug("DOS: initialize: #%zu start=%u size=%u sysid=%02x",
+ i,
+ dos_partition_get_start(pe->pt_entry),
+ dos_partition_get_size(pe->pt_entry),
+ pe->pt_entry->sys_ind));
+ }
+
+ if (fdisk_is_listonly(cxt))
+ return;
+ /*
+ * Various warnings...
+ */
+ if (fdisk_missing_geometry(cxt))
+ fdisk_warnx(cxt, _("You can set geometry from the extra functions menu."));
+
+ if (is_dos_compatible(cxt)) {
+ fdisk_warnx(cxt, _("DOS-compatible mode is deprecated."));
+
+ if (cxt->sector_size != cxt->phy_sector_size)
+ fdisk_info(cxt, _(
+ "The device presents a logical sector size that is smaller than "
+ "the physical sector size. Aligning to a physical sector (or optimal "
+ "I/O) size boundary is recommended, or performance may be impacted."));
+ }
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_warnx(cxt, _("Cylinders as display units are deprecated."));
+
+ if (cxt->total_sectors > UINT_MAX) {
+ uint64_t bytes = cxt->total_sectors * cxt->sector_size;
+ char *szstr = size_to_human_string(SIZE_SUFFIX_SPACE
+ | SIZE_SUFFIX_3LETTER, bytes);
+ fdisk_warnx(cxt,
+ _("The size of this disk is %s (%ju bytes). DOS "
+ "partition table format cannot be used on drives for "
+ "volumes larger than %lu bytes for %lu-byte "
+ "sectors. Use GUID partition table format (GPT)."),
+ szstr, bytes,
+ UINT_MAX * cxt->sector_size,
+ cxt->sector_size);
+ free(szstr);
+ }
+}
+
+/* callback called by libfdisk */
+static void dos_deinit(struct fdisk_label *lb)
+{
+ size_t i;
+ struct fdisk_dos_label *l = (struct fdisk_dos_label *) lb;
+
+ for (i = 0; i < ARRAY_SIZE(l->ptes); i++) {
+ struct pte *pe = &l->ptes[i];
+
+ if (pe->private_sectorbuffer && pe->sectorbuffer) {
+ DBG(LABEL, ul_debug("DOS: freeing pte %zu sector buffer %p",
+ i, pe->sectorbuffer));
+ free(pe->sectorbuffer);
+ }
+ pe->sectorbuffer = NULL;
+ pe->private_sectorbuffer = 0;
+ }
+
+ memset(l->ptes, 0, sizeof(l->ptes));
+}
+
+static void reset_pte(struct pte *pe)
+{
+ assert(pe);
+
+ if (pe->private_sectorbuffer) {
+ DBG(LABEL, ul_debug(" --> freeing pte sector buffer %p",
+ pe->sectorbuffer));
+ free(pe->sectorbuffer);
+ }
+ memset(pe, 0, sizeof(struct pte));
+}
+
+static int delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ struct fdisk_dos_label *l;
+ struct pte *pe;
+ struct dos_partition *p;
+ struct dos_partition *q;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, partnum);
+ if (!pe)
+ return -EINVAL;
+
+ DBG(LABEL, ul_debug("DOS: delete partition %zu (max=%zu)", partnum,
+ cxt->label->nparts_max));
+
+ l = self_label(cxt);
+ p = pe->pt_entry;
+ q = pe->ex_entry;
+
+ /* Note that for the fifth partition (partnum == 4) we don't actually
+ decrement partitions. */
+ if (partnum < 4) {
+ DBG(LABEL, ul_debug("--> delete primary"));
+ if (IS_EXTENDED(p->sys_ind) && partnum == l->ext_index) {
+ size_t i;
+ DBG(LABEL, ul_debug(" --> delete extended"));
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ DBG(LABEL, ul_debug(" --> delete logical #%zu", i));
+ reset_pte(&l->ptes[i]);
+
+ }
+ cxt->label->nparts_max = 4;
+ l->ptes[l->ext_index].ex_entry = NULL;
+ l->ext_offset = 0;
+ l->ext_index = 0;
+ }
+ partition_set_changed(cxt, partnum, 1);
+ clear_partition(p);
+ } else if (!q->sys_ind && partnum > 4) {
+ DBG(LABEL, ul_debug("--> delete logical [last in the chain]"));
+ reset_pte(&l->ptes[partnum]);
+ --cxt->label->nparts_max;
+ --partnum;
+ /* clear link to deleted partition */
+ clear_partition(l->ptes[partnum].ex_entry);
+ partition_set_changed(cxt, partnum, 1);
+ } else {
+ DBG(LABEL, ul_debug("--> delete logical [move down]"));
+ if (partnum > 4) {
+ DBG(LABEL, ul_debug(" --> delete %zu logical link", partnum));
+ p = l->ptes[partnum - 1].ex_entry;
+ *p = *q;
+ dos_partition_set_start(p, dos_partition_get_start(q));
+ dos_partition_set_size(p, dos_partition_get_size(q));
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, partnum - 1, 1);
+
+ } else if (cxt->label->nparts_max > 5) {
+ DBG(LABEL, ul_debug(" --> delete first logical link"));
+ pe = &l->ptes[5]; /* second logical */
+
+ if (pe->pt_entry) /* prevent SEGFAULT */
+ dos_partition_set_start(pe->pt_entry,
+ get_abs_partition_start(pe) -
+ l->ext_offset);
+ pe->offset = l->ext_offset;
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, 5, 1);
+ }
+
+ if (cxt->label->nparts_max > 5) {
+ DBG(LABEL, ul_debug(" --> move ptes"));
+ cxt->label->nparts_max--;
+ reset_pte(&l->ptes[partnum]);
+ while (partnum < cxt->label->nparts_max) {
+ DBG(LABEL, ul_debug(" --> moving pte %zu <-- %zu", partnum, partnum + 1));
+ l->ptes[partnum] = l->ptes[partnum + 1];
+ partnum++;
+ }
+ memset(&l->ptes[partnum], 0, sizeof(struct pte));
+ } else {
+ DBG(LABEL, ul_debug(" --> the only logical: clear only"));
+ clear_partition(l->ptes[partnum].pt_entry);
+ cxt->label->nparts_max--;
+
+ if (partnum == 4) {
+ DBG(LABEL, ul_debug(" --> clear last logical"));
+ reset_pte(&l->ptes[partnum]);
+ partition_set_changed(cxt, l->ext_index, 1);
+ }
+ }
+ }
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ struct pte *pe;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, partnum);
+ if (!pe || !is_used_partition(pe->pt_entry))
+ return -EINVAL;
+
+ return delete_partition(cxt, partnum);
+}
+
+static void read_extended(struct fdisk_context *cxt, size_t ext)
+{
+ size_t i;
+ struct pte *pex, *pe;
+ struct dos_partition *p, *q;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ l->ext_index = ext;
+ pex = self_pte(cxt, ext);
+ if (!pex) {
+ DBG(LABEL, ul_debug("DOS: uninitialized pointer to %zu pex", ext));
+ return;
+ }
+ pex->ex_entry = pex->pt_entry;
+
+ p = pex->pt_entry;
+ if (!dos_partition_get_start(p)) {
+ fdisk_warnx(cxt, _("Bad offset in primary extended partition."));
+ return;
+ }
+
+ DBG(LABEL, ul_debug("DOS: Reading extended %zu", ext));
+
+ while (IS_EXTENDED (p->sys_ind)) {
+ if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+ /* This is not a Linux restriction, but
+ this program uses arrays of size MAXIMUM_PARTS.
+ Do not try to `improve' this test. */
+ struct pte *pre = self_pte(cxt,
+ cxt->label->nparts_max - 1);
+ fdisk_warnx(cxt,
+ _("Omitting partitions after #%zu. They will be deleted "
+ "if you save this partition table."),
+ cxt->label->nparts_max);
+
+ if (pre) {
+ clear_partition(pre->ex_entry);
+ partition_set_changed(cxt,
+ cxt->label->nparts_max - 1, 1);
+ }
+ return;
+ }
+
+ pe = self_pte(cxt, cxt->label->nparts_max);
+ if (!pe)
+ return;
+
+ if (read_pte(cxt, cxt->label->nparts_max, l->ext_offset +
+ dos_partition_get_start(p)))
+ return;
+
+ if (!l->ext_offset)
+ l->ext_offset = dos_partition_get_start(p);
+
+ assert(pe->sectorbuffer);
+ q = p = mbr_get_partition(pe->sectorbuffer, 0);
+
+ for (i = 0; i < 4; i++, p++) {
+ if (!dos_partition_get_size(p))
+ continue;
+
+ if (IS_EXTENDED (p->sys_ind)) {
+ if (pe->ex_entry)
+ fdisk_warnx(cxt, _(
+ "Extra link pointer in partition "
+ "table %zu."),
+ cxt->label->nparts_max + 1);
+ else
+ pe->ex_entry = p;
+ } else if (p->sys_ind) {
+ if (pe->pt_entry)
+ fdisk_warnx(cxt, _(
+ "Ignoring extra data in partition "
+ "table %zu."),
+ cxt->label->nparts_max + 1);
+ else
+ pe->pt_entry = p;
+ }
+ }
+
+ /* very strange code here... */
+ if (!pe->pt_entry) {
+ if (q != pe->ex_entry)
+ pe->pt_entry = q;
+ else
+ pe->pt_entry = q + 1;
+ }
+ if (!pe->ex_entry) {
+ if (q != pe->pt_entry)
+ pe->ex_entry = q;
+ else
+ pe->ex_entry = q + 1;
+ }
+
+ p = pe->ex_entry;
+ cxt->label->nparts_cur = ++cxt->label->nparts_max;
+
+ DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: link: type=%x, start=%u, size=%u; "
+ " data: type=%x, start=%u, size=%u",
+ (uintmax_t) pe->offset,
+ pe->ex_entry->sys_ind,
+ dos_partition_get_start(pe->ex_entry),
+ dos_partition_get_size(pe->ex_entry),
+ pe->pt_entry->sys_ind,
+ dos_partition_get_start(pe->pt_entry),
+ dos_partition_get_size(pe->pt_entry)));
+
+ }
+
+ /* remove last empty EBR */
+ pe = self_pte(cxt, cxt->label->nparts_max - 1);
+ if (pe &&
+ is_cleared_partition(pe->ex_entry) &&
+ is_cleared_partition(pe->pt_entry)) {
+ DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: empty, remove", (uintmax_t) pe->offset));
+ reset_pte(pe);
+ cxt->label->nparts_max--;
+ cxt->label->nparts_cur--;
+ }
+
+ /* remove empty links */
+ remove:
+ q = self_partition(cxt, 4);
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ p = self_partition(cxt, i);
+
+ if (p && !dos_partition_get_size(p) &&
+ (cxt->label->nparts_max > 5 || (q && q->sys_ind))) {
+ fdisk_info(cxt, _("omitting empty partition (%zu)"), i+1);
+ delete_partition(cxt, i);
+ goto remove; /* numbering changed */
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: nparts_max: %zu", cxt->label->nparts_max));
+}
+
+static int dos_create_disklabel(struct fdisk_context *cxt)
+{
+ unsigned int id = 0;
+ int rc, has_id = 0;
+ struct fdisk_dos_label *l;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: creating new disklabel"));
+
+ if (cxt->script) {
+ char *end = NULL;
+ const char *s = fdisk_script_get_header(cxt->script, "label-id");
+
+ if (s) {
+ errno = 0;
+ id = strtoul(s, &end, 16);
+ if (!errno && end && s < end) {
+ has_id = 1;
+ DBG(LABEL, ul_debug("DOS: re-use ID from script (0x%08x)", id));
+ } else
+ DBG(LABEL, ul_debug("DOS: failed to parse label=id '%s'", s));
+ }
+ }
+
+ /* random disk signature */
+ if (!has_id) {
+ DBG(LABEL, ul_debug("DOS: generate new ID"));
+ ul_random_get_bytes(&id, sizeof(id));
+ }
+
+ if (fdisk_has_protected_bootbits(cxt))
+ rc = fdisk_init_firstsector_buffer(cxt, 0, MBR_PT_BOOTBITS_SIZE);
+ else
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+ dos_init(cxt);
+
+ l = self_label(cxt);
+
+ /* Generate an MBR ID for this disk */
+ mbr_set_id(cxt->firstsector, id);
+ l->non_pt_changed = 1;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ /* Put MBR signature */
+ mbr_set_magic(cxt->firstsector);
+
+ fdisk_info(cxt, _("Created a new DOS disklabel with disk "
+ "identifier 0x%08x."), id);
+ return 0;
+}
+
+static int dos_set_disklabel_id(struct fdisk_context *cxt, const char *str)
+{
+ char *buf = NULL;
+ unsigned int id, old;
+ struct fdisk_dos_label *l;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: setting Id"));
+
+ l = self_label(cxt);
+ old = mbr_get_id(cxt->firstsector);
+
+ if (!str) {
+ rc = fdisk_ask_string(cxt,
+ _("Enter the new disk identifier"), &buf);
+ str = buf;
+ }
+ if (!rc) {
+ char *end = NULL;
+
+ errno = 0;
+ id = strtoul(str, &end, 0);
+ if (errno || str == end || (end && *end)) {
+ fdisk_warnx(cxt, _("Incorrect value."));
+ rc = -EINVAL;
+ }
+ }
+
+ free(buf);
+ if (rc)
+ return -EINVAL;
+
+ mbr_set_id(cxt->firstsector, id);
+ l->non_pt_changed = 1;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info(cxt, _("Disk identifier changed from 0x%08x to 0x%08x."),
+ old, id);
+ return 0;
+}
+
+static unsigned int chs_div_minus(unsigned int a1, unsigned int a2, unsigned int b1, unsigned int b2)
+{
+ if (a1 > a2 && b1 > b2) {
+ a1 = a1 - a2;
+ b1 = b1 - b2;
+ } else if (a2 > a1 && b2 > b1) {
+ a1 = a2 - a1;
+ b1 = b2 - b1;
+ } else {
+ return 0;
+ }
+ if (a1 % b1)
+ return 0;
+ return a1 / b1;
+}
+
+static inline int chs_overflowed(unsigned int c, unsigned int h, unsigned int s)
+{
+ /* 1023/254/63 or 1023/255/63 indicates overflowed/invalid C/H/S values */
+ return (c == 1023 && (h == 254 || h == 255) && s == 63);
+}
+
+static inline int lba_overflowed(fdisk_sector_t start, fdisk_sector_t sects)
+{
+ /* Check if the last LBA sector can be represented by unsigned 32bit int */
+ return (start + (sects-1) > UINT32_MAX);
+}
+
+static void get_partition_table_geometry(struct fdisk_context *cxt,
+ unsigned int *ph, unsigned int *ps)
+{
+ unsigned char *bufp = cxt->firstsector;
+ struct { unsigned int c, h, o, v; } t[8];
+ unsigned int n1, n2, n3, n4, n5, n6;
+ struct dos_partition *p;
+ unsigned int c, h, s, l;
+ unsigned int hh, ss;
+ unsigned int sects;
+ int i, j, dif;
+
+#define chs_set_t(c, h, s, l, t, i) do { \
+ t[i].c = c; \
+ t[i].h = h; \
+ t[i].o = l - (s-1); \
+ t[i].v = (!chs_overflowed(c, h, s) && s && s-1 <= l); \
+} while (0)
+
+ /*
+ * Conversion from C/H/S to LBA is defined by formula:
+ * LBA = (c * N_heads + h) * N_sectors + (s - 1)
+ * Let o to be:
+ * o = LBA - (s - 1)
+ * Then formula can be expressed as:
+ * o = (c * N_heads + h) * N_sectors
+ * In general from two tuples (LBA1, c1, h1, s1), (LBA2, c2, h2, s2)
+ * we can derive formulas for N_heads and N_sectors:
+ * N_heads = (o1 * h2 - o2 * h1) / (o2 * c1 - o1 * c2)
+ * N_sectors = (o2 * c1 - o1 * c2) / (c1 * h2 - c2 * h1)
+ * MBR table contains for very partition start and end tuple.
+ * So we have up to 8 tuples which leads to up to 28 equations
+ * for calculating N_heads and N_sectors. Try to calculate
+ * N_heads and N_sectors from the first possible partition and
+ * if it fails then try also mixed tuples (beginning from first
+ * partition and end from second). Calculation may fail if both
+ * first and last sectors are on cylinder or head boundary
+ * (dividend or divisor is zero). It is possible that different
+ * partitions would have different C/H/S geometry. In this case
+ * we want geometry from the first partition as in most cases
+ * this partition is or was used by BIOS for booting.
+ */
+
+ hh = ss = 0;
+ for (i = 0; i < 4; i++) {
+ p = mbr_get_partition(bufp, i);
+ if (!p->sys_ind)
+ continue;
+
+ c = cylinder(p->bs, p->bc);
+ h = p->bh;
+ s = sector(p->bs);
+ l = dos_partition_get_start(p);
+ chs_set_t(c, h, s, l, t, 2*i);
+
+ sects = dos_partition_get_size(p);
+ if (!sects || lba_overflowed(l, sects))
+ continue;
+
+ c = cylinder(p->es, p->ec);
+ h = p->eh;
+ s = sector(p->es);
+ l += sects-1;
+ chs_set_t(c, h, s, l, t, 2*i+1);
+ }
+
+ for (dif = 1; dif < 8; dif++) {
+ for (i = 0; i + dif < 8; i++) {
+ j = i + dif;
+ if (!t[i].v || !t[j].v)
+ continue;
+ n1 = t[i].o * t[j].h;
+ n2 = t[j].o * t[i].h;
+ n3 = t[j].o * t[i].c;
+ n4 = t[i].o * t[j].c;
+ n5 = t[i].c * t[j].h;
+ n6 = t[j].c * t[i].h;
+ if (!hh && n1 != n2 && n3 != n4) {
+ h = chs_div_minus(n1, n2, n3, n4);
+ if (h > 0 && h <= 256)
+ hh = h;
+ }
+ if (!ss && n3 != n4 && n5 != n6) {
+ s = chs_div_minus(n3, n4, n5, n6);
+ if (s > 0 && s <= 63)
+ ss = s;
+ }
+ if (hh && ss)
+ break;
+ }
+ if (hh && ss)
+ break;
+ }
+
+ if (hh && ss) {
+ *ph = hh;
+ *ps = ss;
+ }
+
+ DBG(LABEL, ul_debug("DOS PT geometry: heads=%u, sectors=%u", *ph, *ps));
+}
+
+static int dos_reset_alignment(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ /* overwrite necessary stuff by DOS deprecated stuff */
+ if (is_dos_compatible(cxt)) {
+ DBG(LABEL, ul_debug("DOS: resetting alignment for DOS-compatible PT"));
+ if (cxt->geom.sectors)
+ cxt->first_lba = cxt->geom.sectors; /* usually 63 */
+
+ cxt->grain = cxt->sector_size; /* usually 512 */
+ }
+
+ return 0;
+}
+
+/* TODO: move to include/pt-dos.h and share with libblkid */
+#define AIX_MAGIC_STRING "\xC9\xC2\xD4\xC1"
+#define AIX_MAGIC_STRLEN (sizeof(AIX_MAGIC_STRING) - 1)
+
+static int dos_probe_label(struct fdisk_context *cxt)
+{
+ size_t i;
+ unsigned int h = 0, s = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ /* ignore disks with AIX magic number */
+ if (memcmp(cxt->firstsector, AIX_MAGIC_STRING, AIX_MAGIC_STRLEN) == 0)
+ return 0;
+
+ if (!mbr_is_valid_magic(cxt->firstsector))
+ return 0;
+
+ dos_init(cxt);
+
+ get_partition_table_geometry(cxt, &h, &s);
+ if (h && s) {
+ cxt->geom.heads = h;
+ cxt->geom.sectors = s;
+ cxt->geom.cylinders = cxt->total_sectors /
+ (cxt->geom.heads * cxt->geom.sectors);
+
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+ }
+
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (is_used_partition(pe->pt_entry))
+ cxt->label->nparts_cur++;
+
+ if (IS_EXTENDED (pe->pt_entry->sys_ind)) {
+ if (cxt->label->nparts_max != 4)
+ fdisk_warnx(cxt, _(
+ "Ignoring extra extended partition %zu"),
+ i + 1);
+ else
+ read_extended(cxt, i);
+ }
+ }
+
+ for (i = 3; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ assert(pe);
+ if (!mbr_is_valid_magic(pe->sectorbuffer)) {
+ fdisk_info(cxt, _(
+ "Invalid flag 0x%02x%02x of EBR (for partition %zu) will "
+ "be corrected by w(rite)."),
+ pe->sectorbuffer[510],
+ pe->sectorbuffer[511],
+ i + 1);
+ partition_set_changed(cxt, i, 1);
+
+ /* mark also extended as changed to update the first EBR
+ * in situation that there is no logical partitions at all */
+ partition_set_changed(cxt, l->ext_index, 1);
+ }
+ }
+
+ return 1;
+}
+
+static void set_partition(struct fdisk_context *cxt,
+ int i, int doext, fdisk_sector_t start,
+ fdisk_sector_t stop, int sysid, int boot)
+{
+ struct pte *pe = self_pte(cxt, i);
+ struct dos_partition *p;
+ fdisk_sector_t offset;
+
+ assert(!FDISK_IS_UNDEF(start));
+ assert(!FDISK_IS_UNDEF(stop));
+ assert(pe);
+
+ if (doext) {
+ struct fdisk_dos_label *l = self_label(cxt);
+ p = pe->ex_entry;
+ offset = l->ext_offset;
+ } else {
+ p = pe->pt_entry;
+ offset = pe->offset;
+ }
+
+ DBG(LABEL, ul_debug("DOS: setting partition %d%s, offset=%zu, start=%zu, size=%zu, sysid=%02x",
+ i, doext ? " [extended]" : "",
+ (size_t) offset,
+ (size_t) (start - offset),
+ (size_t) (stop - start + 1),
+ sysid));
+
+ p->boot_ind = boot ? ACTIVE_FLAG : 0;
+ p->sys_ind = sysid;
+ dos_partition_set_start(p, start - offset);
+ dos_partition_set_size(p, stop - start + 1);
+ dos_partition_sync_chs(p, offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, i, 1);
+}
+
+
+static int get_start_from_user( struct fdisk_context *cxt,
+ fdisk_sector_t *start,
+ fdisk_sector_t low,
+ fdisk_sector_t dflt,
+ fdisk_sector_t limit,
+ struct fdisk_partition *pa)
+{
+ assert(start);
+
+ /* try to use template from 'pa' */
+ if (pa && pa->start_follow_default)
+ *start = dflt;
+
+ else if (pa && fdisk_partition_has_start(pa)) {
+ DBG(LABEL, ul_debug("DOS: start: wanted=%ju, low=%ju, limit=%ju",
+ (uintmax_t) pa->start, (uintmax_t) low, (uintmax_t) limit));
+ *start = pa->start;
+ if (*start < low || *start > limit) {
+ fdisk_warnx(cxt, _("Start sector %ju out of range."),
+ (uintmax_t) *start);
+ return -ERANGE;
+ }
+ } else {
+ /* ask user by dialog */
+ struct fdisk_ask *ask = fdisk_new_ask();
+ int rc;
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_query(ask,
+ fdisk_use_cylinders(cxt) ?
+ _("First cylinder") : _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, low));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, dflt));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+
+ rc = fdisk_do_ask(cxt, ask);
+ *start = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt)) {
+ *start = (*start - 1)
+ * fdisk_get_units_per_sector(cxt);
+ if (*start < low)
+ *start = low;
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: start is %ju", (uintmax_t) *start));
+ return 0;
+}
+
+/* Returns last available sector in the free space pointed to by start. */
+static int find_last_free(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t stop,
+ fdisk_sector_t *result)
+{
+ fdisk_sector_t last = stop;
+
+ size_t i = logical ? 4 : 0;
+
+ for ( ; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+
+ if ((p_start >= begin && p_start <= last) ||
+ (p_end >= begin && p_end <= last)) {
+ last = p_start - 1;
+ }
+ if (last < begin) {
+ DBG(LABEL, ul_debug("no free space <%ju,%ju>",
+ (uintmax_t) begin, (uintmax_t) stop));
+ return -ENOSPC;
+ }
+ }
+
+ if (last == begin)
+ last = stop;
+
+ DBG(LABEL, ul_debug("DOS: last free sector <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) stop, (uintmax_t) last));
+
+ *result = last;
+ return 0;
+}
+
+static int find_last_free_sector_in_range(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t end,
+ fdisk_sector_t *result)
+{
+ int last_moved;
+ fdisk_sector_t last = end;
+
+ do {
+ size_t i = logical ? 4 : 0;
+
+ last_moved = 0;
+ for ( ; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+
+ if (last >= p_start && last <= p_end) {
+ last = p_start - 1;
+ last_moved = 1;
+
+ if (last < begin) {
+ DBG(LABEL, ul_debug("DOS: last free out of range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) last));
+
+ return -ENOSPC;
+ }
+ }
+ }
+ } while (last_moved == 1);
+
+ DBG(LABEL, ul_debug("DOS: last unused sector in range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) last));
+
+ *result = last;
+ return 0;
+}
+
+static int find_first_free_sector_in_range(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t end,
+ fdisk_sector_t *result)
+{
+ int first_moved = 0;
+ fdisk_sector_t first = begin;
+
+ do {
+ size_t i = logical ? 4 : 0;
+
+ first_moved = 0;
+ for (; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+ if (first < p_start)
+ continue;
+ if (first <= p_end) {
+ first = p_end + 1 + (logical ? cxt->first_lba : 0);
+ first_moved = 1;
+
+ if (first > end) {
+ DBG(LABEL, ul_debug("DOS: first free out of range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) first));
+ return -ENOSPC;
+ }
+ }
+ }
+ } while (first_moved == 1);
+
+ DBG(LABEL, ul_debug("DOS: first unused sector in range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) first));
+ *result = first;
+ return 0;
+}
+
+static int get_disk_ranges(struct fdisk_context *cxt, int logical,
+ fdisk_sector_t *first, fdisk_sector_t *last)
+{
+ if (logical) {
+ /* logical partitions */
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+ if (!ext_pe)
+ return -EINVAL;
+
+ *first = l->ext_offset + cxt->first_lba;
+ *last = get_abs_partition_end(ext_pe);
+
+ } else {
+ /* primary partitions */
+ if (fdisk_use_cylinders(cxt) || !cxt->total_sectors)
+ *last = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1;
+ else
+ *last = cxt->total_sectors - 1;
+
+ if (*last > UINT_MAX)
+ *last = UINT_MAX;
+ *first = cxt->first_lba;
+ }
+
+ return 0;
+}
+
+/* first free sector on disk */
+static int find_first_free_sector(struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t start,
+ fdisk_sector_t *result)
+{
+ fdisk_sector_t first, last;
+ int rc;
+
+ rc = get_disk_ranges(cxt, logical, &first, &last);
+ if (rc)
+ return rc;
+
+ return find_first_free_sector_in_range(cxt, logical, start, last, result);
+}
+
+static int add_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ int sys, read = 0, rc, isrel = 0, is_logical;
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct dos_partition *p = self_partition(cxt, n);
+ struct fdisk_ask *ask = NULL;
+
+ fdisk_sector_t start, stop, limit, temp;
+
+ DBG(LABEL, ul_debug("DOS: adding partition %zu", n));
+
+ sys = pa && pa->type ? pa->type->code : MBR_LINUX_DATA_PARTITION;
+ is_logical = n >= 4;
+
+ if (p && is_used_partition(p)) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."),
+ n + 1);
+ return -EINVAL;
+ }
+
+ rc = get_disk_ranges(cxt, is_logical, &start, &stop);
+ if (rc)
+ return rc;
+
+ if (!is_logical && cxt->parent && fdisk_is_label(cxt->parent, GPT))
+ start = 1; /* Bad boy modifies hybrid MBR */
+
+ rc = find_last_free_sector_in_range(cxt, is_logical, start, stop, &limit);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+
+ if ((is_logical || !cxt->parent || !fdisk_is_label(cxt->parent, GPT))
+ && cxt->script && pa && fdisk_partition_has_start(pa)
+ && pa->start >= (is_logical ? l->ext_offset : 1)
+ && pa->start < start) {
+ fdisk_set_first_lba(cxt, 1);
+
+ rc = get_disk_ranges(cxt, is_logical, &start, &stop);
+ if (rc) /* won't happen, but checking to be proper */
+ return rc;
+ }
+
+ /*
+ * Ask for first sector
+ */
+ do {
+ fdisk_sector_t dflt, aligned;
+
+ temp = start;
+
+ DBG(LABEL, ul_debug("DOS: >>> search for first free from %ju", start));
+ rc = find_first_free_sector(cxt, is_logical, start, &dflt);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+ start = dflt;
+
+ if (n >= 4 && pa && fdisk_partition_has_start(pa) && cxt->script
+ && cxt->first_lba > 1
+ && temp == start - cxt->first_lba) {
+ fdisk_set_first_lba(cxt, 1);
+ start = pa->start;
+ }
+
+ /* the default sector should be aligned and unused */
+ do {
+ aligned = fdisk_align_lba_in_range(cxt, dflt, dflt, limit);
+ find_first_free_sector(cxt, is_logical, aligned, &dflt);
+ } while (dflt != aligned && dflt > aligned && dflt < limit);
+
+ if (dflt >= limit)
+ dflt = start;
+ if (start > limit)
+ break;
+ if (start >= temp + fdisk_get_units_per_sector(cxt)
+ && read) {
+ if (!pa || !pa->start_follow_default)
+ fdisk_info(cxt, _("Sector %ju is already allocated."),
+ (uintmax_t) temp);
+ temp = start;
+ read = 0;
+ if (pa && fdisk_partition_has_start(pa))
+ break;
+ }
+
+ if (!read && start == temp) {
+ rc = get_start_from_user(cxt, &start, temp, dflt, limit, pa);
+ if (rc)
+ return rc;
+ read = 1;
+ }
+ if (pa && fdisk_partition_has_size(pa)) {
+ fdisk_sector_t last;
+
+ rc = find_last_free(cxt, is_logical, start, limit, &last);
+ if (rc == 0 && last - start + 1 < fdisk_partition_get_size(pa)) {
+ DBG(LABEL, ul_debug("DOS: area <%ju,%ju> too small [wanted=%ju aval=%ju]",
+ (uintmax_t) start, (uintmax_t) last,
+ fdisk_partition_get_size(pa),
+ last - start));
+
+ if (fdisk_partition_has_start(pa)
+ && fdisk_partition_get_start(pa) <= last)
+ rc = -ENOSPC;
+ else {
+ start = last + 1;
+ continue;
+ }
+ }
+ if (rc == -ENOSPC) {
+ fdisk_warnx(cxt, _("No free sectors available."));
+ return rc;
+ }
+ }
+
+ } while (start != temp || !read);
+
+ if (n == 4) {
+ /* The first EBR is stored at begin of the extended partition */
+ struct pte *pe = self_pte(cxt, n);
+
+ assert(pe);
+ pe->offset = l->ext_offset;
+ } else if (n > 4) {
+ /* The second (and another) EBR */
+ struct pte *pe = self_pte(cxt, n);
+
+ assert(pe);
+ assert(start >= cxt->first_lba);
+
+ pe->offset = start - cxt->first_lba;
+ DBG(LABEL, ul_debug("DOS: setting EBR offset to %ju [start=%ju]", pe->offset, start));
+
+ if (pe->offset == l->ext_offset) { /* must be corrected */
+ pe->offset++;
+ if (cxt->first_lba == 1)
+ start++;
+ }
+ }
+
+ rc = find_last_free(cxt, is_logical, start, limit, &stop);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+ limit = stop;
+
+ /*
+ * Ask for last sector
+ */
+ if (fdisk_cround(cxt, start) == fdisk_cround(cxt, limit))
+ stop = limit;
+ else if (pa && pa->end_follow_default)
+ stop = limit;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ stop = start + pa->size;
+ isrel = pa->size_explicit ? 0 : 1;
+ if ((!isrel || !alignment_required(cxt)) && stop > start)
+ stop -= 1;
+ } else {
+ /* ask user by dialog */
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (fdisk_use_cylinders(cxt)) {
+ fdisk_ask_set_query(ask, _("Last cylinder, +/-cylinders or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ } else {
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+ }
+
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, start));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, limit));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+ fdisk_ask_number_set_base(ask, fdisk_cround(cxt, start)); /* base for relative input */
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ stop = fdisk_ask_number_get_result(ask);
+ isrel = fdisk_ask_number_is_relative(ask);
+ if (fdisk_use_cylinders(cxt)) {
+ stop = stop * fdisk_get_units_per_sector(cxt) - 1;
+ if (stop >limit)
+ stop = limit;
+ }
+
+ if (stop >= start && stop <= limit)
+ break;
+ fdisk_warnx(cxt, _("Value out of range."));
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: raw stop: %ju [limit %ju]", (uintmax_t) stop, (uintmax_t) limit));
+
+ if (stop > limit)
+ stop = limit;
+
+ if (isrel && stop - start < (cxt->grain / fdisk_get_sector_size(cxt))) {
+ /* Don't try to be smart on very small partitions and don't align so small sizes */
+ isrel = 0;
+ DBG(LABEL, ul_debug("DOS: don't align end of tiny partition [start=%ju, stop=%ju, grain=%lu]",
+ (uintmax_t)start, (uintmax_t)stop, cxt->grain));
+ }
+
+ if (stop < limit && isrel && alignment_required(cxt)) {
+ /* the last sector has not been exactly requested (but
+ * defined by +size{K,M,G} convention), so be smart and
+ * align the end of the partition. The next partition
+ * will start at phy.block boundary.
+ */
+ stop = fdisk_align_lba_in_range(cxt, stop, start, limit);
+ if (stop > start)
+ stop -= 1; /* end one sector before aligned offset */
+ if (stop > limit)
+ stop = limit;
+ DBG(LABEL, ul_debug("DOS: aligned stop: %ju", (uintmax_t) stop));
+ }
+
+ set_partition(cxt, n, 0, start, stop, sys, fdisk_partition_is_bootable(pa));
+ if (n > 4) {
+ struct pte *pe = self_pte(cxt, n);
+ assert(pe);
+ set_partition(cxt, n - 1, 1, pe->offset, stop,
+ MBR_DOS_EXTENDED_PARTITION, 0);
+ }
+
+ /* report */
+ {
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sys);
+ fdisk_info_new_partition(cxt, n + 1, start, stop, t);
+ fdisk_unref_parttype(t);
+ }
+
+
+ if (IS_EXTENDED(sys)) {
+ struct pte *pen = self_pte(cxt, n);
+
+ assert(pen);
+ l->ext_index = n;
+ l->ext_offset = start;
+ pen->ex_entry = p;
+ }
+
+ fdisk_label_set_changed(cxt->label, 1);
+ rc = 0;
+done:
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+static int add_logical(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct pte *pe;
+ int rc;
+
+ assert(cxt);
+ assert(partno);
+ assert(cxt->label);
+ assert(self_label(cxt)->ext_offset);
+
+ DBG(LABEL, ul_debug("DOS: nparts max: %zu", cxt->label->nparts_max));
+ pe = self_pte(cxt, cxt->label->nparts_max);
+ assert(pe);
+
+ if (!pe->sectorbuffer) {
+ pe->sectorbuffer = calloc(1, cxt->sector_size);
+ if (!pe->sectorbuffer)
+ return -ENOMEM;
+ DBG(LABEL, ul_debug("DOS: logical: %zu: new EBR sector buffer %p",
+ cxt->label->nparts_max, pe->sectorbuffer));
+ pe->private_sectorbuffer = 1;
+ }
+ pe->pt_entry = mbr_get_partition(pe->sectorbuffer, 0);
+ pe->ex_entry = pe->pt_entry + 1;
+ pe->offset = 0;
+ partition_set_changed(cxt, cxt->label->nparts_max, 1);
+
+ cxt->label->nparts_max++;
+
+ /* this message makes sense only when we use extended/primary/logical
+ * dialog. The dialog is disable for scripts, see dos_add_partition() */
+ if (!cxt->script)
+ fdisk_info(cxt, _("Adding logical partition %zu"),
+ cxt->label->nparts_max);
+ *partno = cxt->label->nparts_max - 1;
+ rc = add_partition(cxt, *partno, pa);
+
+ if (rc) {
+ /* reset on error */
+ cxt->label->nparts_max--;
+ pe->pt_entry = NULL;
+ pe->ex_entry = NULL;
+ pe->offset = 0;
+ pe->changed = 0;
+ }
+
+ return rc;
+}
+
+static int check(struct fdisk_context *cxt, size_t n,
+ unsigned int h, unsigned int s, unsigned int c,
+ unsigned int lba_sector)
+{
+ unsigned int chs_sector, real_s, real_c;
+ int nerrors = 0;
+
+ if (!is_dos_compatible(cxt))
+ return 0;
+
+ real_s = sector(s) - 1;
+ real_c = cylinder(s, c);
+ chs_sector = (real_c * cxt->geom.heads + h) * cxt->geom.sectors + real_s;
+
+ if (!chs_sector) {
+ fdisk_warnx(cxt, _("Partition %zu: contains sector 0"), n);
+ nerrors++;
+ }
+ if (h >= cxt->geom.heads) {
+ fdisk_warnx(cxt, _("Partition %zu: head %d greater than "
+ "maximum %d"), n, h + 1, cxt->geom.heads);
+ nerrors++;
+ }
+ if (real_s >= cxt->geom.sectors) {
+ fdisk_warnx(cxt, _("Partition %zu: sector %d greater than "
+ "maximum %ju"), n, real_s + 1,
+ (uintmax_t) cxt->geom.sectors);
+ nerrors++;
+ }
+ if (real_c >= cxt->geom.cylinders) {
+ fdisk_warnx(cxt, _("Partition %zu: cylinder %d greater than "
+ "maximum %ju"),
+ n, real_c + 1,
+ (uintmax_t) cxt->geom.cylinders);
+ nerrors++;
+ }
+ if (lba_sector / (cxt->geom.heads * cxt->geom.sectors) < 1024 && lba_sector != chs_sector) {
+ fdisk_warnx(cxt, _("Partition %zu: LBA sector %u "
+ "disagrees with C/H/S calculated sector %u"),
+ n, lba_sector, chs_sector);
+ nerrors++;
+ }
+
+ return nerrors;
+}
+
+/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993,
+ * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross,
+ * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S.
+ * Lubkin Oct. 1991). */
+
+static void
+long2chs(struct fdisk_context *cxt, unsigned long ls,
+ unsigned int *c, unsigned int *h, unsigned int *s) {
+ int spc = cxt->geom.heads * cxt->geom.sectors;
+
+ *c = ls / spc;
+ ls = ls % spc;
+ *h = ls / cxt->geom.sectors;
+ *s = ls % cxt->geom.sectors + 1; /* sectors count from 1 */
+}
+
+static int check_consistency(struct fdisk_context *cxt, struct dos_partition *p,
+ size_t partition)
+{
+ unsigned int pbc, pbh, pbs; /* physical beginning c, h, s */
+ unsigned int pec, peh, pes; /* physical ending c, h, s */
+ unsigned int lbc, lbh, lbs; /* logical beginning c, h, s */
+ unsigned int lec, leh, les; /* logical ending c, h, s */
+ int nerrors = 0;
+
+ if (!is_dos_compatible(cxt))
+ return 0;
+
+ if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4))
+ return 0; /* do not check extended partitions */
+
+ /* physical beginning c, h, s */
+ pbc = cylinder(p->bs, p->bc);
+ pbh = p->bh;
+ pbs = sector(p->bs);
+
+ /* physical ending c, h, s */
+ pec = cylinder(p->es, p->ec);
+ peh = p->eh;
+ pes = sector(p->es);
+
+ /* compute logical beginning (c, h, s) */
+ long2chs(cxt, dos_partition_get_start(p), &lbc, &lbh, &lbs);
+
+ /* compute logical ending (c, h, s) */
+ long2chs(cxt, dos_partition_get_start(p) + dos_partition_get_size(p) - 1, &lec, &leh, &les);
+
+ /* Same physical / logical beginning? */
+ if (lbc < 1024
+ && (pbc != lbc || pbh != lbh || pbs != lbs)) {
+ fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+ "beginnings (non-Linux?): "
+ "phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+ partition + 1,
+ pbc, pbh, pbs,
+ lbc, lbh, lbs);
+ nerrors++;
+ }
+
+ /* Same physical / logical ending? */
+ if (lec < 1024
+ && (pec != lec || peh != leh || pes != les)) {
+ fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+ "endings: phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+ partition + 1,
+ pec, peh, pes,
+ lec, leh, les);
+ nerrors++;
+ }
+
+ /* Ending on cylinder boundary? */
+ if (peh != (cxt->geom.heads - 1) || pes != cxt->geom.sectors) {
+ fdisk_warnx(cxt, _("Partition %zu: does not end on "
+ "cylinder boundary."),
+ partition + 1);
+ nerrors++;
+ }
+
+ return nerrors;
+}
+
+static void fill_bounds(struct fdisk_context *cxt,
+ fdisk_sector_t *first, fdisk_sector_t *last)
+{
+ size_t i;
+ struct pte *pe = self_pte(cxt, 0);
+ struct dos_partition *p;
+
+ assert(pe);
+ for (i = 0; i < cxt->label->nparts_max; pe++,i++) {
+ p = pe->pt_entry;
+ if (is_cleared_partition(p) || IS_EXTENDED (p->sys_ind)) {
+ first[i] = SIZE_MAX;
+ last[i] = 0;
+ } else {
+ first[i] = get_abs_partition_start(pe);
+ last[i] = get_abs_partition_end(pe);
+ }
+ }
+}
+
+static int dos_verify_disklabel(struct fdisk_context *cxt)
+{
+ size_t i, j;
+ fdisk_sector_t total = 1, n_sectors = cxt->total_sectors;
+ fdisk_sector_t first[cxt->label->nparts_max],
+ last[cxt->label->nparts_max];
+ struct dos_partition *p;
+ struct fdisk_dos_label *l = self_label(cxt);
+ int nerrors = 0;
+
+ assert(fdisk_is_label(cxt, DOS));
+
+ fill_bounds(cxt, first, last);
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ p = self_partition(cxt, i);
+ if (p && is_used_partition(p) && !IS_EXTENDED(p->sys_ind)) {
+ nerrors += check_consistency(cxt, p, i);
+ assert(pe);
+ if (get_abs_partition_start(pe) < first[i]) {
+ fdisk_warnx(cxt, _(
+ "Partition %zu: bad start-of-data."),
+ i + 1);
+ nerrors++;
+ }
+
+ nerrors += check(cxt, i + 1, p->bh, p->bs, p->bc, first[i]);
+ nerrors += check(cxt, i + 1, p->eh, p->es, p->ec, last[i]);
+ total += last[i] + 1 - first[i];
+
+ if (i == 0)
+ total += get_abs_partition_start(pe) - 1;
+
+ for (j = 0; j < i; j++) {
+ if ((first[i] >= first[j] && first[i] <= last[j])
+ || ((last[i] <= last[j] && last[i] >= first[j]))) {
+
+ fdisk_warnx(cxt, _("Partition %zu: "
+ "overlaps partition %zu."),
+ j + 1, i + 1);
+ nerrors++;
+
+ total += first[i] >= first[j] ?
+ first[i] : first[j];
+ total -= last[i] <= last[j] ?
+ last[i] : last[j];
+ }
+ }
+ }
+ }
+
+ if (l->ext_offset) {
+ fdisk_sector_t e_last;
+ struct pte *ext_pe = self_pte(cxt, l->ext_index);
+
+ assert(ext_pe);
+ e_last = get_abs_partition_end(ext_pe);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ total++;
+ p = self_partition(cxt, i);
+ assert(p);
+
+ if (!p->sys_ind) {
+ if (i != 4 || i + 1 < cxt->label->nparts_max) {
+ fdisk_warnx(cxt,
+ _("Partition %zu: empty."),
+ i + 1);
+ nerrors++;
+ }
+ } else if (first[i] < l->ext_offset
+ || last[i] > e_last) {
+
+ fdisk_warnx(cxt, _("Logical partition %zu: "
+ "not entirely in partition %zu."),
+ i + 1, l->ext_index + 1);
+ nerrors++;
+ }
+ }
+ }
+
+ if (!nerrors) {
+ fdisk_info(cxt, _("No errors detected."));
+ if (total > n_sectors)
+ fdisk_info(cxt, _("Total allocated sectors %ju greater "
+ "than the maximum %ju."), (uintmax_t) total, (uintmax_t) n_sectors);
+ else if (total < n_sectors)
+ fdisk_info(cxt, _("Remaining %ju unallocated %ld-byte "
+ "sectors."), (uintmax_t) n_sectors - total, cxt->sector_size);
+ } else
+ fdisk_warnx(cxt,
+ P_("%d error detected.", "%d errors detected.", nerrors),
+ nerrors);
+
+ return nerrors;
+}
+
+/*
+ * Ask the user for new partition type information (logical, extended).
+ * This function calls the actual partition adding logic - add_partition.
+ *
+ * API callback.
+ */
+static int dos_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ size_t i;
+ uint8_t free_primary = 0, free_sectors = 0;
+ fdisk_sector_t first = 0, grain;
+ int rc = 0;
+ struct fdisk_dos_label *l;
+ struct pte *ext_pe;
+ size_t res = 0; /* partno */
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: new partition wanted"));
+
+ l = self_label(cxt);
+
+ if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+ fdisk_warnx(cxt, _("The maximum number of partitions has "
+ "been created."));
+ return -EINVAL;
+ }
+
+ ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+ /*
+ * partition template (@pa) based partitioning
+ */
+
+ /* A) template specifies start within extended partition; add logical */
+ if (pa && fdisk_partition_has_start(pa) && ext_pe
+ && pa->start >= l->ext_offset
+ && pa->start <= get_abs_partition_end(ext_pe)) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add logical (by offset)", pa));
+
+ if (fdisk_partition_has_partno(pa) && fdisk_partition_get_partno(pa) < 4) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno<4 for logical partition"));
+ return -EINVAL;
+ }
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+
+ /* B) template specifies start out of extended partition; add primary */
+ } else if (pa && fdisk_partition_has_start(pa) && ext_pe) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add primary (by offset)", pa));
+
+ if (fdisk_partition_has_partno(pa) && fdisk_partition_get_partno(pa) >= 4) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno>=4 for primary partition"));
+ return -EINVAL;
+ }
+ if (ext_pe && pa->type && IS_EXTENDED(pa->type->code)) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+
+ /* C) template specifies start (or default), partno < 4; add primary */
+ } else if (pa && (fdisk_partition_start_is_default(pa) || fdisk_partition_has_start(pa))
+ && fdisk_partition_has_partno(pa)
+ && pa->partno < 4) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add primary (by partno)", pa));
+
+ if (ext_pe && pa->type && IS_EXTENDED(pa->type->code)) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+
+ /* D) template specifies start (or default), partno >= 4; add logical */
+ } else if (pa && (fdisk_partition_start_is_default(pa) || fdisk_partition_has_start(pa))
+ && fdisk_partition_has_partno(pa)
+ && pa->partno >= 4) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add logical (by partno)", pa));
+
+ if (!ext_pe) {
+ fdisk_warnx(cxt, _("Extended partition does not exists. Failed to add logical partition."));
+ return -EINVAL;
+ }
+
+ if (fdisk_partition_has_start(pa)
+ && pa->start < l->ext_offset
+ && pa->start > get_abs_partition_end(ext_pe)) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno>=4, but start out of extended"));
+ return -EINVAL;
+ }
+
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+ }
+
+ DBG(LABEL, ul_debug("DOS: dialog driven partitioning"));
+ /* Note @pa may be still used for things like partition type, etc */
+
+ /* check if there is space for primary partition */
+ grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+ first = cxt->first_lba;
+
+ if (cxt->parent && fdisk_is_label(cxt->parent, GPT)) {
+ /* modifying a hybrid MBR, which throws out the rules */
+ grain = 1;
+ first = 1;
+ }
+
+ /* set @first after the last used partition, set @free_sectors if there
+ * is gap in front if the first partition or between used parrtitions. */
+ for (i = 0; i < 4; i++) {
+ struct dos_partition *p = self_partition(cxt, i);
+
+ if (p && is_used_partition(p)) {
+ fdisk_sector_t start = dos_partition_get_start(p);
+ if (first + grain <= start)
+ free_sectors = 1;
+ first = start + dos_partition_get_size(p);
+ } else
+ free_primary++;
+ }
+
+ /* set @free_sectors if there is a space after the first usable sector */
+ if (first + grain - 1 <= cxt->total_sectors - 1)
+ free_sectors = 1;
+
+ DBG(LABEL, ul_debug("DOS: primary: first free: %ju, last on disk: %ju, "
+ "free_sectors=%d, free_primary=%d",
+ (uintmax_t) first,
+ (uintmax_t) cxt->total_sectors - 1,
+ free_sectors, free_primary));
+
+ if (!free_primary || !free_sectors) {
+ DBG(LABEL, ul_debug("DOS: primary impossible"));
+ if (l->ext_offset) {
+ if (!pa || fdisk_partition_has_start(pa)) {
+ /* See above case A); here we have start, but
+ * out of extended partition */
+ const char *msg;
+ if (!free_primary)
+ msg = _("All primary partitions are in use.");
+ else
+ msg = _("All space for primary partitions is in use.");
+
+ if (pa && fdisk_partition_has_start(pa)) {
+ fdisk_warnx(cxt, "%s", msg);
+ return -EINVAL;
+ }
+ fdisk_info(cxt, "%s", msg);
+ }
+ DBG(LABEL, ul_debug("DOS: trying logical"));
+ rc = add_logical(cxt, pa, &res);
+ } else {
+ if (free_primary)
+ fdisk_info(cxt, _("All space for primary partitions is in use."));
+ else
+ /* TRANSLATORS: Try to keep this within 80 characters. */
+ fdisk_info(cxt, _("To create more partitions, first replace "
+ "a primary with an extended partition."));
+ return -EINVAL;
+ }
+ } else {
+ char hint[BUFSIZ];
+ struct fdisk_ask *ask;
+ int c = 0;
+
+ /* the default layout for scripts is to create primary partitions */
+ if (cxt->script || !fdisk_has_dialogs(cxt)) {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+ }
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_MENU);
+ fdisk_ask_set_query(ask, _("Partition type"));
+ fdisk_ask_menu_set_default(ask, free_primary == 1
+ && !l->ext_offset ? 'e' : 'p');
+ snprintf(hint, sizeof(hint),
+ _("%u primary, %d extended, %u free"),
+ 4 - (l->ext_offset ? 1 : 0) - free_primary,
+ l->ext_offset ? 1 : 0,
+ free_primary);
+
+ fdisk_ask_menu_add_item(ask, 'p', _("primary"), hint);
+ if (!l->ext_offset)
+ fdisk_ask_menu_add_item(ask, 'e', _("extended"), _("container for logical partitions"));
+ else
+ fdisk_ask_menu_add_item(ask, 'l', _("logical"), _("numbered from 5"));
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ fdisk_ask_menu_get_result(ask, &c);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+
+ if (c == 'p') {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+ } else if (c == 'l' && l->ext_offset) {
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+ } else if (c == 'e' && !l->ext_offset) {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0) {
+ struct fdisk_partition *xpa = NULL;
+ struct fdisk_parttype *t;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label,
+ MBR_DOS_EXTENDED_PARTITION);
+ if (!pa) {
+ pa = xpa = fdisk_new_partition();
+ if (!xpa)
+ return -ENOMEM;
+ }
+ fdisk_partition_set_type(pa, t);
+ rc = add_partition(cxt, res, pa);
+ if (xpa) {
+ fdisk_unref_partition(xpa);
+ pa = NULL;
+ }
+ }
+ goto done;
+ } else
+ fdisk_warnx(cxt, _("Invalid partition type `%c'."), c);
+ }
+done:
+ if (rc == 0) {
+ cxt->label->nparts_cur++;
+ if (partno)
+ *partno = res;
+ }
+ return rc;
+}
+
+static int write_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+ unsigned char *buf)
+{
+ int rc;
+
+ rc = seek_sector(cxt, secno);
+ if (rc != 0) {
+ fdisk_warn(cxt, _("Cannot write sector %jd: seek failed"),
+ (uintmax_t) secno);
+ return rc;
+ }
+
+ DBG(LABEL, ul_debug("DOS: writing to sector %ju", (uintmax_t) secno));
+
+ if (write(cxt->dev_fd, buf, cxt->sector_size) != (ssize_t) cxt->sector_size)
+ return -errno;
+ return 0;
+}
+
+static int dos_write_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ size_t i;
+ int rc = 0, mbr_changed = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: write PT requested [label-changed: %d, non-pt-changed: %d]",
+ cxt->label->changed, l->non_pt_changed));
+
+ mbr_changed = l->non_pt_changed;
+
+ /* MBR (primary partitions) */
+ if (!mbr_changed) {
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (pe->changed)
+ mbr_changed = 1;
+ }
+ }
+ if (mbr_changed) {
+ DBG(LABEL, ul_debug("DOS: MBR changed, writing"));
+ mbr_set_magic(cxt->firstsector);
+ rc = write_sector(cxt, 0, cxt->firstsector);
+ if (rc)
+ goto done;
+ }
+
+ if (cxt->label->nparts_max <= 4 && l->ext_offset) {
+ /* we have empty extended partition, check if the partition has
+ * been modified and then cleanup possible remaining EBR */
+ struct pte *pe = self_pte(cxt, l->ext_index);
+ unsigned char empty[512] = { 0 };
+ fdisk_sector_t off = pe ? get_abs_partition_start(pe) : 0;
+
+ if (off && pe->changed) {
+ mbr_set_magic(empty);
+ write_sector(cxt, off, empty);
+ }
+ }
+
+ /* EBR (logical partitions) */
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (!pe->changed || !pe->offset || !pe->sectorbuffer)
+ continue;
+
+ mbr_set_magic(pe->sectorbuffer);
+ rc = write_sector(cxt, pe->offset, pe->sectorbuffer);
+ if (rc)
+ goto done;
+ }
+
+done:
+ return rc;
+}
+
+static int dos_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name, uint64_t *offset, size_t *size)
+{
+ assert(cxt);
+
+ *name = NULL;
+ *offset = 0;
+ *size = 0;
+
+ switch (n) {
+ case 0:
+ *name = "MBR";
+ *offset = 0;
+ *size = 512;
+ break;
+ default:
+ /* extended partitions */
+ if ((size_t)n - 1 + 4 < cxt->label->nparts_max) {
+ struct pte *pe = self_pte(cxt, n - 1 + 4);
+
+ assert(pe);
+ assert(pe->private_sectorbuffer);
+
+ *name = "EBR";
+ *offset = (uint64_t) pe->offset * cxt->sector_size;
+ *size = 512;
+ } else
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether partition entries are ordered by their starting positions.
+ * Return 0 if OK. Return i if partition i should have been earlier.
+ * Two separate checks: primary and logical partitions.
+ */
+static int wrong_p_order(struct fdisk_context *cxt, size_t *prev)
+{
+ size_t last_p_start_pos = 0, p_start_pos;
+ size_t i, last_i = 0;
+
+ for (i = 0 ; i < cxt->label->nparts_max; i++) {
+
+ struct pte *pe = self_pte(cxt, i);
+ struct dos_partition *p;
+
+ assert(pe);
+ p = pe->pt_entry;
+
+ if (i == 4) {
+ last_i = 4;
+ last_p_start_pos = 0;
+ }
+ if (is_used_partition(p)) {
+ p_start_pos = get_abs_partition_start(pe);
+
+ if (last_p_start_pos > p_start_pos) {
+ if (prev)
+ *prev = last_i;
+ return i;
+ }
+
+ last_p_start_pos = p_start_pos;
+ last_i = i;
+ }
+ }
+ return 0;
+}
+
+static int dos_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ switch (item->id) {
+ case FDISK_LABELITEM_ID:
+ {
+ unsigned int num = mbr_get_id(cxt->firstsector);
+ item->name = _("Disk identifier");
+ item->type = 's';
+ if (asprintf(&item->data.str, "0x%08x", num) < 0)
+ rc = -ENOMEM;
+ break;
+ }
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+
+}
+
+static int dos_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct dos_partition *p;
+ struct pte *pe;
+ struct fdisk_dos_label *lb;
+
+ assert(cxt);
+ assert(pa);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ lb = self_label(cxt);
+
+ pe = self_pte(cxt, n);
+ assert(pe);
+
+ p = pe->pt_entry;
+ pa->used = !is_cleared_partition(p);
+ if (!pa->used)
+ return 0;
+
+ pa->type = dos_partition_parttype(cxt, p);
+ pa->boot = p->boot_ind == ACTIVE_FLAG ? 1 : 0;
+ pa->start = get_abs_partition_start(pe);
+ pa->size = dos_partition_get_size(p);
+ pa->container = lb->ext_offset && n == lb->ext_index;
+
+ if (n >= 4)
+ pa->parent_partno = lb->ext_index;
+
+ if (p->boot_ind && asprintf(&pa->attrs, "%02x", p->boot_ind) < 0)
+ return -ENOMEM;
+
+ /* start C/H/S */
+ if (asprintf(&pa->start_chs, "%d/%d/%d",
+ cylinder(p->bs, p->bc),
+ p->bh,
+ sector(p->bs)) < 0)
+ return -ENOMEM;
+
+ /* end C/H/S */
+ if (asprintf(&pa->end_chs, "%d/%d/%d",
+ cylinder(p->es, p->ec),
+ p->eh,
+ sector(p->es)) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int has_logical(struct fdisk_context *cxt)
+{
+ size_t i;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ if (l->ptes[i].pt_entry)
+ return 1;
+ }
+ return 0;
+}
+
+static int dos_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_dos_label *l;
+ struct dos_partition *p;
+ struct pte *pe;
+ int orgtype;
+ fdisk_sector_t start, size;
+
+ assert(cxt);
+ assert(pa);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (n >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ l = self_label(cxt);
+ p = self_partition(cxt, n);
+ assert(p);
+
+ pe = self_pte(cxt, n);
+ if (!pe)
+ return -EINVAL;
+
+ orgtype = p->sys_ind;
+
+ if (pa->type) {
+ if (IS_EXTENDED(pa->type->code) && l->ext_offset && l->ext_index != n) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+
+ if (!pa->type->code)
+ fdisk_warnx(cxt, _("Type 0 means free space to many systems. "
+ "Having partitions of type 0 is probably unwise."));
+
+ if (IS_EXTENDED(p->sys_ind) && !IS_EXTENDED(pa->type->code) && has_logical(cxt)) {
+ fdisk_warnx(cxt, _(
+ "Cannot change type of the extended partition which is "
+ "already used by logical partitions. Delete logical "
+ "partitions first."));
+ return -EINVAL;
+ }
+ }
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(size);
+
+ if (fdisk_partition_has_start(pa))
+ start = pa->start;
+ if (fdisk_partition_has_size(pa))
+ size = pa->size;
+
+ if (!FDISK_IS_UNDEF(start) || !FDISK_IS_UNDEF(size)) {
+ DBG(LABEL, ul_debug("DOS: resize partition"));
+
+ if (FDISK_IS_UNDEF(start))
+ start = get_abs_partition_start(pe);
+ if (FDISK_IS_UNDEF(size))
+ size = dos_partition_get_size(p);
+
+ set_partition(cxt, n, 0, start, start + size - 1,
+ pa->type ? pa->type->code : p->sys_ind,
+ FDISK_IS_UNDEF(pa->boot) ?
+ p->boot_ind == ACTIVE_FLAG :
+ fdisk_partition_is_bootable(pa));
+ } else {
+ DBG(LABEL, ul_debug("DOS: keep size, modify properties"));
+ if (pa->type)
+ p->sys_ind = pa->type->code;
+ if (!FDISK_IS_UNDEF(pa->boot))
+ p->boot_ind = fdisk_partition_is_bootable(pa) ? ACTIVE_FLAG : 0;
+ }
+
+ if (pa->type) {
+ if (IS_EXTENDED(pa->type->code) && !IS_EXTENDED(orgtype)) {
+ /* new extended partition - create a reference */
+ l->ext_index = n;
+ l->ext_offset = dos_partition_get_start(p);
+ pe->ex_entry = p;
+ } else if (IS_EXTENDED(orgtype)) {
+ /* remove extended partition */
+ cxt->label->nparts_max = 4;
+ l->ptes[l->ext_index].ex_entry = NULL;
+ l->ext_offset = 0;
+ l->ext_index = 0;
+ }
+ }
+
+ partition_set_changed(cxt, n, 1);
+ return 0;
+}
+
+static void print_chain_of_logicals(struct fdisk_context *cxt)
+{
+ size_t i;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ fputc('\n', stdout);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fprintf(stderr, "#%02zu EBR [%10ju], "
+ "data[start=%10ju (%10ju), size=%10ju], "
+ "link[start=%10ju (%10ju), size=%10ju]\n",
+ i, (uintmax_t) pe->offset,
+ /* data */
+ (uintmax_t) dos_partition_get_start(pe->pt_entry),
+ (uintmax_t) get_abs_partition_start(pe),
+ (uintmax_t) dos_partition_get_size(pe->pt_entry),
+ /* link */
+ (uintmax_t) dos_partition_get_start(pe->ex_entry),
+ (uintmax_t) l->ext_offset + dos_partition_get_start(pe->ex_entry),
+ (uintmax_t) dos_partition_get_size(pe->ex_entry));
+ }
+}
+
+static int cmp_ebr_offsets(const void *a, const void *b)
+{
+ const struct pte *ae = (const struct pte *) a,
+ *be = (const struct pte *) b;
+
+ if (ae->offset == 0 && be->offset == 0)
+ return 0;
+ if (ae->offset == 0)
+ return 1;
+ if (be->offset == 0)
+ return -1;
+
+ return cmp_numbers(ae->offset, be->offset);
+}
+
+/*
+ * Fix the chain of logicals.
+ *
+ * The function does not modify data partitions within EBR tables
+ * (pte->pt_entry). It sorts the chain by EBR offsets and then update links
+ * (pte->ex_entry) between EBR tables.
+ *
+ */
+static void fix_chain_of_logicals(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct pte *last;
+ size_t i;
+
+ DBG(LABEL, print_chain_of_logicals(cxt));
+
+ /* Sort chain by EBR offsets */
+ qsort(&l->ptes[4], cxt->label->nparts_max - 4, sizeof(struct pte),
+ cmp_ebr_offsets);
+
+again:
+ /* Sort data partitions by start */
+ for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+ struct pte *cur = self_pte(cxt, i),
+ *nxt = self_pte(cxt, i + 1);
+
+ assert(cur);
+ assert(nxt);
+
+ if (get_abs_partition_start(cur) >
+ get_abs_partition_start(nxt)) {
+
+ struct dos_partition tmp = *cur->pt_entry;
+ fdisk_sector_t cur_start = get_abs_partition_start(cur),
+ nxt_start = get_abs_partition_start(nxt);
+
+ /* swap data partitions */
+ *cur->pt_entry = *nxt->pt_entry;
+ *nxt->pt_entry = tmp;
+
+ /* Recount starts according to EBR offsets, the absolute
+ * address still has to be the same! */
+ dos_partition_set_start(cur->pt_entry, nxt_start - cur->offset);
+ dos_partition_sync_chs(cur->pt_entry, cur->offset, cxt->geom.sectors, cxt->geom.heads);
+ dos_partition_set_start(nxt->pt_entry, cur_start - nxt->offset);
+ dos_partition_sync_chs(nxt->pt_entry, nxt->offset, cxt->geom.sectors, cxt->geom.heads);
+
+ partition_set_changed(cxt, i, 1);
+ partition_set_changed(cxt, i + 1, 1);
+ goto again;
+ }
+ }
+
+ /* Update EBR links */
+ for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+ struct pte *cur = self_pte(cxt, i),
+ *nxt = self_pte(cxt, i + 1);
+
+ assert(cur);
+ assert(nxt);
+
+ fdisk_sector_t noff = nxt->offset - l->ext_offset,
+ ooff = dos_partition_get_start(cur->ex_entry);
+
+ if (noff == ooff)
+ continue;
+
+ DBG(LABEL, ul_debug("DOS: fix EBR [%10ju] link %ju -> %ju",
+ (uintmax_t) cur->offset,
+ (uintmax_t) ooff, (uintmax_t) noff));
+
+ set_partition(cxt, i, 1, nxt->offset,
+ get_abs_partition_end(nxt),
+ MBR_DOS_EXTENDED_PARTITION, 0);
+ }
+
+ /* always terminate the chain ! */
+ last = self_pte(cxt, cxt->label->nparts_max - 1);
+ if (last) {
+ clear_partition(last->ex_entry);
+ partition_set_changed(cxt, cxt->label->nparts_max - 1, 1);
+ }
+
+ DBG(LABEL, print_chain_of_logicals(cxt));
+}
+
+static int dos_reorder(struct fdisk_context *cxt)
+{
+ struct pte *pei, *pek;
+ size_t i,k;
+
+ if (!wrong_p_order(cxt, NULL))
+ return 1;
+
+ while ((i = wrong_p_order(cxt, &k)) != 0 && i < 4) {
+ /* partition i should have come earlier, move it */
+ /* We have to move data in the MBR */
+ struct dos_partition *pi, *pk, *pe, pbuf;
+ pei = self_pte(cxt, i);
+ pek = self_pte(cxt, k);
+
+ assert(pei);
+ assert(pek);
+
+ pe = pei->ex_entry;
+ pei->ex_entry = pek->ex_entry;
+ pek->ex_entry = pe;
+
+ pi = pei->pt_entry;
+ pk = pek->pt_entry;
+
+ memmove(&pbuf, pi, sizeof(struct dos_partition));
+ memmove(pi, pk, sizeof(struct dos_partition));
+ memmove(pk, &pbuf, sizeof(struct dos_partition));
+
+ partition_set_changed(cxt, i, 1);
+ partition_set_changed(cxt, k, 1);
+ }
+
+ if (i)
+ fix_chain_of_logicals(cxt);
+
+ return 0;
+}
+
+/**
+ * fdisk_dos_fix_chs:
+ * @cxt: fdisk context
+ *
+ * Fix beginning and ending C/H/S values for every partition
+ * according to LBA relative offset, relative beginning and
+ * size and fdisk idea of disk geometry (sectors per track
+ * and number of heads).
+ *
+ * Returns: number of fixed (changed) partitions.
+ */
+int fdisk_dos_fix_chs(struct fdisk_context *cxt)
+{
+ unsigned int obc, obh, obs; /* old beginning c, h, s */
+ unsigned int oec, oeh, oes; /* old ending c, h, s */
+ unsigned int nbc, nbh, nbs; /* new beginning c, h, s */
+ unsigned int nec, neh, nes; /* new ending c, h, s */
+ fdisk_sector_t l, sects; /* lba beginning and size */
+ struct dos_partition *p;
+ struct pte *pe;
+ int changed = 0;
+ size_t i;
+
+ assert(fdisk_is_label(cxt, DOS));
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ p = self_partition(cxt, i);
+ if (!p || !is_used_partition(p))
+ continue;
+
+ pe = self_pte(cxt, i);
+
+ /* old beginning c, h, s */
+ obc = cylinder(p->bs, p->bc);
+ obh = p->bh;
+ obs = sector(p->bs);
+
+ /* old ending c, h, s */
+ oec = cylinder(p->es, p->ec);
+ oeh = p->eh;
+ oes = sector(p->es);
+
+ /* new beginning c, h, s */
+ l = get_abs_partition_start(pe);
+ long2chs(cxt, l, &nbc, &nbh, &nbs);
+ if (l > UINT32_MAX || nbc >= 1024) {
+ nbc = 1023;
+ nbh = cxt->geom.heads-1;
+ nbs = cxt->geom.sectors;
+ }
+
+ /* new ending c, h, s */
+ sects = dos_partition_get_size(p);
+ long2chs(cxt, l + sects - 1, &nec, &neh, &nes);
+ if (lba_overflowed(l, sects) || nec >= 1024) {
+ nec = 1023;
+ neh = cxt->geom.heads-1;
+ nes = cxt->geom.sectors;
+ }
+
+ if (obc != nbc || obh != nbh || obs != nbs ||
+ oec != nec || oeh != neh || oes != nes) {
+ DBG(LABEL, ul_debug("DOS: changing %zu partition CHS "
+ "from (%d, %d, %d)-(%d, %d, %d) "
+ "to (%d, %d, %d)-(%d, %d, %d)",
+ i, obc, obh, obs, oec, oeh, oes,
+ nbc, nbh, nbs, nec, neh, nes));
+ p->bc = nbc & 0xff;
+ p->bh = nbh;
+ p->bs = nbs | ((nbc >> 2) & 0xc0);
+ p->ec = nec & 0xff;
+ p->eh = neh;
+ p->es = nes | ((nec >> 2) & 0xc0);
+ partition_set_changed(cxt, i, 1);
+ changed++;
+ }
+ }
+
+ return changed;
+}
+
+/* TODO: use fdisk_set_partition() API */
+int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i)
+{
+ struct pte *pe;
+ struct dos_partition *p;
+ unsigned int new, free_start, curr_start, last;
+ uintmax_t res = 0;
+ size_t x;
+ int rc;
+
+ assert(cxt);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, i);
+ if (!pe)
+ return -EINVAL;
+
+ p = pe->pt_entry;
+
+ if (!is_used_partition(p) || IS_EXTENDED (p->sys_ind)) {
+ fdisk_warnx(cxt, _("Partition %zu: no data area."), i + 1);
+ return 0;
+ }
+
+ /* The safe start is at the second sector, but some use-cases require
+ * to have MBR within the first partition , so default to the first
+ * sector of the disk or at the second sector of the extended partition
+ */
+ free_start = pe->offset ? pe->offset + 1 : 0;
+
+ curr_start = get_abs_partition_start(pe);
+
+ /* look for a free space before the current start of the partition */
+ for (x = 0; x < cxt->label->nparts_max; x++) {
+ unsigned int end;
+ struct pte *prev_pe = self_pte(cxt, x);
+ struct dos_partition *prev_p;
+
+ assert(prev_pe);
+
+ prev_p = prev_pe->pt_entry;
+ if (!prev_p)
+ continue;
+ end = get_abs_partition_start(prev_pe)
+ + dos_partition_get_size(prev_p);
+
+ if (is_used_partition(prev_p) &&
+ end > free_start && end <= curr_start)
+ free_start = end;
+ }
+
+ last = get_abs_partition_end(pe);
+
+ rc = fdisk_ask_number(cxt, free_start, curr_start, last,
+ _("New beginning of data"), &res);
+ if (rc)
+ return rc;
+
+ new = res - pe->offset;
+
+ if (new != dos_partition_get_size(p)) {
+ unsigned int sects = dos_partition_get_size(p)
+ + dos_partition_get_start(p) - new;
+
+ dos_partition_set_size(p, sects);
+ dos_partition_set_start(p, new);
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+
+ partition_set_changed(cxt, i, 1);
+
+ if (new == 0)
+ fdisk_info(cxt, _("The new beginning of the partition overlaps the disk "
+ "label area. Be very careful when using the partition. "
+ "You can lose all your partitions on the disk."));
+ }
+
+ return rc;
+}
+
+static int dos_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct dos_partition *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+
+ p = self_partition(cxt, i);
+
+ return p && !is_cleared_partition(p);
+}
+
+static int dos_toggle_partition_flag(
+ struct fdisk_context *cxt,
+ size_t i,
+ unsigned long flag)
+{
+ struct dos_partition *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ p = self_partition(cxt, i);
+ assert(p);
+
+ switch (flag) {
+ case DOS_FLAG_ACTIVE:
+ if (IS_EXTENDED(p->sys_ind) && !p->boot_ind)
+ fdisk_warnx(cxt, _("Partition %zu: is an extended "
+ "partition."), i + 1);
+
+ p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG);
+ partition_set_changed(cxt, i, 1);
+ fdisk_info(cxt, p->boot_ind ?
+ _("The bootable flag on partition %zu is enabled now.") :
+ _("The bootable flag on partition %zu is disabled now."),
+ i + 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct fdisk_field dos_fields[] =
+{
+ /* basic */
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_BOOT, N_("Boot"), 1, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, 0 },
+
+ /* expert mode */
+ { FDISK_FIELD_SADDR, N_("Start-C/H/S"), 1, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_EADDR, N_("End-C/H/S"), 1, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 2, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL }
+
+};
+
+static const struct fdisk_label_operations dos_operations =
+{
+ .probe = dos_probe_label,
+ .write = dos_write_disklabel,
+ .verify = dos_verify_disklabel,
+ .create = dos_create_disklabel,
+ .locate = dos_locate_disklabel,
+ .get_item = dos_get_disklabel_item,
+ .set_id = dos_set_disklabel_id,
+
+ .get_part = dos_get_partition,
+ .set_part = dos_set_partition,
+ .add_part = dos_add_partition,
+ .del_part = dos_delete_partition,
+ .reorder = dos_reorder,
+
+ .part_toggle_flag = dos_toggle_partition_flag,
+ .part_is_used = dos_partition_is_used,
+
+ .reset_alignment = dos_reset_alignment,
+
+ .deinit = dos_deinit,
+};
+
+/*
+ * allocates DOS in-memory stuff
+ */
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_dos_label *dos;
+
+ dos = calloc(1, sizeof(*dos));
+ if (!dos)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) dos;
+ lb->name = "dos";
+ lb->id = FDISK_DISKLABEL_DOS;
+ lb->op = &dos_operations;
+
+ lb->parttypes = dos_parttypes;
+ lb->nparttypes = ARRAY_SIZE(dos_parttypes) - 1;
+ lb->parttype_cuts = dos_parttype_cuts;
+ lb->nparttype_cuts = ARRAY_SIZE(dos_parttype_cuts);
+
+ lb->fields = dos_fields;
+ lb->nfields = ARRAY_SIZE(dos_fields);
+
+ lb->geom_min.sectors = 1;
+ lb->geom_min.heads = 1;
+ lb->geom_min.cylinders = 1;
+
+ lb->geom_max.sectors = 63;
+ lb->geom_max.heads = 255;
+ lb->geom_max.cylinders = 1048576;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) dos;
+}
+
+/**
+ * fdisk_dos_enable_compatible:
+ * @lb: DOS label (see fdisk_get_label())
+ * @enable: 0 or 1
+ *
+ * Enables deprecated DOS compatible mode, in this mode library checks for
+ * cylinders boundary, cases about CHS addressing and another obscure things.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable)
+{
+ struct fdisk_dos_label *dos = (struct fdisk_dos_label *) lb;
+
+ if (!lb)
+ return -EINVAL;
+
+ dos->compatible = enable;
+ if (enable)
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+ return 0;
+}
+
+/**
+ * fdisk_dos_is_compatible:
+ * @lb: DOS label
+ *
+ * Returns: 0 if DOS compatibility disabled, 1 if enabled
+ */
+int fdisk_dos_is_compatible(struct fdisk_label *lb)
+{
+ return ((struct fdisk_dos_label *) lb)->compatible;
+}
diff --git a/libfdisk/src/fdiskP.h b/libfdisk/src/fdiskP.h
new file mode 100644
index 0000000..7c0830e
--- /dev/null
+++ b/libfdisk/src/fdiskP.h
@@ -0,0 +1,540 @@
+/*
+ * fdiskP.h - private library header file
+ *
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#ifndef _LIBFDISK_PRIVATE_H
+#define _LIBFDISK_PRIVATE_H
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <uuid.h>
+
+#include "c.h"
+#include "libfdisk.h"
+
+#include "list.h"
+#include "debug.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+/*
+ * Debug
+ */
+#define LIBFDISK_DEBUG_HELP (1 << 0)
+#define LIBFDISK_DEBUG_INIT (1 << 1)
+#define LIBFDISK_DEBUG_CXT (1 << 2)
+#define LIBFDISK_DEBUG_LABEL (1 << 3)
+#define LIBFDISK_DEBUG_ASK (1 << 4)
+#define LIBFDISK_DEBUG_PART (1 << 6)
+#define LIBFDISK_DEBUG_PARTTYPE (1 << 7)
+#define LIBFDISK_DEBUG_TAB (1 << 8)
+#define LIBFDISK_DEBUG_SCRIPT (1 << 9)
+#define LIBFDISK_DEBUG_WIPE (1 << 10)
+#define LIBFDISK_DEBUG_ITEM (1 << 11)
+#define LIBFDISK_DEBUG_GPT (1 << 12)
+#define LIBFDISK_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(libfdisk);
+#define DBG(m, x) __UL_DBG(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define DBG_FLUSH __UL_DBG_FLUSH(libfdisk, LIBFDISK_DEBUG_)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libfdisk)
+#include "debugobj.h"
+
+/*
+ * NLS -- the library has to be independent on main program, so define
+ * UL_TEXTDOMAIN_EXPLICIT before you include nls.h.
+ *
+ * Now we use util-linux.po (=PACKAGE), rather than maintain the texts
+ * in the separate libfdisk.po file.
+ */
+#define LIBFDISK_TEXTDOMAIN PACKAGE
+#define UL_TEXTDOMAIN_EXPLICIT LIBFDISK_TEXTDOMAIN
+#include "nls.h"
+
+
+#ifdef TEST_PROGRAM
+struct fdisk_test {
+ const char *name;
+ int (*body)(struct fdisk_test *ts, int argc, char *argv[]);
+ const char *usage;
+};
+
+/* test.c */
+extern int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[]);
+#endif
+
+#define FDISK_GPT_NPARTITIONS_DEFAULT 128
+
+/*
+ * Generic iterator
+ */
+struct fdisk_iter {
+ struct list_head *p; /* current position */
+ struct list_head *head; /* start position */
+ int direction; /* FDISK_ITER_{FOR,BACK}WARD */
+};
+
+#define IS_ITER_FORWARD(_i) ((_i)->direction == FDISK_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i) ((_i)->direction == FDISK_ITER_BACKWARD)
+
+#define FDISK_ITER_INIT(itr, list) \
+ do { \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (list)->next : (list)->prev; \
+ (itr)->head = (list); \
+ } while(0)
+
+#define FDISK_ITER_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+/*
+ * Partition types
+ */
+struct fdisk_parttype {
+ unsigned int code; /* type as number or zero */
+ char *name; /* description */
+ char *typestr; /* type as string or NULL */
+
+ unsigned int flags; /* FDISK_PARTTYPE_* flags */
+ int refcount; /* reference counter for allocated types */
+};
+
+enum {
+ FDISK_PARTTYPE_UNKNOWN = (1 << 1),
+ FDISK_PARTTYPE_INVISIBLE = (1 << 2),
+ FDISK_PARTTYPE_ALLOCATED = (1 << 3)
+};
+
+#define fdisk_parttype_is_invisible(_x) ((_x) && ((_x)->flags & FDISK_PARTTYPE_INVISIBLE))
+#define fdisk_parttype_is_allocated(_x) ((_x) && ((_x)->flags & FDISK_PARTTYPE_ALLOCATED))
+
+/*
+ * Shortcut (used for partition types)
+ */
+struct fdisk_shortcut {
+ const char *shortcut; /* shortcut, usually one letter (e.h. "H") */
+ const char *alias; /* human readable alias (e.g. "home") */
+ const char *data; /* for example partition type */
+
+ unsigned int deprecated : 1;
+};
+
+struct fdisk_partition {
+ int refcount; /* reference counter */
+
+ size_t partno; /* partition number */
+ size_t parent_partno; /* for logical partitions */
+
+ fdisk_sector_t start; /* first sectors */
+ fdisk_sector_t size; /* size in sectors */
+
+ int movestart; /* FDISK_MOVE_* (scripts only) */
+ int resize; /* FDISK_RESIZE_* (scripts only) */
+
+ char *name; /* partition name */
+ char *uuid; /* partition UUID */
+ char *attrs; /* partition flags/attributes converted to string */
+ struct fdisk_parttype *type; /* partition type */
+
+ char *fstype; /* filesystem type */
+ char *fsuuid; /* filesystem uuid */
+ char *fslabel; /* filesystem label */
+
+ struct list_head parts; /* list of partitions */
+
+ /* extra fields for partition_to_string() */
+ char start_post; /* start postfix (e.g. '+') */
+ char end_post; /* end postfix */
+ char size_post; /* size postfix */
+
+ uint64_t fsize; /* bsd junk */
+ uint64_t bsize;
+ uint64_t cpg;
+
+ char *start_chs; /* start C/H/S in string */
+ char *end_chs; /* end C/H/S in string */
+
+ unsigned int boot; /* MBR: bootable */
+
+ unsigned int container : 1, /* container partition (e.g. extended partition) */
+ end_follow_default : 1, /* use default end */
+ freespace : 1, /* this is free space */
+ partno_follow_default : 1, /* use default partno */
+ size_explicit : 1, /* don't align the size */
+ start_follow_default : 1, /* use default start */
+ fs_probed : 1, /* already probed by blkid */
+ used : 1, /* partition already used */
+ wholedisk : 1; /* special system partition */
+};
+
+enum {
+ FDISK_MOVE_NONE = 0,
+ FDISK_MOVE_DOWN = -1,
+ FDISK_MOVE_UP = 1
+};
+
+enum {
+ FDISK_RESIZE_NONE = 0,
+ FDISK_RESIZE_REDUCE = -1,
+ FDISK_RESIZE_ENLARGE = 1
+};
+
+#define FDISK_INIT_UNDEF(_x) ((_x) = (__typeof__(_x)) -1)
+#define FDISK_IS_UNDEF(_x) ((_x) == (__typeof__(_x)) -1)
+
+struct fdisk_table {
+ struct list_head parts; /* partitions */
+ int refcount;
+ size_t nents; /* number of partitions */
+};
+
+/*
+ * Legacy CHS based geometry
+ */
+struct fdisk_geometry {
+ unsigned int heads;
+ fdisk_sector_t sectors;
+ fdisk_sector_t cylinders;
+};
+
+/*
+ * Label specific operations
+ */
+struct fdisk_label_operations {
+ /* probe disk label */
+ int (*probe)(struct fdisk_context *cxt);
+ /* write in-memory changes to disk */
+ int (*write)(struct fdisk_context *cxt);
+ /* verify the partition table */
+ int (*verify)(struct fdisk_context *cxt);
+ /* create new disk label */
+ int (*create)(struct fdisk_context *cxt);
+ /* returns offset and size of the 'n' part of the PT */
+ int (*locate)(struct fdisk_context *cxt, int n, const char **name,
+ uint64_t *offset, size_t *size);
+ /* reorder partitions */
+ int (*reorder)(struct fdisk_context *cxt);
+ /* get details from label */
+ int (*get_item)(struct fdisk_context *cxt, struct fdisk_labelitem *item);
+ /* set disk label ID */
+ int (*set_id)(struct fdisk_context *cxt, const char *str);
+
+
+ /* new partition */
+ int (*add_part)(struct fdisk_context *cxt, struct fdisk_partition *pa,
+ size_t *partno);
+ /* delete partition */
+ int (*del_part)(struct fdisk_context *cxt, size_t partnum);
+
+ /* fill in partition struct */
+ int (*get_part)(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa);
+ /* modify partition */
+ int (*set_part)(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa);
+
+ /* return state of the partition */
+ int (*part_is_used)(struct fdisk_context *cxt, size_t partnum);
+
+ int (*part_toggle_flag)(struct fdisk_context *cxt, size_t i, unsigned long flag);
+
+ /* refresh alignment setting */
+ int (*reset_alignment)(struct fdisk_context *cxt);
+
+ /* free in-memory label stuff */
+ void (*free)(struct fdisk_label *lb);
+
+ /* deinit in-memory label stuff */
+ void (*deinit)(struct fdisk_label *lb);
+};
+
+/*
+ * The fields describes how to display libfdisk_partition
+ */
+struct fdisk_field {
+ int id; /* FDISK_FIELD_* */
+ const char *name; /* field name */
+ double width; /* field width (compatible with libsmartcols whint) */
+ int flags; /* FDISK_FIELDFL_* */
+};
+
+/* note that the defaults is to display a column always */
+enum {
+ FDISK_FIELDFL_DETAIL = (1 << 1), /* only display if fdisk_is_details() */
+ FDISK_FIELDFL_EYECANDY = (1 << 2), /* don't display if fdisk_is_details() */
+ FDISK_FIELDFL_NUMBER = (1 << 3), /* column display numbers */
+};
+
+/*
+ * Generic label
+ */
+struct fdisk_label {
+ const char *name; /* label name */
+ enum fdisk_labeltype id; /* FDISK_DISKLABEL_* */
+ struct fdisk_parttype *parttypes; /* supported partitions types */
+ size_t nparttypes; /* number of items in parttypes[] */
+
+ const struct fdisk_shortcut *parttype_cuts; /* partition type shortcuts */
+ size_t nparttype_cuts; /* number of items in parttype_cuts */
+
+ size_t nparts_max; /* maximal number of partitions */
+ size_t nparts_cur; /* number of currently used partitions */
+
+ int flags; /* FDISK_LABEL_FL_* flags */
+
+ struct fdisk_geometry geom_min; /* minimal geometry */
+ struct fdisk_geometry geom_max; /* maximal geometry */
+
+ unsigned int changed:1, /* label has been modified */
+ disabled:1; /* this driver is disabled at all */
+
+ const struct fdisk_field *fields; /* all possible fields */
+ size_t nfields;
+
+ const struct fdisk_label_operations *op;
+};
+
+
+/* label driver flags */
+enum {
+ FDISK_LABEL_FL_REQUIRE_GEOMETRY = (1 << 2),
+ FDISK_LABEL_FL_INCHARS_PARTNO = (1 << 3)
+};
+
+/* label allocators */
+extern struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt);
+
+
+struct ask_menuitem {
+ char key;
+ const char *name;
+ const char *desc;
+
+ struct ask_menuitem *next;
+};
+
+/* fdisk dialog -- note that nothing from this stuff will be directly exported,
+ * we will have get/set() function for everything.
+ */
+struct fdisk_ask {
+ int type; /* FDISK_ASKTYPE_* */
+ char *query;
+
+ int refcount;
+
+ union {
+ /* FDISK_ASKTYPE_{NUMBER,OFFSET} */
+ struct ask_number {
+ uint64_t hig; /* high limit */
+ uint64_t low; /* low limit */
+ uint64_t dfl; /* default */
+ uint64_t result;
+ uint64_t base; /* for relative results */
+ uint64_t unit; /* unit for offsets */
+ const char *range; /* by library generated list */
+ unsigned int relative :1,
+ inchars :1,
+ wrap_negative :1;
+ } num;
+ /* FDISK_ASKTYPE_{WARN,WARNX,..} */
+ struct ask_print {
+ const char *mesg;
+ int errnum; /* errno */
+ } print;
+ /* FDISK_ASKTYPE_YESNO */
+ struct ask_yesno {
+ int result; /* TRUE or FALSE */
+ } yesno;
+ /* FDISK_ASKTYPE_STRING */
+ struct ask_string {
+ char *result; /* allocated */
+ } str;
+ /* FDISK_ASKTYPE_MENU */
+ struct ask_menu {
+ int dfl; /* default menu item */
+ int result;
+ struct ask_menuitem *first;
+ } menu;
+ } data;
+};
+
+struct fdisk_context {
+ int dev_fd; /* device descriptor */
+ char *dev_path; /* device path */
+ char *dev_model; /* on linux /sys/block/<name>/device/model or NULL */
+ struct stat dev_st; /* stat(2) result */
+
+ int refcount;
+
+ unsigned char *firstsector; /* buffer with master boot record */
+ unsigned long firstsector_bufsz;
+
+
+ /* topology */
+ unsigned long io_size; /* I/O size used by fdisk */
+ unsigned long optimal_io_size; /* optional I/O returned by device */
+ unsigned long min_io_size; /* minimal I/O size */
+ unsigned long phy_sector_size; /* physical size */
+ unsigned long sector_size; /* logical size */
+ unsigned long alignment_offset;
+
+ unsigned int readonly : 1, /* don't write to the device */
+ display_in_cyl_units : 1, /* for obscure labels */
+ display_details : 1, /* expert display mode */
+ protect_bootbits : 1, /* don't zeroize first sector */
+ pt_collision : 1, /* another PT detected by libblkid */
+ no_disalogs : 1, /* disable dialog-driven partititoning */
+ dev_model_probed : 1, /* tried to read from sys */
+ is_priv : 1, /* open by libfdisk */
+ is_excl : 1, /* open with O_EXCL */
+ listonly : 1; /* list partition, nothing else */
+
+ char *collision; /* name of already existing FS/PT */
+ struct list_head wipes; /* list of areas to wipe before write */
+
+ int sizeunit; /* SIZE fields, FDISK_SIZEUNIT_* */
+
+ /* alignment */
+ unsigned long grain; /* alignment unit */
+ fdisk_sector_t first_lba; /* recommended begin of the first partition */
+ fdisk_sector_t last_lba; /* recommended end of last partition */
+
+ /* geometry */
+ fdisk_sector_t total_sectors; /* in logical sectors */
+ struct fdisk_geometry geom;
+
+ /* user setting to overwrite device default */
+ struct fdisk_geometry user_geom;
+ unsigned long user_pyh_sector;
+ unsigned long user_log_sector;
+ unsigned long user_grain;
+
+ struct fdisk_label *label; /* current label, pointer to labels[] */
+
+ size_t nlabels; /* number of initialized label drivers */
+ struct fdisk_label *labels[8]; /* all supported labels,
+ * FIXME: use any enum rather than hardcoded number */
+
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *); /* fdisk dialogs callback */
+ void *ask_data; /* ask_cb() data */
+
+ struct fdisk_context *parent; /* for nested PT */
+ struct fdisk_script *script; /* what we want to follow */
+};
+
+/* table */
+enum {
+ FDISK_DIFF_UNCHANGED = 0,
+ FDISK_DIFF_REMOVED,
+ FDISK_DIFF_ADDED,
+ FDISK_DIFF_MOVED,
+ FDISK_DIFF_RESIZED
+};
+extern int fdisk_diff_tables(struct fdisk_table *a, struct fdisk_table *b,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **res, int *change);
+extern void fdisk_debug_print_table(struct fdisk_table *tb);
+
+
+/* context.c */
+extern int __fdisk_switch_label(struct fdisk_context *cxt,
+ struct fdisk_label *lb);
+extern int fdisk_missing_geometry(struct fdisk_context *cxt);
+
+/* alignment.c */
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num);
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num);
+
+extern int fdisk_discover_geometry(struct fdisk_context *cxt);
+extern int fdisk_discover_topology(struct fdisk_context *cxt);
+
+extern int fdisk_has_user_device_geometry(struct fdisk_context *cxt);
+extern int fdisk_apply_user_device_properties(struct fdisk_context *cxt);
+extern int fdisk_apply_label_device_properties(struct fdisk_context *cxt);
+extern void fdisk_zeroize_device_properties(struct fdisk_context *cxt);
+
+/* utils.c */
+extern int fdisk_init_firstsector_buffer(struct fdisk_context *cxt,
+ unsigned int protect_off, unsigned int protect_size);
+extern int fdisk_read_firstsector(struct fdisk_context *cxt);
+
+/* label.c */
+extern int fdisk_probe_labels(struct fdisk_context *cxt);
+extern void fdisk_deinit_label(struct fdisk_label *lb);
+
+struct fdisk_labelitem {
+ int refcount; /* reference counter */
+ int id; /* <label>_ITEM_* */
+ char type; /* s = string, j = uint64 */
+ const char *name; /* human readable name */
+
+ union {
+ char *str;
+ uint64_t num64;
+ } data;
+};
+
+/* Use only internally for non-allocated items, never use
+ * refcouting for such items!
+ */
+#define FDISK_LABELITEM_INIT { .type = 0, .refcount = 0 }
+
+/* ask.c */
+struct fdisk_ask *fdisk_new_ask(void);
+void fdisk_reset_ask(struct fdisk_ask *ask);
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str);
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type);
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask);
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range);
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt);
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low);
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high);
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base);
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit);
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask);
+int fdisk_ask_number_set_wrap_negative(struct fdisk_ask *ask, int wrap_negative);
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl);
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+ const char *name, const char *desc);
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum);
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg);
+int fdisk_info_new_partition(
+ struct fdisk_context *cxt,
+ int num, fdisk_sector_t start, fdisk_sector_t stop,
+ struct fdisk_parttype *t);
+
+/* dos.c */
+extern struct dos_partition *fdisk_dos_get_partition(
+ struct fdisk_context *cxt,
+ size_t i);
+
+/* wipe.c */
+void fdisk_free_wipe_areas(struct fdisk_context *cxt);
+int fdisk_set_wipe_area(struct fdisk_context *cxt, uint64_t start, uint64_t size, int enable);
+int fdisk_do_wipe(struct fdisk_context *cxt);
+int fdisk_has_wipe_area(struct fdisk_context *cxt, uint64_t start, uint64_t size);
+int fdisk_check_collisions(struct fdisk_context *cxt);
+
+/* parttype.c */
+const char *fdisk_label_translate_type_shortcut(const struct fdisk_label *lb, char *cut);
+
+#endif /* _LIBFDISK_PRIVATE_H */
diff --git a/libfdisk/src/field.c b/libfdisk/src/field.c
new file mode 100644
index 0000000..e924cf8
--- /dev/null
+++ b/libfdisk/src/field.c
@@ -0,0 +1,88 @@
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: field
+ * @title: Field
+ * @short_description: description of the partition fields
+ *
+ * The fdisk fields are static user-friendly descriptions of the partition. The
+ * fields are used to avoid label specific stuff in the functions that list disk
+ * partitions (e.g. fdisk -l). The field Id is the same as Id for fdisk_partition_to_string().
+ *
+ * <informalexample>
+ * <programlisting>
+ * int *ids;
+ * size_t nids;
+ * struct fdisk_partition *pa = NULL;
+ * struct fdisk_label *lb = fdisk_get_label(cxt, NULL);
+ *
+ * fdisk_label_get_fields_ids(lb, cxt, &ids, &nids);
+ *
+ * fdisk_get_partition(cxt, 0, &pa);
+ *
+ * for (i = 0; i < nids; i++) {
+ * const struct fdisk_field *field = fdisk_label_get_field(lb, ids[i]);
+ *
+ * int id = fdisk_field_get_id(fl);
+ * const char *name = fdisk_field_get_name(fl);
+ * char *data;
+ *
+ * fdisk_partition_to_string(pa, id, &data);
+ * printf("%s: %s\n", name, data);
+ * free(data);
+ * }
+ * free(ids);
+ * </programlisting>
+ * </informalexample>
+ *
+ * This example lists all information about the first partition. It will work
+ * for MBR as well as for GPT because fields are not hardcoded in the example.
+ *
+ * See also fdisk_label_get_field_by_name(), fdisk_label_get_fields_ids_all()
+ * and fdisk_label_get_fields_ids().
+ */
+
+/**
+ * fdisk_field_get_id:
+ * @field: field instance
+ *
+ * Returns: field Id (FDISK_FIELD_*)
+ */
+int fdisk_field_get_id(const struct fdisk_field *field)
+{
+ return field ? field->id : -EINVAL;
+}
+
+/**
+ * fdisk_field_get_name:
+ * @field: field instance
+ *
+ * Returns: field name
+ */
+const char *fdisk_field_get_name(const struct fdisk_field *field)
+{
+ return field ? field->name : NULL;
+}
+
+/**
+ * fdisk_field_get_width:
+ * @field: field instance
+ *
+ * Returns: libsmartcols compatible width.
+ */
+double fdisk_field_get_width(const struct fdisk_field *field)
+{
+ return field ? field->width : -EINVAL;
+}
+
+/**
+ * fdisk_field_is_number:
+ * @field: field instance
+ *
+ * Returns: 1 if field represent number
+ */
+int fdisk_field_is_number(const struct fdisk_field *field)
+{
+ return field->flags ? field->flags & FDISK_FIELDFL_NUMBER : 0;
+}
diff --git a/libfdisk/src/gpt.c b/libfdisk/src/gpt.c
new file mode 100644
index 0000000..72370a1
--- /dev/null
+++ b/libfdisk/src/gpt.c
@@ -0,0 +1,3351 @@
+/*
+ * Copyright (C) 2007 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ *
+ * GUID Partition Table (GPT) support. Based on UEFI Specs 2.3.1
+ * Chapter 5: GUID Partition Table (GPT) Disk Layout (Jun 27th, 2012).
+ * Some ideas and inspiration from GNU parted and gptfdisk.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <uuid.h>
+
+#include "fdiskP.h"
+
+#include "crc32.h"
+#include "blkdev.h"
+#include "bitops.h"
+#include "strutils.h"
+#include "all-io.h"
+#include "pt-mbr.h"
+#include "encode.h"
+
+/**
+ * SECTION: gpt
+ * @title: UEFI GPT
+ * @short_description: specific functionality
+ */
+
+#define GPT_HEADER_SIGNATURE 0x5452415020494645LL /* EFI PART */
+#define GPT_HEADER_REVISION_V1_02 0x00010200
+#define GPT_HEADER_REVISION_V1_00 0x00010000
+#define GPT_HEADER_REVISION_V0_99 0x00009900
+#define GPT_HEADER_MINSZ 92 /* bytes */
+
+#define GPT_PMBR_LBA 0
+#define GPT_MBR_PROTECTIVE 1
+#define GPT_MBR_HYBRID 2
+
+#define GPT_PRIMARY_PARTITION_TABLE_LBA 0x00000001ULL
+
+#define EFI_PMBR_OSTYPE 0xEE
+#define MSDOS_MBR_SIGNATURE 0xAA55
+#define GPT_PART_NAME_LEN (72 / sizeof(uint16_t))
+#define GPT_NPARTITIONS ((size_t) FDISK_GPT_NPARTITIONS_DEFAULT)
+
+/* Globally unique identifier */
+struct gpt_guid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_hi;
+ uint8_t clock_seq_low;
+ uint8_t node[6];
+};
+
+
+/* only checking that the GUID is 0 is enough to verify an empty partition. */
+#define GPT_UNUSED_ENTRY_GUID \
+ ((struct gpt_guid) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
+
+/* Linux native partition type */
+#define GPT_DEFAULT_ENTRY_TYPE "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
+
+/*
+ * Attribute bits
+ */
+enum {
+ /* UEFI specific */
+ GPT_ATTRBIT_REQ = 0,
+ GPT_ATTRBIT_NOBLOCK = 1,
+ GPT_ATTRBIT_LEGACY = 2,
+
+ /* GUID specific (range 48..64)*/
+ GPT_ATTRBIT_GUID_FIRST = 48,
+ GPT_ATTRBIT_GUID_COUNT = 16
+};
+
+#define GPT_ATTRSTR_REQ "RequiredPartition"
+#define GPT_ATTRSTR_REQ_TYPO "RequiredPartiton"
+#define GPT_ATTRSTR_NOBLOCK "NoBlockIOProtocol"
+#define GPT_ATTRSTR_LEGACY "LegacyBIOSBootable"
+
+/* The GPT Partition entry array contains an array of GPT entries. */
+struct gpt_entry {
+ struct gpt_guid type; /* purpose and type of the partition */
+ struct gpt_guid partition_guid;
+ uint64_t lba_start;
+ uint64_t lba_end;
+ uint64_t attrs;
+ uint16_t name[GPT_PART_NAME_LEN];
+} __attribute__ ((packed));
+
+/* GPT header */
+struct gpt_header {
+ uint64_t signature; /* header identification */
+ uint32_t revision; /* header version */
+ uint32_t size; /* in bytes */
+ uint32_t crc32; /* header CRC checksum */
+ uint32_t reserved1; /* must be 0 */
+ uint64_t my_lba; /* LBA of block that contains this struct (LBA 1) */
+ uint64_t alternative_lba; /* backup GPT header */
+ uint64_t first_usable_lba; /* first usable logical block for partitions */
+ uint64_t last_usable_lba; /* last usable logical block for partitions */
+ struct gpt_guid disk_guid; /* unique disk identifier */
+ uint64_t partition_entry_lba; /* LBA of start of partition entries array */
+ uint32_t npartition_entries; /* total partition entries - normally 128 */
+ uint32_t sizeof_partition_entry; /* bytes for each GUID pt */
+ uint32_t partition_entry_array_crc32; /* partition CRC checksum */
+ uint8_t reserved2[512 - 92]; /* must all be 0 */
+} __attribute__ ((packed));
+
+struct gpt_record {
+ uint8_t boot_indicator; /* unused by EFI, set to 0x80 for bootable */
+ uint8_t start_head; /* unused by EFI, pt start in CHS */
+ uint8_t start_sector; /* unused by EFI, pt start in CHS */
+ uint8_t start_track;
+ uint8_t os_type; /* EFI and legacy non-EFI OS types */
+ uint8_t end_head; /* unused by EFI, pt end in CHS */
+ uint8_t end_sector; /* unused by EFI, pt end in CHS */
+ uint8_t end_track; /* unused by EFI, pt end in CHS */
+ uint32_t starting_lba; /* used by EFI - start addr of the on disk pt */
+ uint32_t size_in_lba; /* used by EFI - size of pt in LBA */
+} __attribute__ ((packed));
+
+/* Protected MBR and legacy MBR share same structure */
+struct gpt_legacy_mbr {
+ uint8_t boot_code[440];
+ uint32_t unique_mbr_signature;
+ uint16_t unknown;
+ struct gpt_record partition_record[4];
+ uint16_t signature;
+} __attribute__ ((packed));
+
+/*
+ * Here be dragons!
+ * See: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
+ */
+#define DEF_GUID(_u, _n) \
+ { \
+ .typestr = (_u), \
+ .name = (_n), \
+ }
+
+static struct fdisk_parttype gpt_parttypes[] =
+{
+ #include "pt-gpt-partnames.h"
+};
+
+static const struct fdisk_shortcut gpt_parttype_cuts[] =
+{
+ { .shortcut = "L", .alias = "linux", .data = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" }, /* Linux */
+ { .shortcut = "S", .alias = "swap", .data = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F" }, /* Swap */
+ { .shortcut = "H", .alias = "home", .data = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915" }, /* Home */
+ { .shortcut = "U", .alias = "uefi", .data = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" }, /* UEFI system */
+ { .shortcut = "R", .alias = "raid", .data = "A19D880F-05FC-4D3B-A006-743F0F84911E" }, /* Linux RAID */
+ { .shortcut = "V", .alias = "lvm", .data = "E6D6D379-F507-44C2-A23C-238F2A3DF928" } /* LVM */
+};
+
+#define alignment_required(_x) ((_x)->grain != (_x)->sector_size)
+
+/* gpt_entry macros */
+#define gpt_partition_start(_e) le64_to_cpu((_e)->lba_start)
+#define gpt_partition_end(_e) le64_to_cpu((_e)->lba_end)
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_gpt_label {
+ struct fdisk_label head; /* generic part */
+
+ /* gpt specific part */
+ struct gpt_header *pheader; /* primary header */
+ struct gpt_header *bheader; /* backup header */
+
+ unsigned char *ents; /* entries (partitions) */
+
+ unsigned int no_relocate :1, /* do not fix backup location */
+ minimize :1;
+};
+
+static void gpt_deinit(struct fdisk_label *lb);
+
+static inline struct fdisk_gpt_label *self_label(struct fdisk_context *cxt)
+{
+ return (struct fdisk_gpt_label *) cxt->label;
+}
+
+/*
+ * Returns the partition length, or 0 if end is before beginning.
+ */
+static uint64_t gpt_partition_size(const struct gpt_entry *e)
+{
+ uint64_t start = gpt_partition_start(e);
+ uint64_t end = gpt_partition_end(e);
+
+ return start > end ? 0 : end - start + 1ULL;
+}
+
+/* prints UUID in the real byte order! */
+static void gpt_debug_uuid(const char *mesg, struct gpt_guid *guid)
+{
+ const unsigned char *uuid = (unsigned char *) guid;
+
+ fprintf(stderr, "%s: "
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ mesg,
+ uuid[0], uuid[1], uuid[2], uuid[3],
+ uuid[4], uuid[5],
+ uuid[6], uuid[7],
+ uuid[8], uuid[9],
+ uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]);
+}
+
+/*
+ * UUID is traditionally 16 byte big-endian array, except Intel EFI
+ * specification where the UUID is a structure of little-endian fields.
+ */
+static void swap_efi_guid(struct gpt_guid *uid)
+{
+ uid->time_low = swab32(uid->time_low);
+ uid->time_mid = swab16(uid->time_mid);
+ uid->time_hi_and_version = swab16(uid->time_hi_and_version);
+}
+
+static int string_to_guid(const char *in, struct gpt_guid *guid)
+{
+ if (uuid_parse(in, (unsigned char *) guid)) { /* BE */
+ DBG(GPT, ul_debug("failed to parse GUID: %s", in));
+ return -EINVAL;
+ }
+ swap_efi_guid(guid); /* LE */
+ return 0;
+}
+
+static char *guid_to_string(const struct gpt_guid *guid, char *out)
+{
+ struct gpt_guid u = *guid; /* LE */
+
+ swap_efi_guid(&u); /* BE */
+ uuid_unparse_upper((unsigned char *) &u, out);
+
+ return out;
+}
+
+static struct fdisk_parttype *gpt_partition_parttype(
+ struct fdisk_context *cxt,
+ const struct gpt_entry *e)
+{
+ struct fdisk_parttype *t;
+ char str[UUID_STR_LEN];
+ struct gpt_guid guid = e->type;
+
+ guid_to_string(&guid, str);
+ t = fdisk_label_get_parttype_from_string(cxt->label, str);
+ return t ? : fdisk_new_unknown_parttype(0, str);
+}
+
+static void gpt_entry_set_type(struct gpt_entry *e, struct gpt_guid *uuid)
+{
+ e->type = *uuid;
+ DBG(GPT, gpt_debug_uuid("new type", uuid));
+}
+
+static int gpt_entry_set_name(struct gpt_entry *e, char *str)
+{
+ uint16_t name[GPT_PART_NAME_LEN] = { 0 };
+ size_t i, mblen = 0;
+ uint8_t *in = (uint8_t *) str;
+
+ for (i = 0; *in && i < GPT_PART_NAME_LEN; in++) {
+ if (!mblen) {
+ if (!(*in & 0x80)) {
+ name[i++] = *in;
+ } else if ((*in & 0xE0) == 0xC0) {
+ mblen = 1;
+ name[i] = (uint16_t)(*in & 0x1F) << (mblen *6);
+ } else if ((*in & 0xF0) == 0xE0) {
+ mblen = 2;
+ name[i] = (uint16_t)(*in & 0x0F) << (mblen *6);
+ } else {
+ /* broken UTF-8 or code point greater than U+FFFF */
+ return -EILSEQ;
+ }
+ } else {
+ /* incomplete UTF-8 sequence */
+ if ((*in & 0xC0) != 0x80)
+ return -EILSEQ;
+
+ name[i] |= (uint16_t)(*in & 0x3F) << (--mblen *6);
+ if (!mblen) {
+ /* check for code points reserved for surrogate pairs*/
+ if ((name[i] & 0xF800) == 0xD800)
+ return -EILSEQ;
+ i++;
+ }
+ }
+ }
+
+ for (i = 0; i < GPT_PART_NAME_LEN; i++)
+ e->name[i] = cpu_to_le16(name[i]);
+
+ return (int)((char *) in - str);
+}
+
+static int gpt_entry_set_uuid(struct gpt_entry *e, char *str)
+{
+ struct gpt_guid uuid;
+ int rc;
+
+ rc = string_to_guid(str, &uuid);
+ if (rc)
+ return rc;
+
+ e->partition_guid = uuid;
+ return 0;
+}
+
+static inline int gpt_entry_is_used(const struct gpt_entry *e)
+{
+ return memcmp(&e->type, &GPT_UNUSED_ENTRY_GUID,
+ sizeof(struct gpt_guid)) != 0;
+}
+
+
+static const char *gpt_get_header_revstr(struct gpt_header *header)
+{
+ if (!header)
+ goto unknown;
+
+ switch (le32_to_cpu(header->revision)) {
+ case GPT_HEADER_REVISION_V1_02:
+ return "1.2";
+ case GPT_HEADER_REVISION_V1_00:
+ return "1.0";
+ case GPT_HEADER_REVISION_V0_99:
+ return "0.99";
+ default:
+ goto unknown;
+ }
+
+unknown:
+ return "unknown";
+}
+
+static inline unsigned char *gpt_get_entry_ptr(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return gpt->ents + le32_to_cpu(gpt->pheader->sizeof_partition_entry) * i;
+}
+
+static inline struct gpt_entry *gpt_get_entry(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return (struct gpt_entry *) gpt_get_entry_ptr(gpt, i);
+}
+
+static inline struct gpt_entry *gpt_zeroize_entry(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return (struct gpt_entry *) memset(gpt_get_entry_ptr(gpt, i),
+ 0, le32_to_cpu(gpt->pheader->sizeof_partition_entry));
+}
+
+/* Use to access array of entries, for() loops, etc. But don't use when
+ * you directly do something with GPT header, then use uint32_t.
+ */
+static inline size_t gpt_get_nentries(struct fdisk_gpt_label *gpt)
+{
+ return (size_t) le32_to_cpu(gpt->pheader->npartition_entries);
+}
+
+/* calculate size of entries array in bytes for specified number of entries */
+static inline int gpt_calculate_sizeof_entries(
+ struct gpt_header *hdr,
+ uint32_t nents, size_t *sz)
+{
+ uint32_t esz = hdr ? le32_to_cpu(hdr->sizeof_partition_entry) :
+ sizeof(struct gpt_entry);
+
+ if (nents == 0 || esz == 0 || SIZE_MAX/esz < nents) {
+ DBG(GPT, ul_debug("entries array size check failed"));
+ return -ERANGE;
+ }
+
+ *sz = (size_t) nents * esz;
+ return 0;
+}
+
+/* calculate size of entries array in sectors for specified number of entries */
+static inline int gpt_calculate_sectorsof_entries(
+ struct gpt_header *hdr,
+ uint32_t nents, uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ size_t esz = 0;
+ int rc = gpt_calculate_sizeof_entries(hdr, nents, &esz); /* in bytes */
+
+ if (rc == 0)
+ *sz = (esz + cxt->sector_size - 1) / cxt->sector_size;
+ return rc;
+}
+
+/* calculate alternative (backup) entries array offset from primary header */
+static inline int gpt_calculate_alternative_entries_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc)
+ return rc;
+ if (cxt->total_sectors < 1ULL + esects)
+ return -ENOSPC;
+
+ *sz = cxt->total_sectors - 1ULL - esects;
+ return 0;
+}
+
+static inline int gpt_calculate_last_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc)
+ return rc;
+ if (cxt->total_sectors < 2ULL + esects)
+ return -ENOSPC;
+
+ *sz = cxt->total_sectors - 2ULL - esects;
+ return 0;
+}
+
+static inline int gpt_calculate_first_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc == 0)
+ *sz = esects + 2ULL;
+ return rc;
+}
+
+/* the current size of entries array in bytes */
+static inline int gpt_sizeof_entries(struct gpt_header *hdr, size_t *sz)
+{
+ return gpt_calculate_sizeof_entries(hdr, le32_to_cpu(hdr->npartition_entries), sz);
+}
+
+static char *gpt_get_header_id(struct gpt_header *header)
+{
+ char str[UUID_STR_LEN];
+ struct gpt_guid guid = header->disk_guid;
+
+ guid_to_string(&guid, str);
+
+ return strdup(str);
+}
+
+/*
+ * Builds a clean new valid protective MBR - will wipe out any existing data.
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_pmbr(struct fdisk_context *cxt)
+{
+ struct gpt_legacy_mbr *pmbr = NULL;
+ int rc;
+
+ if (!cxt || !cxt->firstsector)
+ return -ENOSYS;
+
+ if (fdisk_has_protected_bootbits(cxt))
+ rc = fdisk_init_firstsector_buffer(cxt, 0, MBR_PT_BOOTBITS_SIZE);
+ else
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+ memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record));
+
+ pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+ pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE;
+ pmbr->partition_record[0].start_sector = 2;
+ pmbr->partition_record[0].end_head = 0xFF;
+ pmbr->partition_record[0].end_sector = 0xFF;
+ pmbr->partition_record[0].end_track = 0xFF;
+ pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+ pmbr->partition_record[0].size_in_lba =
+ cpu_to_le32((uint32_t) min( cxt->total_sectors - 1ULL, 0xFFFFFFFFULL) );
+
+ return 0;
+}
+
+
+/* Move backup header to the end of the device */
+static int gpt_fix_alternative_lba(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ struct gpt_header *p, *b;
+ uint64_t x = 0, orig;
+ size_t nents;
+ int rc;
+
+ if (!cxt)
+ return -EINVAL;
+
+ p = gpt->pheader; /* primary */
+ b = gpt->bheader; /* backup */
+
+ nents = le32_to_cpu(p->npartition_entries);
+ orig = le64_to_cpu(p->alternative_lba);
+
+ /* reference from primary to backup */
+ p->alternative_lba = cpu_to_le64(cxt->total_sectors - 1ULL);
+
+ /* reference from backup to primary */
+ b->alternative_lba = p->my_lba;
+ b->my_lba = p->alternative_lba;
+
+ /* fix backup partitions array address */
+ rc = gpt_calculate_alternative_entries_lba(p, nents, &x, cxt);
+ if (rc)
+ goto failed;
+
+ b->partition_entry_lba = cpu_to_le64(x);
+
+ /* update last usable LBA */
+ rc = gpt_calculate_last_lba(p, nents, &x, cxt);
+ if (rc)
+ goto failed;
+
+ p->last_usable_lba = cpu_to_le64(x);
+ b->last_usable_lba = cpu_to_le64(x);
+
+ DBG(GPT, ul_debug("Alternative-LBA updated from %"PRIu64" to %"PRIu64,
+ orig, le64_to_cpu(p->alternative_lba)));
+ return 0;
+failed:
+ DBG(GPT, ul_debug("failed to fix alternative-LBA [rc=%d]", rc));
+ return rc;
+}
+
+static uint64_t gpt_calculate_minimal_size(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ size_t i;
+ uint64_t x = 0, total = 0;
+ struct gpt_header *hdr;
+
+ assert(cxt);
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ hdr = gpt->pheader;
+
+ /* LBA behind the last partition */
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (gpt_entry_is_used(e)) {
+ uint64_t end = gpt_partition_end(e);
+ if (end > x)
+ x = end;
+ }
+ }
+ total = x + 1;
+
+ /* the current last LBA usable for partitions */
+ gpt_calculate_last_lba(hdr, le32_to_cpu(hdr->npartition_entries), &x, cxt);
+
+ /* size of all stuff at the end of the device */
+ total += cxt->total_sectors - x;
+
+ DBG(GPT, ul_debug("minimal device is %"PRIu64, total));
+ return total;
+}
+
+static int gpt_possible_minimize(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ struct gpt_header *hdr = gpt->pheader;
+ uint64_t total = gpt_calculate_minimal_size(cxt, gpt);
+
+ return le64_to_cpu(hdr->alternative_lba) > (total - 1ULL);
+}
+
+/* move backup header behind the last partition */
+static int gpt_minimize_alternative_lba(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ uint64_t total = gpt_calculate_minimal_size(cxt, gpt);
+ uint64_t orig = cxt->total_sectors;
+ int rc;
+
+ /* Let's temporary change size of the device to recalculate backup header */
+ cxt->total_sectors = total;
+ rc = gpt_fix_alternative_lba(cxt, gpt);
+ if (rc)
+ return rc;
+
+ cxt->total_sectors = orig;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+/* some universal differences between the headers */
+static void gpt_mknew_header_common(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ if (!cxt || !header)
+ return;
+
+ header->my_lba = cpu_to_le64(lba);
+
+ if (lba == GPT_PRIMARY_PARTITION_TABLE_LBA) {
+ /* primary */
+ header->alternative_lba = cpu_to_le64(cxt->total_sectors - 1ULL);
+ header->partition_entry_lba = cpu_to_le64(2ULL);
+
+ } else {
+ /* backup */
+ uint64_t x = 0;
+ gpt_calculate_alternative_entries_lba(header,
+ le32_to_cpu(header->npartition_entries), &x, cxt);
+
+ header->alternative_lba = cpu_to_le64(GPT_PRIMARY_PARTITION_TABLE_LBA);
+ header->partition_entry_lba = cpu_to_le64(x);
+ }
+}
+
+/*
+ * Builds a new GPT header (at sector lba) from a backup header2.
+ * If building a primary header, then backup is the secondary, and vice versa.
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header_from_bkp(struct fdisk_context *cxt,
+ struct gpt_header *header,
+ uint64_t lba,
+ struct gpt_header *header2)
+{
+ if (!cxt || !header || !header2)
+ return -ENOSYS;
+
+ header->signature = header2->signature;
+ header->revision = header2->revision;
+ header->size = header2->size;
+ header->npartition_entries = header2->npartition_entries;
+ header->sizeof_partition_entry = header2->sizeof_partition_entry;
+ header->first_usable_lba = header2->first_usable_lba;
+ header->last_usable_lba = header2->last_usable_lba;
+
+ memcpy(&header->disk_guid,
+ &header2->disk_guid, sizeof(header2->disk_guid));
+ gpt_mknew_header_common(cxt, header, lba);
+
+ return 0;
+}
+
+static struct gpt_header *gpt_copy_header(struct fdisk_context *cxt,
+ struct gpt_header *src)
+{
+ struct gpt_header *res;
+
+ if (!cxt || !src)
+ return NULL;
+
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ res = calloc(1, cxt->sector_size);
+ if (!res) {
+ fdisk_warn(cxt, _("failed to allocate GPT header"));
+ return NULL;
+ }
+
+ res->my_lba = src->alternative_lba;
+ res->alternative_lba = src->my_lba;
+
+ res->signature = src->signature;
+ res->revision = src->revision;
+ res->size = src->size;
+ res->npartition_entries = src->npartition_entries;
+ res->sizeof_partition_entry = src->sizeof_partition_entry;
+ res->first_usable_lba = src->first_usable_lba;
+ res->last_usable_lba = src->last_usable_lba;
+
+ memcpy(&res->disk_guid, &src->disk_guid, sizeof(src->disk_guid));
+
+
+ if (res->my_lba == GPT_PRIMARY_PARTITION_TABLE_LBA)
+ res->partition_entry_lba = cpu_to_le64(2ULL);
+ else {
+ uint64_t esz = (uint64_t) le32_to_cpu(src->npartition_entries) * sizeof(struct gpt_entry);
+ uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size;
+
+ res->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1ULL - esects);
+ }
+
+ return res;
+}
+
+static int get_script_u64(struct fdisk_context *cxt, uint64_t *num, const char *name)
+{
+ const char *str;
+ int pwr = 0, rc = 0;
+
+ assert(cxt);
+
+ *num = 0;
+
+ if (!cxt->script)
+ return 1;
+
+ str = fdisk_script_get_header(cxt->script, name);
+ if (!str)
+ return 1;
+
+ rc = parse_size(str, (uintmax_t *) num, &pwr);
+ if (rc < 0)
+ return rc;
+ if (pwr)
+ *num /= cxt->sector_size;
+ return 0;
+}
+
+static int count_first_last_lba(struct fdisk_context *cxt,
+ uint64_t *first, uint64_t *last,
+ uint32_t *maxents)
+{
+ int rc = 0;
+ uint64_t flba = 0, llba = 0;
+ uint64_t nents = GPT_NPARTITIONS;
+
+ assert(cxt);
+ assert(first);
+ assert(last);
+
+ *first = *last = 0;
+
+ /* Get the table length from the script, if given */
+ if (cxt->script) {
+ rc = get_script_u64(cxt, &nents, "table-length");
+ if (rc == 1)
+ nents = GPT_NPARTITIONS; /* undefined by script */
+ else if (rc < 0)
+ return rc;
+ }
+
+ /* The table length was not changed by the script, compute it. */
+ if (flba == 0) {
+ /* If the device is not large enough reduce this number of
+ * partitions and try to recalculate it again, until we get
+ * something useful or return error.
+ */
+ for (; nents > 0; nents--) {
+ rc = gpt_calculate_last_lba(NULL, nents, &llba, cxt);
+ if (rc == 0)
+ rc = gpt_calculate_first_lba(NULL, nents, &flba, cxt);
+ if (llba < flba)
+ rc = -ENOSPC;
+ else if (rc == 0)
+ break;
+ }
+ }
+
+ if (rc)
+ return rc;
+ if (maxents)
+ *maxents = nents;
+
+ /* script default */
+ if (cxt->script) {
+ rc = get_script_u64(cxt, first, "first-lba");
+ if (rc < 0)
+ return rc;
+
+ DBG(GPT, ul_debug("FirstLBA: script=%"PRIu64", uefi=%"PRIu64", topology=%ju.",
+ *first, flba, (uintmax_t)cxt->first_lba));
+
+ if (rc == 0 && (*first < flba || *first > llba)) {
+ fdisk_warnx(cxt, _("First LBA specified by script is out of range."));
+ return -ERANGE;
+ }
+
+ rc = get_script_u64(cxt, last, "last-lba");
+ if (rc < 0)
+ return rc;
+
+ DBG(GPT, ul_debug("LastLBA: script=%"PRIu64", uefi=%"PRIu64", topology=%ju.",
+ *last, llba, (uintmax_t)cxt->last_lba));
+
+ if (rc == 0 && (*last > llba || *last < flba)) {
+ fdisk_warnx(cxt, _("Last LBA specified by script is out of range."));
+ return -ERANGE;
+ }
+ }
+
+ if (!*last)
+ *last = llba;
+
+ /* default by topology */
+ if (!*first)
+ *first = flba < cxt->first_lba &&
+ cxt->first_lba < *last ? cxt->first_lba : flba;
+ return 0;
+}
+
+/*
+ * Builds a clean new GPT header (currently under revision 1.0).
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ uint64_t first, last;
+ uint32_t nents = 0;
+ int has_id = 0, rc;
+
+ if (!cxt || !header)
+ return -ENOSYS;
+
+ header->signature = cpu_to_le64(GPT_HEADER_SIGNATURE);
+ header->revision = cpu_to_le32(GPT_HEADER_REVISION_V1_00);
+
+ /* According to EFI standard it's valid to count all the first
+ * sector into header size, but some tools may have a problem
+ * to accept it, so use the header without the zeroed area.
+ * This does not have any impact to CRC, etc. --kzak Jan-2015
+ */
+ header->size = cpu_to_le32(sizeof(struct gpt_header)
+ - sizeof(header->reserved2));
+
+ /* Set {First,Last}LBA and number of the partitions
+ * (default is GPT_NPARTITIONS) */
+ rc = count_first_last_lba(cxt, &first, &last, &nents);
+ if (rc)
+ return rc;
+
+ header->npartition_entries = cpu_to_le32(nents);
+ header->sizeof_partition_entry = cpu_to_le32(sizeof(struct gpt_entry));
+
+ header->first_usable_lba = cpu_to_le64(first);
+ header->last_usable_lba = cpu_to_le64(last);
+
+ gpt_mknew_header_common(cxt, header, lba);
+
+ if (cxt->script) {
+ const char *id = fdisk_script_get_header(cxt->script, "label-id");
+ struct gpt_guid guid = header->disk_guid;
+ if (id && string_to_guid(id, &guid) == 0)
+ has_id = 1;
+ header->disk_guid = guid;
+ }
+
+ if (!has_id) {
+ struct gpt_guid guid;
+
+ uuid_generate_random((unsigned char *) &header->disk_guid);
+ guid = header->disk_guid;
+ swap_efi_guid(&guid);
+ }
+ return 0;
+}
+
+/*
+ * Checks if there is a valid protective MBR partition table.
+ * Returns 0 if it is invalid or failure. Otherwise, return
+ * GPT_MBR_PROTECTIVE or GPT_MBR_HYBRID, depending on the detection.
+ */
+static int valid_pmbr(struct fdisk_context *cxt)
+{
+ int i, part = 0, ret = 0; /* invalid by default */
+ struct gpt_legacy_mbr *pmbr = NULL;
+
+ if (!cxt->firstsector)
+ goto done;
+
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+ if (le16_to_cpu(pmbr->signature) != MSDOS_MBR_SIGNATURE)
+ goto done;
+
+ /* seems like a valid MBR was found, check DOS primary partitions */
+ for (i = 0; i < 4; i++) {
+ if (pmbr->partition_record[i].os_type == EFI_PMBR_OSTYPE) {
+ /*
+ * Ok, we at least know that there's a protective MBR,
+ * now check if there are other partition types for
+ * hybrid MBR.
+ */
+ part = i;
+ ret = GPT_MBR_PROTECTIVE;
+ break;
+ }
+ }
+
+ if (ret != GPT_MBR_PROTECTIVE)
+ goto done;
+
+
+ for (i = 0 ; i < 4; i++) {
+ if ((pmbr->partition_record[i].os_type != EFI_PMBR_OSTYPE) &&
+ (pmbr->partition_record[i].os_type != 0x00)) {
+ ret = GPT_MBR_HYBRID;
+ goto done;
+ }
+ }
+
+ /* LBA of the GPT partition header */
+ if (pmbr->partition_record[part].starting_lba !=
+ cpu_to_le32(GPT_PRIMARY_PARTITION_TABLE_LBA))
+ goto done;
+
+ /*
+ * Protective MBRs take up the lesser of the whole disk
+ * or 2 TiB (32bit LBA), ignoring the rest of the disk.
+ * Some partitioning programs, nonetheless, choose to set
+ * the size to the maximum 32-bit limitation, disregarding
+ * the disk size.
+ *
+ * Hybrid MBRs do not necessarily comply with this.
+ *
+ * Consider a bad value here to be a warning to support dd-ing
+ * an image from a smaller disk to a bigger disk.
+ */
+ if (ret == GPT_MBR_PROTECTIVE) {
+ uint64_t sz_lba = (uint64_t) le32_to_cpu(pmbr->partition_record[part].size_in_lba);
+ if (sz_lba != cxt->total_sectors - 1ULL && sz_lba != 0xFFFFFFFFULL) {
+
+ fdisk_warnx(cxt, _("GPT PMBR size mismatch (%"PRIu64" != %"PRIu64") "
+ "will be corrected by write."),
+ sz_lba, cxt->total_sectors - (uint64_t) 1);
+
+ /* Note that gpt_write_pmbr() overwrites PMBR, but we want to keep it valid already
+ * in memory too to disable warnings when valid_pmbr() called next time */
+ pmbr->partition_record[part].size_in_lba =
+ cpu_to_le32((uint32_t) min( cxt->total_sectors - 1ULL, 0xFFFFFFFFULL) );
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+ }
+done:
+ DBG(GPT, ul_debug("PMBR type: %s",
+ ret == GPT_MBR_PROTECTIVE ? "protective" :
+ ret == GPT_MBR_HYBRID ? "hybrid" : "???" ));
+ return ret;
+}
+
+static uint64_t last_lba(struct fdisk_context *cxt)
+{
+ struct stat s;
+ uint64_t sectors = 0;
+
+ memset(&s, 0, sizeof(s));
+ if (fstat(cxt->dev_fd, &s) == -1) {
+ fdisk_warn(cxt, _("gpt: stat() failed"));
+ return 0;
+ }
+
+ if (S_ISBLK(s.st_mode))
+ sectors = cxt->total_sectors - 1ULL;
+ else if (S_ISREG(s.st_mode))
+ sectors = ((uint64_t) s.st_size /
+ (uint64_t) cxt->sector_size) - 1ULL;
+ else
+ fdisk_warnx(cxt, _("gpt: cannot handle files with mode %o"), s.st_mode);
+
+ DBG(GPT, ul_debug("last LBA: %"PRIu64"", sectors));
+ return sectors;
+}
+
+static ssize_t read_lba(struct fdisk_context *cxt, uint64_t lba,
+ void *buffer, const size_t bytes)
+{
+ off_t offset = lba * cxt->sector_size;
+
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1)
+ return -1;
+ return (size_t)read(cxt->dev_fd, buffer, bytes) != bytes;
+}
+
+
+/* Returns the GPT entry array */
+static unsigned char *gpt_read_entries(struct fdisk_context *cxt,
+ struct gpt_header *header)
+{
+ size_t sz = 0;
+ ssize_t ssz;
+
+ unsigned char *ret = NULL;
+ off_t offset;
+
+ assert(cxt);
+ assert(header);
+
+ if (gpt_sizeof_entries(header, &sz))
+ return NULL;
+
+ if (sz > (size_t) SSIZE_MAX) {
+ DBG(GPT, ul_debug("entries array too large to read()"));
+ return NULL;
+ }
+
+ ret = calloc(1, sz);
+ if (!ret)
+ return NULL;
+
+ offset = (off_t) le64_to_cpu(header->partition_entry_lba) *
+ cxt->sector_size;
+
+ if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+ goto fail;
+
+ ssz = read(cxt->dev_fd, ret, sz);
+ if (ssz < 0 || (size_t) ssz != sz)
+ goto fail;
+
+ return ret;
+
+fail:
+ free(ret);
+ return NULL;
+}
+
+static inline uint32_t count_crc32(const unsigned char *buf, size_t len,
+ size_t ex_off, size_t ex_len)
+{
+ return (ul_crc32_exclude_offset(~0L, buf, len, ex_off, ex_len) ^ ~0L);
+}
+
+static inline uint32_t gpt_header_count_crc32(struct gpt_header *header)
+{
+ return count_crc32((unsigned char *) header, /* buffer */
+ le32_to_cpu(header->size), /* size of buffer */
+ offsetof(struct gpt_header, crc32), /* exclude */
+ sizeof(header->crc32)); /* size of excluded area */
+}
+
+static inline uint32_t gpt_entryarr_count_crc32(struct gpt_header *header, unsigned char *ents)
+{
+ size_t arysz = 0;
+
+ if (gpt_sizeof_entries(header, &arysz))
+ return 0;
+
+ return count_crc32(ents, arysz, 0, 0);
+}
+
+
+/*
+ * Recompute header and partition array 32bit CRC checksums.
+ * This function does not fail - if there's corruption, then it
+ * will be reported when checksumming it again (ie: probing or verify).
+ */
+static void gpt_recompute_crc(struct gpt_header *header, unsigned char *ents)
+{
+ if (!header)
+ return;
+
+ header->partition_entry_array_crc32 =
+ cpu_to_le32( gpt_entryarr_count_crc32(header, ents) );
+
+ header->crc32 = cpu_to_le32( gpt_header_count_crc32(header) );
+}
+
+/*
+ * Compute the 32bit CRC checksum of the partition table header.
+ * Returns 1 if it is valid, otherwise 0.
+ */
+static int gpt_check_header_crc(struct gpt_header *header, unsigned char *ents)
+{
+ uint32_t orgcrc = le32_to_cpu(header->crc32),
+ crc = gpt_header_count_crc32(header);
+
+ if (crc == orgcrc)
+ return 1;
+
+ /*
+ * If we have checksum mismatch it may be due to stale data, like a
+ * partition being added or deleted. Recompute the CRC again and make
+ * sure this is not the case.
+ */
+ if (ents) {
+ gpt_recompute_crc(header, ents);
+ return gpt_header_count_crc32(header) == orgcrc;
+ }
+
+ return 0;
+}
+
+/*
+ * It initializes the partition entry array.
+ * Returns 1 if the checksum is valid, otherwise 0.
+ */
+static int gpt_check_entryarr_crc(struct gpt_header *header, unsigned char *ents)
+{
+ if (!header || !ents)
+ return 0;
+
+ return gpt_entryarr_count_crc32(header, ents) ==
+ le32_to_cpu(header->partition_entry_array_crc32);
+}
+
+static int gpt_check_lba_sanity(struct fdisk_context *cxt, struct gpt_header *header)
+{
+ int ret = 0;
+ uint64_t lu, fu, lastlba = last_lba(cxt);
+
+ fu = le64_to_cpu(header->first_usable_lba);
+ lu = le64_to_cpu(header->last_usable_lba);
+
+ /* check if first and last usable LBA make sense */
+ if (lu < fu) {
+ DBG(GPT, ul_debug("error: header last LBA is before first LBA"));
+ goto done;
+ }
+
+ /* check if first and last usable LBAs with the disk's last LBA */
+ if (fu > lastlba || lu > lastlba) {
+ DBG(GPT, ul_debug("error: header LBAs are after the disk's last LBA (%ju..%ju)",
+ (uintmax_t) fu, (uintmax_t) lu));
+ goto done;
+ }
+
+ /* the header has to be outside usable range */
+ if (fu < GPT_PRIMARY_PARTITION_TABLE_LBA &&
+ GPT_PRIMARY_PARTITION_TABLE_LBA < lu) {
+ DBG(GPT, ul_debug("error: header outside of usable range"));
+ goto done;
+ }
+
+ ret = 1; /* sane */
+done:
+ return ret;
+}
+
+/* Check if there is a valid header signature */
+static int gpt_check_signature(struct gpt_header *header)
+{
+ return header->signature == cpu_to_le64(GPT_HEADER_SIGNATURE);
+}
+
+/*
+ * Return the specified GPT Header, or NULL upon failure/invalid.
+ * Note that all tests must pass to ensure a valid header,
+ * we do not rely on only testing the signature for a valid probe.
+ */
+static struct gpt_header *gpt_read_header(struct fdisk_context *cxt,
+ uint64_t lba,
+ unsigned char **_ents)
+{
+ struct gpt_header *header = NULL;
+ unsigned char *ents = NULL;
+ uint32_t hsz;
+
+ if (!cxt)
+ return NULL;
+
+ /* always allocate all sector, the area after GPT header
+ * has to be fill by zeros */
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ header = calloc(1, cxt->sector_size);
+ if (!header)
+ return NULL;
+
+ /* read and verify header */
+ if (read_lba(cxt, lba, header, cxt->sector_size) != 0)
+ goto invalid;
+
+ if (!gpt_check_signature(header))
+ goto invalid;
+
+ /* make sure header size is between 92 and sector size bytes */
+ hsz = le32_to_cpu(header->size);
+ if (hsz < GPT_HEADER_MINSZ || hsz > cxt->sector_size)
+ goto invalid;
+
+ if (!gpt_check_header_crc(header, NULL))
+ goto invalid;
+
+ /* read and verify entries */
+ ents = gpt_read_entries(cxt, header);
+ if (!ents)
+ goto invalid;
+
+ if (!gpt_check_entryarr_crc(header, ents))
+ goto invalid;
+
+ if (!gpt_check_lba_sanity(cxt, header))
+ goto invalid;
+
+ /* valid header must be at MyLBA */
+ if (le64_to_cpu(header->my_lba) != lba)
+ goto invalid;
+
+ if (_ents)
+ *_ents = ents;
+ else
+ free(ents);
+
+ DBG(GPT, ul_debug("found valid header on LBA %"PRIu64"", lba));
+ return header;
+invalid:
+ free(header);
+ free(ents);
+
+ DBG(GPT, ul_debug("read header on LBA %"PRIu64" failed", lba));
+ return NULL;
+}
+
+
+static int gpt_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name, uint64_t *offset, size_t *size)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+
+ *name = NULL;
+ *offset = 0;
+ *size = 0;
+
+ switch (n) {
+ case 0:
+ *name = "PMBR";
+ *offset = 0;
+ *size = 512;
+ break;
+ case 1:
+ *name = _("GPT Header");
+ *offset = (uint64_t) GPT_PRIMARY_PARTITION_TABLE_LBA * cxt->sector_size;
+ *size = sizeof(struct gpt_header);
+ break;
+ case 2:
+ *name = _("GPT Entries");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->pheader->partition_entry_lba) *
+ cxt->sector_size;
+ return gpt_sizeof_entries(gpt->pheader, size);
+ case 3:
+ *name = _("GPT Backup Entries");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->bheader->partition_entry_lba) *
+ cxt->sector_size;
+ return gpt_sizeof_entries(gpt->bheader, size);
+ case 4:
+ *name = _("GPT Backup Header");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->pheader->alternative_lba) * cxt->sector_size;
+ *size = sizeof(struct gpt_header);
+ break;
+ default:
+ return 1; /* no more chunks */
+ }
+
+ return 0;
+}
+
+static int gpt_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct gpt_header *h;
+ int rc = 0;
+ uint64_t x = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ h = self_label(cxt)->pheader;
+
+ switch (item->id) {
+ case GPT_LABELITEM_ID:
+ item->name = _("Disk identifier");
+ item->type = 's';
+ item->data.str = gpt_get_header_id(h);
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case GPT_LABELITEM_FIRSTLBA:
+ item->name = _("First usable LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->first_usable_lba);
+ break;
+ case GPT_LABELITEM_LASTLBA:
+ item->name = _("Last usable LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->last_usable_lba);
+ break;
+ case GPT_LABELITEM_ALTLBA:
+ /* TRANSLATORS: The LBA (Logical Block Address) of the backup GPT header. */
+ item->name = _("Alternative LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->alternative_lba);
+ break;
+ case GPT_LABELITEM_ENTRIESLBA:
+ /* TRANSLATORS: The start of the array of partition entries. */
+ item->name = _("Partition entries starting LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->partition_entry_lba);
+ break;
+ case GPT_LABELITEM_ENTRIESLASTLBA:
+ /* TRANSLATORS: The end of the array of partition entries. */
+ item->name = _("Partition entries ending LBA");
+ item->type = 'j';
+ gpt_calculate_sectorsof_entries(h,
+ le32_to_cpu(h->npartition_entries), &x, cxt);
+ item->data.num64 = le64_to_cpu(h->partition_entry_lba) + x - 1;
+ break;
+ case GPT_LABELITEM_ENTRIESALLOC:
+ item->name = _("Allocated partition entries");
+ item->type = 'j';
+ item->data.num64 = le32_to_cpu(h->npartition_entries);
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Returns the number of partitions that are in use.
+ */
+static size_t partitions_in_use(struct fdisk_gpt_label *gpt)
+{
+ size_t i, used = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (gpt_entry_is_used(e))
+ used++;
+ }
+ return used;
+}
+
+
+/*
+ * Check if a partition is too big for the disk (sectors).
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_too_big_partitions(struct fdisk_gpt_label *gpt, uint64_t sectors)
+{
+ size_t i;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_end(e) >= sectors)
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if a partition ends before it begins
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_start_after_end_partitions(struct fdisk_gpt_label *gpt)
+{
+ size_t i;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_start(e) > gpt_partition_end(e))
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if partition e1 overlaps with partition e2.
+ */
+static inline int partition_overlap(struct gpt_entry *e1, struct gpt_entry *e2)
+{
+ uint64_t start1 = gpt_partition_start(e1);
+ uint64_t end1 = gpt_partition_end(e1);
+ uint64_t start2 = gpt_partition_start(e2);
+ uint64_t end2 = gpt_partition_end(e2);
+
+ return (start1 && start2 && (start1 <= end2) != (end1 < start2));
+}
+
+/*
+ * Find any partitions that overlap.
+ */
+static uint32_t check_overlap_partitions(struct fdisk_gpt_label *gpt)
+{
+ size_t i, j;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++)
+ for (j = 0; j < i; j++) {
+ struct gpt_entry *ei = gpt_get_entry(gpt, i);
+ struct gpt_entry *ej = gpt_get_entry(gpt, j);
+
+ if (!gpt_entry_is_used(ei) || !gpt_entry_is_used(ej))
+ continue;
+ if (partition_overlap(ei, ej)) {
+ DBG(GPT, ul_debug("partitions overlap detected [%zu vs. %zu]", i, j));
+ return i + 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Find the first available block after the starting point; returns 0 if
+ * there are no available blocks left, or error. From gdisk.
+ */
+static uint64_t find_first_available(struct fdisk_gpt_label *gpt, uint64_t start)
+{
+ int first_moved = 0;
+ uint64_t first;
+ uint64_t fu, lu;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ fu = le64_to_cpu(gpt->pheader->first_usable_lba);
+ lu = le64_to_cpu(gpt->pheader->last_usable_lba);
+
+ /*
+ * Begin from the specified starting point or from the first usable
+ * LBA, whichever is greater...
+ */
+ first = start < fu ? fu : start;
+
+ /*
+ * Now search through all partitions; if first is within an
+ * existing partition, move it to the next sector after that
+ * partition and repeat. If first was moved, set firstMoved
+ * flag; repeat until firstMoved is not set, so as to catch
+ * cases where partitions are out of sequential order....
+ */
+ do {
+ size_t i;
+
+ first_moved = 0;
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (first < gpt_partition_start(e))
+ continue;
+ if (first <= gpt_partition_end(e)) {
+ first = gpt_partition_end(e) + 1;
+ first_moved = 1;
+ }
+ }
+ } while (first_moved == 1);
+
+ if (first > lu)
+ first = 0;
+
+ return first;
+}
+
+
+/* Returns last available sector in the free space pointed to by start. From gdisk. */
+static uint64_t find_last_free(struct fdisk_gpt_label *gpt, uint64_t start)
+{
+ size_t i;
+ uint64_t nearest_start;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ nearest_start = le64_to_cpu(gpt->pheader->last_usable_lba);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+ uint64_t ps = gpt_partition_start(e);
+
+ if (nearest_start > ps && ps > start)
+ nearest_start = ps - 1ULL;
+ }
+
+ return nearest_start;
+}
+
+/* Returns the last free sector on the disk. From gdisk. */
+static uint64_t find_last_free_sector(struct fdisk_gpt_label *gpt)
+{
+ int last_moved;
+ uint64_t last = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ /* start by assuming the last usable LBA is available */
+ last = le64_to_cpu(gpt->pheader->last_usable_lba);
+ do {
+ size_t i;
+
+ last_moved = 0;
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (last >= gpt_partition_start(e) &&
+ last <= gpt_partition_end(e)) {
+ last = gpt_partition_start(e) - 1ULL;
+ last_moved = 1;
+ }
+ }
+ } while (last_moved == 1);
+
+ return last;
+}
+
+/*
+ * Finds the first available sector in the largest block of unallocated
+ * space on the disk. Returns 0 if there are no available blocks left.
+ * From gdisk.
+ */
+static uint64_t find_first_in_largest(struct fdisk_gpt_label *gpt)
+{
+ uint64_t start = 0, first_sect, last_sect;
+ uint64_t segment_size, selected_size = 0, selected_segment = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ do {
+ first_sect = find_first_available(gpt, start);
+ if (first_sect != 0) {
+ last_sect = find_last_free(gpt, first_sect);
+ segment_size = last_sect - first_sect + 1ULL;
+
+ if (segment_size > selected_size) {
+ selected_size = segment_size;
+ selected_segment = first_sect;
+ }
+ start = last_sect + 1ULL;
+ }
+ } while (first_sect != 0);
+
+ return selected_segment;
+}
+
+/*
+ * Find the total number of free sectors, the number of segments in which
+ * they reside, and the size of the largest of those segments. From gdisk.
+ */
+static uint64_t get_free_sectors(struct fdisk_context *cxt,
+ struct fdisk_gpt_label *gpt,
+ uint32_t *nsegments,
+ uint64_t *largest_segment)
+{
+ uint32_t num = 0;
+ uint64_t first_sect, last_sect;
+ uint64_t largest_seg = 0, segment_sz;
+ uint64_t totfound = 0, start = 0; /* starting point for each search */
+
+ if (!cxt->total_sectors)
+ goto done;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ do {
+ first_sect = find_first_available(gpt, start);
+ if (first_sect) {
+ last_sect = find_last_free(gpt, first_sect);
+ segment_sz = last_sect - first_sect + 1;
+
+ if (segment_sz > largest_seg)
+ largest_seg = segment_sz;
+ totfound += segment_sz;
+ num++;
+ start = last_sect + 1ULL;
+ }
+ } while (first_sect);
+
+done:
+ if (nsegments)
+ *nsegments = num;
+ if (largest_segment)
+ *largest_segment = largest_seg;
+
+ return totfound;
+}
+
+static int gpt_probe_label(struct fdisk_context *cxt)
+{
+ int mbr_type;
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ /* TODO: it would be nice to support scenario when GPT headers are OK,
+ * but PMBR is corrupt */
+ mbr_type = valid_pmbr(cxt);
+ if (!mbr_type)
+ goto failed;
+
+ /* primary header */
+ gpt->pheader = gpt_read_header(cxt, GPT_PRIMARY_PARTITION_TABLE_LBA,
+ &gpt->ents);
+
+ if (gpt->pheader)
+ /* primary OK, try backup from alternative LBA */
+ gpt->bheader = gpt_read_header(cxt,
+ le64_to_cpu(gpt->pheader->alternative_lba),
+ NULL);
+ else
+ /* primary corrupted -- try last LBA */
+ gpt->bheader = gpt_read_header(cxt, last_lba(cxt), &gpt->ents);
+
+ if (!gpt->pheader && !gpt->bheader)
+ goto failed;
+
+ /* primary OK, backup corrupted -- recovery */
+ if (gpt->pheader && !gpt->bheader) {
+ fdisk_warnx(cxt, _("The backup GPT table is corrupt, but the "
+ "primary appears OK, so that will be used."));
+ gpt->bheader = gpt_copy_header(cxt, gpt->pheader);
+ if (!gpt->bheader)
+ goto failed;
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ /* primary corrupted, backup OK -- recovery */
+ } else if (!gpt->pheader && gpt->bheader) {
+ fdisk_warnx(cxt, _("The primary GPT table is corrupt, but the "
+ "backup appears OK, so that will be used."));
+ gpt->pheader = gpt_copy_header(cxt, gpt->bheader);
+ if (!gpt->pheader)
+ goto failed;
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+
+ /* The headers make be correct, but Backup do not have to be on the end
+ * of the device (due to device resize, etc.). Let's fix this issue. */
+ if (gpt->minimize == 0 &&
+ (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors ||
+ le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1ULL)) {
+
+ if (gpt->no_relocate || fdisk_is_readonly(cxt))
+ fdisk_warnx(cxt, _("The backup GPT table is not on the end of the device."));
+
+ else {
+ fdisk_warnx(cxt, _("The backup GPT table is not on the end of the device. "
+ "This problem will be corrected by write."));
+
+ if (gpt_fix_alternative_lba(cxt, gpt) != 0)
+ fdisk_warnx(cxt, _("Failed to recalculate backup GPT table location"));
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+ }
+
+ if (gpt->minimize && gpt_possible_minimize(cxt, gpt))
+ fdisk_label_set_changed(cxt->label, 1);
+
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+ cxt->label->nparts_cur = partitions_in_use(gpt);
+ return 1;
+failed:
+ DBG(GPT, ul_debug("probe failed"));
+ gpt_deinit(cxt->label);
+ return 0;
+}
+
+static char *encode_to_utf8(unsigned char *src, size_t count)
+{
+ unsigned char *dest;
+ size_t len = (count * 3 / 2) + 1;
+
+ dest = calloc(1, len);
+ if (!dest)
+ return NULL;
+
+ ul_encode_to_utf8(UL_ENCODE_UTF16LE, dest, len, src, count);
+ return (char *) dest;
+}
+
+static int gpt_entry_attrs_to_string(struct gpt_entry *e, char **res)
+{
+ unsigned int n, count = 0;
+ size_t l;
+ char *bits, *p;
+ uint64_t attrs;
+
+ assert(e);
+ assert(res);
+
+ *res = NULL;
+ attrs = e->attrs;
+ if (!attrs)
+ return 0; /* no attributes at all */
+
+ bits = (char *) &attrs;
+
+ /* Note that sizeof() is correct here, we need separators between
+ * the strings so also count \0 is correct */
+ *res = calloc(1, sizeof(GPT_ATTRSTR_NOBLOCK) +
+ sizeof(GPT_ATTRSTR_REQ) +
+ sizeof(GPT_ATTRSTR_LEGACY) +
+ sizeof("GUID:") + (GPT_ATTRBIT_GUID_COUNT * 3));
+ if (!*res)
+ return -errno;
+
+ p = *res;
+ if (isset(bits, GPT_ATTRBIT_REQ)) {
+ memcpy(p, GPT_ATTRSTR_REQ, (l = sizeof(GPT_ATTRSTR_REQ)));
+ p += l - 1;
+ }
+ if (isset(bits, GPT_ATTRBIT_NOBLOCK)) {
+ if (p != *res)
+ *p++ = ' ';
+ memcpy(p, GPT_ATTRSTR_NOBLOCK, (l = sizeof(GPT_ATTRSTR_NOBLOCK)));
+ p += l - 1;
+ }
+ if (isset(bits, GPT_ATTRBIT_LEGACY)) {
+ if (p != *res)
+ *p++ = ' ';
+ memcpy(p, GPT_ATTRSTR_LEGACY, (l = sizeof(GPT_ATTRSTR_LEGACY)));
+ p += l - 1;
+ }
+
+ for (n = GPT_ATTRBIT_GUID_FIRST;
+ n < GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT; n++) {
+
+ if (!isset(bits, n))
+ continue;
+ if (!count) {
+ if (p != *res)
+ *p++ = ' ';
+ p += sprintf(p, "GUID:%u", n);
+ } else
+ p += sprintf(p, ",%u", n);
+ count++;
+ }
+
+ return 0;
+}
+
+static int gpt_entry_attrs_from_string(
+ struct fdisk_context *cxt,
+ struct gpt_entry *e,
+ const char *str)
+{
+ const char *p = str;
+ uint64_t attrs = 0;
+ char *bits;
+
+ assert(e);
+ assert(p);
+
+ DBG(GPT, ul_debug("parsing string attributes '%s'", p));
+
+ bits = (char *) &attrs;
+
+ while (p && *p) {
+ int bit = -1;
+
+ while (isblank(*p)) p++;
+ if (!*p)
+ break;
+
+ DBG(GPT, ul_debug(" item '%s'", p));
+
+ if (strncmp(p, GPT_ATTRSTR_REQ,
+ sizeof(GPT_ATTRSTR_REQ) - 1) == 0) {
+ bit = GPT_ATTRBIT_REQ;
+ p += sizeof(GPT_ATTRSTR_REQ) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_REQ_TYPO,
+ sizeof(GPT_ATTRSTR_REQ_TYPO) - 1) == 0) {
+ bit = GPT_ATTRBIT_REQ;
+ p += sizeof(GPT_ATTRSTR_REQ_TYPO) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_LEGACY,
+ sizeof(GPT_ATTRSTR_LEGACY) - 1) == 0) {
+ bit = GPT_ATTRBIT_LEGACY;
+ p += sizeof(GPT_ATTRSTR_LEGACY) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_NOBLOCK,
+ sizeof(GPT_ATTRSTR_NOBLOCK) - 1) == 0) {
+ bit = GPT_ATTRBIT_NOBLOCK;
+ p += sizeof(GPT_ATTRSTR_NOBLOCK) - 1;
+
+ /* GUID:<bit> as well as <bit> */
+ } else if (isdigit((unsigned char) *p)
+ || (strncmp(p, "GUID:", 5) == 0
+ && isdigit((unsigned char) *(p + 5)))) {
+ char *end = NULL;
+
+ if (*p == 'G')
+ p += 5;
+
+ errno = 0;
+ bit = strtol(p, &end, 0);
+ if (errno || !end || end == str
+ || bit < GPT_ATTRBIT_GUID_FIRST
+ || bit >= GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT)
+ bit = -1;
+ else
+ p = end;
+ }
+
+ if (bit < 0) {
+ fdisk_warnx(cxt, _("unsupported GPT attribute bit '%s'"), p);
+ return -EINVAL;
+ }
+
+ if (*p && *p != ',' && !isblank(*p)) {
+ fdisk_warnx(cxt, _("failed to parse GPT attribute string '%s'"), str);
+ return -EINVAL;
+ }
+
+ setbit(bits, bit);
+
+ while (isblank(*p)) p++;
+ if (*p == ',')
+ p++;
+ }
+
+ e->attrs = attrs;
+ return 0;
+}
+
+static int gpt_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ char u_str[UUID_STR_LEN];
+ int rc = 0;
+ struct gpt_guid guid;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (n >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+ e = gpt_get_entry(gpt, n);
+
+ pa->used = gpt_entry_is_used(e) || gpt_partition_start(e);
+ if (!pa->used)
+ return 0;
+
+ pa->start = gpt_partition_start(e);
+ pa->size = gpt_partition_size(e);
+ pa->type = gpt_partition_parttype(cxt, e);
+
+ guid = e->partition_guid;
+ if (guid_to_string(&guid, u_str)) {
+ pa->uuid = strdup(u_str);
+ if (!pa->uuid) {
+ rc = -errno;
+ goto done;
+ }
+ } else
+ pa->uuid = NULL;
+
+ rc = gpt_entry_attrs_to_string(e, &pa->attrs);
+ if (rc)
+ goto done;
+
+ pa->name = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+ return 0;
+done:
+ fdisk_reset_partition(pa);
+ return rc;
+}
+
+
+static int gpt_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ int rc = 0;
+ uint64_t start, end;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (n >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(end);
+
+ gpt = self_label(cxt);
+ e = gpt_get_entry(gpt, n);
+
+ if (pa->uuid) {
+ char new_u[UUID_STR_LEN], old_u[UUID_STR_LEN];
+ struct gpt_guid guid;
+
+ guid = e->partition_guid;
+ guid_to_string(&guid, old_u);
+ rc = gpt_entry_set_uuid(e, pa->uuid);
+ if (rc)
+ return rc;
+ guid = e->partition_guid;
+ guid_to_string(&guid, new_u);
+ fdisk_info(cxt, _("Partition UUID changed from %s to %s."),
+ old_u, new_u);
+ }
+
+ if (pa->name) {
+ int len;
+ char *old = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+ len = gpt_entry_set_name(e, pa->name);
+ if (len < 0)
+ fdisk_warn(cxt, _("Failed to translate partition name, name not changed."));
+ else
+ fdisk_info(cxt, _("Partition name changed from '%s' to '%.*s'."),
+ old, len, pa->name);
+ free(old);
+ }
+
+ if (pa->type && pa->type->typestr) {
+ struct gpt_guid typeid;
+
+ rc = string_to_guid(pa->type->typestr, &typeid);
+ if (rc)
+ return rc;
+ gpt_entry_set_type(e, &typeid);
+ }
+ if (pa->attrs) {
+ rc = gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_partition_has_start(pa))
+ start = pa->start;
+ if (fdisk_partition_has_size(pa) || fdisk_partition_has_start(pa)) {
+ uint64_t xstart = fdisk_partition_has_start(pa) ? pa->start : gpt_partition_start(e);
+ uint64_t xsize = fdisk_partition_has_size(pa) ? pa->size : gpt_partition_size(e);
+ end = xstart + xsize - 1ULL;
+ }
+
+ if (!FDISK_IS_UNDEF(start)) {
+ if (start < le64_to_cpu(gpt->pheader->first_usable_lba)) {
+ fdisk_warnx(cxt, _("The start of the partition understeps FirstUsableLBA."));
+ return -EINVAL;
+ }
+ e->lba_start = cpu_to_le64(start);
+ }
+ if (!FDISK_IS_UNDEF(end)) {
+ if (end > le64_to_cpu(gpt->pheader->last_usable_lba)) {
+ fdisk_warnx(cxt, _("The end of the partition oversteps LastUsableLBA."));
+ return -EINVAL;
+ }
+ e->lba_end = cpu_to_le64(end);
+ }
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return rc;
+}
+
+static int gpt_write(struct fdisk_context *cxt, off_t offset, void *buf, size_t count)
+{
+ if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+ return -errno;
+
+ if (write_all(cxt->dev_fd, buf, count))
+ return -errno;
+
+ fsync(cxt->dev_fd);
+
+ DBG(GPT, ul_debug(" write OK [offset=%zu, size=%zu]",
+ (size_t) offset, count));
+ return 0;
+}
+
+/*
+ * Write partitions.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_partitions(struct fdisk_context *cxt,
+ struct gpt_header *header, unsigned char *ents)
+{
+ size_t esz = 0;
+ int rc;
+
+ rc = gpt_sizeof_entries(header, &esz);
+ if (rc)
+ return rc;
+
+ return gpt_write(cxt,
+ (off_t) le64_to_cpu(header->partition_entry_lba) * cxt->sector_size,
+ ents, esz);
+}
+
+/*
+ * Write a GPT header to a specified LBA.
+ *
+ * We read all sector, so we have to write all sector back
+ * to the device -- never ever rely on sizeof(struct gpt_header)!
+ *
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_header(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ return gpt_write(cxt, lba * cxt->sector_size, header, cxt->sector_size);
+}
+
+/*
+ * Write the protective MBR.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_pmbr(struct fdisk_context *cxt)
+{
+ struct gpt_legacy_mbr *pmbr;
+
+ assert(cxt);
+ assert(cxt->firstsector);
+
+ DBG(GPT, ul_debug("(over)writing PMBR"));
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+ /* zero out the legacy partitions */
+ memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record));
+
+ pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+ pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE;
+ pmbr->partition_record[0].start_sector = 2;
+ pmbr->partition_record[0].end_head = 0xFF;
+ pmbr->partition_record[0].end_sector = 0xFF;
+ pmbr->partition_record[0].end_track = 0xFF;
+ pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+
+ /*
+ * Set size_in_lba to the size of the disk minus one. If the size of the disk
+ * is too large to be represented by a 32bit LBA (2Tb), set it to 0xFFFFFFFF.
+ */
+ if (cxt->total_sectors - 1ULL > 0xFFFFFFFFULL)
+ pmbr->partition_record[0].size_in_lba = cpu_to_le32(0xFFFFFFFF);
+ else
+ pmbr->partition_record[0].size_in_lba =
+ cpu_to_le32((uint32_t) (cxt->total_sectors - 1ULL));
+
+ /* pMBR covers the first sector (LBA) of the disk */
+ return gpt_write(cxt, GPT_PMBR_LBA * cxt->sector_size,
+ pmbr, cxt->sector_size);
+}
+
+/*
+ * Writes in-memory GPT and pMBR data to disk.
+ * Returns 0 if successful write, otherwise, a corresponding error.
+ * Any indication of error will abort the operation.
+ */
+static int gpt_write_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ int mbr_type;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ DBG(GPT, ul_debug("writing..."));
+
+ gpt = self_label(cxt);
+ mbr_type = valid_pmbr(cxt);
+
+ /* check that disk is big enough to handle the backup header */
+ if (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors)
+ goto err0;
+
+ /* check that the backup header is properly placed */
+ if (le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1ULL)
+ goto err0;
+
+ if (check_overlap_partitions(gpt))
+ goto err0;
+
+ if (gpt->minimize)
+ gpt_minimize_alternative_lba(cxt, gpt);
+
+ /* recompute CRCs for both headers */
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /*
+ * UEFI requires writing in this specific order:
+ * 1) backup partition tables
+ * 2) backup GPT header
+ * 3) primary partition tables
+ * 4) primary GPT header
+ * 5) protective MBR
+ *
+ * If any write fails, we abort the rest.
+ */
+ if (gpt_write_partitions(cxt, gpt->bheader, gpt->ents) != 0)
+ goto err1;
+ if (gpt_write_header(cxt, gpt->bheader,
+ le64_to_cpu(gpt->pheader->alternative_lba)) != 0)
+ goto err1;
+ if (gpt_write_partitions(cxt, gpt->pheader, gpt->ents) != 0)
+ goto err1;
+ if (gpt_write_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA) != 0)
+ goto err1;
+
+ if (mbr_type == GPT_MBR_HYBRID)
+ fdisk_warnx(cxt, _("The device contains hybrid MBR -- writing GPT only."));
+ else if (gpt_write_pmbr(cxt) != 0)
+ goto err1;
+
+ DBG(GPT, ul_debug("...write success"));
+ return 0;
+err0:
+ DBG(GPT, ul_debug("...write failed: incorrect input"));
+ errno = EINVAL;
+ return -EINVAL;
+err1:
+ DBG(GPT, ul_debug("...write failed: %m"));
+ return -errno;
+}
+
+/*
+ * Verify data integrity and report any found problems for:
+ * - primary and backup header validations
+ * - partition validations
+ */
+static int gpt_verify_disklabel(struct fdisk_context *cxt)
+{
+ int nerror = 0;
+ unsigned int ptnum;
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ if (!gpt)
+ return -EINVAL;
+
+ if (!gpt->bheader) {
+ nerror++;
+ fdisk_warnx(cxt, _("Disk does not contain a valid backup header."));
+ }
+
+ if (!gpt_check_header_crc(gpt->pheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid primary header CRC checksum."));
+ }
+ if (gpt->bheader && !gpt_check_header_crc(gpt->bheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid backup header CRC checksum."));
+ }
+
+ if (!gpt_check_entryarr_crc(gpt->pheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid partition entry checksum."));
+ }
+
+ if (!gpt_check_lba_sanity(cxt, gpt->pheader)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid primary header LBA sanity checks."));
+ }
+ if (gpt->bheader && !gpt_check_lba_sanity(cxt, gpt->bheader)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid backup header LBA sanity checks."));
+ }
+
+ if (le64_to_cpu(gpt->pheader->my_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA) {
+ nerror++;
+ fdisk_warnx(cxt, _("MyLBA mismatch with real position at primary header."));
+ }
+ if (gpt->bheader && le64_to_cpu(gpt->bheader->my_lba) != last_lba(cxt)) {
+ nerror++;
+ fdisk_warnx(cxt, _("MyLBA mismatch with real position at backup header."));
+
+ }
+ if (le64_to_cpu(gpt->pheader->alternative_lba) >= cxt->total_sectors) {
+ nerror++;
+ fdisk_warnx(cxt, _("Disk is too small to hold all data."));
+ }
+
+ /*
+ * if the GPT is the primary table, check the alternateLBA
+ * to see if it is a valid GPT
+ */
+ if (gpt->bheader && (le64_to_cpu(gpt->pheader->my_lba) !=
+ le64_to_cpu(gpt->bheader->alternative_lba))) {
+ nerror++;
+ fdisk_warnx(cxt, _("Primary and backup header mismatch."));
+ }
+
+ ptnum = check_overlap_partitions(gpt);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u overlaps with partition %u."),
+ ptnum, ptnum+1);
+ }
+
+ ptnum = check_too_big_partitions(gpt, cxt->total_sectors);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u is too big for the disk."),
+ ptnum);
+ }
+
+ ptnum = check_start_after_end_partitions(gpt);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u ends before it starts."),
+ ptnum);
+ }
+
+ if (!nerror) { /* yay :-) */
+ uint32_t nsegments = 0;
+ uint64_t free_sectors = 0, largest_segment = 0;
+ char *strsz = NULL;
+
+ fdisk_info(cxt, _("No errors detected."));
+ fdisk_info(cxt, _("Header version: %s"), gpt_get_header_revstr(gpt->pheader));
+ fdisk_info(cxt, _("Using %zu out of %zu partitions."),
+ partitions_in_use(gpt),
+ gpt_get_nentries(gpt));
+
+ free_sectors = get_free_sectors(cxt, gpt, &nsegments, &largest_segment);
+ if (largest_segment)
+ strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER,
+ largest_segment * cxt->sector_size);
+
+ fdisk_info(cxt,
+ P_("A total of %ju free sectors is available in %u segment.",
+ "A total of %ju free sectors is available in %u segments "
+ "(the largest is %s).", nsegments),
+ free_sectors, nsegments, strsz ? : "0 B");
+ free(strsz);
+
+ } else
+ fdisk_warnx(cxt,
+ P_("%d error detected.", "%d errors detected.", nerror),
+ nerror);
+
+ return nerror;
+}
+
+/* Delete a single GPT partition, specified by partnum. */
+static int gpt_delete_partition(struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (partnum >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ if (!gpt_entry_is_used(gpt_get_entry(gpt, partnum)))
+ return -EINVAL;
+
+ /* hasta la vista, baby! */
+ gpt_zeroize_entry(gpt, partnum);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ cxt->label->nparts_cur--;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ return 0;
+}
+
+
+/* Performs logical checks to add a new partition entry */
+static int gpt_add_partition(
+ struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ uint64_t user_f, user_l; /* user input ranges for first and last sectors */
+ uint64_t disk_f, disk_l; /* first and last available sector ranges on device*/
+ uint64_t dflt_f, dflt_l, max_l; /* largest segment (default) */
+ struct gpt_guid typeid;
+ struct fdisk_gpt_label *gpt;
+ struct gpt_header *pheader;
+ struct gpt_entry *e;
+ struct fdisk_ask *ask = NULL;
+ size_t partnum;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ pheader = gpt->pheader;
+
+ rc = fdisk_partition_next_partno(pa, cxt, &partnum);
+ if (rc) {
+ DBG(GPT, ul_debug("failed to get next partno"));
+ return rc;
+ }
+
+ assert(partnum < gpt_get_nentries(gpt));
+
+ if (gpt_entry_is_used(gpt_get_entry(gpt, partnum))) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."), partnum +1);
+ return -ERANGE;
+ }
+ if (gpt_get_nentries(gpt) == partitions_in_use(gpt)) {
+ fdisk_warnx(cxt, _("All partitions are already in use."));
+ return -ENOSPC;
+ }
+ if (!get_free_sectors(cxt, gpt, NULL, NULL)) {
+ fdisk_warnx(cxt, _("No free sectors available."));
+ return -ENOSPC;
+ }
+
+ rc = string_to_guid(pa && pa->type && pa->type->typestr ?
+ pa->type->typestr:
+ GPT_DEFAULT_ENTRY_TYPE, &typeid);
+ if (rc)
+ return rc;
+
+ disk_f = find_first_available(gpt, le64_to_cpu(pheader->first_usable_lba));
+ e = gpt_get_entry(gpt, 0);
+
+ /* if first sector no explicitly defined then ignore small gaps before
+ * the first partition */
+ if ((!pa || !fdisk_partition_has_start(pa))
+ && gpt_entry_is_used(e)
+ && disk_f < gpt_partition_start(e)) {
+
+ do {
+ uint64_t x;
+ DBG(GPT, ul_debug("testing first sector %"PRIu64"", disk_f));
+ disk_f = find_first_available(gpt, disk_f);
+ if (!disk_f)
+ break;
+ x = find_last_free(gpt, disk_f);
+ if (x - disk_f >= cxt->grain / cxt->sector_size)
+ break;
+ DBG(GPT, ul_debug("first sector %"PRIu64" addresses to small space, continue...", disk_f));
+ disk_f = x + 1ULL;
+ } while(1);
+
+ if (disk_f == 0)
+ disk_f = find_first_available(gpt, le64_to_cpu(pheader->first_usable_lba));
+ }
+
+ e = NULL;
+ disk_l = find_last_free_sector(gpt);
+
+ /* the default is the largest free space */
+ dflt_f = find_first_in_largest(gpt);
+ dflt_l = find_last_free(gpt, dflt_f);
+
+ /* don't offer too small free space by default, this is possible to
+ * bypass by sfdisk script */
+ if ((!pa || !fdisk_partition_has_start(pa))
+ && dflt_l - dflt_f + 1 < cxt->grain / cxt->sector_size) {
+ fdisk_warnx(cxt, _("No enough free sectors available."));
+ return -ENOSPC;
+ }
+
+ /* align the default in range <dflt_f,dflt_l>*/
+ dflt_f = fdisk_align_lba_in_range(cxt, dflt_f, dflt_f, dflt_l);
+
+ /* first sector */
+ if (pa && pa->start_follow_default) {
+ user_f = dflt_f;
+
+ } else if (pa && fdisk_partition_has_start(pa)) {
+ DBG(GPT, ul_debug("first sector defined: %ju", (uintmax_t)pa->start));
+ if (pa->start != find_first_available(gpt, pa->start)) {
+ fdisk_warnx(cxt, _("Sector %ju already used."), (uintmax_t)pa->start);
+ return -ERANGE;
+ }
+ user_f = pa->start;
+ } else {
+ /* ask by dialog */
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+
+ /* First sector */
+ fdisk_ask_set_query(ask, _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, disk_f); /* minimal */
+ fdisk_ask_number_set_default(ask, dflt_f); /* default */
+ fdisk_ask_number_set_high(ask, disk_l); /* maximal */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ user_f = fdisk_ask_number_get_result(ask);
+ if (user_f != find_first_available(gpt, user_f)) {
+ fdisk_warnx(cxt, _("Sector %ju already used."), user_f);
+ continue;
+ }
+ break;
+ }
+ }
+
+
+ /* Last sector */
+ dflt_l = max_l = find_last_free(gpt, user_f);
+
+ /* Make sure the last partition has aligned size by default because
+ * range specified by LastUsableLBA may be unaligned on disks where
+ * logical sector != physical (512/4K) because backup header size is
+ * calculated from logical sectors. */
+ if (max_l == le64_to_cpu(gpt->pheader->last_usable_lba))
+ dflt_l = fdisk_align_lba_in_range(cxt, max_l, user_f, max_l) - 1;
+
+ if (pa && pa->end_follow_default) {
+ user_l = dflt_l;
+
+ } else if (pa && fdisk_partition_has_size(pa)) {
+ user_l = user_f + pa->size - 1;
+ DBG(GPT, ul_debug("size defined: %ju, end: %"PRIu64
+ "(last possible: %"PRIu64", optimal: %"PRIu64")",
+ (uintmax_t)pa->size, user_l, max_l, dflt_l));
+
+ if (user_l != dflt_l
+ && !pa->size_explicit
+ && alignment_required(cxt)
+ && user_l - user_f > (cxt->grain / fdisk_get_sector_size(cxt))) {
+
+ user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l);
+ if (user_l > user_f)
+ user_l -= 1ULL;
+ }
+ } else {
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+ fdisk_ask_number_set_low(ask, user_f); /* minimal */
+ fdisk_ask_number_set_default(ask, dflt_l); /* default */
+ fdisk_ask_number_set_high(ask, max_l); /* maximal */
+ fdisk_ask_number_set_base(ask, user_f); /* base for relative input */
+ fdisk_ask_number_set_unit(ask, cxt->sector_size);
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ user_l = fdisk_ask_number_get_result(ask);
+ if (fdisk_ask_number_is_relative(ask)) {
+ user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l);
+ if (user_l > user_f)
+ user_l -= 1ULL;
+ }
+
+ if (user_l >= user_f && user_l <= disk_l)
+ break;
+
+ fdisk_warnx(cxt, _("Value out of range."));
+ }
+ }
+
+
+ if (user_f > user_l || partnum >= cxt->label->nparts_max) {
+ fdisk_warnx(cxt, _("Could not create partition %zu"), partnum + 1);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ /* Be paranoid and check against on-disk setting rather than against libfdisk cxt */
+ if (user_l > le64_to_cpu(pheader->last_usable_lba)) {
+ fdisk_warnx(cxt, _("The last usable GPT sector is %ju, but %ju is requested."),
+ le64_to_cpu(pheader->last_usable_lba), user_l);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ if (user_f < le64_to_cpu(pheader->first_usable_lba)) {
+ fdisk_warnx(cxt, _("The first usable GPT sector is %ju, but %ju is requested."),
+ le64_to_cpu(pheader->first_usable_lba), user_f);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ assert(!FDISK_IS_UNDEF(user_l));
+ assert(!FDISK_IS_UNDEF(user_f));
+ assert(partnum < gpt_get_nentries(gpt));
+
+ e = gpt_get_entry(gpt, partnum);
+ e->lba_end = cpu_to_le64(user_l);
+ e->lba_start = cpu_to_le64(user_f);
+
+ gpt_entry_set_type(e, &typeid);
+
+ if (pa && pa->uuid) {
+ /* Sometimes it's necessary to create a copy of the PT and
+ * reuse already defined UUID
+ */
+ rc = gpt_entry_set_uuid(e, pa->uuid);
+ if (rc)
+ goto done;
+ } else {
+ /* Any time a new partition entry is created a new GUID must be
+ * generated for that partition, and every partition is guaranteed
+ * to have a unique GUID.
+ */
+ struct gpt_guid guid;
+
+ uuid_generate_random((unsigned char *) &e->partition_guid);
+ guid = e->partition_guid;
+ swap_efi_guid(&guid);
+ }
+
+ if (pa && pa->name && *pa->name)
+ gpt_entry_set_name(e, pa->name);
+ if (pa && pa->attrs)
+ gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+
+ DBG(GPT, ul_debug("new partition: partno=%zu, start=%"PRIu64", end=%"PRIu64", size=%"PRIu64"",
+ partnum,
+ gpt_partition_start(e),
+ gpt_partition_end(e),
+ gpt_partition_size(e)));
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /* report result */
+ {
+ struct fdisk_parttype *t;
+
+ cxt->label->nparts_cur++;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ t = gpt_partition_parttype(cxt, e);
+ fdisk_info_new_partition(cxt, partnum + 1, user_f, user_l, t);
+ fdisk_unref_parttype(t);
+ }
+
+ rc = 0;
+ if (partno)
+ *partno = partnum;
+done:
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/*
+ * Create a new GPT disklabel - destroys any previous data.
+ */
+static int gpt_create_disklabel(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ size_t esz = 0;
+ char str[UUID_STR_LEN];
+ struct fdisk_gpt_label *gpt;
+ struct gpt_guid guid;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ /* label private stuff has to be empty, see gpt_deinit() */
+ assert(gpt->pheader == NULL);
+ assert(gpt->bheader == NULL);
+
+ /*
+ * When no header, entries or pmbr is set, we're probably
+ * dealing with a new, empty disk - so always allocate memory
+ * to deal with the data structures whatever the case is.
+ */
+ rc = gpt_mknew_pmbr(cxt);
+ if (rc < 0)
+ goto done;
+
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ /* primary */
+ gpt->pheader = calloc(1, cxt->sector_size);
+ if (!gpt->pheader) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ rc = gpt_mknew_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA);
+ if (rc < 0)
+ goto done;
+
+ /* backup ("copy" primary) */
+ gpt->bheader = calloc(1, cxt->sector_size);
+ if (!gpt->bheader) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ rc = gpt_mknew_header_from_bkp(cxt, gpt->bheader,
+ last_lba(cxt), gpt->pheader);
+ if (rc < 0)
+ goto done;
+
+ rc = gpt_sizeof_entries(gpt->pheader, &esz);
+ if (rc)
+ goto done;
+ gpt->ents = calloc(1, esz);
+ if (!gpt->ents) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+ cxt->label->nparts_cur = 0;
+
+ guid = gpt->pheader->disk_guid;
+ guid_to_string(&guid, str);
+ fdisk_label_set_changed(cxt->label, 1);
+ fdisk_info(cxt, _("Created a new GPT disklabel (GUID: %s)."), str);
+
+ if (gpt_get_nentries(gpt) < GPT_NPARTITIONS)
+ fdisk_info(cxt, _("The maximal number of partitions is %zu (default is %zu)."),
+ gpt_get_nentries(gpt), GPT_NPARTITIONS);
+done:
+ return rc;
+}
+
+static int gpt_set_disklabel_id(struct fdisk_context *cxt, const char *str)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_guid uuid;
+ char *old, *new;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ if (!str) {
+ char *buf = NULL;
+
+ if (fdisk_ask_string(cxt,
+ _("Enter new disk UUID (in 8-4-4-4-12 format)"), &buf))
+ return -EINVAL;
+ rc = string_to_guid(buf, &uuid);
+ free(buf);
+ } else
+ rc = string_to_guid(str, &uuid);
+
+ if (rc) {
+ fdisk_warnx(cxt, _("Failed to parse your UUID."));
+ return rc;
+ }
+
+ old = gpt_get_header_id(gpt->pheader);
+
+ gpt->pheader->disk_guid = uuid;
+ gpt->bheader->disk_guid = uuid;
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ new = gpt_get_header_id(gpt->pheader);
+
+ fdisk_info(cxt, _("Disk identifier changed from %s to %s."), old, new);
+
+ free(old);
+ free(new);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_check_table_overlap(struct fdisk_context *cxt,
+ uint64_t first_usable,
+ uint64_t last_usable)
+{
+ struct fdisk_gpt_label *gpt = self_label(cxt);
+ size_t i;
+ int rc = 0;
+
+ /* First check if there's enough room for the table. last_lba may have wrapped */
+ if (first_usable > cxt->total_sectors || /* far too little space */
+ last_usable > cxt->total_sectors || /* wrapped */
+ first_usable > last_usable) { /* too little space */
+ fdisk_warnx(cxt, _("Not enough space for new partition table!"));
+ return -ENOSPC;
+ }
+
+ /* check that all partitions fit in the remaining space */
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_start(e) < first_usable) {
+ fdisk_warnx(cxt, _("Partition #%zu out of range (minimal start is %"PRIu64" sectors)"),
+ i + 1, first_usable);
+ rc = -EINVAL;
+ }
+ if (gpt_partition_end(e) > last_usable) {
+ fdisk_warnx(cxt, _("Partition #%zu out of range (maximal end is %"PRIu64" sectors)"),
+ i + 1, last_usable - (uint64_t) 1);
+ rc = -EINVAL;
+ }
+ }
+ return rc;
+}
+
+/**
+ * fdisk_gpt_set_npartitions:
+ * @cxt: context
+ * @nents: number of wanted entries
+ *
+ * Elarge GPT entries array if possible. The function check if an existing
+ * partition does not overlap the entries array area. If yes, then it report
+ * warning and returns -EINVAL.
+ *
+ * Returns: 0 on success, < 0 on error.
+ * Since: 2.29
+ */
+int fdisk_gpt_set_npartitions(struct fdisk_context *cxt, uint32_t nents)
+{
+ struct fdisk_gpt_label *gpt;
+ size_t new_size = 0;
+ uint32_t old_nents;
+ uint64_t first_usable = 0ULL, last_usable = 0ULL;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+
+ old_nents = le32_to_cpu(gpt->pheader->npartition_entries);
+ if (old_nents == nents)
+ return 0; /* do nothing, say nothing */
+
+ /* calculate the size (bytes) of the entries array */
+ rc = gpt_calculate_sizeof_entries(gpt->pheader, nents, &new_size);
+ if (rc) {
+ uint32_t entry_size = le32_to_cpu(gpt->pheader->sizeof_partition_entry);
+
+ if (entry_size == 0)
+ fdisk_warnx(cxt, _("The partition entry size is zero."));
+ else
+ fdisk_warnx(cxt, _("The number of the partition has to be smaller than %zu."),
+ (size_t) UINT32_MAX / entry_size);
+ return rc;
+ }
+
+ rc = gpt_calculate_first_lba(gpt->pheader, nents, &first_usable, cxt);
+ if (rc == 0)
+ rc = gpt_calculate_last_lba(gpt->pheader, nents, &last_usable, cxt);
+ if (rc)
+ return rc;
+
+ /* if expanding the table, first check that everything fits,
+ * then allocate more memory and zero. */
+ if (nents > old_nents) {
+ unsigned char *ents;
+ size_t old_size = 0;
+
+ rc = gpt_calculate_sizeof_entries(gpt->pheader, old_nents, &old_size);
+ if (rc == 0)
+ rc = gpt_check_table_overlap(cxt, first_usable, last_usable);
+ if (rc)
+ return rc;
+ ents = realloc(gpt->ents, new_size);
+ if (!ents) {
+ fdisk_warnx(cxt, _("Cannot allocate memory!"));
+ return -ENOMEM;
+ }
+ memset(ents + old_size, 0, new_size - old_size);
+ gpt->ents = ents;
+ }
+
+ /* everything's ok, apply the new size */
+ gpt->pheader->npartition_entries = cpu_to_le32(nents);
+ gpt->bheader->npartition_entries = cpu_to_le32(nents);
+
+ /* usable LBA addresses will have changed */
+ fdisk_set_first_lba(cxt, first_usable);
+ fdisk_set_last_lba(cxt, last_usable);
+ gpt->pheader->first_usable_lba = cpu_to_le64(first_usable);
+ gpt->bheader->first_usable_lba = cpu_to_le64(first_usable);
+ gpt->pheader->last_usable_lba = cpu_to_le64(last_usable);
+ gpt->bheader->last_usable_lba = cpu_to_le64(last_usable);
+
+ /* The backup header must be recalculated */
+ gpt_mknew_header_common(cxt, gpt->bheader, le64_to_cpu(gpt->pheader->alternative_lba));
+
+ /* CRCs will have changed */
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /* update library info */
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+
+ fdisk_info(cxt, _("Partition table length changed from %"PRIu32" to %"PRIu32"."),
+ old_nents, nents);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_part_is_used(struct fdisk_context *cxt, size_t i)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (i >= gpt_get_nentries(gpt))
+ return 0;
+
+ e = gpt_get_entry(gpt, i);
+
+ return gpt_entry_is_used(e) || gpt_partition_start(e);
+}
+
+/**
+ * fdisk_gpt_is_hybrid:
+ * @cxt: context
+ *
+ * The regular GPT contains PMBR (dummy protective MBR) where the protective
+ * MBR does not address any partitions.
+ *
+ * Hybrid GPT contains regular MBR where this partition table addresses the
+ * same partitions as GPT. It's recommended to not use hybrid GPT due to MBR
+ * limits.
+ *
+ * The libfdisk does not provide functionality to sync GPT and MBR, you have to
+ * directly access and modify (P)MBR (see fdisk_new_nested_context()).
+ *
+ * Returns: 1 if partition table detected as hybrid otherwise return 0
+ */
+int fdisk_gpt_is_hybrid(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return valid_pmbr(cxt) == GPT_MBR_HYBRID;
+}
+
+/**
+ * fdisk_gpt_get_partition_attrs:
+ * @cxt: context
+ * @partnum: partition number
+ * @attrs: GPT partition attributes
+ *
+ * Sets @attrs for the given partition
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_gpt_get_partition_attrs(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ uint64_t *attrs)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+
+ if (partnum >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ *attrs = le64_to_cpu(gpt_get_entry(gpt, partnum)->attrs);
+ return 0;
+}
+
+/**
+ * fdisk_gpt_set_partition_attrs:
+ * @cxt: context
+ * @partnum: partition number
+ * @attrs: GPT partition attributes
+ *
+ * Sets the GPT partition attributes field to @attrs.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_gpt_set_partition_attrs(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ uint64_t attrs)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ DBG(GPT, ul_debug("entry attributes change requested partno=%zu", partnum));
+ gpt = self_label(cxt);
+
+ if (partnum >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ gpt_get_entry(gpt, partnum)->attrs = cpu_to_le64(attrs);
+ fdisk_info(cxt, _("The attributes on partition %zu changed to 0x%016" PRIx64 "."),
+ partnum + 1, attrs);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_toggle_partition_flag(
+ struct fdisk_context *cxt,
+ size_t i,
+ unsigned long flag)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ uint64_t attrs;
+ uintmax_t tmp;
+ char *bits;
+ const char *name = NULL;
+ int bit = -1, rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ DBG(GPT, ul_debug("entry attribute change requested partno=%zu", i));
+ gpt = self_label(cxt);
+
+ if (i >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ e = gpt_get_entry(gpt, i);
+ attrs = e->attrs;
+ bits = (char *) &attrs;
+
+ switch (flag) {
+ case GPT_FLAG_REQUIRED:
+ bit = GPT_ATTRBIT_REQ;
+ name = GPT_ATTRSTR_REQ;
+ break;
+ case GPT_FLAG_NOBLOCK:
+ bit = GPT_ATTRBIT_NOBLOCK;
+ name = GPT_ATTRSTR_NOBLOCK;
+ break;
+ case GPT_FLAG_LEGACYBOOT:
+ bit = GPT_ATTRBIT_LEGACY;
+ name = GPT_ATTRSTR_LEGACY;
+ break;
+ case GPT_FLAG_GUIDSPECIFIC:
+ rc = fdisk_ask_number(cxt, 48, 48, 63, _("Enter GUID specific bit"), &tmp);
+ if (rc)
+ return rc;
+ bit = tmp;
+ break;
+ default:
+ /* already specified PT_FLAG_GUIDSPECIFIC bit */
+ if (flag >= 48 && flag <= 63) {
+ bit = flag;
+ flag = GPT_FLAG_GUIDSPECIFIC;
+ }
+ break;
+ }
+
+ if (bit < 0) {
+ fdisk_warnx(cxt, _("failed to toggle unsupported bit %lu"), flag);
+ return -EINVAL;
+ }
+
+ if (!isset(bits, bit))
+ setbit(bits, bit);
+ else
+ clrbit(bits, bit);
+
+ e->attrs = attrs;
+
+ if (flag == GPT_FLAG_GUIDSPECIFIC)
+ fdisk_info(cxt, isset(bits, bit) ?
+ _("The GUID specific bit %d on partition %zu is enabled now.") :
+ _("The GUID specific bit %d on partition %zu is disabled now."),
+ bit, i + 1);
+ else
+ fdisk_info(cxt, isset(bits, bit) ?
+ _("The %s flag on partition %zu is enabled now.") :
+ _("The %s flag on partition %zu is disabled now."),
+ name, i + 1);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_entry_cmp_start(const void *a, const void *b)
+{
+ const struct gpt_entry *ae = (const struct gpt_entry *) a,
+ *be = (const struct gpt_entry *) b;
+ int au = gpt_entry_is_used(ae),
+ bu = gpt_entry_is_used(be);
+
+ if (!au && !bu)
+ return 0;
+ if (!au)
+ return 1;
+ if (!bu)
+ return -1;
+
+ return cmp_numbers(gpt_partition_start(ae), gpt_partition_start(be));
+}
+
+/* sort partition by start sector */
+static int gpt_reorder(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ size_t i, nparts, mess;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ nparts = gpt_get_nentries(gpt);
+
+ for (i = 0, mess = 0; mess == 0 && i + 1 < nparts; i++)
+ mess = gpt_entry_cmp_start(
+ (const void *) gpt_get_entry(gpt, i),
+ (const void *) gpt_get_entry(gpt, i + 1)) > 0;
+
+ if (!mess)
+ return 1;
+
+ qsort(gpt->ents, nparts, sizeof(struct gpt_entry),
+ gpt_entry_cmp_start);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ return 0;
+}
+
+static int gpt_reset_alignment(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_header *h;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ h = gpt ? gpt->pheader : NULL;
+
+ if (h) {
+ /* always follow existing table */
+ cxt->first_lba = le64_to_cpu(h->first_usable_lba);
+ cxt->last_lba = le64_to_cpu(h->last_usable_lba);
+ } else {
+ /* estimate ranges for GPT */
+ uint64_t first, last;
+
+ count_first_last_lba(cxt, &first, &last, NULL);
+ if (cxt->first_lba < first)
+ cxt->first_lba = first;
+ if (cxt->last_lba > last)
+ cxt->last_lba = last;
+ }
+
+ return 0;
+}
+/*
+ * Deinitialize fdisk-specific variables
+ */
+static void gpt_deinit(struct fdisk_label *lb)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ if (!gpt)
+ return;
+
+ free(gpt->ents);
+ free(gpt->pheader);
+ free(gpt->bheader);
+
+ gpt->ents = NULL;
+ gpt->pheader = NULL;
+ gpt->bheader = NULL;
+}
+
+static const struct fdisk_label_operations gpt_operations =
+{
+ .probe = gpt_probe_label,
+ .write = gpt_write_disklabel,
+ .verify = gpt_verify_disklabel,
+ .create = gpt_create_disklabel,
+ .locate = gpt_locate_disklabel,
+ .get_item = gpt_get_disklabel_item,
+ .set_id = gpt_set_disklabel_id,
+
+ .get_part = gpt_get_partition,
+ .set_part = gpt_set_partition,
+ .add_part = gpt_add_partition,
+ .del_part = gpt_delete_partition,
+ .reorder = gpt_reorder,
+
+ .part_is_used = gpt_part_is_used,
+ .part_toggle_flag = gpt_toggle_partition_flag,
+
+ .deinit = gpt_deinit,
+
+ .reset_alignment = gpt_reset_alignment
+};
+
+static const struct fdisk_field gpt_fields[] =
+{
+ /* basic */
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY },
+ /* expert */
+ { FDISK_FIELD_TYPEID, N_("Type-UUID"), 36, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_UUID, N_("UUID"), 36, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_NAME, N_("Name"), 0.2, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_DETAIL }
+};
+
+/*
+ * allocates GPT in-memory stuff
+ */
+struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_gpt_label *gpt;
+
+ gpt = calloc(1, sizeof(*gpt));
+ if (!gpt)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) gpt;
+ lb->name = "gpt";
+ lb->id = FDISK_DISKLABEL_GPT;
+ lb->op = &gpt_operations;
+
+ lb->parttypes = gpt_parttypes;
+ lb->nparttypes = ARRAY_SIZE(gpt_parttypes);
+ lb->parttype_cuts = gpt_parttype_cuts;
+ lb->nparttype_cuts = ARRAY_SIZE(gpt_parttype_cuts);
+
+ lb->fields = gpt_fields;
+ lb->nfields = ARRAY_SIZE(gpt_fields);
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) gpt;
+}
+
+/**
+ * fdisk_gpt_disable_relocation
+ * @lb: label
+ * @disable: 0 or 1
+ *
+ * Disable automatic backup header relocation to the end of the device. The
+ * header position is recalculated during libfdisk probing stage by
+ * fdisk_assign_device() and later written by fdisk_write_disklabel(), so you
+ * need to call it before fdisk_assign_device().
+ *
+ * Since: 2.36
+ */
+void fdisk_gpt_disable_relocation(struct fdisk_label *lb, int disable)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ assert(gpt);
+ gpt->no_relocate = disable ? 1 : 0;
+}
+
+/**
+ * fdisk_gpt_enable_minimize
+ * @lb: label
+ * @enable: 0 or 1
+ *
+ * Force libfdisk to write backup header to behind last partition. The
+ * header position is recalculated on fdisk_write_disklabel().
+ *
+ * Since: 2.36
+ */
+void fdisk_gpt_enable_minimize(struct fdisk_label *lb, int enable)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ assert(gpt);
+ gpt->minimize = enable ? 1 : 0;
+}
+
+#ifdef TEST_PROGRAM
+static int test_getattr(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ size_t part = strtoul(argv[2], NULL, 0) - 1;
+ struct fdisk_context *cxt;
+ uint64_t atters = 0;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, disk, 1);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return EXIT_FAILURE;
+
+ if (fdisk_gpt_get_partition_attrs(cxt, part, &atters))
+ return EXIT_FAILURE;
+
+ printf("%s: 0x%016" PRIx64 "\n", argv[2], atters);
+
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+static int test_setattr(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ size_t part = strtoul(argv[2], NULL, 0) - 1;
+ uint64_t atters = strtoull(argv[3], NULL, 0);
+ struct fdisk_context *cxt;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, disk, 0);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return EXIT_FAILURE;
+
+ if (fdisk_gpt_set_partition_attrs(cxt, part, atters))
+ return EXIT_FAILURE;
+
+ if (fdisk_write_disklabel(cxt))
+ return EXIT_FAILURE;
+
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--getattr", test_getattr, "<disk> <partition> print attributes" },
+ { "--setattr", test_setattr, "<disk> <partition> <value> set attributes" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/init.c b/libfdisk/src/init.c
new file mode 100644
index 0000000..501a7d6
--- /dev/null
+++ b/libfdisk/src/init.c
@@ -0,0 +1,62 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debug stuff
+ *
+ */
+
+UL_DEBUG_DEFINE_MASK(libfdisk);
+UL_DEBUG_DEFINE_MASKNAMES(libfdisk) =
+{
+ { "all", LIBFDISK_DEBUG_ALL, "info about all subsystems" },
+ { "ask", LIBFDISK_DEBUG_ASK, "fdisk dialogs" },
+ { "help", LIBFDISK_DEBUG_HELP, "this help" },
+ { "cxt", LIBFDISK_DEBUG_CXT, "library context (handler)" },
+ { "label", LIBFDISK_DEBUG_LABEL, "disk label utils" },
+ { "part", LIBFDISK_DEBUG_PART, "partition utils" },
+ { "parttype", LIBFDISK_DEBUG_PARTTYPE,"partition type utils" },
+ { "script", LIBFDISK_DEBUG_SCRIPT, "sfdisk-like scripts" },
+ { "tab", LIBFDISK_DEBUG_TAB, "table utils"},
+ { "wipe", LIBFDISK_DEBUG_WIPE, "wipe area utils" },
+ { "item", LIBFDISK_DEBUG_ITEM, "disklabel items" },
+ { "gpt", LIBFDISK_DEBUG_GPT, "GPT subsystems" },
+ { NULL, 0 }
+};
+
+/**
+ * fdisk_init_debug:
+ * @mask: debug mask (0xffff to enable full debugging)
+ *
+ * If the @mask is not specified then this function reads
+ * LIBFDISK_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. It does not
+ * have effect to call this function twice.
+ *
+ * It's strongly recommended to use fdisk_init_debug(0) in your code.
+ */
+void fdisk_init_debug(int mask)
+{
+ if (libfdisk_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(libfdisk, LIBFDISK_DEBUG_, mask, LIBFDISK_DEBUG);
+
+
+ if (libfdisk_debug_mask != LIBFDISK_DEBUG_INIT
+ && libfdisk_debug_mask != (LIBFDISK_DEBUG_HELP|LIBFDISK_DEBUG_INIT)) {
+ const char *ver = NULL;
+
+ fdisk_get_library_version(&ver);
+
+ DBG(INIT, ul_debug("library debug mask: 0x%04x", libfdisk_debug_mask));
+ DBG(INIT, ul_debug("library version: %s", ver));
+ }
+
+ ON_DBG(HELP, ul_debug_print_masks("LIBFDISK_DEBUG",
+ UL_DEBUG_MASKNAMES(libfdisk)));
+}
diff --git a/libfdisk/src/item.c b/libfdisk/src/item.c
new file mode 100644
index 0000000..671f9ad
--- /dev/null
+++ b/libfdisk/src/item.c
@@ -0,0 +1,255 @@
+
+#include <inttypes.h>
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: item
+ * @title: Labelitem
+ * @short_description: disk label items
+ *
+ * The labelitem is label specific items stored in the partition table header.
+ * The information provided by labelitems are not specific to the partitions.
+ *
+ * For example
+ *
+ * <informalexample>
+ * <programlisting>
+ * struct fdisk_labelitem *item = fdisk_new_labelitem();
+ *
+ * fdisk_get_disklabel_item(cxt, GPT_LABELITEM_ALTLBA, item);
+ * print("Backup header LBA: %ju\n", fdisk_labelitem_get_data_u64(item));
+ *
+ * fdisk_unref_labelitem(item);
+ * </programlisting>
+ * </informalexample>
+ *
+ * returns LBA of the alternative GPT header.
+ *
+ * See also fdisk_get_disklabel_item(). The IDs are generic (e.g.
+ * FDISK_LABEL_ITEM_*) and label specific ((e.g. GPT_LABELITEM_*).
+ */
+
+/**
+ * fdisk_new_labelitem
+ *
+ * Returns: new instance.
+ * Since: 2.29
+ */
+struct fdisk_labelitem *fdisk_new_labelitem(void)
+{
+ struct fdisk_labelitem *li = calloc(1, sizeof(*li));
+
+ if (!li)
+ return NULL;
+
+ li->refcount = 1;
+ DBG(ITEM, ul_debugobj(li, "alloc"));
+ return li;
+}
+
+/**
+ * fdisk_ref_labelitem:
+ * @li: label item
+ *
+ * Increments reference counter.
+ * Since: 2.29
+ */
+void fdisk_ref_labelitem(struct fdisk_labelitem *li)
+{
+ if (li) {
+ /* me sure we do not use refcouting for static items */
+ assert(li->refcount > 0);
+ li->refcount++;
+ }
+}
+
+/**
+ * fdisk_reset_labelitem:
+ * @li: label item
+ *
+ * Zeroize data stored in the @li (does not modify anything in disk label).
+ *
+ * Returns: 0 on success, or <0 in case of error
+ * Since: 2.29
+ */
+int fdisk_reset_labelitem(struct fdisk_labelitem *li)
+{
+ int refcount;
+
+ if (!li)
+ return -EINVAL;
+ if (li->type == 's')
+ free(li->data.str);
+
+ refcount = li->refcount;
+ memset(li, 0, sizeof(*li));
+ li->refcount = refcount;
+ return 0;
+}
+
+/**
+ * fdisk_unref_labelitem:
+ * @li: label item
+ *
+ * Decrements reference counter, on zero the @li is automatically
+ * deallocated.
+ *
+ * Since: 2.29
+ */
+void fdisk_unref_labelitem(struct fdisk_labelitem *li)
+{
+ if (!li)
+ return;
+
+ /* me sure we do not use refcouting for static items */
+ assert(li->refcount > 0);
+
+ li->refcount--;
+ if (li->refcount <= 0) {
+ DBG(ITEM, ul_debugobj(li, "free"));
+ fdisk_reset_labelitem(li);
+ free(li);
+ }
+}
+
+/**
+ * fdisk_labelitem_get_name:
+ * @li: label item
+ *
+ * Returns: item name or NULL.
+ * Since: 2.29
+ */
+const char *fdisk_labelitem_get_name(struct fdisk_labelitem *li)
+{
+ return li ? li->name : NULL;
+}
+
+/**
+ * fdisk_labelitem_get_id:
+ * @li: label item
+ *
+ * Returns: item Id or <0 in case of error.
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_id(struct fdisk_labelitem *li)
+{
+ return li ? li->id : -EINVAL;
+}
+
+
+/**
+ * fdisk_labelitem_get_data_u64:
+ * @li: label item
+ * @data: returns data
+ *
+ * Returns: 0 on success, <0 on error
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_data_u64(struct fdisk_labelitem *li, uint64_t *data)
+{
+ if (!li || li->type != 'j')
+ return -EINVAL;
+
+ if (data)
+ *data = li->data.num64;
+ return 0;
+}
+
+/**
+ * fdisk_labelitem_get_data_string:
+ * @li: label item
+ * @data: returns data
+ *
+ * Returns: 0 on success, <0 on error.
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_data_string(struct fdisk_labelitem *li, const char **data)
+{
+ if (!li || li->type != 's')
+ return -EINVAL;
+
+ if (data)
+ *data = li->data.str;
+ return 0;
+}
+
+/**
+ * fdisk_labelitem_is_string:
+ * @li: label item
+ *
+ * Returns: 0 or 1
+ * Since: 2.29
+ */
+int fdisk_labelitem_is_string(struct fdisk_labelitem *li)
+{
+ return li && li->type == 's';
+}
+
+/**
+ * fdisk_labelitem_is_number:
+ * @li: label item
+ *
+ * Returns: 0 or 1
+ * Since: 2.29
+ */
+int fdisk_labelitem_is_number(struct fdisk_labelitem *li)
+{
+ return li && li->type == 'j';
+}
+
+#ifdef TEST_PROGRAM
+static int test_listitems(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ struct fdisk_context *cxt;
+ struct fdisk_labelitem *item;
+ int i = 0, rc;
+
+ cxt = fdisk_new_context();
+ item = fdisk_new_labelitem();
+
+ fdisk_assign_device(cxt, disk, 1);
+
+ do {
+ rc = fdisk_get_disklabel_item(cxt, i++, item);
+ switch (rc) {
+ case 0: /* success */
+ {
+ const char *name = fdisk_labelitem_get_name(item);
+ const char *str;
+ uint64_t num;
+
+ if (fdisk_labelitem_is_string(item)
+ && fdisk_labelitem_get_data_string(item, &str) == 0)
+ printf("%s: %s\n", name, str);
+ else if (fdisk_labelitem_get_data_u64(item, &num) == 0)
+ printf("%s: %"PRIu64"\n", name, num);
+ break;
+ }
+ case 1: /* item unsupported by label -- ignore */
+ rc = 0;
+ break;
+ case 2: /* end (out of range) */
+ break;
+ default: /* error */
+ break;
+ }
+ } while (rc == 0);
+
+ fdisk_unref_labelitem(item);
+ fdisk_unref_context(cxt);
+ return rc < 0 ? rc : 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--list-items", test_listitems, "<disk> list items" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/iter.c b/libfdisk/src/iter.c
new file mode 100644
index 0000000..aca5f7c
--- /dev/null
+++ b/libfdisk/src/iter.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: iter
+ * @title: Iterator
+ * @short_description: unified iterator
+ *
+ * The iterator keeps the direction and the last position for access to the
+ * internal library tables/lists.
+ *
+ * It's very unusual to use the same iterator on multiple places in your
+ * application or share the same iterator, for this purpose libfdisk does not
+ * provide reference counting for this object. It's recommended to initialize
+ * the iterator by fdisk_new_iter() at begin of your function and then
+ * fdisk_free_iter() before you return from the function.
+ *
+ * Don't forget to call fdisk_reset_iter() if you want to use the iterator more
+ * than once.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fdiskP.h"
+
+/**
+ * fdisk_new_iter:
+ * @direction: FDISK_INTER_{FOR,BACK}WARD direction
+ *
+ * Returns: newly allocated generic libmount iterator.
+ */
+struct fdisk_iter *fdisk_new_iter(int direction)
+{
+ struct fdisk_iter *itr = calloc(1, sizeof(*itr));
+ if (!itr)
+ return NULL;
+ itr->direction = direction;
+ return itr;
+}
+
+/**
+ * fdisk_free_iter:
+ * @itr: iterator pointer
+ *
+ * Deallocates the iterator.
+ */
+void fdisk_free_iter(struct fdisk_iter *itr)
+{
+ free(itr);
+}
+
+/**
+ * fdisk_reset_iter:
+ * @itr: iterator pointer
+ * @direction: FDISK_ITER_{FOR,BACK}WARD or -1 to keep the direction unchanged
+ *
+ * Resets the iterator.
+ */
+void fdisk_reset_iter(struct fdisk_iter *itr, int direction)
+{
+ if (direction == -1)
+ direction = itr->direction;
+
+ memset(itr, 0, sizeof(*itr));
+ itr->direction = direction;
+}
+
+/**
+ * fdisk_iter_get_direction:
+ * @itr: iterator pointer
+ *
+ * Returns: FDISK_INTER_{FOR,BACK}WARD
+ */
+int fdisk_iter_get_direction(struct fdisk_iter *itr)
+{
+ return itr->direction;
+}
diff --git a/libfdisk/src/label.c b/libfdisk/src/label.c
new file mode 100644
index 0000000..dc0ad9b
--- /dev/null
+++ b/libfdisk/src/label.c
@@ -0,0 +1,752 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: label
+ * @title: Label
+ * @short_description: disk label (PT) specific data and functions
+ *
+ * The fdisk_new_context() initializes all label drivers, and allocate
+ * per-label specific data struct. This concept can be used to store label specific
+ * settings to the label driver independently on the currently active label
+ * driver. Note that label struct cannot be deallocated, so there is no
+ * reference counting for fdisk_label objects. All is destroyed by
+ * fdisk_unref_context() only.
+ *
+ * Anyway, all label drives share in-memory first sector. The function
+ * fdisk_create_disklabel() overwrites this in-memory sector. But it's possible that
+ * label driver also uses another buffers, for example GPT reads more sectors
+ * from the device.
+ *
+ * All label operations are in-memory only, except fdisk_write_disklabel().
+ *
+ * All functions that use "struct fdisk_context" rather than "struct
+ * fdisk_label" use the currently active label driver.
+ */
+
+
+int fdisk_probe_labels(struct fdisk_context *cxt)
+{
+ size_t i;
+
+ cxt->label = NULL;
+
+ for (i = 0; i < cxt->nlabels; i++) {
+ struct fdisk_label *lb = cxt->labels[i];
+ struct fdisk_label *org = fdisk_get_label(cxt, NULL);
+ int rc;
+
+ if (!lb->op->probe)
+ continue;
+ if (lb->disabled) {
+ DBG(CXT, ul_debugobj(cxt, "%s: disabled -- ignore", lb->name));
+ continue;
+ }
+ DBG(CXT, ul_debugobj(cxt, "probing for %s", lb->name));
+
+ cxt->label = lb;
+ rc = lb->op->probe(cxt);
+ cxt->label = org;
+
+ if (rc != 1) {
+ if (lb->op->deinit)
+ lb->op->deinit(lb); /* for sure */
+ continue;
+ }
+
+ __fdisk_switch_label(cxt, lb);
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "no label found"));
+ return 1; /* not found */
+}
+
+/**
+ * fdisk_label_get_name:
+ * @lb: label
+ *
+ * Returns: label name
+ */
+const char *fdisk_label_get_name(const struct fdisk_label *lb)
+{
+ return lb ? lb->name : NULL;
+}
+
+/**
+ * fdisk_label_is_labeltype:
+ * @lb: label
+ *
+ * Returns: FDISK_DISKLABEL_*.
+ */
+int fdisk_label_get_type(const struct fdisk_label *lb)
+{
+ return lb->id;
+}
+
+/**
+ * fdisk_label_require_geometry:
+ * @lb: label
+ *
+ * Returns: 1 if label requires CHS geometry
+ */
+int fdisk_label_require_geometry(const struct fdisk_label *lb)
+{
+ assert(lb);
+
+ return lb->flags & FDISK_LABEL_FL_REQUIRE_GEOMETRY ? 1 : 0;
+}
+
+/**
+ * fdisk_label_get_fields_ids
+ * @lb: label (or NULL for the current label)
+ * @cxt: context
+ * @ids: returns allocated array with FDISK_FIELD_* IDs
+ * @nids: returns number of items in fields
+ *
+ * This function returns the default fields for the label.
+ *
+ * Note that the set of the default fields depends on fdisk_enable_details()
+ * function. If the details are enabled then this function usually returns more
+ * fields.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_fields_ids(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids)
+{
+ size_t i, n;
+ int *c;
+
+ if (!cxt || (!lb && !cxt->label))
+ return -EINVAL;
+
+ lb = cxt->label;
+ if (!lb->fields || !lb->nfields)
+ return -ENOSYS;
+ c = calloc(lb->nfields, sizeof(int));
+ if (!c)
+ return -ENOMEM;
+ for (n = 0, i = 0; i < lb->nfields; i++) {
+ int id = lb->fields[i].id;
+
+ if ((fdisk_is_details(cxt) &&
+ (lb->fields[i].flags & FDISK_FIELDFL_EYECANDY))
+ || (!fdisk_is_details(cxt) &&
+ (lb->fields[i].flags & FDISK_FIELDFL_DETAIL))
+ || (id == FDISK_FIELD_SECTORS &&
+ fdisk_use_cylinders(cxt))
+ || (id == FDISK_FIELD_CYLINDERS &&
+ !fdisk_use_cylinders(cxt)))
+ continue;
+
+ c[n++] = id;
+ }
+ if (ids)
+ *ids = c;
+ else
+ free(c);
+ if (nids)
+ *nids = n;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_fields_ids_all
+ * @lb: label (or NULL for the current label)
+ * @cxt: context
+ * @ids: returns allocated array with FDISK_FIELD_* IDs
+ * @nids: returns number of items in fields
+ *
+ * This function returns all fields for the label.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_fields_ids_all(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids)
+{
+ size_t i, n;
+ int *c;
+
+ if (!cxt || (!lb && !cxt->label))
+ return -EINVAL;
+
+ lb = cxt->label;
+ if (!lb->fields || !lb->nfields)
+ return -ENOSYS;
+ c = calloc(lb->nfields, sizeof(int));
+ if (!c)
+ return -ENOMEM;
+ for (n = 0, i = 0; i < lb->nfields; i++)
+ c[n++] = lb->fields[i].id;
+ if (ids)
+ *ids = c;
+ else
+ free(c);
+ if (nids)
+ *nids = n;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_field:
+ * @lb: label
+ * @id: FDISK_FIELD_*
+ *
+ * The field struct describes data stored in struct fdisk_partition. The info
+ * about data is usable for example to generate human readable output (e.g.
+ * fdisk 'p'rint command). See fdisk_partition_to_string() and fdisk code.
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id)
+{
+ size_t i;
+
+ assert(lb);
+ assert(id > 0);
+
+ for (i = 0; i < lb->nfields; i++) {
+ if (lb->fields[i].id == id)
+ return &lb->fields[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_label_get_field_by_name
+ * @lb: label
+ * @name: field name
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field_by_name(
+ const struct fdisk_label *lb,
+ const char *name)
+{
+ size_t i;
+
+ assert(lb);
+ assert(name);
+
+ for (i = 0; i < lb->nfields; i++) {
+ if (lb->fields[i].name && strcasecmp(lb->fields[i].name, name) == 0)
+ return &lb->fields[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_write_disklabel:
+ * @cxt: fdisk context
+ *
+ * This function wipes the device (if enabled by fdisk_enable_wipe()) and then
+ * it writes in-memory changes to disk. Be careful!
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_write_disklabel(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label || cxt->readonly)
+ return -EINVAL;
+ if (!cxt->label->op->write)
+ return -ENOSYS;
+
+ fdisk_do_wipe(cxt);
+ return cxt->label->op->write(cxt);
+}
+
+/**
+ * fdisk_verify_disklabel:
+ * @cxt: fdisk context
+ *
+ * Verifies the partition table.
+ *
+ * Returns: 0 on success, <1 runtime or option errors, >0 number of detected issues
+ */
+int fdisk_verify_disklabel(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->verify)
+ return -ENOSYS;
+ if (fdisk_missing_geometry(cxt))
+ return -EINVAL;
+
+ return cxt->label->op->verify(cxt);
+}
+
+/**
+ * fdisk_list_disklabel:
+ * @cxt: fdisk context
+ *
+ * Lists details about disklabel, but no partitions.
+ *
+ * This function is based on fdisk_get_disklabel_item() and prints all label
+ * specific information by ASK interface (FDISK_ASKTYPE_INFO, aka fdisk_info()).
+ * The function requires enabled "details" by fdisk_enable_details().
+ *
+ * It's recommended to use fdisk_get_disklabel_item() if you need better
+ * control on output and formatting.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_list_disklabel(struct fdisk_context *cxt)
+{
+ int id = 0, rc = 0;
+ struct fdisk_labelitem item = { .id = id };
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+
+ if (!cxt->display_details)
+ return 0;
+
+ /* List all label items */
+ do {
+ /* rc: < 0 error, 0 success, 1 unknown item, 2 out of range */
+ rc = fdisk_get_disklabel_item(cxt, id++, &item);
+ if (rc != 0)
+ continue;
+ switch (item.type) {
+ case 'j':
+ fdisk_info(cxt, "%s: %ju", item.name, item.data.num64);
+ break;
+ case 's':
+ if (item.data.str && item.name)
+ fdisk_info(cxt, "%s: %s", item.name, item.data.str);
+ break;
+ }
+ fdisk_reset_labelitem(&item);
+ } while (rc == 0 || rc == 1);
+
+ return rc < 0 ? rc : 0;
+}
+
+/**
+ * fdisk_create_disklabel:
+ * @cxt: fdisk context
+ * @name: label name
+ *
+ * Creates a new disk label of type @name. If @name is NULL, then it will
+ * create a default system label type, either SUN or DOS. The function
+ * automatically switches the current label driver to @name. The function
+ * fdisk_get_label() returns the current label driver.
+ *
+ * The function modifies in-memory data only.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name)
+{
+ int haslabel = 0;
+ struct fdisk_label *lb;
+
+ if (!cxt)
+ return -EINVAL;
+
+ if (!name) { /* use default label creation */
+#ifdef __sparc__
+ name = "sun";
+#else
+ name = "dos";
+#endif
+ }
+
+ if (cxt->label) {
+ fdisk_deinit_label(cxt->label);
+ haslabel = 1;
+ }
+
+ lb = fdisk_get_label(cxt, name);
+ if (!lb || lb->disabled)
+ return -EINVAL;
+
+ if (!haslabel || (lb && cxt->label != lb))
+ fdisk_check_collisions(cxt);
+
+ if (!lb->op->create)
+ return -ENOSYS;
+
+ __fdisk_switch_label(cxt, lb);
+ assert(cxt->label == lb);
+
+ if (haslabel && !cxt->parent)
+ fdisk_reset_device_properties(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "create a new %s label", lb->name));
+ return lb->op->create(cxt);
+}
+
+/**
+ * fdisk_locate_disklabel:
+ * @cxt: context
+ * @n: N item
+ * @name: return item name
+ * @offset: return offset where is item
+ * @size: of the item
+ *
+ * Locate disklabel and returns info about @n item of the label.
+ *
+ * For example GPT is composed from three items, PMBR and GPT, n=0 return
+ * offset to PMBR and n=1 return offset to GPT Header and n=2 returns offset to
+ * GPT array of partitions, n=3 and n=4 returns location of the backup GPT
+ * label at the end of the disk.
+ *
+ * The function returns the current in-memory situation. It's possible that a
+ * header location is modified by write operation, for example when enabled
+ * minimization (see fdisk_gpt_enable_minimize()). In this case it's better to
+ * call this function after fdisk_write_disklabel().
+ *
+ * For more details see 'D' expert fdisk command.
+ *
+ * Returns: 0 on success, <0 on error, 1 no more items.
+ */
+int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name,
+ uint64_t *offset, size_t *size)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->locate)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "locating %d chunk of %s.", n, cxt->label->name));
+ return cxt->label->op->locate(cxt, n, name, offset, size);
+}
+
+
+/**
+ * fdisk_get_disklabel_id:
+ * @cxt: fdisk context
+ * @id: returns pointer to allocated string (MBR Id or GPT dirk UUID)
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id)
+{
+ struct fdisk_labelitem item = FDISK_LABELITEM_INIT;
+ int rc;
+
+ if (!cxt || !cxt->label || !id)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "asking for disk %s ID", cxt->label->name));
+
+ rc = fdisk_get_disklabel_item(cxt, FDISK_LABELITEM_ID, &item);
+ if (rc == 0) {
+ *id = item.data.str;
+ item.data.str = NULL;
+ }
+ fdisk_reset_labelitem(&item);
+ if (rc > 0)
+ rc = 0;
+ return rc;
+}
+
+/**
+ * fdisk_get_disklabel_item:
+ * @cxt: fdisk context
+ * @id: item ID (FDISK_LABELITEM_* or *_LABELITEM_*)
+ * @item: specifies and returns the item
+ *
+ * Note that @id is always in range 0..N. It's fine to use the function in loop
+ * until it returns error or 2, the result in @item should be ignored when
+ * function returns 1. Don't forget to use fdisk_reset_labelitem() or fdisk_unref_labelitem().
+ *
+ * Returns: 0 on success, < 0 on error, 1 on unsupported item, 2 id out of range
+ */
+int fdisk_get_disklabel_item(struct fdisk_context *cxt, int id, struct fdisk_labelitem *item)
+{
+ if (!cxt || !cxt->label || !item)
+ return -EINVAL;
+
+ fdisk_reset_labelitem(item);
+ item->id = id;
+ DBG(CXT, ul_debugobj(cxt, "asking for disk %s item %d", cxt->label->name, item->id));
+
+ if (!cxt->label->op->get_item)
+ return -ENOSYS;
+
+ return cxt->label->op->get_item(cxt, item);
+}
+
+/**
+ * fdisk_set_disklabel_id:
+ * @cxt: fdisk context
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_set_disklabel_id(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->set_id)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "setting %s disk ID", cxt->label->name));
+ return cxt->label->op->set_id(cxt, NULL);
+}
+
+/**
+ * fdisk_set_disklabel_id_from_string
+ * @cxt: fdisk context
+ * @str: new Id
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ *
+ * Since: 2.36
+ */
+int fdisk_set_disklabel_id_from_string(struct fdisk_context *cxt, const char *str)
+{
+ if (!cxt || !cxt->label || !str)
+ return -EINVAL;
+ if (!cxt->label->op->set_id)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "setting %s disk ID from '%s'", cxt->label->name, str));
+ return cxt->label->op->set_id(cxt, str);
+}
+
+/**
+ * fdisk_set_partition_type:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @t: new type
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_partition_type(struct fdisk_context *cxt,
+ size_t partnum,
+ struct fdisk_parttype *t)
+{
+ if (!cxt || !cxt->label || !t)
+ return -EINVAL;
+
+
+ if (cxt->label->op->set_part) {
+ struct fdisk_partition *pa = fdisk_new_partition();
+ int rc;
+
+ if (!pa)
+ return -ENOMEM;
+ fdisk_partition_set_type(pa, t);
+
+ DBG(CXT, ul_debugobj(cxt, "partition: %zd: set type", partnum));
+ rc = cxt->label->op->set_part(cxt, partnum, pa);
+ fdisk_unref_partition(pa);
+ return rc;
+ }
+
+ return -ENOSYS;
+}
+
+
+/**
+ * fdisk_toggle_partition_flag:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @flag: flag ID
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_toggle_partition_flag(struct fdisk_context *cxt,
+ size_t partnum,
+ unsigned long flag)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->part_toggle_flag)
+ return -ENOSYS;
+
+ rc = cxt->label->op->part_toggle_flag(cxt, partnum, flag);
+
+ DBG(CXT, ul_debugobj(cxt, "partition: %zd: toggle: 0x%04lx [rc=%d]", partnum, flag, rc));
+ return rc;
+}
+
+/**
+ * fdisk_reorder_partitions
+ * @cxt: fdisk context
+ *
+ * Sort partitions according to the partition start sector.
+ *
+ * Returns: 0 on success, 1 reorder unnecessary, otherwise a corresponding error.
+ */
+int fdisk_reorder_partitions(struct fdisk_context *cxt)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->reorder)
+ return -ENOSYS;
+
+ rc = cxt->label->op->reorder(cxt);
+
+ switch (rc) {
+ case 0:
+ fdisk_info(cxt, _("Partitions order fixed."));
+ break;
+ case 1:
+ fdisk_info(cxt, _("Nothing to do. Ordering is correct already."));
+ break;
+ default:
+ fdisk_warnx(cxt, _("Failed to fix partitions order."));
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Resets the current used label driver to initial state
+ */
+void fdisk_deinit_label(struct fdisk_label *lb)
+{
+ assert(lb);
+
+ /* private label information */
+ if (lb->op->deinit)
+ lb->op->deinit(lb);
+}
+
+/**
+ * fdisk_label_set_changed:
+ * @lb: label
+ * @changed: 0/1
+ *
+ * Marks in-memory data as changed, to force fdisk_write_disklabel() to write
+ * to device. This should be unnecessary by default, the library keeps track
+ * about changes.
+ */
+void fdisk_label_set_changed(struct fdisk_label *lb, int changed)
+{
+ assert(lb);
+ lb->changed = changed ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_changed:
+ * @lb: label
+ *
+ * Returns: 1 if in-memory data has been changed.
+ */
+int fdisk_label_is_changed(const struct fdisk_label *lb)
+{
+ return lb ? lb->changed : 0;
+}
+
+/**
+ * fdisk_label_set_disabled:
+ * @lb: label
+ * @disabled: 0 or 1
+ *
+ * Mark label as disabled, then libfdisk is going to ignore the label when
+ * probe device for labels.
+ */
+void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled)
+{
+ assert(lb);
+
+ DBG(LABEL, ul_debug("%s label %s",
+ lb->name,
+ disabled ? "DISABLED" : "ENABLED"));
+ lb->disabled = disabled ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_disabled:
+ * @lb: label
+ *
+ * Returns: 1 if label driver disabled.
+ */
+int fdisk_label_is_disabled(const struct fdisk_label *lb)
+{
+ assert(lb);
+ return lb ? lb->disabled : 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_sectors:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_sectors(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma)
+{
+ if (!lb || lb->geom_min.sectors == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.sectors;
+ if (ma)
+ *ma = lb->geom_max.sectors;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_heads:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_heads(const struct fdisk_label *lb,
+ unsigned int *mi, unsigned int *ma)
+{
+ if (!lb || lb->geom_min.heads == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.heads;
+ if (ma)
+ *ma = lb->geom_max.heads;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_cylinders:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_cylinders(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma)
+{
+ if (!lb || lb->geom_min.cylinders == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.cylinders;
+ if (ma)
+ *ma = lb->geom_max.cylinders;
+ return 0;
+}
+
diff --git a/libfdisk/src/libfdisk.h.in b/libfdisk/src/libfdisk.h.in
new file mode 100644
index 0000000..1c97149
--- /dev/null
+++ b/libfdisk/src/libfdisk.h.in
@@ -0,0 +1,895 @@
+/*
+ * libfdisk.h - libfdisk API
+ *
+ * Copyright (C) 2012-2014 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 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.
+ */
+
+#ifndef _LIBFDISK_H
+#define _LIBFDISK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * LIBFDISK_VERSION:
+ *
+ * Library version string
+ */
+#define LIBFDISK_VERSION "@LIBFDISK_VERSION@"
+
+#define LIBFDISK_MAJOR_VERSION @LIBFDISK_MAJOR_VERSION@
+#define LIBFDISK_MINOR_VERSION @LIBFDISK_MINOR_VERSION@
+#define LIBFDISK_PATCH_VERSION @LIBFDISK_PATCH_VERSION@
+
+/**
+ * fdisk_context:
+ *
+ * Basic library handler.
+ */
+struct fdisk_context;
+
+/**
+ * fdisk_label:
+ *
+ * Disk label specific driver and setting.
+ */
+struct fdisk_label;
+
+/**
+ * fdisk_parttype:
+ *
+ * Partition type.
+ */
+struct fdisk_parttype;
+
+/**
+ * fdisk_partition:
+ *
+ * Partition abstraction (and template).
+ */
+struct fdisk_partition;
+
+/**
+ * fdisk_ask:
+ *
+ * Ask API handler for dialogs with users.
+ */
+struct fdisk_ask;
+
+/**
+ * fdisk_iter:
+ *
+ * Unified iterator.
+ */
+struct fdisk_iter;
+
+/**
+ * fdisk_table:
+ *
+ * Container for fdisk_partition objects
+ */
+struct fdisk_table;
+
+/**
+ * fdisk_field
+ *
+ * Output field description.
+ */
+struct fdisk_field;
+
+/**
+ * fdisk_script
+ *
+ * library handler for sfdisk compatible scripts and dumps
+ */
+struct fdisk_script;
+
+/**
+ * fdisk_sector_t
+ *
+ * LBA addresses type
+ */
+typedef uint64_t fdisk_sector_t;
+
+/**
+ * fdisk_labeltype:
+ * @FDISK_DISKLABEL_DOS: MBR label type
+ * @FDISK_DISKLABEL_SUN: SUN label type
+ * @FDISK_DISKLABEL_SGI: SGI label type
+ * @FDISK_DISKLABEL_BSD: BSD label type
+ * @FDISK_DISKLABEL_GPT: UEFI GPT type
+ *
+ * Supported partition table types (labels)
+ */
+enum fdisk_labeltype {
+ FDISK_DISKLABEL_DOS = (1 << 1), /* MBR label type */
+ FDISK_DISKLABEL_SUN = (1 << 2), /* SUN label type */
+ FDISK_DISKLABEL_SGI = (1 << 3), /* SGI label type */
+ FDISK_DISKLABEL_BSD = (1 << 4), /* BSD label t ype */
+ FDISK_DISKLABEL_GPT = (1 << 5) /* UEFI GPT type */
+};
+
+/**
+ * fdisk_labelitem:
+ *
+ * library handler for label specific information. See
+ * generic FDISK_LABELITEM_* and label specific {GPT,MBR,..}_LABELITEM_*.
+ */
+struct fdisk_labelitem;
+
+/**
+ * fdisk_asktype:
+ * @FDISK_ASKTYPE_NONE: undefined type
+ * @FDISK_ASKTYPE_NUMBER: ask for number
+ * @FDISK_ASKTYPE_OFFSET: ask for offset
+ * @FDISK_ASKTYPE_WARN: print warning message and errno
+ * @FDISK_ASKTYPE_WARNX: print warning message
+ * @FDISK_ASKTYPE_INFO: print info message
+ * @FDISK_ASKTYPE_YESNO: ask Yes/No question
+ * @FDISK_ASKTYPE_STRING: ask for string
+ * @FDISK_ASKTYPE_MENU: ask for menu item
+ *
+ * Ask API dialog types
+ */
+enum fdisk_asktype {
+ FDISK_ASKTYPE_NONE = 0,
+ FDISK_ASKTYPE_NUMBER,
+ FDISK_ASKTYPE_OFFSET,
+ FDISK_ASKTYPE_WARN,
+ FDISK_ASKTYPE_WARNX,
+ FDISK_ASKTYPE_INFO,
+ FDISK_ASKTYPE_YESNO,
+ FDISK_ASKTYPE_STRING,
+ FDISK_ASKTYPE_MENU
+};
+
+/* init.c */
+extern void fdisk_init_debug(int mask);
+
+/* version.c */
+extern int fdisk_parse_version_string(const char *ver_string);
+extern int fdisk_get_library_version(const char **ver_string);
+extern int fdisk_get_library_features(const char ***features);
+
+/* context.h */
+
+#define FDISK_PLURAL 0
+#define FDISK_SINGULAR 1
+
+struct fdisk_context *fdisk_new_context(void);
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name);
+void fdisk_unref_context(struct fdisk_context *cxt);
+void fdisk_ref_context(struct fdisk_context *cxt);
+
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt);
+size_t fdisk_get_npartitions(struct fdisk_context *cxt);
+
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name);
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb);
+size_t fdisk_get_nlabels(struct fdisk_context *cxt);
+
+int fdisk_has_label(struct fdisk_context *cxt);
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id);
+#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x)
+
+
+int fdisk_assign_device(struct fdisk_context *cxt,
+ const char *fname, int readonly);
+int fdisk_assign_device_by_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly);
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync);
+int fdisk_reassign_device(struct fdisk_context *cxt);
+
+int fdisk_is_readonly(struct fdisk_context *cxt);
+int fdisk_is_regfile(struct fdisk_context *cxt);
+int fdisk_device_is_used(struct fdisk_context *cxt);
+
+int fdisk_disable_dialogs(struct fdisk_context *cxt, int disable);
+int fdisk_has_dialogs(struct fdisk_context *cxt);
+
+int fdisk_enable_details(struct fdisk_context *cxt, int enable);
+int fdisk_is_details(struct fdisk_context *cxt);
+
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable);
+int fdisk_is_listonly(struct fdisk_context *cxt);
+
+int fdisk_enable_wipe(struct fdisk_context *cxt, int enable);
+int fdisk_has_wipe(struct fdisk_context *cxt);
+const char *fdisk_get_collision(struct fdisk_context *cxt);
+int fdisk_is_ptcollision(struct fdisk_context *cxt);
+
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str);
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n);
+int fdisk_use_cylinders(struct fdisk_context *cxt);
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt);
+
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt);
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt);
+
+const char *fdisk_get_devname(struct fdisk_context *cxt);
+int fdisk_get_devfd(struct fdisk_context *cxt);
+dev_t fdisk_get_devno(struct fdisk_context *cxt);
+const char *fdisk_get_devmodel(struct fdisk_context *cxt);
+
+
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt);
+
+enum {
+ FDISK_SIZEUNIT_HUMAN = 0, /* default, human readable {M,G,P,...} */
+ FDISK_SIZEUNIT_BYTES /* bytes */
+};
+int fdisk_set_size_unit(struct fdisk_context *cxt, int unit);
+int fdisk_get_size_unit(struct fdisk_context *cxt);
+
+int fdisk_has_protected_bootbits(struct fdisk_context *cxt);
+int fdisk_enable_bootbits_protection(struct fdisk_context *cxt, int enable);
+
+/* parttype.c */
+struct fdisk_parttype *fdisk_new_parttype(void);
+void fdisk_ref_parttype(struct fdisk_parttype *t);
+void fdisk_unref_parttype(struct fdisk_parttype *t);
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code);
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n);
+int fdisk_label_get_parttype_shortcut(
+ const struct fdisk_label *lb, size_t n,
+ const char **typestr,
+ const char **shortcut,
+ const char **alias);
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb);
+int fdisk_label_has_parttypes_shortcuts(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+ const struct fdisk_label *lb,
+ unsigned int code);
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+ const struct fdisk_label *lb,
+ const char *str);
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+ const char *typestr);
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type);
+struct fdisk_parttype *fdisk_label_parse_parttype(
+ const struct fdisk_label *lb,
+ const char *str);
+struct fdisk_parttype *fdisk_label_advparse_parttype(
+ const struct fdisk_label *lb,
+ const char *str,
+ int flags);
+
+/**
+ * fdisk_parttype_parser_flags:
+ * @FDISK_PARTTYPE_PARSE_DATA: parse hex or UUID from string
+ * @FDISK_PARTTYPE_PARSE_DATALAST: try hex or UUID as the last possibility (don't use!)
+ * @FDISK_PARTTYPE_PARSE_SHORTCUT: try input as type shortcut (e.g 'L' for linux partition)
+ * @FDISK_PARTTYPE_PARSE_ALIAS: try input as type alias (e.g. 'linux' for linux partition)
+ * @FDISK_PARTTYPE_PARSE_DEPRECATED: accept also deprecated aliases and shortcuts
+ * @FDISK_PARTTYPE_PARSE_DEFAULT: recommended flags for new code
+ * @FDISK_PARTTYPE_PARSE_NOUNKNOWN: ignore unknown types
+ * @FDISK_PARTTYPE_PARSE_SEQNUM: use input as sequntial number of type (e.g. list-types fdisk dialog)
+ * @FDISK_PARTTYPE_PARSE_NAME: parse type human readable name
+ */
+enum fdisk_parttype_parser_flags {
+ FDISK_PARTTYPE_PARSE_DATA = (1 << 1),
+ FDISK_PARTTYPE_PARSE_DATALAST = (1 << 2),
+ FDISK_PARTTYPE_PARSE_SHORTCUT = (1 << 3),
+ FDISK_PARTTYPE_PARSE_ALIAS = (1 << 4),
+ FDISK_PARTTYPE_PARSE_DEPRECATED = (1 << 5),
+ FDISK_PARTTYPE_PARSE_NOUNKNOWN = (1 << 6),
+ FDISK_PARTTYPE_PARSE_SEQNUM = (1 << 7),
+ FDISK_PARTTYPE_PARSE_NAME = (1 << 8),
+
+ FDISK_PARTTYPE_PARSE_DEFAULT = (FDISK_PARTTYPE_PARSE_DATA | \
+ FDISK_PARTTYPE_PARSE_SHORTCUT | \
+ FDISK_PARTTYPE_PARSE_ALIAS | \
+ FDISK_PARTTYPE_PARSE_NAME | \
+ FDISK_PARTTYPE_PARSE_SEQNUM )
+};
+
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t);
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t);
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t);
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t);
+
+
+/* field.c */
+extern int fdisk_field_get_id(const struct fdisk_field *field);
+extern const char *fdisk_field_get_name(const struct fdisk_field *field);
+extern double fdisk_field_get_width(const struct fdisk_field *field);
+extern int fdisk_field_is_number(const struct fdisk_field *field);
+
+
+/* label.c */
+
+/**
+ * fdisk_fieldtype:
+ * @FDISK_FIELD_NONE: unspecified item
+ * @FDISK_FIELD_DEVICE: partition device name
+ * @FDISK_FIELD_START: start offset of the partition
+ * @FDISK_FIELD_END: end offset of the partition
+ * @FDISK_FIELD_SECTORS: number of sectors
+ * @FDISK_FIELD_CYLINDERS: number of cylinders (deprecated)
+ * @FDISK_FIELD_SIZE: partition size
+ * @FDISK_FIELD_TYPE: partition type
+ * @FDISK_FIELD_TYPEID: partition type ID
+ * @FDISK_FIELD_ATTR: partition attribute (GPT)
+ * @FDISK_FIELD_BOOT: partition boot flag
+ * @FDISK_FIELD_BSIZE: size of the boot area (BSD)
+ * @FDISK_FIELD_CPG: BSD
+ * @FDISK_FIELD_EADDR: End-C/H/S (MBR)
+ * @FDISK_FIELD_FSIZE: BSD
+ * @FDISK_FIELD_NAME: partition label/name
+ * @FDISK_FIELD_SADDR: Start-C/H/S (MBR)
+ * @FDISK_FIELD_UUID: partition UUID (GPT)
+ * @FDISK_FIELD_FSUUID: Filesystem UUID
+ * @FDISK_FIELD_FSLABEL: Filesystem LABEL
+ * @FDISK_FIELD_FSTYPE: Filesystem type
+ * @FDISK_NFIELDS: Don't use, counter.
+ *
+ * Types of fdisk_field. The fields describe a partition.
+ */
+enum fdisk_fieldtype {
+ FDISK_FIELD_NONE = 0,
+
+ /* generic */
+ FDISK_FIELD_DEVICE,
+ FDISK_FIELD_START,
+ FDISK_FIELD_END,
+ FDISK_FIELD_SECTORS,
+ FDISK_FIELD_CYLINDERS,
+ FDISK_FIELD_SIZE,
+ FDISK_FIELD_TYPE,
+ FDISK_FIELD_TYPEID,
+
+ /* label specific */
+ FDISK_FIELD_ATTR,
+ FDISK_FIELD_BOOT,
+ FDISK_FIELD_BSIZE,
+ FDISK_FIELD_CPG,
+ FDISK_FIELD_EADDR,
+ FDISK_FIELD_FSIZE,
+ FDISK_FIELD_NAME,
+ FDISK_FIELD_SADDR,
+ FDISK_FIELD_UUID,
+
+ FDISK_FIELD_FSUUID,
+ FDISK_FIELD_FSLABEL,
+ FDISK_FIELD_FSTYPE,
+
+ FDISK_NFIELDS /* must be last */
+};
+
+int fdisk_label_get_type(const struct fdisk_label *lb);
+const char *fdisk_label_get_name(const struct fdisk_label *lb);
+int fdisk_label_require_geometry(const struct fdisk_label *lb);
+
+
+extern int fdisk_write_disklabel(struct fdisk_context *cxt);
+extern int fdisk_verify_disklabel(struct fdisk_context *cxt);
+extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name);
+extern int fdisk_list_disklabel(struct fdisk_context *cxt);
+extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name,
+ uint64_t *offset,
+ size_t *size);
+
+extern int fdisk_label_get_geomrange_cylinders(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma);
+extern int fdisk_label_get_geomrange_heads(const struct fdisk_label *lb,
+ unsigned int *mi, unsigned int *ma);
+extern int fdisk_label_get_geomrange_sectors(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma);
+
+/**
+ * fdisk_labelitem_gen:
+ * @FDISK_LABELITEM_ID: Unique disk identifier
+ * @__FDISK_NLABELITEMS: Specifies reserved range for generic items (0..7)
+ *
+ * Generic disklabel items.
+ */
+enum fdisk_labelitem_gen {
+ FDISK_LABELITEM_ID = 0,
+ __FDISK_NLABELITEMS = 8
+};
+
+/* item.c */
+extern struct fdisk_labelitem *fdisk_new_labelitem(void);
+extern void fdisk_ref_labelitem(struct fdisk_labelitem *li);
+extern int fdisk_reset_labelitem(struct fdisk_labelitem *li);
+extern void fdisk_unref_labelitem(struct fdisk_labelitem *li);
+extern const char *fdisk_labelitem_get_name(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_get_id(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_get_data_u64(struct fdisk_labelitem *li, uint64_t *data);
+extern int fdisk_labelitem_get_data_string(struct fdisk_labelitem *li, const char **data);
+extern int fdisk_labelitem_is_string(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_is_number(struct fdisk_labelitem *li);
+
+extern int fdisk_get_disklabel_item(struct fdisk_context *cxt, int id, struct fdisk_labelitem *item);
+
+extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id);
+extern int fdisk_set_disklabel_id(struct fdisk_context *cxt);
+extern int fdisk_set_disklabel_id_from_string(struct fdisk_context *cxt, const char *str);
+
+extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa);
+extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa);
+extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno);
+extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno);
+extern int fdisk_delete_all_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_wipe_partition(struct fdisk_context *cxt, size_t partno, int enable);
+
+extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum,
+ struct fdisk_parttype *t);
+
+
+extern int fdisk_label_get_fields_ids(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids);
+
+extern int fdisk_label_get_fields_ids_all(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids);
+
+extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id);
+extern const struct fdisk_field *fdisk_label_get_field_by_name(
+ const struct fdisk_label *lb,
+ const char *name);
+
+extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed);
+extern int fdisk_label_is_changed(const struct fdisk_label *lb);
+
+extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled);
+extern int fdisk_label_is_disabled(const struct fdisk_label *lb);
+
+extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n);
+
+extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag);
+
+extern struct fdisk_partition *fdisk_new_partition(void);
+extern void fdisk_reset_partition(struct fdisk_partition *pa);
+extern void fdisk_ref_partition(struct fdisk_partition *pa);
+extern void fdisk_unref_partition(struct fdisk_partition *pa);
+extern int fdisk_partition_is_freespace(struct fdisk_partition *pa);
+
+int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off);
+int fdisk_partition_unset_start(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa);
+int fdisk_partition_has_start(struct fdisk_partition *pa);
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+ struct fdisk_partition *b);
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable);
+int fdisk_partition_start_is_default(struct fdisk_partition *pa);
+
+int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz);
+int fdisk_partition_unset_size(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa);
+int fdisk_partition_has_size(struct fdisk_partition *pa);
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable);
+
+int fdisk_partition_has_end(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa);
+
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num);
+int fdisk_partition_unset_partno(struct fdisk_partition *pa);
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa);
+int fdisk_partition_has_partno(struct fdisk_partition *pa);
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+ struct fdisk_partition *b);
+
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable);
+
+extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type);
+extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa);
+extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name);
+extern const char *fdisk_partition_get_name(struct fdisk_partition *pa);
+extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid);
+extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs);
+extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa);
+extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa);
+extern int fdisk_partition_is_nested(struct fdisk_partition *pa);
+extern int fdisk_partition_is_container(struct fdisk_partition *pa);
+extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent);
+extern int fdisk_partition_is_used(struct fdisk_partition *pa);
+extern int fdisk_partition_is_bootable(struct fdisk_partition *pa);
+extern int fdisk_partition_is_wholedisk(struct fdisk_partition *pa);
+extern int fdisk_partition_to_string(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ int id, char **data);
+
+int fdisk_partition_next_partno(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ size_t *n);
+
+extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable);
+extern int fdisk_partition_end_is_default(struct fdisk_partition *pa);
+
+extern int fdisk_reorder_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_partition_has_wipe(struct fdisk_context *cxt, struct fdisk_partition *pa);
+
+
+/* table.c */
+extern struct fdisk_table *fdisk_new_table(void);
+extern int fdisk_reset_table(struct fdisk_table *tb);
+extern void fdisk_ref_table(struct fdisk_table *tb);
+extern void fdisk_unref_table(struct fdisk_table *tb);
+extern size_t fdisk_table_get_nents(struct fdisk_table *tb);
+extern int fdisk_table_is_empty(struct fdisk_table *tb);
+extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+
+extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb);
+extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb);
+
+
+extern int fdisk_table_wrong_order(struct fdisk_table *tb);
+extern int fdisk_table_sort_partitions(struct fdisk_table *tb,
+ int (*cmp)(struct fdisk_partition *,
+ struct fdisk_partition *));
+
+extern int fdisk_table_next_partition(
+ struct fdisk_table *tb,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **pa);
+
+extern struct fdisk_partition *fdisk_table_get_partition(
+ struct fdisk_table *tb,
+ size_t n);
+extern struct fdisk_partition *fdisk_table_get_partition_by_partno(
+ struct fdisk_table *tb,
+ size_t partno);
+
+extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb);
+
+/* alignment.c */
+#define FDISK_ALIGN_UP 1
+#define FDISK_ALIGN_DOWN 2
+#define FDISK_ALIGN_NEAREST 3
+
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction);
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+ fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop);
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba);
+
+int fdisk_override_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors);
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors);
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+ unsigned int phy,
+ unsigned int log);
+
+int fdisk_save_user_grain(struct fdisk_context *cxt, unsigned long grain);
+
+int fdisk_has_user_device_properties(struct fdisk_context *cxt);
+int fdisk_reset_alignment(struct fdisk_context *cxt);
+int fdisk_reset_device_properties(struct fdisk_context *cxt);
+int fdisk_reread_partition_table(struct fdisk_context *cxt);
+int fdisk_reread_changes(struct fdisk_context *cxt, struct fdisk_table *org);
+
+/* iter.c */
+enum {
+
+ FDISK_ITER_FORWARD = 0,
+ FDISK_ITER_BACKWARD
+};
+extern struct fdisk_iter *fdisk_new_iter(int direction);
+extern void fdisk_free_iter(struct fdisk_iter *itr);
+extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction);
+extern int fdisk_iter_get_direction(struct fdisk_iter *itr);
+
+
+/* dos.c */
+#define DOS_FLAG_ACTIVE 1
+
+extern int fdisk_dos_fix_chs(struct fdisk_context *cxt);
+extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i);
+extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable);
+extern int fdisk_dos_is_compatible(struct fdisk_label *lb);
+
+/* sun.h */
+extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt);
+extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt);
+extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_sun:
+ * @SUN_LABELITEM_LABELID: Label ID
+ * @SUN_LABELITEM_VTOCID: Volume ID
+ * @SUN_LABELITEM_RPM: Rpm
+ * @SUN_LABELITEM_ACYL: Alternate cylinders
+ * @SUN_LABELITEM_PCYL: Physical cylinders
+ * @SUN_LABELITEM_APC: Extra sects/cyl
+ * @SUN_LABELITEM_INTRLV: Interleave
+ *
+ * SUN specific label items.
+ */
+enum fdisk_labelitem_sun {
+ SUN_LABELITEM_LABELID = __FDISK_NLABELITEMS,
+ SUN_LABELITEM_VTOCID,
+ SUN_LABELITEM_RPM,
+ SUN_LABELITEM_ACYL,
+ SUN_LABELITEM_PCYL,
+ SUN_LABELITEM_APC,
+ SUN_LABELITEM_INTRLV
+};
+
+/* bsd.c */
+extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt);
+extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt);
+extern int fdisk_bsd_link_partition(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_bsd:
+ * @BSD_LABELITEM_TYPE: type
+ * @BSD_LABELITEM_DISK: disk
+ * @BSD_LABELITEM_PACKNAME: packname
+ * @BSD_LABELITEM_FLAGS: flags (removable, ecc, badsect)
+ * @BSD_LABELITEM_SECSIZE: Bytes/Sector
+ * @BSD_LABELITEM_NTRACKS: Tracks/Cylinder
+ * @BSD_LABELITEM_SECPERCYL: Sectors/Cylinder
+ * @BSD_LABELITEM_CYLINDERS: Cylinders
+ * @BSD_LABELITEM_RPM: rpm
+ * @BSD_LABELITEM_INTERLEAVE: interleave
+ * @BSD_LABELITEM_TRACKSKEW: trackskew
+ * @BSD_LABELITEM_CYLINDERSKEW: cylinderskew
+ * @BSD_LABELITEM_HEADSWITCH: headswitch
+ * @BSD_LABELITEM_TRKSEEK: track-to-track seek
+ *
+ * BSD specific label items.
+ */
+enum fdisk_labelitem_bsd {
+ /* specific */
+ BSD_LABELITEM_TYPE = __FDISK_NLABELITEMS,
+ BSD_LABELITEM_DISK,
+ BSD_LABELITEM_PACKNAME,
+ BSD_LABELITEM_FLAGS,
+ BSD_LABELITEM_SECSIZE,
+ BSD_LABELITEM_NTRACKS,
+ BSD_LABELITEM_SECPERCYL,
+ BSD_LABELITEM_CYLINDERS,
+ BSD_LABELITEM_RPM,
+ BSD_LABELITEM_INTERLEAVE,
+ BSD_LABELITEM_TRACKSKEW,
+ BSD_LABELITEM_CYLINDERSKEW,
+ BSD_LABELITEM_HEADSWITCH,
+ BSD_LABELITEM_TRKSEEK
+};
+
+/* sgi.h */
+#define SGI_FLAG_BOOT 1
+#define SGI_FLAG_SWAP 2
+extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt);
+extern int fdisk_sgi_create_info(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_sgi:
+ * @SGI_LABELITEM_PCYLCOUNT: Physical cylinders
+ * @SGI_LABELITEM_SPARECYL: Extra sects/cyl
+ * @SGI_LABELITEM_ILFACT: nterleave
+ * @SGI_LABELITEM_BOOTFILE: Bootfile
+ *
+ * SGI specific label items.
+ */
+enum fdisk_labelitem_sgi {
+ SGI_LABELITEM_PCYLCOUNT = __FDISK_NLABELITEMS,
+ SGI_LABELITEM_SPARECYL,
+ SGI_LABELITEM_ILFACT,
+ SGI_LABELITEM_BOOTFILE
+};
+
+/* gpt */
+
+/*
+ * GPT partition attributes
+ */
+
+/**
+ * GPT_FLAG_REQUIRED:
+ *
+ * GPT attribute; marks a partition as system partition (disk
+ * partitioning utilities must preserve the partition as is)
+ */
+#define GPT_FLAG_REQUIRED 1
+
+/**
+ * GPT_FLAG_NOBLOCK:
+ *
+ * GPT attribute; EFI firmware should ignore the content of the
+ * partition and not try to read from it
+ */
+#define GPT_FLAG_NOBLOCK 2
+
+/**
+ * GPT_FLAG_LEGACYBOOT:
+ *
+ * GPT attribute; use the partition for legacy boot method
+ */
+#define GPT_FLAG_LEGACYBOOT 3
+
+/**
+ * GPT_FLAG_GUIDSPECIFIC:
+ *
+ * GPT attribute; for bites 48-63, defined and used by the individual partition
+ * type.
+ *
+ * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API)
+ * for a bit number. If you want to toggle specific bit and avoid any
+ * dialog, then use the bit number (in range 48..63). For example:
+ *
+ * // start dialog to ask for bit number
+ * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC);
+ *
+ * // toggle bit 60
+ * fdisk_toggle_partition_flag(cxt, n, 60);
+ */
+#define GPT_FLAG_GUIDSPECIFIC 4
+
+extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt);
+extern int fdisk_gpt_set_npartitions(struct fdisk_context *cxt, uint32_t nents);
+extern int fdisk_gpt_get_partition_attrs(struct fdisk_context *cxt, size_t partnum, uint64_t *attrs);
+extern int fdisk_gpt_set_partition_attrs(struct fdisk_context *cxt, size_t partnum, uint64_t attrs);
+
+extern void fdisk_gpt_disable_relocation(struct fdisk_label *lb, int disable);
+extern void fdisk_gpt_enable_minimize(struct fdisk_label *lb, int enable);
+
+/**
+ * fdisk_labelitem_gpt:
+ * @GPT_LABELITEM_ID: GPT disklabel UUID (!= partition UUID)
+ * @GPT_LABELITEM_FIRSTLBA: First Usable LBA
+ * @GPT_LABELITEM_LASTLBA: Last Usable LBA
+ * @GPT_LABELITEM_ALTLBA: Alternative LBA (backup header LBA)
+ * @GPT_LABELITEM_ENTRIESLBA: Partitions entries array LBA
+ * @GPT_LABELITEM_ENTRIESALLOC: Number of allocated entries in entries array
+ * @GPT_LABELITEM_ENTRIESLASTLBA: Last LBA where is entries array
+ *
+ * GPT specific label items.
+ */
+enum fdisk_labelitem_gpt {
+ /* generic */
+ GPT_LABELITEM_ID = FDISK_LABELITEM_ID,
+ /* specific */
+ GPT_LABELITEM_FIRSTLBA = __FDISK_NLABELITEMS,
+ GPT_LABELITEM_LASTLBA,
+ GPT_LABELITEM_ALTLBA,
+ GPT_LABELITEM_ENTRIESLBA,
+ GPT_LABELITEM_ENTRIESALLOC,
+ GPT_LABELITEM_ENTRIESLASTLBA
+};
+
+/* script.c */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt);
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+ const char *filename);
+void fdisk_ref_script(struct fdisk_script *dp);
+void fdisk_unref_script(struct fdisk_script *dp);
+
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name);
+int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data);
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp);
+int fdisk_script_set_table(struct fdisk_script *dp, struct fdisk_table *tb);
+int fdisk_script_get_nlines(struct fdisk_script *dp);
+int fdisk_script_has_force_label(struct fdisk_script *dp);
+
+int fdisk_script_set_userdata(struct fdisk_script *dp, void *data);
+void *fdisk_script_get_userdata(struct fdisk_script *dp);
+
+int fdisk_script_set_fgets(struct fdisk_script *dp,
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *));
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt);
+int fdisk_script_enable_json(struct fdisk_script *dp, int json);
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz);
+
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt);
+
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp);
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+
+
+/* ask.c */
+#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x)
+
+int fdisk_set_ask(struct fdisk_context *cxt,
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+ void *data);
+
+
+void fdisk_ref_ask(struct fdisk_ask *ask);
+void fdisk_unref_ask(struct fdisk_ask *ask);
+const char *fdisk_ask_get_query(struct fdisk_ask *ask);
+int fdisk_ask_get_type(struct fdisk_ask *ask);
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask);
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result);
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask);
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative);
+int fdisk_ask_number_is_wrap_negative(struct fdisk_ask *ask);
+int fdisk_ask_number_inchars(struct fdisk_ask *ask);
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew);
+
+int fdisk_ask_number(struct fdisk_context *cxt,
+ uintmax_t low,
+ uintmax_t dflt,
+ uintmax_t high,
+ const char *query,
+ uintmax_t *result);
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask);
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result);
+int fdisk_ask_string(struct fdisk_context *cxt,
+ const char *query,
+ char **result);
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+ const char *query,
+ int *result);
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask);
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result);
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask);
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key);
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key);
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+ const char **name, const char **desc);
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask);
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask);
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask);
+
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+
+/* utils.h */
+extern char *fdisk_partname(const char *dev, size_t partno);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBFDISK_H */
diff --git a/libfdisk/src/libfdisk.sym b/libfdisk/src/libfdisk.sym
new file mode 100644
index 0000000..71de805
--- /dev/null
+++ b/libfdisk/src/libfdisk.sym
@@ -0,0 +1,322 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol foo;
+ * can't run with old library.so not providing foo.
+
+ * Version info can't enforce this since we never change the SONAME.
+ *
+ * Copyright (C) 2014-2015 Karel Zak <kzak@redhat.com>
+ */
+FDISK_2.26 {
+global:
+ fdisk_add_partition;
+ fdisk_align_lba;
+ fdisk_align_lba_in_range;
+ fdisk_apply_script;
+ fdisk_apply_script_headers;
+ fdisk_apply_table;
+ fdisk_ask_get_query;
+ fdisk_ask_get_type;
+ fdisk_ask_menu_get_default;
+ fdisk_ask_menu_get_item;
+ fdisk_ask_menu_get_nitems;
+ fdisk_ask_menu_get_result;
+ fdisk_ask_menu_set_result;
+ fdisk_ask_number;
+ fdisk_ask_number_get_base;
+ fdisk_ask_number_get_default;
+ fdisk_ask_number_get_high;
+ fdisk_ask_number_get_low;
+ fdisk_ask_number_get_range;
+ fdisk_ask_number_get_result;
+ fdisk_ask_number_get_unit;
+ fdisk_ask_number_inchars;
+ fdisk_ask_number_set_relative;
+ fdisk_ask_number_set_result;
+ fdisk_ask_partnum;
+ fdisk_ask_print_get_errno;
+ fdisk_ask_print_get_mesg;
+ fdisk_ask_string;
+ fdisk_ask_string_get_result;
+ fdisk_ask_string_set_result;
+ fdisk_ask_yesno;
+ fdisk_ask_yesno_get_result;
+ fdisk_ask_yesno_set_result;
+ fdisk_assign_device;
+ fdisk_bsd_edit_disklabel;
+ fdisk_bsd_link_partition;
+ fdisk_bsd_write_bootstrap;
+ fdisk_copy_parttype;
+ fdisk_create_disklabel;
+ fdisk_deassign_device;
+ fdisk_delete_all_partitions;
+ fdisk_delete_partition;
+ fdisk_dos_enable_compatible;
+ fdisk_dos_is_compatible;
+ fdisk_dos_move_begin;
+ fdisk_enable_details;
+ fdisk_enable_listonly;
+ fdisk_field_get_id;
+ fdisk_field_get_name;
+ fdisk_field_get_width;
+ fdisk_field_is_number;
+ fdisk_free_iter;
+ fdisk_get_alignment_offset;
+ fdisk_get_devfd;
+ fdisk_get_devname;
+ fdisk_get_disklabel_id;
+ fdisk_get_first_lba;
+ fdisk_get_freespaces;
+ fdisk_get_geom_cylinders;
+ fdisk_get_geom_heads;
+ fdisk_get_geom_sectors;
+ fdisk_get_grain_size;
+ fdisk_get_label;
+ fdisk_get_last_lba;
+ fdisk_get_library_features;
+ fdisk_get_library_version;
+ fdisk_get_minimal_iosize;
+ fdisk_get_nlabels;
+ fdisk_get_npartitions;
+ fdisk_get_nsectors;
+ fdisk_get_optimal_iosize;
+ fdisk_get_parent;
+ fdisk_get_partition;
+ fdisk_get_partitions;
+ fdisk_get_physector_size;
+ fdisk_get_script;
+ fdisk_get_sector_size;
+ fdisk_get_size_unit;
+ fdisk_get_unit;
+ fdisk_get_units_per_sector;
+ fdisk_gpt_is_hybrid;
+ fdisk_has_label;
+ fdisk_has_user_device_properties;
+ fdisk_info;
+ fdisk_init_debug;
+ fdisk_is_details;
+ fdisk_is_labeltype;
+ fdisk_is_listonly;
+ fdisk_is_partition_used;
+ fdisk_is_readonly;
+ fdisk_iter_get_direction;
+ fdisk_label_get_field;
+ fdisk_label_get_field_by_name;
+ fdisk_label_get_fields_ids;
+ fdisk_label_get_name;
+ fdisk_label_get_nparttypes;
+ fdisk_label_get_parttype;
+ fdisk_label_get_parttype_from_code;
+ fdisk_label_get_parttype_from_string;
+ fdisk_label_get_type;
+ fdisk_label_has_code_parttypes;
+ fdisk_label_is_changed;
+ fdisk_label_is_disabled;
+ fdisk_label_parse_parttype;
+ fdisk_label_require_geometry;
+ fdisk_label_set_changed;
+ fdisk_label_set_disabled;
+ fdisk_lba_is_phy_aligned;
+ fdisk_list_disklabel;
+ fdisk_locate_disklabel;
+ fdisk_new_context;
+ fdisk_new_iter;
+ fdisk_new_nested_context;
+ fdisk_new_partition;
+ fdisk_new_parttype;
+ fdisk_new_script;
+ fdisk_new_script_from_file;
+ fdisk_new_table;
+ fdisk_new_unknown_parttype;
+ fdisk_next_label;
+ fdisk_override_geometry;
+ fdisk_parse_version_string;
+ fdisk_partition_cmp_partno;
+ fdisk_partition_cmp_start;
+ fdisk_partition_end_follow_default;
+ fdisk_partition_end_is_default;
+ fdisk_partition_get_attrs;
+ fdisk_partition_get_end;
+ fdisk_partition_get_name;
+ fdisk_partition_get_parent;
+ fdisk_partition_get_partno;
+ fdisk_partition_get_size;
+ fdisk_partition_get_start;
+ fdisk_partition_get_type;
+ fdisk_partition_get_uuid;
+ fdisk_partition_has_end;
+ fdisk_partition_has_partno;
+ fdisk_partition_has_size;
+ fdisk_partition_has_start;
+ fdisk_partition_is_bootable;
+ fdisk_partition_is_container;
+ fdisk_partition_is_freespace;
+ fdisk_partition_is_nested;
+ fdisk_partition_is_used;
+ fdisk_partition_is_wholedisk;
+ fdisk_partition_next_partno;
+ fdisk_partition_partno_follow_default;
+ fdisk_partition_set_attrs;
+ fdisk_partition_set_name;
+ fdisk_partition_set_partno;
+ fdisk_partition_set_size;
+ fdisk_partition_set_start;
+ fdisk_partition_set_type;
+ fdisk_partition_set_uuid;
+ fdisk_partition_size_explicit;
+ fdisk_partition_start_follow_default;
+ fdisk_partition_start_is_default;
+ fdisk_partition_to_string;
+ fdisk_partition_unset_partno;
+ fdisk_partition_unset_size;
+ fdisk_partition_unset_start;
+ fdisk_partname;
+ fdisk_parttype_get_code;
+ fdisk_parttype_get_name;
+ fdisk_parttype_get_string;
+ fdisk_parttype_is_unknown;
+ fdisk_parttype_set_code;
+ fdisk_parttype_set_name;
+ fdisk_parttype_set_typestr;
+ fdisk_ref_ask;
+ fdisk_ref_context;
+ fdisk_ref_partition;
+ fdisk_ref_parttype;
+ fdisk_ref_script;
+ fdisk_ref_table;
+ fdisk_reorder_partitions;
+ fdisk_reread_partition_table;
+ fdisk_reset_alignment;
+ fdisk_reset_device_properties;
+ fdisk_reset_iter;
+ fdisk_reset_partition;
+ fdisk_reset_table;
+ fdisk_save_user_geometry;
+ fdisk_save_user_sector_size;
+ fdisk_script_get_header;
+ fdisk_script_get_nlines;
+ fdisk_script_get_table;
+ fdisk_script_read_context;
+ fdisk_script_read_file;
+ fdisk_script_read_line;
+ fdisk_script_set_header;
+ fdisk_script_write_file;
+ fdisk_set_ask;
+ fdisk_set_disklabel_id;
+ fdisk_set_first_lba;
+ fdisk_set_last_lba;
+ fdisk_set_partition;
+ fdisk_set_partition_type;
+ fdisk_set_script;
+ fdisk_set_size_unit;
+ fdisk_set_unit;
+ fdisk_sgi_create_info;
+ fdisk_sgi_set_bootfile;
+ fdisk_sun_set_alt_cyl;
+ fdisk_sun_set_ilfact;
+ fdisk_sun_set_pcylcount;
+ fdisk_sun_set_rspeed;
+ fdisk_sun_set_xcyl;
+ fdisk_table_add_partition;
+ fdisk_table_get_nents;
+ fdisk_table_get_partition;
+ fdisk_table_is_empty;
+ fdisk_table_next_partition;
+ fdisk_table_remove_partition;
+ fdisk_table_sort_partitions;
+ fdisk_table_wrong_order;
+ fdisk_toggle_partition_flag;
+ fdisk_unref_ask;
+ fdisk_unref_context;
+ fdisk_unref_partition;
+ fdisk_unref_parttype;
+ fdisk_unref_script;
+ fdisk_unref_table;
+ fdisk_use_cylinders;
+ fdisk_verify_disklabel;
+ fdisk_warn;
+ fdisk_warnx;
+ fdisk_write_disklabel;
+local:
+ *;
+};
+
+FDISK_2.27 {
+ fdisk_enable_bootbits_protection;
+ fdisk_gpt_get_partition_attrs;
+ fdisk_gpt_set_partition_attrs;
+ fdisk_has_protected_bootbits;
+ fdisk_label_get_fields_ids_all;
+ fdisk_script_get_userdata;
+ fdisk_script_set_fgets;
+ fdisk_script_set_userdata;
+ fdisk_table_get_partition_by_partno;
+ fdisk_get_disklabel_item;
+ fdisk_script_enable_json;
+} FDISK_2.26;
+
+FDISK_2.28 {
+ fdisk_enable_wipe;
+ fdisk_get_collision;
+ fdisk_has_wipe;
+} FDISK_2.27;
+
+FDISK_2.29 {
+ fdisk_wipe_partition;
+ fdisk_new_labelitem;
+ fdisk_ref_labelitem;
+ fdisk_reset_labelitem;
+ fdisk_unref_labelitem;
+ fdisk_labelitem_get_name;
+ fdisk_labelitem_get_id;
+ fdisk_labelitem_get_data_u64;
+ fdisk_labelitem_get_data_string;
+ fdisk_labelitem_is_string;
+ fdisk_labelitem_is_number;
+ fdisk_gpt_set_npartitions;
+} FDISK_2.28;
+
+
+FDISK_2.30 {
+ fdisk_script_has_force_label;
+ fdisk_is_regfile;
+ fdisk_is_ptcollision;
+ fdisk_partition_has_wipe;
+} FDISK_2.29;
+
+FDISK_2.31 {
+ fdisk_reassign_device;
+ fdisk_device_is_used;
+ fdisk_reread_changes;
+ fdisk_disable_dialogs;
+ fdisk_has_dialogs;
+ fdisk_save_user_grain;
+} FDISK_2.30;
+
+FDISK_2.32 {
+ fdisk_label_get_geomrange_sectors;
+ fdisk_label_get_geomrange_heads;
+ fdisk_label_get_geomrange_cylinders;
+} FDISK_2.31;
+
+FDISK_2.33 {
+ fdisk_ask_number_is_wrap_negative;
+ fdisk_get_devmodel;
+ fdisk_get_devno;
+} FDISK_2.32;
+
+FDISK_2.35 {
+ fdisk_script_set_table;
+ fdisk_assign_device_by_fd;
+} FDISK_2.33;
+FDISK_2.36 {
+ fdisk_set_disklabel_id_from_string;
+ fdisk_gpt_disable_relocation;
+ fdisk_gpt_enable_minimize;
+ fdisk_label_has_parttypes_shortcuts;
+ fdisk_label_advparse_parttype;
+ fdisk_label_get_parttype_shortcut;
+} FDISK_2.35;
+
+FDISK_2.38 {
+ fdisk_dos_fix_chs;
+} FDISK_2.36;
diff --git a/libfdisk/src/partition.c b/libfdisk/src/partition.c
new file mode 100644
index 0000000..2677cae
--- /dev/null
+++ b/libfdisk/src/partition.c
@@ -0,0 +1,1617 @@
+
+#include "c.h"
+#include "strutils.h"
+
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: partition
+ * @title: Partition
+ * @short_description: generic label independent partition abstraction
+ *
+ * The fdisk_partition provides label independent abstraction. The partitions
+ * are not directly connected with partition table (label) data. Any change to
+ * fdisk_partition does not affects in-memory or on-disk label data.
+ *
+ * The fdisk_partition is possible to use as a template for
+ * fdisk_add_partition() or fdisk_set_partition() operations.
+ */
+
+static void init_partition(struct fdisk_partition *pa)
+{
+ FDISK_INIT_UNDEF(pa->size);
+ FDISK_INIT_UNDEF(pa->start);
+ FDISK_INIT_UNDEF(pa->partno);
+ FDISK_INIT_UNDEF(pa->parent_partno);
+ FDISK_INIT_UNDEF(pa->boot);
+
+ INIT_LIST_HEAD(&pa->parts);
+}
+
+/**
+ * fdisk_new_partition:
+ *
+ * Returns: new instance.
+ */
+struct fdisk_partition *fdisk_new_partition(void)
+{
+ struct fdisk_partition *pa = calloc(1, sizeof(*pa));
+
+ pa->refcount = 1;
+ init_partition(pa);
+ DBG(PART, ul_debugobj(pa, "alloc"));
+ return pa;
+}
+
+/**
+ * fdisk_reset_partition:
+ * @pa: partition
+ *
+ * Resets partition content.
+ */
+void fdisk_reset_partition(struct fdisk_partition *pa)
+{
+ int ref;
+
+ if (!pa)
+ return;
+
+ DBG(PART, ul_debugobj(pa, "reset"));
+ ref = pa->refcount;
+
+ fdisk_unref_parttype(pa->type);
+ free(pa->name);
+ free(pa->uuid);
+ free(pa->attrs);
+ free(pa->fstype);
+ free(pa->fsuuid);
+ free(pa->fslabel);
+ free(pa->start_chs);
+ free(pa->end_chs);
+
+ memset(pa, 0, sizeof(*pa));
+ pa->refcount = ref;
+
+ init_partition(pa);
+}
+
+static struct fdisk_partition *__copy_partition(struct fdisk_partition *o)
+{
+ struct fdisk_partition *n = fdisk_new_partition();
+ int rc;
+
+ if (!n)
+ return NULL;
+
+ memcpy(n, o, sizeof(*n));
+
+ /* do not copy reference to lists, etc.*/
+ n->refcount = 1;
+ INIT_LIST_HEAD(&n->parts);
+
+ if (n->type)
+ fdisk_ref_parttype(n->type);
+
+ /* note that strdup_between_structs() deallocates destination pointer,
+ * so make sure it's NULL as we call memcpy() before ... */
+ n->name = NULL;
+ rc = strdup_between_structs(n, o, name);
+
+ n->uuid = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, uuid);
+ n->attrs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, attrs);
+ n->fstype = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fstype);
+ n->fsuuid = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fsuuid);
+ n->fslabel = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fslabel);
+ n->start_chs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, start_chs);
+ n->end_chs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, end_chs);
+
+ if (rc) {
+ fdisk_unref_partition(n);
+ n = NULL;
+ }
+ return n;
+}
+
+/**
+ * fdisk_ref_partition:
+ * @pa: partition pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_partition(struct fdisk_partition *pa)
+{
+ if (pa)
+ pa->refcount++;
+}
+
+/**
+ * fdisk_unref_partition:
+ * @pa: partition pointer
+ *
+ * Decrements reference counter, on zero the @pa is automatically
+ * deallocated.
+ */
+void fdisk_unref_partition(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return;
+
+ pa->refcount--;
+ if (pa->refcount <= 0) {
+ list_del(&pa->parts);
+ fdisk_reset_partition(pa);
+ DBG(PART, ul_debugobj(pa, "free"));
+ free(pa);
+ }
+}
+
+/**
+ * fdisk_partition_set_start:
+ * @pa: partition
+ * @off: offset in sectors, maximal is UINT64_MAX-1
+ *
+ * Note that zero is valid offset too. Use fdisk_partition_unset_start() to
+ * undefine the offset.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_start(struct fdisk_partition *pa, fdisk_sector_t off)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(off))
+ return -ERANGE;
+ pa->start = off;
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_start:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_start().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_start(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->start);
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_start:
+ * @pa: partition
+ *
+ * The zero is also valid offset. The function may return random undefined
+ * value when start offset is undefined (for example after
+ * fdisk_partition_unset_start()). Always use fdisk_partition_has_start() to be
+ * sure that you work with valid numbers.
+ *
+ * Returns: start offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa)
+{
+ return pa->start;
+}
+
+/**
+ * fdisk_partition_has_start:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_start(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->start);
+}
+
+
+/**
+ * fdisk_partition_cmp_start:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitions according to start offset, See fdisk_table_sort_partitions().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+ struct fdisk_partition *b)
+{
+ int no_a = FDISK_IS_UNDEF(a->start),
+ no_b = FDISK_IS_UNDEF(b->start);
+
+ if (no_a && no_b)
+ return 0;
+ if (no_a)
+ return -1;
+ if (no_b)
+ return 1;
+
+ return cmp_numbers(a->start, b->start);
+}
+
+/**
+ * fdisk_partition_start_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label driver
+ * to use the first possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->start_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_start_is_default:
+ * @pa: partition
+ *
+ * See fdisk_partition_start_follow_default().
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_start_is_default(struct fdisk_partition *pa)
+{
+ assert(pa);
+ return pa->start_follow_default;
+}
+
+/**
+ * fdisk_partition_set_size:
+ * @pa: partition
+ * @sz: size in sectors, maximal is UIN64_MAX-1
+ *
+ * Note that zero is valid size too. Use fdisk_partition_unset_size() to
+ * undefine the size.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_size(struct fdisk_partition *pa, fdisk_sector_t sz)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(sz))
+ return -ERANGE;
+ pa->size = sz;
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_size:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_size().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_size(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->size);
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_size:
+ * @pa: partition
+ *
+ * The zero is also valid size. The function may return random undefined
+ * value when size is undefined (for example after fdisk_partition_unset_size()).
+ * Always use fdisk_partition_has_size() to be sure that you work with valid
+ * numbers.
+ *
+ * Returns: size offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa)
+{
+ return pa->size;
+}
+
+/**
+ * fdisk_partition_has_size:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_size(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_size_explicit:
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * By default libfdisk aligns the size when add the new partition (by
+ * fdisk_add_partition()). If you want to disable this functionality use
+ * @enable = 1.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->size_explicit = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_set_partno:
+ * @pa: partition
+ * @num: partition number (0 is the first partition, maximal is SIZE_MAX-1)
+ *
+ * Note that zero is valid partno too. Use fdisk_partition_unset_partno() to
+ * undefine the partno.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(num))
+ return -ERANGE;
+ pa->partno = num;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_partno:
+ * @pa: partition
+ *
+ * Sets the partno as undefined. See fdisk_partition_has_partno().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_partno(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->partno);
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_partno:
+ * @pa: partition
+ *
+ * The zero is also valid partition number. The function may return random
+ * value when partno is undefined (for example after fdisk_partition_unset_partno()).
+ * Always use fdisk_partition_has_partno() to be sure that you work with valid
+ * numbers.
+ *
+ * Returns: partition number (0 is the first partition)
+ */
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa)
+{
+ return pa->partno;
+}
+
+/**
+ * fdisk_partition_has_partno:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_partno(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->partno);
+}
+
+
+/**
+ * fdisk_partition_cmp_partno:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitions according to partition number See fdisk_table_sort_partitions().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+ struct fdisk_partition *b)
+{
+ return a->partno - b->partno;
+}
+
+/**
+ * fdisk_partition_partno_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label driver
+ * to add a new partition to the default (next) position.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->partno_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_set_type:
+ * @pa: partition
+ * @type: partition type
+ *
+ * Sets partition type.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_type(struct fdisk_partition *pa,
+ struct fdisk_parttype *type)
+{
+ if (!pa)
+ return -EINVAL;
+
+ fdisk_ref_parttype(type);
+ fdisk_unref_parttype(pa->type);
+ pa->type = type;
+
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_type:
+ * @pa: partition
+ *
+ * Returns: pointer to partition type.
+ */
+struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa)
+{
+ return pa ? pa->type : NULL;
+}
+
+/**
+ * fdisk_partition_set_name:
+ * @pa: partition
+ * @name: partition name
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, name, name);
+}
+
+/**
+ * fdisk_partition_get_name:
+ * @pa: partition
+ *
+ * Returns: partition name
+ */
+const char *fdisk_partition_get_name(struct fdisk_partition *pa)
+{
+ return pa ? pa->name : NULL;
+}
+
+/**
+ * fdisk_partition_set_uuid:
+ * @pa: partition
+ * @uuid: UUID of the partition
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, uuid, uuid);
+}
+
+/**
+ * fdisk_partition_has_end:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition has defined last sector
+ */
+int fdisk_partition_has_end(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->start) && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_get_end:
+ * @pa: partition
+ *
+ * This function may returns absolute non-sense, always check
+ * fdisk_partition_has_end().
+ *
+ * Note that partition end is defined by fdisk_partition_set_start() and
+ * fdisk_partition_set_size().
+ *
+ * Returns: last partition sector LBA.
+ */
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa)
+{
+ return pa->start + pa->size - (pa->size == 0 ? 0 : 1);
+}
+
+/**
+ * fdisk_partition_end_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label
+ * driver to use all the possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->end_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_end_is_default:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_end_is_default(struct fdisk_partition *pa)
+{
+ assert(pa);
+ return pa->end_follow_default;
+}
+
+/**
+ * fdisk_partition_get_uuid:
+ * @pa: partition
+ *
+ * Returns: partition UUID as string
+ */
+const char *fdisk_partition_get_uuid(struct fdisk_partition *pa)
+{
+ return pa ? pa->uuid : NULL;
+}
+
+/**
+ * fdisk_partition_get_attrs:
+ * @pa: partition
+ *
+ * Returns: partition attributes in string format
+ */
+const char *fdisk_partition_get_attrs(struct fdisk_partition *pa)
+{
+ return pa ? pa->attrs : NULL;
+}
+
+/**
+ * fdisk_partition_set_attrs:
+ * @pa: partition
+ * @attrs: attributes
+ *
+ * Sets @attrs to @pa.
+ *
+ * Return: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, attrs, attrs);
+}
+
+/**
+ * fdisk_partition_is_nested:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is nested (e.g. MBR logical partition)
+ */
+int fdisk_partition_is_nested(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->parent_partno);
+}
+
+/**
+ * fdisk_partition_is_container:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is container (e.g. MBR extended partition)
+ */
+int fdisk_partition_is_container(struct fdisk_partition *pa)
+{
+ return pa && pa->container;
+}
+
+/**
+ * fdisk_partition_get_parent:
+ * @pa: partition
+ * @parent: parent parno
+ *
+ * Returns: returns devno of the parent
+ */
+int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent)
+{
+ if (pa && parent)
+ *parent = pa->parent_partno;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * fdisk_partition_is_used:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition points to some area
+ */
+int fdisk_partition_is_used(struct fdisk_partition *pa)
+{
+ return pa && pa->used;
+}
+
+/**
+ * fdisk_partition_is_bootable:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition has enabled boot flag
+ */
+int fdisk_partition_is_bootable(struct fdisk_partition *pa)
+{
+ return pa && pa->boot == 1;
+}
+
+/**
+ * fdisk_partition_is_freespace:
+ * @pa: partition
+ *
+ * Returns: 1 if @pa points to freespace
+ */
+int fdisk_partition_is_freespace(struct fdisk_partition *pa)
+{
+ return pa && pa->freespace;
+}
+
+/**
+ * fdisk_partition_is_wholedisk:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is special whole-disk (e.g. SUN) partition
+ */
+int fdisk_partition_is_wholedisk(struct fdisk_partition *pa)
+{
+ return pa && pa->wholedisk;
+}
+
+/**
+ * fdisk_partition_next_partno:
+ * @pa: partition
+ * @cxt: context
+ * @n: returns partition number
+ *
+ * If @pa specified and partno-follow-default (see fdisk_partition_partno_follow_default())
+ * enabled then returns next expected partno or -ERANGE on error.
+ *
+ * If @pa is NULL, or @pa does not specify any semantic for the next partno
+ * then use Ask API to ask user for the next partno. In this case returns 1 if
+ * no free partition available. If fdisk dialogs are disabled then returns -EINVAL.
+ *
+ * Returns: 0 on success, <0 on error, or 1 for non-free partno by Ask API.
+ */
+int fdisk_partition_next_partno(
+ struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ size_t *n)
+{
+ if (!cxt || !n)
+ return -EINVAL;
+
+ if (pa && pa->partno_follow_default) {
+ size_t i;
+
+ DBG(PART, ul_debugobj(pa, "next partno (follow default)"));
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (!fdisk_is_partition_used(cxt, i)) {
+ *n = i;
+ return 0;
+ }
+ }
+ return -ERANGE;
+
+ }
+
+ if (pa && fdisk_partition_has_partno(pa)) {
+
+ DBG(PART, ul_debugobj(pa, "next partno (specified=%zu)", pa->partno));
+
+ if (pa->partno >= cxt->label->nparts_max ||
+ fdisk_is_partition_used(cxt, pa->partno))
+ return -ERANGE;
+ *n = pa->partno;
+ return 0;
+
+ }
+
+ if (fdisk_has_dialogs(cxt))
+ return fdisk_ask_partnum(cxt, n, 1);
+
+ return -EINVAL;
+}
+
+static int probe_partition_content(struct fdisk_context *cxt, struct fdisk_partition *pa)
+{
+ int rc = 1; /* nothing */
+
+ DBG(PART, ul_debugobj(pa, "start probe #%zu partition [cxt %p] >>>", pa->partno, cxt));
+
+ /* zeroize the current setting */
+ strdup_to_struct_member(pa, fstype, NULL);
+ strdup_to_struct_member(pa, fsuuid, NULL);
+ strdup_to_struct_member(pa, fslabel, NULL);
+
+ if (!fdisk_partition_has_start(pa) ||
+ !fdisk_partition_has_size(pa))
+ goto done;
+
+#ifdef HAVE_LIBBLKID
+ else {
+ uintmax_t start, size;
+
+ blkid_probe pr = blkid_new_probe();
+ if (!pr)
+ goto done;
+
+ DBG(PART, ul_debugobj(pa, "blkid prober: %p", pr));
+
+ start = fdisk_partition_get_start(pa) * fdisk_get_sector_size(cxt);
+ size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cxt);
+
+ if (blkid_probe_set_device(pr, cxt->dev_fd, start, size) == 0
+ && blkid_do_fullprobe(pr) == 0) {
+
+ const char *data;
+ rc = 0;
+
+ if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
+ rc = strdup_to_struct_member(pa, fstype, data);
+
+ if (!rc && !blkid_probe_lookup_value(pr, "LABEL", &data, NULL))
+ rc = strdup_to_struct_member(pa, fslabel, data);
+
+ if (!rc && !blkid_probe_lookup_value(pr, "UUID", &data, NULL))
+ rc = strdup_to_struct_member(pa, fsuuid, data);
+ }
+
+ blkid_free_probe(pr);
+ pa->fs_probed = 1;
+ }
+#endif /* HAVE_LIBBLKID */
+
+done:
+ DBG(PART, ul_debugobj(pa, "<<< end probe #%zu partition[cxt %p, rc=%d]", pa->partno, cxt, rc));
+ return rc;
+}
+
+/**
+ * fdisk_partition_to_string:
+ * @pa: partition
+ * @cxt: context
+ * @id: field (FDISK_FIELD_*)
+ * @data: returns string with allocated data
+ *
+ * Returns info about partition converted to printable string.
+ *
+ * For example
+ * <informalexample>
+ * <programlisting>
+ * struct fdisk_partition *pa;
+ *
+ * fdisk_get_partition(cxt, 0, &pa);
+ * fdisk_partition_to_string(pa, FDISK_FIELD_UUID, &data);
+ * printf("first partition uuid: %s\n", data);
+ * free(data);
+ * fdisk_unref_partition(pa);
+ * </programlisting>
+ * </informalexample>
+ *
+ * returns UUID for the first partition.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_partition_to_string(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ int id,
+ char **data)
+{
+ char *p = NULL;
+ int rc = 0;
+ uint64_t x;
+
+ if (!pa || !cxt || !data)
+ return -EINVAL;
+
+ switch (id) {
+ case FDISK_FIELD_DEVICE:
+ if (pa->freespace)
+ p = strdup(_("Free space"));
+ else if (fdisk_partition_has_partno(pa) && cxt->dev_path) {
+ if (cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+ rc = asprintf(&p, "%c", (int) pa->partno + 'a');
+ else
+ p = fdisk_partname(cxt->dev_path, pa->partno + 1);
+ }
+ break;
+ case FDISK_FIELD_BOOT:
+ p = fdisk_partition_is_bootable(pa) ? strdup("*") : NULL;
+ break;
+ case FDISK_FIELD_START:
+ if (fdisk_partition_has_start(pa)) {
+ x = fdisk_cround(cxt, pa->start);
+ rc = pa->start_post ?
+ asprintf(&p, "%"PRIu64"%c", x, pa->start_post) :
+ asprintf(&p, "%"PRIu64, x);
+ }
+ break;
+ case FDISK_FIELD_END:
+ if (fdisk_partition_has_end(pa)) {
+ x = fdisk_cround(cxt, fdisk_partition_get_end(pa));
+ rc = pa->end_post ?
+ asprintf(&p, "%"PRIu64"%c", x, pa->end_post) :
+ asprintf(&p, "%"PRIu64, x);
+ }
+ break;
+ case FDISK_FIELD_SIZE:
+ if (fdisk_partition_has_size(pa)) {
+ uint64_t sz = pa->size * cxt->sector_size;
+
+ switch (cxt->sizeunit) {
+ case FDISK_SIZEUNIT_BYTES:
+ rc = asprintf(&p, "%"PRIu64"", sz);
+ break;
+ case FDISK_SIZEUNIT_HUMAN:
+ if (fdisk_is_details(cxt))
+ rc = pa->size_post ?
+ asprintf(&p, "%"PRIu64"%c", sz, pa->size_post) :
+ asprintf(&p, "%"PRIu64, sz);
+ else {
+ p = size_to_human_string(SIZE_SUFFIX_1LETTER, sz);
+ if (!p)
+ rc = -ENOMEM;
+ }
+ break;
+ }
+ }
+ break;
+ case FDISK_FIELD_CYLINDERS:
+ {
+ uintmax_t sz = fdisk_partition_has_size(pa) ? pa->size : 0;
+ if (sz)
+ /* Why we need to cast that to uintmax_t? */
+ rc = asprintf(&p, "%ju", (uintmax_t)(sz / (cxt->geom.heads * cxt->geom.sectors)) + 1);
+ break;
+ }
+ case FDISK_FIELD_SECTORS:
+ rc = asprintf(&p, "%ju",
+ fdisk_partition_has_size(pa) ? (uintmax_t) pa->size : 0);
+ break;
+ case FDISK_FIELD_BSIZE:
+ rc = asprintf(&p, "%"PRIu64, pa->bsize);
+ break;
+ case FDISK_FIELD_FSIZE:
+ rc = asprintf(&p, "%"PRIu64, pa->fsize);
+ break;
+ case FDISK_FIELD_CPG:
+ rc = asprintf(&p, "%"PRIu64, pa->cpg);
+ break;
+ case FDISK_FIELD_TYPE:
+ p = pa->type && pa->type->name ? strdup(_(pa->type->name)) : NULL;
+ break;
+ case FDISK_FIELD_TYPEID:
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ rc = asprintf(&p, "%s", fdisk_parttype_get_string(pa->type));
+ else if (pa->type)
+ rc = asprintf(&p, "%x", fdisk_parttype_get_code(pa->type));
+ break;
+ case FDISK_FIELD_UUID:
+ p = pa->uuid && *pa->uuid? strdup(pa->uuid) : NULL;
+ break;
+ case FDISK_FIELD_NAME:
+ p = pa->name && *pa->name ? strdup(pa->name) : NULL;
+ break;
+ case FDISK_FIELD_ATTR:
+ p = pa->attrs && *pa->attrs ? strdup(pa->attrs) : NULL;
+ break;
+ case FDISK_FIELD_SADDR:
+ p = pa->start_chs && *pa->start_chs ? strdup(pa->start_chs) : NULL;
+ break;
+ case FDISK_FIELD_EADDR:
+ p = pa->end_chs && *pa->end_chs? strdup(pa->end_chs) : NULL;
+ break;
+ case FDISK_FIELD_FSUUID:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fsuuid && *pa->fsuuid ? strdup(pa->fsuuid) : NULL;
+ break;
+ case FDISK_FIELD_FSLABEL:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fslabel && *pa->fslabel ? strdup(pa->fslabel) : NULL;
+ break;
+ case FDISK_FIELD_FSTYPE:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fstype && *pa->fstype ? strdup(pa->fstype) : NULL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ rc = -ENOMEM;
+ free(p);
+ p = NULL;
+
+ } else if (rc > 0)
+ rc = 0;
+
+ *data = p;
+
+ return rc;
+}
+
+
+/**
+ * fdisk_get_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: returns data about partition
+ *
+ * Reads disklabel and fills in @pa with data about partition @n.
+ *
+ * Note that partno may address unused partition and then this function does
+ * not fill anything to @pa. See fdisk_is_partition_used(). If @pa points to
+ * NULL then the function allocates a newly allocated fdisk_partition struct,
+ * use fdisk_unref_partition() to deallocate.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_partition(struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition **pa)
+{
+ int rc;
+ struct fdisk_partition *np = NULL;
+
+ if (!cxt || !cxt->label || !pa)
+ return -EINVAL;
+ if (!cxt->label->op->get_part)
+ return -ENOSYS;
+ if (!fdisk_is_partition_used(cxt, partno))
+ return -EINVAL;
+
+ if (!*pa) {
+ np = *pa = fdisk_new_partition();
+ if (!*pa)
+ return -ENOMEM;
+ } else
+ fdisk_reset_partition(*pa);
+
+ (*pa)->partno = partno;
+ rc = cxt->label->op->get_part(cxt, partno, *pa);
+
+ if (rc) {
+ if (np) {
+ fdisk_unref_partition(np);
+ *pa = NULL;
+ } else
+ fdisk_reset_partition(*pa);
+ } else
+ (*pa)->size_explicit = 1;
+ return rc;
+}
+
+static struct fdisk_partition *area_by_offset(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t off)
+{
+ struct fdisk_partition *pa = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (!fdisk_partition_has_start(pa) || !fdisk_partition_has_size(pa))
+ continue;
+ if (fdisk_partition_is_nested(cur) &&
+ pa->parent_partno != cur->parent_partno)
+ continue;
+ if (off >= pa->start && off < pa->start + pa->size)
+ return pa;
+ }
+
+ return NULL;
+}
+
+static int resize_get_first_possible(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t *start)
+{
+ struct fdisk_partition *pa = NULL, *first = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ *start = 0;
+ DBG(TAB, ul_debugobj(tb, "checking first possible before start=%ju", (uintmax_t) cur->start));
+
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+
+ if (pa->start > cur->start || pa == cur)
+ break;
+
+ DBG(TAB, ul_debugobj(tb, " checking entry %p [partno=%zu start=%ju, end=%ju, size=%ju%s%s%s]",
+ pa,
+ fdisk_partition_get_partno(pa),
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa),
+ (uintmax_t) fdisk_partition_get_size(pa),
+ fdisk_partition_is_freespace(pa) ? " freespace" : "",
+ fdisk_partition_is_nested(pa) ? " nested" : "",
+ fdisk_partition_is_container(pa) ? " container" : ""));
+
+
+ if (!fdisk_partition_is_freespace(pa)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no freespace)"));
+ first = NULL;
+ continue;
+ }
+ if (!fdisk_partition_has_start(pa) || !fdisk_partition_has_size(pa)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no start/size)"));
+ first = NULL;
+ continue;
+ }
+ /* The current is nested, free space has to be nested within the same parent */
+ if (fdisk_partition_is_nested(cur)
+ && pa->parent_partno != cur->parent_partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested required)"));
+ first = NULL;
+ continue;
+ }
+ if (pa->start + pa->size <= cur->start) {
+ first = pa;
+ DBG(TAB, ul_debugobj(tb, " entry usable"));
+ }
+ }
+
+ if (first)
+ *start = first->start;
+ else
+ DBG(PART, ul_debugobj(cur, "resize: nothing usable before %ju", (uintmax_t) cur->start));
+
+ return first ? 0 : -1;
+}
+
+/*
+ * Verify that area addressed by @start is freespace or the @cur[rent]
+ * partition and continue to the next table entries until it's freespace, and
+ * counts size of all this space.
+ *
+ * This is core of the partition start offset move operation. We can move the
+ * start within the current partition of to the another free space. It's
+ * forbidden to move start of the partition to another already defined
+ * partition.
+ */
+static int resize_get_last_possible(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t start,
+ fdisk_sector_t *maxsz)
+{
+ struct fdisk_partition *pa = NULL, *last = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ *maxsz = 0;
+ DBG(TAB, ul_debugobj(tb, "checking last possible for start=%ju", (uintmax_t) start));
+
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+
+ DBG(TAB, ul_debugobj(tb, " checking entry %p [partno=%zu start=%ju, end=%ju, size=%ju%s%s%s]",
+ pa,
+ fdisk_partition_get_partno(pa),
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa),
+ (uintmax_t) fdisk_partition_get_size(pa),
+ fdisk_partition_is_freespace(pa) ? " freespace" : "",
+ fdisk_partition_is_nested(pa) ? " nested" : "",
+ fdisk_partition_is_container(pa) ? " container" : ""));
+
+ if (!fdisk_partition_has_start(pa) ||
+ !fdisk_partition_has_size(pa) ||
+ (fdisk_partition_is_container(pa) && pa != cur)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no start/size or container)"));
+ continue;
+ }
+
+ if (fdisk_partition_is_nested(pa)
+ && fdisk_partition_is_container(cur)
+ && pa->parent_partno == cur->partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested child of the current partition)"));
+ continue;
+ }
+
+ /* The current is nested, free space has to be nested within the same parent */
+ if (fdisk_partition_is_nested(cur)
+ && pa->parent_partno != cur->parent_partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested required)"));
+ continue;
+ }
+
+ if (!last) {
+ if (start >= pa->start && start < pa->start + pa->size) {
+ if (fdisk_partition_is_freespace(pa) || pa == cur) {
+ DBG(TAB, ul_debugobj(tb, " accepted as last"));
+ last = pa;
+ } else {
+ DBG(TAB, ul_debugobj(tb, " failed to set last"));
+ break;
+ }
+
+
+ *maxsz = pa->size - (start - pa->start);
+ DBG(TAB, ul_debugobj(tb, " new max=%ju", (uintmax_t) *maxsz));
+ }
+ } else if (!fdisk_partition_is_freespace(pa) && pa != cur) {
+ DBG(TAB, ul_debugobj(tb, " no free space behind current"));
+ break;
+ } else {
+ last = pa;
+ *maxsz = pa->size - (start - pa->start);
+ DBG(TAB, ul_debugobj(tb, " new max=%ju (last updated)", (uintmax_t) *maxsz));
+ }
+ }
+
+ if (last)
+ DBG(PART, ul_debugobj(cur, "resize: max size=%ju", (uintmax_t) *maxsz));
+ else
+ DBG(PART, ul_debugobj(cur, "resize: nothing usable after %ju", (uintmax_t) start));
+
+ return last ? 0 : -1;
+}
+
+/*
+ * Uses template @tpl to recount start and size change of the partition @res. The
+ * @tpl->size and @tpl->start are interpreted as relative to the current setting.
+ */
+static int recount_resize(
+ struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition *res, struct fdisk_partition *tpl)
+{
+ fdisk_sector_t start, size, xsize;
+ struct fdisk_partition *cur = NULL;
+ struct fdisk_table *tb = NULL;
+ int rc;
+
+ DBG(PART, ul_debugobj(tpl, ">>> resize requested"));
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(size);
+
+ rc = fdisk_get_partitions(cxt, &tb);
+ if (!rc) {
+ /* For resize we do not follow grain to detect free-space, but
+ * we support to resize with very small granulation. */
+ unsigned long org = cxt->grain;
+
+ cxt->grain = cxt->sector_size;
+ rc = fdisk_get_freespaces(cxt, &tb);
+ cxt->grain = org;
+ }
+ if (rc) {
+ fdisk_unref_table(tb);
+ return rc;
+ }
+
+ fdisk_table_sort_partitions(tb, fdisk_partition_cmp_start);
+
+ DBG(PART, ul_debugobj(tpl, "resize partition partno=%zu in table:", partno));
+ ON_DBG(PART, fdisk_debug_print_table(tb));
+
+ cur = fdisk_table_get_partition_by_partno(tb, partno);
+ if (!cur) {
+ fdisk_unref_table(tb);
+ return -EINVAL;
+ }
+
+ /* 1a) set new start - change relative to the current on-disk setting */
+ if (tpl->movestart && fdisk_partition_has_start(tpl)) {
+ start = fdisk_partition_get_start(cur);
+ if (tpl->movestart == FDISK_MOVE_DOWN) {
+ if (fdisk_partition_get_start(tpl) > start)
+ goto erange;
+ start -= fdisk_partition_get_start(tpl);
+ } else
+ start += fdisk_partition_get_start(tpl);
+
+ DBG(PART, ul_debugobj(tpl, "resize: moving start %s relative, new start: %ju",
+ tpl->movestart == FDISK_MOVE_DOWN ? "DOWN" : "UP", (uintmax_t)start));
+
+ /* 1b) set new start - try freespace before the curret partition */
+ } else if (tpl->movestart == FDISK_MOVE_DOWN) {
+
+ if (resize_get_first_possible(tb, cur, &start) != 0)
+ goto erange;
+
+ DBG(PART, ul_debugobj(tpl, "resize: moving start DOWN (first possible), new start: %ju",
+ (uintmax_t)start));
+
+ /* 1c) set new start - absolute number */
+ } else if (fdisk_partition_has_start(tpl)) {
+ start = fdisk_partition_get_start(tpl);
+ DBG(PART, ul_debugobj(tpl, "resize: moving start to absolute offset: %ju",
+ (uintmax_t)start));
+ }
+
+ /* 2) verify that start is within the current partition or any freespace area */
+ if (!FDISK_IS_UNDEF(start)) {
+ struct fdisk_partition *area = area_by_offset(tb, cur, start);
+
+ if (area == cur)
+ DBG(PART, ul_debugobj(tpl, "resize: start points to the current partition"));
+ else if (area && fdisk_partition_is_freespace(area))
+ DBG(PART, ul_debugobj(tpl, "resize: start points to freespace"));
+ else if (!area && start >= cxt->first_lba && start < cxt->first_lba + (cxt->grain / cxt->sector_size))
+ DBG(PART, ul_debugobj(tpl, "resize: start points before first partition"));
+ else {
+ DBG(PART, ul_debugobj(tpl, "resize: start verification failed"));
+ goto erange;
+ }
+ } else {
+ /* no change, start points to the current partition */
+ DBG(PART, ul_debugobj(tpl, "resize: start unchanged"));
+ start = fdisk_partition_get_start(cur);
+ }
+
+ /* 3a) set new size -- reduce */
+ if (tpl->resize == FDISK_RESIZE_REDUCE && fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: reduce"));
+ size = fdisk_partition_get_size(cur);
+ if (fdisk_partition_get_size(tpl) > size)
+ goto erange;
+ size -= fdisk_partition_get_size(tpl);
+
+ /* 3b) set new size -- enlarge */
+ } else if (tpl->resize == FDISK_RESIZE_ENLARGE && fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: enlarge"));
+ size = fdisk_partition_get_size(cur);
+ size += fdisk_partition_get_size(tpl);
+
+ /* 3c) set new size -- no size specified, enlarge to all freespace */
+ } else if (tpl->resize == FDISK_RESIZE_ENLARGE) {
+ DBG(PART, ul_debugobj(tpl, "resize: enlarge to all possible"));
+ if (resize_get_last_possible(tb, cur, start, &size))
+ goto erange;
+
+ /* 3d) set new size -- absolute number */
+ } else if (fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: new absolute size"));
+ size = fdisk_partition_get_size(tpl);
+ }
+
+ /* 4) verify that size is within the current partition or next free space */
+ xsize = !FDISK_IS_UNDEF(size) ? size : fdisk_partition_get_size(cur);
+
+ if (fdisk_partition_has_size(cur)) {
+ fdisk_sector_t maxsz;
+
+ if (resize_get_last_possible(tb, cur, start, &maxsz))
+ goto erange;
+ DBG(PART, ul_debugobj(tpl, "resize: size=%ju, max=%ju",
+ (uintmax_t) xsize, (uintmax_t) maxsz));
+ if (xsize > maxsz)
+ goto erange;
+ }
+
+ if (FDISK_IS_UNDEF(size)) {
+ DBG(PART, ul_debugobj(tpl, "resize: size unchanged (undefined)"));
+ }
+
+
+ DBG(PART, ul_debugobj(tpl, "<<< resize: SUCCESS: start %ju->%ju; size %ju->%ju",
+ (uintmax_t) fdisk_partition_get_start(cur), (uintmax_t) start,
+ (uintmax_t) fdisk_partition_get_size(cur), (uintmax_t) size));
+ res->start = start;
+ res->size = size;
+ fdisk_unref_table(tb);
+ return 0;
+erange:
+ DBG(PART, ul_debugobj(tpl, "<<< resize: FAILED"));
+ fdisk_warnx(cxt, _("Failed to resize partition #%zu."), partno + 1);
+ fdisk_unref_table(tb);
+ return -ERANGE;
+
+}
+
+/**
+ * fdisk_set_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: new partition setting
+ *
+ * Modifies disklabel according to setting with in @pa.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_set_partition(struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_partition *xpa = pa, *tmp = NULL;
+ int rc, wipe = 0;
+
+ if (!cxt || !cxt->label || !pa)
+ return -EINVAL;
+ if (!cxt->label->op->set_part)
+ return -ENOSYS;
+
+ pa->fs_probed = 0;
+
+ if (!fdisk_is_partition_used(cxt, partno)) {
+ pa->partno = partno;
+ return fdisk_add_partition(cxt, pa, NULL);
+ }
+
+ if (pa->resize || pa->movestart
+ || fdisk_partition_has_start(pa) || fdisk_partition_has_size(pa)) {
+ xpa = __copy_partition(pa);
+ if (!xpa) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ xpa->movestart = 0;
+ xpa->resize = 0;
+ FDISK_INIT_UNDEF(xpa->size);
+ FDISK_INIT_UNDEF(xpa->start);
+
+ rc = recount_resize(cxt, partno, xpa, pa);
+ if (rc)
+ goto done;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "setting partition %zu %p (start=%ju, end=%ju, size=%ju)",
+ partno, xpa,
+ (uintmax_t) fdisk_partition_get_start(xpa),
+ (uintmax_t) fdisk_partition_get_end(xpa),
+ (uintmax_t) fdisk_partition_get_size(xpa)));
+
+ /* disable wipe for old offset/size setting */
+ if (fdisk_get_partition(cxt, partno, &tmp) == 0 && tmp) {
+ wipe = fdisk_set_wipe_area(cxt, fdisk_partition_get_start(tmp),
+ fdisk_partition_get_size(tmp), FALSE);
+ fdisk_unref_partition(tmp);
+ }
+
+ /* call label driver */
+ rc = cxt->label->op->set_part(cxt, partno, xpa);
+
+ /* enable wipe for new offset/size */
+ if (!rc && wipe)
+ fdisk_wipe_partition(cxt, partno, TRUE);
+done:
+ DBG(CXT, ul_debugobj(cxt, "set_partition() rc=%d", rc));
+ if (xpa != pa)
+ fdisk_unref_partition(xpa);
+ return rc;
+}
+
+/**
+ * fdisk_wipe_partition:
+ * @cxt: fdisk context
+ * @partno: partition number
+ * @enable: 0 or 1
+ *
+ * Enable/disable filesystems/RAIDs wiping in area defined by partition start and size.
+ *
+ * Returns: <0 in case of error, 0 on success
+ * Since: 2.29
+ */
+int fdisk_wipe_partition(struct fdisk_context *cxt, size_t partno, int enable)
+{
+ struct fdisk_partition *pa = NULL;
+ int rc;
+
+ rc = fdisk_get_partition(cxt, partno, &pa);
+ if (rc)
+ return rc;
+
+ rc = fdisk_set_wipe_area(cxt, fdisk_partition_get_start(pa),
+ fdisk_partition_get_size(pa), enable);
+ fdisk_unref_partition(pa);
+ return rc < 0 ? rc : 0;
+}
+
+/**
+ * fdisk_partition_has_wipe:
+ * @cxt: fdisk context
+ * @pa: partition
+ *
+ * Since: 2.30
+ *
+ * Returns: 1 if the area specified by @pa will be wiped by write command, or 0.
+ */
+int fdisk_partition_has_wipe(struct fdisk_context *cxt, struct fdisk_partition *pa)
+{
+ return fdisk_has_wipe_area(cxt, fdisk_partition_get_start(pa),
+ fdisk_partition_get_size(pa));
+}
+
+
+/**
+ * fdisk_add_partition:
+ * @cxt: fdisk context
+ * @pa: template for the partition (or NULL)
+ * @partno: NULL or returns new partition number
+ *
+ * If @pa is not specified or any @pa item is missing the libfdisk will ask by
+ * fdisk_ask_ API.
+ *
+ * The @pa template is important for non-interactive partitioning,
+ * especially for MBR where is necessary to differentiate between
+ * primary/logical; this is done by start offset or/and partno.
+ * The rules for MBR:
+ *
+ * A) template specifies start within extended partition: add logical
+ * B) template specifies start out of extended partition: add primary
+ * C) template specifies start (or default), partno < 4: add primary
+ * D) template specifies default start, partno >= 4: add logical
+ *
+ * otherwise MBR driver uses Ask-API to get missing information.
+ *
+ * Adds a new partition to disklabel.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->add_part)
+ return -ENOSYS;
+ if (fdisk_missing_geometry(cxt))
+ return -EINVAL;
+
+ if (pa) {
+ pa->fs_probed = 0;
+ DBG(CXT, ul_debugobj(cxt, "adding new partition %p", pa));
+ if (fdisk_partition_has_start(pa))
+ DBG(CXT, ul_debugobj(cxt, " start: %ju", (uintmax_t) fdisk_partition_get_start(pa)));
+ if (fdisk_partition_has_end(pa))
+ DBG(CXT, ul_debugobj(cxt, " end: %ju", (uintmax_t) fdisk_partition_get_end(pa)));
+ if (fdisk_partition_has_size(pa))
+ DBG(CXT, ul_debugobj(cxt, " size: %ju", (uintmax_t) fdisk_partition_get_size(pa)));
+
+ DBG(CXT, ul_debugobj(cxt, " defaults: start=%s, end=%s, partno=%s",
+ pa->start_follow_default ? "yes" : "no",
+ pa->end_follow_default ? "yes" : "no",
+ pa->partno_follow_default ? "yes" : "no"));
+ } else
+ DBG(CXT, ul_debugobj(cxt, "adding partition"));
+
+ rc = cxt->label->op->add_part(cxt, pa, partno);
+
+ DBG(CXT, ul_debugobj(cxt, "add partition done (rc=%d)", rc));
+ return rc;
+}
+
+/**
+ * fdisk_delete_partition:
+ * @cxt: fdisk context
+ * @partno: partition number to delete (0 is the first partition)
+ *
+ * Deletes a @partno partition from disklabel.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->del_part)
+ return -ENOSYS;
+
+ fdisk_wipe_partition(cxt, partno, 0);
+
+ DBG(CXT, ul_debugobj(cxt, "deleting %s partition number %zd",
+ cxt->label->name, partno));
+ return cxt->label->op->del_part(cxt, partno);
+}
+
+/**
+ * fdisk_delete_all_partitions:
+ * @cxt: fdisk context
+ *
+ * Delete all used partitions from disklabel.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_delete_all_partitions(struct fdisk_context *cxt)
+{
+ size_t i;
+ int rc = 0;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+
+ if (!fdisk_is_partition_used(cxt, i))
+ continue;
+ rc = fdisk_delete_partition(cxt, i);
+ if (rc)
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * fdisk_is_partition_used:
+ * @cxt: context
+ * @n: partition number (0 is the first partition)
+ *
+ * Check if the partition number @n is used by partition table. This function
+ * does not check if the device is used (e.g. mounted) by system!
+ *
+ * This is faster than fdisk_get_partition() + fdisk_partition_is_used().
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->part_is_used)
+ return -ENOSYS;
+
+ return cxt->label->op->part_is_used(cxt, n);
+}
+
diff --git a/libfdisk/src/parttype.c b/libfdisk/src/parttype.c
new file mode 100644
index 0000000..271b671
--- /dev/null
+++ b/libfdisk/src/parttype.c
@@ -0,0 +1,569 @@
+
+#include <ctype.h>
+
+#include "fdiskP.h"
+#include "strutils.h"
+
+/**
+ * SECTION: parttype
+ * @title: Partition types
+ * @short_description: abstraction to partition types
+ *
+ * There are two basic types of parttypes, string based (e.g. GPT)
+ * and code/hex based (e.g. MBR).
+ */
+
+/**
+ * fdisk_new_parttype:
+ *
+ * It's recommended to use fdisk_label_get_parttype_from_code() or
+ * fdisk_label_get_parttype_from_string() for well known types rather
+ * than allocate a new instance.
+ *
+ * Returns: new instance.
+ */
+struct fdisk_parttype *fdisk_new_parttype(void)
+{
+ struct fdisk_parttype *t = calloc(1, sizeof(*t));
+
+ if (!t)
+ return NULL;
+
+ t->refcount = 1;
+ t->flags = FDISK_PARTTYPE_ALLOCATED;
+ DBG(PARTTYPE, ul_debugobj(t, "alloc"));
+ return t;
+}
+
+/**
+ * fdisk_ref_parttype:
+ * @t: partition type
+ *
+ * Increments reference counter for allocated types
+ */
+void fdisk_ref_parttype(struct fdisk_parttype *t)
+{
+ if (fdisk_parttype_is_allocated(t))
+ t->refcount++;
+}
+
+/**
+ * fdisk_unref_parttype
+ * @t: partition pointer
+ *
+ * Decrements reference counter, on zero the @t is automatically
+ * deallocated.
+ */
+void fdisk_unref_parttype(struct fdisk_parttype *t)
+{
+ if (!fdisk_parttype_is_allocated(t))
+ return;
+
+ t->refcount--;
+ if (t->refcount <= 0) {
+ DBG(PARTTYPE, ul_debugobj(t, "free"));
+ free(t->typestr);
+ free(t->name);
+ free(t);
+ }
+}
+
+/**
+ * fdisk_parttype_set_name:
+ * @t: partition type
+ * @str: type name
+ *
+ * Sets type name to allocated partition type, for static types
+ * it returns -EINVAL.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ return strdup_to_struct_member(t, name, str);
+}
+
+/**
+ * fdisk_parttype_set_typestr:
+ * @t: partition type
+ * @str: type identifier (e.g. GUID for GPT)
+ *
+ * Sets type string to allocated partition type, for static types
+ * it returns -EINVAL. Don't use this function for MBR, see
+ * fdisk_parttype_set_code().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ return strdup_to_struct_member(t, typestr, str);
+}
+
+/**
+ * fdisk_parttype_set_code:
+ * @t: partition type
+ * @code: type identifier (e.g. MBR type codes)
+ *
+ * Sets type code to allocated partition type, for static types it returns
+ * -EINVAL. Don't use this function for GPT, see fdisk_parttype_set_typestr().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ t->code = code;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_nparttypes:
+ * @lb: label
+ *
+ * Returns: number of types supported by label.
+ */
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb)
+{
+ if (!lb)
+ return 0;
+ return lb->nparttypes;
+}
+
+/**
+ * fdisk_label_get_parttype:
+ * @lb: label
+ * @n: number
+ *
+ * Returns: return parttype
+ */
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n)
+{
+ if (!lb || n >= lb->nparttypes)
+ return NULL;
+ return &lb->parttypes[n];
+}
+
+/**
+ * fdisk_label_get_parttype_shortcut:
+ * @lb: label
+ * @n: number
+ * @typestr: returns type as string
+ * @shortcut: returns type shortcut string
+ * @alias: returns type alias string
+ *
+ * Returns: return 0 on success, <0 on error, 2 for deprecated alias, 1 for @n out of range
+ *
+ * Since: 2.36
+ */
+int fdisk_label_get_parttype_shortcut(const struct fdisk_label *lb, size_t n,
+ const char **typestr, const char **shortcut, const char **alias)
+{
+ const struct fdisk_shortcut *sc;
+
+ if (!lb)
+ return -EINVAL;
+ if (n >= lb->nparttype_cuts)
+ return 1;
+
+ sc = &lb->parttype_cuts[n];
+ if (typestr)
+ *typestr = sc->data;
+ if (shortcut)
+ *shortcut = sc->shortcut;
+ if (alias)
+ *alias = sc->alias;
+
+ return sc->deprecated == 1 ? 2 : 0;
+
+}
+
+
+/**
+ * fdisk_label_has_code_parttypes:
+ * @lb: label
+ *
+ * Returns: 1 if the label uses code as partition type
+ * identifiers (e.g. MBR) or 0.
+ */
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb)
+{
+ assert(lb);
+
+ if (lb->parttypes && lb->parttypes[0].typestr)
+ return 0;
+ return 1;
+}
+
+/**
+ * fdisk_label_has_parttypes_shortcuts
+ * @lb: label
+ *
+ * Returns: 1 if the label support shortuts/aliases for partition types or 0.
+ *
+ * Since: 2.36
+ */
+int fdisk_label_has_parttypes_shortcuts(const struct fdisk_label *lb)
+{
+ assert(lb);
+ return lb->nparttype_cuts ? 1 : 0;
+}
+
+
+/**
+ * fdisk_label_get_parttype_from_code:
+ * @lb: label
+ * @code: code to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @code.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+ const struct fdisk_label *lb,
+ unsigned int code)
+{
+ size_t i;
+
+ assert(lb);
+
+ if (!lb->nparttypes)
+ return NULL;
+
+ for (i = 0; i < lb->nparttypes; i++)
+ if (lb->parttypes[i].code == code)
+ return &lb->parttypes[i];
+ return NULL;
+}
+
+/**
+ * fdisk_label_get_parttype_from_string:
+ * @lb: label
+ * @str: string to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @str.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ size_t i;
+
+ assert(lb);
+
+ if (!lb->nparttypes)
+ return NULL;
+
+ for (i = 0; i < lb->nparttypes; i++)
+ if (lb->parttypes[i].typestr
+ && strcasecmp(lb->parttypes[i].typestr, str) == 0)
+ return &lb->parttypes[i];
+
+ return NULL;
+}
+
+/**
+ * fdisk_new_unknown_parttype:
+ * @code: type as number
+ * @typestr: type as string
+
+ * Allocates new 'unknown' partition type. Use fdisk_unref_parttype() to
+ * deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+ const char *typestr)
+{
+ struct fdisk_parttype *t = fdisk_new_parttype();
+
+ if (!t)
+ return NULL;
+
+ fdisk_parttype_set_name(t, _("unknown"));
+ fdisk_parttype_set_code(t, code);
+ fdisk_parttype_set_typestr(t, typestr);
+ t->flags |= FDISK_PARTTYPE_UNKNOWN;
+
+ return t;
+}
+
+/**
+ * fdisk_copy_parttype:
+ * @type: type to copy
+ *
+ * Use fdisk_unref_parttype() to deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type)
+{
+ struct fdisk_parttype *t = fdisk_new_parttype();
+
+ if (!t)
+ return NULL;
+
+ fdisk_parttype_set_name(t, type->name);
+ fdisk_parttype_set_code(t, type->code);
+ fdisk_parttype_set_typestr(t, type->typestr);
+
+ return t;
+}
+
+static struct fdisk_parttype *parttype_from_data(
+ const struct fdisk_label *lb,
+ const char *str,
+ unsigned int *xcode,
+ int use_seqnum)
+{
+ struct fdisk_parttype *types, *ret = NULL;
+ char *end = NULL;
+
+ assert(lb);
+ assert(str);
+
+ if (xcode)
+ *xcode = 0;
+ if (!lb->nparttypes)
+ return NULL;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' data", str));
+ types = lb->parttypes;
+
+ if (types[0].typestr == NULL) {
+ unsigned int code;
+
+ DBG(LABEL, ul_debugobj(lb, " +hex"));
+
+ errno = 0;
+ code = strtol(str, &end, 16);
+
+ if (errno || *end != '\0') {
+ DBG(LABEL, ul_debugobj(lb, " failed: %m"));
+ return NULL;
+ }
+ if (xcode)
+ *xcode = code;
+ ret = fdisk_label_get_parttype_from_code(lb, code);
+ } else {
+ DBG(LABEL, ul_debugobj(lb, " +string"));
+
+ /* maybe specified by type string (e.g. UUID) */
+ ret = fdisk_label_get_parttype_from_string(lb, str);
+
+ if (!ret) {
+ /* maybe specified by order number */
+ int i;
+
+ errno = 0;
+ i = strtol(str, &end, 0);
+
+ if (use_seqnum && errno == 0
+ && *end == '\0' && i > 0
+ && i - 1 < (int) lb->nparttypes)
+ ret = &types[i - 1];
+ }
+ }
+
+ if (ret)
+ DBG(PARTTYPE, ul_debugobj(ret, " result '%s'", ret->name));
+ return ret;
+}
+
+static struct fdisk_parttype *parttype_from_shortcut(
+ const struct fdisk_label *lb,
+ const char *str, int deprecated)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' shortcut", str));
+
+ for (i = 0; i < lb->nparttype_cuts; i++) {
+ const struct fdisk_shortcut *sc = &lb->parttype_cuts[i];
+
+ if (sc->deprecated && !deprecated)
+ continue;
+ if (sc->shortcut && strcmp(sc->shortcut, str) == 0)
+ return parttype_from_data(lb, sc->data, NULL, 0);
+ }
+ return NULL;
+}
+
+static struct fdisk_parttype *parttype_from_alias(
+ const struct fdisk_label *lb,
+ const char *str, int deprecated)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' alias", str));
+
+ for (i = 0; i < lb->nparttype_cuts; i++) {
+ const struct fdisk_shortcut *sc = &lb->parttype_cuts[i];
+
+ if (sc->deprecated && !deprecated)
+ continue;
+ if (sc->alias && strcmp(sc->alias, str) == 0)
+ return parttype_from_data(lb, sc->data, NULL, 0);
+ }
+ return NULL;
+}
+
+static struct fdisk_parttype *parttype_from_name(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' name", str));
+
+ for (i = 0; i < lb->nparttypes; i++) {
+ const char *name = lb->parttypes[i].name;
+
+ if (name && *name && ul_stralnumcmp(name, str) == 0)
+ return &lb->parttypes[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_label_advparse_parttype:
+ * @lb: label
+ * @str: string to parse from
+ * @flags: FDISK_PARTTYPE_PARSE_*
+ *
+ * This function is advanced partition types parser. It parses partition type
+ * from @str according to the label. The function returns a pointer to static
+ * table of the partition types, or newly allocated partition type for unknown
+ * types (see fdisk_parttype_is_unknown(). It's safe to call fdisk_unref_parttype()
+ * for all results.
+ *
+ * The @str may be type data (hex code or UUID), alias or shortcut. For GPT
+ * also sequence number of the type in the list of the supported types.
+ *
+ * Returns: pointer to type or NULL on error.
+ */
+struct fdisk_parttype *fdisk_label_advparse_parttype(
+ const struct fdisk_label *lb,
+ const char *str,
+ int flags)
+{
+ struct fdisk_parttype *res = NULL;
+ unsigned int code = 0;
+
+ if (!lb || !lb->nparttypes)
+ return NULL;
+
+ DBG(LABEL, ul_debugobj(lb, "parsing '%s' (%s) type", str, lb->name));
+
+ if ((flags & FDISK_PARTTYPE_PARSE_DATA)
+ && !(flags & FDISK_PARTTYPE_PARSE_DATALAST))
+ res = parttype_from_data(lb, str, &code,
+ flags & FDISK_PARTTYPE_PARSE_SEQNUM);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_ALIAS))
+ res = parttype_from_alias(lb, str,
+ flags & FDISK_PARTTYPE_PARSE_DEPRECATED);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_SHORTCUT))
+ res = parttype_from_shortcut(lb, str,
+ flags & FDISK_PARTTYPE_PARSE_DEPRECATED);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_NAME))
+ res = parttype_from_name(lb, str);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_DATA)
+ && (flags & FDISK_PARTTYPE_PARSE_DATALAST))
+ res = parttype_from_data(lb, str, &code,
+ flags & FDISK_PARTTYPE_PARSE_SEQNUM);
+
+ if (!res && !(flags & FDISK_PARTTYPE_PARSE_NOUNKNOWN)) {
+ if (lb->parttypes[0].typestr)
+ res = fdisk_new_unknown_parttype(0, str);
+ else
+ res = fdisk_new_unknown_parttype(code, NULL);
+ }
+
+ if (res)
+ DBG(PARTTYPE, ul_debugobj(res, "returns parsed '%s' [%s] partition type",
+ res->name, res->typestr ? : ""));
+ return res;
+}
+
+/**
+ * fdisk_label_parse_parttype:
+ * @lb: label
+ * @str: string to parse from (type name, UUID, etc.)
+ *
+ * Parses partition type from @str according to the label. The function returns
+ * a pointer to static table of the partition types, or newly allocated
+ * partition type for unknown types (see fdisk_parttype_is_unknown(). It's
+ * safe to call fdisk_unref_parttype() for all results.
+ *
+ * Note that for GPT it accepts sequence number of UUID.
+ *
+ * Returns: pointer to type or NULL on error.
+ */
+struct fdisk_parttype *fdisk_label_parse_parttype(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ return fdisk_label_advparse_parttype(lb, str, FDISK_PARTTYPE_PARSE_DATA);
+}
+
+/**
+ * fdisk_parttype_get_string:
+ * @t: type
+ *
+ * Returns: partition type string (e.g. GUID for GPT)
+ */
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->typestr && *t->typestr ? t->typestr : NULL;
+}
+
+/**
+ * fdisk_parttype_get_code:
+ * @t: type
+ *
+ * Returns: partition type code (e.g. for MBR)
+ */
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->code;
+}
+
+/**
+ * fdisk_parttype_get_name:
+ * @t: type
+ *
+ * Returns: partition type human readable name
+ */
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->name;
+}
+
+/**
+ * fdisk_parttype_is_unknown:
+ * @t: type
+ *
+ * Checks for example result from fdisk_label_parse_parttype().
+ *
+ * Returns: 1 is type is "unknown" or 0.
+ */
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t)
+{
+ return t && (t->flags & FDISK_PARTTYPE_UNKNOWN) ? 1 : 0;
+}
diff --git a/libfdisk/src/script.c b/libfdisk/src/script.c
new file mode 100644
index 0000000..d93b981
--- /dev/null
+++ b/libfdisk/src/script.c
@@ -0,0 +1,1756 @@
+
+#include "fdiskP.h"
+#include "strutils.h"
+#include "carefulputc.h"
+#include "mangle.h"
+#include "jsonwrt.h"
+#include "fileutils.h"
+
+#ifdef FUZZ_TARGET
+#include "fuzz.h"
+#endif
+
+/**
+ * SECTION: script
+ * @title: Script
+ * @short_description: complex way to create and dump partition table
+ *
+ * This interface can be used to compose in-memory partition table with all details,
+ * write all partition table description to human readable text file, read it
+ * from the file, and apply the script to on-disk label.
+ *
+ * The libfdisk scripts are based on original sfdisk script (dumps). Each
+ * script has two parts: script headers and partition table entries
+ * (partitions). The script is possible to dump in JSON too (read JSON is not
+ * implemented yet).
+ *
+ * For more details about script format see sfdisk man page.
+ *
+ * There are four ways how to build the script:
+ *
+ * - read the current on-disk partition table by fdisk_script_read_context())
+ * - read it from text file by fdisk_script_read_file()
+ * - read it interactively from user by fdisk_script_read_line() and fdisk_script_set_fgets()
+ * - manually in code by fdisk_script_set_header() and fdisk_script_set_table()
+ *
+ * The read functions fdisk_script_read_context() and fdisk_script_read_file()
+ * creates always a new script partition table. The table (see
+ * fdisk_script_get_table()) is possible to modify by standard
+ * fdisk_table_...() functions and then apply by fdisk_apply_script().
+ *
+ * Note that script API is fully non-interactive and forces libfdisk to not use
+ * standard dialog driven partitioning as we have in fdisk(8).
+ */
+
+/* script header (e.g. unit: sectors) */
+struct fdisk_scriptheader {
+ struct list_head headers;
+ char *name;
+ char *data;
+};
+
+/* script control struct */
+struct fdisk_script {
+ struct fdisk_table *table;
+ struct list_head headers;
+ struct fdisk_context *cxt;
+
+ int refcount;
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *);
+ void *userdata;
+
+ /* parser's state */
+ size_t nlines;
+ struct fdisk_label *label;
+
+ unsigned int json : 1, /* JSON output */
+ force_label : 1; /* label: <name> specified */
+};
+
+static void fdisk_script_free_header(struct fdisk_scriptheader *fi)
+{
+ if (!fi)
+ return;
+
+ DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name));
+ free(fi->name);
+ free(fi->data);
+ list_del(&fi->headers);
+ free(fi);
+}
+
+/**
+ * fdisk_new_script:
+ * @cxt: context
+ *
+ * The script hold fdisk_table and additional information to read/write
+ * script to the file.
+ *
+ * Returns: newly allocated script struct.
+ */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt)
+{
+ struct fdisk_script *dp = NULL;
+
+ dp = calloc(1, sizeof(*dp));
+ if (!dp)
+ return NULL;
+
+ DBG(SCRIPT, ul_debugobj(dp, "alloc"));
+ dp->refcount = 1;
+ dp->cxt = cxt;
+ fdisk_ref_context(cxt);
+
+ INIT_LIST_HEAD(&dp->headers);
+ return dp;
+}
+
+/**
+ * fdisk_new_script_from_file:
+ * @cxt: context
+ * @filename: path to the script file
+ *
+ * Allocates a new script and reads script from @filename.
+ *
+ * Returns: new script instance or NULL in case of error (check errno for more details).
+ */
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+ const char *filename)
+{
+ int rc;
+ FILE *f;
+ struct fdisk_script *dp, *res = NULL;
+
+ assert(cxt);
+ assert(filename);
+
+ DBG(SCRIPT, ul_debug("opening %s", filename));
+ f = fopen(filename, "r");
+ if (!f)
+ return NULL;
+
+ dp = fdisk_new_script(cxt);
+ if (!dp)
+ goto done;
+
+ rc = fdisk_script_read_file(dp, f);
+ if (rc) {
+ errno = -rc;
+ goto done;
+ }
+
+ res = dp;
+done:
+ fclose(f);
+ if (!res)
+ fdisk_unref_script(dp);
+ else
+ errno = 0;
+
+ return res;
+}
+
+/**
+ * fdisk_ref_script:
+ * @dp: script pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_script(struct fdisk_script *dp)
+{
+ if (dp)
+ dp->refcount++;
+}
+
+static void fdisk_reset_script(struct fdisk_script *dp)
+{
+ assert(dp);
+
+ DBG(SCRIPT, ul_debugobj(dp, "reset"));
+
+ if (dp->table)
+ fdisk_reset_table(dp->table);
+
+ while (!list_empty(&dp->headers)) {
+ struct fdisk_scriptheader *fi = list_entry(dp->headers.next,
+ struct fdisk_scriptheader, headers);
+ fdisk_script_free_header(fi);
+ }
+ INIT_LIST_HEAD(&dp->headers);
+}
+
+/**
+ * fdisk_unref_script:
+ * @dp: script pointer
+ *
+ * Decrements reference counter, on zero the @dp is automatically
+ * deallocated.
+ */
+void fdisk_unref_script(struct fdisk_script *dp)
+{
+ if (!dp)
+ return;
+
+ dp->refcount--;
+ if (dp->refcount <= 0) {
+ fdisk_reset_script(dp);
+ fdisk_unref_context(dp->cxt);
+ fdisk_unref_table(dp->table);
+ DBG(SCRIPT, ul_debugobj(dp, "free script"));
+ free(dp);
+ }
+}
+
+/**
+ * fdisk_script_set_userdata
+ * @dp: script
+ * @data: your data
+ *
+ * Sets data usable for example in callbacks (e.g fdisk_script_set_fgets()).
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_set_userdata(struct fdisk_script *dp, void *data)
+{
+ assert(dp);
+ dp->userdata = data;
+ return 0;
+}
+
+/**
+ * fdisk_script_get_userdata
+ * @dp: script
+ *
+ * Returns: user data or NULL.
+ */
+void *fdisk_script_get_userdata(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->userdata;
+}
+
+static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp,
+ const char *name)
+{
+ struct list_head *p;
+
+ list_for_each(p, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers);
+
+ if (strcasecmp(fi->name, name) == 0)
+ return fi;
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_script_get_header:
+ * @dp: script instance
+ * @name: header name
+ *
+ * Returns: pointer to header data or NULL.
+ */
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name)
+{
+ struct fdisk_scriptheader *fi;
+
+ assert(dp);
+ assert(name);
+
+ fi = script_get_header(dp, name);
+ return fi ? fi->data : NULL;
+}
+
+/**
+ * fdisk_script_set_header:
+ * @dp: script instance
+ * @name: header name
+ * @data: header data (or NULL)
+ *
+ * The headers are used as global options for whole partition
+ * table, always one header per line.
+ *
+ * If no @data is specified then the header is removed. If header does not exist
+ * and @data is specified then a new header is added.
+ *
+ * Note that libfdisk can be used to specify arbitrary custom header, the default
+ * built-in headers are "unit" and "label", and some label specific headers
+ * (for example "uuid" and "name" for GPT).
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_script_set_header(struct fdisk_script *dp,
+ const char *name,
+ const char *data)
+{
+ struct fdisk_scriptheader *fi;
+
+ if (!dp || !name)
+ return -EINVAL;
+
+ fi = script_get_header(dp, name);
+ if (!fi && !data)
+ return 0; /* want to remove header that does not exist, success */
+
+ if (!data) {
+ DBG(SCRIPT, ul_debugobj(dp, "freeing header %s", name));
+
+ /* no data, remove the header */
+ fdisk_script_free_header(fi);
+ return 0;
+ }
+
+ if (!fi) {
+ int rc;
+
+ DBG(SCRIPT, ul_debugobj(dp, "setting new header %s='%s'", name, data));
+
+ /* new header */
+ fi = calloc(1, sizeof(*fi));
+ if (!fi)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&fi->headers);
+
+ rc = strdup_to_struct_member(fi, name, name);
+ if (!rc)
+ rc = strdup_to_struct_member(fi, data, data);
+ if (rc) {
+ fdisk_script_free_header(fi);
+ return rc;
+ }
+ list_add_tail(&fi->headers, &dp->headers);
+ } else {
+ /* update existing */
+ char *x = strdup(data);
+
+ DBG(SCRIPT, ul_debugobj(dp, "update '%s' header '%s' -> '%s'", name, fi->data, data));
+
+ if (!x)
+ return -ENOMEM;
+ free(fi->data);
+ fi->data = x;
+ }
+
+ if (strcmp(name, "label") == 0)
+ dp->label = NULL;
+
+ return 0;
+}
+
+/**
+ * fdisk_script_get_table:
+ * @dp: script
+ *
+ * The table represents partitions holded by the script. The table is possible to
+ * fill by fdisk_script_read_context() or fdisk_script_read_file(). All the "read"
+ * functions remove old partitions from the table. See also fdisk_script_set_table().
+ *
+ * Returns: NULL or script table.
+ */
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp)
+{
+ assert(dp);
+
+ if (!dp->table)
+ /*
+ * Make sure user has access to the same table as script. If
+ * there is no table then create a new one and reuse it later.
+ */
+ dp->table = fdisk_new_table();
+
+ return dp->table;
+}
+
+/**
+ * fdisk_script_set_table:
+ * @dp: script
+ * @tb: table
+ *
+ * Replaces table used by script and creates a new reference to @tb. This
+ * function can be used to generate a new script table independently on the current
+ * context and without any file reading.
+ *
+ * This is useful for example to create partition table with the same basic
+ * settings (e.g. label-id, ...) but with different partitions -- just call
+ * fdisk_script_read_context() to get current settings and then
+ * fdisk_script_set_table() to set a different layout.
+ *
+ * If @tb is NULL then the current script table is unreferenced.
+ *
+ * Note that script read_ functions (e.g. fdisk_script_read_context()) create
+ * always a new script table.
+ *
+ * Returns: 0 on success, <0 on error
+ *
+ * Since: 2.35
+ */
+int fdisk_script_set_table(struct fdisk_script *dp, struct fdisk_table *tb)
+{
+ if (!dp)
+ return -EINVAL;
+
+ fdisk_ref_table(tb);
+ fdisk_unref_table(dp->table);
+ dp->table = tb;
+
+ DBG(SCRIPT, ul_debugobj(dp, "table replaced"));
+ return 0;
+}
+
+static struct fdisk_label *script_get_label(struct fdisk_script *dp)
+{
+ assert(dp);
+ assert(dp->cxt);
+
+ if (!dp->label) {
+ dp->label = fdisk_get_label(dp->cxt,
+ fdisk_script_get_header(dp, "label"));
+ DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : ""));
+ }
+ return dp->label;
+}
+
+/**
+ * fdisk_script_get_nlines:
+ * @dp: script
+ *
+ * Returns: number of parsed lines or <0 on error.
+ */
+int fdisk_script_get_nlines(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->nlines;
+}
+
+/**
+ * fdisk_script_has_force_label:
+ * @dp: script
+ *
+ * Label has been explicitly specified in the script.
+ *
+ * Since: 2.30
+ *
+ * Returns: true if "label: name" has been parsed.
+ */
+int fdisk_script_has_force_label(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->force_label;
+}
+
+
+/**
+ * fdisk_script_read_context:
+ * @dp: script
+ * @cxt: context
+ *
+ * Reads data from the @cxt context (on disk partition table) into the script.
+ * If the context is not specified then defaults to context used for fdisk_new_script().
+ *
+ * Return: 0 on success, <0 on error.
+ */
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt)
+{
+ struct fdisk_label *lb;
+ int rc;
+ char *p = NULL;
+ char buf[64];
+
+ if (!dp || (!cxt && !dp->cxt))
+ return -EINVAL;
+
+ if (!cxt)
+ cxt = dp->cxt;
+
+ DBG(SCRIPT, ul_debugobj(dp, "reading context into script"));
+ fdisk_reset_script(dp);
+
+ lb = fdisk_get_label(cxt, NULL);
+ if (!lb)
+ return -EINVAL;
+
+ /* allocate (if not yet) and fill table */
+ rc = fdisk_get_partitions(cxt, &dp->table);
+ if (rc)
+ return rc;
+
+ /* generate headers */
+ rc = fdisk_script_set_header(dp, "label", fdisk_label_get_name(lb));
+
+ if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
+ rc = fdisk_script_set_header(dp, "label-id", p);
+ free(p);
+ }
+ if (!rc && cxt->dev_path)
+ rc = fdisk_script_set_header(dp, "device", cxt->dev_path);
+ if (!rc)
+ rc = fdisk_script_set_header(dp, "unit", "sectors");
+
+ if (!rc && fdisk_is_label(cxt, GPT)) {
+ struct fdisk_labelitem item = FDISK_LABELITEM_INIT;
+
+ /* first-lba */
+ rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_FIRSTLBA, &item);
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
+ rc = fdisk_script_set_header(dp, "first-lba", buf);
+ }
+
+ /* last-lba */
+ if (!rc)
+ rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_LASTLBA, &item);
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
+ rc = fdisk_script_set_header(dp, "last-lba", buf);
+ }
+
+ /* table-length */
+ if (!rc) {
+ size_t n = fdisk_get_npartitions(cxt);
+ if (n != FDISK_GPT_NPARTITIONS_DEFAULT) {
+ snprintf(buf, sizeof(buf), "%zu", n);
+ rc = fdisk_script_set_header(dp, "table-length", buf);
+ }
+ }
+ }
+
+ if (!rc && fdisk_get_grain_size(cxt) != 2048 * 512) {
+ snprintf(buf, sizeof(buf), "%lu", fdisk_get_grain_size(cxt));
+ rc = fdisk_script_set_header(dp, "grain", buf);
+ }
+
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%lu", fdisk_get_sector_size(cxt));
+ rc = fdisk_script_set_header(dp, "sector-size", buf);
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * fdisk_script_enable_json:
+ * @dp: script
+ * @json: 0 or 1
+ *
+ * Disable/Enable JSON output format.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_enable_json(struct fdisk_script *dp, int json)
+{
+ assert(dp);
+
+ dp->json = json;
+ return 0;
+}
+
+static int write_file_json(struct fdisk_script *dp, FILE *f)
+{
+ struct list_head *h;
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ const char *devname = NULL;
+ int ct = 0;
+ struct ul_jsonwrt json;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "writing json dump to file"));
+
+ ul_jsonwrt_init(&json, f, 0);
+ ul_jsonwrt_root_open(&json);
+
+ ul_jsonwrt_object_open(&json, "partitiontable");
+
+ /* script headers */
+ list_for_each(h, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
+ const char *name = fi->name;
+ int num = 0;
+
+ if (strcmp(name, "first-lba") == 0) {
+ name = "firstlba";
+ num = 1;
+ } else if (strcmp(name, "last-lba") == 0) {
+ name = "lastlba";
+ num = 1;
+ } else if (strcmp(name, "sector-size") == 0) {
+ name = "sectorsize";
+ num = 1;
+ } else if (strcmp(name, "label-id") == 0)
+ name = "id";
+
+ if (num)
+ ul_jsonwrt_value_raw(&json, name, fi->data);
+ else
+ ul_jsonwrt_value_s(&json, name, fi->data);
+
+ if (strcmp(name, "device") == 0)
+ devname = fi->data;
+ }
+
+
+ if (!dp->table || fdisk_table_is_empty(dp->table)) {
+ DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
+ goto done;
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
+
+ ul_jsonwrt_array_open(&json, "partitions");
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
+ char *p = NULL;
+
+ ct++;
+ ul_jsonwrt_object_open(&json, NULL);
+ if (devname)
+ p = fdisk_partname(devname, pa->partno + 1);
+ if (p) {
+ DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
+ ul_jsonwrt_value_s(&json, "node", p);
+ free(p);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ ul_jsonwrt_value_u64(&json, "start", (uintmax_t)pa->start);
+
+ if (fdisk_partition_has_size(pa))
+ ul_jsonwrt_value_u64(&json, "size", (uintmax_t)pa->size);
+
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ ul_jsonwrt_value_s(&json, "type", fdisk_parttype_get_string(pa->type));
+
+ else if (pa->type) {
+ ul_jsonwrt_value_open(&json, "type");
+ fprintf(f, "\"%x\"", fdisk_parttype_get_code(pa->type));
+ ul_jsonwrt_value_close(&json);
+ }
+
+ if (pa->uuid)
+ ul_jsonwrt_value_s(&json, "uuid", pa->uuid);
+ if (pa->name && *pa->name)
+ ul_jsonwrt_value_s(&json, "name", pa->name);
+
+ /* for MBR attr=80 means bootable */
+ if (pa->attrs) {
+ struct fdisk_label *lb = script_get_label(dp);
+
+ if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
+ ul_jsonwrt_value_s(&json, "attrs", pa->attrs);
+ }
+
+ if (fdisk_partition_is_bootable(pa))
+ ul_jsonwrt_value_boolean(&json, "bootable", 1);
+ ul_jsonwrt_object_close(&json);
+ }
+
+ ul_jsonwrt_array_close(&json);
+done:
+ ul_jsonwrt_object_close(&json);
+ ul_jsonwrt_root_close(&json);
+
+ DBG(SCRIPT, ul_debugobj(dp, "write script done"));
+ return 0;
+}
+
+static int write_file_sfdisk(struct fdisk_script *dp, FILE *f)
+{
+ struct list_head *h;
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ const char *devname = NULL;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "writing sfdisk-like script to file"));
+
+ /* script headers */
+ list_for_each(h, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
+ fprintf(f, "%s: %s\n", fi->name, fi->data);
+ if (strcmp(fi->name, "device") == 0)
+ devname = fi->data;
+ }
+
+ if (!dp->table || fdisk_table_is_empty(dp->table)) {
+ DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
+ return 0;
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
+
+ fputc('\n', f);
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
+ char *p = NULL;
+
+ if (devname)
+ p = fdisk_partname(devname, pa->partno + 1);
+ if (p) {
+ DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
+ fprintf(f, "%s :", p);
+ free(p);
+ } else
+ fprintf(f, "%zu :", pa->partno + 1);
+
+ if (fdisk_partition_has_start(pa))
+ fprintf(f, " start=%12ju", (uintmax_t)pa->start);
+ if (fdisk_partition_has_size(pa))
+ fprintf(f, ", size=%12ju", (uintmax_t)pa->size);
+
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
+ else if (pa->type)
+ fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));
+
+ if (pa->uuid)
+ fprintf(f, ", uuid=%s", pa->uuid);
+ if (pa->name && *pa->name) {
+ fputs(", name=", f);
+ fputs_quoted(pa->name, f);
+ }
+
+ /* for MBR attr=80 means bootable */
+ if (pa->attrs) {
+ struct fdisk_label *lb = script_get_label(dp);
+
+ if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
+ fprintf(f, ", attrs=\"%s\"", pa->attrs);
+ }
+ if (fdisk_partition_is_bootable(pa))
+ fprintf(f, ", bootable");
+ fputc('\n', f);
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "write script done"));
+ return 0;
+}
+
+/**
+ * fdisk_script_write_file:
+ * @dp: script
+ * @f: output file
+ *
+ * Writes script @dp to the file @f.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
+{
+ assert(dp);
+
+ if (dp->json)
+ return write_file_json(dp, f);
+
+ return write_file_sfdisk(dp, f);
+}
+
+static inline int is_header_line(const char *s)
+{
+ const char *p = strchr(s, ':');
+
+ if (!p || p == s || !*(p + 1) || strchr(s, '='))
+ return 0;
+
+ return 1;
+}
+
+/* parses "<name>: value", note modifies @s*/
+static int parse_line_header(struct fdisk_script *dp, char *s)
+{
+ size_t i;
+ char *name, *value;
+ static const char *supported[] = {
+ "label", "unit", "label-id", "device", "grain",
+ "first-lba", "last-lba", "table-length", "sector-size"
+ };
+
+ DBG(SCRIPT, ul_debugobj(dp, " parse header '%s'", s));
+
+ if (!s || !*s)
+ return -EINVAL;
+
+ name = s;
+ value = strchr(s, ':');
+ if (!value)
+ return -EINVAL;
+ *value = '\0';
+ value++;
+
+ ltrim_whitespace((unsigned char *) name);
+ rtrim_whitespace((unsigned char *) name);
+ ltrim_whitespace((unsigned char *) value);
+ rtrim_whitespace((unsigned char *) value);
+
+ if (!*name || !*value)
+ return -EINVAL;
+
+ /* check header name */
+ for (i = 0; i < ARRAY_SIZE(supported); i++) {
+ if (strcmp(name, supported[i]) == 0)
+ break;
+ }
+ if (i == ARRAY_SIZE(supported))
+ return -ENOTSUP;
+
+ /* header specific actions */
+ if (strcmp(name, "label") == 0) {
+ if (dp->cxt && !fdisk_get_label(dp->cxt, value))
+ return -EINVAL; /* unknown label name */
+ dp->force_label = 1;
+
+ } else if (strcmp(name, "unit") == 0) {
+ if (strcmp(value, "sectors") != 0)
+ return -EINVAL; /* only "sectors" supported */
+
+ }
+
+ return fdisk_script_set_header(dp, name, value);
+}
+
+static int partno_from_devname(char *s)
+{
+ intmax_t num;
+ size_t sz;
+ char *end, *p;
+
+ if (!s || !*s)
+ return -1;
+
+ sz = rtrim_whitespace((unsigned char *)s);
+ end = p = s + sz;
+
+ while (p > s && isdigit(*(p - 1)))
+ p--;
+ if (p == end)
+ return -1;
+ end = NULL;
+ errno = 0;
+ num = strtol(p, &end, 10);
+ if (errno || !end || p == end)
+ return -1;
+
+ if (num < INT32_MIN || num > INT32_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ return num - 1;
+}
+
+
+/* returns zero terminated string with next token and @str is updated */
+static char *next_token(char **str)
+{
+ char *tk_begin = NULL,
+ *tk_end = NULL,
+ *end = NULL,
+ *p;
+ int open_quote = 0, terminated = 0;
+
+ for (p = *str; p && *p; p++) {
+ if (!tk_begin) {
+ if (isblank(*p))
+ continue;
+ tk_begin = *p == '"' ? p + 1 : p;
+ }
+ if (*p == '"')
+ open_quote ^= 1;
+ if (open_quote)
+ continue;
+ if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
+ tk_end = p;
+ else if (*(p + 1) == '\0')
+ tk_end = p + 1;
+ if (tk_begin && tk_end)
+ break;
+ }
+
+ if (!tk_end)
+ return NULL;
+
+ end = tk_end;
+
+ /* skip closing quotes */
+ if (*end == '"')
+ end++;
+
+ /* token is terminated by blank (or blank is before "," or ";") */
+ if (isblank(*end)) {
+ end = (char *) skip_blank(end);
+ terminated++;
+ }
+
+ /* token is terminated by "," or ";" */
+ if (*end == ',' || *end == ';') {
+ end++;
+ terminated++;
+
+ /* token is terminated by \0 */
+ } else if (!*end)
+ terminated++;
+
+ if (!terminated) {
+ DBG(SCRIPT, ul_debug("unterminated token '%s'", end));
+ return NULL;
+ }
+
+ /* skip extra space after terminator */
+ end = (char *) skip_blank(end);
+
+ *tk_end = '\0';
+ *str = end;
+ return tk_begin;
+}
+
+/*
+ * "[-]<,;>"
+ * "[ ]<,;>"
+ * "- <value>"
+ */
+static int is_default_value(char **str)
+{
+ char *p = (char *) skip_blank(*str);
+ int blank = 0;
+
+ if (*p == '-') {
+ char *x = ++p;
+ p = (char *) skip_blank(x);
+ blank = x < p; /* "- " */
+ }
+
+ if (*p == ';' || *p == ',') {
+ *str = ++p;
+ return 1;
+ }
+ if (*p == '\0' || blank) {
+ *str = p;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int next_string(char **s, char **str)
+{
+ char *tk, *p = NULL;
+ int rc = -EINVAL;
+
+ assert(s);
+ assert(str);
+
+ tk = next_token(s);
+ if (tk) {
+ p = strdup(tk);
+ rc = p ? 0 : -ENOMEM;
+ }
+
+ *str = p;
+ return rc;
+}
+
+static int skip_optional_sign(char **str)
+{
+ char *p = (char *) skip_blank(*str);
+
+ if (*p == '-' || *p == '+') {
+ *str = p+1;
+ return *p;
+ }
+ return 0;
+}
+
+static int parse_start_value(struct fdisk_script *dp, struct fdisk_partition *pa, char **str)
+{
+ char *tk;
+ int rc = 0;
+
+ assert(str);
+
+ if (is_default_value(str)) {
+ fdisk_partition_start_follow_default(pa, 1);
+ return 0;
+ }
+
+ tk = next_token(str);
+ if (!tk)
+ return -EINVAL;
+
+ if (strcmp(tk, "+") == 0) {
+ fdisk_partition_start_follow_default(pa, 1);
+ pa->movestart = FDISK_MOVE_DOWN;
+ } else {
+ int pow = 0, sign = skip_optional_sign(&tk);
+ uint64_t num;
+
+ rc = parse_size(tk, (uintmax_t *) &num, &pow);
+ if (!rc) {
+ if (pow) { /* specified as <num><suffix> */
+ if (!dp->cxt->sector_size) {
+ rc = -EINVAL;
+ goto done;
+ }
+ num /= dp->cxt->sector_size;
+ }
+ fdisk_partition_set_start(pa, num);
+
+ pa->movestart = sign == '-' ? FDISK_MOVE_DOWN :
+ sign == '+' ? FDISK_MOVE_UP :
+ FDISK_MOVE_NONE;
+ }
+ fdisk_partition_start_follow_default(pa, 0);
+ }
+
+done:
+ DBG(SCRIPT, ul_debugobj(dp, " start parse result: rc=%d, move=%s, start=%ju, default=%s",
+ rc, pa->movestart == FDISK_MOVE_DOWN ? "down" :
+ pa->movestart == FDISK_MOVE_UP ? "up" : "none",
+ pa->start,
+ pa->start_follow_default ? "on" : "off"));
+ return rc;
+}
+
+static int parse_size_value(struct fdisk_script *dp, struct fdisk_partition *pa, char **str)
+{
+ char *tk;
+ int rc = 0;
+
+ if (is_default_value(str)) {
+ fdisk_partition_end_follow_default(pa, 1);
+ return 0;
+ }
+
+ tk = next_token(str);
+ if (!tk)
+ return -EINVAL;
+
+ if (strcmp(tk, "+") == 0) {
+ fdisk_partition_end_follow_default(pa, 1);
+ pa->resize = FDISK_RESIZE_ENLARGE;
+ } else {
+ /* '[+-]<number>[<suffix] */
+ int pow = 0, sign = skip_optional_sign(&tk);
+ uint64_t num;
+
+ rc = parse_size(tk, (uintmax_t *) &num, &pow);
+ if (!rc) {
+ if (pow) { /* specified as <size><suffix> */
+ if (!dp->cxt->sector_size) {
+ rc = -EINVAL;
+ goto done;
+ }
+ num /= dp->cxt->sector_size;
+ } else /* specified as number of sectors */
+ fdisk_partition_size_explicit(pa, 1);
+
+ fdisk_partition_set_size(pa, num);
+ pa->resize = sign == '-' ? FDISK_RESIZE_REDUCE :
+ sign == '+' ? FDISK_RESIZE_ENLARGE :
+ FDISK_RESIZE_NONE;
+ }
+ fdisk_partition_end_follow_default(pa, 0);
+ }
+
+done:
+ DBG(SCRIPT, ul_debugobj(dp, " size parse result: rc=%d, move=%s, size=%ju, default=%s",
+ rc, pa->resize == FDISK_RESIZE_REDUCE ? "reduce" :
+ pa->resize == FDISK_RESIZE_ENLARGE ? "enlage" : "none",
+ pa->size,
+ pa->end_follow_default ? "on" : "off"));
+ return rc;
+}
+
+
+#define FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS \
+ (FDISK_PARTTYPE_PARSE_DATA | FDISK_PARTTYPE_PARSE_DATALAST | \
+ FDISK_PARTTYPE_PARSE_SHORTCUT | FDISK_PARTTYPE_PARSE_ALIAS | \
+ FDISK_PARTTYPE_PARSE_NAME | \
+ FDISK_PARTTYPE_PARSE_DEPRECATED)
+
+/* dump format
+ * <device>: start=<num>, size=<num>, type=<string>, ...
+ */
+static int parse_line_nameval(struct fdisk_script *dp, char *s)
+{
+ char *p, *x;
+ struct fdisk_partition *pa;
+ int rc = 0;
+ int pno;
+
+ assert(dp);
+ assert(s);
+ assert(dp->table);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s));
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ return -ENOMEM;
+
+ fdisk_partition_start_follow_default(pa, 1);
+ fdisk_partition_end_follow_default(pa, 1);
+ fdisk_partition_partno_follow_default(pa, 1);
+
+ /* set partno */
+ p = strchr(s, ':');
+ x = strchr(s, '=');
+ if (p && (!x || p < x)) {
+ *p = '\0';
+ p++;
+
+ pno = partno_from_devname(s);
+ if (pno >= 0) {
+ fdisk_partition_partno_follow_default(pa, 0);
+ fdisk_partition_set_partno(pa, pno);
+ }
+ } else
+ p = s;
+
+ while (rc == 0 && p && *p) {
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
+ p = (char *) skip_blank(p);
+
+ if (!strncasecmp(p, "start=", 6)) {
+ p += 6;
+ rc = parse_start_value(dp, pa, &p);
+
+ } else if (!strncasecmp(p, "size=", 5)) {
+ p += 5;
+ rc = parse_size_value(dp, pa, &p);
+
+ } else if (!strncasecmp(p, "bootable", 8)) {
+ /* we use next_token() to skip possible extra space */
+ char *tk = next_token(&p);
+ if (tk && strcasecmp(tk, "bootable") == 0)
+ pa->boot = 1;
+ else
+ rc = -EINVAL;
+
+ } else if (!strncasecmp(p, "attrs=", 6)) {
+ p += 6;
+ free(pa->attrs);
+ rc = next_string(&p, &pa->attrs);
+
+ } else if (!strncasecmp(p, "uuid=", 5)) {
+ p += 5;
+ free(pa->uuid);
+ rc = next_string(&p, &pa->uuid);
+
+ } else if (!strncasecmp(p, "name=", 5)) {
+ p += 5;
+ free(pa->name);
+ rc = next_string(&p, &pa->name);
+ if (!rc)
+ unhexmangle_string(pa->name);
+
+ } else if (!strncasecmp(p, "type=", 5) ||
+ !strncasecmp(p, "Id=", 3)) { /* backward compatibility */
+ char *type = NULL;
+
+ fdisk_unref_parttype(pa->type);
+ pa->type = NULL;
+
+ p += ((*p == 'I' || *p == 'i') ? 3 : 5); /* "Id=", "type=" */
+
+ rc = next_string(&p, &type);
+ if (rc == 0) {
+ pa->type = fdisk_label_advparse_parttype(script_get_label(dp),
+ type, FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS);
+ if (!pa->type)
+ rc = -EINVAL;
+ }
+ free(type);
+ } else {
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
+ rc = -EINVAL;
+ break;
+ }
+ }
+
+ if (!rc)
+ rc = fdisk_table_add_partition(dp->table, pa);
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+ fdisk_unref_partition(pa);
+ return rc;
+}
+
+/* simple format:
+ * <start>, <size>, <type>, <bootable>, ...
+ */
+static int parse_line_valcommas(struct fdisk_script *dp, char *s)
+{
+ int rc = 0;
+ char *p = s;
+ struct fdisk_partition *pa;
+ enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
+ int item = -1;
+
+ assert(dp);
+ assert(s);
+ assert(dp->table);
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ return -ENOMEM;
+
+ fdisk_partition_start_follow_default(pa, 1);
+ fdisk_partition_end_follow_default(pa, 1);
+ fdisk_partition_partno_follow_default(pa, 1);
+
+ while (rc == 0 && p && *p) {
+ char *begin;
+
+ p = (char *) skip_blank(p);
+ item++;
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
+ begin = p;
+
+ switch (item) {
+ case ITEM_START:
+ rc = parse_start_value(dp, pa, &p);
+ break;
+ case ITEM_SIZE:
+ rc = parse_size_value(dp, pa, &p);
+ break;
+ case ITEM_TYPE:
+ {
+ char *str = NULL;
+
+ fdisk_unref_parttype(pa->type);
+ pa->type = NULL;
+
+ if (*p == ',' || *p == ';' || is_default_value(&p))
+ break; /* use default type */
+
+ rc = next_string(&p, &str);
+ if (rc)
+ break;
+
+ pa->type = fdisk_label_advparse_parttype(script_get_label(dp),
+ str, FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS);
+ free(str);
+ if (!pa->type)
+ rc = -EINVAL;
+ break;
+ }
+ case ITEM_BOOTABLE:
+ if (*p == ',' || *p == ';')
+ break;
+ else {
+ char *tk = next_token(&p);
+ if (tk && *tk == '*' && *(tk + 1) == '\0')
+ pa->boot = 1;
+ else if (tk && *tk == '-' && *(tk + 1) == '\0')
+ pa->boot = 0;
+ else if (tk && *tk == '+' && *(tk + 1) == '\0')
+ pa->boot = 1;
+ else
+ rc = -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (begin == p)
+ p++;
+ }
+
+ if (!rc)
+ rc = fdisk_table_add_partition(dp->table, pa);
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+ fdisk_unref_partition(pa);
+ return rc;
+}
+
+/* modifies @s ! */
+static int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
+{
+ int rc = 0;
+
+ assert(dp);
+ assert(s);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing buffer"));
+
+ s = (char *) skip_blank(s);
+ if (!s || !*s)
+ return 0; /* nothing baby, ignore */
+
+ if (!dp->table && fdisk_script_get_table(dp) == NULL)
+ return -ENOMEM;
+
+ /* parse header lines only if no partition specified yet */
+ if (fdisk_table_is_empty(dp->table) && is_header_line(s))
+ rc = parse_line_header(dp, s);
+
+ /* parse script format */
+ else if (strchr(s, '='))
+ rc = parse_line_nameval(dp, s);
+
+ /* parse simple <value>, ... format */
+ else
+ rc = parse_line_valcommas(dp, s);
+
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
+ dp->nlines, rc));
+ return rc;
+}
+
+/**
+ * fdisk_script_set_fgets:
+ * @dp: script
+ * @fn_fgets: callback function
+ *
+ * The library uses fgets() function to read the next line from the script.
+ * This default maybe overridden by another function. Note that the function has
+ * to return the line terminated by \n (for example readline(3) removes \n).
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_script_set_fgets(struct fdisk_script *dp,
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *))
+{
+ assert(dp);
+
+ dp->fn_fgets = fn_fgets;
+ return 0;
+}
+
+/**
+ * fdisk_script_read_line:
+ * @dp: script
+ * @f: file
+ * @buf: buffer to store one line of the file
+ * @bufsz: buffer size
+ *
+ * Reads next line into dump.
+ *
+ * Returns: 0 on success, <0 on error, 1 when nothing to read. For unknown headers
+ * returns -ENOTSUP, it's usually safe to ignore this error.
+ */
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
+{
+ char *s;
+
+ assert(dp);
+ assert(f);
+ assert(bufsz);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
+
+ /* read the next non-blank non-comment line */
+ do {
+ buf[0] = '\0';
+ if (dp->fn_fgets) {
+ if (dp->fn_fgets(dp, buf, bufsz, f) == NULL)
+ return 1;
+ } else if (fgets(buf, bufsz, f) == NULL)
+ return 1;
+
+ dp->nlines++;
+ s = strchr(buf, '\n');
+ if (!s) {
+ /* Missing final newline? Otherwise an extremely */
+ /* long line - assume file was corrupted */
+ if (feof(f)) {
+ DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
+ s = strchr(buf, '\0');
+ } else {
+ DBG(SCRIPT, ul_debugobj(dp,
+ "%zu: missing newline at line", dp->nlines));
+ return -EINVAL;
+ }
+ }
+
+ *s = '\0';
+ if (--s >= buf && *s == '\r')
+ *s = '\0';
+ s = (char *) skip_blank(buf);
+ } while (*s == '\0' || *s == '#');
+
+ return fdisk_script_read_buffer(dp, s);
+}
+
+
+/**
+ * fdisk_script_read_file:
+ * @dp: script
+ * @f: input file
+ *
+ * Reads file @f into script @dp.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
+{
+ char buf[BUFSIZ] = { '\0' };
+ int rc = 1;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
+
+ while (!feof(f)) {
+ rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
+ if (rc && rc != -ENOTSUP)
+ break;
+ }
+
+ if (rc == 1)
+ rc = 0; /* end of file */
+
+ DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * fdisk_set_script:
+ * @cxt: context
+ * @dp: script (or NULL to remove previous reference)
+ *
+ * Sets reference to the @dp script and remove reference to the previously used
+ * script.
+ *
+ * The script headers might be used by label drivers to overwrite
+ * built-in defaults (for example disk label Id) and label driver might
+ * optimize the default semantic to be more usable for scripts (for example to
+ * not ask for primary/logical/extended partition type).
+ *
+ * Note that script also contains reference to the fdisk context (see
+ * fdisk_new_script()). This context may be completely independent on
+ * context used for fdisk_set_script().
+ *
+ * Don't forget to call fdisk_set_script(cxt, NULL); to remove this reference
+ * if no more necessary!
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ assert(cxt);
+
+ /* unref old */
+ if (cxt->script)
+ fdisk_unref_script(cxt->script);
+
+ /* ref new */
+ cxt->script = dp;
+ if (cxt->script) {
+ DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
+ fdisk_ref_script(cxt->script);
+ }
+
+ return 0;
+}
+
+/**
+ * fdisk_get_script:
+ * @cxt: context
+ *
+ * Returns: the current script or NULL.
+ */
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->script;
+}
+
+/**
+ * fdisk_apply_script_headers:
+ * @cxt: context
+ * @dp: script
+ *
+ * Associate context @cxt with script @dp and creates a new empty disklabel.
+ * The script may be later unreference by fdisk_set_script() with NULL as script.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ const char *name;
+ const char *str;
+ int rc;
+
+ assert(cxt);
+ assert(dp);
+
+ DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
+ fdisk_set_script(cxt, dp);
+
+ str = fdisk_script_get_header(dp, "grain");
+ if (str) {
+ uintmax_t sz;
+
+ rc = parse_size(str, &sz, NULL);
+ if (rc == 0)
+ rc = fdisk_save_user_grain(cxt, sz);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_has_user_device_properties(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ /* create empty label */
+ name = fdisk_script_get_header(dp, "label");
+ if (!name)
+ return -EINVAL;
+
+ rc = fdisk_create_disklabel(cxt, name);
+ if (rc)
+ return rc;
+
+ str = fdisk_script_get_header(dp, "table-length");
+ if (str) {
+ uintmax_t sz;
+
+ rc = parse_size(str, &sz, NULL);
+ if (rc == 0)
+ rc = fdisk_gpt_set_npartitions(cxt, sz);
+ }
+
+ return rc;
+}
+
+/**
+ * fdisk_apply_script:
+ * @cxt: context
+ * @dp: script
+ *
+ * This function creates a new disklabel and partition within context @cxt. You
+ * have to call fdisk_write_disklabel() to apply changes to the device.
+ *
+ * Returns: 0 on error, <0 on error.
+ */
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ int rc;
+ struct fdisk_script *old;
+
+ assert(dp);
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
+
+ old = fdisk_get_script(cxt);
+ fdisk_ref_script(old);
+
+ /* create empty disk label */
+ rc = fdisk_apply_script_headers(cxt, dp);
+
+ /* create partitions */
+ if (!rc && dp->table)
+ rc = fdisk_apply_table(cxt, dp->table);
+
+ fdisk_set_script(cxt, old);
+ fdisk_unref_script(old);
+
+ DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
+ return rc;
+}
+
+#ifdef FUZZ_TARGET
+# include "all-io.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char name[] = "/tmp/test-script-fuzz.XXXXXX";
+ int fd;
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ FILE *f;
+
+ fd = mkstemp_cloexec(name);
+ if (fd < 0)
+ err(EXIT_FAILURE, "mkstemp() failed");
+ if (write_all(fd, data, size) != 0)
+ err(EXIT_FAILURE, "write() failed");
+ f = fopen(name, "r");
+ if (!f)
+ err(EXIT_FAILURE, "cannot open %s", name);
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+
+ fdisk_script_read_file(dp, f);
+ fclose(f);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ close(fd);
+ unlink(name);
+
+ return 0;
+}
+#endif
+
+#ifdef TEST_PROGRAM
+static int test_dump(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *devname = argv[1];
+ struct fdisk_context *cxt;
+ struct fdisk_script *dp;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, devname, 1);
+
+ dp = fdisk_new_script(cxt);
+ fdisk_script_read_context(dp, NULL);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return 0;
+}
+
+static int test_read(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *filename = argv[1];
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ FILE *f;
+
+ if (!(f = fopen(filename, "r")))
+ err(EXIT_FAILURE, "%s: cannot open", filename);
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+
+ fdisk_script_read_file(dp, f);
+ fclose(f);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return 0;
+}
+
+static int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char buf[BUFSIZ] = { '\0' };
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ int rc = 0;
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+ fdisk_script_set_header(dp, "label", "dos");
+
+ printf("<start>, <size>, <type>, <bootable: *|->\n");
+ do {
+ struct fdisk_partition *pa;
+ size_t n = dp->table ? fdisk_table_get_nents(dp->table) : 0;
+
+ printf(" #%zu :\n", n + 1);
+ rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
+
+ if (rc == 0) {
+ pa = fdisk_table_get_partition(dp->table, n);
+ printf(" #%zu %12ju %12ju\n", n + 1,
+ (uintmax_t)fdisk_partition_get_start(pa),
+ (uintmax_t)fdisk_partition_get_size(pa));
+ }
+ } while (rc == 0);
+
+ if (!rc)
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return rc;
+}
+
+static int test_apply(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *devname = argv[1], *scriptname = argv[2];
+ struct fdisk_context *cxt;
+ struct fdisk_script *dp;
+ struct fdisk_table *tb = NULL;
+ struct fdisk_iter *itr = NULL;
+ struct fdisk_partition *pa = NULL;
+ int rc;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, devname, 0);
+
+ dp = fdisk_new_script_from_file(cxt, scriptname);
+ if (!dp)
+ return -errno;
+
+ rc = fdisk_apply_script(cxt, dp);
+ if (rc)
+ goto done;
+ fdisk_unref_script(dp);
+
+ /* list result */
+ fdisk_list_disklabel(cxt);
+ fdisk_get_partitions(cxt, &tb);
+
+ itr = fdisk_new_iter(FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
+ printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa),
+ (uintmax_t)fdisk_partition_get_start(pa),
+ (uintmax_t)fdisk_partition_get_size(pa));
+ }
+
+done:
+ fdisk_free_iter(itr);
+ fdisk_unref_table(tb);
+
+ /*fdisk_write_disklabel(cxt);*/
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+static int test_tokens(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *p, *str = argc == 2 ? strdup(argv[1]) : NULL;
+ int i;
+
+ for (i = 1, p = str; p && *p; i++) {
+ char *tk = next_token(&p);
+
+ if (!tk)
+ break;
+
+ printf("#%d: '%s'\n", i, tk);
+ }
+
+ free(str);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--dump", test_dump, "<device> dump PT as script" },
+ { "--read", test_read, "<file> read PT script from file" },
+ { "--apply", test_apply, "<device> <file> try apply script from file to device" },
+ { "--stdin", test_stdin, " read input like sfdisk" },
+ { "--tokens", test_tokens, "<string> parse string" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/sgi.c b/libfdisk/src/sgi.c
new file mode 100644
index 0000000..6740535
--- /dev/null
+++ b/libfdisk/src/sgi.c
@@ -0,0 +1,1210 @@
+/*
+ *
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ * 2013 Karel Zak <kzak@redhat.com>
+ *
+ * This is a re-written version for libfdisk, the original was fdisksgilabel.c
+ * from util-linux fdisk, by:
+ *
+ * Andreas Neuper, Sep 1998,
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, Mar 1999,
+ * Phillip Kesling <pkesling@sgi.com>, Mar 2003.
+ */
+
+#include "c.h"
+#include "all-io.h"
+
+#include "blkdev.h"
+
+#include "bitops.h"
+#include "pt-sgi.h"
+#include "pt-mbr.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: sgi
+ * @title: SGI
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SGI stuff
+ */
+struct fdisk_sgi_label {
+ struct fdisk_label head; /* generic fdisk part */
+ struct sgi_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */
+
+ struct sgi_freeblocks {
+ unsigned int first;
+ unsigned int last;
+ } freelist[SGI_MAXPARTITIONS + 1];
+};
+
+static struct fdisk_parttype sgi_parttypes[] =
+{
+ {SGI_TYPE_VOLHDR, N_("SGI volhdr")},
+ {SGI_TYPE_TRKREPL, N_("SGI trkrepl")},
+ {SGI_TYPE_SECREPL, N_("SGI secrepl")},
+ {SGI_TYPE_SWAP, N_("SGI raw")},
+ {SGI_TYPE_BSD, N_("SGI bsd")},
+ {SGI_TYPE_SYSV, N_("SGI sysv")},
+ {SGI_TYPE_ENTIRE_DISK, N_("SGI volume")},
+ {SGI_TYPE_EFS, N_("SGI efs")},
+ {SGI_TYPE_LVOL, N_("SGI lvol")},
+ {SGI_TYPE_RLVOL, N_("SGI rlvol")},
+ {SGI_TYPE_XFS, N_("SGI xfs")},
+ {SGI_TYPE_XFSLOG, N_("SGI xfslog")},
+ {SGI_TYPE_XLV, N_("SGI xlv")},
+ {SGI_TYPE_XVM, N_("SGI xvm")},
+ {MBR_LINUX_SWAP_PARTITION, N_("Linux swap")},
+ {MBR_LINUX_DATA_PARTITION, N_("Linux native")},
+ {MBR_LINUX_LVM_PARTITION, N_("Linux LVM")},
+ {MBR_LINUX_RAID_PARTITION, N_("Linux RAID")},
+ {0, NULL }
+};
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i );
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i );
+static int sgi_get_bootpartition(struct fdisk_context *cxt);
+static int sgi_get_swappartition(struct fdisk_context *cxt);
+
+/* Returns a pointer buffer with on-disk data. */
+static inline struct sgi_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ return ((struct fdisk_sgi_label *) cxt->label)->header;
+}
+
+/* Returns in-memory fdisk data. */
+static inline struct fdisk_sgi_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ return (struct fdisk_sgi_label *) cxt->label;
+}
+
+/*
+ * Information within second on-disk block
+ */
+#define SGI_INFO_MAGIC 0x00072959
+
+struct sgi_info {
+ unsigned int magic; /* looks like a magic number */
+ unsigned int a2;
+ unsigned int a3;
+ unsigned int a4;
+ unsigned int b1;
+ unsigned short b2;
+ unsigned short b3;
+ unsigned int c[16];
+ unsigned short d[3];
+ unsigned char scsi_string[50];
+ unsigned char serial[137];
+ unsigned short check1816;
+ unsigned char installer[225];
+};
+
+static struct sgi_info *sgi_new_info(void)
+{
+ struct sgi_info *info = calloc(1, sizeof(struct sgi_info));
+
+ if (!info)
+ return NULL;
+
+ info->magic = cpu_to_be32(SGI_INFO_MAGIC);
+ info->b1 = cpu_to_be32(-1);
+ info->b2 = cpu_to_be16(-1);
+ info->b3 = cpu_to_be16(1);
+
+ /* You may want to replace this string !!!!!!! */
+ strcpy((char *) info->scsi_string, "IBM OEM 0662S12 3 30");
+ strcpy((char *) info->serial, "0000");
+ info->check1816 = cpu_to_be16(18 * 256 + 16);
+ strcpy((char *) info->installer, "Sfx version 5.3, Oct 18, 1994");
+
+ return info;
+}
+
+static void sgi_free_info(struct sgi_info *info)
+{
+ free(info);
+}
+
+/**
+ * fdisk_sgi_create_info:
+ * @cxt: context
+ *
+ * This function add hint about SGI label (e.g. set "sgilabel" as volume name)
+ * to the first SGI volume. This is probably old SGI convention without any
+ * effect to the device partitioning.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_create_info(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ /* I keep SGI's habit to write the sgilabel to the second block */
+ sgilabel->volume[0].block_num = cpu_to_be32(2);
+ sgilabel->volume[0].num_bytes = cpu_to_be32(sizeof(struct sgi_info));
+ memcpy((char *) sgilabel->volume[0].name, "sgilabel", 8);
+
+ fdisk_info(cxt, _("SGI info created on second sector."));
+ return 0;
+}
+
+
+/*
+ * only dealing with free blocks here
+ */
+static void set_freelist(struct fdisk_context *cxt,
+ size_t i, unsigned int f, unsigned int l)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+
+ if (i < ARRAY_SIZE(sgi->freelist)) {
+ sgi->freelist[i].first = f;
+ sgi->freelist[i].last = l;
+ }
+}
+
+static void add_to_freelist(struct fdisk_context *cxt,
+ unsigned int f, unsigned int l)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+ if (sgi->freelist[i].last == 0)
+ break;
+ }
+ set_freelist(cxt, i, f, l);
+}
+
+static void clear_freelist(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+
+ memset(sgi->freelist, 0, sizeof(sgi->freelist));
+}
+
+static unsigned int is_in_freelist(struct fdisk_context *cxt, unsigned int b)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+ if (sgi->freelist[i].first <= b
+ && sgi->freelist[i].last >= b)
+ return sgi->freelist[i].last;
+ }
+
+ return 0;
+}
+
+
+static int sgi_get_nsect(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->devparam.nsect);
+}
+
+static int sgi_get_ntrks(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->devparam.ntrks);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+ size_t i, ct = 0;
+
+ for (i = 0; i < cxt->label->nparts_max; i++)
+ ct += sgi_get_num_sectors(cxt, i) > 0;
+
+ return ct;
+}
+
+static int sgi_probe_label(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi;
+ struct sgi_disklabel *sgilabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+ assert(sizeof(struct sgi_disklabel) <= 512);
+
+ /* map first sector to header */
+ sgi = (struct fdisk_sgi_label *) cxt->label;
+ sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+ sgilabel = sgi->header;
+
+ if (be32_to_cpu(sgilabel->magic) != SGI_LABEL_MAGIC) {
+ sgi->header = NULL;
+ return 0;
+ }
+
+ /*
+ * test for correct checksum
+ */
+ if (sgi_pt_checksum(sgilabel) != 0)
+ fdisk_warnx(cxt, _("Detected an SGI disklabel with wrong checksum."));
+
+ clear_freelist(cxt);
+ cxt->label->nparts_max = SGI_MAXPARTITIONS;
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ return 1;
+}
+
+static int sgi_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct sgi_disklabel *sgilabel;
+ struct sgi_device_parameter *sgiparam;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgiparam = &sgilabel->devparam;
+
+ switch (item->id) {
+ case SGI_LABELITEM_PCYLCOUNT:
+ item->name = _("Physical cylinders");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) be16_to_cpu(sgiparam->pcylcount);
+ break;
+ case SGI_LABELITEM_SPARECYL:
+ item->name = _("Extra sects/cyl");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) sgiparam->sparecyl;
+ break;
+ case SGI_LABELITEM_ILFACT:
+ item->name = _("Interleave");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) be16_to_cpu(sgiparam->ilfact);
+ break;
+ case SGI_LABELITEM_BOOTFILE:
+ item->name = _("Bootfile");
+ item->type = 's';
+ item->data.str = *sgilabel->boot_file ? strdup((char *) sgilabel->boot_file) : NULL;
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].first_block);
+}
+
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].num_blocks);
+}
+
+static int sgi_get_sysid(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].type);
+}
+
+static int sgi_get_bootpartition(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->root_part_num);
+}
+
+static int sgi_get_swappartition(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->swap_part_num);
+}
+
+static unsigned int sgi_get_lastblock(struct fdisk_context *cxt)
+{
+ return cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders;
+}
+
+static struct fdisk_parttype *sgi_get_parttype(struct fdisk_context *cxt, size_t n)
+{
+ struct fdisk_parttype *t;
+
+ if (n >= cxt->label->nparts_max)
+ return NULL;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label, sgi_get_sysid(cxt, n));
+ return t ? : fdisk_new_unknown_parttype(sgi_get_sysid(cxt, n), NULL);
+}
+
+/* fdisk_get_partition() backend */
+static int sgi_get_partition(struct fdisk_context *cxt, size_t n, struct fdisk_partition *pa)
+{
+ fdisk_sector_t start, len;
+
+ pa->used = sgi_get_num_sectors(cxt, n) > 0;
+ if (!pa->used)
+ return 0;
+
+ start = sgi_get_start_sector(cxt, n);
+ len = sgi_get_num_sectors(cxt, n);
+
+ pa->type = sgi_get_parttype(cxt, n);
+ pa->size = len;
+ pa->start = start;
+
+ if (pa->type && pa->type->code == SGI_TYPE_ENTIRE_DISK)
+ pa->wholedisk = 1;
+
+ pa->attrs = sgi_get_swappartition(cxt) == (int) n ? "swap" :
+ sgi_get_bootpartition(cxt) == (int) n ? "boot" : NULL;
+ if (pa->attrs)
+ pa->attrs = strdup(pa->attrs);
+
+ return 0;
+}
+
+
+static int sgi_check_bootfile(struct fdisk_context *cxt, const char *name)
+{
+ size_t sz;
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ sz = strlen(name);
+
+ if (sz < 3) {
+ /* "/a\n" is minimum */
+ fdisk_warnx(cxt, _("Invalid bootfile! The bootfile must "
+ "be an absolute non-zero pathname, "
+ "e.g. \"/unix\" or \"/unix.save\"."));
+ return -EINVAL;
+
+ }
+
+ if (sz > sizeof(sgilabel->boot_file)) {
+ fdisk_warnx(cxt, P_("Name of bootfile is too long: %zu byte maximum.",
+ "Name of bootfile is too long: %zu bytes maximum.",
+ sizeof(sgilabel->boot_file)),
+ sizeof(sgilabel->boot_file));
+ return -EINVAL;
+
+ }
+
+ if (*name != '/') {
+ fdisk_warnx(cxt, _("Bootfile must have a fully qualified pathname."));
+ return -EINVAL;
+ }
+
+ if (strncmp(name, (char *) sgilabel->boot_file,
+ sizeof(sgilabel->boot_file)) != 0) {
+ fdisk_warnx(cxt, _("Be aware that the bootfile is not checked "
+ "for existence. SGI's default is \"/unix\", "
+ "and for backup \"/unix.save\"."));
+ return 0; /* filename is correct and did change */
+ }
+
+ return 1; /* filename did not change */
+}
+
+/**
+ * fdisk_sgi_set_bootfile:
+ * @cxt: context
+ *
+ * Allows to set SGI boot file. The function uses Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_set_bootfile(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ size_t sz;
+ char *name = NULL;
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ fdisk_info(cxt, _("The current boot file is: %s"), sgilabel->boot_file);
+
+ rc = fdisk_ask_string(cxt, _("Enter of the new boot file"), &name);
+ if (rc == 0)
+ rc = sgi_check_bootfile(cxt, name);
+ if (rc) {
+ if (rc == 1)
+ fdisk_info(cxt, _("Boot file is unchanged."));
+ goto done;
+ }
+
+ memset(sgilabel->boot_file, 0, sizeof(sgilabel->boot_file));
+ sz = strlen(name);
+
+ assert(sz <= sizeof(sgilabel->boot_file)); /* see sgi_check_bootfile() */
+
+ memcpy(sgilabel->boot_file, name, sz);
+
+ fdisk_info(cxt, _("Bootfile has been changed to \"%s\"."), name);
+done:
+ free(name);
+ return rc;
+}
+
+static int sgi_write_disklabel(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel;
+ struct sgi_info *info = NULL;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgilabel->csum = 0;
+ sgilabel->csum = cpu_to_be32(sgi_pt_checksum(sgilabel));
+
+ assert(sgi_pt_checksum(sgilabel) == 0);
+
+ if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+ goto err;
+ if (write_all(cxt->dev_fd, sgilabel, DEFAULT_SECTOR_SIZE))
+ goto err;
+ if (!strncmp((char *) sgilabel->volume[0].name, "sgilabel", 8)) {
+ /*
+ * Keep this habit of first writing the "sgilabel".
+ * I never tested whether it works without. (AN 1998-10-02)
+ */
+ int infostartblock
+ = be32_to_cpu(sgilabel->volume[0].block_num);
+
+ if (lseek(cxt->dev_fd, (off_t) infostartblock *
+ DEFAULT_SECTOR_SIZE, SEEK_SET) < 0)
+ goto err;
+ info = sgi_new_info();
+ if (!info)
+ goto err;
+ if (write_all(cxt->dev_fd, info, sizeof(*info)))
+ goto err;
+ }
+
+ sgi_free_info(info);
+ return 0;
+err:
+ sgi_free_info(info);
+ return -errno;
+}
+
+static int compare_start(struct fdisk_context *cxt,
+ const void *x, const void *y)
+{
+ /*
+ * Sort according to start sectors and prefer the largest partition:
+ * entry zero is the entire-disk entry.
+ */
+ const unsigned int i = *(const int *) x;
+ const unsigned int j = *(const int *) y;
+ unsigned int a = sgi_get_start_sector(cxt, i);
+ unsigned int b = sgi_get_start_sector(cxt, j);
+ unsigned int c = sgi_get_num_sectors(cxt, i);
+ unsigned int d = sgi_get_num_sectors(cxt, j);
+
+ if (a == b)
+ return (d > c) ? 1 : (d == c) ? 0 : -1;
+ return (a > b) ? 1 : -1;
+}
+
+static void generic_swap(void *a0, void *b0, int size)
+{
+ char *a = a0, *b = b0;
+
+ for (; size > 0; --size, a++, b++) {
+ char t = *a;
+ *a = *b;
+ *b = t;
+ }
+}
+
+
+/* heap sort, based on Matt Mackall's linux kernel version */
+static void sort(void *base0, size_t num, size_t size, struct fdisk_context *cxt,
+ int (*cmp_func)(struct fdisk_context *, const void *, const void *))
+{
+ /* pre-scale counters for performance */
+ int i = (num/2 - 1) * size;
+ size_t n = num * size, c, r;
+ char *base = base0;
+
+ /* heapify */
+ for ( ; i >= 0; i -= size) {
+ for (r = i; r * 2 + size < n; r = c) {
+ c = r * 2 + size;
+ if (c < n - size &&
+ cmp_func(cxt, base + c, base + c + size) < 0)
+ c += size;
+ if (cmp_func(cxt, base + r, base + c) >= 0)
+ break;
+ generic_swap(base + r, base + c, size);
+ }
+ }
+
+ /* sort */
+ for (i = n - size; i > 0; i -= size) {
+ generic_swap(base, base + i, size);
+ for (r = 0; r * 2 + size < (size_t) i; r = c) {
+ c = r * 2 + size;
+ if (c < i - size &&
+ cmp_func(cxt, base + c, base + c + size) < 0)
+ c += size;
+ if (cmp_func(cxt, base + r, base + c) >= 0)
+ break;
+ generic_swap(base + r, base + c, size);
+ }
+ }
+}
+
+static int verify_disklabel(struct fdisk_context *cxt, int verbose)
+{
+ int Index[SGI_MAXPARTITIONS]; /* list of valid partitions */
+ int sortcount = 0; /* number of used partitions, i.e. non-zero lengths */
+ int entire = 0, i = 0;
+ unsigned int start = 0;
+ long long gap = 0; /* count unused blocks */
+ unsigned int lastblock = sgi_get_lastblock(cxt);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ clear_freelist(cxt);
+ memset(Index, 0, sizeof(Index));
+
+ for (i=0; i < SGI_MAXPARTITIONS; i++) {
+ if (sgi_get_num_sectors(cxt, i) != 0) {
+ Index[sortcount++] = i;
+ if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK
+ && entire++ == 1 && verbose) {
+ fdisk_info(cxt, _("More than one entire "
+ "disk entry present."));
+ }
+ }
+ }
+ if (sortcount == 0) {
+ if (verbose)
+ fdisk_info(cxt, _("No partitions defined."));
+ if (lastblock)
+ add_to_freelist(cxt, 0, lastblock);
+ return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1;
+ }
+
+ sort(Index, sortcount, sizeof(Index[0]), cxt, compare_start);
+
+ if (sgi_get_sysid(cxt, Index[0]) == SGI_TYPE_ENTIRE_DISK) {
+ if (verbose && Index[0] != 10)
+ fdisk_info(cxt, _("IRIX likes it when partition 11 "
+ "covers the entire disk."));
+
+ if (verbose && sgi_get_start_sector(cxt, Index[0]) != 0)
+ fdisk_info(cxt, _("The entire disk partition should "
+ "start at block 0, not at block %d."),
+ sgi_get_start_sector(cxt, Index[0]));
+
+ if (verbose && sgi_get_num_sectors(cxt, Index[0]) != lastblock)
+ DBG(LABEL, ul_debug(
+ "entire disk partition=%ds, but disk=%ds",
+ sgi_get_num_sectors(cxt, Index[0]),
+ lastblock));
+ lastblock = sgi_get_num_sectors(cxt, Index[0]);
+ } else if (verbose) {
+ fdisk_info(cxt, _("Partition 11 should cover the entire disk."));
+ DBG(LABEL, ul_debug("sysid=%d\tpartition=%d",
+ sgi_get_sysid(cxt, Index[0]), Index[0]+1));
+ }
+ for (i=1, start=0; i<sortcount; i++) {
+ int cylsize = sgi_get_nsect(cxt) * sgi_get_ntrks(cxt);
+
+ if (verbose && cylsize
+ && (sgi_get_start_sector(cxt, Index[i]) % cylsize) != 0)
+ DBG(LABEL, ul_debug("partition %d does not start on "
+ "cylinder boundary.", Index[i]+1));
+
+ if (verbose && cylsize
+ && sgi_get_num_sectors(cxt, Index[i]) % cylsize != 0)
+ DBG(LABEL, ul_debug("partition %d does not end on "
+ "cylinder boundary.", Index[i]+1));
+
+ /* We cannot handle several "entire disk" entries. */
+ if (sgi_get_sysid(cxt, Index[i]) == SGI_TYPE_ENTIRE_DISK)
+ continue;
+
+ if (start > sgi_get_start_sector(cxt, Index[i])) {
+ if (verbose)
+ fdisk_info(cxt,
+ P_("Partitions %d and %d overlap by %d sector.",
+ "Partitions %d and %d overlap by %d sectors.",
+ start - sgi_get_start_sector(cxt, Index[i])),
+ Index[i-1]+1, Index[i]+1,
+ start - sgi_get_start_sector(cxt, Index[i]));
+ if (gap > 0) gap = -gap;
+ if (gap == 0) gap = -1;
+ }
+ if (start < sgi_get_start_sector(cxt, Index[i])) {
+ if (verbose)
+ fdisk_info(cxt,
+ P_("Unused gap of %8u sector: sector %8u",
+ "Unused gap of %8u sectors: sectors %8u-%u",
+ sgi_get_start_sector(cxt, Index[i]) - start),
+ sgi_get_start_sector(cxt, Index[i]) - start,
+ start, sgi_get_start_sector(cxt, Index[i])-1);
+ gap += sgi_get_start_sector(cxt, Index[i]) - start;
+ add_to_freelist(cxt, start,
+ sgi_get_start_sector(cxt, Index[i]));
+ }
+ start = sgi_get_start_sector(cxt, Index[i])
+ + sgi_get_num_sectors(cxt, Index[i]);
+ /* Align free space on cylinder boundary. */
+ if (cylsize && start % cylsize)
+ start += cylsize - (start % cylsize);
+
+ DBG(LABEL, ul_debug("%2d:%12d\t%12d\t%12d", Index[i],
+ sgi_get_start_sector(cxt, Index[i]),
+ sgi_get_num_sectors(cxt, Index[i]),
+ sgi_get_sysid(cxt, Index[i])));
+ }
+ if (start < lastblock) {
+ if (verbose)
+ fdisk_info(cxt, P_("Unused gap of %8u sector: sector %8u",
+ "Unused gap of %8u sectors: sectors %8u-%u",
+ lastblock - start),
+ lastblock - start, start, lastblock-1);
+ gap += lastblock - start;
+ add_to_freelist(cxt, start, lastblock);
+ }
+ /*
+ * Done with arithmetic. Go for details now.
+ */
+ if (verbose) {
+ if (sgi_get_bootpartition(cxt) < 0
+ || !sgi_get_num_sectors(cxt, sgi_get_bootpartition(cxt)))
+ fdisk_info(cxt, _("The boot partition does not exist."));
+
+ if (sgi_get_swappartition(cxt) < 0
+ || !sgi_get_num_sectors(cxt, sgi_get_swappartition(cxt)))
+ fdisk_info(cxt, _("The swap partition does not exist."));
+
+ else if (sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != SGI_TYPE_SWAP
+ && sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != MBR_LINUX_SWAP_PARTITION)
+ fdisk_info(cxt, _("The swap partition has no swap type."));
+
+ if (sgi_check_bootfile(cxt, "/unix"))
+ fdisk_info(cxt, _("You have chosen an unusual bootfile name."));
+ }
+
+ return (gap > 0) ? 1 : (gap == 0) ? 0 : -1;
+}
+
+static int sgi_verify_disklabel(struct fdisk_context *cxt)
+{
+ return verify_disklabel(cxt, 1);
+}
+
+static int sgi_gaps(struct fdisk_context *cxt)
+{
+ /*
+ * returned value is:
+ * = 0 : disk is properly filled to the rim
+ * < 0 : there is an overlap
+ * > 0 : there is still some vacant space
+ */
+ return verify_disklabel(cxt, 0);
+}
+
+/* Returns partition index of first entry marked as entire disk. */
+static int sgi_entire(struct fdisk_context *cxt)
+{
+ size_t i;
+
+ for (i = 0; i < SGI_MAXPARTITIONS; i++)
+ if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK)
+ return i;
+ return -1;
+}
+
+static int set_partition(struct fdisk_context *cxt, size_t i,
+ unsigned int start, unsigned int length, int sys)
+{
+ struct sgi_disklabel *sgilabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgilabel->partitions[i].type = cpu_to_be32(sys);
+ sgilabel->partitions[i].num_blocks = cpu_to_be32(length);
+ sgilabel->partitions[i].first_block = cpu_to_be32(start);
+
+ fdisk_label_set_changed(cxt->label, 1);
+
+ if (sgi_gaps(cxt) < 0) /* rebuild freelist */
+ fdisk_warnx(cxt, _("Partition overlap on the disk."));
+ if (length) {
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sys);
+ fdisk_info_new_partition(cxt, i + 1, start, start + length, t);
+ }
+
+ return 0;
+}
+
+static void sgi_set_entire(struct fdisk_context *cxt)
+{
+ size_t n;
+
+ for (n = 10; n < cxt->label->nparts_max; n++) {
+ if (!sgi_get_num_sectors(cxt, n)) {
+ set_partition(cxt, n, 0, sgi_get_lastblock(cxt), SGI_TYPE_ENTIRE_DISK);
+ break;
+ }
+ }
+}
+
+static void sgi_set_volhdr(struct fdisk_context *cxt)
+{
+ size_t n;
+
+ for (n = 8; n < cxt->label->nparts_max; n++) {
+ if (!sgi_get_num_sectors(cxt, n)) {
+ /* Choose same default volume header size as IRIX fx uses. */
+ if (4096 < sgi_get_lastblock(cxt))
+ set_partition(cxt, n, 0, 4096, SGI_TYPE_VOLHDR);
+ break;
+ }
+ }
+}
+
+static int sgi_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (partnum > cxt->label->nparts_max)
+ return -EINVAL;
+
+ rc = set_partition(cxt, partnum, 0, 0, 0);
+
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ return rc;
+}
+
+static int sgi_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct fdisk_sgi_label *sgi;
+ char mesg[256];
+ unsigned int first = 0, last = 0;
+ struct fdisk_ask *ask;
+ int sys = pa && pa->type ? pa->type->code : SGI_TYPE_XFS;
+ int rc;
+ size_t n;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ if (rc)
+ return rc;
+ if (n == 10)
+ sys = SGI_TYPE_ENTIRE_DISK;
+ else if (n == 8)
+ sys = 0;
+
+ sgi = self_label(cxt);
+
+ if (sgi_get_num_sectors(cxt, n)) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."), n + 1);
+ return -EINVAL;
+ }
+ if (!cxt->script && sgi_entire(cxt) == -1 && sys != SGI_TYPE_ENTIRE_DISK) {
+ fdisk_info(cxt, _("Attempting to generate entire disk entry automatically."));
+ sgi_set_entire(cxt);
+ sgi_set_volhdr(cxt);
+ }
+ if (sgi_gaps(cxt) == 0 && sys != SGI_TYPE_ENTIRE_DISK) {
+ fdisk_warnx(cxt, _("The entire disk is already covered with partitions."));
+ return -EINVAL;
+ }
+ if (sgi_gaps(cxt) < 0) {
+ fdisk_warnx(cxt, _("You got a partition overlap on the disk. Fix it first!"));
+ return -EINVAL;
+ }
+
+ if (sys == SGI_TYPE_ENTIRE_DISK) {
+ first = 0;
+ last = sgi_get_lastblock(cxt);
+ } else {
+ first = sgi->freelist[0].first;
+ last = sgi->freelist[0].last;
+ }
+
+ /* first sector */
+ if (pa && pa->start_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ first = pa->start;
+ last = is_in_freelist(cxt, first);
+
+ if (sys != SGI_TYPE_ENTIRE_DISK && !last)
+ return -ERANGE;
+ } else {
+ snprintf(mesg, sizeof(mesg), _("First %s"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR));
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, first)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1); /* maximal */
+
+ rc = fdisk_do_ask(cxt, ask);
+ first = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ first *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (first && sys == SGI_TYPE_ENTIRE_DISK)
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "eleventh partition covers the entire "
+ "disk and is of type 'SGI volume'."));
+ if (!last)
+ last = is_in_freelist(cxt, first);
+
+ /* last sector */
+ if (pa && pa->end_follow_default)
+ last -= 1ULL;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ if (first + pa->size - 1ULL > last)
+ return -ERANGE;
+ last = first + pa->size - 1ULL;
+ } else {
+ snprintf(mesg, sizeof(mesg),
+ _("Last %s or +%s or +size{K,M,G,T,P}"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR),
+ fdisk_get_unit(cxt, FDISK_PLURAL));
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, last) - 1);/* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1);/* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ else
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+
+ rc = fdisk_do_ask(cxt, ask);
+ last = fdisk_ask_number_get_result(ask) + 1;
+
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ last *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (sys == SGI_TYPE_ENTIRE_DISK
+ && (first != 0 || last != sgi_get_lastblock(cxt)))
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "eleventh partition covers the entire "
+ "disk and is of type 'SGI volume'."));
+
+ set_partition(cxt, n, first, last - first, sys);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ if (partno)
+ *partno = n;
+ return 0;
+}
+
+static int sgi_create_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi;
+ struct sgi_disklabel *sgilabel;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (cxt->geom.heads && cxt->geom.sectors) {
+ fdisk_sector_t llsectors;
+
+ if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+ /* the get device size ioctl was successful */
+ fdisk_sector_t llcyls;
+ int sec_fac = cxt->sector_size / 512;
+
+ llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+ cxt->geom.cylinders = llcyls;
+ if (cxt->geom.cylinders != llcyls) /* truncated? */
+ cxt->geom.cylinders = ~0;
+ } else {
+ /* otherwise print error and use truncated version */
+ fdisk_warnx(cxt,
+ _("BLKGETSIZE ioctl failed on %s. "
+ "Using geometry cylinder value of %ju. "
+ "This value may be truncated for devices "
+ "> 33.8 GB."), cxt->dev_path,
+ (uintmax_t) cxt->geom.cylinders);
+ }
+ }
+
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ sgi = (struct fdisk_sgi_label *) cxt->label;
+ sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+
+ sgilabel = sgi->header;
+
+ sgilabel->magic = cpu_to_be32(SGI_LABEL_MAGIC);
+ sgilabel->root_part_num = cpu_to_be16(0);
+ sgilabel->swap_part_num = cpu_to_be16(1);
+
+ /* sizeof(sgilabel->boot_file) = 16 > 6 */
+ memset(sgilabel->boot_file, 0, 16);
+ strcpy((char *) sgilabel->boot_file, "/unix");
+
+ sgilabel->devparam.skew = (0);
+ sgilabel->devparam.gap1 = (0);
+ sgilabel->devparam.gap2 = (0);
+ sgilabel->devparam.sparecyl = (0);
+ sgilabel->devparam.pcylcount = cpu_to_be16(cxt->geom.cylinders);
+ sgilabel->devparam.head_vol0 = cpu_to_be16(0);
+ sgilabel->devparam.ntrks = cpu_to_be16(cxt->geom.heads);
+ /* tracks/cylinder (heads) */
+ sgilabel->devparam.cmd_tag_queue_depth = (0);
+ sgilabel->devparam.unused0 = (0);
+ sgilabel->devparam.unused1 = cpu_to_be16(0);
+ sgilabel->devparam.nsect = cpu_to_be16(cxt->geom.sectors);
+ /* sectors/track */
+ sgilabel->devparam.bytes = cpu_to_be16(cxt->sector_size);
+ sgilabel->devparam.ilfact = cpu_to_be16(1);
+ sgilabel->devparam.flags = cpu_to_be32(
+ SGI_DEVPARAM_TRACK_FWD
+ | SGI_DEVPARAM_IGNORE_ERRORS
+ | SGI_DEVPARAM_RESEEK);
+ sgilabel->devparam.datarate = cpu_to_be32(0);
+ sgilabel->devparam.retries_on_error = cpu_to_be32(1);
+ sgilabel->devparam.ms_per_word = cpu_to_be32(0);
+ sgilabel->devparam.xylogics_gap1 = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_syncdelay = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_readdelay = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_gap2 = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_readgate = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_writecont = cpu_to_be16(0);
+
+ memset(&(sgilabel->volume), 0,
+ sizeof(struct sgi_volume) * SGI_MAXVOLUMES);
+ memset(&(sgilabel->partitions), 0,
+ sizeof(struct sgi_partition) * SGI_MAXPARTITIONS);
+ cxt->label->nparts_max = SGI_MAXPARTITIONS;
+
+ /* don't create default layout when a script defined */
+ if (!cxt->script) {
+ sgi_set_entire(cxt);
+ sgi_set_volhdr(cxt);
+ }
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ fdisk_info(cxt, _("Created a new SGI disklabel."));
+ return 0;
+}
+
+static int sgi_set_partition(struct fdisk_context *cxt,
+ size_t i,
+ struct fdisk_partition *pa)
+{
+ struct sgi_disklabel *sgilabel;
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sgilabel = self_disklabel(cxt);
+
+ if (pa->type) {
+ struct fdisk_parttype *t = pa->type;
+
+ if (sgi_get_num_sectors(cxt, i) == 0) /* caught already before, ... */ {
+ fdisk_warnx(cxt, _("Sorry, only for non-empty partitions you can change the tag."));
+ return -EINVAL;
+ }
+
+ if ((i == 10 && t->code != SGI_TYPE_ENTIRE_DISK)
+ || (i == 8 && t->code != 0))
+ fdisk_info(cxt, _("Consider leaving partition 9 as volume header (0), "
+ "and partition 11 as entire volume (6), "
+ "as IRIX expects it."));
+
+ if (cxt->script == NULL
+ && ((t->code != SGI_TYPE_ENTIRE_DISK) && (t->code != SGI_TYPE_VOLHDR))
+ && (sgi_get_start_sector(cxt, i) < 1)) {
+ int yes = 0;
+ fdisk_ask_yesno(cxt,
+ _("It is highly recommended that the partition at offset 0 "
+ "is of type \"SGI volhdr\", the IRIX system will rely on it to "
+ "retrieve from its directory standalone tools like sash and fx. "
+ "Only the \"SGI volume\" entire disk section may violate this. "
+ "Are you sure about tagging this partition differently?"), &yes);
+ if (!yes)
+ return 1;
+ }
+
+ sgilabel->partitions[i].type = cpu_to_be32(t->code);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ sgilabel->partitions[i].first_block = cpu_to_be32(pa->start);
+ if (fdisk_partition_has_size(pa))
+ sgilabel->partitions[i].num_blocks = cpu_to_be32(pa->size);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+static int sgi_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ assert(cxt);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+ return sgi_get_num_sectors(cxt, i) ? 1 : 0;
+}
+
+static int sgi_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+ struct sgi_disklabel *sgilabel;
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sgilabel = self_disklabel(cxt);
+
+ switch (flag) {
+ case SGI_FLAG_BOOT:
+ sgilabel->root_part_num =
+ be16_to_cpu(sgilabel->root_part_num) == i ?
+ 0 : cpu_to_be16(i);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ case SGI_FLAG_SWAP:
+ sgilabel->swap_part_num =
+ be16_to_cpu(sgilabel->swap_part_num) == i ?
+ 0 : cpu_to_be16(i);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct fdisk_field sgi_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_NUMBER }
+};
+
+static const struct fdisk_label_operations sgi_operations =
+{
+ .probe = sgi_probe_label,
+ .write = sgi_write_disklabel,
+ .verify = sgi_verify_disklabel,
+ .get_item = sgi_get_disklabel_item,
+ .create = sgi_create_disklabel,
+
+ .get_part = sgi_get_partition,
+ .set_part = sgi_set_partition,
+ .add_part = sgi_add_partition,
+ .del_part = sgi_delete_partition,
+
+ .part_is_used = sgi_partition_is_used,
+ .part_toggle_flag = sgi_toggle_partition_flag
+};
+
+/* Allocates an SGI label driver. */
+struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_sgi_label *sgi;
+
+ sgi = calloc(1, sizeof(*sgi));
+ if (!sgi)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) sgi;
+ lb->name = "sgi";
+ lb->id = FDISK_DISKLABEL_SGI;
+ lb->op = &sgi_operations;
+ lb->parttypes = sgi_parttypes;
+ lb->nparttypes = ARRAY_SIZE(sgi_parttypes) - 1;
+ lb->fields = sgi_fields;
+ lb->nfields = ARRAY_SIZE(sgi_fields);
+
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) sgi;
+}
diff --git a/libfdisk/src/sun.c b/libfdisk/src/sun.c
new file mode 100644
index 0000000..dde9750
--- /dev/null
+++ b/libfdisk/src/sun.c
@@ -0,0 +1,1192 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on original code from fdisk:
+ * Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996
+ * Merged with fdisk for other architectures, aeb, June 1998.
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> Mar 1999, Internationalization
+ */
+#include <stdio.h> /* stderr */
+#include <stdlib.h> /* qsort */
+#include <string.h> /* strstr */
+#include <unistd.h> /* write */
+#include <sys/ioctl.h> /* ioctl */
+
+#include "blkdev.h"
+#include "bitops.h"
+
+#include "fdiskP.h"
+#include "pt-sun.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: sun
+ * @title: SUN
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SUN stuff
+ */
+struct fdisk_sun_label {
+ struct fdisk_label head; /* generic part */
+ struct sun_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */
+};
+
+static struct fdisk_parttype sun_parttypes[] = {
+ {SUN_TAG_UNASSIGNED, N_("Unassigned")},
+ {SUN_TAG_BOOT, N_("Boot")},
+ {SUN_TAG_ROOT, N_("SunOS root")},
+ {SUN_TAG_SWAP, N_("SunOS swap")},
+ {SUN_TAG_USR, N_("SunOS usr")},
+ {SUN_TAG_WHOLEDISK, N_("Whole disk")},
+ {SUN_TAG_STAND, N_("SunOS stand")},
+ {SUN_TAG_VAR, N_("SunOS var")},
+ {SUN_TAG_HOME, N_("SunOS home")},
+ {SUN_TAG_ALTSCTR, N_("SunOS alt sectors")},
+ {SUN_TAG_CACHE, N_("SunOS cachefs")},
+ {SUN_TAG_RESERVED, N_("SunOS reserved")},
+ {SUN_TAG_LINUX_SWAP, N_("Linux swap")},
+ {SUN_TAG_LINUX_NATIVE, N_("Linux native")},
+ {SUN_TAG_LINUX_LVM, N_("Linux LVM")},
+ {SUN_TAG_LINUX_RAID, N_("Linux raid autodetect")},
+ { 0, NULL }
+};
+
+/* return pointer buffer with on-disk data */
+static inline struct sun_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ return ((struct fdisk_sun_label *) cxt->label)->header;
+}
+
+/* return in-memory sun fdisk data */
+static inline struct fdisk_sun_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ return (struct fdisk_sun_label *) cxt->label;
+}
+
+static void set_partition(struct fdisk_context *cxt, size_t i,
+ uint64_t start, uint64_t stop, uint16_t sysid)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sysid);
+
+ if (start / (cxt->geom.heads * cxt->geom.sectors) > UINT32_MAX)
+ fdisk_warnx(cxt, _("#%zu: start cylinder overflows Sun label limits"), i+1);
+
+ if (stop - start > UINT32_MAX)
+ fdisk_warnx(cxt, _("#%zu: number of sectors overflow Sun label limits"), i+1);
+
+ sunlabel->vtoc.infos[i].id = cpu_to_be16(sysid);
+ sunlabel->vtoc.infos[i].flags = cpu_to_be16(0);
+ sunlabel->partitions[i].start_cylinder =
+ cpu_to_be32(start / (cxt->geom.heads * cxt->geom.sectors));
+ sunlabel->partitions[i].num_sectors = cpu_to_be32(stop - start);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info_new_partition(cxt, i + 1, start, stop, t);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ size_t ct = 0, i;
+
+ assert(sunlabel);
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (sunlabel->partitions[i].num_sectors)
+ ct++;
+ }
+ return ct;
+}
+
+static int sun_probe_label(struct fdisk_context *cxt)
+{
+ struct fdisk_sun_label *sun;
+ struct sun_disklabel *sunlabel;
+ int need_fixing = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ /* map first sector to header */
+ sun = self_label(cxt);
+ sun->header = (struct sun_disklabel *) cxt->firstsector;
+ sunlabel = sun->header;
+
+ if (be16_to_cpu(sunlabel->magic) != SUN_LABEL_MAGIC) {
+ sun->header = NULL;
+ return 0; /* failed */
+ }
+
+ if (sun_pt_checksum(sunlabel)) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong checksum. "
+ "Probably you'll have to set all the values, "
+ "e.g. heads, sectors, cylinders and partitions "
+ "or force a fresh label (s command in main menu)"));
+ return 1;
+ }
+
+ cxt->label->nparts_max = SUN_MAXPARTITIONS;
+ cxt->geom.heads = be16_to_cpu(sunlabel->nhead);
+ cxt->geom.cylinders = be16_to_cpu(sunlabel->ncyl);
+ cxt->geom.sectors = be16_to_cpu(sunlabel->nsect);
+
+ /* we have on label geom, but user has to win */
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ if (be32_to_cpu(sunlabel->vtoc.version) != SUN_VTOC_VERSION) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong version [%d]."),
+ be32_to_cpu(sunlabel->vtoc.version));
+ need_fixing = 1;
+ }
+ if (be32_to_cpu(sunlabel->vtoc.sanity) != SUN_VTOC_SANITY) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.sanity [0x%08x]."),
+ be32_to_cpu(sunlabel->vtoc.sanity));
+ need_fixing = 1;
+ }
+ if (be16_to_cpu(sunlabel->vtoc.nparts) != SUN_MAXPARTITIONS) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.nparts [%u]."),
+ be16_to_cpu(sunlabel->vtoc.nparts));
+ need_fixing = 1;
+ }
+ if (need_fixing) {
+ fdisk_warnx(cxt, _("Warning: Wrong values need to be fixed up and "
+ "will be corrected by w(rite)"));
+
+ sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+ sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+ sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ return 1;
+}
+
+static void ask_geom(struct fdisk_context *cxt)
+{
+ uintmax_t res;
+
+ assert(cxt);
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.heads, 1,
+ cxt->label->geom_max.heads,
+ _("Heads"), &res) == 0)
+ cxt->geom.heads = res;
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.sectors, 1,
+ cxt->label->geom_max.sectors,
+ _("Sectors/track"), &res) == 0)
+ cxt->geom.sectors = res;
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.cylinders, 1,
+ cxt->label->geom_max.cylinders,
+ _("Cylinders"), &res) == 0)
+ cxt->geom.cylinders = res;
+}
+
+static int sun_create_disklabel(struct fdisk_context *cxt)
+{
+ unsigned int ndiv;
+ struct fdisk_sun_label *sun; /* libfdisk sun handler */
+ struct sun_disklabel *sunlabel; /* on disk data */
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ /* map first sector to header */
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ sun = self_label(cxt);
+ sun->header = (struct sun_disklabel *) cxt->firstsector;
+
+ sunlabel = sun->header;
+
+ cxt->label->nparts_max = SUN_MAXPARTITIONS;
+
+ sunlabel->magic = cpu_to_be16(SUN_LABEL_MAGIC);
+ sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+ sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+ sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+
+ if (cxt->geom.heads && cxt->geom.sectors) {
+ fdisk_sector_t llsectors;
+
+ if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+ int sec_fac = cxt->sector_size / 512;
+ fdisk_sector_t llcyls;
+
+ llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+ cxt->geom.cylinders = llcyls;
+ if (cxt->geom.cylinders != llcyls)
+ cxt->geom.cylinders = ~0;
+ } else {
+ fdisk_warnx(cxt,
+ _("BLKGETSIZE ioctl failed on %s. "
+ "Using geometry cylinder value of %ju. "
+ "This value may be truncated for devices "
+ "> 33.8 GB."),
+ cxt->dev_path, (uintmax_t) cxt->geom.cylinders);
+ }
+ } else
+ ask_geom(cxt);
+
+ sunlabel->acyl = cpu_to_be16(0);
+ sunlabel->pcyl = cpu_to_be16(cxt->geom.cylinders);
+ sunlabel->rpm = cpu_to_be16(5400);
+ sunlabel->intrlv = cpu_to_be16(1);
+ sunlabel->apc = cpu_to_be16(0);
+
+ sunlabel->nhead = cpu_to_be16(cxt->geom.heads);
+ sunlabel->nsect = cpu_to_be16(cxt->geom.sectors);
+ sunlabel->ncyl = cpu_to_be16(cxt->geom.cylinders);
+
+ snprintf((char *) sunlabel->label_id, sizeof(sunlabel->label_id),
+ "Linux cyl %ju alt %u hd %u sec %ju",
+ (uintmax_t) cxt->geom.cylinders,
+ be16_to_cpu(sunlabel->acyl),
+ cxt->geom.heads,
+ (uintmax_t) cxt->geom.sectors);
+
+ if (cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors >= 150 * 2048) {
+ ndiv = cxt->geom.cylinders - (50 * 2048 / (cxt->geom.heads * cxt->geom.sectors)); /* 50M swap */
+ } else
+ ndiv = cxt->geom.cylinders * 2 / 3;
+
+ /* create the default layout only if no-script defined */
+ if (!cxt->script) {
+ set_partition(cxt, 0, 0,
+ (uint64_t) ndiv * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_LINUX_NATIVE);
+ set_partition(cxt, 1,
+ (uint64_t) ndiv * cxt->geom.heads * cxt->geom.sectors,
+ (uint64_t) cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_LINUX_SWAP);
+ sunlabel->vtoc.infos[1].flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+
+ set_partition(cxt, 2, 0,
+ (uint64_t) cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_WHOLEDISK);
+ }
+
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ fdisk_info(cxt, _("Created a new Sun disklabel."));
+ return 0;
+}
+
+static int sun_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_info *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sunlabel = self_disklabel(cxt);
+ p = &sunlabel->vtoc.infos[i];
+
+ switch (flag) {
+ case SUN_FLAG_UNMNT:
+ p->flags ^= cpu_to_be16(SUN_FLAG_UNMNT);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ case SUN_FLAG_RONLY:
+ p->flags ^= cpu_to_be16(SUN_FLAG_RONLY);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static void fetch_sun(struct fdisk_context *cxt,
+ uint32_t *starts,
+ uint32_t *lens,
+ uint32_t *start,
+ uint32_t *stop)
+{
+ struct sun_disklabel *sunlabel;
+ int continuous = 1;
+ size_t i;
+ int sectors_per_cylinder = cxt->geom.heads * cxt->geom.sectors;
+
+ assert(cxt);
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ *start = 0;
+ *stop = cxt->geom.cylinders * sectors_per_cylinder;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ struct sun_partition *part = &sunlabel->partitions[i];
+ struct sun_info *info = &sunlabel->vtoc.infos[i];
+
+ if (part->num_sectors &&
+ be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED &&
+ be16_to_cpu(info->id) != SUN_TAG_WHOLEDISK) {
+ starts[i] = be32_to_cpu(part->start_cylinder) *
+ sectors_per_cylinder;
+ lens[i] = be32_to_cpu(part->num_sectors);
+ if (continuous) {
+ if (starts[i] == *start) {
+ *start += lens[i];
+ int remained_sectors = *start % sectors_per_cylinder;
+ if (remained_sectors) {
+ *start += sectors_per_cylinder - remained_sectors;
+ }
+ } else if (starts[i] + lens[i] >= *stop)
+ *stop = starts[i];
+ else
+ continuous = 0;
+ /* There will be probably more gaps
+ than one, so lets check afterwards */
+ }
+ } else {
+ starts[i] = 0;
+ lens[i] = 0;
+ }
+ }
+}
+
+/* non-Linux qsort_r(3) has usually differently ordered arguments */
+#if !defined (__linux__) || !defined (__GLIBC__)
+# undef HAVE_QSORT_R
+#endif
+
+#ifdef HAVE_QSORT_R
+static int verify_sun_cmp(int *a, int *b, void *data)
+{
+ unsigned int *verify_sun_starts = (unsigned int *) data;
+
+ if (*a == -1)
+ return 1;
+ if (*b == -1)
+ return -1;
+ if (verify_sun_starts[*a] > verify_sun_starts[*b])
+ return 1;
+ return -1;
+}
+#endif
+
+static int sun_verify_disklabel(struct fdisk_context *cxt)
+{
+ uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS], start, stop;
+ uint32_t i,j,k,starto,endo;
+#ifdef HAVE_QSORT_R
+ int array[SUN_MAXPARTITIONS];
+ unsigned int *verify_sun_starts;
+#endif
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ fetch_sun(cxt, starts, lens, &start, &stop);
+
+ for (k = 0; k < 7; k++) {
+ for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+ if (k && (lens[i] % (cxt->geom.heads * cxt->geom.sectors)))
+ fdisk_warnx(cxt, _("Partition %u doesn't end on cylinder boundary."), i+1);
+ if (lens[i]) {
+ for (j = 0; j < i; j++)
+ if (lens[j]) {
+ if (starts[j] == starts[i]+lens[i]) {
+ starts[j] = starts[i]; lens[j] += lens[i];
+ lens[i] = 0;
+ } else if (starts[i] == starts[j]+lens[j]){
+ lens[j] += lens[i];
+ lens[i] = 0;
+ } else if (!k) {
+ if (starts[i] < starts[j]+lens[j] &&
+ starts[j] < starts[i]+lens[i]) {
+ starto = starts[i];
+ if (starts[j] > starto)
+ starto = starts[j];
+ endo = starts[i]+lens[i];
+ if (starts[j]+lens[j] < endo)
+ endo = starts[j]+lens[j];
+ fdisk_warnx(cxt, _("Partition %u overlaps with others in "
+ "sectors %u-%u."), i+1, starto, endo);
+ }
+ }
+ }
+ }
+ }
+ }
+
+#ifdef HAVE_QSORT_R
+ for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+ if (lens[i])
+ array[i] = i;
+ else
+ array[i] = -1;
+ }
+ verify_sun_starts = starts;
+
+ qsort_r(array,ARRAY_SIZE(array),sizeof(array[0]),
+ (int (*)(const void *,const void *,void *)) verify_sun_cmp,
+ verify_sun_starts);
+
+ if (array[0] == -1) {
+ fdisk_info(cxt, _("No partitions defined."));
+ return 0;
+ }
+ stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors;
+ if (starts[array[0]])
+ fdisk_warnx(cxt, _("Unused gap - sectors 0-%u."), starts[array[0]]);
+ for (i = 0; i < 7 && array[i+1] != -1; i++) {
+ fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."),
+ (starts[array[i]] + lens[array[i]]),
+ starts[array[i+1]]);
+ }
+ start = (starts[array[i]] + lens[array[i]]);
+ if (start < stop)
+ fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."), start, stop);
+#endif
+ return 0;
+}
+
+
+static int is_free_sector(struct fdisk_context *cxt,
+ fdisk_sector_t s, uint32_t starts[], uint32_t lens[])
+{
+ size_t i;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (lens[i] && starts[i] <= s
+ && starts[i] + lens[i] > s)
+ return 0;
+ }
+ return 1;
+}
+
+static int sun_add_partition(
+ struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS];
+ struct sun_partition *part;
+ struct sun_info *info;
+ uint32_t start, stop, stop2;
+ int whole_disk = 0;
+ int sys = pa && pa->type ? pa->type->code : SUN_TAG_LINUX_NATIVE;
+ int rc;
+ size_t n;
+
+ char mesg[256];
+ size_t i;
+ unsigned int first, last;
+
+ DBG(LABEL, ul_debug("SUN adding partition"));
+
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ if (rc)
+ return rc;
+
+ part = &sunlabel->partitions[n];
+ info = &sunlabel->vtoc.infos[n];
+
+ if (part->num_sectors && be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED) {
+ fdisk_info(cxt, _("Partition %zu is already defined. Delete "
+ "it before re-adding it."), n + 1);
+ return -EINVAL;
+ }
+
+ fetch_sun(cxt, starts, lens, &start, &stop);
+
+ if (pa && pa->type && pa->type->code == SUN_TAG_WHOLEDISK)
+ whole_disk = 1;
+
+ if (stop <= start) {
+ if (n == 2)
+ whole_disk = 1;
+ else {
+ fdisk_info(cxt, _("Other partitions already cover the "
+ "whole disk. Delete some/shrink them before retry."));
+ return -EINVAL;
+ }
+ }
+
+ if (pa && pa->start_follow_default)
+ first = start;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ first = pa->start;
+
+ if (!whole_disk && !is_free_sector(cxt, first, starts, lens))
+ return -ERANGE;
+ } else {
+ struct fdisk_ask *ask;
+
+ if (n == 2)
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "third partition covers the whole disk "
+ "and is of type `Whole disk'"));
+
+ snprintf(mesg, sizeof(mesg), _("First %s"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR));
+ for (;;) {
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+ if (whole_disk) {
+ fdisk_ask_number_set_low(ask, 0); /* minimal */
+ fdisk_ask_number_set_default(ask, 0); /* default */
+ fdisk_ask_number_set_high(ask, 0); /* maximal */
+ } else if (n == 2) {
+ fdisk_ask_number_set_low(ask, 0); /* minimal */
+ fdisk_ask_number_set_default(ask, 0); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ } else {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, start)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, start)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ }
+ rc = fdisk_do_ask(cxt, ask);
+ first = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+
+ if (fdisk_use_cylinders(cxt))
+ first *= fdisk_get_units_per_sector(cxt);
+
+ if (!fdisk_use_cylinders(cxt)) {
+ /* Starting sector has to be properly aligned */
+ int cs = cxt->geom.heads * cxt->geom.sectors;
+ int x = first % cs;
+
+ if (x) {
+ fdisk_info(cxt, _("Aligning the first sector from %u to %u "
+ "to be on cylinder boundary."),
+ first, first + cs - x);
+ first += cs - x;
+ }
+ }
+
+ /* ewt asks to add: "don't start a partition at cyl 0"
+ However, edmundo@rano.demon.co.uk writes:
+ "In addition to having a Sun partition table, to be able to
+ boot from the disc, the first partition, /dev/sdX1, must
+ start at cylinder 0. This means that /dev/sdX1 contains
+ the partition table and the boot block, as these are the
+ first two sectors of the disc. Therefore you must be
+ careful what you use /dev/sdX1 for. In particular, you must
+ not use a partition starting at cylinder 0 for Linux swap,
+ as that would overwrite the partition table and the boot
+ block. You may, however, use such a partition for a UFS
+ or EXT2 file system, as these file systems leave the first
+ 1024 bytes undisturbed. */
+ /* On the other hand, one should not use partitions
+ starting at block 0 in an md, or the label will
+ be trashed. */
+ if (!is_free_sector(cxt, first, starts, lens) && !whole_disk) {
+ if (n == 2 && !first) {
+ whole_disk = 1;
+ break;
+ }
+ fdisk_warnx(cxt, _("Sector %d is already allocated"), first);
+ } else
+ break;
+ }
+ }
+
+ stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors; /* ancient */
+ stop2 = stop;
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (starts[i] > first && starts[i] < stop)
+ stop = starts[i];
+ }
+
+ /* last */
+ if (pa && pa->end_follow_default)
+ last = whole_disk || (n == 2 && !first) ? stop2 : stop;
+
+ else if (pa && fdisk_partition_has_size(pa)) {
+ last = first + pa->size;
+
+ if (!whole_disk && last > stop)
+ return -ERANGE;
+ } else {
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+
+ snprintf(mesg, sizeof(mesg),
+ _("Last %s or +/-%s or +/-size{K,M,G,T,P}"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR),
+ fdisk_get_unit(cxt, FDISK_PLURAL));
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (whole_disk) {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, stop2)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */
+ fdisk_ask_number_set_base(ask, 0);
+ } else if (n == 2 && !first) {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ } else {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ }
+
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ else
+ fdisk_ask_number_set_unit(ask, cxt->sector_size);
+
+ rc = fdisk_do_ask(cxt, ask);
+ last = fdisk_ask_number_get_result(ask);
+
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ last *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (n == 2 && !first) {
+ if (last >= stop2) {
+ whole_disk = 1;
+ last = stop2;
+ } else if (last > stop) {
+ fdisk_warnx(cxt,
+ _("You haven't covered the whole disk with the 3rd partition, but your value\n"
+ "%lu %s covers some other partition. Your entry has been changed\n"
+ "to %lu %s"),
+ (unsigned long) fdisk_scround(cxt, last), fdisk_get_unit(cxt, FDISK_SINGULAR),
+ (unsigned long) fdisk_scround(cxt, stop), fdisk_get_unit(cxt, FDISK_SINGULAR));
+ last = stop;
+ }
+ } else if (!whole_disk && last > stop)
+ last = stop;
+
+ if (whole_disk)
+ sys = SUN_TAG_WHOLEDISK;
+
+ DBG(LABEL, ul_debug("SUN new partition #%zu: first=%u, last=%u, sys=%d", n, first, last, sys));
+
+ set_partition(cxt, n, first, last, sys);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ if (partno)
+ *partno = n;
+ return 0;
+}
+
+static int sun_delete_partition(struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ struct sun_info *info;
+ unsigned int nsec;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+ part = &sunlabel->partitions[partnum];
+ info = &sunlabel->vtoc.infos[partnum];
+
+ if (partnum == 2 &&
+ be16_to_cpu(info->id) == SUN_TAG_WHOLEDISK &&
+ !part->start_cylinder &&
+ (nsec = be32_to_cpu(part->num_sectors))
+ == cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders)
+ fdisk_info(cxt, _("If you want to maintain SunOS/Solaris compatibility, "
+ "consider leaving this "
+ "partition as Whole disk (5), starting at 0, with %u "
+ "sectors"), nsec);
+ info->id = cpu_to_be16(SUN_TAG_UNASSIGNED);
+ part->num_sectors = 0;
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int sun_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct sun_disklabel *sunlabel;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ switch (item->id) {
+ case SUN_LABELITEM_LABELID:
+ item->name =_("Label ID");
+ item->type = 's';
+ item->data.str = *sunlabel->label_id ? strndup((char *)sunlabel->label_id, sizeof(sunlabel->label_id)) : NULL;
+ break;
+ case SUN_LABELITEM_VTOCID:
+ item->name =_("Volume ID");
+ item->type = 's';
+ item->data.str = *sunlabel->vtoc.volume_id ? strndup((char *)sunlabel->vtoc.volume_id, sizeof(sunlabel->vtoc.volume_id)) : NULL;
+ break;
+ case SUN_LABELITEM_RPM:
+ item->name =_("Rpm");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->rpm);
+ break;
+ case SUN_LABELITEM_ACYL:
+ item->name =_("Alternate cylinders");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->acyl);
+ break;
+ case SUN_LABELITEM_PCYL:
+ item->name =_("Physical cylinders");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->pcyl);
+ break;
+ case SUN_LABELITEM_APC:
+ item->name =_("Extra sects/cyl");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->apc);
+ break;
+ case SUN_LABELITEM_INTRLV:
+ item->name =_("Interleave");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->intrlv);
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static struct fdisk_parttype *sun_get_parttype(
+ struct fdisk_context *cxt,
+ size_t n)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ struct fdisk_parttype *t;
+
+ if (n >= cxt->label->nparts_max)
+ return NULL;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label,
+ be16_to_cpu(sunlabel->vtoc.infos[n].id));
+ return t ? : fdisk_new_unknown_parttype(be16_to_cpu(sunlabel->vtoc.infos[n].id), NULL);
+}
+
+
+static int sun_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ uint16_t flags;
+ uint64_t start, len;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (n >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sunlabel = self_disklabel(cxt);
+ part = &sunlabel->partitions[n];
+
+ pa->used = part->num_sectors ? 1 : 0;
+ if (!pa->used)
+ return 0;
+
+ flags = be16_to_cpu(sunlabel->vtoc.infos[n].flags);
+ start = (uint64_t) be32_to_cpu(part->start_cylinder)
+ * cxt->geom.heads * cxt->geom.sectors;
+ len = be32_to_cpu(part->num_sectors);
+
+ pa->type = sun_get_parttype(cxt, n);
+ if (pa->type && pa->type->code == SUN_TAG_WHOLEDISK)
+ pa->wholedisk = 1;
+
+ if (flags & SUN_FLAG_UNMNT || flags & SUN_FLAG_RONLY) {
+ if (asprintf(&pa->attrs, "%c%c",
+ flags & SUN_FLAG_UNMNT ? 'u' : ' ',
+ flags & SUN_FLAG_RONLY ? 'r' : ' ') < 0)
+ return -ENOMEM;
+ }
+
+ pa->start = start;
+ pa->size = len;
+
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_alt_cyl:
+ * @cxt: context
+ *
+ * Sets number of alternative cylinders. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->acyl), /* default */
+ 65535, /* high */
+ _("Number of alternate cylinders"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+
+ sunlabel->acyl = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_xcyl:
+ * @cxt: context
+ *
+ * Sets number of extra sectors per cylinder. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_xcyl(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->apc), /* default */
+ cxt->geom.sectors, /* high */
+ _("Extra sectors per cylinder"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->apc = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_ilfact:
+ * @cxt: context
+ *
+ * Sets interleave factor. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_ilfact(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 1, /* low */
+ be16_to_cpu(sunlabel->intrlv), /* default */
+ 32, /* high */
+ _("Interleave factor"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->intrlv = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_rspeed
+ * @cxt: context
+ *
+ * Sets rotation speed. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_rspeed(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 1, /* low */
+ be16_to_cpu(sunlabel->rpm), /* default */
+ USHRT_MAX, /* high */
+ _("Rotation speed (rpm)"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->rpm = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_pcylcount
+ * @cxt: context
+ *
+ * Sets number of physical cylinders. This function uses libfdisk Ask API for
+ * dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_pcylcount(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->pcyl), /* default */
+ USHRT_MAX, /* high */
+ _("Number of physical cylinders"), /* query */
+ &res); /* result */
+ if (!rc)
+ return rc;
+ sunlabel->pcyl = cpu_to_be16(res);
+ return 0;
+}
+
+static int sun_write_disklabel(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel;
+ const size_t sz = sizeof(struct sun_disklabel);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ /* Maybe geometry has been modified */
+ sunlabel->nhead = cpu_to_be16(cxt->geom.heads);
+ sunlabel->nsect = cpu_to_be16(cxt->geom.sectors);
+
+ if (cxt->geom.cylinders != be16_to_cpu(sunlabel->ncyl)) {
+ int a = cpu_to_be16(cxt->geom.cylinders);
+ int b = be16_to_cpu(sunlabel->acyl);
+ sunlabel->ncyl = a - b;
+ }
+
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+ return -errno;
+ if (write_all(cxt->dev_fd, sunlabel, sz) != 0)
+ return -errno;
+
+ return 0;
+}
+
+static int sun_set_partition(
+ struct fdisk_context *cxt,
+ size_t i,
+ struct fdisk_partition *pa)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ struct sun_info *info;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ if (pa->type) {
+ struct fdisk_parttype *t = pa->type;
+
+ if (t->code > UINT16_MAX)
+ return -EINVAL;
+
+ if (i == 2 && t->code != SUN_TAG_WHOLEDISK)
+ fdisk_info(cxt, _("Consider leaving partition 3 as Whole disk (5),\n"
+ "as SunOS/Solaris expects it and even Linux likes it.\n"));
+
+ part = &sunlabel->partitions[i];
+ info = &sunlabel->vtoc.infos[i];
+
+ if (cxt->script == NULL &&
+ t->code == SUN_TAG_LINUX_SWAP && !part->start_cylinder) {
+ int yes, rc;
+
+ rc = fdisk_ask_yesno(cxt,
+ _("It is highly recommended that the partition at offset 0\n"
+ "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n"
+ "there may destroy your partition table and bootblock.\n"
+ "Are you sure you want to tag the partition as Linux swap?"), &yes);
+ if (rc)
+ return rc;
+ if (!yes)
+ return 1;
+ }
+
+ switch (t->code) {
+ case SUN_TAG_SWAP:
+ case SUN_TAG_LINUX_SWAP:
+ /* swaps are not mountable by default */
+ info->flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+ break;
+ default:
+ /* assume other types are mountable;
+ user can change it anyway */
+ info->flags &= ~cpu_to_be16(SUN_FLAG_UNMNT);
+ break;
+ }
+ info->id = cpu_to_be16(t->code);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ sunlabel->partitions[i].start_cylinder =
+ cpu_to_be32(pa->start / (cxt->geom.heads * cxt->geom.sectors));
+ if (fdisk_partition_has_size(pa))
+ sunlabel->partitions[i].num_sectors = cpu_to_be32(pa->size);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+static int sun_reset_alignment(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ fdisk_set_first_lba(cxt, 0);
+ return 0;
+}
+
+
+static int sun_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct sun_disklabel *sunlabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+
+ sunlabel = self_disklabel(cxt);
+ return sunlabel->partitions[i].num_sectors ? 1 : 0;
+}
+
+static const struct fdisk_field sun_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, 0 },
+ { FDISK_FIELD_ATTR, N_("Flags"), 0, FDISK_FIELDFL_NUMBER }
+};
+
+static const struct fdisk_label_operations sun_operations =
+{
+ .probe = sun_probe_label,
+ .write = sun_write_disklabel,
+ .verify = sun_verify_disklabel,
+ .create = sun_create_disklabel,
+ .get_item = sun_get_disklabel_item,
+
+ .get_part = sun_get_partition,
+ .set_part = sun_set_partition,
+ .add_part = sun_add_partition,
+ .del_part = sun_delete_partition,
+
+ .part_is_used = sun_partition_is_used,
+ .part_toggle_flag = sun_toggle_partition_flag,
+
+ .reset_alignment = sun_reset_alignment,
+};
+
+/*
+ * allocates SUN label driver
+ */
+struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_sun_label *sun;
+
+ sun = calloc(1, sizeof(*sun));
+ if (!sun)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) sun;
+ lb->name = "sun";
+ lb->id = FDISK_DISKLABEL_SUN;
+ lb->op = &sun_operations;
+ lb->parttypes = sun_parttypes;
+ lb->nparttypes = ARRAY_SIZE(sun_parttypes) - 1;
+ lb->fields = sun_fields;
+ lb->nfields = ARRAY_SIZE(sun_fields);
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ lb->geom_min.sectors = 1;
+ lb->geom_min.heads = 1;
+ lb->geom_min.cylinders = 1;
+
+ lb->geom_max.sectors = 1024;
+ lb->geom_max.heads = 1024;
+ lb->geom_max.cylinders = USHRT_MAX;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) sun;
+}
diff --git a/libfdisk/src/table.c b/libfdisk/src/table.c
new file mode 100644
index 0000000..84c60d4
--- /dev/null
+++ b/libfdisk/src/table.c
@@ -0,0 +1,797 @@
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: table
+ * @title: Table
+ * @short_description: container for fdisk partitions
+ *
+ * The fdisk_table is simple container for fdisk_partitions. The table is no
+ * directly connected to label data (partition table), and table changes don't
+ * affect in-memory or on-disk data.
+ */
+
+/**
+ * fdisk_new_table:
+ *
+ * The table is a container for struct fdisk_partition entries. The container
+ * does not have any real connection with label (partition table) and with
+ * real on-disk data.
+ *
+ * Returns: newly allocated table struct.
+ */
+struct fdisk_table *fdisk_new_table(void)
+{
+ struct fdisk_table *tb = NULL;
+
+ tb = calloc(1, sizeof(*tb));
+ if (!tb)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "alloc"));
+ tb->refcount = 1;
+ INIT_LIST_HEAD(&tb->parts);
+ return tb;
+}
+
+/**
+ * fdisk_reset_table:
+ * @tb: tab pointer
+ *
+ * Removes all entries (partitions) from the table. The partitions with zero
+ * reference count will be deallocated. This function does not modify partition
+ * table.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_reset_table(struct fdisk_table *tb)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "reset"));
+
+ while (!list_empty(&tb->parts)) {
+ struct fdisk_partition *pa = list_entry(tb->parts.next,
+ struct fdisk_partition, parts);
+ fdisk_table_remove_partition(tb, pa);
+ }
+
+ tb->nents = 0;
+ return 0;
+}
+
+/**
+ * fdisk_ref_table:
+ * @tb: table pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_table(struct fdisk_table *tb)
+{
+ if (tb)
+ tb->refcount++;
+}
+
+/**
+ * fdisk_unref_table:
+ * @tb: table pointer
+ *
+ * Descrements reference counter, on zero the @tb is automatically
+ * deallocated.
+ */
+void fdisk_unref_table(struct fdisk_table *tb)
+{
+ if (!tb)
+ return;
+
+ tb->refcount--;
+ if (tb->refcount <= 0) {
+ fdisk_reset_table(tb);
+
+ DBG(TAB, ul_debugobj(tb, "free"));
+ free(tb);
+ }
+}
+
+/**
+ * fdisk_table_is_empty:
+ * @tb: pointer to tab
+ *
+ * Returns: 1 if the table is without filesystems, or 0.
+ */
+int fdisk_table_is_empty(struct fdisk_table *tb)
+{
+ return tb == NULL || list_empty(&tb->parts) ? 1 : 0;
+}
+
+/**
+ * fdisk_table_get_nents:
+ * @tb: pointer to tab
+ *
+ * Returns: number of entries in table.
+ */
+size_t fdisk_table_get_nents(struct fdisk_table *tb)
+{
+ return tb ? tb->nents : 0;
+}
+
+/**
+ * fdisk_table_next_partition:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @pa: returns the next tab entry
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * while(fdisk_table_next_partition(tb, itr, &pa) == 0) {
+ * ...
+ * }
+ * </programlisting>
+ * </informalexample>
+ */
+int fdisk_table_next_partition(
+ struct fdisk_table *tb,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **pa)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !pa)
+ return -EINVAL;
+ *pa = NULL;
+
+ if (!itr->head)
+ FDISK_ITER_INIT(itr, &tb->parts);
+ if (itr->p != itr->head) {
+ FDISK_ITER_ITERATE(itr, *pa, struct fdisk_partition, parts);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * fdisk_table_get_partition:
+ * @tb: tab pointer
+ * @n: number of entry in table
+ *
+ * Returns: n-th entry from table or NULL
+ */
+struct fdisk_partition *fdisk_table_get_partition(
+ struct fdisk_table *tb,
+ size_t n)
+{
+ struct fdisk_partition *pa = NULL;
+ struct fdisk_iter itr;
+
+ if (!tb)
+ return NULL;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (n == 0)
+ return pa;
+ n--;
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_table_get_partition_by_partno:
+ * @tb: tab pointer
+ * @partno: partition number
+ *
+ * Returns: partition with @partno or NULL.
+ */
+struct fdisk_partition *fdisk_table_get_partition_by_partno(
+ struct fdisk_table *tb,
+ size_t partno)
+{
+ struct fdisk_partition *pa = NULL;
+ struct fdisk_iter itr;
+
+ if (!tb)
+ return NULL;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (pa->partno == partno)
+ return pa;
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_table_add_partition
+ * @tb: tab pointer
+ * @pa: new entry
+ *
+ * Adds a new entry to table and increment @pa reference counter. Don't forget to
+ * use fdisk_unref_partition() after fdisk_table_add_partition() if you want to keep
+ * the @pa referenced by the table only.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
+{
+ if (!tb || !pa)
+ return -EINVAL;
+
+ if (!list_empty(&pa->parts))
+ return -EBUSY;
+
+ fdisk_ref_partition(pa);
+ list_add_tail(&pa->parts, &tb->parts);
+ tb->nents++;
+
+ DBG(TAB, ul_debugobj(tb, "add entry %p [start=%ju, end=%ju, size=%ju, %s %s %s]",
+ pa,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ fdisk_partition_has_end(pa) ? (uintmax_t) fdisk_partition_get_end(pa) : 0,
+ fdisk_partition_has_size(pa) ? (uintmax_t) fdisk_partition_get_size(pa) : 0,
+ fdisk_partition_is_freespace(pa) ? "freespace" : "",
+ fdisk_partition_is_nested(pa) ? "nested" : "",
+ fdisk_partition_is_container(pa) ? "container" : "primary"));
+ return 0;
+}
+
+/* inserts @pa after @poz */
+static int table_insert_partition(
+ struct fdisk_table *tb,
+ struct fdisk_partition *poz,
+ struct fdisk_partition *pa)
+{
+ assert(tb);
+ assert(pa);
+
+ fdisk_ref_partition(pa);
+ if (poz)
+ list_add(&pa->parts, &poz->parts);
+ else
+ list_add(&pa->parts, &tb->parts);
+ tb->nents++;
+
+ DBG(TAB, ul_debugobj(tb, "insert entry %p pre=%p [start=%ju, end=%ju, size=%ju, %s %s %s]",
+ pa, poz ? poz : NULL,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa),
+ (uintmax_t) fdisk_partition_get_size(pa),
+ fdisk_partition_is_freespace(pa) ? "freespace" : "",
+ fdisk_partition_is_nested(pa) ? "nested" : "",
+ fdisk_partition_is_container(pa) ? "container" : ""));
+ return 0;
+}
+
+/**
+ * fdisk_table_remove_partition
+ * @tb: tab pointer
+ * @pa: new entry
+ *
+ * Removes the @pa from the table and de-increment reference counter of the @pa. The
+ * partition with zero reference counter will be deallocated. Don't forget to use
+ * fdisk_ref_partition() before call fdisk_table_remove_partition() if you want
+ * to use @pa later.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
+{
+ if (!tb || !pa)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "remove entry %p", pa));
+ list_del(&pa->parts);
+ INIT_LIST_HEAD(&pa->parts);
+
+ fdisk_unref_partition(pa);
+ tb->nents--;
+
+ return 0;
+}
+
+/**
+ * fdisk_get_partitions
+ * @cxt: fdisk context
+ * @tb: returns table
+ *
+ * This function adds partitions from disklabel to @table, it allocates a new
+ * table if @table points to NULL.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb)
+{
+ size_t i;
+
+ if (!cxt || !cxt->label || !tb)
+ return -EINVAL;
+ if (!cxt->label->op->get_part)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, " -- get table --"));
+
+ if (!*tb && !(*tb = fdisk_new_table()))
+ return -ENOMEM;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ struct fdisk_partition *pa = NULL;
+
+ if (fdisk_get_partition(cxt, i, &pa) != 0)
+ continue;
+ if (fdisk_partition_is_used(pa))
+ fdisk_table_add_partition(*tb, pa);
+ fdisk_unref_partition(pa);
+ }
+
+ return 0;
+}
+
+void fdisk_debug_print_table(struct fdisk_table *tb)
+{
+ struct fdisk_iter itr;
+ struct fdisk_partition *pa;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0)
+ ul_debugobj(tb, "partition %p [partno=%zu, start=%ju, end=%ju, size=%ju%s%s%s] ",
+ pa, pa->partno,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa),
+ (uintmax_t) fdisk_partition_get_size(pa),
+ fdisk_partition_is_nested(pa) ? " nested" : "",
+ fdisk_partition_is_freespace(pa) ? " freespace" : "",
+ fdisk_partition_is_container(pa) ? " container" : "");
+
+}
+
+
+typedef int (*fdisk_partcmp_t)(struct fdisk_partition *, struct fdisk_partition *);
+
+static int cmp_parts_wrapper(struct list_head *a, struct list_head *b, void *data)
+{
+ struct fdisk_partition *pa = list_entry(a, struct fdisk_partition, parts),
+ *pb = list_entry(b, struct fdisk_partition, parts);
+
+ fdisk_partcmp_t cmp = (fdisk_partcmp_t) data;
+
+ return cmp(pa, pb);
+}
+
+
+/**
+ * fdisk_table_sort_partitions:
+ * @tb: table
+ * @cmp: compare function
+ *
+ * Sort partition in the table.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_table_sort_partitions(struct fdisk_table *tb,
+ int (*cmp)(struct fdisk_partition *,
+ struct fdisk_partition *))
+{
+ if (!tb)
+ return -EINVAL;
+
+ /*
+ DBG(TAB, ul_debugobj(tb, "Before sort:"));
+ ON_DBG(TAB, fdisk_debug_print_table(tb));
+ */
+
+ list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp);
+
+ /*
+ DBG(TAB, ul_debugobj(tb, "After sort:"));
+ ON_DBG(TAB, fdisk_debug_print_table(tb));
+ */
+
+ return 0;
+}
+
+/* allocates a new freespace description */
+static int new_freespace(struct fdisk_context *cxt,
+ fdisk_sector_t start,
+ fdisk_sector_t end,
+ struct fdisk_partition *parent,
+ struct fdisk_partition **pa)
+{
+ fdisk_sector_t aligned_start, size;
+
+ assert(cxt);
+ assert(pa);
+
+ *pa = NULL;
+
+ if (start == end)
+ return 0;
+
+ assert(start >= cxt->first_lba);
+ assert(end);
+ assert(end > start);
+
+ aligned_start = fdisk_align_lba_in_range(cxt, start, start, end);
+ size = end - aligned_start + 1ULL;
+
+ if (size == 0) {
+ DBG(TAB, ul_debug("ignore freespace (aligned size is zero)"));
+ return 0;
+ }
+
+ *pa = fdisk_new_partition();
+ if (!*pa)
+ return -ENOMEM;
+
+ (*pa)->freespace = 1;
+ (*pa)->start = aligned_start;
+ (*pa)->size = size;
+
+ if (parent)
+ (*pa)->parent_partno = parent->partno;
+ return 0;
+}
+
+/* add freespace description to the right place within @tb */
+static int table_add_freespace(
+ struct fdisk_context *cxt,
+ struct fdisk_table *tb,
+ fdisk_sector_t start,
+ fdisk_sector_t end,
+ struct fdisk_partition *parent)
+{
+ struct fdisk_partition *pa, *x, *real_parent = NULL, *best = NULL;
+ struct fdisk_iter itr;
+ int rc = 0;
+
+ assert(tb);
+
+ rc = new_freespace(cxt, start, end, parent, &pa);
+ if (rc)
+ return -ENOMEM;
+ if (!pa)
+ return 0;
+
+ assert(fdisk_partition_has_start(pa));
+ assert(fdisk_partition_has_end(pa));
+
+ DBG(TAB, ul_debugobj(tb, "adding freespace"));
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ if (parent && fdisk_partition_has_partno(parent)) {
+ while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
+ if (!fdisk_partition_has_partno(x))
+ continue;
+ if (x->partno == parent->partno) {
+ real_parent = x;
+ break;
+ }
+ }
+ if (!real_parent) {
+ DBG(TAB, ul_debugobj(tb, "not found freespace parent (partno=%zu)",
+ parent->partno));
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ }
+ }
+
+ while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
+ fdisk_sector_t the_end, best_end = 0;
+
+ if (!fdisk_partition_has_end(x))
+ continue;
+
+ the_end = fdisk_partition_get_end(x);
+ if (best)
+ best_end = fdisk_partition_get_end(best);
+
+ if (the_end < pa->start && (!best || best_end < the_end))
+ best = x;
+ }
+
+ if (!best && real_parent)
+ best = real_parent;
+ rc = table_insert_partition(tb, best, pa);
+
+ fdisk_unref_partition(pa);
+
+ DBG(TAB, ul_debugobj(tb, "adding freespace DONE [rc=%d]", rc));
+ return rc;
+}
+
+/* analyze @cont(ainer) in @parts and add all detected freespace into @tb, note
+ * that @parts has to be sorted by partition starts */
+static int check_container_freespace(struct fdisk_context *cxt,
+ struct fdisk_table *parts,
+ struct fdisk_table *tb,
+ struct fdisk_partition *cont)
+{
+ struct fdisk_iter itr;
+ struct fdisk_partition *pa;
+ fdisk_sector_t x, last, grain, lastplusoff;
+ int rc = 0;
+
+ assert(cxt);
+ assert(parts);
+ assert(tb);
+ assert(cont);
+ assert(fdisk_partition_has_start(cont));
+
+ DBG(TAB, ul_debugobj(tb, "analyze container 0x%p", cont));
+
+ last = fdisk_partition_get_start(cont);
+ grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju",
+ (uintmax_t)last, (uintmax_t)grain));
+
+ while (fdisk_table_next_partition(parts, &itr, &pa) == 0) {
+
+ DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju",
+ pa->partno, (uintmax_t)pa->start));
+
+ if (!pa->used || !fdisk_partition_is_nested(pa)
+ || !fdisk_partition_has_start(pa))
+ continue;
+
+ DBG(CXT, ul_debugobj(cxt, "freespace container analyze: partno=%zu, start=%ju, end=%ju",
+ pa->partno,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa)));
+
+ lastplusoff = last + cxt->first_lba;
+ if (pa->start > lastplusoff && pa->start - lastplusoff > grain)
+ rc = table_add_freespace(cxt, tb, lastplusoff, pa->start, cont);
+ if (rc)
+ goto done;
+ last = fdisk_partition_get_end(pa);
+ }
+
+ /* free-space remaining in extended partition */
+ x = fdisk_partition_get_start(cont) + fdisk_partition_get_size(cont) - 1;
+ lastplusoff = last + cxt->first_lba;
+ if (lastplusoff < x && x - lastplusoff > grain) {
+ DBG(TAB, ul_debugobj(tb, "add remaining space in container 0x%p", cont));
+ rc = table_add_freespace(cxt, tb, lastplusoff, x, cont);
+ }
+
+done:
+ DBG(TAB, ul_debugobj(tb, "analyze container 0x%p DONE [rc=%d]", cont, rc));
+ return rc;
+}
+
+
+/**
+ * fdisk_get_freespaces
+ * @cxt: fdisk context
+ * @tb: returns table
+ *
+ * This function adds freespace (described by fdisk_partition) to @table, it
+ * allocates a new table if the @table points to NULL.
+ *
+ * Note that free space smaller than grain (see fdisk_get_grain_size()) is
+ * ignored.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb)
+{
+ int rc = 0;
+ size_t nparts = 0;
+ fdisk_sector_t last, grain;
+ struct fdisk_table *parts = NULL;
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+
+ DBG(CXT, ul_debugobj(cxt, "-- get freespace --"));
+
+ if (!cxt || !cxt->label || !tb)
+ return -EINVAL;
+ if (!*tb && !(*tb = fdisk_new_table()))
+ return -ENOMEM;
+
+ rc = fdisk_get_partitions(cxt, &parts);
+ if (rc)
+ goto done;
+
+ fdisk_table_sort_partitions(parts, fdisk_partition_cmp_start);
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ last = cxt->first_lba;
+ grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+
+ DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju",
+ (uintmax_t)last, (uintmax_t)grain));
+
+ /* analyze gaps between partitions */
+ while (rc == 0 && fdisk_table_next_partition(parts, &itr, &pa) == 0) {
+
+ DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju",
+ pa->partno, (uintmax_t)pa->start));
+
+ if (!pa->used || pa->wholedisk || fdisk_partition_is_nested(pa)
+ || !fdisk_partition_has_start(pa))
+ continue;
+ DBG(CXT, ul_debugobj(cxt, "freespace analyze: partno=%zu, start=%ju, end=%ju",
+ pa->partno,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa)));
+
+ /* We ignore small free spaces (smaller than grain) to keep partitions
+ * aligned, the exception is space before the first partition when
+ * cxt->first_lba is aligned. */
+ if (last + grain < pa->start
+ || (nparts == 0 &&
+ (fdisk_align_lba(cxt, last, FDISK_ALIGN_UP) <
+ pa->start))) {
+ rc = table_add_freespace(cxt, *tb,
+ last + (nparts == 0 ? 0 : 1),
+ pa->start - 1, NULL);
+ }
+ /* add gaps between logical partitions */
+ if (fdisk_partition_is_container(pa))
+ rc = check_container_freespace(cxt, parts, *tb, pa);
+
+ if (fdisk_partition_has_end(pa)) {
+ fdisk_sector_t pa_end = fdisk_partition_get_end(pa);
+ if (pa_end > last)
+ last = fdisk_partition_get_end(pa);
+ }
+ nparts++;
+ }
+
+ /* add free-space behind last partition to the end of the table (so
+ * don't use table_add_freespace()) */
+ if (rc == 0 && last + grain < cxt->last_lba - 1) {
+ DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected"));
+ rc = new_freespace(cxt,
+ last + (last > cxt->first_lba || nparts ? 1 : 0),
+ cxt->last_lba, NULL, &pa);
+ if (pa) {
+ fdisk_table_add_partition(*tb, pa);
+ fdisk_unref_partition(pa);
+ }
+ }
+
+done:
+ fdisk_unref_table(parts);
+
+ DBG(CXT, ul_debugobj(cxt, "get freespace DONE [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * fdisk_table_wrong_order:
+ * @tb: table
+ *
+ * Returns: 1 of the table is not in disk order
+ */
+int fdisk_table_wrong_order(struct fdisk_table *tb)
+{
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ fdisk_sector_t last = 0;
+
+ DBG(TAB, ul_debugobj(tb, "wrong older check"));
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (!fdisk_partition_has_start(pa) || fdisk_partition_is_wholedisk(pa))
+ continue;
+ if (pa->start < last)
+ return 1;
+ last = pa->start;
+ }
+ return 0;
+}
+
+/**
+ * fdisk_apply_table:
+ * @cxt: context
+ * @tb: table
+ *
+ * Add partitions from table @tb to the in-memory disk label. See
+ * fdisk_add_partition(), fdisk_delete_all_partitions(). The partitions
+ * that does not define start (or does not follow the default start)
+ * are ignored.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb)
+{
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ int rc = 0;
+
+ assert(cxt);
+ assert(tb);
+
+ DBG(TAB, ul_debugobj(tb, "applying to context %p", cxt));
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (!fdisk_partition_has_start(pa) && !pa->start_follow_default)
+ continue;
+ rc = fdisk_add_partition(cxt, pa, NULL);
+ if (rc)
+ break;
+ }
+
+ return rc;
+}
+
+int fdisk_diff_tables(struct fdisk_table *a, struct fdisk_table *b,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **res, int *change)
+{
+ struct fdisk_partition *pa = NULL, *pb;
+ int rc = 1;
+
+ assert(itr);
+ assert(res);
+ assert(change);
+
+ DBG(TAB, ul_debugobj(a, "table diff [new table=%p]", b));
+
+ if (a && (itr->head == NULL || itr->head == &a->parts)) {
+ DBG(TAB, ul_debugobj(a, " scanning old table"));
+ do {
+ rc = fdisk_table_next_partition(a, itr, &pa);
+ if (rc != 0)
+ break;
+ } while (!fdisk_partition_has_partno(pa));
+ }
+
+ if (rc == 1 && b) {
+ DBG(TAB, ul_debugobj(a, " scanning new table"));
+ if (itr->head != &b->parts) {
+ DBG(TAB, ul_debugobj(a, " initialize to TAB=%p", b));
+ fdisk_reset_iter(itr, FDISK_ITER_FORWARD);
+ }
+
+ while (fdisk_table_next_partition(b, itr, &pb) == 0) {
+ if (!fdisk_partition_has_partno(pb))
+ continue;
+ if (a == NULL ||
+ fdisk_table_get_partition_by_partno(a, pb->partno) == NULL) {
+ DBG(TAB, ul_debugobj(a, " #%zu ADDED", pb->partno));
+ *change = FDISK_DIFF_ADDED;
+ *res = pb;
+ return 0;
+ }
+ }
+ }
+
+ if (rc) {
+ DBG(TAB, ul_debugobj(a, "table diff done [rc=%d]", rc));
+ return rc; /* error or done */
+ }
+
+ pb = fdisk_table_get_partition_by_partno(b, pa->partno);
+
+ if (!pb) {
+ DBG(TAB, ul_debugobj(a, " #%zu REMOVED", pa->partno));
+ *change = FDISK_DIFF_REMOVED;
+ *res = pa;
+ } else if (pb->start != pa->start) {
+ DBG(TAB, ul_debugobj(a, " #%zu MOVED", pb->partno));
+ *change = FDISK_DIFF_MOVED;
+ *res = pb;
+ } else if (pb->size != pa->size) {
+ DBG(TAB, ul_debugobj(a, " #%zu RESIZED", pb->partno));
+ *change = FDISK_DIFF_RESIZED;
+ *res = pb;
+ } else {
+ DBG(TAB, ul_debugobj(a, " #%zu UNCHANGED", pb->partno));
+ *change = FDISK_DIFF_UNCHANGED;
+ *res = pa;
+ }
+ return 0;
+}
+
diff --git a/libfdisk/src/test.c b/libfdisk/src/test.c
new file mode 100644
index 0000000..31ed7e0
--- /dev/null
+++ b/libfdisk/src/test.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Routines for TEST_PROGRAMs
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM
+#endif
+
+#include "fdiskP.h"
+
+int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[])
+{
+ int rc = -1;
+ struct fdisk_test *ts;
+
+ assert(tests);
+ assert(argc);
+ assert(argv);
+
+ if (argc < 2 ||
+ strcmp(argv[1], "--help") == 0 ||
+ strcmp(argv[1], "-h") == 0)
+ goto usage;
+
+ fdisk_init_debug(0);
+
+ for (ts = tests; ts->name; ts++) {
+ if (strcmp(ts->name, argv[1]) == 0) {
+ rc = ts->body(ts, argc - 1, argv + 1);
+ if (rc)
+ printf("FAILED [rc=%d]", rc);
+ break;
+ }
+ }
+
+ if (rc < 0 && ts->name == NULL)
+ goto usage;
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+usage:
+ printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n",
+ program_invocation_short_name);
+ for (ts = tests; ts->name; ts++) {
+ printf("\t%-15s", ts->name);
+ if (ts->usage)
+ printf(" %s\n", ts->usage);
+ }
+ printf("\n");
+ return EXIT_FAILURE;
+}
diff --git a/libfdisk/src/utils.c b/libfdisk/src/utils.c
new file mode 100644
index 0000000..38ad233
--- /dev/null
+++ b/libfdisk/src/utils.c
@@ -0,0 +1,213 @@
+
+#include "fdiskP.h"
+#include "pathnames.h"
+#include "canonicalize.h"
+
+#include <ctype.h>
+
+/**
+ * SECTION: utils
+ * @title: Utils
+ * @short_description: misc fdisk functions
+ */
+
+static int read_from_device(struct fdisk_context *cxt,
+ unsigned char *buf,
+ uintmax_t start, size_t size)
+{
+ ssize_t r;
+
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "reading: offset=%ju, size=%zu",
+ start, size));
+
+ r = lseek(cxt->dev_fd, start, SEEK_SET);
+ if (r == -1)
+ {
+ DBG(CXT, ul_debugobj(cxt, "failed to seek to offset %ju: %m", start));
+ return -errno;
+ }
+
+ r = read(cxt->dev_fd, buf, size);
+ if (r < 0 || (size_t)r != size) {
+ if (!errno)
+ errno = EINVAL; /* probably too small file/device */
+ DBG(CXT, ul_debugobj(cxt, "failed to read %zu from offset %ju: %m",
+ size, start));
+ return -errno;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Zeros in-memory first sector buffer
+ */
+int fdisk_init_firstsector_buffer(struct fdisk_context *cxt,
+ unsigned int protect_off,
+ unsigned int protect_size)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ assert(protect_off + protect_size <= cxt->sector_size);
+
+ if (!cxt->firstsector || cxt->firstsector_bufsz != cxt->sector_size) {
+ /* Let's allocate a new buffer if no allocated yet, or the
+ * current buffer has incorrect size */
+ if (!cxt->parent || cxt->parent->firstsector != cxt->firstsector)
+ free(cxt->firstsector);
+
+ DBG(CXT, ul_debugobj(cxt, "initialize in-memory first sector "
+ "buffer [sector_size=%lu]", cxt->sector_size));
+ cxt->firstsector = calloc(1, cxt->sector_size);
+ if (!cxt->firstsector)
+ return -ENOMEM;
+
+ cxt->firstsector_bufsz = cxt->sector_size;
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "zeroize in-memory first sector buffer"));
+ memset(cxt->firstsector, 0, cxt->firstsector_bufsz);
+
+ if (protect_size) {
+ /*
+ * It would be possible to reuse data from cxt->firstsector
+ * (call memset() for non-protected area only) and avoid one
+ * read() from the device, but it seems like a too fragile
+ * solution as we have no clue about stuff in the buffer --
+ * maybe it was already modified. Let's re-read from the device
+ * to be sure. -- kzak 13-Apr-2015
+ */
+ DBG(CXT, ul_debugobj(cxt, "first sector protection enabled -- re-reading"));
+ read_from_device(cxt, cxt->firstsector, protect_off, protect_size);
+ }
+ return 0;
+}
+
+int fdisk_read_firstsector(struct fdisk_context *cxt)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->sector_size);
+
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ assert(cxt->sector_size == cxt->firstsector_bufsz);
+
+
+ return read_from_device(cxt, cxt->firstsector, 0, cxt->sector_size);
+}
+
+/**
+ * fdisk_partname:
+ * @dev: device name
+ * @partno: partition name
+ *
+ * Return: allocated buffer with partition name, use free() to deallocate.
+ */
+char *fdisk_partname(const char *dev, size_t partno)
+{
+ char *res = NULL;
+ const char *p = "";
+ char *dev_mapped = NULL;
+ int w = 0;
+
+ if (!dev || !*dev) {
+ if (asprintf(&res, "%zd", partno) > 0)
+ return res;
+ return NULL;
+ }
+
+ /* It is impossible to predict /dev/dm-N partition names. */
+ if (strncmp(dev, "/dev/dm-", sizeof("/dev/dm-") - 1) == 0) {
+ dev_mapped = canonicalize_dm_name (dev + 5);
+ if (dev_mapped)
+ dev = dev_mapped;
+ }
+
+ w = strlen(dev);
+ if (isdigit(dev[w - 1]))
+#ifdef __GNU__
+ p = "s";
+#else
+ p = "p";
+#endif
+
+ /* devfs kludge - note: fdisk partition names are not supposed
+ to equal kernel names, so there is no reason to do this */
+ if (endswith(dev, "disc")) {
+ w -= 4;
+ p = "part";
+ }
+
+ /* udev names partitions by appending -partN
+ e.g. ata-SAMSUNG_SV8004H_0357J1FT712448-part1
+ multipath-tools kpartx.rules also append -partN */
+ if ((strncmp(dev, _PATH_DEV_BYID, sizeof(_PATH_DEV_BYID) - 1) == 0) ||
+ strncmp(dev, _PATH_DEV_BYPATH, sizeof(_PATH_DEV_BYPATH) - 1) == 0 ||
+ strncmp(dev, "/dev/mapper", sizeof("/dev/mapper") - 1) == 0) {
+
+ /* check for <name><partno>, e.g. mpatha1 */
+ if (asprintf(&res, "%.*s%zu", w, dev, partno) <= 0)
+ res = NULL;
+ if (res && access(res, F_OK) == 0)
+ goto done;
+
+ free(res);
+
+ /* check for partition separator "p" */
+ if (asprintf(&res, "%.*sp%zu", w, dev, partno) <= 0)
+ res = NULL;
+ if (res && access(res, F_OK) == 0)
+ goto done;
+
+ free(res);
+
+ /* otherwise, default to "-path" */
+ p = "-part";
+ }
+
+ if (asprintf(&res, "%.*s%s%zu", w, dev, p, partno) <= 0)
+ res = NULL;
+done:
+ free(dev_mapped);
+ return res;
+}
+
+#ifdef TEST_PROGRAM
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) { return NULL; }
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt) { return NULL; }
+
+static int test_partnames(struct fdisk_test *ts, int argc, char *argv[])
+{
+ size_t i;
+ const char *disk = argv[1];
+
+ for (i = 0; i < 5; i++) {
+ char *p = fdisk_partname(disk, i + 1);
+ if (p)
+ printf("%zu: '%s'\n", i + 1, p);
+ free(p);
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--partnames", test_partnames, "<diskname>" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/version.c b/libfdisk/src/version.c
new file mode 100644
index 0000000..9d84b4c
--- /dev/null
+++ b/libfdisk/src/version.c
@@ -0,0 +1,125 @@
+/*
+ * version.c - Return the version of the library
+ *
+ * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
+ *
+ */
+
+/**
+ * SECTION: version-utils
+ * @title: Version functions
+ * @short_description: functions to get the library version.
+ */
+
+#include <ctype.h>
+
+#include "fdiskP.h"
+
+static const char *lib_version = LIBFDISK_VERSION;
+static const char *lib_features[] = {
+#if !defined(NDEBUG) /* libc assert.h stuff */
+ "assert",
+#endif
+ "debug", /* always enabled */
+ NULL
+};
+
+/**
+ * fdisk_parse_version_string:
+ * @ver_string: version string (e.g "2.18.0")
+ *
+ * Returns: release version code.
+ */
+int fdisk_parse_version_string(const char *ver_string)
+{
+ const char *cp;
+ int version = 0;
+
+ assert(ver_string);
+
+ for (cp = ver_string; *cp; cp++) {
+ if (*cp == '.')
+ continue;
+ if (!isdigit(*cp))
+ break;
+ version = (version * 10) + (*cp - '0');
+ }
+ return version;
+}
+
+/**
+ * fdisk_get_library_version:
+ * @ver_string: return pointer to the static library version string if not NULL
+ *
+ * Returns: release version number.
+ */
+int fdisk_get_library_version(const char **ver_string)
+{
+ if (ver_string)
+ *ver_string = lib_version;
+
+ return fdisk_parse_version_string(lib_version);
+}
+
+/**
+ * fdisk_get_library_features:
+ * @features: returns a pointer to the static array of strings, the array is
+ * terminated by NULL.
+ *
+ * Returns: number of items in the features array not including the last NULL,
+ * or less than zero in case of error
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * const char *features;
+ *
+ * fdisk_get_library_features(&features);
+ * while (features && *features)
+ * printf("%s\n", *features++);
+ * </programlisting>
+ * </informalexample>
+ *
+ */
+int fdisk_get_library_features(const char ***features)
+{
+ if (!features)
+ return -EINVAL;
+
+ *features = lib_features;
+ return ARRAY_SIZE(lib_features) - 1;
+}
+
+#ifdef TEST_PROGRAM
+static int test_version(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *ver;
+ const char **features;
+
+ fdisk_get_library_version(&ver);
+
+ printf("Library version: %s\n", ver);
+ printf("Library API version: " LIBFDISK_VERSION "\n");
+ printf("Library features:");
+
+ fdisk_get_library_features(&features);
+ while (features && *features)
+ printf(" %s", *features++);
+
+ if (fdisk_get_library_version(NULL) ==
+ fdisk_parse_version_string(LIBFDISK_VERSION))
+ return 0;
+
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test ts[] = {
+ { "--print", test_version, "print versions" },
+ { NULL }
+ };
+
+ return fdisk_run_test(ts, argc, argv);
+}
+#endif
diff --git a/libfdisk/src/wipe.c b/libfdisk/src/wipe.c
new file mode 100644
index 0000000..ab1d7b8
--- /dev/null
+++ b/libfdisk/src/wipe.c
@@ -0,0 +1,209 @@
+#include "c.h"
+#include "strutils.h"
+
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "fdiskP.h"
+
+struct fdisk_wipe {
+ struct list_head wipes;
+ uint64_t start; /* sectors */
+ uint64_t size; /* sectors */
+};
+
+static struct fdisk_wipe *fdisk_get_wipe_area(
+ struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size)
+{
+ struct list_head *p;
+
+ if (cxt == NULL || list_empty(&cxt->wipes))
+ return NULL;
+
+ list_for_each(p, &cxt->wipes) {
+ struct fdisk_wipe *wp = list_entry(p, struct fdisk_wipe, wipes);
+ if (wp->start == start && wp->size == size)
+ return wp;
+ }
+ return NULL;
+}
+
+void fdisk_free_wipe_areas(struct fdisk_context *cxt)
+{
+ while (!list_empty(&cxt->wipes)) {
+ struct fdisk_wipe *wp = list_entry(cxt->wipes.next,
+ struct fdisk_wipe, wipes);
+ DBG(WIPE, ul_debugobj(wp, "free [start=%ju, size=%ju]",
+ (uintmax_t) wp->start, (uintmax_t) wp->size));
+ list_del(&wp->wipes);
+ free(wp);
+ }
+}
+
+int fdisk_has_wipe_area(struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size)
+{
+ return fdisk_get_wipe_area(cxt, start, size) != NULL;
+}
+
+/* Add/remove new wiping area
+ *
+ * Returns: <0 on error, or old area setting (1: enabled, 0: disabled)
+ */
+int fdisk_set_wipe_area(struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size,
+ int enable)
+{
+ struct fdisk_wipe *wp;
+
+ if (FDISK_IS_UNDEF(start) || FDISK_IS_UNDEF(size))
+ return -EINVAL;
+
+ wp = fdisk_get_wipe_area(cxt, start, size);
+
+ /* disable */
+ if (!enable) {
+ if (wp) {
+ DBG(WIPE, ul_debugobj(wp, "disable [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+ list_del(&wp->wipes);
+ free(wp);
+ return 1;
+ }
+ return 0;
+ }
+
+ /* enable */
+ if (wp)
+ return 1; /* already enabled */
+
+ wp = calloc(1, sizeof(*wp));
+ if (!wp)
+ return -ENOMEM;
+
+ DBG(WIPE, ul_debugobj(wp, "enable [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+
+ INIT_LIST_HEAD(&wp->wipes);
+ wp->start = start;
+ wp->size = size;
+ list_add_tail(&wp->wipes, &cxt->wipes);
+
+ return 0;
+}
+
+#ifndef HAVE_LIBBLKID
+int fdisk_do_wipe(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return 0;
+}
+#else
+int fdisk_do_wipe(struct fdisk_context *cxt)
+{
+ struct list_head *p;
+ blkid_probe pr;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ if (list_empty(&cxt->wipes))
+ return 0;
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return -ENOMEM;
+
+ list_for_each(p, &cxt->wipes) {
+ struct fdisk_wipe *wp = list_entry(p, struct fdisk_wipe, wipes);
+ blkid_loff_t start = (blkid_loff_t) wp->start * cxt->sector_size,
+ size = (blkid_loff_t) wp->size * cxt->sector_size;
+
+ DBG(WIPE, ul_debugobj(wp, "initialize libblkid prober [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+
+ rc = blkid_probe_set_device(pr, cxt->dev_fd, start, size);
+ if (rc) {
+ DBG(WIPE, ul_debugobj(wp, "blkid_probe_set_device() failed [rc=%d]", rc));
+ return rc;
+ }
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC);
+ blkid_probe_enable_partitions(pr, 1);
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC);
+
+ while (blkid_do_probe(pr) == 0) {
+ DBG(WIPE, ul_debugobj(wp, " wiping..."));
+ blkid_do_wipe(pr, FALSE);
+ }
+ }
+
+ blkid_free_probe(pr);
+ return 0;
+}
+#endif
+
+
+/*
+ * Please don't call this function if there is already a PT.
+ *
+ * Returns: 0 if nothing found, < 0 on error, 1 if found a signature
+ */
+#ifndef HAVE_LIBBLKID
+int fdisk_check_collisions(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return 0;
+}
+#else
+int fdisk_check_collisions(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ blkid_probe pr;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ DBG(CXT, ul_debugobj(cxt, "wipe check: initialize libblkid prober"));
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return -ENOMEM;
+ rc = blkid_probe_set_device(pr, cxt->dev_fd, 0, 0);
+ if (rc)
+ return rc;
+
+ cxt->pt_collision = 0;
+ free(cxt->collision);
+ cxt->collision = NULL;
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
+ blkid_probe_enable_partitions(pr, 1);
+
+ /* we care about the first found FS/raid, so don't call blkid_do_probe()
+ * in loop or don't use blkid_do_fullprobe() ... */
+ rc = blkid_do_probe(pr);
+ if (rc == 0) {
+ const char *name = NULL;
+
+ if (blkid_probe_lookup_value(pr, "TYPE", &name, 0) == 0)
+ cxt->collision = strdup(name);
+ else if (blkid_probe_lookup_value(pr, "PTTYPE", &name, 0) == 0) {
+ cxt->collision = strdup(name);
+ cxt->pt_collision = 1;
+ }
+
+ if (name && !cxt->collision)
+ rc = -ENOMEM;
+ }
+
+ blkid_free_probe(pr);
+ return rc;
+}
+#endif
diff --git a/libmount/COPYING b/libmount/COPYING
new file mode 100644
index 0000000..db5ec32
--- /dev/null
+++ b/libmount/COPYING
@@ -0,0 +1,8 @@
+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.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPL-2.1-or-later
diff --git a/libmount/Makemodule.am b/libmount/Makemodule.am
new file mode 100644
index 0000000..8546c2f
--- /dev/null
+++ b/libmount/Makemodule.am
@@ -0,0 +1,15 @@
+if BUILD_LIBMOUNT
+
+include libmount/src/Makemodule.am
+include libmount/python/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libmount/docs
+endif
+
+pkgconfig_DATA += libmount/mount.pc
+PATHFILES += libmount/mount.pc
+EXTRA_DIST += libmount/COPYING
+
+endif # BUILD_LIBMOUNT
diff --git a/libmount/docs/Makefile.am b/libmount/docs/Makefile.am
new file mode 100644
index 0000000..18b036f
--- /dev/null
+++ b/libmount/docs/Makefile.am
@@ -0,0 +1,93 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libmount
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space mnt
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libmount/src/libmount.h
+CFILE_GLOB=$(top_srcdir)/libmount/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=mountP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
diff --git a/libmount/docs/Makefile.in b/libmount/docs/Makefile.in
new file mode 100644
index 0000000..99e2c8f
--- /dev/null
+++ b/libmount/docs/Makefile.in
@@ -0,0 +1,894 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# WARNING: this is not gtk-doc.make file from gtk-doc project. This
+# file has been modified to match with util-linux requirements:
+#
+# * install files to $datadir
+# * don't maintain generated files in git repository
+# * don't distribute the final html files
+# * don't require --enable-gtk-doc for "make dist"
+# * support out-of-tree build ($srcdir != $builddir)
+#
+# -- kzak, Nov 2009
+#
+
+####################################
+# Everything below here is generic #
+####################################
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = libmount/docs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_vscript.m4 \
+ $(top_srcdir)/m4/compiler.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/tls.m4 \
+ $(top_srcdir)/m4/ul.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = version.xml
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/version.xml.in \
+ $(top_srcdir)/config/gtk-doc.make
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ADJTIME_PATH = @ADJTIME_PATH@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+ASCIIDOCTOR = @ASCIIDOCTOR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BSD_WARN_CFLAGS = @BSD_WARN_CFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTSETUP_CFLAGS = @CRYPTSETUP_CFLAGS@
+CRYPTSETUP_LIBS = @CRYPTSETUP_LIBS@
+CRYPTSETUP_LIBS_STATIC = @CRYPTSETUP_LIBS_STATIC@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DAEMON_CFLAGS = @DAEMON_CFLAGS@
+DAEMON_LDFLAGS = @DAEMON_LDFLAGS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+ECONF_CFLAGS = @ECONF_CFLAGS@
+ECONF_LIBS = @ECONF_LIBS@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZING_ENGINE_LDFLAGS = @FUZZING_ENGINE_LDFLAGS@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBBLKID_DATE = @LIBBLKID_DATE@
+LIBBLKID_VERSION = @LIBBLKID_VERSION@
+LIBBLKID_VERSION_INFO = @LIBBLKID_VERSION_INFO@
+LIBFDISK_MAJOR_VERSION = @LIBFDISK_MAJOR_VERSION@
+LIBFDISK_MINOR_VERSION = @LIBFDISK_MINOR_VERSION@
+LIBFDISK_PATCH_VERSION = @LIBFDISK_PATCH_VERSION@
+LIBFDISK_PC_REQUIRES = @LIBFDISK_PC_REQUIRES@
+LIBFDISK_VERSION = @LIBFDISK_VERSION@
+LIBFDISK_VERSION_INFO = @LIBFDISK_VERSION_INFO@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMOUNT_MAJOR_VERSION = @LIBMOUNT_MAJOR_VERSION@
+LIBMOUNT_MINOR_VERSION = @LIBMOUNT_MINOR_VERSION@
+LIBMOUNT_PATCH_VERSION = @LIBMOUNT_PATCH_VERSION@
+LIBMOUNT_VERSION = @LIBMOUNT_VERSION@
+LIBMOUNT_VERSION_INFO = @LIBMOUNT_VERSION_INFO@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSMARTCOLS_VERSION = @LIBSMARTCOLS_VERSION@
+LIBSMARTCOLS_VERSION_INFO = @LIBSMARTCOLS_VERSION_INFO@
+LIBTOOL = @LIBTOOL@
+LIBUSER_CFLAGS = @LIBUSER_CFLAGS@
+LIBUSER_LIBS = @LIBUSER_LIBS@
+LIBUUID_VERSION = @LIBUUID_VERSION@
+LIBUUID_VERSION_INFO = @LIBUUID_VERSION_INFO@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAGIC_LIBS = @MAGIC_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MATH_LIBS = @MATH_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NCURSES5_CONFIG = @NCURSES5_CONFIG@
+NCURSES6_CONFIG = @NCURSES6_CONFIG@
+NCURSESW5_CONFIG = @NCURSESW5_CONFIG@
+NCURSESW6_CONFIG = @NCURSESW6_CONFIG@
+NCURSESW_CFLAGS = @NCURSESW_CFLAGS@
+NCURSESW_LIBS = @NCURSESW_LIBS@
+NCURSES_CFLAGS = @NCURSES_CFLAGS@
+NCURSES_LIBS = @NCURSES_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NO_UNUSED_WARN_CFLAGS = @NO_UNUSED_WARN_CFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+PYTHON_WARN_CFLAGS = @PYTHON_WARN_CFLAGS@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+READLINE_LIBS_STATIC = @READLINE_LIBS_STATIC@
+REALTIME_LIBS = @REALTIME_LIBS@
+RTAS_LIBS = @RTAS_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SELINUX_LIBS_STATIC = @SELINUX_LIBS_STATIC@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SOLIB_CFLAGS = @SOLIB_CFLAGS@
+SOLIB_LDFLAGS = @SOLIB_LDFLAGS@
+STRIP = @STRIP@
+SUID_CFLAGS = @SUID_CFLAGS@
+SUID_LDFLAGS = @SUID_LDFLAGS@
+SYSCONFSTATICDIR = @SYSCONFSTATICDIR@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@
+SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+TINFOW_CFLAGS = @TINFOW_CFLAGS@
+TINFOW_LIBS = @TINFOW_LIBS@
+TINFO_CFLAGS = @TINFO_CFLAGS@
+TINFO_LIBS = @TINFO_LIBS@
+TINFO_LIBS_STATIC = @TINFO_LIBS_STATIC@
+UBSAN_LDFLAGS = @UBSAN_LDFLAGS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+VSCRIPT_LDFLAGS = @VSCRIPT_LDFLAGS@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XSLTPROC = @XSLTPROC@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bashcompletiondir = @bashcompletiondir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+sysconfstaticdir = @sysconfstaticdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+usrbin_execdir = @usrbin_execdir@
+usrlib_execdir = @usrlib_execdir@
+usrsbin_execdir = @usrsbin_execdir@
+vendordir = @vendordir@
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE = libmount
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR = ../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS =
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space mnt
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS =
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS =
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB = $(top_builddir)/libmount/src/libmount.h
+CFILE_GLOB = $(top_srcdir)/libmount/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES =
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES = mountP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES =
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files =
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS =
+GTKDOC_LIBS =
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_CC = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_LD = $(LIBTOOL) --tag=CC --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_RUN =
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_RUN = $(LIBTOOL) --mode=execute
+
+# We set GPATH here; this gives us semantics for GNU make
+# which are more like other make's VPATH, when it comes to
+# whether a source that is a target of one rule is then
+# searched for in VPATH/GPATH.
+#
+GPATH = $(srcdir)
+TARGET_DIR = $(docdir)/$(DOC_MODULE)
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES = version.xml
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST = $(content_files) $(HTML_IMAGES) $(DOC_MAIN_SGML_FILE) \
+ $(DOC_MODULE)-sections.txt version.xml.in
+# $(DOC_MODULE)-overrides.txt
+DOC_STAMPS = scan-build.stamp sgml-build.stamp html-build.stamp \
+ $(srcdir)/setup.stamp $(srcdir)/sgml.stamp \
+ $(srcdir)/html.stamp
+
+SCANOBJ_FILES = \
+ $(DOC_MODULE).args \
+ $(DOC_MODULE).hierarchy \
+ $(DOC_MODULE).interfaces \
+ $(DOC_MODULE).prerequisites \
+ $(DOC_MODULE).signals \
+ $(DOC_MODULE).types # util-linux: we don't use types
+
+REPORT_FILES = \
+ $(DOC_MODULE)-undocumented.txt \
+ $(DOC_MODULE)-undeclared.txt \
+ $(DOC_MODULE)-unused.txt
+
+CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/config/gtk-doc.make $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libmount/docs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign libmount/docs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/config/gtk-doc.make $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+version.xml: $(top_builddir)/config.status $(srcdir)/version.xml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile all-local
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-local
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am all-local check check-am clean clean-generic \
+ clean-libtool clean-local cscopelist-am ctags-am distclean \
+ distclean-generic distclean-libtool distclean-local distdir \
+ dvi dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-data-local install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am uninstall-local
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_GTK_DOC_TRUE@all-local: html-build.stamp
+@ENABLE_GTK_DOC_FALSE@all-local:
+
+docs: html-build.stamp
+
+$(REPORT_FILES): sgml-build.stamp
+
+#### setup ####
+
+setup-build.stamp:
+ -@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
+ echo 'gtk-doc: Preparing build'; \
+ files=`echo $(EXTRA_DIST) $(expand_content_files) $(srcdir)/$(DOC_MODULE).types`; \
+ if test "x$$files" != "x" ; then \
+ for file in $$files ; do \
+ test -f $(abs_srcdir)/$$file && \
+ cp -p $(abs_srcdir)/$$file $(abs_builddir)/; \
+ done \
+ fi \
+ fi
+ @touch setup-build.stamp
+
+setup.stamp: setup-build.stamp
+ @true
+
+#### scan ####
+
+scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB) $(srcdir)/$(DOC_MODULE)-*.txt $(content_files)
+
+ @test -f $(DOC_MODULE)-sections.txt || \
+ cp $(srcdir)/$(DOC_MODULE)-sections.txt $(builddir)
+
+ $(AM_V_GEN)gtkdoc-scan --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --ignore-decorators="__ul_attribute__\(.*\)" \
+ --ignore-headers="$(IGNORE_HFILES)" \
+ --output-dir=$(builddir) \
+ $(SCAN_OPTIONS) $(EXTRA_HFILES)
+
+ @ if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \
+ CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" RUN="$(GTKDOC_RUN)" \
+ CFLAGS="$(GTKDOC_CFLAGS) $(CFLAGS)" LDFLAGS="$(GTKDOC_LIBS) \
+ $(LDFLAGS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) \
+ --module=$(DOC_MODULE) --output-dir=$(builddir) ; \
+ else \
+ for i in $(SCANOBJ_FILES) ; do \
+ test -f $$i || touch $$i ; \
+ done \
+ fi
+ @ touch scan-build.stamp
+
+$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp
+ @true
+
+#### templates ####
+#
+#tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(srcdir)/$(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt
+# @echo 'gtk-doc: Rebuilding template files'
+# test -z $(builddir)/tmpl || $(MKDIR_P) $(builddir)/tmpl
+# gtkdoc-mktmpl --module=$(DOC_MODULE) \
+# $(MKTMPL_OPTIONS)
+# touch tmpl-build.stamp
+#
+#tmpl.stamp: tmpl-build.stamp
+# @true
+#
+#tmpl/*.sgml:
+# @true
+#
+
+#### xml ####
+
+sgml-build.stamp: setup.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-decl.txt $(DOC_MODULE)-sections.txt $(expand_content_files)
+ $(AM_V_GEN)gtkdoc-mkdb --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --output-format=xml \
+ --ignore-files="$(IGNORE_HFILES)" \
+ --expand-content-files="$(expand_content_files)" \
+ --main-sgml-file=$(srcdir)/$(DOC_MAIN_SGML_FILE) \
+ $(MKDB_OPTIONS)
+ @touch sgml-build.stamp
+
+sgml.stamp: sgml-build.stamp
+ @true
+
+#### html ####
+
+html-build.stamp: sgml.stamp $(srcdir)/$(DOC_MAIN_SGML_FILE) $(content_files)
+ @rm -rf $(builddir)/html
+ @$(MKDIR_P) $(builddir)/html
+ $(AM_V_GEN)cd $(builddir)/html && \
+ gtkdoc-mkhtml --path="$(abs_builddir):$(abs_builddir)/xml:$(abs_srcdir)" \
+ $(MKHTML_OPTIONS) \
+ $(DOC_MODULE) \
+ $(abs_srcdir)/$(DOC_MAIN_SGML_FILE)
+
+ @test "x$(HTML_IMAGES)" = "x" || \
+ ( cd $(srcdir) && cp $(HTML_IMAGES) $(abs_builddir)/html )
+
+ $(AM_V_GEN)gtkdoc-fixxref --module-dir=html \
+ --module=$(DOC_MODULE) \
+ --html-dir=$(HTML_DIR) \
+ $(FIXXREF_OPTIONS)
+ @touch html-build.stamp
+
+##############
+
+clean-local:
+ rm -f *~ *.bak
+ rm -rf .libs
+
+distclean-local:
+ rm -rf xml html $(REPORT_FILES) *.stamp \
+ $(DOC_MODULE)-overrides.txt \
+ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
+ test $(abs_builddir) == $(abs_srcdir) || \
+ rm -f $(DOC_MODULE)-*.txt $(DOC_MODULE)-*.xml *.xml.in
+
+install-data-local:
+ installfiles=`echo $(builddir)/html/*`; \
+ if test "$$installfiles" = '$(builddir)/html/*'; \
+ then echo '-- Nothing to install' ; \
+ else \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ $(mkinstalldirs) $${installdir} ; \
+ for i in $$installfiles; do \
+ echo '-- Installing '$$i ; \
+ $(INSTALL_DATA) $$i $${installdir}; \
+ done; \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp2 \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp2; \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp; \
+ fi; \
+ ! which gtkdoc-rebase >/dev/null 2>&1 || \
+ gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$${installdir} ; \
+ fi
+
+uninstall-local:
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ rm -rf $${installdir}
+
+#
+# Require gtk-doc when making dist
+#
+@ENABLE_GTK_DOC_TRUE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@ @echo "*** gtk-doc must be installed and enabled in order to make dist"
+@ENABLE_GTK_DOC_FALSE@ @false
+
+#dist-hook: dist-check-gtkdoc dist-hook-local sgml.stamp html-build.stamp
+# mkdir $(distdir)/tmpl
+# mkdir $(distdir)/xml
+# mkdir $(distdir)/html
+# -cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl
+# -cp $(srcdir)/xml/*.xml $(distdir)/xml
+# cp $(srcdir)/html/* $(distdir)/html
+# -cp $(srcdir)/$(DOC_MODULE).types $(distdir)/
+# -cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/
+# cd $(distdir) && rm -f $(DISTCLEANFILES)
+# ! which gtkdoc-rebase >/dev/null 2>&1 || \
+# gtkdoc-rebase --online --relative --html-dir=$(distdir)/html
+#
+#.PHONY : dist-hook-local docs
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/libmount/docs/libmount-docs.xml b/libmount/docs/libmount-docs.xml
new file mode 100644
index 0000000..ad816e7
--- /dev/null
+++ b/libmount/docs/libmount-docs.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>libmount Reference Manual</title>
+ <releaseinfo>for libmount version &version;</releaseinfo>
+ <copyright>
+ <year>2010-2022</year>
+ <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+ </copyright>
+ </bookinfo>
+
+ <part id="gtk">
+ <title>libmount Overview</title>
+ <partintro>
+ <para>
+The libmount library is used to parse /etc/fstab, /etc/mtab and
+/proc/self/mountinfo files, manage the mtab file, evaluate mount options, etc.
+ </para>
+ <para>
+The library is part of the util-linux package since version 2.18 and is
+available from https://www.kernel.org/pub/linux/utils/util-linux/.
+ </para>
+ </partintro>
+ </part>
+
+ <part>
+ <title>High-level API</title>
+ <xi:include href="xml/context.xml"/>
+ <xi:include href="xml/context-mount.xml"/>
+ <xi:include href="xml/context-umount.xml"/>
+ </part>
+ <part>
+ <title>Files parsing</title>
+ <xi:include href="xml/table.xml"/>
+ <xi:include href="xml/fs.xml"/>
+ </part>
+ <part>
+ <title>Tables management</title>
+ <xi:include href="xml/lock.xml"/>
+ <xi:include href="xml/update.xml"/>
+ <xi:include href="xml/monitor.xml"/>
+ <xi:include href="xml/tabdiff.xml"/>
+ </part>
+ <part>
+ <title>Mount options</title>
+ <xi:include href="xml/optstr.xml"/>
+ <xi:include href="xml/optmap.xml"/>
+ </part>
+ <part>
+ <title>Misc</title>
+ <xi:include href="xml/init.xml"/>
+ <xi:include href="xml/cache.xml"/>
+ <xi:include href="xml/iter.xml"/>
+ <xi:include href="xml/utils.xml"/>
+ <xi:include href="xml/version-utils.xml"/>
+ </part>
+ <index id="api-index">
+ <title>API Index</title>
+ <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.28">
+ <title>Index of new symbols in 2.28</title>
+ <xi:include href="xml/api-index-2.28.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.30">
+ <title>Index of new symbols in 2.30</title>
+ <xi:include href="xml/api-index-2.30.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.33">
+ <title>Index of new symbols in 2.33</title>
+ <xi:include href="xml/api-index-2.33.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.34">
+ <title>Index of new symbols in 2.34</title>
+ <xi:include href="xml/api-index-2.34.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.35">
+ <title>Index of new symbols in 2.35</title>
+ <xi:include href="xml/api-index-2.35.xml"><xi:fallback /></xi:include>
+ </index>
+</book>
diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt
new file mode 100644
index 0000000..097e2f0
--- /dev/null
+++ b/libmount/docs/libmount-sections.txt
@@ -0,0 +1,452 @@
+<SECTION>
+<FILE>cache</FILE>
+libmnt_cache
+mnt_new_cache
+mnt_free_cache
+mnt_ref_cache
+mnt_unref_cache
+mnt_cache_device_has_tag
+mnt_cache_find_tag_value
+mnt_cache_read_tags
+mnt_cache_set_targets
+mnt_get_fstype
+mnt_pretty_path
+mnt_resolve_path
+mnt_resolve_spec
+mnt_resolve_tag
+mnt_resolve_target
+</SECTION>
+
+<SECTION>
+<FILE>context</FILE>
+libmnt_context
+libmnt_ns
+mnt_free_context
+mnt_new_context
+mnt_reset_context
+mnt_context_append_options
+mnt_context_apply_fstab
+mnt_context_disable_canonicalize
+mnt_context_disable_helpers
+mnt_context_disable_mtab
+mnt_context_disable_swapmatch
+mnt_context_enable_fake
+mnt_context_enable_force
+mnt_context_enable_fork
+mnt_context_enable_lazy
+mnt_context_enable_loopdel
+mnt_context_enable_rdonly_umount
+mnt_context_enable_rwonly_mount
+mnt_context_enable_sloppy
+mnt_context_enable_verbose
+mnt_context_forced_rdonly
+mnt_context_force_unrestricted
+mnt_context_get_cache
+mnt_context_get_excode
+mnt_context_get_fs
+mnt_context_get_fstab
+mnt_context_get_fstab_userdata
+mnt_context_get_fstype
+mnt_context_get_fs_userdata
+mnt_context_get_helper_status
+mnt_context_get_lock
+mnt_context_get_mflags
+mnt_context_get_mtab
+mnt_context_get_mtab_userdata
+mnt_context_get_options
+mnt_context_get_optsmode
+mnt_context_get_origin_ns
+mnt_context_get_source
+mnt_context_get_status
+mnt_context_get_syscall_errno
+mnt_context_get_table
+mnt_context_get_target
+mnt_context_get_target_ns
+mnt_context_get_target_prefix
+mnt_context_get_user_mflags
+mnt_context_helper_executed
+mnt_context_helper_setopt
+mnt_context_init_helper
+mnt_context_is_child
+mnt_context_is_fake
+mnt_context_is_force
+mnt_context_is_fork
+mnt_context_is_fs_mounted
+mnt_context_is_lazy
+mnt_context_is_loopdel
+mnt_context_is_nocanonicalize
+mnt_context_is_nohelpers
+mnt_context_is_nomtab
+mnt_context_is_parent
+mnt_context_is_rdonly_umount
+mnt_context_is_restricted
+mnt_context_is_rwonly_mount
+mnt_context_is_sloppy
+mnt_context_is_swapmatch
+mnt_context_is_verbose
+mnt_context_reset_status
+mnt_context_set_cache
+mnt_context_set_fs
+mnt_context_set_fstab
+mnt_context_set_fstype
+mnt_context_set_fstype_pattern
+mnt_context_set_mflags
+mnt_context_set_mountdata
+mnt_context_set_options
+mnt_context_set_options_pattern
+mnt_context_set_optsmode
+mnt_context_set_passwd_cb
+mnt_context_set_source
+mnt_context_set_syscall_status
+mnt_context_set_tables_errcb
+mnt_context_set_target
+mnt_context_set_target_ns
+mnt_context_set_target_prefix
+mnt_context_set_user_mflags
+mnt_context_strerror
+mnt_context_switch_ns
+mnt_context_switch_origin_ns
+mnt_context_switch_target_ns
+mnt_context_syscall_called
+mnt_context_tab_applied
+mnt_context_wait_for_children
+<SUBSECTION>
+MNT_ERR_AMBIFS
+MNT_ERR_APPLYFLAGS
+MNT_ERR_LOOPDEV
+MNT_ERR_MOUNTOPT
+MNT_ERR_NOFSTAB
+MNT_ERR_NOFSTYPE
+MNT_ERR_NOSOURCE
+MNT_ERR_LOOPOVERLAP
+MNT_ERR_LOCK
+MNT_ERR_NAMESPACE
+<SUBSECTION>
+MNT_EX_SUCCESS
+MNT_EX_USAGE
+MNT_EX_SYSERR
+MNT_EX_SOFTWARE
+MNT_EX_USER
+MNT_EX_FILEIO
+MNT_EX_FAIL
+MNT_EX_SOMEOK
+</SECTION>
+
+<SECTION>
+<FILE>context-mount</FILE>
+mnt_context_do_mount
+mnt_context_finalize_mount
+mnt_context_mount
+mnt_context_next_mount
+mnt_context_next_remount
+mnt_context_prepare_mount
+<SUBSECTION>
+MNT_MS_COMMENT
+MNT_MS_GROUP
+MNT_MS_HELPER
+MNT_MS_LOOP
+MNT_MS_NETDEV
+MNT_MS_NOAUTO
+MNT_MS_NOFAIL
+MNT_MS_OFFSET
+MNT_MS_OWNER
+MNT_MS_SIZELIMIT
+MNT_MS_ENCRYPTION
+MNT_MS_UHELPER
+MNT_MS_USER
+MNT_MS_USERS
+MNT_MS_XCOMMENT
+MNT_MS_XFSTABCOMM
+MNT_MS_HASH_DEVICE
+MNT_MS_ROOT_HASH
+MNT_MS_HASH_OFFSET
+MNT_MS_ROOT_HASH_FILE
+MNT_MS_FEC_DEVICE
+MNT_MS_FEC_OFFSET
+MNT_MS_FEC_ROOTS
+MNT_MS_ROOT_HASH_SIG
+<SUBSECTION>
+MS_BIND
+MS_DIRSYNC
+MS_I_VERSION
+MS_MANDLOCK
+MS_MGC_MSK
+MS_MGC_VAL
+MS_MOVE
+MS_NOATIME
+MS_NODEV
+MS_NODIRATIME
+MS_NOEXEC
+MS_NOSUID
+MS_OWNERSECURE
+MS_PRIVATE
+MS_PROPAGATION
+MS_RDONLY
+MS_REC
+MS_RELATIME
+MS_REMOUNT
+MS_SECURE
+MS_SHARED
+MS_SILENT
+MS_SLAVE
+MS_STRICTATIME
+MS_SYNCHRONOUS
+MS_UNBINDABLE
+MS_LAZYTIME
+</SECTION>
+
+<SECTION>
+<FILE>context-umount</FILE>
+mnt_context_find_umount_fs
+mnt_context_do_umount
+mnt_context_finalize_umount
+mnt_context_next_umount
+mnt_context_prepare_umount
+mnt_context_umount
+</SECTION>
+
+<SECTION>
+<FILE>fs</FILE>
+libmnt_fs
+mnt_copy_fs
+mnt_free_fs
+mnt_free_mntent
+mnt_ref_fs
+mnt_unref_fs
+mnt_fs_append_attributes
+mnt_fs_append_comment
+mnt_fs_append_options
+mnt_fs_get_attribute
+mnt_fs_get_attributes
+mnt_fs_get_bindsrc
+mnt_fs_get_comment
+mnt_fs_get_devno
+mnt_fs_get_freq
+mnt_fs_get_fs_options
+mnt_fs_get_fstype
+mnt_fs_get_id
+mnt_fs_get_option
+mnt_fs_get_optional_fields
+mnt_fs_get_options
+mnt_fs_get_parent_id
+mnt_fs_get_passno
+mnt_fs_get_priority
+mnt_fs_get_propagation
+mnt_fs_get_root
+mnt_fs_get_size
+mnt_fs_get_source
+mnt_fs_get_srcpath
+mnt_fs_get_swaptype
+mnt_fs_get_tag
+mnt_fs_get_table
+mnt_fs_get_target
+mnt_fs_get_tid
+mnt_fs_get_usedsize
+mnt_fs_get_userdata
+mnt_fs_get_user_options
+mnt_fs_get_vfs_options
+mnt_fs_get_vfs_options_all
+mnt_fs_is_kernel
+mnt_fs_is_netfs
+mnt_fs_is_pseudofs
+mnt_fs_is_regularfs
+mnt_fs_is_swaparea
+mnt_fs_match_fstype
+mnt_fs_match_options
+mnt_fs_match_source
+mnt_fs_match_target
+mnt_fs_prepend_attributes
+mnt_fs_prepend_options
+mnt_fs_print_debug
+mnt_fs_set_attributes
+mnt_fs_set_bindsrc
+mnt_fs_set_comment
+mnt_fs_set_freq
+mnt_fs_set_fstype
+mnt_fs_set_options
+mnt_fs_set_passno
+mnt_fs_set_priority
+mnt_fs_set_root
+mnt_fs_set_source
+mnt_fs_set_target
+mnt_fs_set_userdata
+mnt_fs_strdup_options
+mnt_fs_streq_srcpath
+mnt_fs_streq_target
+mnt_fs_to_mntent
+mnt_new_fs
+mnt_reset_fs
+</SECTION>
+
+<SECTION>
+<FILE>init</FILE>
+mnt_init_debug
+</SECTION>
+
+<SECTION>
+<FILE>iter</FILE>
+libmnt_iter
+mnt_free_iter
+mnt_iter_get_direction
+mnt_new_iter
+mnt_reset_iter
+</SECTION>
+
+<SECTION>
+<FILE>lock</FILE>
+libmnt_lock
+mnt_free_lock
+mnt_lock_file
+mnt_new_lock
+mnt_unlock_file
+mnt_lock_block_signals
+</SECTION>
+
+<SECTION>
+<FILE>optmap</FILE>
+libmnt_optmap
+mnt_get_builtin_optmap
+MNT_INVERT
+MNT_NOMTAB
+MNT_PREFIX
+MNT_NOHLPS
+</SECTION>
+
+<SECTION>
+<FILE>optstr</FILE>
+mnt_optstr_append_option
+mnt_optstr_apply_flags
+mnt_optstr_deduplicate_option
+mnt_optstr_get_flags
+mnt_optstr_get_option
+mnt_optstr_get_options
+mnt_optstr_next_option
+mnt_optstr_prepend_option
+mnt_optstr_remove_option
+mnt_optstr_set_option
+mnt_split_optstr
+mnt_match_options
+</SECTION>
+
+<SECTION>
+<FILE>table</FILE>
+libmnt_table
+mnt_free_table
+mnt_new_table
+mnt_reset_table
+mnt_ref_table
+mnt_unref_table
+mnt_new_table_from_dir
+mnt_new_table_from_file
+mnt_table_add_fs
+mnt_table_append_intro_comment
+mnt_table_append_trailing_comment
+mnt_table_enable_comments
+mnt_table_find_devno
+mnt_table_find_fs
+mnt_table_find_mountpoint
+mnt_table_find_next_fs
+mnt_table_find_pair
+mnt_table_find_source
+mnt_table_find_srcpath
+mnt_table_find_tag
+mnt_table_find_target
+mnt_table_find_target_with_option
+mnt_table_first_fs
+mnt_table_get_cache
+mnt_table_get_intro_comment
+mnt_table_get_nents
+mnt_table_get_root_fs
+mnt_table_get_trailing_comment
+mnt_table_get_userdata
+mnt_table_insert_fs
+mnt_table_is_empty
+mnt_table_is_fs_mounted
+mnt_table_last_fs
+mnt_table_move_fs
+mnt_table_next_child_fs
+mnt_table_next_fs
+mnt_table_over_fs
+mnt_table_parse_dir
+mnt_table_parse_file
+mnt_table_parse_fstab
+mnt_table_parse_mtab
+mnt_table_parse_stream
+mnt_table_parse_swaps
+mnt_table_remove_fs
+mnt_table_set_cache
+mnt_table_set_intro_comment
+mnt_table_set_iter
+mnt_table_set_parser_errcb
+mnt_table_set_trailing_comment
+mnt_table_set_userdata
+mnt_table_uniq_fs
+mnt_table_with_comments
+</SECTION>
+
+<SECTION>
+<FILE>tabdiff</FILE>
+libmnt_tabdiff
+mnt_new_tabdiff
+mnt_free_tabdiff
+mnt_tabdiff_next_change
+mnt_diff_tables
+</SECTION>
+
+<SECTION>
+<FILE>update</FILE>
+libmnt_update
+mnt_free_update
+mnt_new_update
+mnt_table_replace_file
+mnt_table_write_file
+mnt_update_force_rdonly
+mnt_update_get_filename
+mnt_update_get_fs
+mnt_update_get_mflags
+mnt_update_is_ready
+mnt_update_set_fs
+mnt_update_table
+</SECTION>
+
+<SECTION>
+<FILE>utils</FILE>
+mnt_fstype_is_netfs
+mnt_fstype_is_pseudofs
+mnt_get_fstab_path
+mnt_get_mountpoint
+mnt_get_mtab_path
+mnt_get_swaps_path
+mnt_guess_system_root
+mnt_has_regular_mtab
+mnt_mangle
+mnt_match_fstype
+mnt_tag_is_valid
+mnt_unmangle
+</SECTION>
+
+<SECTION>
+<FILE>version-utils</FILE>
+LIBMOUNT_MAJOR_VERSION
+LIBMOUNT_MINOR_VERSION
+LIBMOUNT_PATCH_VERSION
+mnt_parse_version_string
+mnt_get_library_version
+mnt_get_library_features
+LIBMOUNT_VERSION
+</SECTION>
+
+<SECTION>
+<FILE>monitor</FILE>
+libmnt_monitor
+mnt_new_monitor
+mnt_ref_monitor
+mnt_unref_monitor
+mnt_monitor_enable_userspace
+mnt_monitor_enable_kernel
+mnt_monitor_get_fd
+mnt_monitor_close_fd
+mnt_monitor_next_change
+mnt_monitor_event_cleanup
+mnt_monitor_wait
+</SECTION>
diff --git a/libmount/docs/version.xml b/libmount/docs/version.xml
new file mode 100644
index 0000000..d8c7561
--- /dev/null
+++ b/libmount/docs/version.xml
@@ -0,0 +1 @@
+2.38.1
diff --git a/libmount/docs/version.xml.in b/libmount/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libmount/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libmount/meson.build b/libmount/meson.build
new file mode 100644
index 0000000..de4328f
--- /dev/null
+++ b/libmount/meson.build
@@ -0,0 +1,98 @@
+dir_libmount = include_directories('.', 'src')
+
+defs = configuration_data()
+defs.set('LIBMOUNT_VERSION', pc_version)
+defs.set('LIBMOUNT_MAJOR_VERSION', pc_version.split('.')[0])
+defs.set('LIBMOUNT_MINOR_VERSION', pc_version.split('.')[1])
+defs.set('LIBMOUNT_PATCH_VERSION', pc_version.split('.')[2])
+
+libmount_h = configure_file(
+ input : 'src/libmount.h.in',
+ output : 'libmount.h',
+ configuration : defs,
+ install : build_libmount,
+ install_dir : join_paths(get_option('includedir'), 'libmount'),
+)
+
+lib_mount_sources = '''
+ src/mountP.h
+ src/cache.c
+ src/fs.c
+ src/init.c
+ src/iter.c
+ src/lock.c
+ src/optmap.c
+ src/optstr.c
+ src/tab.c
+ src/tab_diff.c
+ src/tab_parse.c
+ src/tab_update.c
+ src/test.c
+ src/utils.c
+ src/version.c
+'''.split() + [
+ list_h,
+ monotonic_c,
+]
+
+if LINUX
+ lib_mount_sources += '''
+ src/context.c
+ src/context_loopdev.c
+ src/context_veritydev.c
+ src/context_mount.c
+ src/context_umount.c
+ src/monitor.c
+'''.split()
+endif
+
+if enable_btrfs
+ lib_mount_sources += 'src/btrfs.c'
+endif
+
+libmount_sym = 'src/libmount.sym'
+libmount_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libmount_sym)
+
+if build_libmount and not have_dirfd and not have_ddfd
+ error('neither dirfd nor ddfd are available')
+endif
+
+lib__mount = static_library(
+ '_mount',
+ lib_mount_sources,
+ include_directories : [dir_include,
+ dir_libmount,
+ dir_libblkid])
+
+lib_mount_static = static_library(
+ 'mount',
+ link_whole : lib__mount,
+ link_with : [lib_common,
+ lib_blkid.get_static_lib()],
+ dependencies : [realtime_libs],
+ install : false)
+
+lib_mount = library(
+ 'mount',
+ link_whole : lib__mount,
+ include_directories : [dir_include,
+ dir_libmount,
+ dir_libblkid],
+ link_depends : libmount_sym,
+ version : libmount_version,
+ link_args : ['-Wl,--version-script=@0@'.format(libmount_sym_path)],
+ link_with : [lib_common,
+ lib_blkid],
+ dependencies : [lib_selinux,
+ get_option('cryptsetup-dlopen').enabled() ? lib_dl : lib_cryptsetup,
+ realtime_libs],
+ install : build_libmount)
+
+if build_libmount
+ pkgconfig.generate(lib_mount,
+ description : 'mount library',
+ subdirs : 'libmount',
+ version : pc_version)
+endif
+
+subdir('python')
diff --git a/libmount/mount.pc.in b/libmount/mount.pc.in
new file mode 100644
index 0000000..37ff59b
--- /dev/null
+++ b/libmount/mount.pc.in
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of libmount from util-linux project.
+#
+# Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+#
+# libmount 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.
+#
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: mount
+Description: mount library
+Version: @LIBMOUNT_VERSION@
+Requires.private: blkid @LIBSELINUX@ @LIBCRYPTSETUP@
+Cflags: -I${includedir}/libmount
+Libs: -L${libdir} -lmount
+Libs.private: @LIBDL@
diff --git a/libmount/python/Makemodule.am b/libmount/python/Makemodule.am
new file mode 100644
index 0000000..b338ea0
--- /dev/null
+++ b/libmount/python/Makemodule.am
@@ -0,0 +1,35 @@
+if BUILD_PYLIBMOUNT
+
+pylibmountexecdir = $(pyexecdir)/libmount
+
+# Please, don't use $pythondir for the scripts. We have to use the same
+# directory for binary stuff as well as for the scripts otherwise it's
+# not possible to install 32-bit and 64-bit version on the same system.
+pylibmountexec_LTLIBRARIES = pylibmount.la
+pylibmountexec_PYTHON = libmount/python/__init__.py
+
+pylibmount_la_SOURCES = \
+ libmount/python/pylibmount.c \
+ libmount/python/pylibmount.h \
+ libmount/python/fs.c \
+ libmount/python/tab.c
+if LINUX
+pylibmount_la_SOURCES += libmount/python/context.c
+endif
+
+pylibmount_la_LIBADD = libmount.la $(PYTHON_LIBS)
+
+pylibmount_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(PYTHON_CFLAGS) $(PYTHON_WARN_CFLAGS) \
+ -I$(ul_libmount_incdir) \
+ -fno-strict-aliasing #-ggdb3 -O0
+
+pylibmount_la_LDFLAGS = \
+ -avoid-version -module -shared -export-dynamic
+
+dist_check_SCRIPTS += libmount/python/test_mount_context.py
+dist_check_SCRIPTS += libmount/python/test_mount_tab.py
+dist_check_SCRIPTS += libmount/python/test_mount_tab_update.py
+
+endif # BUILD_PYLIBMOUNT
diff --git a/libmount/python/__init__.py b/libmount/python/__init__.py
new file mode 100644
index 0000000..09104e2
--- /dev/null
+++ b/libmount/python/__init__.py
@@ -0,0 +1,2 @@
+from .pylibmount import *
+
diff --git a/libmount/python/context.c b/libmount/python/context.c
new file mode 100644
index 0000000..353a893
--- /dev/null
+++ b/libmount/python/context.c
@@ -0,0 +1,1228 @@
+/*
+ * Python bindings for the libmount library.
+ *
+ * Copyright (C) 2013, Red Hat, Inc. All rights reserved.
+ * Written by Ondrej Oprala and Karel Zak
+ *
+ * This file 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 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "pylibmount.h"
+
+static PyMemberDef Context_members[] = {
+ { NULL }
+};
+
+static PyObject *Context_set_tables_errcb(ContextObjext *self, PyObject *func,
+ void *closure __attribute__((unused)))
+{
+ if (!func) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return NULL;
+ }
+
+ if (!PyCallable_Check(func))
+ return NULL;
+
+ PyObject *tmp = self->table_errcb;
+ Py_INCREF(func);
+ self->table_errcb = func;
+ Py_XDECREF(tmp);
+
+ return UL_IncRef(self);
+}
+
+static void Context_dealloc(ContextObjext *self)
+{
+ if (!self->cxt) /* if init fails */
+ return;
+
+ Py_XDECREF(mnt_context_get_fs_userdata(self->cxt));
+ Py_XDECREF(mnt_context_get_fstab_userdata(self->cxt));
+ Py_XDECREF(mnt_context_get_mtab_userdata(self->cxt));
+
+ mnt_free_context(self->cxt);
+ PyFree(self);
+}
+
+static PyObject *Context_new(PyTypeObject *type,
+ PyObject *args __attribute__((unused)),
+ PyObject *kwds __attribute__((unused)))
+{
+ ContextObjext *self = (ContextObjext*) type->tp_alloc(type, 0);
+
+ if (self) {
+ self->cxt = NULL;
+ self->table_errcb = NULL;
+ }
+
+ return (PyObject *)self;
+}
+
+/*
+ * Note there is no pointer to encapsulating object needed here, since Cxt is
+ * on top of the Context(Table(Filesystem)) hierarchy
+ */
+#define Context_HELP "Context(source=None, target=None, fstype=None, " \
+ "options=None, mflags=0, fstype_pattern=None, " \
+ "options_pattern=None, fs=None, fstab=None, optsmode=0)"
+static int Context_init(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ char *source = NULL, *target = NULL, *fstype = NULL;
+ char *options = NULL, *fstype_pattern = NULL, *options_pattern = NULL;
+ unsigned long mflags = 0;
+ int optsmode = 0, syscall_status = 1;
+ FsObject *fs = NULL;
+ TableObject *fstab = NULL;
+ int rc = 0;
+ char *kwlist[] = {
+ "source", "target", "fstype",
+ "options", "mflags", "fstype_pattern",
+ "options_pattern", "fs", "fstab",
+ "optsmode", NULL
+ };
+
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwds, "|sssskssO!O!i", kwlist,
+ &source, &target, &fstype, &options, &mflags,
+ &fstype_pattern, &options_pattern, &FsType, &fs,
+ &TableType, &fstab, &optsmode, &syscall_status)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+
+ if (self->cxt)
+ mnt_free_context(self->cxt);
+
+ self->cxt = mnt_new_context();
+ if (!self->cxt) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return -1;
+ }
+
+ if (source && (rc = mnt_context_set_source(self->cxt, source))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (target && (rc = mnt_context_set_target(self->cxt, target))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (fstype && (rc = mnt_context_set_fstype(self->cxt, fstype))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (options && (rc = mnt_context_set_options(self->cxt, options))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (fstype_pattern && (rc = mnt_context_set_fstype_pattern(self->cxt, fstype_pattern))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (options_pattern && (rc = mnt_context_set_options_pattern(self->cxt, options_pattern))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (fs && (rc = mnt_context_set_fs(self->cxt, fs->fs))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (fstab && (rc = mnt_context_set_fstab(self->cxt, fstab->tab))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ if (optsmode && (rc = mnt_context_set_optsmode(self->cxt, optsmode))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+
+ mnt_context_set_mflags(self->cxt, mflags);
+ mnt_context_set_optsmode(self->cxt, optsmode);
+ mnt_context_set_tables_errcb(self->cxt, pymnt_table_parser_errcb);
+
+ return 0;
+}
+
+#define Context_enable_fake_HELP "enable_fake(enable)\n\n" \
+ "Enable/disable fake mounting (see mount(8) man page, option -f).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_fake(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_fake(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_force_HELP "enable_force(enable)\n\n" \
+ "Enable/disable force umounting (see umount(8) man page, option -f).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_force(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_force(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_lazy_HELP "enable_lazy(enable)\n\n" \
+ "Enable/disable lazy umount (see umount(8) man page, option -l).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_lazy(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_lazy(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_loopdel_HELP "enable_loopdel(enable)\n\n" \
+ "Enable/disable loop delete (destroy) after umount (see umount(8), option -d)\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_loopdel(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_loopdel(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_rdonly_umount_HELP "enable_rdonly_umount(enable)\n\n" \
+ "Enable/disable read-only remount on failed umount(2)\n "\
+ "(see umount(8) man page, option -r).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_rdonly_umount(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_rdonly_umount(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_sloppy_HELP "enable_sloppy(enable)\n\n" \
+ "Set/unset sloppy mounting (see mount(8) man page, option -s).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_sloppy(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_sloppy(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_verbose_HELP "enable_verbose(enable)\n\n" \
+ "Enable/disable verbose output (TODO: not implemented yet)\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_verbose(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = { "enable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_verbose(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_enable_fork_HELP "enable_fork(enable)\n\n" \
+ "Enable/disable fork(2) call in Cxt.next_mount()(not yet implemented) (see mount(8) man\n" \
+ "page, option -F).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_enable_fork(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int enable;
+ char *kwlist[] = {"enable", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_enable_fork(self->cxt, enable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_disable_canonicalize_HELP "disable_canonicalize(disable)\n\n" \
+ "Enable/disable paths canonicalization and tags evaluation. The libmount context\n" \
+ "canonicalizes paths when searching fstab and when preparing source and target paths\n" \
+ "for mount(2) syscall.\n" \
+ "\n" \
+ "This function has effect to the private (within context) fstab instance only\n" \
+ "(see Cxt.fstab).\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_disable_canonicalize(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int disable;
+ char *kwlist[] = {"disable", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &disable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_disable_canonicalize(self->cxt, disable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_disable_helpers_HELP "disable_helpers(disable)\n\n" \
+ "Enable/disable /sbin/[u]mount.* helpers (see mount(8) man page, option -i).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_disable_helpers(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int disable;
+ char *kwlist[] = {"disable", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &disable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_disable_helpers(self->cxt, disable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_disable_mtab_HELP "disable_mtab(disable)\n\n" \
+ "Disable/enable mtab update (see mount(8) man page, option -n).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_disable_mtab(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int disable;
+ char *kwlist[] = {"disable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &disable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_disable_mtab(self->cxt, disable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_disable_swapmatch_HELP "disable_swapmatch(disable)\n\n" \
+ "Disable/enable swap between source and target for mount(8) if only one path\n" \
+ "is specified.\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_disable_swapmatch(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int disable;
+ char *kwlist[] = { "disable", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &disable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_context_disable_swapmatch(self->cxt, disable);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+static int Context_set_source(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *source;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(source = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_source(self->cxt, source);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_mountdata(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *mountdata;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(mountdata = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_mountdata(self->cxt, mountdata);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_target(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char * target;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(target = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_target(self->cxt, target);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_fstype(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char * fstype;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(fstype = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_fstype(self->cxt, fstype);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_options(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char * options;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(options = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_options(self->cxt, options);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_fstype_pattern(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char * fstype_pattern;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(fstype_pattern = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_fstype_pattern(self->cxt, fstype_pattern);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_options_pattern(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char * options_pattern;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(options_pattern = pystos(value)))
+ return -1;
+
+ rc = mnt_context_set_options_pattern(self->cxt, options_pattern);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static int Context_set_fs(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ FsObject *fs;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyArg_Parse(value, "O!", &FsType, &fs)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ Py_INCREF(fs);
+ Py_XDECREF(mnt_context_get_fs_userdata(self->cxt));
+
+ return mnt_context_set_fs(self->cxt, fs->fs);
+}
+
+static int Context_set_fstab(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ TableObject *fstab;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyArg_Parse(value, "O!", &TableType, &fstab)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ Py_INCREF(fstab);
+ Py_XDECREF(mnt_context_get_fstab_userdata(self->cxt));
+
+ return mnt_context_set_fstab(self->cxt, fstab->tab);
+}
+
+static int Context_set_optsmode(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ int optsmode;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ optsmode = PyLong_AsLong(value);
+ return mnt_context_set_optsmode(self->cxt, optsmode);
+}
+
+static int Context_set_syscall_status(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ int syscall_status;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ syscall_status = PyLong_AsLong(value);
+ return mnt_context_set_syscall_status(self->cxt, syscall_status);
+}
+
+static int Context_set_user_mflags(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ unsigned long flags;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ flags = PyLong_AsUnsignedLong(value);
+ return mnt_context_set_mflags(self->cxt, flags);
+
+}
+
+static int Context_set_mflags(ContextObjext *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ unsigned long flags;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+ flags = PyLong_AsUnsignedLong(value);
+ return mnt_context_set_mflags(self->cxt, flags);
+}
+
+/* returns a flags integer (behavior differs from C API) */
+static PyObject *Context_get_mflags(ContextObjext *self)
+{
+ unsigned long flags;
+
+ PyObject *result;
+ mnt_context_get_mflags(self->cxt, &flags);
+ result = Py_BuildValue("k", flags);
+
+ if (!result)
+ PyErr_SetString(PyExc_RuntimeError, CONSTRUCT_ERR);
+ return result;
+
+}
+/* returns a flags integer (behavior differs from C API) */
+static PyObject *Context_get_user_mflags(ContextObjext *self)
+{
+ unsigned long flags;
+
+ PyObject *result;
+ mnt_context_get_user_mflags(self->cxt, &flags);
+ result = Py_BuildValue("k", flags);
+
+ if (!result)
+ PyErr_SetString(PyExc_RuntimeError, CONSTRUCT_ERR);
+ return result;
+
+}
+#define Context_reset_status_HELP "reset_status()\n\n" \
+ "Resets mount(2) and mount.type statuses, so Cxt.do_mount() or\n" \
+ "Cxt.do_umount() could be again called with the same settings.\n" \
+ "\n" \
+ "BE CAREFUL -- after this soft reset the libmount will NOT parse mount\n" \
+ "options, evaluate permissions or apply stuff from fstab.\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_reset_status(ContextObjext *self)
+{
+ int rc = mnt_context_reset_status(self->cxt);
+
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_is_fake_HELP "is_fake()\n\n" \
+"Returns True if fake flag is enabled or False"
+static PyObject *Context_is_fake(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_fake(self->cxt));
+}
+
+#define Context_is_force_HELP "is_force()\n\n" \
+"Returns True if force umounting flag is enabled or False"
+static PyObject *Context_is_force(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_force(self->cxt));
+}
+
+#define Context_is_lazy_HELP "is_lazy()\n\n" \
+"Returns True if lazy umount is enabled or False"
+static PyObject *Context_is_lazy(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_lazy(self->cxt));
+}
+
+#define Context_is_nomtab_HELP "is_nomtab()\n\n" \
+ "Returns True if no-mtab is enabled or False"
+static PyObject *Context_is_nomtab(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_nomtab(self->cxt));
+}
+
+#define Context_is_rdonly_umount_HELP "is_rdonly_umount()\n\n" \
+ "Enable/disable read-only remount on failed umount(2)\n" \
+ "(see umount(8) man page, option -r).\n" \
+ "\n" \
+ "Returns self on success, raises an exception in case of error."
+static PyObject *Context_is_rdonly_umount(ContextObjext *self)
+{
+ int rc = mnt_context_is_rdonly_umount(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_is_restricted_HELP "is_restricted()\n\n" \
+ "Returns False for unrestricted mount (user is root), or True for non-root mounts"
+static PyObject *Context_is_restricted(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_restricted(self->cxt));
+}
+
+#define Context_is_sloppy_HELP "is_sloppy()\n\n" \
+ "Returns True if sloppy flag is enabled or False"
+static PyObject *Context_is_sloppy(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_sloppy(self->cxt));
+}
+
+#define Context_is_verbose_HELP "is_verbose()\n\n" \
+ "Returns True if verbose flag is enabled or False"
+static PyObject *Context_is_verbose(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_verbose(self->cxt));
+}
+#define Context_is_fs_mounted_HELP "is_fs_mounted(fs, mounted)\n\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_is_fs_mounted(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"fs", "mounted", NULL};
+ FsObject *fs;
+ int mounted;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwds, "O!i", kwlist,
+ &FsType, &fs, &mounted)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_context_is_fs_mounted(self->cxt, fs->fs, &mounted));
+}
+
+#define Context_is_child_HELP "is_child()\n\n" \
+ "Returns True if mount -F enabled and the current context is child, or False"
+static PyObject *Context_is_child(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_child(self->cxt));
+}
+
+#define Context_is_fork_HELP "is_fork()\n\n" \
+ "Returns True if fork (mount -F) is enabled or False"
+static PyObject *Context_is_fork(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_fork(self->cxt));
+}
+
+#define Context_is_parent_HELP "is_parent()\n\n" \
+ "Returns True if mount -F enabled and the current context is parent, or False"
+static PyObject *Context_is_parent(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_parent(self->cxt));
+}
+
+#define Context_is_loopdel_HELP "is_loopdel()\n\n" \
+ "Returns True if loop device should be deleted after umount (umount -d) or False."
+static PyObject *Context_is_loopdel(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_loopdel(self->cxt));
+}
+
+#define Context_is_nocanonicalize_HELP "is_nocanonicalize()\n\n" \
+ "Returns True if no-canonicalize mode enabled or False."
+static PyObject *Context_is_nocanonicalize(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_nocanonicalize(self->cxt));
+}
+
+#define Context_is_nohelpers_HELP "is_nohelpers()\n\n" \
+ "Returns True if helpers are disabled (mount -i) or False."
+static PyObject *Context_is_nohelpers(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_nohelpers(self->cxt));
+}
+
+#define Context_syscall_called_HELP "syscall_called()\n\n" \
+ "Returns True if mount(2) syscall has been called, or False."
+static PyObject *Context_syscall_called(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_syscall_called(self->cxt));
+}
+
+#define Context_is_swapmatch_HELP "is_swapmatch()\n\n" \
+ "Returns True if swap between source and target is allowed (default is True) or False."
+static PyObject *Context_is_swapmatch(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_is_swapmatch(self->cxt));
+}
+
+#define Context_tab_applied_HELP "tab_applied()\n\n" \
+ "Returns True if fstab (or mtab) has been applied to the context, False otherwise."
+static PyObject *Context_tab_applied(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_tab_applied(self->cxt));
+}
+
+#define Context_apply_fstab_HELP "apply_fstab()\n\n" \
+ "This function is optional.\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_apply_fstab(ContextObjext *self)
+{
+ int rc = mnt_context_apply_fstab(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_helper_executed_HELP "helper_executed()\n\n" \
+ "Returns True if mount.type helper has been executed, or False."
+static PyObject *Context_helper_executed(ContextObjext *self)
+{
+ return PyBool_FromLong(mnt_context_helper_executed(self->cxt));
+}
+
+static PyObject *Context_get_source(ContextObjext *self)
+{
+ return PyObjectResultStr(mnt_context_get_source(self->cxt));
+}
+
+static PyObject *Context_get_target(ContextObjext *self)
+{
+ return PyObjectResultStr(mnt_context_get_target(self->cxt));
+}
+
+static PyObject *Context_get_options(ContextObjext *self)
+{
+ return PyObjectResultStr(mnt_context_get_options(self->cxt));
+}
+
+static PyObject *Context_get_fstype(ContextObjext *self)
+{
+ return PyObjectResultStr(mnt_context_get_fstype(self->cxt));
+}
+
+static PyObject *Context_get_fs(ContextObjext *self)
+{
+ return PyObjectResultFs(mnt_context_get_fs(self->cxt));
+}
+
+static PyObject *Context_get_fstab(ContextObjext *self)
+{
+ struct libmnt_table *tab = NULL;
+
+ if (mnt_context_get_fstab(self->cxt, &tab) != 0 || !tab)
+ return NULL;
+ return PyObjectResultTab(tab);
+}
+
+static PyObject *Context_get_mtab(ContextObjext *self)
+{
+ struct libmnt_table *tab = NULL;
+
+ if (mnt_context_get_mtab(self->cxt, &tab) != 0 || !tab)
+ return NULL;
+ return PyObjectResultTab(tab);
+}
+
+static PyObject *Context_get_optsmode(ContextObjext *self)
+{
+ return PyObjectResultInt(mnt_context_get_optsmode(self->cxt));
+}
+
+static PyObject *Context_get_status(ContextObjext *self)
+{
+ return PyObjectResultInt(mnt_context_get_status(self->cxt));
+}
+
+static PyObject *Context_get_syscall_errno(ContextObjext *self)
+{
+ return PyObjectResultInt(mnt_context_get_syscall_errno(self->cxt));
+}
+
+#define Context_do_mount_HELP "do_mount()\n\n" \
+ "Call mount(2) or mount.type helper. Unnecessary for Cxt.mount().\n" \
+ "\n" \
+ "Note that this function could be called only once. If you want to mount\n" \
+ "another source or target than you have to call Cxt.reset_context().\n" \
+ "\n" \
+ "If you want to call mount(2) for the same source and target with a different\n" \
+ "mount flags or fstype then call Cxt.reset_status() and then try\n" \
+ "again Cxt.do_mount().\n" \
+ "\n" \
+ "WARNING: non-zero return code does not mean that mount(2) syscall or\n" \
+ "mount.type helper wasn't successfully called.\n" \
+ "\n" \
+ "Check Cxt.status after error!\n" \
+ "\n" \
+ "Returns self on success or an exception in case of other errors."
+static PyObject *Context_do_mount(ContextObjext *self)
+{
+ int rc = mnt_context_do_mount(self->cxt);
+ return rc ? UL_RaiseExc(rc < 0 ? -rc : rc) : UL_IncRef(self);
+}
+
+#define Context_do_umount_HELP "do_umount()\n\n" \
+ "Umount filesystem by umount(2) or fork()+exec(/sbin/umount.type).\n" \
+ "Unnecessary for Cxt.umount().\n" \
+ "\n" \
+ "See also Cxt.disable_helpers().\n" \
+ "\n" \
+ "WARNING: non-zero return code does not mean that umount(2) syscall or\n" \
+ "umount.type helper wasn't successfully called.\n" \
+ "\n" \
+ "Check Cxt.status after error!\n" \
+ "\n" \
+ "Returns self on success or an exception in case of other errors."
+static PyObject *Context_do_umount(ContextObjext *self)
+{
+ int rc = mnt_context_do_umount(self->cxt);
+ return rc ? UL_RaiseExc(rc < 0 ? -rc : rc) : UL_IncRef(self);
+}
+
+#define Context_mount_HELP "mount()\n\n" \
+ "High-level, mounts filesystem by mount(2) or fork()+exec(/sbin/mount.type).\n" \
+ "\n" \
+ "This is similar to:\n" \
+ "\n" \
+ "Cxt.prepare_mount();\n" \
+ "Cxt.do_mount();\n" \
+ "Cxt.finalize_mount();\n" \
+ "\n" \
+ "See also Cxt.disable_helper().\n" \
+ "\n" \
+ "Note that this function could be called only once. If you want to mount with\n" \
+ "different setting than you have to call Cxt.reset_context(). It's NOT enough\n" \
+ "to call Cxt.reset_status() if you want call this function more than\n" \
+ "once, whole context has to be reset.\n" \
+ "\n" \
+ "WARNING: non-zero return code does not mean that mount(2) syscall or\n" \
+ "mount.type helper wasn't successfully called.\n" \
+ "\n" \
+ "Check Cxt.status after error!\n" \
+ "\n" \
+ "Returns self on success or an exception in case of other errors."
+static PyObject *Context_mount(ContextObjext *self)
+{
+ int rc = mnt_context_mount(self->cxt);
+ return rc ? UL_RaiseExc(rc < 0 ? -rc : rc) : UL_IncRef(self);
+}
+
+#define Context_umount_HELP "umount()\n\n" \
+ "High-level, umounts filesystem by umount(2) or fork()+exec(/sbin/umount.type).\n" \
+ "\n" \
+ "This is similar to:\n" \
+ "\n" \
+ "Cxt.prepare_umount();\n" \
+ "Cxt.do_umount();\n" \
+ "Cxt.finalize_umount();\n" \
+ "\n" \
+ "See also Cxt.disable_helpers().\n" \
+ "\n" \
+ "WARNING: non-zero return code does not mean that umount(2) syscall or\n" \
+ "umount.type helper wasn't successfully called.\n" \
+ "\n" \
+ "Check Cxt.status after error!\n" \
+ "\n" \
+ "Returns self on success or an exception in case of other errors."
+static PyObject *Context_umount(ContextObjext *self)
+{
+ int rc = mnt_context_umount(self->cxt);
+ return rc ? UL_RaiseExc(rc < 0 ? -rc : rc) : UL_IncRef(self);
+}
+
+#define Context_finalize_mount_HELP "finalize_mount()\n\n" \
+ "Mtab update, etc. Unnecessary for Cxt.mount(), but should be called\n" \
+ "after Cxt.do_mount(). See also Cxt.syscall_status.\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_finalize_mount(ContextObjext *self)
+{
+ int rc = mnt_context_finalize_mount(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_prepare_umount_HELP "prepare_umount()\n\n" \
+ "Prepare context for umounting, unnecessary for Cxt.umount().\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_prepare_umount(ContextObjext *self)
+{
+ int rc = mnt_context_prepare_umount(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_prepare_mount_HELP "prepare_mount()\n\n" \
+ "Prepare context for mounting, unnecessary for Cxt.mount().\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_prepare_mount(ContextObjext *self)
+{
+ int rc = mnt_context_prepare_mount(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_finalize_umount_HELP "finalize_umount()\n\n" \
+ "Mtab update, etc. Unnecessary for Cxt.umount(), but should be called\n" \
+ "after Cxt.do_umount(). See also Cxt.syscall_status.\n" \
+ "\n" \
+ "Returns self on success, raises LibmountError if target filesystem not found, or other exception on error."
+static PyObject *Context_finalize_umount(ContextObjext *self)
+{
+ int rc = mnt_context_finalize_umount(self->cxt);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_find_umount_fs_HELP "find_umount_fs(tgt, pfs)\n\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_find_umount_fs(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ char *kwlist[] = { "tgt", "pfs", NULL };
+ char *tgt = NULL;
+ FsObject *fs;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO!", kwlist, &tgt, &FsType, &fs)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+
+ rc = mnt_context_find_umount_fs(self->cxt, tgt, &fs->fs);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_append_options_HELP "append_options(optstr)\n\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_append_options(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ char *kwlist[] = {"optstr", NULL};
+ char *optstr = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &optstr)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+
+ rc = mnt_context_append_options(self->cxt, optstr);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_helper_setopt_HELP "helper_setopt(c, arg)\n\n" \
+ "This function applies [u]mount.type command line option (for example parsed\n" \
+ "by getopt or getopt_long) to cxt. All unknown options are ignored and\n" \
+ "then ValueError is raised.\n" \
+ "\n" \
+ "Returns self on success, raises ValueError if c is unknown or other exception in case of an error."
+static PyObject *Context_helper_setopt(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int c;
+ char *arg;
+ char *kwlist[] = { "c", "arg", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "is", kwlist, &c, &arg)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+
+ rc = mnt_context_helper_setopt(self->cxt, c, arg);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Context_init_helper_HELP "init_helper(action, flags)\n\n" \
+ "This function informs libmount that it is used from [u]mount.type helper.\n" \
+ "\n" \
+ "The function also calls Cxt.disable_helpers() to avoid calling\n" \
+ "mount.type helpers recursively. If you really want to call another\n" \
+ "mount.type helper from your helper then you have to explicitly enable this\n" \
+ "feature by:\n" \
+ "\n" \
+ "Cxt.disable_helpers(False);\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Context_init_helper(ContextObjext *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ int action, flags;
+ char *kwlist[] = {"action", "flags", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", kwlist, &action, &flags)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+
+ rc = mnt_context_init_helper(self->cxt, action, flags);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+static PyGetSetDef Context_getseters[] = {
+ {"tables_errcb", NULL, (setter)Context_set_tables_errcb, "error callback function", NULL},
+ {"status", (getter)Context_get_status, NULL, "status", NULL},
+ {"source", (getter)Context_get_source, (setter)Context_set_source, "source", NULL},
+ {"target", (getter)Context_get_target, (setter)Context_set_target, "target", NULL},
+ {"fstype", (getter)Context_get_fstype, (setter)Context_set_fstype, "fstype", NULL},
+ {"options", (getter)Context_get_options, (setter)Context_set_options, "options", NULL},
+ {"mflags", (getter)Context_get_mflags, (setter)Context_set_mflags, "mflags", NULL},
+ {"mountdata", NULL, (setter)Context_set_mountdata, "mountdata", NULL},
+ {"fstype_pattern", NULL, (setter)Context_set_fstype_pattern, "fstype_pattern", NULL},
+ {"options_pattern", NULL, (setter)Context_set_options_pattern, "options_pattern", NULL},
+ {"fs", (getter)Context_get_fs, (setter)Context_set_fs, "filesystem description (type, mountpoint, device, ...)", NULL},
+ {"mtab", (getter)Context_get_mtab, NULL, "mtab entries", NULL},
+ {"fstab", (getter)Context_get_fstab, (setter)Context_set_fstab, "fstab (or mtab for some remounts)", NULL},
+ {"optsmode", (getter)Context_get_optsmode, (setter)Context_set_optsmode, "fstab optstr mode MNT_OPTSMODE_{AUTO,FORCE,IGNORE}", NULL},
+ {"syscall_errno", (getter)Context_get_syscall_errno, (setter)Context_set_syscall_status, "1: not_called yet, 0: success, <0: -errno", NULL},
+ {"user_mflags", (getter)Context_get_user_mflags, (setter)Context_set_user_mflags, "user mflags", NULL},
+ {NULL}
+};
+static PyMethodDef Context_methods[] = {
+ {"find_umount_fs", (PyCFunction)Context_find_umount_fs, METH_VARARGS|METH_KEYWORDS, Context_find_umount_fs_HELP},
+ {"reset_status", (PyCFunction)Context_reset_status, METH_NOARGS, Context_reset_status_HELP},
+ {"helper_executed", (PyCFunction)Context_helper_executed, METH_NOARGS, Context_helper_executed_HELP},
+ {"init_helper", (PyCFunction)Context_init_helper, METH_VARARGS|METH_KEYWORDS, Context_init_helper_HELP},
+ {"helper_setopt", (PyCFunction)Context_helper_setopt, METH_VARARGS|METH_KEYWORDS, Context_helper_setopt_HELP},
+ {"append_options", (PyCFunction)Context_append_options, METH_VARARGS|METH_KEYWORDS, Context_append_options_HELP},
+ {"apply_fstab", (PyCFunction)Context_apply_fstab, METH_NOARGS, Context_apply_fstab_HELP},
+ {"disable_canonicalize", (PyCFunction)Context_disable_canonicalize, METH_VARARGS|METH_KEYWORDS, Context_disable_canonicalize_HELP},
+ {"disable_helpers", (PyCFunction)Context_disable_helpers, METH_VARARGS|METH_KEYWORDS, Context_disable_helpers_HELP},
+ {"disable_mtab", (PyCFunction)Context_disable_mtab, METH_VARARGS|METH_KEYWORDS, Context_disable_mtab_HELP},
+ {"do_mount", (PyCFunction)Context_do_mount, METH_NOARGS, Context_do_mount_HELP},
+ {"do_umount", (PyCFunction)Context_do_umount, METH_NOARGS , Context_do_umount_HELP},
+ {"enable_fake", (PyCFunction)Context_enable_fake, METH_VARARGS|METH_KEYWORDS, Context_enable_fake_HELP},
+ {"enable_force", (PyCFunction)Context_enable_force, METH_VARARGS|METH_KEYWORDS, Context_enable_force_HELP},
+ {"enable_lazy", (PyCFunction)Context_enable_lazy, METH_VARARGS|METH_KEYWORDS, Context_enable_lazy_HELP},
+ {"enable_loopdel", (PyCFunction)Context_enable_loopdel, METH_VARARGS|METH_KEYWORDS, Context_enable_loopdel_HELP},
+ {"enable_rdonly_umount", (PyCFunction)Context_enable_rdonly_umount, METH_VARARGS|METH_KEYWORDS, Context_enable_rdonly_umount_HELP},
+ {"enable_sloppy", (PyCFunction)Context_enable_sloppy, METH_VARARGS|METH_KEYWORDS, Context_enable_sloppy_HELP},
+ {"enable_verbose", (PyCFunction)Context_enable_verbose, METH_VARARGS|METH_KEYWORDS, Context_enable_verbose_HELP},
+ {"enable_fork", (PyCFunction)Context_enable_fork, METH_VARARGS|METH_KEYWORDS, Context_enable_fork_HELP},
+ {"finalize_mount", (PyCFunction)Context_finalize_mount, METH_NOARGS, Context_finalize_mount_HELP},
+ {"finalize_umount", (PyCFunction)Context_finalize_umount, METH_NOARGS, Context_finalize_umount_HELP},
+ {"is_fake", (PyCFunction)Context_is_fake, METH_NOARGS, Context_is_fake_HELP},
+ {"is_force", (PyCFunction)Context_is_force, METH_NOARGS, Context_is_force_HELP},
+ {"is_fork", (PyCFunction)Context_is_fork, METH_NOARGS, Context_is_fork_HELP},
+ {"is_fs_mounted", (PyCFunction)Context_is_fs_mounted, METH_VARARGS|METH_KEYWORDS, Context_is_fs_mounted_HELP},
+ {"is_lazy", (PyCFunction)Context_is_lazy, METH_NOARGS, Context_is_lazy_HELP},
+ {"is_nomtab", (PyCFunction)Context_is_nomtab, METH_NOARGS, Context_is_nomtab_HELP},
+ {"is_rdonly_umount", (PyCFunction)Context_is_rdonly_umount, METH_NOARGS, Context_is_rdonly_umount_HELP},
+ {"is_restricted", (PyCFunction)Context_is_restricted, METH_NOARGS, Context_is_restricted_HELP},
+ {"is_sloppy", (PyCFunction)Context_is_sloppy, METH_NOARGS, Context_is_sloppy_HELP},
+ {"is_verbose", (PyCFunction)Context_is_verbose, METH_NOARGS, Context_is_verbose_HELP},
+ {"is_child", (PyCFunction)Context_is_child, METH_NOARGS, Context_is_child_HELP},
+ {"is_parent", (PyCFunction)Context_is_parent, METH_NOARGS, Context_is_parent_HELP},
+ {"is_loopdel", (PyCFunction)Context_is_loopdel, METH_NOARGS, Context_is_loopdel_HELP},
+ {"is_nocanonicalize", (PyCFunction)Context_is_nocanonicalize, METH_NOARGS, Context_is_nocanonicalize_HELP},
+ {"is_nohelpers", (PyCFunction)Context_is_nohelpers, METH_NOARGS, Context_is_nohelpers_HELP},
+ {"is_swapmatch", (PyCFunction)Context_is_swapmatch, METH_NOARGS, Context_is_swapmatch_HELP},
+ {"mount", (PyCFunction)Context_mount, METH_NOARGS, Context_mount_HELP},
+ {"prepare_mount", (PyCFunction)Context_prepare_mount, METH_NOARGS, Context_prepare_mount_HELP},
+ {"prepare_umount", (PyCFunction)Context_prepare_umount, METH_NOARGS, Context_prepare_umount_HELP},
+ {"umount", (PyCFunction)Context_umount, METH_NOARGS, Context_umount_HELP},
+ {"syscall_called", (PyCFunction)Context_syscall_called, METH_NOARGS, Context_syscall_called_HELP},
+ {"disable_swapmatch", (PyCFunction)Context_disable_swapmatch, METH_VARARGS|METH_KEYWORDS, Context_disable_swapmatch_HELP},
+ {"tab_applied", (PyCFunction)Context_tab_applied, METH_NOARGS, Context_tab_applied_HELP},
+ {NULL}
+};
+
+static PyObject *Context_repr(ContextObjext *self)
+{
+ return PyUnicode_FromFormat("<libmount.Context object at %p, restricted=%s>",
+ self, mnt_context_is_restricted(self->cxt) ? "True" : "False");
+}
+
+PyTypeObject ContextType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "libmount.Context", /*tp_name*/
+ sizeof(ContextObjext), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Context_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ NULL, /*tp_getattr*/
+ NULL, /*tp_setattr*/
+ NULL, /*tp_compare*/
+ (reprfunc) Context_repr,
+ NULL, /*tp_as_number*/
+ NULL, /*tp_as_sequence*/
+ NULL, /*tp_as_mapping*/
+ NULL, /*tp_hash */
+ NULL, /*tp_call*/
+ NULL, /*tp_str*/
+ NULL, /*tp_getattro*/
+ NULL, /*tp_setattro*/
+ NULL, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ Context_HELP, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ Context_methods, /* tp_methods */
+ Context_members, /* tp_members */
+ Context_getseters, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Context_init, /* tp_init */
+ NULL, /* tp_alloc */
+ Context_new, /* tp_new */
+};
+
+void Context_AddModuleObject(PyObject *mod)
+{
+ if (PyType_Ready(&ContextType) < 0)
+ return;
+
+ DBG(CXT, pymnt_debug("add to module"));
+
+ Py_INCREF(&ContextType);
+ PyModule_AddObject(mod, "Context", (PyObject *)&ContextType);
+}
+
+
diff --git a/libmount/python/fs.c b/libmount/python/fs.c
new file mode 100644
index 0000000..e989124
--- /dev/null
+++ b/libmount/python/fs.c
@@ -0,0 +1,883 @@
+/*
+ * Python bindings for the libmount library.
+ *
+ * Copyright (C) 2013, Red Hat, Inc. All rights reserved.
+ * Written by Ondrej Oprala and Karel Zak
+ *
+ * This file 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 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * TODO:
+ * mnt_fs_match_{source,target}
+ * mnt_fs_get_{attribute,option}
+ */
+
+#include "pylibmount.h"
+#include <errno.h>
+
+#define Fs_HELP "Fs(source=None, root=None, target=None, fstype=None, options=None, attributes=None, freq=0, passno=0)"
+
+static PyMemberDef Fs_members[] = {
+ {NULL}
+};
+
+static PyObject *Fs_get_tag(FsObject *self)
+{
+ const char *tag = NULL, *val = NULL;
+ PyObject *result;
+
+ if (mnt_fs_get_tag(self->fs, &tag, &val) != 0)
+ return NULL;
+
+ result = Py_BuildValue("(ss)", tag, val);
+ if (!result)
+ PyErr_SetString(PyExc_RuntimeError, CONSTRUCT_ERR);
+ return result;
+}
+
+static PyObject *Fs_get_id(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_id(self->fs));
+}
+
+static PyObject *Fs_get_parent_id(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_parent_id(self->fs));
+}
+
+static PyObject *Fs_get_devno(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_devno(self->fs));
+}
+
+static void _dump_debug_string(const char *lead, const char *s, char quote)
+{
+ /* PySys_WriteStdout() will automatically truncate any '%s' token
+ * longer than a certain length (documented as 1000 bytes, but we
+ * give ourselves some margin here just in case). The only way I
+ * know to get around this is to print such strings in bite-sized
+ * chunks.
+ */
+ static const unsigned int _PY_MAX_LEN = 900;
+ static const char *_PY_MAX_LEN_FMT = "%.900s";
+ unsigned int len;
+
+ if (lead != NULL)
+ PySys_WriteStdout("%s", lead);
+
+ if (quote != 0)
+ PySys_WriteStdout("%c", quote);
+
+ for (len = strlen(s); len > _PY_MAX_LEN; len -= _PY_MAX_LEN, s += _PY_MAX_LEN)
+ PySys_WriteStdout(_PY_MAX_LEN_FMT, s);
+
+ if (len > 0)
+ PySys_WriteStdout(_PY_MAX_LEN_FMT, s);
+
+ if (quote != 0)
+ PySys_WriteStdout("%c\n", quote);
+ else
+ PySys_WriteStdout("\n");
+}
+
+#define Fs_print_debug_HELP "print_debug()\n\n"
+static PyObject *Fs_print_debug(FsObject *self)
+{
+ PySys_WriteStdout("------ fs: %p\n", self->fs);
+ _dump_debug_string("source: ", mnt_fs_get_source(self->fs), 0);
+ _dump_debug_string("target: ", mnt_fs_get_target(self->fs), 0);
+ _dump_debug_string("fstype: ", mnt_fs_get_fstype(self->fs), 0);
+
+ if (mnt_fs_get_options(self->fs))
+ _dump_debug_string("optstr: ", mnt_fs_get_options(self->fs), 0);
+ if (mnt_fs_get_vfs_options(self->fs))
+ _dump_debug_string("VFS-optstr: ", mnt_fs_get_vfs_options(self->fs), 0);
+ if (mnt_fs_get_fs_options(self->fs))
+ _dump_debug_string("FS-opstr: ", mnt_fs_get_fs_options(self->fs), 0);
+ if (mnt_fs_get_user_options(self->fs))
+ _dump_debug_string("user-optstr: ", mnt_fs_get_user_options(self->fs), 0);
+ if (mnt_fs_get_optional_fields(self->fs))
+ _dump_debug_string("optional-fields: ", mnt_fs_get_optional_fields(self->fs), '\'');
+ if (mnt_fs_get_attributes(self->fs))
+ _dump_debug_string("attributes: ", mnt_fs_get_attributes(self->fs), 0);
+
+ if (mnt_fs_get_root(self->fs))
+ _dump_debug_string("root: ", mnt_fs_get_root(self->fs), 0);
+
+ if (mnt_fs_get_swaptype(self->fs))
+ _dump_debug_string("swaptype: ", mnt_fs_get_swaptype(self->fs), 0);
+ if (mnt_fs_get_size(self->fs))
+ PySys_WriteStdout("size: %jd\n", mnt_fs_get_size(self->fs));
+ if (mnt_fs_get_usedsize(self->fs))
+ PySys_WriteStdout("usedsize: %jd\n", mnt_fs_get_usedsize(self->fs));
+ if (mnt_fs_get_priority(self->fs))
+ PySys_WriteStdout("priority: %d\n", mnt_fs_get_priority(self->fs));
+
+ if (mnt_fs_get_bindsrc(self->fs))
+ _dump_debug_string("bindsrc: ", mnt_fs_get_bindsrc(self->fs), 0);
+ if (mnt_fs_get_freq(self->fs))
+ PySys_WriteStdout("freq: %d\n", mnt_fs_get_freq(self->fs));
+ if (mnt_fs_get_passno(self->fs))
+ PySys_WriteStdout("pass: %d\n", mnt_fs_get_passno(self->fs));
+ if (mnt_fs_get_id(self->fs))
+ PySys_WriteStdout("id: %d\n", mnt_fs_get_id(self->fs));
+ if (mnt_fs_get_parent_id(self->fs))
+ PySys_WriteStdout("parent: %d\n", mnt_fs_get_parent_id(self->fs));
+ if (mnt_fs_get_devno(self->fs))
+ PySys_WriteStdout("devno: %d:%d\n", major(mnt_fs_get_devno(self->fs)),
+ minor(mnt_fs_get_devno(self->fs)));
+ if (mnt_fs_get_tid(self->fs))
+ PySys_WriteStdout("tid: %d\n", mnt_fs_get_tid(self->fs));
+ if (mnt_fs_get_comment(self->fs))
+ _dump_debug_string("comment: ", mnt_fs_get_comment(self->fs), '\'');
+ return UL_IncRef(self);
+}
+/*
+ ** Fs getters/setters
+ */
+
+static PyObject *Fs_get_comment(FsObject *self, void *closure __attribute__((unused)))
+{
+ return PyObjectResultStr(mnt_fs_get_comment(self->fs));
+}
+
+static int Fs_set_comment(FsObject *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *comment = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(comment = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_comment(self->fs, comment);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+/* source */
+static PyObject *Fs_get_source(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_source(self->fs));
+}
+
+static int Fs_set_source(FsObject *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *source = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(source = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_source(self->fs, source);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_srcpath(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_srcpath(self->fs));
+}
+
+static PyObject *Fs_get_root(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_root(self->fs));
+}
+
+static int Fs_set_root(FsObject *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *root = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(root = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_root(self->fs, root);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_target(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_target(self->fs));
+}
+
+static int Fs_set_target(FsObject *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ char *target = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(target = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_target(self->fs, target);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_fstype(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_fstype(self->fs));
+}
+
+static int Fs_set_fstype(FsObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ char *fstype = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(fstype = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_fstype(self->fs, fstype);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_options(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_options(self->fs));
+}
+
+static int Fs_set_options(FsObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ char *options = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(options = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_options(self->fs, options);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_vfs_options(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_vfs_options(self->fs));
+}
+
+
+static PyObject *Fs_get_optional_fields(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_optional_fields(self->fs));
+}
+
+
+static PyObject *Fs_get_fs_options(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_fs_options(self->fs));
+}
+
+
+static PyObject *Fs_get_user_options(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_user_options(self->fs));
+}
+
+
+static PyObject *Fs_get_attributes(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_attributes(self->fs));
+}
+
+static int Fs_set_attributes(FsObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ char *attributes = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(attributes = pystos(value)))
+ return -1;
+
+ rc = mnt_fs_set_attributes(self->fs, attributes);
+ if (rc) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Fs_get_freq(FsObject *self, void *closure __attribute__((unused)))
+{
+ return PyObjectResultInt(mnt_fs_get_freq(self->fs));
+}
+
+static int Fs_set_freq(FsObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ int freq = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+
+ }
+
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+
+ freq = PyLong_AsLong(value);
+ if (freq == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_RuntimeError, "type conversion failed");
+ return -1;
+ }
+ return mnt_fs_set_freq(self->fs, freq);
+}
+
+static PyObject *Fs_get_passno(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_passno(self->fs));
+}
+
+static int Fs_set_passno(FsObject *self, PyObject *value, void *closure __attribute__((unused)))
+{
+ int passno = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+
+ if (!PyLong_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return -1;
+ }
+
+ passno = PyLong_AsLong(value);
+ if (passno == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_RuntimeError, "type conversion failed");
+ return -1;
+ }
+ return mnt_fs_set_passno(self->fs, passno);
+}
+
+static PyObject *Fs_get_swaptype(FsObject *self)
+{
+ return PyObjectResultStr(mnt_fs_get_swaptype(self->fs));
+}
+
+static PyObject *Fs_get_size(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_size(self->fs));
+}
+
+static PyObject *Fs_get_usedsize(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_usedsize(self->fs));
+}
+
+static PyObject *Fs_get_priority(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_priority(self->fs));
+}
+
+#define Fs_get_propagation_HELP "get_propagation(flags)\n\n\
+Note that this function set flags to zero if not found any propagation flag\n\
+in mountinfo file. The kernel default is MS_PRIVATE, this flag is not stored\n\
+in the mountinfo file.\n\
+\n\
+Returns self or raises an exception in case of an error."
+static PyObject *Fs_get_propagation(FsObject *self)
+{
+ unsigned long flags;
+ int rc;
+
+ rc = mnt_fs_get_propagation(self->fs, &flags);
+ return rc ? UL_RaiseExc(-rc) : PyObjectResultInt(flags);
+}
+
+static PyObject *Fs_get_tid(FsObject *self)
+{
+ return PyObjectResultInt(mnt_fs_get_tid(self->fs));
+}
+
+#define Fs_is_kernel_HELP "is_kernel()\n\nReturns 1 if the filesystem " \
+ "description is read from kernel e.g. /proc/mounts."
+static PyObject *Fs_is_kernel(FsObject *self)
+{
+ return PyBool_FromLong(mnt_fs_is_kernel(self->fs));
+}
+
+#define Fs_is_netfs_HELP "is_netfs()\n\nReturns 1 if the filesystem is " \
+ "a network filesystem"
+static PyObject *Fs_is_netfs(FsObject *self)
+{
+ return PyBool_FromLong(mnt_fs_is_netfs(self->fs));
+}
+
+#define Fs_is_pseudofs_HELP "is_pseudofs()\n\nReturns 1 if the filesystem is "\
+ "a pseudo fs type (proc, cgroups)"
+static PyObject *Fs_is_pseudofs(FsObject *self)
+{
+ return PyBool_FromLong(mnt_fs_is_pseudofs(self->fs));
+}
+
+#define Fs_is_swaparea_HELP "is_swaparea()\n\nReturns 1 if the filesystem " \
+ "uses \"swap\" as a type"
+static PyObject *Fs_is_swaparea(FsObject *self)
+{
+ return PyBool_FromLong(mnt_fs_is_swaparea(self->fs));
+}
+
+#define Fs_append_attributes_HELP "append_attributes(optstr)\n\n" \
+ "Appends mount attributes."
+static PyObject *Fs_append_attributes(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"optstr", NULL};
+ char *optstr = NULL;
+ int rc;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &optstr)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_fs_append_attributes(self->fs, optstr);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Fs_append_options_HELP "append_options(optstr)\n\n" \
+ "Parses (splits) optstr and appends results to VFS, " \
+ "FS and userspace lists of options."
+static PyObject *Fs_append_options(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"optstr", NULL};
+ char *optstr = NULL;
+ int rc;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &optstr)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_fs_append_options(self->fs, optstr);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Fs_prepend_attributes_HELP "prepend_attributes(optstr)\n\n" \
+ "Prepends mount attributes."
+static PyObject *Fs_prepend_attributes(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"optstr", NULL};
+ char *optstr = NULL;
+ int rc;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &optstr)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_fs_prepend_attributes(self->fs, optstr);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Fs_prepend_options_HELP "prepend_options(optstr)\n\n" \
+ "Parses (splits) optstr and prepends results to VFS, " \
+ "FS and userspace lists of options."
+static PyObject *Fs_prepend_options(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"optstr", NULL};
+ char *optstr = NULL;
+ int rc;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &optstr)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_fs_prepend_options(self->fs, optstr);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Fs_match_fstype_HELP "match_fstype(pattern)\n\n" \
+ "pattern: filesystem name or comma delimited list(string) of names\n\n" \
+ "The pattern list of filesystem can be prefixed with a global\n" \
+ "\"no\" prefix to invert matching of the whole list. The \"no\" could\n" \
+ "also be used for individual items in the pattern list. So,\n" \
+ "\"nofoo,bar\" has the same meaning as \"nofoo,nobar\".\n" \
+ "\"bar\" : \"nofoo,bar\" -> False (global \"no\" prefix)\n" \
+ "\"bar\" : \"foo,bar\" -> True\n" \
+ "\"bar\" : \"foo,nobar\" -> False\n\n" \
+ "Returns True if type is matching, else False."
+static PyObject *Fs_match_fstype(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"pattern", NULL};
+ char *pattern = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &pattern)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_fs_match_fstype(self->fs, pattern));
+}
+
+#define Fs_match_options_HELP "match_options(options)\n\n" \
+ "options: comma delimited list of options (and nooptions)\n" \
+ "Returns True if fs type is matching to options else False."
+static PyObject *Fs_match_options(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"options", NULL};
+ char *options = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &options)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_fs_match_options(self->fs, options));
+}
+
+#define Fs_streq_srcpath_HELP "streq_srcpath(srcpath)\n\n" \
+ "Compares fs source path with path. The tailing slash is ignored.\n" \
+ "Returns True if fs source path equal to path, otherwise False."
+static PyObject *Fs_streq_srcpath(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"srcpath", NULL};
+ char *srcpath = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &srcpath)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_fs_streq_srcpath(self->fs, srcpath));
+}
+
+#define Fs_streq_target_HELP "streq_target(target)\n\n" \
+ "Compares fs target path with path. The tailing slash is ignored.\n" \
+ "See also Fs.match_target().\n" \
+ "Returns True if fs target path equal to path, otherwise False."
+static PyObject *Fs_streq_target(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"target", NULL};
+ char *target = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &target)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_fs_streq_target(self->fs, target));
+}
+
+#define Fs_copy_fs_HELP "copy_fs(dest=None)\n\n" \
+ "If dest is None, a new object is created, if any fs " \
+ "field is already set, then the field is NOT overwritten."
+static PyObject *Fs_copy_fs(FsObject *self, PyObject *args, PyObject *kwds);
+
+static PyMethodDef Fs_methods[] = {
+ {"get_propagation", (PyCFunction)Fs_get_propagation, METH_NOARGS, Fs_get_propagation_HELP},
+ {"mnt_fs_append_attributes", (PyCFunction)Fs_append_attributes, METH_VARARGS|METH_KEYWORDS, Fs_append_attributes_HELP},
+ {"append_options", (PyCFunction)Fs_append_options, METH_VARARGS|METH_KEYWORDS, Fs_append_options_HELP},
+ {"mnt_fs_prepend_attributes", (PyCFunction)Fs_prepend_attributes, METH_VARARGS|METH_KEYWORDS, Fs_prepend_attributes_HELP},
+ {"prepend_options", (PyCFunction)Fs_prepend_options, METH_VARARGS|METH_KEYWORDS, Fs_prepend_options_HELP},
+ {"copy_fs", (PyCFunction)Fs_copy_fs, METH_VARARGS|METH_KEYWORDS, Fs_copy_fs_HELP},
+ {"is_kernel", (PyCFunction)Fs_is_kernel, METH_NOARGS, Fs_is_kernel_HELP},
+ {"is_netfs", (PyCFunction)Fs_is_netfs, METH_NOARGS, Fs_is_netfs_HELP},
+ {"is_pseudofs", (PyCFunction)Fs_is_pseudofs, METH_NOARGS, Fs_is_pseudofs_HELP},
+ {"is_swaparea", (PyCFunction)Fs_is_swaparea, METH_NOARGS, Fs_is_swaparea_HELP},
+ {"match_fstype", (PyCFunction)Fs_match_fstype, METH_VARARGS|METH_KEYWORDS, Fs_match_fstype_HELP},
+ {"match_options", (PyCFunction)Fs_match_options, METH_VARARGS|METH_KEYWORDS, Fs_match_options_HELP},
+ {"streq_srcpath", (PyCFunction)Fs_streq_srcpath, METH_VARARGS|METH_KEYWORDS, Fs_streq_srcpath_HELP},
+ {"streq_target", (PyCFunction)Fs_streq_target, METH_VARARGS|METH_KEYWORDS, Fs_streq_target_HELP},
+ {"print_debug", (PyCFunction)Fs_print_debug, METH_NOARGS, Fs_print_debug_HELP},
+ {NULL}
+};
+
+static void Fs_destructor(FsObject *self)
+{
+ DBG(FS, pymnt_debug_h(self->fs, "destructor py-obj: %p, py-refcnt=%d",
+ self, (int) ((PyObject *) self)->ob_refcnt));
+ mnt_unref_fs(self->fs);
+ PyFree(self);
+}
+
+static PyObject *Fs_new(PyTypeObject *type, PyObject *args __attribute__((unused)),
+ PyObject *kwds __attribute__((unused)))
+{
+ FsObject *self = (FsObject*)type->tp_alloc(type, 0);
+
+ if (self) {
+ self->fs = NULL;
+ DBG(FS, pymnt_debug_h(self, "new"));
+ }
+ return (PyObject *) self;
+}
+
+static int Fs_init(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ char *source = NULL, *root = NULL, *target = NULL;
+ char *fstype = NULL, *options = NULL, *attributes =NULL;
+ int freq = 0; int passno = 0;
+ int rc = 0;
+ char *kwlist[] = {
+ "source", "root", "target",
+ "fstype", "options", "attributes",
+ "freq", "passno", NULL
+ };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ssssssii", kwlist,
+ &source, &root, &target, &fstype, &options,
+ &attributes, &freq, &passno)) {
+ PyErr_SetString(PyExc_TypeError, "Invalid type");
+ return -1;
+ }
+
+ DBG(FS, pymnt_debug_h(self, "init"));
+
+ if (self->fs)
+ mnt_unref_fs(self->fs);
+
+ self->fs = mnt_new_fs(); /* new FS with refcount=1 */
+
+ if (source && (rc = mnt_fs_set_source(self->fs, source))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+ if (root && (rc = mnt_fs_set_root(self->fs, root))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+ if (target && (rc = mnt_fs_set_target(self->fs, target))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+ if (fstype && (rc = mnt_fs_set_fstype(self->fs, fstype))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+ if (options && (rc = mnt_fs_set_options(self->fs, options))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+ if (attributes && (rc = mnt_fs_set_attributes(self->fs, attributes))) {
+ PyErr_SetString(PyExc_MemoryError, MEMORY_ERR);
+ return rc;
+ }
+
+ mnt_fs_set_freq(self->fs, freq);
+ mnt_fs_set_passno(self->fs, passno);
+ mnt_fs_set_userdata(self->fs, self); /* store a pointer to self, convenient when resetting the table */
+ return 0;
+}
+
+/*
+ * missing:
+ * attribute
+ * option
+ */
+static PyGetSetDef Fs_getseters[] = {
+ {"id", (getter)Fs_get_id, NULL, "mountinfo[1]: ID", NULL},
+ {"parent", (getter)Fs_get_parent_id, NULL, "mountinfo[2]: parent", NULL},
+ {"devno", (getter)Fs_get_devno, NULL, "mountinfo[3]: st_dev", NULL},
+ {"comment", (getter)Fs_get_comment, (setter)Fs_set_comment, "fstab entry comment", NULL},
+ {"source", (getter)Fs_get_source, (setter)Fs_set_source, "fstab[1], mountinfo[10], swaps[1]: source dev, file, dir or TAG", NULL},
+ {"srcpath", (getter)Fs_get_srcpath, NULL, "mount source path or NULL in case of error or when the path is not defined.", NULL},
+ {"root", (getter)Fs_get_root, (setter)Fs_set_root, "mountinfo[4]: root of the mount within the FS", NULL},
+ {"target", (getter)Fs_get_target, (setter)Fs_set_target, "mountinfo[5], fstab[2]: mountpoint", NULL},
+ {"fstype", (getter)Fs_get_fstype, (setter)Fs_set_fstype, "mountinfo[9], fstab[3]: filesystem type", NULL},
+ {"options", (getter)Fs_get_options, (setter)Fs_set_options, "fstab[4]: merged options", NULL},
+ {"vfs_options", (getter)Fs_get_vfs_options, NULL, "mountinfo[6]: fs-independent (VFS) options", NULL},
+ {"opt_fields", (getter)Fs_get_optional_fields, NULL, "mountinfo[7]: optional fields", NULL},
+ {"fs_options", (getter)Fs_get_fs_options, NULL, "mountinfo[11]: fs-dependent options", NULL},
+ {"usr_options", (getter)Fs_get_user_options, NULL, "userspace mount options", NULL},
+ {"attributes", (getter)Fs_get_attributes, (setter)Fs_set_attributes, "mount attributes", NULL},
+ {"freq", (getter)Fs_get_freq, (setter)Fs_set_freq, "fstab[5]: dump frequency in days", NULL},
+ {"passno", (getter)Fs_get_passno, (setter)Fs_set_passno, "fstab[6]: pass number on parallel fsck", NULL},
+ {"swaptype", (getter)Fs_get_swaptype, NULL, "swaps[2]: device type", NULL},
+ {"size", (getter)Fs_get_size, NULL, "saps[3]: swaparea size", NULL},
+ {"usedsize", (getter)Fs_get_usedsize, NULL, "swaps[4]: used size", NULL},
+ {"priority", (getter)Fs_get_priority, NULL, "swaps[5]: swap priority", NULL},
+ {"tag", (getter)Fs_get_tag, NULL, "(Name, Value)", NULL},
+ {"tid", (getter)Fs_get_tid, NULL, "/proc/<tid>/mountinfo, otherwise zero", NULL},
+ {NULL}
+};
+
+static PyObject *Fs_repr(FsObject *self)
+{
+ const char *src = mnt_fs_get_source(self->fs),
+ *tgt = mnt_fs_get_target(self->fs),
+ *type = mnt_fs_get_fstype(self->fs);
+
+ return PyUnicode_FromFormat(
+ "<libmount.Fs object at %p, "
+ "source=%s, target=%s, fstype=%s>",
+ self,
+ src ? src : "None",
+ tgt ? tgt : "None",
+ type ? type : "None");
+}
+
+PyObject *PyObjectResultFs(struct libmnt_fs *fs)
+{
+ FsObject *result;
+
+ if (!fs) {
+ PyErr_SetString(LibmountError, "internal exception");
+ return NULL;
+ }
+
+ result = mnt_fs_get_userdata(fs);
+ if (result) {
+ Py_INCREF(result);
+ DBG(FS, pymnt_debug_h(fs, "result py-obj %p: already exists, py-refcnt=%d",
+ result, (int) ((PyObject *) result)->ob_refcnt));
+ return (PyObject *) result;
+ }
+
+ /* Creating an encapsulating object: increment the refcount, so that code
+ * such as tab.next_fs() doesn't call the destructor, which would free
+ * our fs struct as well
+ */
+ result = PyObject_New(FsObject, &FsType);
+ if (!result) {
+ UL_RaiseExc(ENOMEM);
+ return NULL;
+ }
+
+ Py_INCREF(result);
+ mnt_ref_fs(fs);
+
+ DBG(FS, pymnt_debug_h(fs, "result py-obj %p new, py-refcnt=%d",
+ result, (int) ((PyObject *) result)->ob_refcnt));
+ result->fs = fs;
+ mnt_fs_set_userdata(fs, result);
+ return (PyObject *) result;
+}
+
+static PyObject *Fs_copy_fs(FsObject *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *dest = NULL;
+ char *kwlist[] = {"dest", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &dest)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ if (PyObject_TypeCheck(dest, &FsType)) { /* existing object passed as argument */
+ if (!mnt_copy_fs(((FsObject *)dest)->fs, self->fs))
+ return NULL;
+ DBG(FS, pymnt_debug_h(dest, "copy data"));
+ return (PyObject *)dest;
+
+ }
+
+ if (dest == Py_None) { /* create new object */
+ FsObject *result = PyObject_New(FsObject, &FsType);
+
+ DBG(FS, pymnt_debug_h(result, "new copy"));
+ result->fs = mnt_copy_fs(NULL, self->fs);
+ mnt_fs_set_userdata(result->fs, result); /* keep a pointer to encapsulating object */
+ return (PyObject *)result;
+ }
+
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+}
+
+
+PyTypeObject FsType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "libmount.Fs", /*tp_name*/
+ sizeof(FsObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Fs_destructor, /*tp_dealloc*/
+ 0, /*tp_print*/
+ NULL, /*tp_getattr*/
+ NULL, /*tp_setattr*/
+ NULL, /*tp_compare*/
+ (reprfunc)Fs_repr, /*tp_repr*/
+ NULL, /*tp_as_number*/
+ NULL, /*tp_as_sequence*/
+ NULL, /*tp_as_mapping*/
+ NULL, /*tp_hash */
+ NULL, /*tp_call*/
+ NULL, /*tp_str*/
+ NULL, /*tp_getattro*/
+ NULL, /*tp_setattro*/
+ NULL, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ Fs_HELP, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ Fs_methods, /* tp_methods */
+ Fs_members, /* tp_members */
+ Fs_getseters, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Fs_init, /* tp_init */
+ NULL, /* tp_alloc */
+ Fs_new, /* tp_new */
+};
+
+void FS_AddModuleObject(PyObject *mod)
+{
+ if (PyType_Ready(&FsType) < 0)
+ return;
+
+ DBG(FS, pymnt_debug("add to module"));
+ Py_INCREF(&FsType);
+ PyModule_AddObject(mod, "Fs", (PyObject *)&FsType);
+}
+
diff --git a/libmount/python/meson.build b/libmount/python/meson.build
new file mode 100644
index 0000000..59d61d1
--- /dev/null
+++ b/libmount/python/meson.build
@@ -0,0 +1,34 @@
+python_module = import('python')
+
+python = python_module.find_installation(
+ get_option('python'),
+ required : get_option('build-python'),
+ disabler : true)
+build_python = python.found()
+
+pylibmount_sources = '''
+ pylibmount.c
+ pylibmount.h
+ fs.c
+ tab.c
+'''.split()
+
+if LINUX
+ pylibmount_sources += 'context.c'
+endif
+
+python.extension_module(
+ 'pylibmount',
+ pylibmount_sources,
+ include_directories : [dir_include, dir_libmount],
+ subdir : 'libmount',
+ link_with : lib_mount,
+ dependencies : python.dependency(),
+ install : true)
+
+if build_python
+ python.install_sources(
+ '__init__.py',
+ subdir : 'libmount',
+ pure : false)
+endif
diff --git a/libmount/python/pylibmount.c b/libmount/python/pylibmount.c
new file mode 100644
index 0000000..bfc100f
--- /dev/null
+++ b/libmount/python/pylibmount.c
@@ -0,0 +1,326 @@
+/*
+ * Python bindings for the libmount library.
+ *
+ * Copyright (C) 2013, Red Hat, Inc. All rights reserved.
+ * Written by Ondrej Oprala and Karel Zak
+ *
+ * This file 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 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "pylibmount.h"
+
+/* Libmount-specific Exception class */
+PyObject *LibmountError;
+int pylibmount_debug_mask;
+
+PyObject *UL_IncRef(void *killme)
+{
+ Py_INCREF(killme);
+ return killme;
+}
+
+void PyFree(void *o)
+{
+#if PY_MAJOR_VERSION >= 3
+ Py_TYPE(o)->tp_free((PyObject *)o);
+#else
+ ((PyObject *)o)->ob_type->tp_free((PyObject *)o);
+#endif
+}
+
+/* Demultiplexer for various possible error conditions across the libmount library */
+void *UL_RaiseExc(int e)
+{
+ /* TODO: Do we need to deal with -1/1? */
+ switch (e) {
+ case ENOMEM:
+ PyErr_SetString(PyExc_MemoryError, strerror(e));
+ break;
+ case EINVAL:
+ PyErr_SetString(PyExc_TypeError, strerror(e));
+ break;
+ /* libmount-specific errors */
+ case MNT_ERR_NOFSTAB:
+ PyErr_SetString(LibmountError, "Not found required entry in fstab");
+ break;
+ case MNT_ERR_NOFSTYPE:
+ PyErr_SetString(LibmountError, "Lailed to detect filesystem type");
+ break;
+ case MNT_ERR_NOSOURCE:
+ PyErr_SetString(LibmountError, "Required mount source undefined");
+ break;
+ case MNT_ERR_LOOPDEV:
+ PyErr_SetString(LibmountError, "Loopdev setup failed");
+ break;
+ case MNT_ERR_APPLYFLAGS:
+ PyErr_SetString(LibmountError, "Failed to parse/use userspace mount options");
+ break;
+ case MNT_ERR_MOUNTOPT:
+ PyErr_SetString(LibmountError, "Failed to apply propagation flags");
+ break;
+ case MNT_ERR_AMBIFS:
+ PyErr_SetString(LibmountError, "Libblkid detected more filesystems on the device");
+ break;
+ case MNT_ERR_LOOPOVERLAP:
+ PyErr_SetString(LibmountError, "Detected overlapping loop device that cannot be re-use");
+ break;
+ case MNT_ERR_LOCK:
+ PyErr_SetString(LibmountError, "Failed to lock mtab/utab or so");
+ break;
+ case MNT_ERR_NAMESPACE:
+ PyErr_SetString(LibmountError, "Failed to switch namespace");
+ break;
+ /* some other errno */
+ default:
+ PyErr_SetString(PyExc_Exception, strerror(e));
+ break;
+ }
+ return NULL;
+}
+
+/*
+ * General functions
+ */
+PyObject *PyObjectResultInt(int i)
+{
+ PyObject *result = Py_BuildValue("i", i);
+ if (!result)
+ PyErr_SetString(PyExc_RuntimeError, CONSTRUCT_ERR);
+ return result;
+}
+
+PyObject *PyObjectResultStr(const char *s)
+{
+ PyObject *result;
+ if (!s)
+ /* TODO: maybe lie about it and return "":
+ * which would allow for
+ * fs = libmount.Fs()
+ * fs.comment += "comment"
+ return Py_BuildValue("s", ""); */
+ Py_RETURN_NONE;
+ result = Py_BuildValue("s", s);
+ if (!result)
+ PyErr_SetString(PyExc_RuntimeError, CONSTRUCT_ERR);
+ return result;
+}
+
+/* wrapper around a common use case for PyUnicode_AsASCIIString() */
+char *pystos(PyObject *pys)
+{
+#if PY_MAJOR_VERSION >= 3
+ if (!PyUnicode_Check(pys)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return (char *)PyUnicode_1BYTE_DATA(pys);
+#else
+ if (!PyString_Check(pys)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyString_AsString(pys);
+#endif
+}
+
+/*
+ * the libmount module
+ */
+#define PYLIBMOUNT_DESC \
+ "Python API for the util-linux libmount library.\n\n" \
+ "Please note that none of the classes' attributes may be deleted.\n" \
+ "This is not a complete mapping to the libmount C API, nor is it\n" \
+ "attempting to be one.\n" "Iterator functions only allow forward\n" \
+ "iteration for now. Context.get_{user_,}mflags() differs from the C API\n" \
+ "and returns the flags directly. Fs.get_tag() differs from the C API\n" \
+ "and returns a (tag, value) tuple. Every attribute is \"filtered\"" \
+ "through appropriate getters/setters, no values are set directly."
+
+
+struct module_state {
+ PyObject *error;
+};
+
+#if PY_MAJOR_VERSION >= 3
+#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
+#else
+#define GETSTATE(m) (&_state)
+static struct module_state _state;
+#endif
+
+static PyObject *
+error_out(PyObject *m __attribute__((unused))) {
+ struct module_state *st = GETSTATE(m);
+ PyErr_SetString(st->error, "something bad happened");
+ return NULL;
+}
+
+static PyMethodDef pylibmount_methods[] = {
+ {"error_out", (PyCFunction)error_out, METH_NOARGS, NULL},
+ {NULL, NULL}
+};
+
+#if PY_MAJOR_VERSION >= 3
+
+static int pylibmount_traverse(PyObject *m, visitproc visit, void *arg) {
+ Py_VISIT(GETSTATE(m)->error);
+ return 0;
+}
+
+static int pylibmount_clear(PyObject *m) {
+ Py_CLEAR(GETSTATE(m)->error);
+ return 0;
+}
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "pylibmount",
+ NULL,
+ sizeof(struct module_state),
+ pylibmount_methods,
+ NULL,
+ pylibmount_traverse,
+ pylibmount_clear,
+ NULL
+};
+#define INITERROR return NULL
+PyObject * PyInit_pylibmount(void);
+PyObject * PyInit_pylibmount(void)
+#else
+#define INITERROR return
+# ifndef PyMODINIT_FUNC
+# define PyMODINIT_FUNC void
+# endif
+PyMODINIT_FUNC initpylibmount(void);
+PyMODINIT_FUNC initpylibmount(void)
+#endif
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *m = PyModule_Create(&moduledef);
+#else
+ PyObject *m = Py_InitModule3("pylibmount", pylibmount_methods, PYLIBMOUNT_DESC);
+#endif
+
+ if (!m)
+ INITERROR;
+ /*
+ * init debug stuff
+ */
+ if (!(pylibmount_debug_mask & PYMNT_DEBUG_INIT)) {
+ char *str = getenv("PYLIBMOUNT_DEBUG");
+
+ errno = 0;
+ pylibmount_debug_mask = 0;
+ if (str)
+ pylibmount_debug_mask = strtoul(str, NULL, 0);
+ if (errno)
+ pylibmount_debug_mask = 0;
+
+ pylibmount_debug_mask |= PYMNT_DEBUG_INIT;
+ }
+
+ if (pylibmount_debug_mask && pylibmount_debug_mask != PYMNT_DEBUG_INIT)
+ DBG(INIT, pymnt_debug("library debug mask: 0x%04x",
+ pylibmount_debug_mask));
+ mnt_init_debug(0);
+
+ /*
+ * Add module objects
+ */
+ LibmountError = PyErr_NewException("libmount.Error", NULL, NULL);
+ Py_INCREF(LibmountError);
+ PyModule_AddObject(m, "Error", (PyObject *)LibmountError);
+
+ FS_AddModuleObject(m);
+ Table_AddModuleObject(m);
+#ifdef __linux__
+ Context_AddModuleObject(m);
+#endif
+
+ /*
+ * mount(8) userspace options masks (MNT_MAP_USERSPACE map)
+ */
+ PyModule_AddIntConstant(m, "MNT_MS_COMMENT", MNT_MS_COMMENT);
+ PyModule_AddIntConstant(m, "MNT_MS_GROUP", MNT_MS_GROUP);
+ PyModule_AddIntConstant(m, "MNT_MS_HELPER", MNT_MS_HELPER);
+ PyModule_AddIntConstant(m, "MNT_MS_LOOP", MNT_MS_LOOP);
+ PyModule_AddIntConstant(m, "MNT_MS_NETDEV", MNT_MS_NETDEV);
+ PyModule_AddIntConstant(m, "MNT_MS_NOAUTO", MNT_MS_NOAUTO);
+ PyModule_AddIntConstant(m, "MNT_MS_NOFAIL", MNT_MS_NOFAIL);
+ PyModule_AddIntConstant(m, "MNT_MS_OFFSET", MNT_MS_OFFSET);
+ PyModule_AddIntConstant(m, "MNT_MS_OWNER", MNT_MS_OWNER);
+ PyModule_AddIntConstant(m, "MNT_MS_SIZELIMIT", MNT_MS_SIZELIMIT);
+ PyModule_AddIntConstant(m, "MNT_MS_ENCRYPTION", MNT_MS_ENCRYPTION);
+ PyModule_AddIntConstant(m, "MNT_MS_UHELPER", MNT_MS_UHELPER);
+ PyModule_AddIntConstant(m, "MNT_MS_USER", MNT_MS_USER);
+ PyModule_AddIntConstant(m, "MNT_MS_USERS", MNT_MS_USERS);
+ PyModule_AddIntConstant(m, "MNT_MS_XCOMMENT", MNT_MS_XCOMMENT);
+ PyModule_AddIntConstant(m, "MNT_MS_HASH_DEVICE", MNT_MS_HASH_DEVICE);
+ PyModule_AddIntConstant(m, "MNT_MS_ROOT_HASH", MNT_MS_ROOT_HASH);
+ PyModule_AddIntConstant(m, "MNT_MS_HASH_OFFSET", MNT_MS_HASH_OFFSET);
+ PyModule_AddIntConstant(m, "MNT_MS_ROOT_HASH_FILE", MNT_MS_ROOT_HASH_FILE);
+ PyModule_AddIntConstant(m, "MNT_MS_FEC_DEVICE", MNT_MS_FEC_DEVICE);
+ PyModule_AddIntConstant(m, "MNT_MS_FEC_OFFSET", MNT_MS_FEC_OFFSET);
+ PyModule_AddIntConstant(m, "MNT_MS_FEC_ROOTS", MNT_MS_FEC_ROOTS);
+ PyModule_AddIntConstant(m, "MNT_MS_ROOT_HASH_SIG", MNT_MS_ROOT_HASH_SIG);
+
+ /*
+ * mount(2) MS_* masks (MNT_MAP_LINUX map)
+ */
+ PyModule_AddIntConstant(m, "MS_BIND", MS_BIND);
+ PyModule_AddIntConstant(m, "MS_DIRSYNC", MS_DIRSYNC);
+ PyModule_AddIntConstant(m, "MS_I_VERSION", MS_I_VERSION);
+ PyModule_AddIntConstant(m, "MS_MANDLOCK", MS_MANDLOCK);
+ PyModule_AddIntConstant(m, "MS_MGC_MSK", MS_MGC_MSK);
+ PyModule_AddIntConstant(m, "MS_MGC_VAL", MS_MGC_VAL);
+ PyModule_AddIntConstant(m, "MS_MOVE", MS_MOVE);
+ PyModule_AddIntConstant(m, "MS_NOATIME", MS_NOATIME);
+ PyModule_AddIntConstant(m, "MS_NODEV", MS_NODEV);
+ PyModule_AddIntConstant(m, "MS_NODIRATIME", MS_NODIRATIME);
+ PyModule_AddIntConstant(m, "MS_NOEXEC", MS_NOEXEC);
+ PyModule_AddIntConstant(m, "MS_NOSUID", MS_NOSUID);
+ PyModule_AddIntConstant(m, "MS_OWNERSECURE", MS_OWNERSECURE);
+ PyModule_AddIntConstant(m, "MS_PRIVATE", MS_PRIVATE);
+ PyModule_AddIntConstant(m, "MS_PROPAGATION", MS_PROPAGATION);
+ PyModule_AddIntConstant(m, "MS_RDONLY", MS_RDONLY);
+ PyModule_AddIntConstant(m, "MS_REC", MS_REC);
+ PyModule_AddIntConstant(m, "MS_RELATIME", MS_RELATIME);
+ PyModule_AddIntConstant(m, "MS_REMOUNT", MS_REMOUNT);
+ PyModule_AddIntConstant(m, "MS_SECURE", MS_SECURE);
+ PyModule_AddIntConstant(m, "MS_SHARED", MS_SHARED);
+ PyModule_AddIntConstant(m, "MS_SILENT", MS_SILENT);
+ PyModule_AddIntConstant(m, "MS_SLAVE", MS_SLAVE);
+ PyModule_AddIntConstant(m, "MS_STRICTATIME", MS_STRICTATIME);
+ PyModule_AddIntConstant(m, "MS_SYNCHRONOUS", MS_SYNCHRONOUS);
+ PyModule_AddIntConstant(m, "MS_UNBINDABLE", MS_UNBINDABLE);
+
+ /* Will we need these directly?
+ PyModule_AddIntConstant(m, "MNT_ERR_AMBIFS", MNT_ERR_AMBIFS);
+ PyModule_AddIntConstant(m, "MNT_ERR_APPLYFLAGS", MNT_ERR_APPLYFLAGS);
+ PyModule_AddIntConstant(m, "MNT_ERR_LOOPDEV", MNT_ERR_LOOPDEV);
+ PyModule_AddIntConstant(m, "MNT_ERR_MOUNTOPT", MNT_ERR_MOUNTOPT);
+ PyModule_AddIntConstant(m, "MNT_ERR_NOFSTAB", MNT_ERR_NOFSTAB);
+ PyModule_AddIntConstant(m, "MNT_ERR_NOFSTYPE", MNT_ERR_NOFSTYPE);
+ PyModule_AddIntConstant(m, "MNT_ERR_NOSOURCE", MNT_ERR_NOSOURCE);
+ */
+
+ /* Still useful for functions using iterators internally */
+ PyModule_AddIntConstant(m, "MNT_ITER_FORWARD", MNT_ITER_FORWARD);
+ PyModule_AddIntConstant(m, "MNT_ITER_BACKWARD", MNT_ITER_BACKWARD);
+
+#if PY_MAJOR_VERSION >= 3
+ return m;
+#endif
+}
+
diff --git a/libmount/python/pylibmount.h b/libmount/python/pylibmount.h
new file mode 100644
index 0000000..bf3278d
--- /dev/null
+++ b/libmount/python/pylibmount.h
@@ -0,0 +1,131 @@
+#ifndef UTIL_LINUX_PYLIBMOUNT_H
+#define UTIL_LINUX_PYLIBMOUNT_H
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "c.h"
+#include "libmount.h"
+
+#define CONFIG_PYLIBMOUNT_DEBUG
+
+#define PYMNT_DEBUG_INIT (1 << 1)
+#define PYMNT_DEBUG_TAB (1 << 2)
+#define PYMNT_DEBUG_FS (1 << 3)
+#define PYMNT_DEBUG_CXT (1 << 4)
+
+#ifdef CONFIG_PYLIBMOUNT_DEBUG
+# include <stdio.h>
+# include <stdarg.h>
+
+# define DBG(m, x) do { \
+ if ((PYMNT_DEBUG_ ## m) & pylibmount_debug_mask) { \
+ fprintf(stderr, "%d: pylibmount: %6s: ", getpid(), # m); \
+ x; \
+ } \
+ } while (0)
+
+extern int pylibmount_debug_mask;
+
+static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
+pymnt_debug(const char *mesg, ...)
+{
+ va_list ap;
+ va_start(ap, mesg);
+ vfprintf(stderr, mesg, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+static inline void __attribute__ ((__format__ (__printf__, 2, 3)))
+pymnt_debug_h(void *handler, const char *mesg, ...)
+{
+ va_list ap;
+
+ if (handler)
+ fprintf(stderr, "[%p]: ", handler);
+ va_start(ap, mesg);
+ vfprintf(stderr, mesg, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+#else /* !CONFIG_PYLIBMOUNT_DEBUG */
+# define DBG(m,x) do { ; } while (0)
+#endif
+
+
+#define NODEL_ATTR "This attribute cannot be deleted"
+#define CONSTRUCT_ERR "Error during object construction"
+#define ARG_ERR "Invalid number or type of arguments"
+#define NOFS_ERR "No filesystems to mount"
+#define MEMORY_ERR strerror(ENOMEM)
+#define CONV_ERR "Type conversion failed"
+
+/*
+ * fs.c
+ */
+typedef struct {
+ PyObject_HEAD
+ struct libmnt_fs *fs;
+} FsObject;
+
+extern PyTypeObject FsType;
+
+extern PyObject *PyObjectResultFs(struct libmnt_fs *fs);
+extern void FS_AddModuleObject(PyObject *mod);
+
+/*
+ * tab.c
+ */
+typedef struct {
+ PyObject_HEAD
+
+ struct libmnt_table *tab;
+ struct libmnt_iter *iter;
+ PyObject *errcb;
+} TableObject;
+
+extern PyTypeObject TableType;
+
+extern PyObject *PyObjectResultTab(struct libmnt_table *tab);
+
+extern void Table_unref(struct libmnt_table *tab);
+extern void Table_AddModuleObject(PyObject *mod);
+
+extern int pymnt_table_parser_errcb(struct libmnt_table *tb, const char *filename, int line);
+
+#ifdef __linux__
+
+/*
+ * context.c
+ */
+typedef struct {
+ PyObject_HEAD
+
+ struct libmnt_context *cxt;
+ PyObject *table_errcb;
+
+} ContextObjext;
+
+extern PyTypeObject ContextType;
+extern void Context_AddModuleObject(PyObject *mod);
+
+#endif /* __linux__ */
+
+/*
+ * misc
+ */
+extern PyObject *LibmountError;
+extern PyObject *UL_IncRef(void *killme);
+extern void *UL_RaiseExc(int e);
+
+extern PyObject *PyObjectResultInt(int i);
+extern PyObject *PyObjectResultStr(const char *s);
+
+extern char *pystos(PyObject *pys);
+extern void PyFree(void *o);
+
+
+
+#endif /* UTIL_LINUX_PYLIBMOUNT */
diff --git a/libmount/python/tab.c b/libmount/python/tab.c
new file mode 100644
index 0000000..000bc13
--- /dev/null
+++ b/libmount/python/tab.c
@@ -0,0 +1,783 @@
+/*
+ * Python bindings for the libmount library.
+ *
+ * Copyright (C) 2013, Red Hat, Inc. All rights reserved.
+ * Written by Ondrej Oprala and Karel Zak
+ *
+ * This file 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 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "pylibmount.h"
+
+static PyMemberDef Table_members[] = {
+ { NULL }
+};
+
+static int Table_set_parser_errcb(TableObject *self, PyObject *func,
+ void *closure __attribute__((unused)))
+{
+ PyObject *tmp;
+
+ if (!func) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+
+ if (!PyCallable_Check(func))
+ return -1;
+
+ tmp = self->errcb;
+ Py_INCREF(func);
+ self->errcb = func;
+ Py_XDECREF(tmp);
+ return 0;
+}
+
+static PyObject *Table_get_intro_comment(TableObject *self,
+ void *closure __attribute__((unused)))
+{
+ return PyObjectResultStr(mnt_table_get_intro_comment(self->tab));
+}
+
+static int Table_set_intro_comment(TableObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ char *comment = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(comment = pystos(value)))
+ return -1;
+
+ if ((rc = mnt_table_set_intro_comment(self->tab, comment))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *Table_get_trailing_comment(TableObject *self,
+ void *closure __attribute__((unused)))
+{
+ return PyObjectResultStr(mnt_table_get_trailing_comment(self->tab));
+}
+
+static int Table_set_trailing_comment(TableObject *self, PyObject *value,
+ void *closure __attribute__((unused)))
+{
+ char *comment = NULL;
+ int rc = 0;
+
+ if (!value) {
+ PyErr_SetString(PyExc_TypeError, NODEL_ATTR);
+ return -1;
+ }
+ if (!(comment = pystos(value)))
+ return -1;
+
+ if ((rc = mnt_table_set_trailing_comment(self->tab, comment))) {
+ UL_RaiseExc(-rc);
+ return -1;
+ }
+ return 0;
+}
+
+#define Table_enable_comments_HELP "enable_comments(enable)\n\n" \
+ "Enables parsing of comments.\n\n" \
+ "The initial (intro) file comment is accessible by\n" \
+ "Tab.intro_comment. The intro and the comment of the first fstab" \
+ "entry has to be separated by blank line. The filesystem comments are\n" \
+ "accessible by Fs.comment. The tailing fstab comment is accessible\n" \
+ "by Tab.trailing_comment.\n" \
+ "\n" \
+ "<informalexample>\n" \
+ "<programlisting>\n" \
+ "#\n" \
+ "# Intro comment\n" \
+ "#\n" \
+ "\n" \
+ "# this comments belongs to the first fs\n" \
+ "LABEL=foo /mnt/foo auto defaults 1 2\n" \
+ "# this comments belongs to the second fs\n" \
+ "LABEL=bar /mnt/bar auto defaults 1 2 \n" \
+ "# tailing comment\n" \
+ "</programlisting>\n" \
+ "</informalexample>"
+static PyObject *Table_enable_comments(TableObject *self, PyObject *args,
+ PyObject *kwds)
+{
+ int enable = 0;
+ char *kwlist[] = {"enable", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &enable)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ mnt_table_enable_comments(self->tab, enable);
+ Py_INCREF(self);
+ return (PyObject *)self;
+}
+
+#define Table_replace_file_HELP "replace_file(filename)\n\n" \
+ "This function replaces filename with the new content from TableObject."
+static PyObject *Table_replace_file(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ char *filename = NULL;
+ char *kwlist[] = {"filename", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &filename)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_replace_file(self->tab, filename);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_write_file_HELP "write_file(path)\n\n" \
+ "This function writes tab to file(stream)"
+static PyObject *Table_write_file(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ int rc;
+ //PyObject *stream = NULL;
+ FILE *f = NULL;
+ char *path = NULL;
+ char *kwlist[] = {"path", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist,
+ &path)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ if (!(f = fopen(path, "w")))
+ return UL_RaiseExc(errno);
+ rc = mnt_table_write_file(self->tab, f);
+ fclose(f);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_find_devno_HELP "find_devno(devno, [direction])\n\n" \
+ "Note that zero could be valid device number for root pseudo " \
+ "filesystem (e.g. tmpfs)\n" \
+ "Returns a tab entry or None"
+static PyObject *Table_find_devno(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ dev_t devno;
+ int direction = MNT_ITER_BACKWARD;
+ char *kwlist[] = {"devno", "direction", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I|i", kwlist, &devno, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_devno(self->tab, devno, direction));
+}
+
+#define Table_find_mountpoint_HELP "find_mountpoint(path, [direction])\n\n" \
+ "Returns a tab entry or None."
+static PyObject *Table_find_mountpoint(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *path;
+ int direction = MNT_ITER_BACKWARD;
+ char *kwlist[] = {"path", "direction", NULL};
+
+ if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &path, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_mountpoint(self->tab, path, direction));
+}
+
+#define Table_find_pair_HELP "find_pair(source, target, [direction])\n\n" \
+ "Returns a tab entry or None."
+static PyObject *Table_find_pair(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"source", "target", "direction", NULL};
+ char *source;
+ char *target;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|i", kwlist, &source, &target, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_pair(self->tab, source, target, direction));
+}
+
+#define Table_find_source_HELP "find_source(source, [direction])\n\n" \
+ "Returns a tab entry or None."
+static PyObject *Table_find_source(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"source", "direction", NULL};
+ char *source;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &source, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_source(self->tab, source, direction));
+}
+
+#define Table_find_target_HELP "find_target(target, [direction])\n\n" \
+ "Try to lookup an entry in given tab, possible are three iterations, first\n" \
+ "with path, second with realpath(path) and third with realpath(path)\n" \
+ "against realpath(fs->target). The 2nd and 3rd iterations are not performed\n" \
+ "when tb cache is not set (cache not implemented yet).\n" \
+ "\n" \
+ "Returns a tab entry or None."
+static PyObject *Table_find_target(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"target", "direction", NULL};
+ char *target;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &target, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_target(self->tab, target, direction));
+}
+
+#define Table_find_srcpath_HELP "find_srcpath(srcpath, [direction])\n\n" \
+ "Try to lookup an entry in given tab, possible are four iterations, first\n" \
+ "with path, second with realpath(path), third with tags (LABEL, UUID, ..)\n" \
+ "from path and fourth with realpath(path) against realpath(entry->srcpath).\n" \
+ "\n" \
+ "The 2nd, 3rd and 4th iterations are not performed when tb cache is not\n" \
+ "set (not implemented yet).\n" \
+ "\n" \
+ "Note that None is a valid source path; it will be replaced with \"none\". The\n" \
+ "\"none\" is used in /proc/{mounts,self/mountinfo} for pseudo filesystems.\n" \
+ "\n" \
+ "Returns a tab entry or None."
+static PyObject *Table_find_srcpath(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"srcpath", "direction", NULL};
+ char *srcpath;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &srcpath, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_srcpath(self->tab, srcpath, direction));
+}
+
+#define Table_find_tag_HELP "find_tag(tag, val, [direction])\n\n" \
+ "Try to lookup an entry in given tab, first attempt is lookup by tag and\n" \
+ "val, for the second attempt the tag is evaluated (converted to the device\n" \
+ "name) and Tab.find_srcpath() is performed. The second attempt is not\n" \
+ "performed when tb cache is not set (not implemented yet).\n" \
+ "\n" \
+ "Returns a tab entry or NULL."
+static PyObject *Table_find_tag(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"tag", "val", "direction", NULL};
+ char *tag;
+ char *val;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|i", kwlist, &tag, &val, &direction)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyObjectResultFs(mnt_table_find_tag(self->tab, tag, val, direction));
+}
+
+static PyObject *Table_get_nents(TableObject *self)
+{
+ return PyObjectResultInt(mnt_table_get_nents(self->tab));
+}
+
+#define Table_is_fs_mounted_HELP "is_fs_mounted(fstab_fs)\n\n" \
+ "Checks if the fstab_fs entry is already in the tb table. The \"swap\" is\n" \
+ "ignored. This function explicitly compares source, target and root of the\n" \
+ "filesystems.\n" \
+ "\n" \
+ "Note that source and target are canonicalized only if a cache for tb is\n" \
+ "defined (not implemented yet). The target canonicalization may\n" \
+ "trigger automount on autofs mountpoints!\n" \
+ "\n" \
+ "Don't use it if you want to know if a device is mounted, just use\n" \
+ "Tab.find_source() for the device.\n" \
+ "\n" \
+ "This function is designed mostly for \"mount -a\".\n" \
+ "\n" \
+ "Returns a boolean value."
+static PyObject *Table_is_fs_mounted(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ FsObject *fs;
+ char *kwlist[] = {"fstab_fs", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!", kwlist, &FsType, &fs)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ return PyBool_FromLong(mnt_table_is_fs_mounted(self->tab, fs->fs));
+}
+
+#define Table_parse_file_HELP "parse_file(file)\n\n" \
+ "Parses whole table (e.g. /etc/mtab) and appends new records to the tab.\n" \
+ "\n" \
+ "The libmount parser ignores broken (syntax error) lines, these lines are\n" \
+ "reported to caller by errcb() function (see Tab.parser_errcb).\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Table_parse_file(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ char *file = NULL;
+ char *kwlist[] = {"file", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &file)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_parse_file(self->tab, file);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_parse_fstab_HELP "parse_fstab([fstab])\n\n" \
+ "This function parses /etc/fstab and appends new lines to the tab. If the\n" \
+ "filename is a directory then Tab.parse_dir() is called.\n" \
+ "\n" \
+ "See also Tab.parser_errcb.\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Table_parse_fstab(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ char *fstab = NULL;
+ char *kwlist[] = {"fstab", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &fstab)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_parse_fstab(self->tab, fstab);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_parse_mtab_HELP "parse_mtab([mtab])\n\n" \
+ "This function parses /etc/mtab or /proc/self/mountinfo\n" \
+ "/run/mount/utabs or /proc/mounts.\n" \
+ "\n" \
+ "See also Tab.parser_errcb().\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Table_parse_mtab(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ char *mtab = NULL;
+ char *kwlist[] = {"mtab", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &mtab)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_parse_mtab(self->tab, mtab);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_parse_dir_HELP "parse_dir(dir)\n\n" \
+ "The directory:\n" \
+ "- files are sorted by strverscmp(3)\n" \
+ "- files that start with \".\" are ignored (e.g. \".10foo.fstab\")\n" \
+ "- files without the \".fstab\" extension are ignored\n" \
+ "\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Table_parse_dir(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ char *dir = NULL;
+ char *kwlist[] = {"dir", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &dir)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_parse_dir(self->tab, dir);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_parse_swaps_HELP "parse_swaps(swaps)\n\n" \
+ "This function parses /proc/swaps and appends new lines to the tab"
+static PyObject *Table_parse_swaps(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ char *swaps = NULL;
+ char *kwlist[] = {"swaps", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &swaps)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_parse_swaps(self->tab, swaps);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_add_fs_HELP "add_fs(fs)\n\nAdds a new entry to tab.\n" \
+ "Returns self or raises an exception in case of an error."
+
+static PyObject *Table_add_fs(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ FsObject *fs = NULL;
+ char *kwlist[] = {"fs", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!", kwlist, &FsType, &fs)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ Py_INCREF(fs);
+ rc = mnt_table_add_fs(self->tab, fs->fs);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_remove_fs_HELP "remove_fs(fs)\n\n" \
+ "Returns self or raises an exception in case of an error."
+static PyObject *Table_remove_fs(TableObject *self, PyObject* args, PyObject *kwds)
+{
+ int rc;
+ FsObject *fs = NULL;
+ char *kwlist[] = {"fs", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!", kwlist, &FsType, &fs)) {
+ PyErr_SetString(PyExc_TypeError, ARG_ERR);
+ return NULL;
+ }
+ rc = mnt_table_remove_fs(self->tab, fs->fs);
+ Py_DECREF(fs);
+ return rc ? UL_RaiseExc(-rc) : UL_IncRef(self);
+}
+
+#define Table_next_fs_HELP "next_fs()\n\n" \
+ "Returns the next Fs on success, raises an exception in case " \
+ "of an error and None at end of list.\n" \
+ "\n" \
+ "Example:\n" \
+ "<informalexample>\n" \
+ "<programlisting>\n" \
+ "import libmount\n" \
+ "import functools\n" \
+ "for fs in iter(functools.partial(tb.next_fs), None):\n" \
+ " dir = Fs.target\n" \
+ " print \"mount point: {:s}\".format(dir)\n" \
+ "\n" \
+ "</programlisting>\n" \
+ "</informalexample>\n" \
+ "\n" \
+ "lists all mountpoints from fstab in backward order."
+static PyObject *Table_next_fs(TableObject *self)
+{
+ struct libmnt_fs *fs;
+ int rc;
+
+ /* Reset the builtin iterator after reaching the end of the list */
+ rc = mnt_table_next_fs(self->tab, self->iter, &fs);
+ if (rc == 1) {
+ mnt_reset_iter(self->iter, MNT_ITER_FORWARD);
+ Py_RETURN_NONE;
+ }
+
+ if (rc)
+ return UL_RaiseExc(-rc);
+
+ return PyObjectResultFs(fs);
+}
+
+static PyMethodDef Table_methods[] = {
+ {"enable_comments", (PyCFunction)Table_enable_comments, METH_VARARGS|METH_KEYWORDS, Table_enable_comments_HELP},
+ {"find_pair", (PyCFunction)Table_find_pair, METH_VARARGS|METH_KEYWORDS, Table_find_pair_HELP},
+ {"find_source", (PyCFunction)Table_find_source, METH_VARARGS|METH_KEYWORDS, Table_find_source_HELP},
+ {"find_srcpath", (PyCFunction)Table_find_srcpath, METH_VARARGS|METH_KEYWORDS, Table_find_srcpath_HELP},
+ {"find_tag", (PyCFunction)Table_find_tag, METH_VARARGS|METH_KEYWORDS, Table_find_tag_HELP},
+ {"find_target", (PyCFunction)Table_find_target, METH_VARARGS|METH_KEYWORDS, Table_find_target_HELP},
+ {"find_devno", (PyCFunction)Table_find_devno, METH_VARARGS|METH_KEYWORDS, Table_find_devno_HELP},
+ {"find_mountpoint", (PyCFunction)Table_find_mountpoint, METH_VARARGS|METH_KEYWORDS, Table_find_mountpoint_HELP},
+ {"parse_file", (PyCFunction)Table_parse_file, METH_VARARGS|METH_KEYWORDS, Table_parse_file_HELP},
+ {"parse_fstab", (PyCFunction)Table_parse_fstab, METH_VARARGS|METH_KEYWORDS, Table_parse_fstab_HELP},
+ {"parse_mtab", (PyCFunction)Table_parse_mtab, METH_VARARGS|METH_KEYWORDS, Table_parse_mtab_HELP},
+ {"parse_dir", (PyCFunction)Table_parse_dir, METH_VARARGS|METH_KEYWORDS, Table_parse_dir_HELP},
+ {"parse_swaps", (PyCFunction)Table_parse_swaps, METH_VARARGS|METH_KEYWORDS, Table_parse_swaps_HELP},
+ {"is_fs_mounted", (PyCFunction)Table_is_fs_mounted, METH_VARARGS|METH_KEYWORDS, Table_is_fs_mounted_HELP},
+ {"add_fs", (PyCFunction)Table_add_fs, METH_VARARGS|METH_KEYWORDS, Table_add_fs_HELP},
+ {"remove_fs", (PyCFunction)Table_remove_fs, METH_VARARGS|METH_KEYWORDS, Table_remove_fs_HELP},
+ {"next_fs", (PyCFunction)Table_next_fs, METH_NOARGS, Table_next_fs_HELP},
+ {"write_file", (PyCFunction)Table_write_file, METH_VARARGS|METH_KEYWORDS, Table_write_file_HELP},
+ {"replace_file", (PyCFunction)Table_replace_file, METH_VARARGS|METH_KEYWORDS, Table_replace_file_HELP},
+ {NULL}
+};
+
+/* mnt_free_tab() with a few necessary additions */
+void Table_unref(struct libmnt_table *tab)
+{
+ struct libmnt_fs *fs;
+ struct libmnt_iter *iter;
+
+ if (!tab)
+ return;
+
+ DBG(TAB, pymnt_debug_h(tab, "un-referencing filesystems"));
+
+ iter = mnt_new_iter(MNT_ITER_BACKWARD);
+
+ /* remove pylibmount specific references to the entries */
+ while (mnt_table_next_fs(tab, iter, &fs) == 0)
+ Py_XDECREF(mnt_fs_get_userdata(fs));
+
+ DBG(TAB, pymnt_debug_h(tab, "un-referencing table"));
+
+ mnt_unref_table(tab);
+ mnt_free_iter(iter);
+}
+
+static void Table_destructor(TableObject *self)
+{
+ DBG(TAB, pymnt_debug_h(self->tab, "destructor py-obj: %p, py-refcnt=%d",
+ self, (int) ((PyObject *) self)->ob_refcnt));
+ Table_unref(self->tab);
+ self->tab = NULL;
+
+ mnt_free_iter(self->iter);
+ Py_XDECREF(self->errcb);
+ PyFree(self);
+}
+
+static PyObject *Table_new(PyTypeObject *type,
+ PyObject *args __attribute__((unused)),
+ PyObject *kwds __attribute__((unused)))
+{
+ TableObject *self = (TableObject*)type->tp_alloc(type, 0);
+
+ if (self) {
+ DBG(TAB, pymnt_debug_h(self, "new"));
+
+ self->tab = NULL;
+ self->iter = NULL;
+ self->errcb = NULL;
+ }
+ return (PyObject *)self;
+}
+
+/* explicit tab.__init__() serves as mnt_reset_table(tab) would in C
+ * and as mnt_new_table{,_from_dir,_from_file}() with proper arguments */
+#define Table_HELP "Table(path=None, errcb=None)"
+static int Table_init(TableObject *self, PyObject *args, PyObject *kwds)
+{
+ struct libmnt_cache *cache;
+ char *path = NULL;
+ char *kwlist[] = {"path", "errcb", NULL};
+ PyObject *errcb = NULL;
+ struct stat buf;
+
+ memset (&buf, 0, sizeof(struct stat));
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sO",
+ kwlist, &path, &errcb))
+ return -1;
+
+ DBG(TAB, pymnt_debug_h(self, "init"));
+
+ Table_unref(self->tab);
+ self->tab = NULL;
+
+ if (self->iter)
+ mnt_reset_iter(self->iter, MNT_ITER_FORWARD);
+ else
+ self->iter = mnt_new_iter(MNT_ITER_FORWARD);
+
+ if (errcb) {
+ PyObject *tmp;
+ if (!PyCallable_Check(errcb))
+ return -1;
+ tmp = self->errcb;
+ Py_INCREF(errcb);
+ self->errcb = errcb;
+ Py_XDECREF(tmp);
+ } else {
+ Py_XDECREF(self->errcb);
+ self->errcb = NULL;
+ }
+
+ if (path) {
+ DBG(TAB, pymnt_debug_h(self, "init: path defined (%s)", path));
+
+ if (stat(path, &buf)) {
+ /* TODO: weird */
+ PyErr_SetFromErrno(PyExc_RuntimeError);
+ return -1;
+ }
+ if (S_ISREG(buf.st_mode))
+ self->tab = mnt_new_table_from_file(path);
+ else if (S_ISDIR(buf.st_mode))
+ self->tab = mnt_new_table_from_dir(path);
+ } else {
+ DBG(TAB, pymnt_debug_h(self, "init: allocate empty table"));
+ self->tab = mnt_new_table();
+ }
+
+ /* Always set custom handler when using libmount from python */
+ mnt_table_set_parser_errcb(self->tab, pymnt_table_parser_errcb);
+ mnt_table_set_userdata(self->tab, self);
+
+ cache = mnt_new_cache(); /* TODO: make it optional? */
+ if (!cache)
+ return -1;
+ mnt_table_set_cache(self->tab, cache);
+ mnt_unref_cache(cache);
+
+ return 0;
+}
+
+/* Handler for the tab->errcb callback */
+int pymnt_table_parser_errcb(struct libmnt_table *tb, const char *filename, int line)
+{
+ int rc = 0;
+ PyObject *obj;
+
+ obj = mnt_table_get_userdata(tb);
+ if (obj && ((TableObject*) obj)->errcb) {
+ PyObject *arglist, *result;
+
+ arglist = Py_BuildValue("(Osi)", obj, filename, line);
+ if (!arglist)
+ return -ENOMEM;
+
+ /* A python callback was set, so tb is definitely encapsulated in an object */
+ result = PyObject_Call(((TableObject *)obj)->errcb, arglist, NULL);
+ Py_DECREF(arglist);
+
+ if (!result)
+ return -EINVAL;
+ if (!PyArg_Parse(result, "i", &rc))
+ rc = -EINVAL;
+ Py_DECREF(result);
+ }
+ return rc;
+}
+
+PyObject *PyObjectResultTab(struct libmnt_table *tab)
+{
+ TableObject *result;
+
+ if (!tab) {
+ PyErr_SetString(LibmountError, "internal exception");
+ return NULL;
+ }
+
+ result = mnt_table_get_userdata(tab);
+ if (result) {
+ Py_INCREF(result);
+ DBG(TAB, pymnt_debug_h(tab, "result py-obj %p: already exists, py-refcnt=%d",
+ result, (int) ((PyObject *) result)->ob_refcnt));
+ return (PyObject *) result;
+ }
+
+ /* Creating an encapsulating object: increment the refcount, so that
+ * code such as: cxt.get_fstab() doesn't call the destructor, which
+ * would free our tab struct as well
+ */
+ result = PyObject_New(TableObject, &TableType);
+ if (!result) {
+ UL_RaiseExc(ENOMEM);
+ return NULL;
+ }
+
+ Py_INCREF(result);
+ mnt_ref_table(tab);
+
+ DBG(TAB, pymnt_debug_h(tab, "result py-obj %p new, py-refcnt=%d",
+ result, (int) ((PyObject *) result)->ob_refcnt));
+ result->tab = tab;
+ result->iter = mnt_new_iter(MNT_ITER_FORWARD);
+ mnt_table_set_userdata(result->tab, result);
+ result->errcb = NULL;
+ return (PyObject *) result;
+}
+
+static PyGetSetDef Table_getseters[] = {
+ {"nents", (getter)Table_get_nents, NULL, "number of valid entries in tab", NULL},
+ {"intro_comment", (getter)Table_get_intro_comment, (setter)Table_set_intro_comment, "fstab intro comment", NULL},
+ {"trailing_comment", (getter)Table_get_trailing_comment, (setter)Table_set_trailing_comment, "fstab trailing comment", NULL},
+ {"errcb", NULL, (setter)Table_set_parser_errcb, "parser error callback", NULL},
+ {NULL}
+};
+
+
+static PyObject *Table_repr(TableObject *self)
+{
+ return PyUnicode_FromFormat(
+ "<libmount.Table object at %p, entries=%d, comments_enabled=%s, errcb=%s>",
+ self,
+ mnt_table_get_nents(self->tab),
+ mnt_table_with_comments(self->tab) ? "True" : "False",
+ self->errcb ? pystos(PyObject_Repr(self->errcb)) : "None");
+}
+
+PyTypeObject TableType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "libmount.Table", /*tp_name*/
+ sizeof(TableObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Table_destructor, /*tp_dealloc*/
+ 0, /*tp_print*/
+ NULL, /*tp_getattr*/
+ NULL, /*tp_setattr*/
+ NULL, /*tp_compare*/
+ (reprfunc) Table_repr, /*tp_repr*/
+ NULL, /*tp_as_number*/
+ NULL, /*tp_as_sequence*/
+ NULL, /*tp_as_mapping*/
+ NULL, /*tp_hash */
+ NULL, /*tp_call*/
+ NULL, /*tp_str*/
+ NULL, /*tp_getattro*/
+ NULL, /*tp_setattro*/
+ NULL, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ Table_HELP, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ Table_methods, /* tp_methods */
+ Table_members, /* tp_members */
+ Table_getseters, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Table_init, /* tp_init */
+ NULL, /* tp_alloc */
+ Table_new, /* tp_new */
+};
+
+void Table_AddModuleObject(PyObject *mod)
+{
+ if (PyType_Ready(&TableType) < 0)
+ return;
+
+ DBG(TAB, pymnt_debug("add to module"));
+
+ Py_INCREF(&TableType);
+ PyModule_AddObject(mod, "Table", (PyObject *)&TableType);
+}
+
diff --git a/libmount/python/test_mount_context.py b/libmount/python/test_mount_context.py
new file mode 100755
index 0000000..7aea444
--- /dev/null
+++ b/libmount/python/test_mount_context.py
@@ -0,0 +1,173 @@
+import os
+import sys
+import stat
+import errno
+
+# use "import libmount" for in a standard way installed python binding
+import pylibmount as mnt
+
+def usage(tss):
+ print("\nUsage:\n\t{:s} <test> [testoptions]\nTests:\n".format(sys.argv[0]))
+ for i in tss:
+ print("\t{15:-s}".format(i[0]))
+ if i[2] != "":
+ print(" {:s}\n".format(i[2]))
+
+ print("\n")
+ return 1
+
+def mnt_run_test(tss, argv):
+ rc = -1
+ if ((len(argv) < 2) or (argv[1] == "--help") or (argv[1] == "-h")):
+ return usage(tss)
+
+ #mnt_init_debug(0)
+
+ i=()
+ for i in tss:
+ if i[0] == argv[1]:
+ rc = i[1](i, argv[1:])
+ if rc:
+ print("FAILED [rc={:d}]".format(rc))
+ break
+
+ if ((rc < 0) and (i == ())):
+ return usage(tss)
+ return not not rc #because !!rc is too mainstream for python
+
+def test_mount(ts, argv):
+ idx = 1
+ rc = 0
+
+ if len(argv) < 2:
+ return -errno.EINVAL
+
+ cxt = mnt.Context()
+
+ if argv[idx] == "-o":
+ cxt.options = argv[idx+1]
+ idx += 2
+ if argv[idx] == "-t":
+ cxt.fstype = argv[idx+1]
+ idx += 2
+ if len(argv) == idx + 1:
+ cxt.target = argv[idx]
+ idx+=1
+ elif (len(argv) == idx + 2):
+ cxt.source = argv[idx]
+ idx += 1
+ cxt.target = argv[idx]
+ idx += 1
+
+ try:
+ cxt.mount()
+ except Exception:
+ print("failed to mount")
+ return -1
+ print("successfully mounted")
+ return rc
+
+def test_umount(ts, argv):
+ idx = 1
+ rc = 0
+ if len(argv) < 2:
+ return -errno.EINVAL
+
+ cxt = mnt.Context()
+
+ if argv[idx] == "-t":
+ cxt.options = argv[idx+1]
+ idx += 2
+
+ if argv[idx] == "-f":
+ cxt.enable_force(True)
+
+ if argv[idx] == "-l":
+ cxt.enable_lazy(True)
+ idx += 1
+ elif argv[idx] == "-r":
+ cxt.enable_rdonly_umount(True)
+ idx += 1
+
+ if len(argv) == idx + 1:
+ cxt.target = argv[idx]
+ idx += 1
+ else:
+ return -errno.EINVAL
+ try:
+ cxt.umount()
+ except Exception:
+ print("failed to umount")
+ return 1
+ print("successfully umounted")
+ return rc
+
+def test_flags(ts, argv):
+ idx = 1
+ rc = 0
+ opt = ""
+ flags = 0
+ cxt = mnt.Context()
+
+ if argv[idx] == "-o":
+ cxt.options = argv[idx + 1]
+ idx += 2
+ if len(argv) == idx + 1:
+ cxt.target = argv[idx]
+ idx += 1
+
+ try:
+ cxt.prepare_mount()
+ # catch ioerror here
+ except IOError as e:
+ print("failed to prepare mount {:s}".format(e.strerror))
+
+ opt = cxt.fs.options
+ if (opt):
+ print("options: {:s}", opt)
+
+ print("flags: {08:lx}".format(cxt.mflags()))
+ return rc
+
+def test_mountall(ts, argv):
+ mntrc = 1
+ ignored = 1
+ idx = 1
+ cxt = mnt.Context()
+
+ if len(argv) > 2:
+ if argv[idx] == "-O":
+ cxt.options_pattern = argv[idx+1]
+ idx += 2
+ if argv[idx] == "-t":
+ cxt.fstype_pattern = argv[idx+1]
+ idx += 2
+
+ i = ()
+ while (cxt.next_mount()):
+ tgt = i.target
+ if (ignored == 1):
+ print("{:s}: ignored: not match".format(tgt))
+ elif (ignored == 2):
+ print("{:s}: ignored: already mounted".format(tgt))
+ elif (not cxt.status):
+ if (mntrc > 0):
+ # ?? errno = mntrc
+ print("{:s}: mount failed".format(tgt))
+ else:
+ print("{:s}: mount failed".format(tgt))
+ else:
+ print("{:s}: successfully mounted".format(tgt))
+
+ return 0
+
+
+tss = (
+ ( "--mount", test_mount, "[-o <opts>] [-t <type>] <spec>|<src> <target>" ),
+ ( "--umount", test_umount, "[-t <type>] [-f][-l][-r] <src>|<target>" ),
+ ( "--mount-all", test_mountall, "[-O <pattern>] [-t <pattern] mount all filesystems from fstab" ),
+ ( "--flags", test_flags, "[-o <opts>] <spec>" )
+)
+os.umask(stat.S_IWGRP | stat.S_IWOTH) #to be compatible with mount(8)
+
+sys.exit(mnt_run_test(tss, sys.argv))
diff --git a/libmount/python/test_mount_tab.py b/libmount/python/test_mount_tab.py
new file mode 100755
index 0000000..1f63b95
--- /dev/null
+++ b/libmount/python/test_mount_tab.py
@@ -0,0 +1,164 @@
+import os
+import sys
+import stat
+import errno
+import functools as ft
+
+# use "import libmount" for in a standard way installed python binding
+import pylibmount as mnt
+
+def usage(tss):
+ print("\nUsage:\n\t{:s} <test> [testoptions]\nTests:\n".format(sys.argv[0]))
+ for i in tss:
+ print("\t{15:-s}".format(i[0]))
+ if i[2] != "":
+ print(" {:s}\n".format(i[2]))
+
+ print("\n")
+ return 1
+
+def mnt_run_test(tss, argv):
+ rc = -1
+ if ((len(argv) < 2) or (argv[1] == "--help") or (argv[1] == "-h")):
+ return usage(tss)
+
+ #mnt_init_debug(0)
+
+ i=()
+ for i in tss:
+ if i[0] == argv[1]:
+ rc = i[1](i, argv[1:])
+ if rc:
+ print("FAILED [rc={:d}]".format(rc))
+ break
+
+ if ((rc < 0) and (i == ())):
+ return usage(tss)
+ return not not rc #because !!rc is too mainstream for python
+
+def parser_errcb(tb, fname, line):
+ print("{:s}:{:d}: parse error".format(fname, line))
+ return 1
+
+def create_table(f, comments):
+ if not f:
+ return None
+
+ tb = mnt.Table()
+ tb.enable_comments(comments)
+ tb.errcb = parser_errcb
+
+ try:
+ tb.parse_file(f)
+ except Exception:
+ print("{:s}: parsing failed".format(f))
+ return None
+ return tb
+
+def test_copy_fs(ts, argv):
+ rc = -1
+ tb = create_table(argv[1], False)
+ fs = tb.find_target("/", mnt.MNT_ITER_FORWARD)
+ if not fs:
+ return rc
+
+ print("ORIGINAL:")
+ fs.print_debug()
+
+ fs = fs.copy_fs(None)
+ if not fs:
+ return rc
+ print("COPY:")
+ fs.print_debug()
+ return 0
+
+def test_parse(ts, argv):
+ parse_comments = False
+
+ if len(argv) == 3 and argv[2] == "--comments":
+ parse_comments = True
+ tb = create_table(argv[1], parse_comments)
+
+ if tb.intro_comment:
+ print("Initial comment:\n\"{:s}\"".format(tb.intro_comment))
+ #while ((fs = tb.next_fs()) != None):
+ for fs in iter(ft.partial(tb.next_fs), None):
+ fs.print_debug()
+ if tb.trailing_comment:
+ print("Trailing comment:\n\"{:s}\"".format(tb.trailing_comment))
+ return 0
+
+def test_find(ts, argv, dr):
+ if len(argv) != 4:
+ print("try --help")
+ return -errno.EINVAL
+
+ f, find, what = argv[1:]
+
+ tb = create_table(f, False)
+ if find.lower() == "source":
+ fs = tb.find_source(what, dr)
+ elif find.lower() == "target":
+ fs = tb.find_target(what, dr)
+
+ if not fs:
+ print("{:s}: not found {:s} '{:s}'".format(f, find, what))
+ else:
+ fs.print_debug()
+ return 0
+
+def test_find_fw(ts, argv):
+ return test_find(ts, argv, mnt.MNT_ITER_FORWARD)
+
+def test_find_bw(ts, argv):
+ return test_find(ts, argv, mnt.MNT_ITER_BACKWARD)
+
+def test_find_pair(ts, argv):
+ rc = -1
+ tb = create_table(argv[1], False)
+ fs = tb.find_pair(argv[2], argv[3], mnt.MNT_ITER_FORWARD)
+ if not fs:
+ return rc
+ fs.print_debug()
+ return 0
+
+def test_is_mounted(ts, argv):
+ rc = -1
+ tb = mnt.Tab(path="/proc/self/mountinfo")
+ if not tb:
+ print("failed to parse mountinto")
+ return rc
+
+ fstab = create_table(argv[1], False)
+ if not fstab:
+ return rc
+ fs = ()
+ for fs in ft.iter(tb.next_fs(), -1):
+ if tb.is_fs_mounted(fs):
+ print("{:s} already mounted on {:s}".format(fs.source, fs.target))
+ else:
+ print("{:s} not mounted on {:s}".format(fs.source, fs.target))
+ return 0
+
+def test_find_mountpoint(ts, argv):
+ rc = -1
+ tb = mnt.Table("/proc/self/mountinfo")
+ if not tb:
+ return rc
+ fs = tb.find_mountpoint(argv[1], mnt.MNT_ITER_BACKWARD)
+ if not fs:
+ return rc
+ fs.print_debug()
+ return 0
+
+
+tss = (
+ ( "--parse", test_parse, "<file> [--comments] parse and print(tab" ),
+ ( "--find-forward", test_find_fw, "<file> <source|target> <string>" ),
+ ( "--find-backward", test_find_bw, "<file> <source|target> <string>" ),
+ ( "--find-pair", test_find_pair, "<file> <source> <target>" ),
+ ( "--find-mountpoint", test_find_mountpoint, "<path>" ),
+ ( "--copy-fs", test_copy_fs, "<file> copy root FS from the file" ),
+ ( "--is-mounted", test_is_mounted, "<fstab> check what from <file> are already mounted" ),
+)
+sys.exit(mnt_run_test(tss, sys.argv))
diff --git a/libmount/python/test_mount_tab_update.py b/libmount/python/test_mount_tab_update.py
new file mode 100755
index 0000000..a16e9e3
--- /dev/null
+++ b/libmount/python/test_mount_tab_update.py
@@ -0,0 +1,59 @@
+import os
+import sys
+import stat
+import errno
+
+# use "import libmount" for in a standard way installed python binding
+import pylibmount as mnt
+
+def usage(tss):
+ print("\nUsage:\n\t{:s} <test> [testoptions]\nTests:\n".format(sys.argv[0]))
+ for i in tss:
+ print("\t{15:-s}".format(i[0]))
+ if i[2] != "":
+ print(" {:s}\n".format(i[2]))
+
+ print("\n")
+ return 1
+
+def mnt_run_test(tss, argv):
+ rc = -1
+ if ((len(argv) < 2) or (argv[1] == "--help") or (argv[1] == "-h")):
+ return usage(tss)
+
+ #mnt_init_debug(0)
+
+ i=()
+ for i in tss:
+ if i[0] == argv[1]:
+ rc = i[1](i, argv[1:])
+ if rc:
+ print("FAILED [rc={:d}]".format(rc))
+ break
+
+ if ((rc < 0) and (i == ())):
+ return usage(tss)
+ return not not rc #because !!rc is too mainstream for python
+
+def test_replace(ts, argv):
+ fs = mnt.Fs()
+ tb = mnt.Table()
+
+ if (len(argv) < 3):
+ return -1
+ tb.enable_comments(True)
+ tb.parse_fstab()
+
+ fs.source = argv[1]
+ fs.target = argv[2]
+ #TODO: resolve None + string
+ fs.comment = "# this is new filesystem\n"
+ tb.add_fs(fs)
+ tb.replace_file(os.environ["LIBMOUNT_FSTAB"])
+ return 0
+
+tss = (
+ ( "--replace",test_replace, "<src> <target> Add a line to LIBMOUNT_FSTAB and replace the original file" ),
+)
+
+sys.exit(mnt_run_test(tss, sys.argv))
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
new file mode 100644
index 0000000..c2579b0
--- /dev/null
+++ b/libmount/src/Makemodule.am
@@ -0,0 +1,197 @@
+
+# libmount.h is generated, so it's store in builddir!
+mountincdir = $(includedir)/libmount
+nodist_mountinc_HEADERS = libmount/src/libmount.h
+
+usrlib_exec_LTLIBRARIES += libmount.la
+libmount_la_SOURCES = \
+ include/list.h \
+ lib/monotonic.c \
+ \
+ libmount/src/mountP.h \
+ libmount/src/cache.c \
+ libmount/src/fs.c \
+ libmount/src/init.c \
+ libmount/src/iter.c \
+ libmount/src/lock.c \
+ libmount/src/optmap.c \
+ libmount/src/optstr.c \
+ libmount/src/tab.c \
+ libmount/src/tab_diff.c \
+ libmount/src/tab_parse.c \
+ libmount/src/tab_update.c \
+ libmount/src/test.c \
+ libmount/src/utils.c \
+ libmount/src/version.c
+
+if LINUX
+libmount_la_SOURCES += \
+ libmount/src/context.c \
+ libmount/src/context_loopdev.c \
+ libmount/src/context_veritydev.c \
+ libmount/src/context_mount.c \
+ libmount/src/context_umount.c \
+ libmount/src/monitor.c
+
+if HAVE_BTRFS
+libmount_la_SOURCES += libmount/src/btrfs.c
+endif
+
+endif # LINUX
+
+
+libmount_la_LIBADD = \
+ libcommon.la \
+ libblkid.la \
+ $(SELINUX_LIBS) \
+ $(REALTIME_LIBS)
+
+if HAVE_CRYPTSETUP
+if CRYPTSETUP_VIA_DLOPEN
+libmount_la_LIBADD += -ldl
+else
+libmount_la_LIBADD += $(CRYPTSETUP_LIBS)
+endif
+endif
+
+libmount_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ $(CRYPTSETUP_CFLAGS) \
+ -I$(ul_libblkid_incdir) \
+ -I$(ul_libmount_incdir) \
+ -I$(top_srcdir)/libmount/src
+
+EXTRA_libmount_la_DEPENDENCIES = \
+ libmount/src/libmount.sym
+
+libmount_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libmount_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libmount/src/libmount.sym
+endif
+libmount_la_LDFLAGS += -version-info $(LIBMOUNT_VERSION_INFO)
+
+
+EXTRA_DIST += \
+ libmount/src/libmount.sym
+
+if BUILD_LIBMOUNT_TESTS
+check_PROGRAMS += \
+ test_mount_cache \
+ test_mount_lock \
+ test_mount_optstr \
+ test_mount_tab \
+ test_mount_tab_diff \
+ test_mount_tab_update \
+ test_mount_utils \
+ test_mount_version \
+ test_mount_debug
+if LINUX
+check_PROGRAMS += test_mount_context
+check_PROGRAMS += test_mount_monitor
+endif
+
+libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+libmount_tests_ldflags = -static
+libmount_tests_ldadd = libmount.la libblkid.la $(LDADD) $(REALTIME_LIBS)
+
+if HAVE_SELINUX
+libmount_tests_ldadd += $(SELINUX_LIBS)
+endif
+
+if HAVE_CRYPTSETUP
+if CRYPTSETUP_VIA_DLOPEN
+libmount_tests_ldadd += -ldl
+else
+libmount_tests_ldadd += $(CRYPTSETUP_LIBS)
+endif
+endif
+
+test_mount_cache_SOURCES = libmount/src/cache.c
+test_mount_cache_CFLAGS = $(libmount_tests_cflags)
+test_mount_cache_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_cache_LDADD = $(libmount_tests_ldadd)
+
+test_mount_context_SOURCES = libmount/src/context.c
+test_mount_context_CFLAGS = $(libmount_tests_cflags)
+test_mount_context_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_context_LDADD = $(libmount_tests_ldadd)
+
+test_mount_lock_SOURCES = libmount/src/lock.c
+test_mount_lock_CFLAGS = $(libmount_tests_cflags)
+test_mount_lock_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_lock_LDADD = $(libmount_tests_ldadd)
+
+test_mount_optstr_SOURCES = libmount/src/optstr.c
+test_mount_optstr_CFLAGS = $(libmount_tests_cflags)
+test_mount_optstr_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_optstr_LDADD = $(libmount_tests_ldadd)
+
+test_mount_tab_SOURCES = libmount/src/tab.c
+test_mount_tab_CFLAGS = $(libmount_tests_cflags)
+test_mount_tab_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_tab_LDADD = $(libmount_tests_ldadd)
+
+test_mount_tab_diff_SOURCES = libmount/src/tab_diff.c
+test_mount_tab_diff_CFLAGS = $(libmount_tests_cflags)
+test_mount_tab_diff_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_tab_diff_LDADD = $(libmount_tests_ldadd)
+
+test_mount_monitor_SOURCES = libmount/src/monitor.c
+test_mount_monitor_CFLAGS = $(libmount_tests_cflags)
+test_mount_monitor_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_monitor_LDADD = $(libmount_tests_ldadd)
+
+test_mount_tab_update_SOURCES = libmount/src/tab_update.c
+test_mount_tab_update_CFLAGS = $(libmount_tests_cflags)
+test_mount_tab_update_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_tab_update_LDADD = $(libmount_tests_ldadd)
+
+test_mount_utils_SOURCES = libmount/src/utils.c
+test_mount_utils_CFLAGS = $(libmount_tests_cflags)
+test_mount_utils_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_utils_LDADD = $(libmount_tests_ldadd)
+
+test_mount_version_SOURCES = libmount/src/version.c
+test_mount_version_CFLAGS = $(libmount_tests_cflags)
+test_mount_version_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_version_LDADD = $(libmount_tests_ldadd)
+
+test_mount_debug_SOURCES = libmount/src/init.c
+test_mount_debug_CFLAGS = $(libmount_tests_cflags)
+test_mount_debug_LDFLAGS = $(libmount_tests_ldflags)
+test_mount_debug_LDADD = $(libmount_tests_ldadd)
+
+if FUZZING_ENGINE
+check_PROGRAMS += test_mount_fuzz
+
+test_mount_fuzz_SOURCES = libmount/src/fuzz.c
+
+# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements
+nodist_EXTRA_test_mount_fuzz_SOURCES = dummy.cxx
+
+test_mount_fuzz_CFLAGS = $(libmount_tests_cflags)
+test_mount_fuzz_LDFLAGS = $(libmount_tests_ldflags) -lpthread
+test_mount_fuzz_LDADD = $(libmount_tests_ldadd) $(LIB_FUZZING_ENGINE)
+endif
+
+endif # BUILD_LIBMOUNT_TESTS
+
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libmount:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libmount.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libmount.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libmount.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libmount.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libmount.so); \
+ fi
+
+uninstall-hook-libmount:
+ rm -f $(DESTDIR)$(libdir)/libmount.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libmount
+UNINSTALL_HOOKS += uninstall-hook-libmount
diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c
new file mode 100644
index 0000000..a831ce8
--- /dev/null
+++ b/libmount/src/btrfs.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2016 David Sterba <dsterba@suse.cz>
+ * Copyright (C) 2016 Stanislav Brabec <sbrabec@suse.cz>
+ *
+ * libmount 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.
+ *
+ * Based on kernel ctree.h, rbtree.h and btrfs-progs.
+ */
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <linux/btrfs.h>
+
+#include "mountP.h"
+#include "bitops.h"
+
+
+/* linux/btrfs.h lacks large parts of stuff needed for getting default
+ * sub-volume. Suppose that if BTRFS_DIR_ITEM_KEY is not defined, all
+ * declarations are still missing.
+ */
+#ifndef BTRFS_DIR_ITEM_KEY
+
+/*
+ * dir items are the name -> inode pointers in a directory. There is one
+ * for every name in a directory.
+ */
+#define BTRFS_DIR_ITEM_KEY 84
+
+/* holds pointers to all of the tree roots */
+#define BTRFS_ROOT_TREE_OBJECTID 1ULL
+
+/* directory objectid inside the root tree */
+#define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL
+
+/*
+ * the key defines the order in the tree, and so it also defines (optimal)
+ * block layout. objectid corresponds with the inode number. The flags
+ * tells us things about the object, and is a kind of stream selector.
+ * so for a given inode, keys with flags of 1 might refer to the inode
+ * data, flags of 2 may point to file data in the btree and flags == 3
+ * may point to extents.
+ *
+ * offset is the starting byte offset for this key in the stream.
+ *
+ * btrfs_disk_key is in disk byte order. struct btrfs_key is always
+ * in cpu native order. Otherwise they are identical and their sizes
+ * should be the same (ie both packed)
+ */
+struct btrfs_disk_key {
+ uint64_t objectid; /* little endian */
+ uint8_t type;
+ uint64_t offset; /* little endian */
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+ struct btrfs_disk_key location;
+ uint64_t transid; /* little endian */
+ uint16_t data_len; /* little endian */
+ uint16_t name_len; /* little endian */
+ uint8_t type;
+} __attribute__ ((__packed__));
+
+#define BTRFS_SETGET_STACK_FUNCS(name, type, member, bits) \
+static inline uint##bits##_t btrfs_##name(const type *s) \
+{ \
+ return le##bits##_to_cpu(s->member); \
+}
+
+/* struct btrfs_disk_key */
+BTRFS_SETGET_STACK_FUNCS(disk_key_objectid, struct btrfs_disk_key,
+ objectid, 64)
+
+BTRFS_SETGET_STACK_FUNCS(stack_dir_name_len, struct btrfs_dir_item, name_len, 16)
+
+/*
+ Red Black Trees
+*/
+struct rb_node {
+ unsigned long __rb_parent_color;
+ struct rb_node *rb_right;
+ struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+ /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+#endif /* BTRFS_DIR_ITEM_KEY */
+
+/*
+ * btrfs_get_default_subvol_id:
+ * @path: Path to mounted btrfs volume
+ *
+ * Searches for the btrfs default subvolume id.
+ *
+ * Returns: default subvolume id or UINT64_MAX (-1) in case of no
+ * default subvolume or error. In case of error, errno is set
+ * properly.
+ */
+uint64_t btrfs_get_default_subvol_id(const char *path)
+{
+ int iocret;
+ int fd;
+ DIR *dirstream;
+ struct btrfs_ioctl_search_args args;
+ struct btrfs_ioctl_search_key *sk = &args.key;
+ struct btrfs_ioctl_search_header *sh;
+ uint64_t found = UINT64_MAX;
+
+ dirstream = opendir(path);
+ if (!dirstream) {
+ DBG(BTRFS, ul_debug("opendir() failed for \"%s\" [errno=%d %m]", path, errno));
+ return UINT64_MAX;
+ }
+ fd = dirfd(dirstream);
+ if (fd < 0) {
+ DBG(BTRFS, ul_debug("dirfd(opendir()) failed for \"%s\" [errno=%d %m]", path, errno));
+ goto out;
+ }
+
+ memset(&args, 0, sizeof(args));
+ sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+ sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+ sk->min_type = BTRFS_DIR_ITEM_KEY;
+ sk->max_type = BTRFS_DIR_ITEM_KEY;
+ sk->max_offset = UINT64_MAX;
+ sk->max_transid = UINT64_MAX;
+ sk->nr_items = 1;
+
+ iocret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (iocret < 0) {
+ DBG(BTRFS, ul_debug("ioctl() failed for \"%s\" [errno=%d %m]", path, errno));
+ goto out;
+ }
+
+ /* the ioctl returns the number of items it found in nr_items */
+ if (sk->nr_items == 0) {
+ DBG(BTRFS, ul_debug("root tree dir object id not found"));
+ goto out;
+ }
+ DBG(BTRFS, ul_debug("found %d root tree dir object id items", sk->nr_items));
+
+ sh = (struct btrfs_ioctl_search_header *)args.buf;
+
+ if (sh->type == BTRFS_DIR_ITEM_KEY) {
+ struct btrfs_dir_item *di;
+ int name_len;
+ char *name;
+
+ di = (struct btrfs_dir_item *)(sh + 1);
+ name_len = btrfs_stack_dir_name_len(di);
+ name = (char *)(di + 1);
+
+ if (!strncmp("default", name, name_len)) {
+ found = btrfs_disk_key_objectid(&di->location);
+ DBG(BTRFS, ul_debug("\"default\" id is %llu", (unsigned long long)found));
+ } else {
+ DBG(BTRFS, ul_debug("\"default\" id not found in tree root"));
+ goto out;
+ }
+ } else {
+ DBG(BTRFS, ul_debug("unexpected type found: %d", (int)sh->type));
+ goto out;
+ }
+
+out:
+ closedir(dirstream);
+ return found;
+}
diff --git a/libmount/src/cache.c b/libmount/src/cache.c
new file mode 100644
index 0000000..2fafdd0
--- /dev/null
+++ b/libmount/src/cache.c
@@ -0,0 +1,840 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: cache
+ * @title: Cache
+ * @short_description: paths and tags (UUID/LABEL) caching
+ *
+ * The cache is a very simple API for working with tags (LABEL, UUID, ...) and
+ * paths. The cache uses libblkid as a backend for TAGs resolution.
+ *
+ * All returned paths are always canonicalized.
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <blkid.h>
+
+#include "canonicalize.h"
+#include "mountP.h"
+#include "loopdev.h"
+#include "strutils.h"
+
+/*
+ * Canonicalized (resolved) paths & tags cache
+ */
+#define MNT_CACHE_CHUNKSZ 128
+
+#define MNT_CACHE_ISTAG (1 << 1) /* entry is TAG */
+#define MNT_CACHE_ISPATH (1 << 2) /* entry is path */
+#define MNT_CACHE_TAGREAD (1 << 3) /* tag read by mnt_cache_read_tags() */
+
+/* path cache entry */
+struct mnt_cache_entry {
+ char *key; /* search key (e.g. uncanonicalized path) */
+ char *value; /* value (e.g. canonicalized path) */
+ int flag;
+};
+
+struct libmnt_cache {
+ struct mnt_cache_entry *ents;
+ size_t nents;
+ size_t nallocs;
+ int refcount;
+
+ /* blkid_evaluate_tag() works in two ways:
+ *
+ * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks,
+ * then the blkid_cache is NULL.
+ *
+ * 2/ all tags are read from blkid.tab and verified by /dev
+ * scanning, then the blkid_cache is not NULL and then it's
+ * better to reuse the blkid_cache.
+ */
+ blkid_cache bc;
+
+ struct libmnt_table *mtab;
+};
+
+/**
+ * mnt_new_cache:
+ *
+ * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error.
+ */
+struct libmnt_cache *mnt_new_cache(void)
+{
+ struct libmnt_cache *cache = calloc(1, sizeof(*cache));
+ if (!cache)
+ return NULL;
+ DBG(CACHE, ul_debugobj(cache, "alloc"));
+ cache->refcount = 1;
+ return cache;
+}
+
+/**
+ * mnt_free_cache:
+ * @cache: pointer to struct libmnt_cache instance
+ *
+ * Deallocates the cache. This function does not care about reference count. Don't
+ * use this function directly -- it's better to use mnt_unref_cache().
+ */
+void mnt_free_cache(struct libmnt_cache *cache)
+{
+ size_t i;
+
+ if (!cache)
+ return;
+
+ DBG(CACHE, ul_debugobj(cache, "free [refcount=%d]", cache->refcount));
+
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (e->value != e->key)
+ free(e->value);
+ free(e->key);
+ }
+ free(cache->ents);
+ if (cache->bc)
+ blkid_put_cache(cache->bc);
+ free(cache);
+}
+
+/**
+ * mnt_ref_cache:
+ * @cache: cache pointer
+ *
+ * Increments reference counter.
+ */
+void mnt_ref_cache(struct libmnt_cache *cache)
+{
+ if (cache) {
+ cache->refcount++;
+ /*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/
+ }
+}
+
+/**
+ * mnt_unref_cache:
+ * @cache: cache pointer
+ *
+ * De-increments reference counter, on zero the cache is automatically
+ * deallocated by mnt_free_cache().
+ */
+void mnt_unref_cache(struct libmnt_cache *cache)
+{
+ if (cache) {
+ cache->refcount--;
+ /*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/
+ if (cache->refcount <= 0) {
+ mnt_unref_table(cache->mtab);
+
+ mnt_free_cache(cache);
+ }
+ }
+}
+
+/**
+ * mnt_cache_set_targets:
+ * @cache: cache pointer
+ * @mtab: table with already canonicalized mountpoints
+ *
+ * Add to @cache reference to @mtab. This can be used to avoid unnecessary paths
+ * canonicalization in mnt_resolve_target().
+ *
+ * Returns: negative number in case of error, or 0 o success.
+ */
+int mnt_cache_set_targets(struct libmnt_cache *cache,
+ struct libmnt_table *mtab)
+{
+ if (!cache)
+ return -EINVAL;
+
+ mnt_ref_table(mtab);
+ mnt_unref_table(cache->mtab);
+ cache->mtab = mtab;
+ return 0;
+}
+
+
+/* note that the @key could be the same pointer as @value */
+static int cache_add_entry(struct libmnt_cache *cache, char *key,
+ char *value, int flag)
+{
+ struct mnt_cache_entry *e;
+
+ assert(cache);
+ assert(value);
+ assert(key);
+
+ if (cache->nents == cache->nallocs) {
+ size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ;
+
+ e = realloc(cache->ents, sz * sizeof(struct mnt_cache_entry));
+ if (!e)
+ return -ENOMEM;
+ cache->ents = e;
+ cache->nallocs = sz;
+ }
+
+ e = &cache->ents[cache->nents];
+ e->key = key;
+ e->value = value;
+ e->flag = flag;
+ cache->nents++;
+
+ DBG(CACHE, ul_debugobj(cache, "add entry [%2zd] (%s): %s: %s",
+ cache->nents,
+ (flag & MNT_CACHE_ISPATH) ? "path" : "tag",
+ value, key));
+ return 0;
+}
+
+/* add tag to the cache, @devname has to be an allocated string */
+static int cache_add_tag(struct libmnt_cache *cache, const char *tagname,
+ const char *tagval, char *devname, int flag)
+{
+ size_t tksz, vlsz;
+ char *key;
+ int rc;
+
+ assert(cache);
+ assert(devname);
+ assert(tagname);
+ assert(tagval);
+
+ /* add into cache -- cache format for TAGs is
+ * key = "TAG_NAME\0TAG_VALUE\0"
+ * value = "/dev/foo"
+ */
+ tksz = strlen(tagname);
+ vlsz = strlen(tagval);
+
+ key = malloc(tksz + vlsz + 2);
+ if (!key)
+ return -ENOMEM;
+
+ memcpy(key, tagname, tksz + 1); /* include '\0' */
+ memcpy(key + tksz + 1, tagval, vlsz + 1);
+
+ rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG);
+ if (!rc)
+ return 0;
+
+ free(key);
+ return rc;
+}
+
+
+/*
+ * Returns cached canonicalized path or NULL.
+ */
+static const char *cache_find_path(struct libmnt_cache *cache, const char *path)
+{
+ size_t i;
+
+ if (!cache || !path)
+ return NULL;
+
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (!(e->flag & MNT_CACHE_ISPATH))
+ continue;
+ if (streq_paths(path, e->key))
+ return e->value;
+ }
+ return NULL;
+}
+
+/*
+ * Returns cached path or NULL.
+ */
+static const char *cache_find_tag(struct libmnt_cache *cache,
+ const char *token, const char *value)
+{
+ size_t i;
+ size_t tksz;
+
+ if (!cache || !token || !value)
+ return NULL;
+
+ tksz = strlen(token);
+
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (!(e->flag & MNT_CACHE_ISTAG))
+ continue;
+ if (strcmp(token, e->key) == 0 &&
+ strcmp(value, e->key + tksz + 1) == 0)
+ return e->value;
+ }
+ return NULL;
+}
+
+static char *cache_find_tag_value(struct libmnt_cache *cache,
+ const char *devname, const char *token)
+{
+ size_t i;
+
+ assert(cache);
+ assert(devname);
+ assert(token);
+
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (!(e->flag & MNT_CACHE_ISTAG))
+ continue;
+ if (strcmp(e->value, devname) == 0 && /* dev name */
+ strcmp(token, e->key) == 0) /* tag name */
+ return e->key + strlen(token) + 1; /* tag value */
+ }
+
+ return NULL;
+}
+
+/**
+ * mnt_cache_read_tags
+ * @cache: pointer to struct libmnt_cache instance
+ * @devname: path device
+ *
+ * Reads @devname LABEL and UUID to the @cache.
+ *
+ * Returns: 0 if at least one tag was added, 1 if no tag was added or
+ * negative number in case of error.
+ */
+int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname)
+{
+ blkid_probe pr;
+ size_t i, ntags = 0;
+ int rc;
+ const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" };
+ const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" };
+
+ if (!cache || !devname)
+ return -EINVAL;
+
+ DBG(CACHE, ul_debugobj(cache, "tags for %s requested", devname));
+
+ /* check if device is already cached */
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (!(e->flag & MNT_CACHE_TAGREAD))
+ continue;
+ if (strcmp(e->value, devname) == 0)
+ /* tags have already been read */
+ return 0;
+ }
+
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ return -1;
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_TYPE);
+
+ blkid_probe_enable_partitions(pr, 1);
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+
+ rc = blkid_do_safeprobe(pr);
+ if (rc)
+ goto error;
+
+ DBG(CACHE, ul_debugobj(cache, "reading tags for: %s", devname));
+
+ for (i = 0; i < ARRAY_SIZE(tags); i++) {
+ const char *data;
+ char *dev;
+
+ if (cache_find_tag_value(cache, devname, tags[i])) {
+ DBG(CACHE, ul_debugobj(cache,
+ "\ntag %s already cached", tags[i]));
+ continue;
+ }
+ if (blkid_probe_lookup_value(pr, blktags[i], &data, NULL))
+ continue;
+ dev = strdup(devname);
+ if (!dev)
+ goto error;
+ if (cache_add_tag(cache, tags[i], data, dev,
+ MNT_CACHE_TAGREAD)) {
+ free(dev);
+ goto error;
+ }
+ ntags++;
+ }
+
+ DBG(CACHE, ul_debugobj(cache, "\tread %zd tags", ntags));
+ blkid_free_probe(pr);
+ return ntags ? 0 : 1;
+error:
+ blkid_free_probe(pr);
+ return rc < 0 ? rc : -1;
+}
+
+/**
+ * mnt_cache_device_has_tag:
+ * @cache: paths cache
+ * @devname: path to the device
+ * @token: tag name (e.g "LABEL")
+ * @value: tag value
+ *
+ * Look up @cache to check if @tag+@value are associated with @devname.
+ *
+ * Returns: 1 on success or 0.
+ */
+int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname,
+ const char *token, const char *value)
+{
+ const char *path = cache_find_tag(cache, token, value);
+
+ if (path && devname && strcmp(path, devname) == 0)
+ return 1;
+ return 0;
+}
+
+static int __mnt_cache_find_tag_value(struct libmnt_cache *cache,
+ const char *devname, const char *token, char **data)
+{
+ int rc = 0;
+
+ if (!cache || !devname || !token || !data)
+ return -EINVAL;
+
+ rc = mnt_cache_read_tags(cache, devname);
+ if (rc)
+ return rc;
+
+ *data = cache_find_tag_value(cache, devname, token);
+ return *data ? 0 : -1;
+}
+
+/**
+ * mnt_cache_find_tag_value:
+ * @cache: cache for results
+ * @devname: device name
+ * @token: tag name ("LABEL" or "UUID")
+ *
+ * Returns: LABEL or UUID for the @devname or NULL in case of error.
+ */
+char *mnt_cache_find_tag_value(struct libmnt_cache *cache,
+ const char *devname, const char *token)
+{
+ char *data = NULL;
+
+ if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0)
+ return data;
+ return NULL;
+}
+
+/**
+ * mnt_get_fstype:
+ * @devname: device name
+ * @ambi: returns TRUE if probing result is ambivalent (optional argument)
+ * @cache: cache for results or NULL
+ *
+ * Returns: filesystem type or NULL in case of error. The result has to be
+ * deallocated by free() if @cache is NULL.
+ */
+char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache)
+{
+ blkid_probe pr;
+ const char *data;
+ char *type = NULL;
+ int rc;
+
+ DBG(CACHE, ul_debugobj(cache, "get %s FS type", devname));
+
+ if (cache) {
+ char *val = NULL;
+ rc = __mnt_cache_find_tag_value(cache, devname, "TYPE", &val);
+ if (ambi)
+ *ambi = rc == -2 ? TRUE : FALSE;
+ return rc ? NULL : val;
+ }
+
+ /*
+ * no cache, probe directly
+ */
+ pr = blkid_new_probe_from_filename(devname);
+ if (!pr)
+ return NULL;
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
+
+ rc = blkid_do_safeprobe(pr);
+
+ DBG(CACHE, ul_debugobj(cache, "libblkid rc=%d", rc));
+
+ if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
+ type = strdup(data);
+
+ if (ambi)
+ *ambi = rc == -2 ? TRUE : FALSE;
+
+ blkid_free_probe(pr);
+ return type;
+}
+
+static char *canonicalize_path_and_cache(const char *path,
+ struct libmnt_cache *cache)
+{
+ char *p;
+ char *key;
+ char *value;
+
+ DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path));
+ p = canonicalize_path(path);
+
+ if (p && cache) {
+ value = p;
+ key = strcmp(path, p) == 0 ? value : strdup(path);
+
+ if (!key || !value)
+ goto error;
+
+ if (cache_add_entry(cache, key, value,
+ MNT_CACHE_ISPATH))
+ goto error;
+ }
+
+ return p;
+error:
+ if (value != key)
+ free(value);
+ free(key);
+ return NULL;
+}
+
+/**
+ * mnt_resolve_path:
+ * @path: "native" path
+ * @cache: cache for results or NULL
+ *
+ * Converts path:
+ * - to the absolute path
+ * - /dev/dm-N to /dev/mapper/name
+ *
+ * Returns: absolute path or NULL in case of error. The result has to be
+ * deallocated by free() if @cache is NULL.
+ */
+char *mnt_resolve_path(const char *path, struct libmnt_cache *cache)
+{
+ char *p = NULL;
+
+ /*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/
+
+ if (!path)
+ return NULL;
+ if (cache)
+ p = (char *) cache_find_path(cache, path);
+ if (!p)
+ p = canonicalize_path_and_cache(path, cache);
+
+ return p;
+}
+
+/**
+ * mnt_resolve_target:
+ * @path: "native" path, a potential mount point
+ * @cache: cache for results or NULL.
+ *
+ * Like mnt_resolve_path(), unless @cache is not NULL and
+ * mnt_cache_set_targets(cache, mtab) was called: if @path is found in the
+ * cached @mtab and the matching entry was provided by the kernel, assume that
+ * @path is already canonicalized. By avoiding a call to realpath(2) on
+ * known mount points, there is a lower risk of stepping on a stale mount
+ * point, which can result in an application freeze. This is also faster in
+ * general, as stat(2) on a mount point is slower than on a regular file.
+ *
+ * Returns: absolute path or NULL in case of error. The result has to be
+ * deallocated by free() if @cache is NULL.
+ */
+char *mnt_resolve_target(const char *path, struct libmnt_cache *cache)
+{
+ char *p = NULL;
+
+ /*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
+
+ if (!cache || !cache->mtab)
+ return mnt_resolve_path(path, cache);
+
+ p = (char *) cache_find_path(cache, path);
+ if (p)
+ return p;
+
+ {
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+ while (mnt_table_next_fs(cache->mtab, &itr, &fs) == 0) {
+
+ if (!mnt_fs_is_kernel(fs)
+ || mnt_fs_is_swaparea(fs)
+ || !mnt_fs_streq_target(fs, path))
+ continue;
+
+ p = strdup(path);
+ if (!p)
+ return NULL; /* ENOMEM */
+
+ if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) {
+ free(p);
+ return NULL; /* ENOMEM */
+ }
+ break;
+ }
+ }
+
+ if (!p)
+ p = canonicalize_path_and_cache(path, cache);
+ return p;
+}
+
+/**
+ * mnt_pretty_path:
+ * @path: any path
+ * @cache: NULL or pointer to the cache
+ *
+ * Converts path:
+ * - to the absolute path
+ * - /dev/dm-N to /dev/mapper/name
+ * - /dev/loopN to the loop backing filename
+ * - empty path (NULL) to 'none'
+ *
+ * Returns: newly allocated string with path, result always has to be deallocated
+ * by free().
+ */
+char *mnt_pretty_path(const char *path, struct libmnt_cache *cache)
+{
+ char *pretty = mnt_resolve_path(path, cache);
+
+ if (!pretty)
+ return strdup("none");
+
+#ifdef __linux__
+ /* users assume backing file name rather than /dev/loopN in
+ * output if the device has been initialized by mount(8).
+ */
+ if (strncmp(pretty, "/dev/loop", 9) == 0) {
+ struct loopdev_cxt lc;
+
+ if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty))
+ goto done;
+
+ if (loopcxt_is_autoclear(&lc)) {
+ char *tmp = loopcxt_get_backing_file(&lc);
+ if (tmp) {
+ loopcxt_deinit(&lc);
+ if (!cache)
+ free(pretty); /* not cached, deallocate */
+ return tmp; /* return backing file */
+ }
+ }
+ loopcxt_deinit(&lc);
+
+ }
+#endif
+
+done:
+ /* don't return pointer to the cache, allocate a new string */
+ return cache ? strdup(pretty) : pretty;
+}
+
+/**
+ * mnt_resolve_tag:
+ * @token: tag name
+ * @value: tag value
+ * @cache: for results or NULL
+ *
+ * Returns: device name or NULL in case of error. The result has to be
+ * deallocated by free() if @cache is NULL.
+ */
+char *mnt_resolve_tag(const char *token, const char *value,
+ struct libmnt_cache *cache)
+{
+ char *p = NULL;
+
+ /*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s",
+ token, value));*/
+
+ if (!token || !value)
+ return NULL;
+
+ if (cache)
+ p = (char *) cache_find_tag(cache, token, value);
+
+ if (!p) {
+ /* returns newly allocated string */
+ p = blkid_evaluate_tag(token, value, cache ? &cache->bc : NULL);
+
+ if (p && cache &&
+ cache_add_tag(cache, token, value, p, 0))
+ goto error;
+ }
+
+ return p;
+error:
+ free(p);
+ return NULL;
+}
+
+
+
+/**
+ * mnt_resolve_spec:
+ * @spec: path or tag
+ * @cache: paths cache
+ *
+ * Returns: canonicalized path or NULL. The result has to be
+ * deallocated by free() if @cache is NULL.
+ */
+char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache)
+{
+ char *cn = NULL;
+ char *t = NULL, *v = NULL;
+
+ if (!spec)
+ return NULL;
+
+ if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t))
+ cn = mnt_resolve_tag(t, v, cache);
+ else
+ cn = mnt_resolve_path(spec, cache);
+
+ free(t);
+ free(v);
+ return cn;
+}
+
+
+#ifdef TEST_PROGRAM
+
+static int test_resolve_path(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char line[BUFSIZ];
+ struct libmnt_cache *cache;
+
+ cache = mnt_new_cache();
+ if (!cache)
+ return -ENOMEM;
+
+ while(fgets(line, sizeof(line), stdin)) {
+ size_t sz = strlen(line);
+ char *p;
+
+ if (sz > 0 && line[sz - 1] == '\n')
+ line[sz - 1] = '\0';
+
+ p = mnt_resolve_path(line, cache);
+ printf("%s : %s\n", line, p);
+ }
+ mnt_unref_cache(cache);
+ return 0;
+}
+
+static int test_resolve_spec(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char line[BUFSIZ];
+ struct libmnt_cache *cache;
+
+ cache = mnt_new_cache();
+ if (!cache)
+ return -ENOMEM;
+
+ while(fgets(line, sizeof(line), stdin)) {
+ size_t sz = strlen(line);
+ char *p;
+
+ if (sz > 0 && line[sz - 1] == '\n')
+ line[sz - 1] = '\0';
+
+ p = mnt_resolve_spec(line, cache);
+ printf("%s : %s\n", line, p);
+ }
+ mnt_unref_cache(cache);
+ return 0;
+}
+
+static int test_read_tags(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char line[BUFSIZ];
+ struct libmnt_cache *cache;
+ size_t i;
+
+ cache = mnt_new_cache();
+ if (!cache)
+ return -ENOMEM;
+
+ while(fgets(line, sizeof(line), stdin)) {
+ size_t sz = strlen(line);
+ char *t = NULL, *v = NULL;
+
+ if (sz > 0 && line[sz - 1] == '\n')
+ line[sz - 1] = '\0';
+
+ if (!strcmp(line, "quit"))
+ break;
+
+ if (*line == '/') {
+ if (mnt_cache_read_tags(cache, line) < 0)
+ fprintf(stderr, "%s: read tags failed\n", line);
+
+ } else if (blkid_parse_tag_string(line, &t, &v) == 0) {
+ const char *cn = NULL;
+
+ if (mnt_valid_tagname(t))
+ cn = cache_find_tag(cache, t, v);
+ free(t);
+ free(v);
+
+ if (cn)
+ printf("%s: %s\n", line, cn);
+ else
+ printf("%s: not cached\n", line);
+ }
+ }
+
+ for (i = 0; i < cache->nents; i++) {
+ struct mnt_cache_entry *e = &cache->ents[i];
+ if (!(e->flag & MNT_CACHE_ISTAG))
+ continue;
+
+ printf("%15s : %5s : %s\n", e->value, e->key,
+ e->key + strlen(e->key) + 1);
+ }
+
+ mnt_unref_cache(cache);
+ return 0;
+
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test ts[] = {
+ { "--resolve-path", test_resolve_path, " resolve paths from stdin" },
+ { "--resolve-spec", test_resolve_spec, " evaluate specs from stdin" },
+ { "--read-tags", test_read_tags, " read devname or TAG from stdin (\"quit\" to exit)" },
+ { NULL }
+ };
+
+ return mnt_run_test(ts, argc, argv);
+}
+#endif
diff --git a/libmount/src/context.c b/libmount/src/context.c
new file mode 100644
index 0000000..69fd8bf
--- /dev/null
+++ b/libmount/src/context.c
@@ -0,0 +1,3531 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: context
+ * @title: Library high-level context
+ * @short_description: high-level API to mount/umount devices.
+ *
+ * <informalexample>
+ * <programlisting>
+ * struct libmnt_context *cxt = mnt_new_context();
+ *
+ * mnt_context_set_options(cxt, "aaa,bbb,ccc=CCC");
+ * mnt_context_set_mflags(cxt, MS_NOATIME|MS_NOEXEC);
+ * mnt_context_set_target(cxt, "/mnt/foo");
+ *
+ * if (!mnt_context_mount(cxt))
+ * printf("successfully mounted\n");
+ * mnt_free_context(cxt);
+ *
+ * </programlisting>
+ * </informalexample>
+ *
+ * This code is similar to:
+ *
+ * mount -o aaa,bbb,ccc=CCC,noatime,noexec /mnt/foo
+ *
+ */
+
+#include "mountP.h"
+#include "fileutils.h"
+#include "strutils.h"
+#include "namespace.h"
+
+#include <sys/wait.h>
+
+/**
+ * mnt_new_context:
+ *
+ * Returns: newly allocated mount context
+ */
+struct libmnt_context *mnt_new_context(void)
+{
+ struct libmnt_context *cxt;
+ uid_t ruid, euid;
+
+ cxt = calloc(1, sizeof(*cxt));
+ if (!cxt)
+ return NULL;
+
+ INIT_LIST_HEAD(&cxt->addmounts);
+
+ ruid = getuid();
+ euid = geteuid();
+
+ mnt_context_reset_status(cxt);
+
+ cxt->loopdev_fd = -1;
+
+ cxt->ns_orig.fd = -1;
+ cxt->ns_tgt.fd = -1;
+ cxt->ns_cur = &cxt->ns_orig;
+
+ /* if we're really root and aren't running setuid */
+ cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
+
+ DBG(CXT, ul_debugobj(cxt, "----> allocate %s",
+ cxt->restricted ? "[RESTRICTED]" : ""));
+
+
+ return cxt;
+}
+
+/**
+ * mnt_free_context:
+ * @cxt: mount context
+ *
+ * Deallocates context struct.
+ */
+void mnt_free_context(struct libmnt_context *cxt)
+{
+ if (!cxt)
+ return;
+
+ mnt_reset_context(cxt);
+
+ free(cxt->fstype_pattern);
+ free(cxt->optstr_pattern);
+ free(cxt->tgt_prefix);
+
+ mnt_unref_table(cxt->fstab);
+ mnt_unref_cache(cxt->cache);
+ mnt_unref_fs(cxt->fs);
+ mnt_unref_fs(cxt->fs_template);
+
+ mnt_context_clear_loopdev(cxt);
+ mnt_free_lock(cxt->lock);
+ mnt_free_update(cxt->update);
+
+ mnt_context_set_target_ns(cxt, NULL);
+
+ free(cxt->children);
+
+ DBG(CXT, ul_debugobj(cxt, "<---- free"));
+ free(cxt);
+}
+
+/**
+ * mnt_reset_context:
+ * @cxt: mount context
+ *
+ * Resets all information in the context that is directly related to
+ * the latest mount (spec, source, target, mount options, ...).
+ *
+ * The match patterns, target namespace, prefix, cached fstab, cached canonicalized
+ * paths and tags and [e]uid are not reset. You have to use
+ *
+ * mnt_context_set_fstab(cxt, NULL);
+ * mnt_context_set_cache(cxt, NULL);
+ * mnt_context_set_fstype_pattern(cxt, NULL);
+ * mnt_context_set_options_pattern(cxt, NULL);
+ * mnt_context_set_target_ns(cxt, NULL);
+ *
+ *
+ * to reset this stuff.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_reset_context(struct libmnt_context *cxt)
+{
+ int fl;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "<---- reset [status=%d] ---->",
+ mnt_context_get_status(cxt)));
+
+ fl = cxt->flags;
+
+ mnt_unref_fs(cxt->fs);
+ mnt_unref_table(cxt->mtab);
+ mnt_unref_table(cxt->utab);
+
+ free(cxt->helper);
+ free(cxt->orig_user);
+ free(cxt->subdir);
+
+ cxt->fs = NULL;
+ cxt->mtab = NULL;
+ cxt->utab = NULL;
+ cxt->helper = NULL;
+ cxt->orig_user = NULL;
+ cxt->mountflags = 0;
+ cxt->user_mountflags = 0;
+ cxt->mountdata = NULL;
+ cxt->subdir = NULL;
+ cxt->flags = MNT_FL_DEFAULT;
+
+ /* free additional mounts list */
+ while (!list_empty(&cxt->addmounts)) {
+ struct libmnt_addmount *ad = list_entry(cxt->addmounts.next,
+ struct libmnt_addmount,
+ mounts);
+ mnt_free_addmount(ad);
+ }
+
+ mnt_context_reset_status(cxt);
+
+ if (cxt->table_fltrcb)
+ mnt_context_set_tabfilter(cxt, NULL, NULL);
+
+ /* restore non-resettable flags */
+ cxt->flags |= (fl & MNT_FL_NOMTAB);
+ cxt->flags |= (fl & MNT_FL_FAKE);
+ cxt->flags |= (fl & MNT_FL_SLOPPY);
+ cxt->flags |= (fl & MNT_FL_VERBOSE);
+ cxt->flags |= (fl & MNT_FL_NOHELPERS);
+ cxt->flags |= (fl & MNT_FL_LOOPDEL);
+ cxt->flags |= (fl & MNT_FL_LAZY);
+ cxt->flags |= (fl & MNT_FL_FORK);
+ cxt->flags |= (fl & MNT_FL_FORCE);
+ cxt->flags |= (fl & MNT_FL_NOCANONICALIZE);
+ cxt->flags |= (fl & MNT_FL_RDONLY_UMOUNT);
+ cxt->flags |= (fl & MNT_FL_RWONLY_MOUNT);
+ cxt->flags |= (fl & MNT_FL_NOSWAPMATCH);
+ cxt->flags |= (fl & MNT_FL_TABPATHS_CHECKED);
+
+ mnt_context_apply_template(cxt);
+
+ return 0;
+}
+
+/*
+ * Saves the current context FS setting (mount options, etc) to make it usable after
+ * mnt_reset_context() or by mnt_context_apply_template(). This is usable for
+ * example for mnt_context_next_mount() where for the next mount operation we
+ * need to restore to the original context setting.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_save_template(struct libmnt_context *cxt)
+{
+ struct libmnt_fs *fs = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "save FS as template"));
+
+ if (cxt->fs) {
+ fs = mnt_copy_fs(NULL, cxt->fs);
+ if (!fs)
+ return -ENOMEM;
+ }
+
+ mnt_unref_fs(cxt->fs_template);
+ cxt->fs_template = fs;
+
+ return 0;
+}
+
+/*
+ * Restores context FS setting from previously saved template (see
+ * mnt_context_save_template()).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_apply_template(struct libmnt_context *cxt)
+{
+ struct libmnt_fs *fs = NULL;
+ int rc = 0;
+
+ if (!cxt)
+ return -EINVAL;
+
+ if (cxt->fs_template) {
+ DBG(CXT, ul_debugobj(cxt, "copy FS from template"));
+ fs = mnt_copy_fs(NULL, cxt->fs_template);
+ if (!fs)
+ return -ENOMEM;
+ rc = mnt_context_set_fs(cxt, fs);
+ mnt_unref_fs(fs);
+ } else {
+ DBG(CXT, ul_debugobj(cxt, "no FS template, reset only"));
+ mnt_unref_fs(cxt->fs);
+ cxt->fs = NULL;
+ }
+
+ return rc;
+}
+
+int mnt_context_has_template(struct libmnt_context *cxt)
+{
+ return cxt && cxt->fs_template ? 1 : 0;
+}
+
+struct libmnt_context *mnt_copy_context(struct libmnt_context *o)
+{
+ struct libmnt_context *n;
+
+ n = mnt_new_context();
+ if (!n)
+ return NULL;
+
+ DBG(CXT, ul_debugobj(n, "<---- clone ---->"));
+
+ n->flags = o->flags;
+
+ if (o->fs) {
+ n->fs = mnt_copy_fs(NULL, o->fs);
+ if (!n->fs)
+ goto failed;
+ }
+
+ n->mtab = o->mtab;
+ mnt_ref_table(n->mtab);
+
+ n->mtab = o->utab;
+ mnt_ref_table(n->utab);
+
+ if (strdup_between_structs(n, o, tgt_prefix))
+ goto failed;
+ if (strdup_between_structs(n, o, helper))
+ goto failed;
+ if (strdup_between_structs(n, o, orig_user))
+ goto failed;
+ if (strdup_between_structs(n, o, subdir))
+ goto failed;
+
+ n->mountflags = o->mountflags;
+ n->mountdata = o->mountdata;
+
+ mnt_context_reset_status(n);
+
+ n->table_fltrcb = o->table_fltrcb;
+ n->table_fltrcb_data = o->table_fltrcb_data;
+
+ return n;
+failed:
+ mnt_free_context(n);
+ return NULL;
+}
+
+/**
+ * mnt_context_reset_status:
+ * @cxt: context
+ *
+ * Resets mount(2) and mount.type statuses, so mnt_context_do_mount() or
+ * mnt_context_do_umount() could be again called with the same settings.
+ *
+ * BE CAREFUL -- after this soft reset the libmount will NOT parse mount
+ * options, evaluate permissions or apply stuff from fstab.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_reset_status(struct libmnt_context *cxt)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ cxt->syscall_status = 1; /* means not called yet */
+ cxt->helper_exec_status = 1;
+ cxt->helper_status = 0;
+ return 0;
+}
+
+static int context_init_paths(struct libmnt_context *cxt, int writable)
+{
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (!cxt->mtab_path) {
+ cxt->mtab_path = mnt_get_mtab_path();
+ DBG(CXT, ul_debugobj(cxt, "mtab path initialized to: %s", cxt->mtab_path));
+ }
+#endif
+ if (!cxt->utab_path) {
+ cxt->utab_path = mnt_get_utab_path();
+ DBG(CXT, ul_debugobj(cxt, "utab path initialized to: %s", cxt->utab_path));
+ }
+
+ if (!writable)
+ return 0; /* only paths wanted */
+ if (mnt_context_is_nomtab(cxt))
+ return 0; /* write mode overridden by mount -n */
+ if (cxt->flags & MNT_FL_TABPATHS_CHECKED)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "checking for writable tab files"));
+
+ cxt->mtab_writable = 0;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ mnt_has_regular_mtab(&cxt->mtab_path, &cxt->mtab_writable);
+ if (!cxt->mtab_writable)
+#endif
+ /* use /run/mount/utab if /etc/mtab is useless */
+ mnt_has_regular_utab(&cxt->utab_path, &cxt->utab_writable);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ cxt->flags |= MNT_FL_TABPATHS_CHECKED;
+ return 0;
+}
+
+int mnt_context_mtab_writable(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ context_init_paths(cxt, 1);
+ return cxt->mtab_writable == 1;
+}
+
+int mnt_context_utab_writable(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ context_init_paths(cxt, 1);
+ return cxt->utab_writable == 1;
+}
+
+const char *mnt_context_get_writable_tabpath(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ context_init_paths(cxt, 1);
+ return cxt->mtab_writable ? cxt->mtab_path : cxt->utab_path;
+}
+
+
+static int set_flag(struct libmnt_context *cxt, int flag, int enable)
+{
+ if (!cxt)
+ return -EINVAL;
+ if (enable) {
+ DBG(CXT, ul_debugobj(cxt, "enabling flag %04x", flag));
+ cxt->flags |= flag;
+ } else {
+ DBG(CXT, ul_debugobj(cxt, "disabling flag %04x", flag));
+ cxt->flags &= ~flag;
+ }
+ return 0;
+}
+
+/**
+ * mnt_context_is_restricted:
+ * @cxt: mount context
+ *
+ * Returns: 0 for an unrestricted mount (user is root), or 1 for non-root mounts
+ */
+int mnt_context_is_restricted(struct libmnt_context *cxt)
+{
+ return cxt->restricted;
+}
+
+/**
+ * mnt_context_force_unrestricted:
+ * @cxt: mount context
+ *
+ * This function is DANGEROURS as it disables all security policies in libmount.
+ * Don't use if not sure. It removes "restricted" flag from the context, so
+ * libmount will use the current context as for root user.
+ *
+ * This function is designed for case you have no any suid permissions, so you
+ * can depend on kernel.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ *
+ * Since: 2.35
+ */
+int mnt_context_force_unrestricted(struct libmnt_context *cxt)
+{
+ if (mnt_context_is_restricted(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "force UNRESTRICTED"));
+ cxt->restricted = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * mnt_context_set_optsmode
+ * @cxt: mount context
+ * @mode: MNT_OMODE_* flags
+ *
+ * Controls how to use mount optionssource and target paths from fstab/mtab.
+ *
+ * @MNT_OMODE_IGNORE: ignore mtab/fstab options
+ *
+ * @MNT_OMODE_APPEND: append mtab/fstab options to existing options
+ *
+ * @MNT_OMODE_PREPEND: prepend mtab/fstab options to existing options
+ *
+ * @MNT_OMODE_REPLACE: replace existing options with options from mtab/fstab
+ *
+ * @MNT_OMODE_FORCE: always read mtab/fstab (although source and target are defined)
+ *
+ * @MNT_OMODE_FSTAB: read from fstab
+ *
+ * @MNT_OMODE_MTAB: read from mtab if fstab not enabled or failed
+ *
+ * @MNT_OMODE_NOTAB: do not read fstab/mtab at all
+ *
+ * @MNT_OMODE_AUTO: default mode (MNT_OMODE_PREPEND | MNT_OMODE_FSTAB | MNT_OMODE_MTAB)
+ *
+ * @MNT_OMODE_USER: default for non-root users (MNT_OMODE_REPLACE | MNT_OMODE_FORCE | MNT_OMODE_FSTAB)
+ *
+ * Notes:
+ *
+ * - MNT_OMODE_USER is always used if mount context is in restricted mode
+ * - MNT_OMODE_AUTO is used if nothing else is defined
+ * - the flags are evaluated in this order: MNT_OMODE_NOTAB, MNT_OMODE_FORCE,
+ * MNT_OMODE_FSTAB, MNT_OMODE_MTAB and then the mount options from fstab/mtab
+ * are set according to MNT_OMODE_{IGNORE,APPEND,PREPEND,REPLACE}
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_optsmode(struct libmnt_context *cxt, int mode)
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->optsmode = mode;
+ return 0;
+}
+
+/**
+ * mnt_context_get_optsmode
+ * @cxt: mount context
+ *
+ * Returns: MNT_OMODE_* mask or zero.
+ */
+
+int mnt_context_get_optsmode(struct libmnt_context *cxt)
+{
+ return cxt->optsmode;
+}
+
+/**
+ * mnt_context_disable_canonicalize:
+ * @cxt: mount context
+ * @disable: TRUE or FALSE
+ *
+ * Enable/disable paths canonicalization and tags evaluation. The libmount context
+ * canonicalizes paths when searching in fstab and when preparing source and target paths
+ * for mount(2) syscall.
+ *
+ * This function has an effect on the private (within context) fstab instance only
+ * (see mnt_context_set_fstab()). If you want to use an external fstab then you
+ * need to manage your private struct libmnt_cache (see mnt_table_set_cache(fstab,
+ * NULL).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_disable_canonicalize(struct libmnt_context *cxt, int disable)
+{
+ return set_flag(cxt, MNT_FL_NOCANONICALIZE, disable);
+}
+
+/**
+ * mnt_context_is_nocanonicalize:
+ * @cxt: mount context
+ *
+ * Returns: 1 if no-canonicalize mode is enabled or 0.
+ */
+int mnt_context_is_nocanonicalize(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_NOCANONICALIZE ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_lazy:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable lazy umount (see umount(8) man page, option -l).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_lazy(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_LAZY, enable);
+}
+
+/**
+ * mnt_context_is_lazy:
+ * @cxt: mount context
+ *
+ * Returns: 1 if lazy umount is enabled or 0
+ */
+int mnt_context_is_lazy(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_LAZY ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_fork:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable fork(2) call in mnt_context_next_mount() (see mount(8) man
+ * page, option -F).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_fork(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_FORK, enable);
+}
+
+/**
+ * mnt_context_is_fork:
+ * @cxt: mount context
+ *
+ * Returns: 1 if fork (mount -F) is enabled or 0
+ */
+int mnt_context_is_fork(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_FORK ? 1 : 0;
+}
+
+/**
+ * mnt_context_is_parent:
+ * @cxt: mount context
+ *
+ * Return: 1 if mount -F enabled and the current context is parent, or 0
+ */
+int mnt_context_is_parent(struct libmnt_context *cxt)
+{
+ return mnt_context_is_fork(cxt) && cxt->pid == 0;
+}
+
+/**
+ * mnt_context_is_child:
+ * @cxt: mount context
+ *
+ * Return: 1 f the current context is child, or 0
+ */
+int mnt_context_is_child(struct libmnt_context *cxt)
+{
+ /* See mnt_fork_context(), the for fork flag is always disabled
+ * for children to avoid recursive forking.
+ */
+ return !mnt_context_is_fork(cxt) && cxt->pid;
+}
+
+/**
+ * mnt_context_enable_rdonly_umount:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable read-only remount on failed umount(2)
+ * (see umount(8) man page, option -r).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_rdonly_umount(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_RDONLY_UMOUNT, enable);
+}
+
+/**
+ * mnt_context_is_rdonly_umount
+ * @cxt: mount context
+ *
+ * See also mnt_context_enable_rdonly_umount() and umount(8) man page,
+ * option -r.
+ *
+ * Returns: 1 if read-only remount failed umount(2) is enables or 0
+ */
+int mnt_context_is_rdonly_umount(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_RDONLY_UMOUNT ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_rwonly_mount:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Force read-write mount; if enabled libmount will never try MS_RDONLY
+ * after failed mount(2) EROFS. (See mount(8) man page, option -w).
+ *
+ * Since: 2.30
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_rwonly_mount(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_RWONLY_MOUNT, enable);
+}
+
+/**
+ * mnt_context_is_rwonly_mount
+ * @cxt: mount context
+ *
+ * See also mnt_context_enable_rwonly_mount() and mount(8) man page,
+ * option -w.
+ *
+ * Since: 2.30
+ *
+ * Returns: 1 if only read-write mount is allowed.
+ */
+int mnt_context_is_rwonly_mount(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_RWONLY_MOUNT ? 1 : 0;
+}
+
+/**
+ * mnt_context_forced_rdonly:
+ * @cxt: mount context
+ *
+ * See also mnt_context_enable_rwonly_mount().
+ *
+ * Since: 2.30
+ *
+ * Returns: 1 if mounted read-only on write-protected device.
+ */
+int mnt_context_forced_rdonly(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_FORCED_RDONLY ? 1 : 0;
+}
+
+/**
+ * mnt_context_disable_helpers:
+ * @cxt: mount context
+ * @disable: TRUE or FALSE
+ *
+ * Enable/disable /sbin/[u]mount.* helpers (see mount(8) man page, option -i).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_disable_helpers(struct libmnt_context *cxt, int disable)
+{
+ return set_flag(cxt, MNT_FL_NOHELPERS, disable);
+}
+
+/**
+ * mnt_context_is_nohelpers
+ * @cxt: mount context
+ *
+ * Returns: 1 if helpers are disabled (mount -i) or 0
+ */
+int mnt_context_is_nohelpers(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_NOHELPERS ? 1 : 0;
+}
+
+
+/**
+ * mnt_context_enable_sloppy:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Set/unset sloppy mounting (see mount(8) man page, option -s).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_sloppy(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_SLOPPY, enable);
+}
+
+/**
+ * mnt_context_is_sloppy:
+ * @cxt: mount context
+ *
+ * Returns: 1 if sloppy flag is enabled or 0
+ */
+int mnt_context_is_sloppy(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_SLOPPY ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_fake:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable fake mounting (see mount(8) man page, option -f).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_fake(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_FAKE, enable);
+}
+
+/**
+ * mnt_context_is_fake:
+ * @cxt: mount context
+ *
+ * Returns: 1 if fake flag is enabled or 0
+ */
+int mnt_context_is_fake(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_FAKE ? 1 : 0;
+}
+
+/**
+ * mnt_context_disable_mtab:
+ * @cxt: mount context
+ * @disable: TRUE or FALSE
+ *
+ * Disable/enable mtab update (see mount(8) man page, option -n).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_disable_mtab(struct libmnt_context *cxt, int disable)
+{
+ return set_flag(cxt, MNT_FL_NOMTAB, disable);
+}
+
+/**
+ * mnt_context_is_nomtab:
+ * @cxt: mount context
+ *
+ * Returns: 1 if no-mtab is enabled or 0
+ */
+int mnt_context_is_nomtab(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_NOMTAB ? 1 : 0;
+}
+
+/**
+ * mnt_context_disable_swapmatch:
+ * @cxt: mount context
+ * @disable: TRUE or FALSE
+ *
+ * Disable/enable swap between source and target for mount(8) if only one path
+ * is specified.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_disable_swapmatch(struct libmnt_context *cxt, int disable)
+{
+ return set_flag(cxt, MNT_FL_NOSWAPMATCH, disable);
+}
+
+/**
+ * mnt_context_is_swapmatch:
+ * @cxt: mount context
+ *
+ * Returns: 1 if swap between source and target is allowed (default is 1) or 0.
+ */
+int mnt_context_is_swapmatch(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_NOSWAPMATCH ? 0 : 1;
+}
+
+/**
+ * mnt_context_enable_force:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable force umounting (see umount(8) man page, option -f).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_force(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_FORCE, enable);
+}
+
+/**
+ * mnt_context_is_force
+ * @cxt: mount context
+ *
+ * Returns: 1 if force umounting flag is enabled or 0
+ */
+int mnt_context_is_force(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_FORCE ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_verbose:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable verbose output (TODO: not implemented yet)
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_verbose(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_VERBOSE, enable);
+}
+
+/**
+ * mnt_context_is_verbose
+ * @cxt: mount context
+ *
+ * Returns: 1 if verbose flag is enabled or 0
+ */
+int mnt_context_is_verbose(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_VERBOSE ? 1 : 0;
+}
+
+/**
+ * mnt_context_enable_loopdel:
+ * @cxt: mount context
+ * @enable: TRUE or FALSE
+ *
+ * Enable/disable the loop delete (destroy) after umount (see umount(8), option -d)
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_enable_loopdel(struct libmnt_context *cxt, int enable)
+{
+ return set_flag(cxt, MNT_FL_LOOPDEL, enable);
+}
+
+/**
+ * mnt_context_is_loopdel:
+ * @cxt: mount context
+ *
+ * Returns: 1 if loop device should be deleted after umount (umount -d) or 0.
+ */
+int mnt_context_is_loopdel(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_LOOPDEL ? 1 : 0;
+}
+
+/**
+ * mnt_context_set_fs:
+ * @cxt: mount context
+ * @fs: filesystem description
+ *
+ * The mount context uses private @fs by default. This function can be used to
+ * overwrite the private @fs with an external instance. This function
+ * increments @fs reference counter (and decrement reference counter of the
+ * old fs).
+ *
+ * The @fs will be modified by mnt_context_set_{source,target,options,fstype}
+ * functions, If the @fs is NULL, then all current FS specific settings (source,
+ * target, etc., exclude spec) are reset.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_fs(struct libmnt_context *cxt, struct libmnt_fs *fs)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "setting new FS"));
+ mnt_ref_fs(fs); /* new */
+ mnt_unref_fs(cxt->fs); /* old */
+ cxt->fs = fs;
+ return 0;
+}
+
+/**
+ * mnt_context_get_fs:
+ * @cxt: mount context
+ *
+ * The FS contains the basic description of mountpoint, fs type and so on.
+ * Note that the FS is modified by mnt_context_set_{source,target,options,fstype}
+ * functions.
+ *
+ * Returns: pointer to FS description or NULL in case of a calloc() error.
+ */
+struct libmnt_fs *mnt_context_get_fs(struct libmnt_context *cxt)
+{
+ if (!cxt)
+ return NULL;
+ if (!cxt->fs)
+ cxt->fs = mnt_new_fs();
+ return cxt->fs;
+}
+
+/**
+ * mnt_context_get_fs_userdata:
+ * @cxt: mount context
+ *
+ * Returns: pointer to userdata or NULL.
+ */
+void *mnt_context_get_fs_userdata(struct libmnt_context *cxt)
+{
+ return cxt->fs ? mnt_fs_get_userdata(cxt->fs) : NULL;
+}
+
+/**
+ * mnt_context_get_fstab_userdata:
+ * @cxt: mount context
+ *
+ * Returns: pointer to userdata or NULL.
+ */
+void *mnt_context_get_fstab_userdata(struct libmnt_context *cxt)
+{
+ return cxt->fstab ? mnt_table_get_userdata(cxt->fstab) : NULL;
+}
+
+/**
+ * mnt_context_get_mtab_userdata:
+ * @cxt: mount context
+ *
+ * Returns: pointer to userdata or NULL.
+ */
+void *mnt_context_get_mtab_userdata(struct libmnt_context *cxt)
+{
+ return cxt->mtab ? mnt_table_get_userdata(cxt->mtab) : NULL;
+}
+
+/**
+ * mnt_context_set_source:
+ * @cxt: mount context
+ * @source: mount source (device, directory, UUID, LABEL, ...)
+ *
+ * Note that libmount does not interpret "nofail" (MNT_MS_NOFAIL)
+ * mount option. The real return code is always returned, when
+ * the device does not exist then it's usually MNT_ERR_NOSOURCE
+ * from libmount or ENOENT, ENOTDIR, ENOTBLK, ENXIO from mount(2).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_source(struct libmnt_context *cxt, const char *source)
+{
+ return mnt_fs_set_source(mnt_context_get_fs(cxt), source);
+}
+
+/**
+ * mnt_context_get_source:
+ * @cxt: mount context
+ *
+ * Returns: returns pointer or NULL in case of error or if not set.
+ */
+const char *mnt_context_get_source(struct libmnt_context *cxt)
+{
+ return mnt_fs_get_source(mnt_context_get_fs(cxt));
+}
+
+/**
+ * mnt_context_set_target:
+ * @cxt: mount context
+ * @target: mountpoint
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_target(struct libmnt_context *cxt, const char *target)
+{
+ return mnt_fs_set_target(mnt_context_get_fs(cxt), target);
+}
+
+/**
+ * mnt_context_get_target:
+ * @cxt: mount context
+ *
+ * Returns: returns pointer or NULL in case of error or if not set.
+ */
+const char *mnt_context_get_target(struct libmnt_context *cxt)
+{
+ return mnt_fs_get_target(mnt_context_get_fs(cxt));
+}
+
+/**
+ * mnt_context_set_target_prefix:
+ * @cxt: mount context
+ * @path: mountpoint prefix
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_target_prefix(struct libmnt_context *cxt, const char *path)
+{
+ char *p = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+ if (path) {
+ p = strdup(path);
+ if (!p)
+ return -ENOMEM;
+ }
+ free(cxt->tgt_prefix);
+ cxt->tgt_prefix = p;
+
+ return 0;
+}
+
+/**
+ * mnt_context_get_target_prefix:
+ * @cxt: mount context
+ *
+ * Returns: returns pointer or NULL in case of error or if not set.
+ */
+const char *mnt_context_get_target_prefix(struct libmnt_context *cxt)
+{
+ return cxt ? cxt->tgt_prefix : NULL;
+}
+
+
+/**
+ * mnt_context_set_fstype:
+ * @cxt: mount context
+ * @fstype: filesystem type
+ *
+ * Note that the @fstype has to be a FS type. For patterns with
+ * comma-separated list of filesystems or for the "nofs" notation, use
+ * mnt_context_set_fstype_pattern().
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_fstype(struct libmnt_context *cxt, const char *fstype)
+{
+ return mnt_fs_set_fstype(mnt_context_get_fs(cxt), fstype);
+}
+
+/**
+ * mnt_context_get_fstype:
+ * @cxt: mount context
+ *
+ * Returns: pointer or NULL in case of error or if not set.
+ */
+const char *mnt_context_get_fstype(struct libmnt_context *cxt)
+{
+ return mnt_fs_get_fstype(mnt_context_get_fs(cxt));
+}
+
+/**
+ * mnt_context_set_options:
+ * @cxt: mount context
+ * @optstr: comma delimited mount options
+ *
+ * Note that MS_MOVE cannot be specified as "string". It's operation that
+ * is no supported in fstab (etc.)
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_options(struct libmnt_context *cxt, const char *optstr)
+{
+ return mnt_fs_set_options(mnt_context_get_fs(cxt), optstr);
+}
+
+/**
+ * mnt_context_append_options:
+ * @cxt: mount context
+ * @optstr: comma delimited mount options
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_append_options(struct libmnt_context *cxt, const char *optstr)
+{
+ return mnt_fs_append_options(mnt_context_get_fs(cxt), optstr);
+}
+
+/**
+ * mnt_context_get_options:
+ * @cxt: mount context
+ *
+ * This function returns mount options set by mnt_context_set_options() or
+ * mnt_context_append_options().
+ *
+ * Note that *after* mnt_context_prepare_mount(), the mount options string
+ * may also include options set by mnt_context_set_mflags() or other options
+ * generated by this library.
+ *
+ * Returns: pointer or NULL
+ */
+const char *mnt_context_get_options(struct libmnt_context *cxt)
+{
+ return mnt_fs_get_options(mnt_context_get_fs(cxt));
+}
+
+/**
+ * mnt_context_set_fstype_pattern:
+ * @cxt: mount context
+ * @pattern: FS name pattern (or NULL to reset the current setting)
+ *
+ * See mount(8), option -t.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_fstype_pattern(struct libmnt_context *cxt, const char *pattern)
+{
+ char *p = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+ if (pattern) {
+ p = strdup(pattern);
+ if (!p)
+ return -ENOMEM;
+ }
+ free(cxt->fstype_pattern);
+ cxt->fstype_pattern = p;
+ return 0;
+}
+
+/**
+ * mnt_context_set_options_pattern:
+ * @cxt: mount context
+ * @pattern: options pattern (or NULL to reset the current setting)
+ *
+ * See mount(8), option -O.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_options_pattern(struct libmnt_context *cxt, const char *pattern)
+{
+ char *p = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+ if (pattern) {
+ p = strdup(pattern);
+ if (!p)
+ return -ENOMEM;
+ }
+ free(cxt->optstr_pattern);
+ cxt->optstr_pattern = p;
+ return 0;
+}
+
+/**
+ * mnt_context_set_fstab:
+ * @cxt: mount context
+ * @tb: fstab
+ *
+ * The mount context reads /etc/fstab to the private struct libmnt_table by default.
+ * This function can be used to overwrite the private fstab with an external
+ * instance.
+ *
+ * This function modify the @tb reference counter. This function does not set
+ * the cache for the @tb. You have to explicitly call mnt_table_set_cache(tb,
+ * mnt_context_get_cache(cxt));
+ *
+ * The fstab is used read-only and is not modified, it should be possible to
+ * share the fstab between more mount contexts (TODO: test it.)
+ *
+ * If the @tb argument is NULL, then the current private fstab instance is
+ * reset.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_fstab(struct libmnt_context *cxt, struct libmnt_table *tb)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ mnt_ref_table(tb); /* new */
+ mnt_unref_table(cxt->fstab); /* old */
+
+ cxt->fstab = tb;
+ return 0;
+}
+
+/**
+ * mnt_context_get_fstab:
+ * @cxt: mount context
+ * @tb: returns fstab
+ *
+ * See also mnt_table_parse_fstab() for more details about fstab.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_get_fstab(struct libmnt_context *cxt, struct libmnt_table **tb)
+{
+ struct libmnt_ns *ns_old;
+
+ if (!cxt)
+ return -EINVAL;
+ if (!cxt->fstab) {
+ int rc;
+
+ cxt->fstab = mnt_new_table();
+ if (!cxt->fstab)
+ return -ENOMEM;
+ if (cxt->table_errcb)
+ mnt_table_set_parser_errcb(cxt->fstab, cxt->table_errcb);
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ mnt_table_set_cache(cxt->fstab, mnt_context_get_cache(cxt));
+ rc = mnt_table_parse_fstab(cxt->fstab, NULL);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ if (rc)
+ return rc;
+ }
+
+ if (tb)
+ *tb = cxt->fstab;
+ return 0;
+}
+
+/**
+ * mnt_context_get_mtab:
+ * @cxt: mount context
+ * @tb: returns mtab
+ *
+ * See also mnt_table_parse_mtab() for more details about mtab/mountinfo. The
+ * result will be deallocated by mnt_free_context(@cxt).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_get_mtab(struct libmnt_context *cxt, struct libmnt_table **tb)
+{
+ int rc = 0;
+ struct libmnt_ns *ns_old = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+ if (!cxt->mtab) {
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ context_init_paths(cxt, 0);
+
+ cxt->mtab = mnt_new_table();
+ if (!cxt->mtab) {
+ rc = -ENOMEM;
+ goto end;
+ }
+
+ if (cxt->table_errcb)
+ mnt_table_set_parser_errcb(cxt->mtab, cxt->table_errcb);
+ if (cxt->table_fltrcb)
+ mnt_table_set_parser_fltrcb(cxt->mtab,
+ cxt->table_fltrcb,
+ cxt->table_fltrcb_data);
+
+ mnt_table_set_cache(cxt->mtab, mnt_context_get_cache(cxt));
+ }
+
+ /* Read the table; it's empty, because this first mnt_context_get_mtab()
+ * call, or because /proc was not accessible in previous calls */
+ if (mnt_table_is_empty(cxt->mtab)) {
+ if (!ns_old) {
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+ }
+
+ /*
+ * Note that mtab_path is NULL if mtab is useless or unsupported
+ */
+ if (cxt->utab)
+ /* utab already parsed, don't parse it again */
+ rc = __mnt_table_parse_mtab(cxt->mtab,
+ cxt->mtab_path, cxt->utab);
+ else
+ rc = mnt_table_parse_mtab(cxt->mtab, cxt->mtab_path);
+ if (rc)
+ goto end;
+ }
+
+ if (tb)
+ *tb = cxt->mtab;
+
+ DBG(CXT, ul_debugobj(cxt, "mtab requested [nents=%d]",
+ mnt_table_get_nents(cxt->mtab)));
+
+end:
+ if (ns_old && !mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/*
+ * Called by mtab parser to filter out entries, non-zero means that
+ * an entry has to be filtered out.
+ */
+static int mtab_filter(struct libmnt_fs *fs, void *data)
+{
+ if (!fs || !data)
+ return 0;
+ if (mnt_fs_streq_target(fs, data))
+ return 0;
+ if (mnt_fs_streq_srcpath(fs, data))
+ return 0;
+ return 1;
+}
+
+/*
+ * The same like mnt_context_get_mtab(), but does not read all mountinfo/mtab
+ * file, but only entries relevant for @tgt.
+ */
+int mnt_context_get_mtab_for_target(struct libmnt_context *cxt,
+ struct libmnt_table **mtab,
+ const char *tgt)
+{
+ struct stat st;
+ struct libmnt_cache *cache = NULL;
+ char *cn_tgt = NULL;
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ if (mnt_context_is_nocanonicalize(cxt))
+ mnt_context_set_tabfilter(cxt, mtab_filter, (void *) tgt);
+
+ else if (mnt_stat_mountpoint(tgt, &st) == 0 && S_ISDIR(st.st_mode)) {
+ cache = mnt_context_get_cache(cxt);
+ cn_tgt = mnt_resolve_path(tgt, cache);
+ if (cn_tgt)
+ mnt_context_set_tabfilter(cxt, mtab_filter, cn_tgt);
+ }
+
+ rc = mnt_context_get_mtab(cxt, mtab);
+ mnt_context_set_tabfilter(cxt, NULL, NULL);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ if (cn_tgt && !cache)
+ free(cn_tgt);
+
+ return rc;
+}
+
+/*
+ * Allows to specify a filter for tab file entries. The filter is called by
+ * the table parser. Currently used for mtab and utab only.
+ */
+int mnt_context_set_tabfilter(struct libmnt_context *cxt,
+ int (*fltr)(struct libmnt_fs *, void *),
+ void *data)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ cxt->table_fltrcb = fltr;
+ cxt->table_fltrcb_data = data;
+
+ if (cxt->mtab)
+ mnt_table_set_parser_fltrcb(cxt->mtab,
+ cxt->table_fltrcb,
+ cxt->table_fltrcb_data);
+
+ DBG(CXT, ul_debugobj(cxt, "tabfilter %s", fltr ? "ENABLED!" : "disabled"));
+ return 0;
+}
+
+/**
+ * mnt_context_get_table:
+ * @cxt: mount context
+ * @filename: e.g. /proc/self/mountinfo
+ * @tb: returns the table
+ *
+ * This function allocates a new table and parses the @file. The parser error
+ * callback and cache for tags and paths is set according to the @cxt setting.
+ * See also mnt_table_parse_file().
+ *
+ * It's strongly recommended to use the mnt_context_get_mtab() and
+ * mnt_context_get_fstab() functions for mtab and fstab files. This function
+ * does not care about LIBMOUNT_* env.variables and does not merge userspace
+ * options.
+ *
+ * The result will NOT be deallocated by mnt_free_context(@cxt).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_get_table(struct libmnt_context *cxt,
+ const char *filename, struct libmnt_table **tb)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ if (!cxt || !tb)
+ return -EINVAL;
+
+ *tb = mnt_new_table();
+ if (!*tb)
+ return -ENOMEM;
+
+ if (cxt->table_errcb)
+ mnt_table_set_parser_errcb(*tb, cxt->table_errcb);
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = mnt_table_parse_file(*tb, filename);
+
+ if (rc) {
+ mnt_unref_table(*tb);
+ goto end;
+ }
+
+ mnt_table_set_cache(*tb, mnt_context_get_cache(cxt));
+
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_set_tables_errcb
+ * @cxt: mount context
+ * @cb: pointer to callback function
+ *
+ * The error callback is used for all tab files (e.g. mtab, fstab)
+ * parsed within the context.
+ *
+ * See also mnt_context_get_mtab(),
+ * mnt_context_get_fstab(),
+ * mnt_table_set_parser_errcb().
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_tables_errcb(struct libmnt_context *cxt,
+ int (*cb)(struct libmnt_table *tb, const char *filename, int line))
+{
+ if (!cxt)
+ return -EINVAL;
+
+ if (cxt->mtab)
+ mnt_table_set_parser_errcb(cxt->mtab, cb);
+ if (cxt->fstab)
+ mnt_table_set_parser_errcb(cxt->fstab, cb);
+
+ cxt->table_errcb = cb;
+ return 0;
+}
+
+/**
+ * mnt_context_set_cache:
+ * @cxt: mount context
+ * @cache: cache instance or NULL
+ *
+ * The mount context maintains a private struct libmnt_cache by default. This
+ * function can be used to overwrite the private cache with an external instance.
+ * This function increments cache reference counter.
+ *
+ * If the @cache argument is NULL, then the current cache instance is reset.
+ * This function apply the cache to fstab and mtab instances (if already
+ * exists).
+ *
+ * The old cache instance reference counter is de-incremented.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_cache(struct libmnt_context *cxt, struct libmnt_cache *cache)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ mnt_ref_cache(cache); /* new */
+ mnt_unref_cache(cxt->cache); /* old */
+
+ cxt->cache = cache;
+
+ if (cxt->mtab)
+ mnt_table_set_cache(cxt->mtab, cache);
+ if (cxt->fstab)
+ mnt_table_set_cache(cxt->fstab, cache);
+
+ return 0;
+}
+
+/**
+ * mnt_context_get_cache
+ * @cxt: mount context
+ *
+ * See also mnt_context_set_cache().
+ *
+ * Returns: pointer to cache or NULL if canonicalization is disabled.
+ */
+struct libmnt_cache *mnt_context_get_cache(struct libmnt_context *cxt)
+{
+ if (!cxt || mnt_context_is_nocanonicalize(cxt))
+ return NULL;
+
+ if (!cxt->cache) {
+ struct libmnt_cache *cache = mnt_new_cache();
+ mnt_context_set_cache(cxt, cache);
+ mnt_unref_cache(cache);
+ }
+ return cxt->cache;
+}
+
+/**
+ * mnt_context_set_passwd_cb:
+ * @cxt: mount context
+ * @get: callback to get password
+ * @release: callback to release (deallocate) password
+ *
+ * Sets callbacks for encryption password (e.g encrypted loopdev). This
+ * function is deprecated (encrypted loops are no longer supported).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_passwd_cb(struct libmnt_context *cxt,
+ char *(*get)(struct libmnt_context *),
+ void (*release)(struct libmnt_context *, char *))
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->pwd_get_cb = get;
+ cxt->pwd_release_cb = release;
+ return 0;
+}
+
+/**
+ * mnt_context_get_lock:
+ * @cxt: mount context
+ *
+ * The libmount applications don't have to care about mtab locking, but with a
+ * small exception: the application has to be able to remove the lock file when
+ * interrupted by signal or signals have to be ignored when the lock is locked.
+ *
+ * The default behavior is to ignore all signals (except SIGALRM and
+ * SIGTRAP for mtab update) when the lock is locked. If this behavior
+ * is unacceptable, then use:
+ *
+ * lc = mnt_context_get_lock(cxt);
+ * if (lc)
+ * mnt_lock_block_signals(lc, FALSE);
+ *
+ * and don't forget to call mnt_unlock_file(lc) before exit.
+ *
+ * Returns: pointer to lock struct or NULL.
+ */
+struct libmnt_lock *mnt_context_get_lock(struct libmnt_context *cxt)
+{
+ /*
+ * DON'T call this function within libmount, it will always allocate
+ * the lock. The mnt_update_* functions are able to allocate the lock
+ * only when mtab/utab update is really necessary.
+ */
+ if (!cxt || mnt_context_is_nomtab(cxt))
+ return NULL;
+
+ if (!cxt->lock) {
+ cxt->lock = mnt_new_lock(
+ mnt_context_get_writable_tabpath(cxt), 0);
+ if (cxt->lock)
+ mnt_lock_block_signals(cxt->lock, TRUE);
+ }
+ return cxt->lock;
+}
+
+/**
+ * mnt_context_set_mflags:
+ * @cxt: mount context
+ * @flags: mount(2) flags (MS_* flags)
+ *
+ * Sets mount flags (see mount(2) man page).
+ *
+ * Note that mount context can be used to define mount options by mount flags. It
+ * means you can for example use
+ *
+ * mnt_context_set_mflags(cxt, MS_NOEXEC | MS_NOSUID);
+ *
+ * rather than
+ *
+ * mnt_context_set_options(cxt, "noexec,nosuid");
+ *
+ * both of these calls have the same effect.
+ *
+ * Be careful if you want to use MS_REC flag -- in this case the bit is applied
+ * to all bind/slave/etc. options. If you want to mix more propadation flags
+ * and/or bind operations than it's better to specify mount options by
+ * strings.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_mflags(struct libmnt_context *cxt, unsigned long flags)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ cxt->mountflags = flags;
+
+ if ((cxt->flags & MNT_FL_MOUNTOPTS_FIXED) && cxt->fs)
+ /*
+ * the final mount options are already generated, refresh...
+ */
+ return mnt_optstr_apply_flags(
+ &cxt->fs->vfs_optstr,
+ cxt->mountflags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+
+ return 0;
+}
+
+/**
+ * mnt_context_get_mflags:
+ * @cxt: mount context
+ * @flags: returns MS_* mount flags
+ *
+ * Converts mount options string to MS_* flags and bitwise-OR the result with
+ * the already defined flags (see mnt_context_set_mflags()).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_get_mflags(struct libmnt_context *cxt, unsigned long *flags)
+{
+ int rc = 0;
+ struct list_head *p;
+
+ if (!cxt || !flags)
+ return -EINVAL;
+
+ *flags = 0;
+ if (!(cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) && cxt->fs) {
+ const char *o = mnt_fs_get_options(cxt->fs);
+ if (o)
+ rc = mnt_optstr_get_flags(o, flags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ }
+
+ list_for_each(p, &cxt->addmounts) {
+ struct libmnt_addmount *ad =
+ list_entry(p, struct libmnt_addmount, mounts);
+
+ *flags |= ad->mountflags;
+ }
+
+ if (!rc)
+ *flags |= cxt->mountflags;
+ return rc;
+}
+
+/**
+ * mnt_context_set_user_mflags:
+ * @cxt: mount context
+ * @flags: mount(2) flags (MNT_MS_* flags, e.g. MNT_MS_LOOP)
+ *
+ * Sets userspace mount flags.
+ *
+ * See also notes for mnt_context_set_mflags().
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_user_mflags(struct libmnt_context *cxt, unsigned long flags)
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->user_mountflags = flags;
+ return 0;
+}
+
+/**
+ * mnt_context_get_user_mflags:
+ * @cxt: mount context
+ * @flags: returns mount flags
+ *
+ * Converts mount options string to MNT_MS_* flags and bitwise-OR the result
+ * with the already defined flags (see mnt_context_set_user_mflags()).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_get_user_mflags(struct libmnt_context *cxt, unsigned long *flags)
+{
+ int rc = 0;
+
+ if (!cxt || !flags)
+ return -EINVAL;
+
+ *flags = 0;
+ if (!(cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) && cxt->fs) {
+ const char *o = mnt_fs_get_user_options(cxt->fs);
+ if (o)
+ rc = mnt_optstr_get_flags(o, flags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
+ }
+ if (!rc)
+ *flags |= cxt->user_mountflags;
+ return rc;
+}
+
+/**
+ * mnt_context_set_mountdata:
+ * @cxt: mount context
+ * @data: mount(2) data
+ *
+ * The mount context generates mountdata from mount options by default. This
+ * function can be used to overwrite this behavior, and @data will be used instead
+ * of mount options.
+ *
+ * The libmount does not deallocate the data by mnt_free_context(). Note that
+ * NULL is also valid mount data.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_mountdata(struct libmnt_context *cxt, void *data)
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->mountdata = data;
+ cxt->flags |= MNT_FL_MOUNTDATA;
+ return 0;
+}
+
+/*
+ * Translates LABEL/UUID/path to mountable path
+ */
+int mnt_context_prepare_srcpath(struct libmnt_context *cxt)
+{
+ const char *path = NULL;
+ struct libmnt_cache *cache;
+ const char *t, *v, *src, *type;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "--> preparing source path"));
+
+ src = mnt_fs_get_source(cxt->fs);
+
+ if (!src && mnt_context_propagation_only(cxt))
+ /* mount --make-{shared,private,...} */
+ return mnt_fs_set_source(cxt->fs, "none");
+
+ /* ignore filesystems without source or filesystems
+ * where the source is a quasi-path (//foo/bar)
+ */
+ if (!src || mnt_fs_is_netfs(cxt->fs))
+ return 0;
+
+ /* ZFS source is always "dataset", not a real path */
+ type = mnt_fs_get_fstype(cxt->fs);
+ if (type && strcmp(type, "zfs") == 0)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "srcpath '%s'", src));
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ cache = mnt_context_get_cache(cxt);
+
+ if (!mnt_fs_get_tag(cxt->fs, &t, &v)) {
+ /*
+ * Source is TAG (evaluate)
+ */
+ if (cache)
+ path = mnt_resolve_tag(t, v, cache);
+
+ rc = path ? mnt_fs_set_source(cxt->fs, path) : -MNT_ERR_NOSOURCE;
+
+ } else if (cache && !mnt_fs_is_pseudofs(cxt->fs)) {
+ /*
+ * Source is PATH (canonicalize)
+ */
+ path = mnt_resolve_path(src, cache);
+ if (path && strcmp(path, src) != 0)
+ rc = mnt_fs_set_source(cxt->fs, path);
+ }
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "failed to prepare srcpath [rc=%d]", rc));
+ goto end;
+ }
+
+ if (!path)
+ path = src;
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE | MS_REMOUNT))
+ || mnt_fs_is_pseudofs(cxt->fs)) {
+ DBG(CXT, ul_debugobj(cxt, "REMOUNT/BIND/MOVE/pseudo FS source: %s", path));
+ goto end;
+ }
+
+
+ /*
+ * Initialize verity or loop device
+ * ENOTSUP means verity options were requested, but the library is built without
+ * libcryptsetup so integrity cannot be enforced, and this should be an error
+ * rather than a silent fallback to a simple loopdev mount
+ */
+ rc = mnt_context_is_veritydev(cxt);
+ if (rc == -ENOTSUP) {
+ goto end;
+ } else if (rc) {
+ rc = mnt_context_setup_veritydev(cxt);
+ if (rc)
+ goto end;
+ } else if (mnt_context_is_loopdev(cxt)) {
+ rc = mnt_context_setup_loopdev(cxt);
+ if (rc)
+ goto end;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "final srcpath '%s'",
+ mnt_fs_get_source(cxt->fs)));
+
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+static int is_subdir_required(struct libmnt_context *cxt, int *rc)
+{
+ char *dir;
+ size_t sz;
+
+ assert(cxt);
+ assert(rc);
+
+ *rc = 0;
+
+ if (!cxt->fs
+ || !cxt->fs->user_optstr
+ || mnt_optstr_get_option(cxt->fs->user_optstr,
+ "X-mount.subdir", &dir, &sz) != 0)
+ return 0;
+
+ if (dir && *dir == '"')
+ dir++, sz-=2;
+
+ if (!dir || sz < 1) {
+ DBG(CXT, ul_debug("failed to parse X-mount.subdir '%s'", dir));
+ *rc = -MNT_ERR_MOUNTOPT;
+ } else {
+ cxt->subdir = strndup(dir, sz);
+ if (!cxt->subdir)
+ *rc = -ENOMEM;
+
+ DBG(CXT, ul_debug("subdir %s wanted", dir));
+ }
+
+ return *rc == 0;
+}
+
+static int is_mkdir_required(const char *tgt, struct libmnt_fs *fs, mode_t *mode, int *rc)
+{
+ char *mstr = NULL;
+ size_t mstr_sz = 0;
+ struct stat st;
+
+ assert(tgt);
+ assert(fs);
+ assert(mode);
+ assert(rc);
+
+ *mode = 0;
+ *rc = 0;
+
+ if (mnt_optstr_get_option(fs->user_optstr, "X-mount.mkdir", &mstr, &mstr_sz) != 0 &&
+ mnt_optstr_get_option(fs->user_optstr, "x-mount.mkdir", &mstr, &mstr_sz) != 0) /* obsolete */
+ return 0;
+
+ if (mnt_stat_mountpoint(tgt, &st) == 0)
+ return 0;
+
+ DBG(CXT, ul_debug("mkdir %s (%s) wanted", tgt, mstr));
+
+ if (mstr && mstr_sz) {
+ char *end = NULL;
+
+ if (*mstr == '"')
+ mstr++, mstr_sz-=2;
+
+ errno = 0;
+ *mode = strtol(mstr, &end, 8);
+
+ if (errno || !end || mstr + mstr_sz != end) {
+ DBG(CXT, ul_debug("failed to parse mkdir mode '%s'", mstr));
+ *rc = -MNT_ERR_MOUNTOPT;
+ return 0;
+ }
+ }
+
+ if (!*mode)
+ *mode = S_IRWXU | /* 0755 */
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH;
+
+ return 1;
+}
+
+int mnt_context_prepare_target(struct libmnt_context *cxt)
+{
+ const char *tgt, *prefix;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+ mode_t mode = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "--> preparing target path"));
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ if (!tgt)
+ return 0;
+
+ /* apply prefix */
+ prefix = mnt_context_get_target_prefix(cxt);
+ if (prefix) {
+ const char *p = *tgt == '/' ? tgt + 1 : tgt;
+
+ if (!*p)
+ /* target is "/", use "/prefix" */
+ rc = mnt_fs_set_target(cxt->fs, prefix);
+ else {
+ char *path = NULL;
+
+ if (asprintf(&path, "%s/%s", prefix, p) <= 0)
+ rc = -ENOMEM;
+ else {
+ rc = mnt_fs_set_target(cxt->fs, path);
+ free(path);
+ }
+ }
+ if (rc)
+ return rc;
+ tgt = mnt_fs_get_target(cxt->fs);
+ }
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ /* X-mount.mkdir target */
+ if (cxt->action == MNT_ACT_MOUNT
+ && (cxt->user_mountflags & MNT_MS_XCOMMENT ||
+ cxt->user_mountflags & MNT_MS_XFSTABCOMM)
+ && is_mkdir_required(tgt, cxt->fs, &mode, &rc)) {
+
+ /* supported only for root or non-suid mount(8) */
+ if (!mnt_context_is_restricted(cxt)) {
+ rc = ul_mkdir_p(tgt, mode);
+ if (rc)
+ DBG(CXT, ul_debug("mkdir %s failed: %m", tgt));
+ } else
+ rc = -EPERM;
+ }
+
+ /* canonicalize the path */
+ if (rc == 0) {
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+
+ if (cache) {
+ char *path = mnt_resolve_path(tgt, cache);
+ if (path && strcmp(path, tgt) != 0)
+ rc = mnt_fs_set_target(cxt->fs, path);
+ }
+ }
+
+ /* X-mount.subdir= target */
+ if (rc == 0
+ && cxt->action == MNT_ACT_MOUNT
+ && (cxt->user_mountflags & MNT_MS_XFSTABCOMM)
+ && is_subdir_required(cxt, &rc)) {
+
+ DBG(CXT, ul_debugobj(cxt, "subdir %s required", cxt->subdir));
+ }
+
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ DBG(CXT, ul_debugobj(cxt, "final target '%s' [rc=%d]",
+ mnt_fs_get_target(cxt->fs), rc));
+ return rc;
+}
+
+/* Guess type, but not set to cxt->fs, always use free() for the result. It's
+ * no error when we're not able to guess a filesystem type. Note that error
+ * does not mean that result in @type is NULL.
+ */
+int mnt_context_guess_srcpath_fstype(struct libmnt_context *cxt, char **type)
+{
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+ const char *dev;
+
+ assert(type);
+ assert(cxt);
+
+ *type = NULL;
+
+ dev = mnt_fs_get_srcpath(cxt->fs);
+ if (!dev)
+ return 0;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ if (access(dev, F_OK) == 0) {
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+ int ambi = 0;
+
+ *type = mnt_get_fstype(dev, &ambi, cache);
+ if (ambi)
+ rc = -MNT_ERR_AMBIFS;
+
+ if (cache && *type) {
+ *type = strdup(*type);
+ if (!*type)
+ rc = -ENOMEM;
+ }
+ } else {
+ DBG(CXT, ul_debugobj(cxt, "access(%s) failed [%m]", dev));
+ if (strchr(dev, ':') != NULL) {
+ *type = strdup("nfs");
+ if (!*type)
+ rc = -ENOMEM;
+ } else if (!strncmp(dev, "//", 2)) {
+ *type = strdup("cifs");
+ if (!*type)
+ rc = -ENOMEM;
+ }
+ }
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/*
+ * It's usually no error when we're not able to detect the filesystem type -- we
+ * will try to use the types from /{etc,proc}/filesystems.
+ */
+int mnt_context_guess_fstype(struct libmnt_context *cxt)
+{
+ char *type;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "--> preparing fstype"));
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE))
+ || mnt_context_propagation_only(cxt))
+ goto none;
+
+ type = (char *) mnt_fs_get_fstype(cxt->fs);
+ if (type && !strcmp(type, "auto")) {
+ mnt_fs_set_fstype(cxt->fs, NULL);
+ type = NULL;
+ }
+
+ if (type)
+ goto done;
+ if (cxt->mountflags & MS_REMOUNT)
+ goto none;
+ if (cxt->fstype_pattern)
+ goto done;
+
+ rc = mnt_context_guess_srcpath_fstype(cxt, &type);
+ if (rc == 0 && type)
+ __mnt_fs_set_fstype_ptr(cxt->fs, type);
+ else
+ free(type);
+done:
+ DBG(CXT, ul_debugobj(cxt, "FS type: %s [rc=%d]",
+ mnt_fs_get_fstype(cxt->fs), rc));
+ return rc;
+none:
+ return mnt_fs_set_fstype(cxt->fs, "none");
+}
+
+/*
+ * The default is to use fstype from cxt->fs, this could be overwritten by
+ * @type. The @act is MNT_ACT_{MOUNT,UMOUNT}.
+ *
+ * Returns: 0 on success or negative number in case of error. Note that success
+ * does not mean that there is any usable helper, you have to check cxt->helper.
+ */
+int mnt_context_prepare_helper(struct libmnt_context *cxt, const char *name,
+ const char *type)
+{
+ char search_path[] = FS_SEARCH_PATH; /* from config.h */
+ char *p = NULL, *path;
+ struct libmnt_ns *ns_old;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (cxt->helper) {
+ free(cxt->helper);
+ cxt->helper = NULL;
+ }
+
+ if (!type)
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ if (type && strchr(type, ','))
+ return 0; /* type is fstype pattern */
+
+ if (mnt_context_is_nohelpers(cxt)
+ || !type
+ || !strcmp(type, "none")
+ || strstr(type, "/..") /* don't try to smuggle path */
+ || mnt_fs_is_swaparea(cxt->fs))
+ return 0;
+
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ /* Ignore errors when search in $PATH and do not modify
+ * @rc due to stat() etc.
+ */
+ path = strtok_r(search_path, ":", &p);
+ while (path) {
+ char helper[PATH_MAX];
+ struct stat st;
+ int xrc;
+
+ xrc = snprintf(helper, sizeof(helper), "%s/%s.%s",
+ path, name, type);
+ path = strtok_r(NULL, ":", &p);
+
+ if (xrc < 0 || (size_t) xrc >= sizeof(helper))
+ continue;
+
+ xrc = stat(helper, &st);
+ if (xrc == -1 && errno == ENOENT && strchr(type, '.')) {
+ /* If type ends with ".subtype" try without it */
+ char *hs = strrchr(helper, '.');
+ if (hs)
+ *hs = '\0';
+ xrc = stat(helper, &st);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "%-25s ... %s", helper,
+ xrc ? "not found" : "found"));
+ if (xrc)
+ continue;
+
+ /* success */
+ rc = strdup_to_struct_member(cxt, helper, helper);
+ break;
+ }
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ rc = -MNT_ERR_NAMESPACE;
+
+ /* make sure helper is not set on error */
+ if (rc) {
+ free(cxt->helper);
+ cxt->helper = NULL;
+ }
+ return rc;
+}
+
+int mnt_context_merge_mflags(struct libmnt_context *cxt)
+{
+ unsigned long fl = 0;
+ int rc;
+
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "merging mount flags"));
+
+ rc = mnt_context_get_mflags(cxt, &fl);
+ if (rc)
+ return rc;
+ cxt->mountflags = fl;
+
+ fl = 0;
+ rc = mnt_context_get_user_mflags(cxt, &fl);
+ if (rc)
+ return rc;
+ cxt->user_mountflags = fl;
+
+ DBG(CXT, ul_debugobj(cxt, "final flags: VFS=%08lx user=%08lx",
+ cxt->mountflags, cxt->user_mountflags));
+
+ cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED;
+ return 0;
+}
+
+/*
+ * Prepare /etc/mtab or /run/mount/utab
+ */
+int mnt_context_prepare_update(struct libmnt_context *cxt)
+{
+ int rc;
+ const char *target;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->action);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "--> prepare update"));
+
+ if (mnt_context_propagation_only(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "skip update: only MS_PROPAGATION"));
+ return 0;
+ }
+
+ target = mnt_fs_get_target(cxt->fs);
+
+ if (cxt->action == MNT_ACT_UMOUNT && target && !strcmp(target, "/")) {
+ DBG(CXT, ul_debugobj(cxt, "root umount: setting NOMTAB"));
+ mnt_context_disable_mtab(cxt, TRUE);
+ }
+ if (mnt_context_is_nomtab(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "skip update: NOMTAB flag"));
+ return 0;
+ }
+ if (!mnt_context_get_writable_tabpath(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "skip update: no writable destination"));
+ return 0;
+ }
+ /* 0 = success, 1 = not called yet */
+ if (cxt->syscall_status != 1 && cxt->syscall_status != 0) {
+ DBG(CXT, ul_debugobj(cxt,
+ "skip update: syscall failed [status=%d]",
+ cxt->syscall_status));
+ return 0;
+ }
+
+ if (!cxt->update) {
+ const char *name = mnt_context_get_writable_tabpath(cxt);
+
+ if (cxt->action == MNT_ACT_UMOUNT && is_file_empty(name)) {
+ DBG(CXT, ul_debugobj(cxt,
+ "skip update: umount, no table"));
+ return 0;
+ }
+
+ cxt->update = mnt_new_update();
+ if (!cxt->update)
+ return -ENOMEM;
+
+ mnt_update_set_filename(cxt->update, name,
+ !mnt_context_mtab_writable(cxt));
+ }
+
+ if (cxt->action == MNT_ACT_UMOUNT)
+ rc = mnt_update_set_fs(cxt->update, cxt->mountflags,
+ mnt_context_get_target(cxt), NULL);
+ else
+ rc = mnt_update_set_fs(cxt->update, cxt->mountflags,
+ NULL, cxt->fs);
+
+ return rc < 0 ? rc : 0;
+}
+
+int mnt_context_update_tabs(struct libmnt_context *cxt)
+{
+ unsigned long fl;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+
+ if (mnt_context_is_nomtab(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "don't update: NOMTAB flag"));
+ return 0;
+ }
+ if (!cxt->update || !mnt_update_is_ready(cxt->update)) {
+ DBG(CXT, ul_debugobj(cxt, "don't update: no update prepared"));
+ return 0;
+ }
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ /* check utab update when external helper executed */
+ if (mnt_context_helper_executed(cxt)
+ && mnt_context_get_helper_status(cxt) == 0
+ && mnt_context_utab_writable(cxt)) {
+
+ if (mnt_update_already_done(cxt->update, cxt->lock)) {
+ DBG(CXT, ul_debugobj(cxt, "don't update: error evaluate or already updated"));
+ goto end;
+ }
+ } else if (cxt->helper) {
+ DBG(CXT, ul_debugobj(cxt, "don't update: external helper"));
+ goto end;
+ }
+
+ if (cxt->syscall_status != 0
+ && !(mnt_context_helper_executed(cxt) &&
+ mnt_context_get_helper_status(cxt) == 0)) {
+
+ DBG(CXT, ul_debugobj(cxt, "don't update: syscall/helper failed/not called"));
+ goto end;
+ }
+
+ fl = mnt_update_get_mflags(cxt->update);
+ if ((cxt->mountflags & MS_RDONLY) != (fl & MS_RDONLY))
+ /*
+ * fix MS_RDONLY in options
+ */
+ mnt_update_force_rdonly(cxt->update,
+ cxt->mountflags & MS_RDONLY);
+
+ rc = mnt_update_table(cxt->update, cxt->lock);
+
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+/* apply @fs to @cxt;
+ *
+ * @mflags are mount flags as specified on command-line -- used only to save
+ * MS_RDONLY which is allowed for non-root users.
+ */
+static int apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs, unsigned long mflags)
+{
+ int rc;
+
+ if (!cxt->optsmode) {
+ if (mnt_context_is_restricted(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "force fstab usage for non-root users!"));
+ cxt->optsmode = MNT_OMODE_USER;
+ } else {
+ DBG(CXT, ul_debugobj(cxt, "use default optsmode"));
+ cxt->optsmode = MNT_OMODE_AUTO;
+ }
+
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "apply entry:"));
+ DBG(CXT, mnt_fs_print_debug(fs, stderr));
+ DBG(CXT, ul_debugobj(cxt, "OPTSMODE (opt-part): ignore=%d, append=%d, prepend=%d, replace=%d",
+ cxt->optsmode & MNT_OMODE_IGNORE ? 1 : 0,
+ cxt->optsmode & MNT_OMODE_APPEND ? 1 : 0,
+ cxt->optsmode & MNT_OMODE_PREPEND ? 1 : 0,
+ cxt->optsmode & MNT_OMODE_REPLACE ? 1 : 0));
+
+ /* copy from fs to our FS description
+ */
+ rc = mnt_fs_set_source(cxt->fs, mnt_fs_get_source(fs));
+ if (!rc)
+ rc = mnt_fs_set_target(cxt->fs, mnt_fs_get_target(fs));
+
+ if (!rc && !mnt_fs_get_fstype(cxt->fs))
+ rc = mnt_fs_set_fstype(cxt->fs, mnt_fs_get_fstype(fs));
+
+ if (!rc && !mnt_fs_get_root(cxt->fs) && mnt_fs_get_root(fs))
+ rc = mnt_fs_set_root(cxt->fs, mnt_fs_get_root(fs));
+
+ if (rc)
+ goto done;
+
+ if (cxt->optsmode & MNT_OMODE_IGNORE)
+ ;
+ else if (cxt->optsmode & MNT_OMODE_REPLACE) {
+ rc = mnt_fs_set_options(cxt->fs, mnt_fs_get_options(fs));
+
+ /* mount --read-only for non-root users is allowed */
+ if (rc == 0 && (mflags & MS_RDONLY)
+ && mnt_context_is_restricted(cxt)
+ && cxt->optsmode == MNT_OMODE_USER)
+ rc = mnt_fs_append_options(cxt->fs, "ro");
+ }
+ else if (cxt->optsmode & MNT_OMODE_APPEND)
+ rc = mnt_fs_append_options(cxt->fs, mnt_fs_get_options(fs));
+
+ else if (cxt->optsmode & MNT_OMODE_PREPEND)
+ rc = mnt_fs_prepend_options(cxt->fs, mnt_fs_get_options(fs));
+
+ if (!rc)
+ cxt->flags |= MNT_FL_TAB_APPLIED;
+
+done:
+ DBG(CXT, ul_debugobj(cxt, "final entry [rc=%d]:", rc));
+ DBG(CXT, mnt_fs_print_debug(cxt->fs, stderr));
+
+ return rc;
+}
+
+static int apply_table(struct libmnt_context *cxt, struct libmnt_table *tb,
+ int direction, unsigned long mflags)
+{
+ struct libmnt_fs *fs = NULL;
+ const char *src, *tgt;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ src = mnt_fs_get_source(cxt->fs);
+ tgt = mnt_fs_get_target(cxt->fs);
+
+ if (tgt && src)
+ fs = mnt_table_find_pair(tb, src, tgt, direction);
+ else {
+ if (src)
+ fs = mnt_table_find_source(tb, src, direction);
+ else if (tgt)
+ fs = mnt_table_find_target(tb, tgt, direction);
+
+ if (!fs && mnt_context_is_swapmatch(cxt)) {
+ /* swap source and target (if @src is not LABEL/UUID),
+ * for example in
+ *
+ * mount /foo/bar
+ *
+ * the path could be a mountpoint as well as a source (for
+ * example bind mount, symlink to a device, ...).
+ */
+ if (src && !mnt_fs_get_tag(cxt->fs, NULL, NULL))
+ fs = mnt_table_find_target(tb, src, direction);
+ if (!fs && tgt)
+ fs = mnt_table_find_source(tb, tgt, direction);
+ }
+ }
+
+ if (!fs)
+ return -MNT_ERR_NOFSTAB; /* not found */
+
+ return apply_fs(cxt, fs, mflags);
+}
+
+/* apply @fs to @cxt -- use mnt_context_apply_fstab() if not sure
+ */
+int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs)
+{
+ return apply_fs(cxt, fs, 0);
+}
+
+/**
+ * mnt_context_apply_fstab:
+ * @cxt: mount context
+ *
+ * This function is optional.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_apply_fstab(struct libmnt_context *cxt)
+{
+ int rc = -1, isremount = 0, iscmdbind = 0;
+ struct libmnt_ns *ns_old;
+ struct libmnt_table *tab = NULL;
+ const char *src = NULL, *tgt = NULL;
+ unsigned long mflags = 0;
+
+ if (!cxt || !cxt->fs)
+ return -EINVAL;
+
+ if (mnt_context_tab_applied(cxt)) { /* already applied */
+ DBG(CXT, ul_debugobj(cxt, "fstab already applied -- skip"));
+ return 0;
+ }
+
+ if (mnt_context_is_restricted(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "force fstab usage for non-root users!"));
+ cxt->optsmode = MNT_OMODE_USER;
+ } else if (cxt->optsmode == 0) {
+ DBG(CXT, ul_debugobj(cxt, "use default optsmode"));
+ cxt->optsmode = MNT_OMODE_AUTO;
+ } else if (cxt->optsmode & MNT_OMODE_NOTAB) {
+ cxt->optsmode &= ~MNT_OMODE_FSTAB;
+ cxt->optsmode &= ~MNT_OMODE_MTAB;
+ cxt->optsmode &= ~MNT_OMODE_FORCE;
+ }
+
+ if (mnt_context_get_mflags(cxt, &mflags) == 0) {
+ isremount = !!(mflags & MS_REMOUNT);
+ iscmdbind = !!(mflags & MS_BIND);
+ }
+
+ if (cxt->fs) {
+ src = mnt_fs_get_source(cxt->fs);
+ tgt = mnt_fs_get_target(cxt->fs);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "OPTSMODE (file-part): force=%d, fstab=%d, mtab=%d",
+ cxt->optsmode & MNT_OMODE_FORCE ? 1 : 0,
+ cxt->optsmode & MNT_OMODE_FSTAB ? 1 : 0,
+ cxt->optsmode & MNT_OMODE_MTAB ? 1 : 0));
+
+ /* fstab is not required if source and target are specified */
+ if (src && tgt && !(cxt->optsmode & MNT_OMODE_FORCE)) {
+ DBG(CXT, ul_debugobj(cxt, "fstab not required -- skip"));
+ return 0;
+ }
+
+ if (!src && tgt
+ && !(cxt->optsmode & MNT_OMODE_FSTAB)
+ && !(cxt->optsmode & MNT_OMODE_MTAB)) {
+ DBG(CXT, ul_debugobj(cxt, "only target; fstab/mtab not required "
+ "-- skip, probably MS_PROPAGATION"));
+ return 0;
+ }
+
+ /* let's initialize cxt->fs */
+ ignore_result( mnt_context_get_fs(cxt) );
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ /* try fstab */
+ if (cxt->optsmode & MNT_OMODE_FSTAB) {
+ DBG(CXT, ul_debugobj(cxt, "trying to apply fstab (src=%s, target=%s)", src, tgt));
+ rc = mnt_context_get_fstab(cxt, &tab);
+ if (!rc)
+ rc = apply_table(cxt, tab, MNT_ITER_FORWARD, mflags);
+ }
+
+ /* try mtab */
+ if (rc < 0 && (cxt->optsmode & MNT_OMODE_MTAB)
+ && (isremount || cxt->action == MNT_ACT_UMOUNT)) {
+ DBG(CXT, ul_debugobj(cxt, "trying to apply mtab (src=%s, target=%s)", src, tgt));
+ if (tgt)
+ rc = mnt_context_get_mtab_for_target(cxt, &tab, tgt);
+ else
+ rc = mnt_context_get_mtab(cxt, &tab);
+ if (!rc)
+ rc = apply_table(cxt, tab, MNT_ITER_BACKWARD, mflags);
+ }
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ if (rc) {
+ if (!mnt_context_is_restricted(cxt)
+ && tgt && !src
+ && isremount) {
+ DBG(CXT, ul_debugobj(cxt, "only target; ignore missing mtab entry on remount"));
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "failed to find entry in fstab/mtab [rc=%d]: %m", rc));
+
+ /* force to "not found in fstab/mtab" error, the details why
+ * not found are not so important and may be misinterpreted by
+ * applications... */
+ rc = -MNT_ERR_NOFSTAB;
+
+
+ } else if (isremount && !iscmdbind) {
+
+ /* remove "bind" from fstab (or no-op if not present) */
+ mnt_optstr_remove_option(&cxt->fs->optstr, "bind");
+ }
+ return rc;
+}
+
+/**
+ * mnt_context_tab_applied:
+ * @cxt: mount context
+ *
+ * Returns: 1 if fstab (or mtab) has been applied to the context, or 0.
+ */
+int mnt_context_tab_applied(struct libmnt_context *cxt)
+{
+ return cxt->flags & MNT_FL_TAB_APPLIED;
+}
+
+/*
+ * This is not a public function!
+ *
+ * Returns 1 if *only propagation flags* change is requested.
+ */
+int mnt_context_propagation_only(struct libmnt_context *cxt)
+{
+ if (cxt->action != MNT_ACT_MOUNT)
+ return 0;
+
+ /* has to be called after context_mount.c: fix_opts() */
+ assert((cxt->flags & MNT_FL_MOUNTOPTS_FIXED));
+
+ /* all propagation mounts are in cxt->addmount */
+ return !list_empty(&cxt->addmounts)
+ && (cxt->mountflags == 0 || cxt->mountflags == MS_SILENT)
+ && cxt->fs
+ && (!cxt->fs->fstype || strcmp(cxt->fs->fstype, "none") == 0)
+ && (!cxt->fs->source || strcmp(cxt->fs->source, "none") == 0);
+}
+
+/**
+ * mnt_context_get_status:
+ * @cxt: mount context
+ *
+ * Global libmount status.
+ *
+ * The real exit code of the mount.type helper has to be tested by
+ * mnt_context_get_helper_status(). The mnt_context_get_status() only informs
+ * that exec() has been successful.
+ *
+ * Returns: 1 if mount.type or mount(2) syscall has been successfully called.
+ */
+int mnt_context_get_status(struct libmnt_context *cxt)
+{
+ return !cxt->syscall_status || !cxt->helper_exec_status;
+}
+
+/**
+ * mnt_context_helper_executed:
+ * @cxt: mount context
+ *
+ * Returns: 1 if mount.type helper has been executed, or 0.
+ */
+int mnt_context_helper_executed(struct libmnt_context *cxt)
+{
+ return cxt->helper_exec_status != 1;
+}
+
+/**
+ * mnt_context_get_helper_status:
+ * @cxt: mount context
+ *
+ * Return: mount.type helper exit status, result is reliable only if
+ * mnt_context_helper_executed() returns 1.
+ */
+int mnt_context_get_helper_status(struct libmnt_context *cxt)
+{
+ return cxt->helper_status;
+}
+
+/**
+ * mnt_context_syscall_called:
+ * @cxt: mount context
+ *
+ * Returns: 1 if mount(2) syscall has been called, or 0.
+ */
+int mnt_context_syscall_called(struct libmnt_context *cxt)
+{
+ return cxt->syscall_status != 1;
+}
+
+/**
+ * mnt_context_get_syscall_errno:
+ * @cxt: mount context
+ *
+ * The result from this function is reliable only if
+ * mnt_context_syscall_called() returns 1.
+ *
+ * Returns: mount(2) errno if the syscall failed or 0.
+ */
+int mnt_context_get_syscall_errno(struct libmnt_context *cxt)
+{
+ if (cxt->syscall_status < 0)
+ return -cxt->syscall_status;
+ return 0;
+}
+
+/**
+ * mnt_context_set_syscall_status:
+ * @cxt: mount context
+ * @status: mount(2) status
+ *
+ * The @status should be 0 on success, or negative number on error (-errno).
+ *
+ * This function should only be used if the [u]mount(2) syscall is NOT called by
+ * libmount code.
+ *
+ * Returns: 0 or negative number in case of error.
+ */
+int mnt_context_set_syscall_status(struct libmnt_context *cxt, int status)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "syscall status set to: %d", status));
+ cxt->syscall_status = status;
+ return 0;
+}
+
+/**
+ * mnt_context_strerror
+ * @cxt: context
+ * @buf: buffer
+ * @bufsiz: size of the buffer
+ *
+ * Not implemented, deprecated in favor or mnt_context_get_excode().
+ *
+ * Returns: 0 or negative number in case of error.
+ */
+int mnt_context_strerror(struct libmnt_context *cxt __attribute__((__unused__)),
+ char *buf __attribute__((__unused__)),
+ size_t bufsiz __attribute__((__unused__)))
+{
+ /* TODO: based on cxt->syscall_errno or cxt->helper_status */
+ return 0;
+}
+
+
+int mnt_context_get_generic_excode(int rc, char *buf, size_t bufsz, const char *fmt, ...)
+{
+ va_list va;
+
+ if (rc == 0)
+ return MNT_EX_SUCCESS;
+
+ va_start(va, fmt);
+
+ /* we need to support "%m" */
+ errno = rc < 0 ? -rc : rc;
+
+ if (buf && bufsz && vsnprintf(buf, bufsz, fmt, va) < 0)
+ *buf = '\0';
+
+ switch (errno) {
+ case EINVAL:
+ case EPERM:
+ rc = MNT_EX_USAGE;
+ break;
+ case ENOMEM:
+ rc = MNT_EX_SYSERR;
+ break;
+ default:
+ rc = MNT_EX_FAIL;
+ break;
+ }
+ va_end(va);
+ return rc;
+}
+
+/**
+ * mnt_context_get_excode:
+ * @cxt: context
+ * @rc: return code of the previous operation
+ * @buf: buffer to print error message (optional)
+ * @bufsz: size of the buffer
+ *
+ * This function analyzes context, [u]mount syscall and external helper status
+ * and @mntrc and generates unified return code (see MNT_EX_*) as expected
+ * from mount(8) or umount(8).
+ *
+ * If the external helper (e.g. /sbin/mount.type) has been executed than it
+ * returns status from wait() of the helper. It's not libmount fail if helper
+ * returns some crazy undocumented codes... See mnt_context_helper_executed()
+ * and mnt_context_get_helper_status(). Note that mount(8) and umount(8) utils
+ * always return code from helper without extra care about it.
+ *
+ * The current implementation does not read error message from external
+ * helper into @buf.
+ *
+ * If the argument @buf is not NULL then error message is generated (if
+ * anything failed).
+ *
+ * The @mntrc is usually return code from mnt_context_mount(),
+ * mnt_context_umount(), or 'mntrc' as returned by mnt_context_next_mount().
+ *
+ * Since: 2.30
+ *
+ * Returns: MNT_EX_* codes.
+ */
+int mnt_context_get_excode(
+ struct libmnt_context *cxt,
+ int rc,
+ char *buf,
+ size_t bufsz)
+{
+ if (buf) {
+ *buf = '\0'; /* for sure */
+
+ if (!cxt->enabled_textdomain) {
+ bindtextdomain(LIBMOUNT_TEXTDOMAIN, LOCALEDIR);
+ cxt->enabled_textdomain = 1;
+ }
+ }
+
+ switch (cxt->action) {
+ case MNT_ACT_MOUNT:
+ rc = mnt_context_get_mount_excode(cxt, rc, buf, bufsz);
+ break;
+ case MNT_ACT_UMOUNT:
+ rc = mnt_context_get_umount_excode(cxt, rc, buf, bufsz);
+ break;
+ default:
+ if (rc)
+ rc = mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("operation failed: %m"));
+ else
+ rc = MNT_EX_SUCCESS;
+ break;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "excode: rc=%d message=\"%s\"", rc,
+ buf ? buf : "<no-message>"));
+ return rc;
+}
+
+
+/**
+ * mnt_context_init_helper
+ * @cxt: mount context
+ * @action: MNT_ACT_{UMOUNT,MOUNT}
+ * @flags: not used now
+ *
+ * This function informs libmount that used from [u]mount.type helper.
+ *
+ * The function also calls mnt_context_disable_helpers() to avoid recursive
+ * mount.type helpers calling. It you really want to call another
+ * mount.type helper from your helper, then you have to explicitly enable this
+ * feature by:
+ *
+ * mnt_context_disable_helpers(cxt, FALSE);
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_init_helper(struct libmnt_context *cxt, int action,
+ int flags __attribute__((__unused__)))
+{
+ int rc;
+
+ if (!cxt)
+ return -EINVAL;
+
+ rc = mnt_context_disable_helpers(cxt, TRUE);
+ if (!rc)
+ rc = set_flag(cxt, MNT_FL_HELPER, 1);
+ if (!rc)
+ cxt->action = action;
+
+ DBG(CXT, ul_debugobj(cxt, "initialized for [u]mount.<type> helper [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * mnt_context_helper_setopt:
+ * @cxt: context
+ * @c: getopt() result
+ * @arg: getopt() optarg
+ *
+ * This function applies the [u]mount.type command line option (for example parsed
+ * by getopt or getopt_long) to @cxt. All unknown options are ignored and
+ * then 1 is returned.
+ *
+ * Returns: negative number on error, 1 if @c is unknown option, 0 on success.
+ */
+int mnt_context_helper_setopt(struct libmnt_context *cxt, int c, char *arg)
+{
+ if (cxt) {
+ switch(cxt->action) {
+ case MNT_ACT_MOUNT:
+ return mnt_context_mount_setopt(cxt, c, arg);
+ case MNT_ACT_UMOUNT:
+ return mnt_context_umount_setopt(cxt, c, arg);
+ }
+ }
+ return -EINVAL;
+}
+
+/**
+ * mnt_context_is_fs_mounted:
+ * @cxt: context
+ * @fs: filesystem
+ * @mounted: returns 1 for mounted and 0 for non-mounted filesystems
+ *
+ * Please, read the mnt_table_is_fs_mounted() description!
+ *
+ * Returns: 0 on success and negative number in case of error.
+ */
+int mnt_context_is_fs_mounted(struct libmnt_context *cxt,
+ struct libmnt_fs *fs, int *mounted)
+{
+ struct libmnt_table *mtab, *orig;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ if (!cxt || !fs || !mounted)
+ return -EINVAL;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ orig = cxt->mtab;
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ if (rc == -ENOENT && mnt_fs_streq_target(fs, "/proc") &&
+ (!cxt->mtab_path || startswith(cxt->mtab_path, "/proc/"))) {
+ if (!orig) {
+ mnt_unref_table(cxt->mtab);
+ cxt->mtab = NULL;
+ }
+ *mounted = 0;
+ rc = 0; /* /proc not mounted */
+
+ } else if (rc == 0) {
+ *mounted = __mnt_table_is_fs_mounted(mtab, fs,
+ mnt_context_get_target_prefix(cxt));
+
+ }
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+static int mnt_context_add_child(struct libmnt_context *cxt, pid_t pid)
+{
+ pid_t *pids;
+
+ if (!cxt)
+ return -EINVAL;
+
+ pids = realloc(cxt->children, sizeof(pid_t) * cxt->nchildren + 1);
+ if (!pids)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "add new child %d", pid));
+ cxt->children = pids;
+ cxt->children[cxt->nchildren++] = pid;
+
+ return 0;
+}
+
+int mnt_fork_context(struct libmnt_context *cxt)
+{
+ int rc = 0;
+ pid_t pid;
+
+ assert(cxt);
+ if (!mnt_context_is_parent(cxt))
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "forking context"));
+
+ DBG_FLUSH;
+
+ pid = fork();
+
+ switch (pid) {
+ case -1: /* error */
+ DBG(CXT, ul_debugobj(cxt, "fork failed %m"));
+ return -errno;
+
+ case 0: /* child */
+ cxt->pid = getpid();
+ mnt_context_enable_fork(cxt, FALSE);
+ DBG(CXT, ul_debugobj(cxt, "child created"));
+ break;
+
+ default:
+ rc = mnt_context_add_child(cxt, pid);
+ break;
+ }
+
+ return rc;
+}
+
+int mnt_context_wait_for_children(struct libmnt_context *cxt,
+ int *nchildren, int *nerrs)
+{
+ int i;
+
+ if (!cxt)
+ return -EINVAL;
+
+ assert(mnt_context_is_parent(cxt));
+
+ for (i = 0; i < cxt->nchildren; i++) {
+ pid_t pid = cxt->children[i];
+ int rc = 0, ret = 0;
+
+ if (!pid)
+ continue;
+ do {
+ DBG(CXT, ul_debugobj(cxt,
+ "waiting for child (%d/%d): %d",
+ i + 1, cxt->nchildren, pid));
+ errno = 0;
+ rc = waitpid(pid, &ret, 0);
+
+ } while (rc == -1 && errno == EINTR);
+
+ if (nchildren)
+ (*nchildren)++;
+
+ if (rc != -1 && nerrs) {
+ if (WIFEXITED(ret))
+ (*nerrs) += WEXITSTATUS(ret) == 0 ? 0 : 1;
+ else
+ (*nerrs)++;
+ }
+ cxt->children[i] = 0;
+ }
+
+ cxt->nchildren = 0;
+ free(cxt->children);
+ cxt->children = NULL;
+ return 0;
+}
+
+static void close_ns(struct libmnt_ns *ns)
+{
+ if (ns->fd == -1)
+ return;
+
+ close(ns->fd);
+ ns->fd = -1;
+
+ mnt_unref_cache(ns->cache);
+ ns->cache = NULL;
+}
+
+/**
+ * mnt_context_set_target_ns:
+ * @cxt: mount context
+ * @path: path to target namespace or NULL
+ *
+ * Sets target namespace to namespace represented by @path. If @path is NULL,
+ * target namespace is cleared.
+ *
+ * This function sets errno to ENOSYS and returns error if libmount is
+ * compiled without namespaces support.
+*
+ * Returns: 0 on success, negative number in case of error.
+ *
+ * Since: 2.33
+ */
+int mnt_context_set_target_ns(struct libmnt_context *cxt, const char *path)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "Setting %s as target namespace", path));
+
+ /* cleanup only */
+ if (!path) {
+ close_ns(&cxt->ns_orig);
+ close_ns(&cxt->ns_tgt);
+ return 0;
+ }
+
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ int errsv = 0;
+ int tmp;
+
+ errno = 0;
+
+ /* open original namespace */
+ if (cxt->ns_orig.fd == -1) {
+ cxt->ns_orig.fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+ if (cxt->ns_orig.fd == -1)
+ return -errno;
+ cxt->ns_orig.cache = NULL;
+ }
+
+ /* open target (wanted) namespace */
+ tmp = open(path, O_RDONLY | O_CLOEXEC);
+ if (tmp == -1)
+ return -errno;
+
+ /* test whether namespace switching works */
+ DBG(CXT, ul_debugobj(cxt, "Trying whether namespace is valid"));
+ if (setns(tmp, CLONE_NEWNS)
+ || setns(cxt->ns_orig.fd, CLONE_NEWNS)) {
+ errsv = errno;
+ DBG(CXT, ul_debugobj(cxt, "setns(2) failed [errno=%d %m]", errno));
+ goto err;
+ }
+
+ close_ns(&cxt->ns_tgt);
+
+ cxt->ns_tgt.fd = tmp;
+ cxt->ns_tgt.cache = NULL;
+
+ return 0;
+err:
+ close(tmp);
+ errno = errsv;
+
+#else /* ! USE_LIBMOUNT_SUPPORT_NAMESPACES */
+ errno = ENOSYS;
+#endif
+ return -errno;
+}
+
+/**
+ * mnt_context_get_target_ns:
+ * @cxt: mount context
+ *
+ * Returns: pointer to target namespace
+ *
+ * Since: 2.33
+ */
+struct libmnt_ns *mnt_context_get_target_ns(struct libmnt_context *cxt)
+{
+ return &cxt->ns_tgt;
+}
+
+/**
+ * mnt_context_get_origin_ns:
+ * @cxt: mount context
+ *
+ * Returns: pointer to original namespace
+ *
+ * Since: 2.33
+ */
+struct libmnt_ns *mnt_context_get_origin_ns(struct libmnt_context *cxt)
+{
+ return &cxt->ns_orig;
+}
+
+
+/**
+ * mnt_context_switch_ns:
+ * @cxt: mount context
+ * @ns: namespace to switch to
+ *
+ * Switch to namespace specified by ns
+ *
+ * Typical usage:
+ * <informalexample>
+ * <programlisting>
+ * struct libmnt_ns *ns_old;
+ * ns_old = mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt));
+ * ... code ...
+ * mnt_context_switch_ns(cxt, ns_old);
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: pointer to previous namespace or NULL on error
+ *
+ * Since: 2.33
+ */
+struct libmnt_ns *mnt_context_switch_ns(struct libmnt_context *cxt, struct libmnt_ns *ns)
+{
+ struct libmnt_ns *old = NULL;
+
+ if (!cxt || !ns)
+ return NULL;
+
+ /*
+ * If mnt_context_set_target_ns() has never been used than @ns file
+ * descriptor is -1 and this function is noop.
+ */
+ old = cxt->ns_cur;
+ if (ns == old || ns->fd == -1)
+ return old;
+
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ /* remember the current cache */
+ if (old->cache != cxt->cache) {
+ mnt_unref_cache(old->cache);
+ old->cache = cxt->cache;
+ mnt_ref_cache(old->cache);
+ }
+
+ /* switch */
+ DBG(CXT, ul_debugobj(cxt, "Switching to %s namespace",
+ ns == mnt_context_get_target_ns(cxt) ? "target" :
+ ns == mnt_context_get_origin_ns(cxt) ? "original" : "other"));
+
+ if (setns(ns->fd, CLONE_NEWNS)) {
+ int errsv = errno;
+
+ DBG(CXT, ul_debugobj(cxt, "setns(2) failed [errno=%d %m]", errno));
+ errno = errsv;
+ return NULL;
+ }
+
+ /* update pointer to the current namespace */
+ cxt->ns_cur = ns;
+
+ /* update pointer to the cache */
+ mnt_unref_cache(cxt->cache);
+ cxt->cache = ns->cache;
+ mnt_ref_cache(cxt->cache);
+#endif /* USE_LIBMOUNT_SUPPORT_NAMESPACES */
+
+ return old;
+}
+
+/**
+ * mnt_context_switch_origin_ns:
+ * @cxt: mount context
+ *
+ * Switch to original namespace
+ *
+ * This is shorthand for
+ * <informalexample>
+ * <programlisting>
+ * mnt_context_switch_ns(cxt, mnt_context_get_origin_ns(cxt));
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: pointer to previous namespace or NULL on error
+ *
+ * Since: 2.33
+ */
+struct libmnt_ns *mnt_context_switch_origin_ns(struct libmnt_context *cxt)
+{
+ return mnt_context_switch_ns(cxt, mnt_context_get_origin_ns(cxt));
+}
+
+/**
+ * mnt_context_switch_target_ns:
+ * @cxt: mount context
+ *
+ * Switch to target namespace
+ *
+ * This is shorthand for
+ * <informalexample>
+ * <programlisting>
+ * mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt));
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: pointer to previous namespace or NULL on error
+ *
+ * Since: 2.33
+ */
+struct libmnt_ns *mnt_context_switch_target_ns(struct libmnt_context *cxt)
+{
+ return mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt));
+}
+
+
+#ifdef TEST_PROGRAM
+
+static int test_search_helper(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_context *cxt;
+ const char *type;
+ int rc;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ cxt = mnt_new_context();
+ if (!cxt)
+ return -ENOMEM;
+
+ type = argv[1];
+
+ mnt_context_get_fs(cxt); /* just to fill cxt->fs */
+ cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED; /* fake */
+
+ rc = mnt_context_prepare_helper(cxt, "mount", type);
+ printf("helper is: %s\n", cxt->helper ? cxt->helper : "not found");
+
+ mnt_free_context(cxt);
+ return rc;
+}
+
+
+static struct libmnt_lock *lock;
+
+static void lock_fallback(void)
+{
+ if (lock)
+ mnt_unlock_file(lock);
+}
+
+static int test_mount(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int idx = 1, rc = 0;
+ struct libmnt_context *cxt;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ cxt = mnt_new_context();
+ if (!cxt)
+ return -ENOMEM;
+
+ if (!strcmp(argv[idx], "-o")) {
+ mnt_context_set_options(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+ if (!strcmp(argv[idx], "-t")) {
+ /* TODO: use mnt_context_set_fstype_pattern() */
+ mnt_context_set_fstype(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+
+ if (argc == idx + 1)
+ /* mount <mountpoint>|<device> */
+ mnt_context_set_target(cxt, argv[idx++]);
+
+ else if (argc == idx + 2) {
+ /* mount <device> <mountpoint> */
+ mnt_context_set_source(cxt, argv[idx++]);
+ mnt_context_set_target(cxt, argv[idx++]);
+ }
+
+ /* this is unnecessary! -- libmount is able to internally
+ * create and manage the lock
+ */
+ lock = mnt_context_get_lock(cxt);
+ if (lock)
+ atexit(lock_fallback);
+
+ rc = mnt_context_mount(cxt);
+ if (rc)
+ warn("failed to mount");
+ else
+ printf("successfully mounted\n");
+
+ lock = NULL; /* because we use atexit lock_fallback */
+ mnt_free_context(cxt);
+ return rc;
+}
+
+static int test_umount(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int idx = 1, rc = 0;
+ struct libmnt_context *cxt;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ cxt = mnt_new_context();
+ if (!cxt)
+ return -ENOMEM;
+
+ if (!strcmp(argv[idx], "-t")) {
+ mnt_context_set_fstype(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+
+ if (!strcmp(argv[idx], "-f")) {
+ mnt_context_enable_force(cxt, TRUE);
+ idx++;
+ }
+
+ if (!strcmp(argv[idx], "-l")) {
+ mnt_context_enable_lazy(cxt, TRUE);
+ idx++;
+ }
+
+ if (!strcmp(argv[idx], "-r")) {
+ mnt_context_enable_rdonly_umount(cxt, TRUE);
+ idx++;
+ }
+
+ if (argc == idx + 1) {
+ /* mount <mountpoint>|<device> */
+ mnt_context_set_target(cxt, argv[idx++]);
+ } else {
+ rc = -EINVAL;
+ goto err;
+ }
+
+ lock = mnt_context_get_lock(cxt);
+ if (lock)
+ atexit(lock_fallback);
+
+ rc = mnt_context_umount(cxt);
+ if (rc)
+ printf("failed to umount\n");
+ else
+ printf("successfully umounted\n");
+err:
+ lock = NULL; /* because we use atexit lock_fallback */
+ mnt_free_context(cxt);
+ return rc;
+}
+
+static int test_flags(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int idx = 1, rc = 0;
+ struct libmnt_context *cxt;
+ const char *opt = NULL;
+ unsigned long flags = 0;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ cxt = mnt_new_context();
+ if (!cxt)
+ return -ENOMEM;
+
+ if (!strcmp(argv[idx], "-o")) {
+ mnt_context_set_options(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+
+ if (argc == idx + 1)
+ /* mount <mountpoint>|<device> */
+ mnt_context_set_target(cxt, argv[idx++]);
+
+ rc = mnt_context_prepare_mount(cxt);
+ if (rc)
+ printf("failed to prepare mount %s\n", strerror(-rc));
+
+ opt = mnt_fs_get_options(cxt->fs);
+ if (opt)
+ fprintf(stdout, "options: %s\n", opt);
+
+ mnt_context_get_mflags(cxt, &flags);
+ fprintf(stdout, "flags: %08lx\n", flags);
+
+ mnt_free_context(cxt);
+ return rc;
+}
+
+static int test_mountall(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_context *cxt;
+ struct libmnt_iter *itr;
+ struct libmnt_fs *fs;
+ int mntrc, ignored, idx = 1;
+
+ cxt = mnt_new_context();
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+
+ if (!cxt || !itr)
+ return -ENOMEM;
+
+ if (argc > 2) {
+ if (argv[idx] && !strcmp(argv[idx], "-O")) {
+ mnt_context_set_options_pattern(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+ if (argv[idx] && !strcmp(argv[idx], "-t")) {
+ mnt_context_set_fstype_pattern(cxt, argv[idx + 1]);
+ idx += 2;
+ }
+ }
+
+ while (mnt_context_next_mount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
+
+ const char *tgt = mnt_fs_get_target(fs);
+
+ if (ignored == 1)
+ printf("%s: ignored: not match\n", tgt);
+ else if (ignored == 2)
+ printf("%s: ignored: already mounted\n", tgt);
+
+ else if (!mnt_context_get_status(cxt)) {
+ if (mntrc > 0) {
+ errno = mntrc;
+ warn("%s: mount failed", tgt);
+ } else
+ warnx("%s: mount failed", tgt);
+ } else
+ printf("%s: successfully mounted\n", tgt);
+ }
+
+ mnt_free_context(cxt);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--mount", test_mount, "[-o <opts>] [-t <type>] <spec>|<src> <target>" },
+ { "--umount", test_umount, "[-t <type>] [-f][-l][-r] <src>|<target>" },
+ { "--mount-all", test_mountall, "[-O <pattern>] [-t <pattern] mount all filesystems from fstab" },
+ { "--flags", test_flags, "[-o <opts>] <spec>" },
+ { "--search-helper", test_search_helper, "<fstype>" },
+ { NULL }};
+
+ umask(S_IWGRP|S_IWOTH); /* to be compatible with mount(8) */
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/context_loopdev.c b/libmount/src/context_loopdev.c
new file mode 100644
index 0000000..62e319c
--- /dev/null
+++ b/libmount/src/context_loopdev.c
@@ -0,0 +1,465 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/*
+ * DOCS: - "lo@" prefix for fstype is unsupported
+ */
+
+#include <blkid.h>
+#include <stdbool.h>
+
+#include "mountP.h"
+#include "loopdev.h"
+#include "linux_version.h"
+
+
+int mnt_context_is_loopdev(struct libmnt_context *cxt)
+{
+ const char *type, *src;
+
+ assert(cxt);
+
+ /* The mount flags have to be merged, otherwise we have to use
+ * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return 0; /* backing file not set */
+
+ if (cxt->user_mountflags & (MNT_MS_LOOP |
+ MNT_MS_OFFSET |
+ MNT_MS_SIZELIMIT)) {
+
+ DBG(LOOP, ul_debugobj(cxt, "loopdev specific options detected"));
+ return 1;
+ }
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE))
+ || mnt_context_propagation_only(cxt))
+ return 0;
+
+ /* Automatically create a loop device from a regular file if a
+ * filesystem is not specified or the filesystem is known for libblkid
+ * (these filesystems work with block devices only). The file size
+ * should be at least 1KiB, otherwise we will create an empty loopdev with
+ * no mountable filesystem...
+ *
+ * Note that there is no restriction (on kernel side) that would prevent a regular
+ * file as a mount(2) source argument. A filesystem that is able to mount
+ * regular files could be implemented.
+ */
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ if (mnt_fs_is_regularfs(cxt->fs) &&
+ (!type || strcmp(type, "auto") == 0 || blkid_known_fstype(type))) {
+ struct stat st;
+
+ if (stat(src, &st) == 0 && S_ISREG(st.st_mode) &&
+ st.st_size > 1024) {
+ DBG(LOOP, ul_debugobj(cxt, "automatically enabling loop= option"));
+ cxt->user_mountflags |= MNT_MS_LOOP;
+ mnt_optstr_append_option(&cxt->fs->user_optstr, "loop", NULL);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Check if there already exists a mounted loop device on the mountpoint node
+ * with the same parameters.
+ */
+static int __attribute__((nonnull))
+is_mounted_same_loopfile(struct libmnt_context *cxt,
+ const char *target,
+ const char *backing_file,
+ uint64_t offset)
+{
+ struct libmnt_table *tb;
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+ struct libmnt_cache *cache;
+ const char *bf;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (mnt_context_get_mtab(cxt, &tb))
+ return 0;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ DBG(LOOP, ul_debugobj(cxt, "checking if %s mounted on %s",
+ backing_file, target));
+
+ cache = mnt_context_get_cache(cxt);
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+
+ bf = cache ? mnt_resolve_path(backing_file, cache) : backing_file;
+
+ /* Search for a mountpoint node in mtab, proceed if any of these have the
+ * loop option set or the device is a loop device
+ */
+ while (rc == 0 && mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ const char *src = mnt_fs_get_source(fs);
+ const char *opts = mnt_fs_get_user_options(fs);
+ char *val;
+ size_t len;
+
+ if (!src || !mnt_fs_match_target(fs, target, cache))
+ continue;
+
+ rc = 0;
+
+ if (strncmp(src, "/dev/loop", 9) == 0) {
+ rc = loopdev_is_used((char *) src, bf, offset, 0, LOOPDEV_FL_OFFSET);
+
+ } else if (opts && (cxt->user_mountflags & MNT_MS_LOOP) &&
+ mnt_optstr_get_option(opts, "loop", &val, &len) == 0 && val) {
+
+ val = strndup(val, len);
+ rc = loopdev_is_used((char *) val, bf, offset, 0, LOOPDEV_FL_OFFSET);
+ free(val);
+ }
+ }
+ if (rc)
+ DBG(LOOP, ul_debugobj(cxt, "%s already mounted", backing_file));
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+int mnt_context_setup_loopdev(struct libmnt_context *cxt)
+{
+ const char *backing_file, *optstr, *loopdev = NULL;
+ char *val = NULL, *loopval = NULL;
+ size_t len;
+ struct loopdev_cxt lc;
+ int rc = 0, lo_flags = 0;
+ uint64_t offset = 0, sizelimit = 0;
+ bool reuse = FALSE;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ backing_file = mnt_fs_get_srcpath(cxt->fs);
+ if (!backing_file)
+ return -EINVAL;
+
+ DBG(LOOP, ul_debugobj(cxt, "trying to setup device for %s", backing_file));
+
+ if (cxt->mountflags & MS_RDONLY) {
+ DBG(LOOP, ul_debugobj(cxt, "enabling READ-ONLY flag"));
+ lo_flags |= LO_FLAGS_READ_ONLY;
+ }
+
+ optstr = mnt_fs_get_user_options(cxt->fs);
+
+ /*
+ * loop=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_LOOP) &&
+ mnt_optstr_get_option(optstr, "loop", &val, &len) == 0 && val) {
+ loopval = strndup(val, len);
+ rc = loopval ? 0 : -ENOMEM;
+ }
+
+ /*
+ * offset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_OFFSET) &&
+ mnt_optstr_get_option(optstr, "offset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &offset);
+ if (rc) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to parse offset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * sizelimit=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_SIZELIMIT) &&
+ mnt_optstr_get_option(optstr, "sizelimit", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &sizelimit);
+ if (rc) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to parse sizelimit="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * encryption=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ENCRYPTION) &&
+ mnt_optstr_get_option(optstr, "encryption", &val, &len) == 0) {
+ DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported"));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+
+ if (rc == 0 && is_mounted_same_loopfile(cxt,
+ mnt_context_get_target(cxt),
+ backing_file, offset))
+ rc = -EBUSY;
+
+ if (rc)
+ goto done_no_deinit;
+
+ /* It is possible to mount the same file more times. If we set more
+ * than one loop device referring to the same file, kernel has no
+ * mechanism to detect it. To prevent data corruption, the same loop
+ * device has to be recycled.
+ */
+ if (backing_file) {
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ goto done_no_deinit;
+
+ rc = loopcxt_find_overlap(&lc, backing_file, offset, sizelimit);
+ switch (rc) {
+ case 0: /* not found */
+ DBG(LOOP, ul_debugobj(cxt, "not found overlapping loopdev"));
+ loopcxt_deinit(&lc);
+ break;
+
+ case 1: /* overlap */
+ DBG(LOOP, ul_debugobj(cxt, "overlapping %s detected",
+ loopcxt_get_device(&lc)));
+ rc = -MNT_ERR_LOOPOVERLAP;
+ goto done;
+
+ case 2: /* overlap -- full size and offset match (reuse) */
+ {
+ uint32_t lc_encrypt_type = 0;
+
+ DBG(LOOP, ul_debugobj(cxt, "re-using existing loop device %s",
+ loopcxt_get_device(&lc)));
+
+ /* Open loop device to block device autoclear... */
+ if (loopcxt_get_fd(&lc) < 0) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD"));
+ rc = -errno;
+ goto done;
+ }
+
+ /*
+ * Now that we certainly have the loop device open,
+ * verify the loop device was not autocleared in the
+ * mean time.
+ */
+ if (!loopcxt_get_info(&lc)) {
+ DBG(LOOP, ul_debugobj(cxt, "lost race with %s teardown",
+ loopcxt_get_device(&lc)));
+ loopcxt_deinit(&lc);
+ break;
+ }
+
+ /* 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)) {
+ DBG(LOOP, ul_debugobj(cxt, "%s is read-only",
+ loopcxt_get_device(&lc)));
+ rc = -EROFS;
+ goto done;
+ }
+
+ /* 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) {
+ DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported for device %s",
+ loopcxt_get_device(&lc)));
+ rc = -MNT_ERR_LOOPOVERLAP;
+ goto done;
+ }
+ rc = 0;
+ /* loop= used with argument. Conflict will occur. */
+ if (loopval) {
+ rc = -MNT_ERR_LOOPOVERLAP;
+ goto done;
+ } else {
+ reuse = TRUE;
+ goto success;
+ }
+ }
+ default: /* error */
+ goto done;
+ }
+ }
+
+ DBG(LOOP, ul_debugobj(cxt, "not found; create a new loop device"));
+ rc = loopcxt_init(&lc, 0);
+ if (rc)
+ goto done_no_deinit;
+ if (loopval) {
+ rc = loopcxt_set_device(&lc, loopval);
+ if (rc == 0)
+ loopdev = loopcxt_get_device(&lc);
+ }
+ if (rc)
+ goto done;
+
+ /* since 2.6.37 we don't have to store backing filename to mtab
+ * because kernel provides the name in /sys.
+ */
+ if (get_linux_version() >= KERNEL_VERSION(2, 6, 37) ||
+ !mnt_context_mtab_writable(cxt)) {
+ DBG(LOOP, ul_debugobj(cxt, "enabling AUTOCLEAR flag"));
+ lo_flags |= LO_FLAGS_AUTOCLEAR;
+ }
+
+ do {
+ /* found free device */
+ if (!loopdev) {
+ rc = loopcxt_find_unused(&lc);
+ if (rc)
+ goto done;
+ DBG(LOOP, ul_debugobj(cxt, "trying to use %s",
+ loopcxt_get_device(&lc)));
+ }
+
+ /* set device attributes
+ * -- note that loopcxt_find_unused() resets "lc"
+ */
+ rc = loopcxt_set_backing_file(&lc, backing_file);
+
+ if (!rc && offset)
+ rc = loopcxt_set_offset(&lc, offset);
+ if (!rc && sizelimit)
+ rc = loopcxt_set_sizelimit(&lc, sizelimit);
+ if (!rc)
+ loopcxt_set_flags(&lc, lo_flags);
+ if (rc) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to set loop attributes"));
+ goto done;
+ }
+
+ /* setup the device */
+ rc = loopcxt_setup_device(&lc);
+ if (!rc)
+ break; /* success */
+
+ if (loopdev || rc != -EBUSY) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to setup device"));
+ rc = -MNT_ERR_LOOPDEV;
+ goto done;
+ }
+ DBG(LOOP, ul_debugobj(cxt, "device stolen...trying again"));
+ } while (1);
+
+success:
+ if (!rc)
+ rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc));
+
+ if (!rc) {
+ /* success */
+ cxt->flags |= MNT_FL_LOOPDEV_READY;
+
+ if (reuse || ( (cxt->user_mountflags & MNT_MS_LOOP) &&
+ loopcxt_is_autoclear(&lc))) {
+ /*
+ * autoclear flag accepted by the kernel, don't store
+ * the "loop=" option to mtab.
+ */
+ DBG(LOOP, ul_debugobj(cxt, "removing unnecessary loop= from mtab"));
+ cxt->user_mountflags &= ~MNT_MS_LOOP;
+ mnt_optstr_remove_option(&cxt->fs->user_optstr, "loop");
+ }
+
+ if (!(cxt->mountflags & MS_RDONLY) &&
+ loopcxt_is_readonly(&lc))
+ /*
+ * mount planned read-write, but loopdev is read-only,
+ * let's fix mount options...
+ */
+ mnt_context_set_mflags(cxt, cxt->mountflags | MS_RDONLY);
+
+ /* we have to keep the device open until mount(1),
+ * otherwise it will be auto-cleared by kernel
+ */
+ cxt->loopdev_fd = loopcxt_get_fd(&lc);
+ if (cxt->loopdev_fd < 0) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD"));
+ rc = -errno;
+ } else
+ loopcxt_set_fd(&lc, -1, 0);
+ }
+done:
+ loopcxt_deinit(&lc);
+done_no_deinit:
+ free(loopval);
+ return rc;
+}
+
+/*
+ * Deletes loop device
+ */
+int mnt_context_delete_loopdev(struct libmnt_context *cxt)
+{
+ const char *src;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return -EINVAL;
+
+ if (cxt->loopdev_fd > -1)
+ close(cxt->loopdev_fd);
+
+ rc = loopdev_delete(src);
+ cxt->flags &= ~MNT_FL_LOOPDEV_READY;
+ cxt->loopdev_fd = -1;
+
+ DBG(LOOP, ul_debugobj(cxt, "deleted [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Clears loopdev stuff in context, should be called after
+ * failed or successful mount(2).
+ */
+int mnt_context_clear_loopdev(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ if (mnt_context_get_status(cxt) == 0 &&
+ (cxt->flags & MNT_FL_LOOPDEV_READY)) {
+ /*
+ * mount(2) failed, delete loopdev
+ */
+ mnt_context_delete_loopdev(cxt);
+
+ } else if (cxt->loopdev_fd > -1) {
+ /*
+ * mount(2) success, close the device
+ */
+ DBG(LOOP, ul_debugobj(cxt, "closing FD"));
+ close(cxt->loopdev_fd);
+ }
+ cxt->loopdev_fd = -1;
+ return 0;
+}
+
diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c
new file mode 100644
index 0000000..1fc3ff2
--- /dev/null
+++ b/libmount/src/context_mount.c
@@ -0,0 +1,2018 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: context-mount
+ * @title: Mount context
+ * @short_description: high-level API to mount operation.
+ */
+
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#endif
+
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include "linux_version.h"
+#include "mountP.h"
+#include "strutils.h"
+
+/*
+ * Kernel supports only one MS_PROPAGATION flag change by one mount(2) syscall,
+ * to bypass this restriction we call mount(2) per flag. It's really not a perfect
+ * solution, but it's the same like to execute multiple mount(8) commands.
+ *
+ * We use cxt->addmounts (additional mounts) list to keep order of the requested
+ * flags changes.
+ */
+struct libmnt_addmount *mnt_new_addmount(void)
+{
+ struct libmnt_addmount *ad = calloc(1, sizeof(*ad));
+ if (!ad)
+ return NULL;
+
+ INIT_LIST_HEAD(&ad->mounts);
+ return ad;
+}
+
+void mnt_free_addmount(struct libmnt_addmount *ad)
+{
+ if (!ad)
+ return;
+ list_del(&ad->mounts);
+ free(ad);
+}
+
+static int mnt_context_append_additional_mount(struct libmnt_context *cxt,
+ struct libmnt_addmount *ad)
+{
+ assert(cxt);
+ assert(ad);
+
+ if (!list_empty(&ad->mounts))
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt,
+ "mount: add additional flag: 0x%08lx",
+ ad->mountflags));
+
+ list_add_tail(&ad->mounts, &cxt->addmounts);
+ return 0;
+}
+
+/*
+ * add additional mount(2) syscall requests when necessary to set propagation flags
+ * after regular mount(2).
+ */
+static int init_propagation(struct libmnt_context *cxt)
+{
+ char *name;
+ char *opts = (char *) mnt_fs_get_vfs_options(cxt->fs);
+ size_t namesz;
+ struct libmnt_optmap const *maps[1];
+ int rec_count = 0;
+
+ if (!opts)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: initialize additional propagation mounts"));
+
+ maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+
+ while (!mnt_optstr_next_option(&opts, &name, &namesz, NULL, NULL)) {
+ const struct libmnt_optmap *ent;
+ struct libmnt_addmount *ad;
+ int rc;
+
+ if (!mnt_optmap_get_entry(maps, 1, name, namesz, &ent) || !ent)
+ continue;
+
+ DBG(CXT, ul_debugobj(cxt, " checking %s", ent->name));
+
+ /* Note that MS_REC may be used for more flags, so we have to keep
+ * track about number of recursive options to keep the MS_REC in the
+ * mountflags if necessary.
+ */
+ if (ent->id & MS_REC)
+ rec_count++;
+
+ if (!(ent->id & MS_PROPAGATION))
+ continue;
+
+ ad = mnt_new_addmount();
+ if (!ad)
+ return -ENOMEM;
+
+ ad->mountflags = ent->id;
+ DBG(CXT, ul_debugobj(cxt, " adding extra mount(2) call for %s", ent->name));
+ rc = mnt_context_append_additional_mount(cxt, ad);
+ if (rc)
+ return rc;
+
+ DBG(CXT, ul_debugobj(cxt, " removing %s from primary mount(2) call", ent->name));
+ cxt->mountflags &= ~ent->id;
+
+ if (ent->id & MS_REC)
+ rec_count--;
+ }
+
+ if (rec_count)
+ cxt->mountflags |= MS_REC;
+
+ return 0;
+}
+
+/*
+ * add additional mount(2) syscall request to implement "bind,<flags>", the first regular
+ * mount(2) is the "bind" operation, the second is "remount,bind,<flags>" call.
+ */
+static int init_bind_remount(struct libmnt_context *cxt)
+{
+ struct libmnt_addmount *ad;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->mountflags & MS_BIND);
+ assert(!(cxt->mountflags & MS_REMOUNT));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: initialize additional ro,bind mount"));
+
+ ad = mnt_new_addmount();
+ if (!ad)
+ return -ENOMEM;
+
+ ad->mountflags = cxt->mountflags;
+ ad->mountflags |= (MS_REMOUNT | MS_BIND);
+
+ rc = mnt_context_append_additional_mount(cxt, ad);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+#if defined(HAVE_LIBSELINUX) || defined(HAVE_SMACK)
+struct libmnt_optname {
+ const char *name;
+ size_t namesz;
+};
+
+#define DEF_OPTNAME(n) { .name = n, .namesz = sizeof(n) - 1 }
+#define DEF_OPTNAME_LAST { .name = NULL }
+
+static int is_option(const char *name, size_t namesz,
+ const struct libmnt_optname *names)
+{
+ const struct libmnt_optname *p;
+
+ for (p = names; p && p->name; p++) {
+ if (p->namesz == namesz
+ && strncmp(name, p->name, namesz) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* HAVE_LIBSELINUX || HAVE_SMACK */
+
+/*
+ * this has to be called after mnt_context_evaluate_permissions()
+ */
+static int fix_optstr(struct libmnt_context *cxt)
+{
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+ char *next;
+ char *name, *val;
+ size_t namesz, valsz;
+ struct libmnt_fs *fs;
+#ifdef HAVE_LIBSELINUX
+ int se_fix = 0, se_rem = 0;
+ static const struct libmnt_optname selinux_options[] = {
+ DEF_OPTNAME("context"),
+ DEF_OPTNAME("fscontext"),
+ DEF_OPTNAME("defcontext"),
+ DEF_OPTNAME("rootcontext"),
+ DEF_OPTNAME("seclabel"),
+ DEF_OPTNAME_LAST
+ };
+#endif
+#ifdef HAVE_SMACK
+ int sm_rem = 0;
+ static const struct libmnt_optname smack_options[] = {
+ DEF_OPTNAME("smackfsdef"),
+ DEF_OPTNAME("smackfsfloor"),
+ DEF_OPTNAME("smackfshat"),
+ DEF_OPTNAME("smackfsroot"),
+ DEF_OPTNAME("smackfstransmute"),
+ DEF_OPTNAME_LAST
+ };
+#endif
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs || (cxt->flags & MNT_FL_MOUNTOPTS_FIXED))
+ return 0;
+
+ fs = cxt->fs;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing options, current "
+ "vfs: '%s' fs: '%s' user: '%s', optstr: '%s'",
+ fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr));
+
+ /*
+ * The "user" options is our business (so we can modify the option),
+ * the exception is command line for /sbin/mount.<type> helpers. Let's
+ * save the original user=<name> to call the helpers with an unchanged
+ * "user" setting.
+ */
+ if (cxt->user_mountflags & MNT_MS_USER) {
+ if (!mnt_optstr_get_option(fs->user_optstr,
+ "user", &val, &valsz) && val) {
+ cxt->orig_user = strndup(val, valsz);
+ if (!cxt->orig_user) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ }
+ cxt->flags |= MNT_FL_SAVED_USER;
+ }
+
+ /*
+ * Sync mount options with mount flags
+ */
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing vfs optstr"));
+ rc = mnt_optstr_apply_flags(&fs->vfs_optstr, cxt->mountflags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ if (rc)
+ goto done;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing user optstr"));
+ rc = mnt_optstr_apply_flags(&fs->user_optstr, cxt->user_mountflags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
+ if (rc)
+ goto done;
+
+ if (fs->vfs_optstr && *fs->vfs_optstr == '\0') {
+ free(fs->vfs_optstr);
+ fs->vfs_optstr = NULL;
+ }
+ if (fs->user_optstr && *fs->user_optstr == '\0') {
+ free(fs->user_optstr);
+ fs->user_optstr = NULL;
+ }
+ if (cxt->mountflags & MS_PROPAGATION) {
+ rc = init_propagation(cxt);
+ if (rc)
+ return rc;
+ }
+ if ((cxt->mountflags & MS_BIND)
+ && (cxt->mountflags & MNT_BIND_SETTABLE)
+ && !(cxt->mountflags & MS_REMOUNT)) {
+ rc = init_bind_remount(cxt);
+ if (rc)
+ return rc;
+ }
+
+ next = fs->fs_optstr;
+
+#ifdef HAVE_LIBSELINUX
+ if (!is_selinux_enabled())
+ /* Always remove SELinux garbage if SELinux disabled */
+ se_rem = 1;
+ else if (cxt->mountflags & MS_REMOUNT)
+ /*
+ * Linux kernel < 2.6.39 does not support remount operation
+ * with any selinux specific mount options.
+ *
+ * Kernel 2.6.39 commits: ff36fe2c845cab2102e4826c1ffa0a6ebf487c65
+ * 026eb167ae77244458fa4b4b9fc171209c079ba7
+ * fix this odd behavior, so we don't have to care about it in
+ * userspace.
+ */
+ se_rem = get_linux_version() < KERNEL_VERSION(2, 6, 39);
+ else
+ /* For normal mount, contexts are translated */
+ se_fix = 1;
+
+ if (!se_rem) {
+ /* de-duplicate SELinux options */
+ const struct libmnt_optname *p;
+ for (p = selinux_options; p && p->name; p++)
+ mnt_optstr_deduplicate_option(&fs->fs_optstr, p->name);
+ }
+#endif
+#ifdef HAVE_SMACK
+ if (access("/sys/fs/smackfs", F_OK) != 0)
+ sm_rem = 1;
+#endif
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+
+ if (namesz == 3 && !strncmp(name, "uid", 3))
+ rc = mnt_optstr_fix_uid(&fs->fs_optstr, val, valsz, &next);
+ else if (namesz == 3 && !strncmp(name, "gid", 3))
+ rc = mnt_optstr_fix_gid(&fs->fs_optstr, val, valsz, &next);
+#ifdef HAVE_LIBSELINUX
+ else if ((se_rem || se_fix)
+ && is_option(name, namesz, selinux_options)) {
+
+ if (se_rem) {
+ /* remove context= option */
+ next = name;
+ rc = mnt_optstr_remove_option_at(&fs->fs_optstr,
+ name,
+ val ? val + valsz :
+ name + namesz);
+ } else if (se_fix && val && valsz)
+ /* translate selinux contexts */
+ rc = mnt_optstr_fix_secontext(&fs->fs_optstr,
+ val, valsz, &next);
+ }
+#endif
+#ifdef HAVE_SMACK
+ else if (sm_rem && is_option(name, namesz, smack_options)) {
+
+ next = name;
+ rc = mnt_optstr_remove_option_at(&fs->fs_optstr,
+ name,
+ val ? val + valsz : name + namesz);
+ }
+#endif
+ if (rc)
+ goto done;
+ }
+
+
+ if (!rc && mnt_context_is_restricted(cxt) && (cxt->user_mountflags & MNT_MS_USER)) {
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = mnt_optstr_fix_user(&fs->user_optstr);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ }
+
+ /* refresh merged optstr */
+ free(fs->optstr);
+ fs->optstr = NULL;
+ fs->optstr = mnt_fs_strdup_options(fs);
+done:
+ cxt->flags |= MNT_FL_MOUNTOPTS_FIXED;
+
+ DBG(CXT, ul_debugobj(cxt, "fixed options [rc=%d]: "
+ "vfs: '%s' fs: '%s' user: '%s', optstr: '%s'", rc,
+ fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr));
+
+ if (rc)
+ rc = -MNT_ERR_MOUNTOPT;
+ return rc;
+}
+
+/*
+ * Converts the already evaluated and fixed options to the form that is compatible
+ * with /sbin/mount.type helpers.
+ */
+static int generate_helper_optstr(struct libmnt_context *cxt, char **optstr)
+{
+ struct libmnt_optmap const *maps[2];
+ char *next, *name, *val;
+ size_t namesz, valsz;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(optstr);
+
+ DBG(CXT, ul_debugobj(cxt, "mount: generate helper mount options"));
+
+ *optstr = mnt_fs_strdup_options(cxt->fs);
+ if (!*optstr)
+ return -ENOMEM;
+
+ if ((cxt->user_mountflags & MNT_MS_USER) ||
+ (cxt->user_mountflags & MNT_MS_USERS)) {
+ /*
+ * This is unnecessary for real user-mounts as mount.<type>
+ * helpers always have to follow fstab rather than mount
+ * options on the command line.
+ *
+ * However, if you call mount.<type> as root, then the helper follows
+ * the command line. If there is (for example) "user,exec" in fstab,
+ * then we have to manually append the "exec" back to the options
+ * string, because there is nothing like MS_EXEC (we only have
+ * MS_NOEXEC in mount flags and we don't care about the original
+ * mount string in libmount for VFS options).
+ *
+ * This use-case makes sense for MS_SECURE flags only (see
+ * mnt_optstr_get_flags() and mnt_context_merge_mflags()).
+ */
+ if (!(cxt->mountflags & MS_NOEXEC))
+ mnt_optstr_append_option(optstr, "exec", NULL);
+ if (!(cxt->mountflags & MS_NOSUID))
+ mnt_optstr_append_option(optstr, "suid", NULL);
+ if (!(cxt->mountflags & MS_NODEV))
+ mnt_optstr_append_option(optstr, "dev", NULL);
+ }
+
+ if (cxt->flags & MNT_FL_SAVED_USER)
+ rc = mnt_optstr_set_option(optstr, "user", cxt->orig_user);
+ if (rc)
+ goto err;
+
+ /* remove userspace options with MNT_NOHLPS flag */
+ maps[0] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
+ maps[1] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ next = *optstr;
+
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+ const struct libmnt_optmap *ent;
+
+ mnt_optmap_get_entry(maps, 2, name, namesz, &ent);
+ if (ent && ent->id && (ent->mask & MNT_NOHLPS)) {
+ next = name;
+ rc = mnt_optstr_remove_option_at(optstr, name,
+ val ? val + valsz : name + namesz);
+ if (rc)
+ goto err;
+ }
+ }
+
+ return rc;
+err:
+ free(*optstr);
+ *optstr = NULL;
+ return rc;
+}
+
+/*
+ * this has to be called before fix_optstr()
+ *
+ * Note that user=<name> may be used by some filesystems as a filesystem
+ * specific option (e.g. cifs). Yes, developers of such filesystems have
+ * allocated pretty hot place in hell...
+ */
+static int evaluate_permissions(struct libmnt_context *cxt)
+{
+ unsigned long u_flags = 0;
+
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: evaluating permissions"));
+
+ mnt_context_get_user_mflags(cxt, &u_flags);
+
+ if (!mnt_context_is_restricted(cxt)) {
+ /*
+ * superuser mount
+ */
+ cxt->user_mountflags &= ~MNT_MS_OWNER;
+ cxt->user_mountflags &= ~MNT_MS_GROUP;
+ } else {
+ /*
+ * user mount
+ */
+ if (!mnt_context_tab_applied(cxt))
+ {
+ DBG(CXT, ul_debugobj(cxt, "perms: fstab not applied, ignore user mount"));
+ return -EPERM;
+ }
+
+ /*
+ * MS_OWNERSECURE and MS_SECURE mount options are already
+ * applied by mnt_optstr_get_flags() in mnt_context_merge_mflags()
+ * if "user" (but no user=<name> !) options is set.
+ *
+ * Let's ignore all user=<name> (if <name> is set) requests.
+ */
+ if (cxt->user_mountflags & MNT_MS_USER) {
+ size_t valsz = 0;
+
+ if (!mnt_optstr_get_option(cxt->fs->user_optstr,
+ "user", NULL, &valsz) && valsz) {
+
+ DBG(CXT, ul_debugobj(cxt, "perms: user=<name> detected, ignore"));
+ cxt->user_mountflags &= ~MNT_MS_USER;
+ }
+ }
+
+ /*
+ * MS_OWNER: Allow owners to mount when fstab contains the
+ * owner option. Note that this should never be used in a high
+ * security environment, but may be useful to give people at
+ * the console the possibility of mounting a floppy. MS_GROUP:
+ * Allow members of device group to mount. (Martin Dickopp)
+ */
+ if (u_flags & (MNT_MS_OWNER | MNT_MS_GROUP)) {
+ struct stat sb;
+ struct libmnt_cache *cache = NULL;
+ char *xsrc = NULL;
+ const char *srcpath = mnt_fs_get_srcpath(cxt->fs);
+
+ if (!srcpath) { /* Ah... source is TAG */
+ cache = mnt_context_get_cache(cxt);
+ xsrc = mnt_resolve_spec(
+ mnt_context_get_source(cxt),
+ cache);
+ srcpath = xsrc;
+ }
+ if (!srcpath) {
+ DBG(CXT, ul_debugobj(cxt, "perms: src undefined"));
+ return -EPERM;
+ }
+
+ if (strncmp(srcpath, "/dev/", 5) == 0 &&
+ stat(srcpath, &sb) == 0 &&
+ (((u_flags & MNT_MS_OWNER) && getuid() == sb.st_uid) ||
+ ((u_flags & MNT_MS_GROUP) && mnt_in_group(sb.st_gid))))
+
+ cxt->user_mountflags |= MNT_MS_USER;
+
+ if (!cache)
+ free(xsrc);
+ }
+
+ if (!(cxt->user_mountflags & (MNT_MS_USER | MNT_MS_USERS))) {
+ DBG(CXT, ul_debugobj(cxt, "permissions evaluation ends with -EPERMS"));
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * mnt_context_helper_setopt() backend
+ *
+ * This function applies the mount.type command line option (for example parsed
+ * by getopt() or getopt_long()) to @cxt. All unknown options are ignored and
+ * then 1 is returned.
+ *
+ * Returns: negative number on error, 1 if @c is unknown option, 0 on success.
+ */
+int mnt_context_mount_setopt(struct libmnt_context *cxt, int c, char *arg)
+{
+ int rc = -EINVAL;
+
+ assert(cxt);
+ assert(cxt->action == MNT_ACT_MOUNT);
+
+ switch(c) {
+ case 'f':
+ rc = mnt_context_enable_fake(cxt, TRUE);
+ break;
+ case 'n':
+ rc = mnt_context_disable_mtab(cxt, TRUE);
+ break;
+ case 'r':
+ rc = mnt_context_append_options(cxt, "ro");
+ break;
+ case 'v':
+ rc = mnt_context_enable_verbose(cxt, TRUE);
+ break;
+ case 'w':
+ rc = mnt_context_append_options(cxt, "rw");
+ break;
+ case 'o':
+ if (arg)
+ rc = mnt_context_append_options(cxt, arg);
+ break;
+ case 's':
+ rc = mnt_context_enable_sloppy(cxt, TRUE);
+ break;
+ case 't':
+ if (arg)
+ rc = mnt_context_set_fstype(cxt, arg);
+ break;
+ case 'N':
+ if (arg)
+ rc = mnt_context_set_target_ns(cxt, arg);
+ break;
+ default:
+ return 1;
+ }
+
+ return rc;
+}
+
+static int exec_helper(struct libmnt_context *cxt)
+{
+ char *o = NULL, *namespace = NULL;
+ struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt);
+ int rc;
+ pid_t pid;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: executing helper %s", cxt->helper));
+
+ rc = generate_helper_optstr(cxt, &o);
+ if (rc)
+ return -EINVAL;
+
+ if (ns_tgt->fd != -1
+ && asprintf(&namespace, "/proc/%i/fd/%i",
+ getpid(), ns_tgt->fd) == -1) {
+ free(o);
+ return -ENOMEM;
+ }
+
+ DBG_FLUSH;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ {
+ const char *args[14], *type;
+ int i = 0;
+
+ if (drop_permissions() != 0)
+ _exit(EXIT_FAILURE);
+
+ if (!mnt_context_switch_origin_ns(cxt))
+ _exit(EXIT_FAILURE);
+
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ args[i++] = cxt->helper; /* 1 */
+ args[i++] = mnt_fs_get_srcpath(cxt->fs);/* 2 */
+ args[i++] = mnt_fs_get_target(cxt->fs); /* 3 */
+
+ if (mnt_context_is_sloppy(cxt))
+ args[i++] = "-s"; /* 4 */
+ if (mnt_context_is_fake(cxt))
+ args[i++] = "-f"; /* 5 */
+ if (mnt_context_is_nomtab(cxt))
+ args[i++] = "-n"; /* 6 */
+ if (mnt_context_is_verbose(cxt))
+ args[i++] = "-v"; /* 7 */
+ if (o) {
+ args[i++] = "-o"; /* 8 */
+ args[i++] = o; /* 9 */
+ }
+ if (type
+ && strchr(type, '.')
+ && !endswith(cxt->helper, type)) {
+ args[i++] = "-t"; /* 10 */
+ args[i++] = type; /* 11 */
+ }
+ if (namespace) {
+ args[i++] = "-N"; /* 11 */
+ args[i++] = namespace; /* 12 */
+ }
+ args[i] = NULL; /* 13 */
+ for (i = 0; args[i]; i++)
+ DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"",
+ i, args[i]));
+ DBG_FLUSH;
+ execv(cxt->helper, (char * const *) args);
+ _exit(EXIT_FAILURE);
+ }
+ default:
+ {
+ int st;
+
+ if (waitpid(pid, &st, 0) == (pid_t) -1) {
+ cxt->helper_status = -1;
+ rc = -errno;
+ } else {
+ cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1;
+ cxt->helper_exec_status = rc = 0;
+ }
+ DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]",
+ cxt->helper,
+ cxt->helper_status, rc,
+ rc ? " waitpid failed" : ""));
+ break;
+ }
+
+ case -1:
+ cxt->helper_exec_status = rc = -errno;
+ DBG(CXT, ul_debugobj(cxt, "fork() failed"));
+ break;
+ }
+
+ free(o);
+ free(namespace);
+ return rc;
+}
+
+static int do_mount_additional(struct libmnt_context *cxt,
+ const char *target,
+ unsigned long flags,
+ int *syserr)
+{
+ struct list_head *p;
+
+ assert(cxt);
+ assert(target);
+
+ if (syserr)
+ *syserr = 0;
+
+ list_for_each(p, &cxt->addmounts) {
+ int rc;
+ struct libmnt_addmount *ad =
+ list_entry(p, struct libmnt_addmount, mounts);
+
+ DBG(CXT, ul_debugobj(cxt, "mount(2) changing flag: 0x%08lx %s",
+ ad->mountflags,
+ ad->mountflags & MS_REC ? " (recursive)" : ""));
+
+ rc = mount("none", target, NULL,
+ ad->mountflags | (flags & MS_SILENT), NULL);
+ if (rc) {
+ if (syserr)
+ *syserr = -errno;
+ DBG(CXT, ul_debugobj(cxt,
+ "mount(2) failed [errno=%d %m]",
+ errno));
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int do_mount_subdir(struct libmnt_context *cxt,
+ const char *root,
+ const char *subdir,
+ const char *target)
+{
+ char *src = NULL;
+ int rc = 0;
+
+ if (asprintf(&src, "%s/%s", root, subdir) < 0)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "mount subdir %s to %s", src, target));
+ if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ DBG(CXT, ul_debugobj(cxt, "umount old root %s", root));
+ if (umount(root) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ free(src);
+ return rc;
+}
+
+/*
+ * The default is to use fstype from cxt->fs, this could be overwritten by
+ * @try_type argument. If @try_type is specified then mount with MS_SILENT.
+ *
+ * Returns: 0 on success,
+ * >0 in case of mount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+static int do_mount(struct libmnt_context *cxt, const char *try_type)
+{
+ int rc = 0, old_ns_fd = -1;
+ const char *src, *target, *type;
+ unsigned long flags;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (try_type) {
+ rc = mnt_context_prepare_helper(cxt, "mount", try_type);
+ if (rc)
+ return rc;
+ }
+
+ flags = cxt->mountflags;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ target = mnt_fs_get_target(cxt->fs);
+
+ if (cxt->helper) {
+ rc = exec_helper(cxt);
+
+ if (mnt_context_helper_executed(cxt)
+ && mnt_context_get_helper_status(cxt) == 0
+ && !list_empty(&cxt->addmounts)
+ && do_mount_additional(cxt, target, flags, NULL))
+
+ return -MNT_ERR_APPLYFLAGS;
+ return rc;
+ }
+
+ if (!target)
+ return -EINVAL;
+ if (!src) {
+ /* unnecessary, should be already resolved in
+ * mnt_context_prepare_srcpath(), but to be sure... */
+ DBG(CXT, ul_debugobj(cxt, "WARNING: source is NULL -- using \"none\"!"));
+ src = "none";
+ }
+ type = try_type ? : mnt_fs_get_fstype(cxt->fs);
+
+ if (try_type)
+ flags |= MS_SILENT;
+
+
+ if (mnt_context_is_fake(cxt)) {
+ /*
+ * fake
+ */
+ cxt->syscall_status = 0;
+
+ DBG(CXT, ul_debugobj(cxt, "FAKE mount(2) "
+ "[source=%s, target=%s, type=%s, "
+ " mountflags=0x%08lx, mountdata=%s]",
+ src, target, type,
+ flags, cxt->mountdata ? "yes" : "<none>"));
+
+ } else if (mnt_context_propagation_only(cxt)) {
+ /*
+ * propagation flags *only*
+ */
+ if (do_mount_additional(cxt, target, flags, &cxt->syscall_status))
+ return -MNT_ERR_APPLYFLAGS;
+ } else {
+ /*
+ * regular mount
+ */
+
+ /* create unhared temporary target */
+ if (cxt->subdir) {
+ rc = mnt_tmptgt_unshare(&old_ns_fd);
+ if (rc)
+ return rc;
+ target = MNT_PATH_TMPTGT;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "mount(2) "
+ "[source=%s, target=%s, type=%s, "
+ " mountflags=0x%08lx, mountdata=%s]",
+ src, target, type,
+ flags, cxt->mountdata ? "yes" : "<none>"));
+
+ if (mount(src, target, type, flags, cxt->mountdata)) {
+ cxt->syscall_status = -errno;
+ DBG(CXT, ul_debugobj(cxt, "mount(2) failed [errno=%d %m]",
+ -cxt->syscall_status));
+ rc = -cxt->syscall_status;
+ goto done;
+ }
+ DBG(CXT, ul_debugobj(cxt, " mount(2) success"));
+ cxt->syscall_status = 0;
+
+ /*
+ * additional mounts for extra propagation flags
+ */
+ if (!list_empty(&cxt->addmounts)
+ && do_mount_additional(cxt, target, flags, NULL)) {
+
+ /* TODO: call umount? */
+ rc = -MNT_ERR_APPLYFLAGS;
+ goto done;
+ }
+
+ /*
+ * bind subdir to the real target, umount temporary target
+ */
+ if (cxt->subdir) {
+ target = mnt_fs_get_target(cxt->fs);
+ rc = do_mount_subdir(cxt, MNT_PATH_TMPTGT, cxt->subdir, target);
+ if (rc)
+ goto done;
+ mnt_tmptgt_cleanup(old_ns_fd);
+ old_ns_fd = -1;
+ }
+ }
+
+ if (try_type && cxt->update) {
+ struct libmnt_fs *fs = mnt_update_get_fs(cxt->update);
+ if (fs)
+ rc = mnt_fs_set_fstype(fs, try_type);
+ }
+
+done:
+ if (old_ns_fd >= 0)
+ mnt_tmptgt_cleanup(old_ns_fd);
+
+ return rc;
+}
+
+static int is_success_status(struct libmnt_context *cxt)
+{
+ if (mnt_context_helper_executed(cxt))
+ return mnt_context_get_helper_status(cxt) == 0;
+
+ if (mnt_context_syscall_called(cxt))
+ return mnt_context_get_status(cxt) == 1;
+
+ return 0;
+}
+
+/* try mount(2) for all items in comma separated list of the filesystem @types */
+static int do_mount_by_types(struct libmnt_context *cxt, const char *types)
+{
+ int rc = -EINVAL;
+ char *p, *p0;
+
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "trying to mount by FS list '%s'", types));
+
+ p0 = p = strdup(types);
+ if (!p)
+ return -ENOMEM;
+ do {
+ char *autotype = NULL;
+ char *end = strchr(p, ',');
+
+ if (end)
+ *end = '\0';
+
+ DBG(CXT, ul_debugobj(cxt, "-->trying '%s'", p));
+
+ /* Let's support things like "udf,iso9660,auto" */
+ if (strcmp(p, "auto") == 0) {
+ rc = mnt_context_guess_srcpath_fstype(cxt, &autotype);
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "failed to guess FS type [rc=%d]", rc));
+ free(p0);
+ free(autotype);
+ return rc;
+ }
+ p = autotype;
+ DBG(CXT, ul_debugobj(cxt, " --> '%s'", p));
+ }
+
+ if (p)
+ rc = do_mount(cxt, p);
+ p = end ? end + 1 : NULL;
+ free(autotype);
+ } while (!is_success_status(cxt) && p);
+
+ free(p0);
+ return rc;
+}
+
+
+static int do_mount_by_pattern(struct libmnt_context *cxt, const char *pattern)
+{
+ int neg = pattern && strncmp(pattern, "no", 2) == 0;
+ int rc = -EINVAL;
+ char **filesystems, **fp;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ /*
+ * Use the pattern as list of the filesystems
+ */
+ if (!neg && pattern) {
+ DBG(CXT, ul_debugobj(cxt, "use FS pattern as FS list"));
+ return do_mount_by_types(cxt, pattern);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "trying to mount by FS pattern '%s'", pattern));
+
+ /*
+ * Apply pattern to /etc/filesystems and /proc/filesystems
+ */
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+ rc = mnt_get_filesystems(&filesystems, neg ? pattern : NULL);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ if (rc)
+ return rc;
+
+ if (filesystems == NULL)
+ return -MNT_ERR_NOFSTYPE;
+
+ for (fp = filesystems; *fp; fp++) {
+ DBG(CXT, ul_debugobj(cxt, " ##### trying '%s'", *fp));
+ rc = do_mount(cxt, *fp);
+ if (is_success_status(cxt))
+ break;
+ if (mnt_context_get_syscall_errno(cxt) != EINVAL &&
+ mnt_context_get_syscall_errno(cxt) != ENODEV)
+ break;
+ }
+ mnt_free_filesystems(filesystems);
+ return rc;
+}
+
+/**
+ * mnt_context_prepare_mount:
+ * @cxt: context
+ *
+ * Prepare context for mounting, unnecessary for mnt_context_mount().
+ *
+ * Returns: negative number on error, zero on success
+ */
+int mnt_context_prepare_mount(struct libmnt_context *cxt)
+{
+ int rc = -EINVAL;
+ struct libmnt_ns *ns_old;
+
+ if (!cxt || !cxt->fs || mnt_fs_is_swaparea(cxt->fs))
+ return -EINVAL;
+ if (!mnt_fs_get_source(cxt->fs) && !mnt_fs_get_target(cxt->fs))
+ return -EINVAL;
+ if (cxt->flags & MNT_FL_PREPARED)
+ return 0;
+
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ cxt->action = MNT_ACT_MOUNT;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: preparing"));
+
+ rc = mnt_context_apply_fstab(cxt);
+ if (!rc)
+ rc = mnt_context_merge_mflags(cxt);
+ if (!rc)
+ rc = evaluate_permissions(cxt);
+ if (!rc)
+ rc = fix_optstr(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_srcpath(cxt);
+ if (!rc)
+ rc = mnt_context_guess_fstype(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_target(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_helper(cxt, "mount", NULL);
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "mount: preparing failed"));
+ goto end;
+ }
+ cxt->flags |= MNT_FL_PREPARED;
+
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_do_mount
+ * @cxt: context
+ *
+ * Call mount(2) or mount.type helper. Unnecessary for mnt_context_mount().
+ *
+ * Note that this function could be called only once. If you want to mount
+ * another source or target, then you have to call mnt_reset_context().
+ *
+ * If you want to call mount(2) for the same source and target with different
+ * mount flags or fstype, then call mnt_context_reset_status() and then try
+ * again mnt_context_do_mount().
+ *
+ * WARNING: non-zero return code does not mean that mount(2) syscall or
+ * mount.type helper wasn't successfully called.
+ *
+ * Check mnt_context_get_status() after error! See mnt_context_mount() for more
+ * details about errors and warnings.
+ *
+ * Returns: 0 on success;
+ * >0 in case of mount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_do_mount(struct libmnt_context *cxt)
+{
+ const char *type;
+ int res;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->action == MNT_ACT_MOUNT));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: do mount"));
+
+ if (!(cxt->flags & MNT_FL_MOUNTDATA))
+ cxt->mountdata = (char *) mnt_fs_get_fs_options(cxt->fs);
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ type = mnt_fs_get_fstype(cxt->fs);
+ if (type) {
+ if (strchr(type, ','))
+ /* this only happens if fstab contains a list of filesystems */
+ res = do_mount_by_types(cxt, type);
+ else
+ res = do_mount(cxt, NULL);
+ } else
+ res = do_mount_by_pattern(cxt, cxt->fstype_pattern);
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (mnt_context_get_status(cxt)
+ && !mnt_context_is_fake(cxt)
+ && !cxt->helper
+ && mnt_context_mtab_writable(cxt)) {
+
+ int is_rdonly = -1;
+
+ DBG(CXT, ul_debugobj(cxt, "checking for RDONLY mismatch"));
+
+ /*
+ * Mounted by mount(2), do some post-mount checks
+ *
+ * Kernel can be used to use MS_RDONLY for bind mounts, but the
+ * read-only request could be silently ignored. Check it to
+ * avoid 'ro' in mtab and 'rw' in /proc/mounts.
+ */
+ if ((cxt->mountflags & MS_BIND)
+ && (cxt->mountflags & MS_RDONLY)) {
+
+ if (is_rdonly < 0)
+ is_rdonly = mnt_is_readonly(mnt_context_get_target(cxt));
+ if (!is_rdonly)
+ mnt_context_set_mflags(cxt, cxt->mountflags & ~MS_RDONLY);
+ }
+
+
+ /* Kernel can silently add MS_RDONLY flag when mounting file
+ * system that does not have write support. Check this to avoid
+ * 'ro' in /proc/mounts and 'rw' in mtab.
+ */
+ if (!(cxt->mountflags & (MS_RDONLY | MS_MOVE))
+ && !mnt_context_propagation_only(cxt)) {
+
+ if (is_rdonly < 0)
+ is_rdonly = mnt_is_readonly(mnt_context_get_target(cxt));
+ if (is_rdonly)
+ mnt_context_set_mflags(cxt, cxt->mountflags | MS_RDONLY);
+ }
+ }
+#endif
+
+ /* Cleanup will be immediate on failure, and deferred to umount on success */
+ if (mnt_context_is_veritydev(cxt))
+ mnt_context_deferred_delete_veritydev(cxt);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return res;
+}
+
+/*
+ * Returns mountinfo FS entry of context source patch if the source is already
+ * mounted. This function is used for "already mounted" message or to get FS of
+ * re-used loop device.
+ */
+static struct libmnt_fs *get_already_mounted_source(struct libmnt_context *cxt)
+{
+ const char *src;
+ struct libmnt_table *tb;
+
+ assert(cxt);
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+
+ if (src && mnt_context_get_mtab(cxt, &tb) == 0) {
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ const char *s = mnt_fs_get_srcpath(fs),
+ *t = mnt_fs_get_target(fs);
+
+ if (t && s && mnt_fs_streq_srcpath(fs, src))
+ return fs;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Checks if source filesystem superblock is already ro-mounted. Note that we
+ * care about FS superblock as VFS node is irrelevant here.
+ */
+static int is_source_already_rdonly(struct libmnt_context *cxt)
+{
+ struct libmnt_fs *fs = get_already_mounted_source(cxt);
+ const char *opts = fs ? mnt_fs_get_fs_options(fs) : NULL;
+
+ return opts && mnt_optstr_get_option(opts, "ro", NULL, NULL) == 0;
+}
+
+/**
+ * mnt_context_finalize_mount:
+ * @cxt: context
+ *
+ * Mtab update, etc. Unnecessary for mnt_context_mount(), but should be called
+ * after mnt_context_do_mount(). See also mnt_context_set_syscall_status().
+ *
+ * Returns: negative number on error, 0 on success.
+ */
+int mnt_context_finalize_mount(struct libmnt_context *cxt)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert((cxt->flags & MNT_FL_PREPARED));
+
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+ return rc;
+}
+
+/**
+ * mnt_context_mount:
+ * @cxt: mount context
+ *
+ * High-level, mounts the filesystem by mount(2) or fork()+exec(/sbin/mount.type).
+ *
+ * This is similar to:
+ *
+ * mnt_context_prepare_mount(cxt);
+ * mnt_context_do_mount(cxt);
+ * mnt_context_finalize_mount(cxt);
+ *
+ * See also mnt_context_disable_helpers().
+ *
+ * Note that this function should be called only once. If you want to mount with
+ * different settings, then you have to call mnt_reset_context(). It's NOT enough
+ * to call mnt_context_reset_status(). If you want to call this function more than
+ * once, the whole context has to be reset.
+ *
+ * WARNING: non-zero return code does not mean that mount(2) syscall or
+ * mount.type helper wasn't successfully called.
+ *
+ * Always use mnt_context_get_status():
+ *
+ * <informalexample>
+ * <programlisting>
+ * rc = mnt_context_mount(cxt);
+ *
+ * if (mnt_context_helper_executed(cxt))
+ * return mnt_context_get_helper_status(cxt);
+ * if (rc == 0 && mnt_context_get_status(cxt) == 1)
+ * return MNT_EX_SUCCESS;
+ * return MNT_EX_FAIL;
+ * </programlisting>
+ * </informalexample>
+ *
+ * or mnt_context_get_excode() to generate mount(8) compatible error
+ * or warning message:
+ *
+ * <informalexample>
+ * <programlisting>
+ * rc = mnt_context_mount(cxt);
+ * rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf));
+ * if (buf)
+ * warnx(_("%s: %s"), mnt_context_get_target(cxt), buf);
+ * return rc; // MNT_EX_*
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: 0 on success;
+ * >0 in case of mount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_mount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+again:
+ rc = mnt_context_prepare_mount(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_do_mount(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+
+ /*
+ * Read-only device or already read-only mounted FS.
+ * Try mount the filesystem read-only.
+ */
+ if ((rc == -EROFS && !mnt_context_syscall_called(cxt)) /* before syscall; rdonly loopdev */
+ || mnt_context_get_syscall_errno(cxt) == EROFS /* syscall failed with EROFS */
+ || mnt_context_get_syscall_errno(cxt) == EACCES /* syscall failed with EACCES */
+ || (mnt_context_get_syscall_errno(cxt) == EBUSY /* already ro-mounted FS */
+ && is_source_already_rdonly(cxt)))
+ {
+ unsigned long mflags = 0;
+
+ mnt_context_get_mflags(cxt, &mflags);
+
+ if (!(mflags & MS_RDONLY) /* not yet RDONLY */
+ && !(mflags & MS_REMOUNT) /* not remount */
+ && !(mflags & MS_BIND) /* not bin mount */
+ && !mnt_context_is_rwonly_mount(cxt)) { /* no explicit read-write */
+
+ assert(!(cxt->flags & MNT_FL_FORCED_RDONLY));
+ DBG(CXT, ul_debugobj(cxt, "write-protected source, trying RDONLY."));
+
+ mnt_context_reset_status(cxt);
+ mnt_context_set_mflags(cxt, mflags | MS_RDONLY);
+ cxt->flags |= MNT_FL_FORCED_RDONLY;
+ goto again;
+ }
+ }
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+/**
+ * mnt_context_next_mount:
+ * @cxt: context
+ * @itr: iterator
+ * @fs: returns the current filesystem
+ * @mntrc: returns the return code from mnt_context_mount()
+ * @ignored: returns 1 for non-matching and 2 for already mounted filesystems
+ *
+ * This function tries to mount the next filesystem from fstab (as returned by
+ * mnt_context_get_fstab()). See also mnt_context_set_fstab().
+ *
+ * You can filter out filesystems by:
+ * mnt_context_set_options_pattern() to simulate mount -a -O pattern
+ * mnt_context_set_fstype_pattern() to simulate mount -a -t pattern
+ *
+ * If the filesystem is already mounted or does not match defined criteria,
+ * then the mnt_context_next_mount() function returns zero, but the @ignored is
+ * non-zero. Note that the root filesystem and filesystems with "noauto" option
+ * are always ignored.
+ *
+ * If mount(2) syscall or mount.type helper failed, then the
+ * mnt_context_next_mount() function returns zero, but the @mntrc is non-zero.
+ * Use also mnt_context_get_status() to check if the filesystem was
+ * successfully mounted.
+ *
+ * See mnt_context_mount() for more details about errors and warnings.
+ *
+ * Returns: 0 on success,
+ * <0 in case of error (!= mount(2) errors)
+ * 1 at the end of the list.
+ */
+int mnt_context_next_mount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc,
+ int *ignored)
+{
+ struct libmnt_table *fstab, *mtab;
+ const char *o, *tgt;
+ int rc, mounted = 0;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_fstab(cxt, &fstab);
+ if (rc)
+ return rc;
+
+ rc = mnt_table_next_fs(fstab, itr, fs);
+ if (rc != 0)
+ return rc; /* more filesystems (or error) */
+
+ o = mnt_fs_get_user_options(*fs);
+ tgt = mnt_fs_get_target(*fs);
+
+ DBG(CXT, ul_debugobj(cxt, "next-mount: trying %s", tgt));
+
+ /* ignore swap */
+ if (mnt_fs_is_swaparea(*fs) ||
+
+ /* ignore root filesystem */
+ (tgt && (strcmp(tgt, "/") == 0 || strcmp(tgt, "root") == 0)) ||
+
+ /* ignore noauto filesystems */
+ (o && mnt_optstr_get_option(o, "noauto", NULL, NULL) == 0) ||
+
+ /* ignore filesystems which don't match options patterns */
+ (cxt->fstype_pattern && !mnt_fs_match_fstype(*fs,
+ cxt->fstype_pattern)) ||
+
+ /* ignore filesystems which don't match type patterns */
+ (cxt->optstr_pattern && !mnt_fs_match_options(*fs,
+ cxt->optstr_pattern))) {
+ if (ignored)
+ *ignored = 1;
+ DBG(CXT, ul_debugobj(cxt, "next-mount: not-match "
+ "[fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]",
+ mnt_fs_get_fstype(*fs),
+ cxt->fstype_pattern,
+ mnt_fs_get_options(*fs),
+ cxt->optstr_pattern));
+ return 0;
+ }
+
+ /* ignore already mounted filesystems */
+ rc = mnt_context_is_fs_mounted(cxt, *fs, &mounted);
+ if (rc) {
+ if (mnt_table_is_empty(cxt->mtab)) {
+ DBG(CXT, ul_debugobj(cxt, "next-mount: no mount table [rc=%d], ignore", rc));
+ rc = 0;
+ if (ignored)
+ *ignored = 1;
+ }
+ return rc;
+ }
+ if (mounted) {
+ if (ignored)
+ *ignored = 2;
+ return 0;
+ }
+
+ /* Save mount options, etc. -- this is effective for the first
+ * mnt_context_next_mount() call only. Make sure that cxt has not set
+ * source, target or fstype.
+ */
+ if (!mnt_context_has_template(cxt)) {
+ mnt_context_set_source(cxt, NULL);
+ mnt_context_set_target(cxt, NULL);
+ mnt_context_set_fstype(cxt, NULL);
+ mnt_context_save_template(cxt);
+ }
+
+ /* reset context, but protect mtab */
+ mtab = cxt->mtab;
+ cxt->mtab = NULL;
+ mnt_reset_context(cxt);
+ cxt->mtab = mtab;
+
+ if (mnt_context_is_fork(cxt)) {
+ rc = mnt_fork_context(cxt);
+ if (rc)
+ return rc; /* fork error */
+
+ if (mnt_context_is_parent(cxt)) {
+ return 0; /* parent */
+ }
+ }
+
+ /*
+ * child or non-forked
+ */
+
+ /* copy stuff from fstab to context */
+ rc = mnt_context_apply_fs(cxt, *fs);
+ if (!rc) {
+ /*
+ * "-t <pattern>" is used to filter out fstab entries, but for ordinary
+ * mount operation -t means "-t <type>". We have to zeroize the pattern
+ * to avoid misinterpretation.
+ */
+ char *pattern = cxt->fstype_pattern;
+ cxt->fstype_pattern = NULL;
+
+ rc = mnt_context_mount(cxt);
+
+ cxt->fstype_pattern = pattern;
+
+ if (mntrc)
+ *mntrc = rc;
+ }
+
+ if (mnt_context_is_child(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "next-mount: child exit [rc=%d]", rc));
+ DBG_FLUSH;
+ _exit(rc);
+ }
+ return 0;
+}
+
+
+/**
+ * mnt_context_next_remount:
+ * @cxt: context
+ * @itr: iterator
+ * @fs: returns the current filesystem
+ * @mntrc: returns the return code from mnt_context_mount()
+ * @ignored: returns 1 for non-matching
+ *
+ * This function tries to remount the next mounted filesystem (as returned by
+ * mnt_context_get_mtab()).
+ *
+ * You can filter out filesystems by:
+ * mnt_context_set_options_pattern() to simulate mount -a -O pattern
+ * mnt_context_set_fstype_pattern() to simulate mount -a -t pattern
+ *
+ * If the filesystem does not match defined criteria, then the
+ * mnt_context_next_remount() function returns zero, but the @ignored is
+ * non-zero.
+ *
+ * IMPORTANT -- the mount operation is performed in the current context.
+ * The context is reset before the next mount (see mnt_reset_context()).
+ * The context setting related to the filesystem (e.g. mount options,
+ * etc.) are protected.
+
+ * If mount(2) syscall or mount.type helper failed, then the
+ * mnt_context_next_mount() function returns zero, but the @mntrc is non-zero.
+ * Use also mnt_context_get_status() to check if the filesystem was
+ * successfully mounted.
+ *
+ * See mnt_context_mount() for more details about errors and warnings.
+ *
+ * Returns: 0 on success,
+ * <0 in case of error (!= mount(2) errors)
+ * 1 at the end of the list.
+ *
+ * Since: 2.34
+ */
+int mnt_context_next_remount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc,
+ int *ignored)
+{
+ struct libmnt_table *mtab;
+ const char *tgt;
+ int rc;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ if (rc)
+ return rc;
+
+ rc = mnt_table_next_fs(mtab, itr, fs);
+ if (rc != 0)
+ return rc; /* more filesystems (or error) */
+
+ tgt = mnt_fs_get_target(*fs);
+
+ DBG(CXT, ul_debugobj(cxt, "next-remount: trying %s", tgt));
+
+ /* ignore filesystems which don't match options patterns */
+ if ((cxt->fstype_pattern && !mnt_fs_match_fstype(*fs,
+ cxt->fstype_pattern)) ||
+
+ /* ignore filesystems which don't match type patterns */
+ (cxt->optstr_pattern && !mnt_fs_match_options(*fs,
+ cxt->optstr_pattern))) {
+ if (ignored)
+ *ignored = 1;
+ DBG(CXT, ul_debugobj(cxt, "next-remount: not-match "
+ "[fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]",
+ mnt_fs_get_fstype(*fs),
+ cxt->fstype_pattern,
+ mnt_fs_get_options(*fs),
+ cxt->optstr_pattern));
+ return 0;
+ }
+
+ /* Save mount options, etc. -- this is effective for the first
+ * mnt_context_next_remount() call only. Make sure that cxt has not set
+ * source, target or fstype.
+ */
+ if (!mnt_context_has_template(cxt)) {
+ mnt_context_set_source(cxt, NULL);
+ mnt_context_set_target(cxt, NULL);
+ mnt_context_set_fstype(cxt, NULL);
+ mnt_context_save_template(cxt);
+ }
+
+ /* restore original, but protect mtab */
+ cxt->mtab = NULL;
+ mnt_reset_context(cxt);
+ cxt->mtab = mtab;
+
+ rc = mnt_context_set_target(cxt, tgt);
+ if (!rc) {
+ /*
+ * "-t <pattern>" is used to filter out fstab entries, but for ordinary
+ * mount operation -t means "-t <type>". We have to zeroize the pattern
+ * to avoid misinterpretation.
+ */
+ char *pattern = cxt->fstype_pattern;
+ cxt->fstype_pattern = NULL;
+
+ rc = mnt_context_mount(cxt);
+
+ cxt->fstype_pattern = pattern;
+
+ if (mntrc)
+ *mntrc = rc;
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/*
+ * Returns 1 if @dir parent is shared
+ */
+static int is_shared_tree(struct libmnt_context *cxt, const char *dir)
+{
+ struct libmnt_table *tb = NULL;
+ struct libmnt_fs *fs;
+ unsigned long mflags = 0;
+ char *mnt = NULL, *p;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ if (!dir)
+ return 0;
+ if (mnt_context_get_mtab(cxt, &tb) || !tb)
+ goto done;
+
+ mnt = strdup(dir);
+ if (!mnt)
+ goto done;
+ p = strrchr(mnt, '/');
+ if (!p)
+ goto done;
+ if (p > mnt)
+ *p = '\0';
+ fs = mnt_table_find_mountpoint(tb, mnt, MNT_ITER_BACKWARD);
+
+ rc = fs && mnt_fs_is_kernel(fs)
+ && mnt_fs_get_propagation(fs, &mflags) == 0
+ && (mflags & MS_SHARED);
+done:
+ free(mnt);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+int mnt_context_get_mount_excode(
+ struct libmnt_context *cxt,
+ int rc,
+ char *buf,
+ size_t bufsz)
+{
+ int syserr;
+ struct stat st;
+ unsigned long uflags = 0, mflags = 0;
+
+ int restricted = mnt_context_is_restricted(cxt);
+ const char *tgt = mnt_context_get_target(cxt);
+ const char *src = mnt_context_get_source(cxt);
+
+ if (mnt_context_helper_executed(cxt)) {
+ /*
+ * /sbin/mount.<type> called, return status
+ */
+ if (rc == -MNT_ERR_APPLYFLAGS && buf)
+ snprintf(buf, bufsz, _("WARNING: failed to apply propagation flags"));
+
+ return mnt_context_get_helper_status(cxt);
+ }
+
+ if (rc == 0 && mnt_context_get_status(cxt) == 1) {
+ /*
+ * Libmount success && syscall success.
+ */
+ if (buf && mnt_context_forced_rdonly(cxt))
+ snprintf(buf, bufsz, _("WARNING: source write-protected, mounted read-only"));
+ return MNT_EX_SUCCESS;
+ }
+
+ mnt_context_get_mflags(cxt, &mflags); /* mount(2) flags */
+ mnt_context_get_user_mflags(cxt, &uflags); /* userspace flags */
+
+ if (!mnt_context_syscall_called(cxt)) {
+ /*
+ * libmount errors (extra library checks)
+ */
+ switch (rc) {
+ case -EPERM:
+ if (buf)
+ snprintf(buf, bufsz, _("operation permitted for root only"));
+ return MNT_EX_USAGE;
+ case -EBUSY:
+ if (buf)
+ snprintf(buf, bufsz, _("%s is already mounted"), src);
+ return MNT_EX_USAGE;
+ case -MNT_ERR_NOFSTAB:
+ if (!buf)
+ return MNT_EX_USAGE;
+ if (mnt_context_is_swapmatch(cxt))
+ snprintf(buf, bufsz, _("can't find in %s"),
+ mnt_get_fstab_path());
+ else if (tgt)
+ snprintf(buf, bufsz, _("can't find mount point in %s"),
+ mnt_get_fstab_path());
+ else if (src)
+ snprintf(buf, bufsz, _("can't find mount source %s in %s"),
+ src, mnt_get_fstab_path());
+ return MNT_EX_USAGE;
+ case -MNT_ERR_AMBIFS:
+ if (buf)
+ snprintf(buf, bufsz, _("more filesystems detected on %s; use -t <type> or wipefs(8)"), src);
+ return MNT_EX_USAGE;
+ case -MNT_ERR_NOFSTYPE:
+ if (buf)
+ snprintf(buf, bufsz, restricted ?
+ _("failed to determine filesystem type") :
+ _("no filesystem type specified"));
+ return MNT_EX_USAGE;
+ case -MNT_ERR_NOSOURCE:
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (buf) {
+ if (src)
+ snprintf(buf, bufsz, _("can't find %s"), src);
+ else
+ snprintf(buf, bufsz, _("no mount source specified"));
+ }
+ return MNT_EX_USAGE;
+ case -MNT_ERR_MOUNTOPT:
+ if (buf) {
+ const char *opts = mnt_context_get_options(cxt);
+
+ if (!opts)
+ opts = "";
+ if (opts)
+ snprintf(buf, bufsz, errno ?
+ _("failed to parse mount options '%s': %m") :
+ _("failed to parse mount options '%s'"), opts);
+ else
+ snprintf(buf, bufsz, errno ?
+ _("failed to parse mount options: %m") :
+ _("failed to parse mount options"));
+ }
+ return MNT_EX_USAGE;
+ case -MNT_ERR_LOOPDEV:
+ if (buf)
+ snprintf(buf, bufsz, _("failed to setup loop device for %s"), src);
+ return MNT_EX_FAIL;
+ case -MNT_ERR_LOOPOVERLAP:
+ if (buf)
+ snprintf(buf, bufsz, _("overlapping loop device exists for %s"), src);
+ return MNT_EX_FAIL;
+ case -MNT_ERR_LOCK:
+ if (buf)
+ snprintf(buf, bufsz, _("locking failed"));
+ return MNT_EX_FILEIO;
+ case -MNT_ERR_NAMESPACE:
+ if (buf)
+ snprintf(buf, bufsz, _("failed to switch namespace"));
+ return MNT_EX_SYSERR;
+ default:
+ return mnt_context_get_generic_excode(rc, buf, bufsz, _("mount failed: %m"));
+ }
+
+ } else if (mnt_context_get_syscall_errno(cxt) == 0) {
+ /*
+ * mount(2) syscall success, but something else failed
+ * (probably error in mtab processing).
+ */
+ if (rc == -MNT_ERR_LOCK) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was mounted, but failed to update userspace mount table"));
+ return MNT_EX_FILEIO;
+ }
+
+ if (rc == -MNT_ERR_NAMESPACE) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was mounted, but failed to switch namespace back"));
+ return MNT_EX_SYSERR;
+
+ }
+
+ if (rc < 0)
+ return mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("filesystem was mounted, but any subsequent operation failed: %m"));
+
+ return MNT_EX_SOFTWARE; /* internal error */
+
+ }
+
+ /*
+ * mount(2) errors
+ */
+ syserr = mnt_context_get_syscall_errno(cxt);
+
+
+ switch(syserr) {
+ case EPERM:
+ if (!buf)
+ break;
+ if (geteuid() == 0) {
+ if (mnt_stat_mountpoint(tgt, &st) || !S_ISDIR(st.st_mode))
+ snprintf(buf, bufsz, _("mount point is not a directory"));
+ else
+ snprintf(buf, bufsz, _("permission denied"));
+ } else
+ snprintf(buf, bufsz, _("must be superuser to use mount"));
+ break;
+
+ case EBUSY:
+ if (!buf)
+ break;
+ if (mflags & MS_REMOUNT) {
+ snprintf(buf, bufsz, _("mount point is busy"));
+ break;
+ }
+ if (src) {
+ struct libmnt_fs *fs = get_already_mounted_source(cxt);
+
+ if (fs && mnt_fs_get_target(fs))
+ snprintf(buf, bufsz, _("%s already mounted on %s"),
+ src, mnt_fs_get_target(fs));
+ }
+ if (!*buf)
+ snprintf(buf, bufsz, _("%s already mounted or mount point busy"), src);
+ break;
+ case ENOENT:
+ if (tgt && mnt_lstat_mountpoint(tgt, &st)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point does not exist"));
+ } else if (tgt && mnt_stat_mountpoint(tgt, &st)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point is a symbolic link to nowhere"));
+ } else if (src && stat(src, &st)) {
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (buf)
+ snprintf(buf, bufsz, _("special device %s does not exist"), src);
+ } else if (buf) {
+ errno = syserr;
+ snprintf(buf, bufsz, _("mount(2) system call failed: %m"));
+ }
+ break;
+
+ case ENOTDIR:
+ if (mnt_stat_mountpoint(tgt, &st) || ! S_ISDIR(st.st_mode)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point is not a directory"));
+ } else if (src && stat(src, &st) && errno == ENOTDIR) {
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (buf)
+ snprintf(buf, bufsz, _("special device %s does not exist "
+ "(a path prefix is not a directory)"), src);
+ } else if (buf) {
+ errno = syserr;
+ snprintf(buf, bufsz, _("mount(2) system call failed: %m"));
+ }
+ break;
+
+ case EINVAL:
+ if (!buf)
+ break;
+ if (mflags & MS_REMOUNT)
+ snprintf(buf, bufsz, _("mount point not mounted or bad option"));
+ else if (rc == -MNT_ERR_APPLYFLAGS)
+ snprintf(buf, bufsz, _("not mount point or bad option"));
+ else if ((mflags & MS_MOVE) && is_shared_tree(cxt, src))
+ snprintf(buf, bufsz,
+ _("bad option; moving a mount "
+ "residing under a shared mount is unsupported"));
+ else if (mnt_fs_is_netfs(mnt_context_get_fs(cxt)))
+ snprintf(buf, bufsz,
+ _("bad option; for several filesystems (e.g. nfs, cifs) "
+ "you might need a /sbin/mount.<type> helper program"));
+ else
+ snprintf(buf, bufsz,
+ _("wrong fs type, bad option, bad superblock on %s, "
+ "missing codepage or helper program, or other error"),
+ src);
+ break;
+
+ case EMFILE:
+ if (buf)
+ snprintf(buf, bufsz, _("mount table full"));
+ break;
+
+ case EIO:
+ if (buf)
+ snprintf(buf, bufsz, _("can't read superblock on %s"), src);
+ break;
+
+ case ENODEV:
+ if (!buf)
+ break;
+ if (mnt_context_get_fstype(cxt))
+ snprintf(buf, bufsz, _("unknown filesystem type '%s'"),
+ mnt_context_get_fstype(cxt));
+ else
+ snprintf(buf, bufsz, _("unknown filesystem type"));
+ break;
+
+ case ENOTBLK:
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (!buf)
+ break;
+ if (src && stat(src, &st))
+ snprintf(buf, bufsz, _("%s is not a block device, and stat(2) fails?"), src);
+ else if (src && S_ISBLK(st.st_mode))
+ snprintf(buf, bufsz,
+ _("the kernel does not recognize %s as a block device; "
+ "maybe \"modprobe driver\" is necessary"), src);
+ else if (src && S_ISREG(st.st_mode))
+ snprintf(buf, bufsz, _("%s is not a block device; try \"-o loop\""), src);
+ else
+ snprintf(buf, bufsz, _("%s is not a block device"), src);
+ break;
+
+ case ENXIO:
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (buf)
+ snprintf(buf, bufsz, _("%s is not a valid block device"), src);
+ break;
+
+ case EACCES:
+ case EROFS:
+ if (!buf)
+ break;
+ if (mflags & MS_RDONLY)
+ snprintf(buf, bufsz, _("cannot mount %s read-only"), src);
+ else if (mnt_context_is_rwonly_mount(cxt))
+ snprintf(buf, bufsz, _("%s is write-protected but explicit read-write mode requested"), src);
+ else if (mflags & MS_REMOUNT)
+ snprintf(buf, bufsz, _("cannot remount %s read-write, is write-protected"), src);
+ else if (mflags & MS_BIND)
+ snprintf(buf, bufsz, _("bind %s failed"), src);
+ else {
+ errno = syserr;
+ snprintf(buf, bufsz, _("mount(2) system call failed: %m"));
+ }
+ break;
+
+ case ENOMEDIUM:
+ if (uflags & MNT_MS_NOFAIL)
+ return MNT_EX_SUCCESS;
+ if (buf)
+ snprintf(buf, bufsz, _("no medium found on %s"), src);
+ break;
+
+ case EBADMSG:
+ /* Bad CRC for classic filesystems (e.g. extN or XFS) */
+ if (buf && src && stat(src, &st) == 0
+ && (S_ISBLK(st.st_mode) || S_ISREG(st.st_mode))) {
+ snprintf(buf, bufsz, _("cannot mount; probably corrupted filesystem on %s"), src);
+ break;
+ }
+ /* fallthrough */
+
+ default:
+ if (buf) {
+ errno = syserr;
+ snprintf(buf, bufsz, _("mount(2) system call failed: %m"));
+ }
+ break;
+ }
+
+ return MNT_EX_FAIL;
+}
+
diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c
new file mode 100644
index 0000000..9c6d190
--- /dev/null
+++ b/libmount/src/context_umount.c
@@ -0,0 +1,1328 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: context-umount
+ * @title: Umount context
+ * @short_description: high-level API to umount operation.
+ */
+
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include "pathnames.h"
+#include "loopdev.h"
+#include "strutils.h"
+#include "mountP.h"
+
+/*
+ * umount2 flags
+ */
+#ifndef MNT_FORCE
+# define MNT_FORCE 0x00000001 /* Attempt to forcibly umount */
+#endif
+
+#ifndef MNT_DETACH
+# define MNT_DETACH 0x00000002 /* Just detach from the tree */
+#endif
+
+#ifndef UMOUNT_NOFOLLOW
+# define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+#endif
+
+#ifndef UMOUNT_UNUSED
+# define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+#endif
+
+/* search in mountinfo/mtab */
+static int __mtab_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+ struct libmnt_table *mtab = NULL;
+ struct libmnt_fs *fs;
+ char *loopdev = NULL;
+
+ assert(cxt);
+ assert(tgt);
+ assert(pfs);
+
+ *pfs = NULL;
+ DBG(CXT, ul_debugobj(cxt, " search %s in mountinfo", tgt));
+
+ /*
+ * The mount table may be huge, and on systems with utab we have to
+ * merge userspace mount options into /proc/self/mountinfo. This all is
+ * expensive. The tab filter can be used to filter out entries, then a mount
+ * table and utab are very tiny files.
+ *
+ * The filter uses mnt_fs_streq_{target,srcpath} function where all
+ * paths should be absolute and canonicalized. This is done within
+ * mnt_context_get_mtab_for_target() where LABEL, UUID or symlinks are
+ * canonicalized. If --no-canonicalize is enabled than the target path
+ * is expected already canonical.
+ *
+ * Anyway it's better to read huge mount table than canonicalize target
+ * paths. It means we use the filter only if --no-canonicalize enabled.
+ *
+ * It also means that we have to read mount table from kernel
+ * (non-writable mtab).
+ */
+ if (mnt_context_is_nocanonicalize(cxt) &&
+ !mnt_context_mtab_writable(cxt) && *tgt == '/')
+ rc = mnt_context_get_mtab_for_target(cxt, &mtab, tgt);
+ else
+ rc = mnt_context_get_mtab(cxt, &mtab);
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "umount: failed to read mtab"));
+ return rc;
+ }
+
+ if (mnt_table_get_nents(mtab) == 0) {
+ DBG(CXT, ul_debugobj(cxt, "umount: mtab empty"));
+ return 1;
+ }
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+try_loopdev:
+ fs = mnt_table_find_target(mtab, tgt, MNT_ITER_BACKWARD);
+ if (!fs && mnt_context_is_swapmatch(cxt)) {
+ /*
+ * Maybe the option is source rather than target (sometimes
+ * people use e.g. "umount /dev/sda1")
+ */
+ fs = mnt_table_find_source(mtab, tgt, MNT_ITER_BACKWARD);
+
+ if (fs) {
+ struct libmnt_fs *fs1 = mnt_table_find_target(mtab,
+ mnt_fs_get_target(fs),
+ MNT_ITER_BACKWARD);
+ if (!fs1) {
+ DBG(CXT, ul_debugobj(cxt, "mtab is broken?!?!"));
+ rc = -EINVAL;
+ goto err;
+ }
+ if (fs != fs1) {
+ /* Something was stacked over `file' on the
+ * same mount point. */
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: %s: %s is mounted "
+ "over it on the same point",
+ tgt, mnt_fs_get_source(fs1)));
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+ }
+
+ if (!fs && !loopdev && mnt_context_is_swapmatch(cxt)) {
+ /*
+ * Maybe the option is /path/file.img, try to convert to /dev/loopN
+ */
+ struct stat st;
+
+ if (mnt_stat_mountpoint(tgt, &st) == 0 && S_ISREG(st.st_mode)) {
+ int count;
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+ const char *bf = cache ? mnt_resolve_path(tgt, cache) : tgt;
+
+ count = loopdev_count_by_backing_file(bf, &loopdev);
+ if (count == 1) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: %s --> %s (retry)", tgt, loopdev));
+ tgt = loopdev;
+ goto try_loopdev;
+
+ } else if (count > 1)
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: warning: %s is associated "
+ "with more than one loopdev", tgt));
+ }
+ }
+
+ *pfs = fs;
+ free(loopdev);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ DBG(CXT, ul_debugobj(cxt, "umount fs: %s", fs ? mnt_fs_get_target(fs) :
+ "<not found>"));
+ return fs ? 0 : 1;
+err:
+ free(loopdev);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+/**
+ * mnt_context_find_umount_fs:
+ * @cxt: mount context
+ * @tgt: mountpoint, device, ...
+ * @pfs: returns point to filesystem
+ *
+ * Returns: 0 on success, <0 on error, 1 if target filesystem not found
+ */
+int mnt_context_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs)
+{
+ if (pfs)
+ *pfs = NULL;
+
+ if (!cxt || !tgt || !pfs)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "umount: lookup FS for '%s'", tgt));
+
+ if (!*tgt)
+ return 1; /* empty string is not an error */
+
+ /* In future this function should be extended to support for example
+ * fsinfo() (or another cheap way kernel will support), for now the
+ * default is expensive mountinfo/mtab.
+ */
+ return __mtab_find_umount_fs(cxt, tgt, pfs);
+}
+
+/* Check if there is something important in the utab file. The parsed utab is
+ * stored in context->utab and deallocated by mnt_free_context().
+ *
+ * This function exists to avoid (if possible) /proc/self/mountinfo usage, so
+ * don't use things like mnt_resolve_target(), mnt_context_get_mtab() etc here.
+ * See lookup_umount_fs() for more details.
+ */
+static int has_utab_entry(struct libmnt_context *cxt, const char *target)
+{
+ struct libmnt_cache *cache = NULL;
+ struct libmnt_fs *fs;
+ struct libmnt_iter itr;
+ char *cn = NULL;
+ int rc = 0;
+
+ assert(cxt);
+
+ if (!cxt->utab) {
+ const char *path = mnt_get_utab_path();
+
+ if (!path || is_file_empty(path))
+ return 0;
+ cxt->utab = mnt_new_table();
+ if (!cxt->utab)
+ return 0;
+ cxt->utab->fmt = MNT_FMT_UTAB;
+ if (mnt_table_parse_file(cxt->utab, path))
+ return 0;
+ }
+
+ /* paths in utab are canonicalized */
+ cache = mnt_context_get_cache(cxt);
+ cn = mnt_resolve_path(target, cache);
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+
+ while (mnt_table_next_fs(cxt->utab, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, cn)) {
+ rc = 1;
+ break;
+ }
+ }
+
+ if (!cache)
+ free(cn);
+ return rc;
+}
+
+/* returns: 1 not found; <0 on error; 1 success */
+static int lookup_umount_fs_by_statfs(struct libmnt_context *cxt, const char *tgt)
+{
+ struct stat st;
+ const char *type;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, " lookup by statfs"));
+
+ /*
+ * Let's try to avoid mountinfo usage at all to minimize performance
+ * degradation. Don't forget that kernel has to compose *whole*
+ * mountinfo about all mountpoints although we look for only one entry.
+ *
+ * All we need is fstype and to check if there is no userspace mount
+ * options for the target (e.g. helper=udisks to call /sbin/umount.udisks).
+ *
+ * So, let's use statfs() if possible (it's bad idea for --lazy/--force
+ * umounts as target is probably unreachable NFS, also for --detach-loop
+ * as this additionally needs to know the name of the loop device).
+ */
+ if (mnt_context_is_restricted(cxt)
+ || *tgt != '/'
+ || (cxt->flags & MNT_FL_HELPER)
+ || mnt_context_mtab_writable(cxt)
+ || mnt_context_is_force(cxt)
+ || mnt_context_is_lazy(cxt)
+ || mnt_context_is_nocanonicalize(cxt)
+ || mnt_context_is_loopdel(cxt)
+ || mnt_stat_mountpoint(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
+ || has_utab_entry(cxt, tgt))
+ return 1; /* not found */
+
+ type = mnt_fs_get_fstype(cxt->fs);
+ if (!type) {
+ struct statfs vfs;
+ int fd;
+
+ DBG(CXT, ul_debugobj(cxt, " trying fstatfs()"));
+
+ /* O_PATH avoids triggering automount points. */
+ fd = open(tgt, O_PATH);
+ if (fd >= 0) {
+ if (fstatfs(fd, &vfs) == 0)
+ type = mnt_statfs_get_fstype(&vfs);
+ close(fd);
+ }
+ if (type) {
+ int rc = mnt_fs_set_fstype(cxt->fs, type);
+ if (rc)
+ return rc;
+ }
+ }
+ if (type) {
+ DBG(CXT, ul_debugobj(cxt, " umount: disabling mtab"));
+ mnt_context_disable_mtab(cxt, TRUE);
+
+ DBG(CXT, ul_debugobj(cxt,
+ " mountinfo unnecessary [type=%s]", type));
+ return 0;
+ }
+
+ return 1; /* not found */
+}
+
+/* returns: 1 not found; <0 on error; 1 success */
+static int lookup_umount_fs_by_mountinfo(struct libmnt_context *cxt, const char *tgt)
+{
+ struct libmnt_fs *fs = NULL;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, " lookup by mountinfo"));
+
+ /* search */
+ rc = __mtab_find_umount_fs(cxt, tgt, &fs);
+ if (rc != 0)
+ return rc;
+
+ /* apply result */
+ if (fs != cxt->fs) {
+ mnt_fs_set_source(cxt->fs, NULL);
+ mnt_fs_set_target(cxt->fs, NULL);
+
+ if (!mnt_copy_fs(cxt->fs, fs)) {
+ DBG(CXT, ul_debugobj(cxt, " failed to copy FS"));
+ return -errno;
+ }
+ DBG(CXT, ul_debugobj(cxt, " mtab applied"));
+ }
+
+ cxt->flags |= MNT_FL_TAB_APPLIED;
+ return 0;
+}
+
+/* This finction search for FS according to cxt->fs->target,
+ * apply result to cxt->fs and it's umount replacement to
+ * mnt_context_apply_fstab(), use mnt_context_tab_applied()
+ * to check result.
+ *
+ * The goal is to minimize situations when we need to parse
+ * /proc/self/mountinfo.
+ */
+static int lookup_umount_fs(struct libmnt_context *cxt)
+{
+ const char *tgt;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, "umount: lookup FS"));
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ if (!tgt) {
+ DBG(CXT, ul_debugobj(cxt, " undefined target"));
+ return -EINVAL;
+ }
+
+ /* try get fs type by statfs() */
+ rc = lookup_umount_fs_by_statfs(cxt, tgt);
+ if (rc <= 0)
+ return rc;
+
+ /* get complete fs from fs entry from mountinfo */
+ rc = lookup_umount_fs_by_mountinfo(cxt, tgt);
+ if (rc <= 0)
+ return rc;
+
+ DBG(CXT, ul_debugobj(cxt, " cannot find '%s'", tgt));
+ return 0; /* this is correct! */
+}
+
+/* check if @devname is loopdev and if the device is associated
+ * with a source from @fstab_fs
+ */
+static int is_associated_fs(const char *devname, struct libmnt_fs *fs)
+{
+ uintmax_t offset = 0;
+ const char *src, *optstr;
+ char *val;
+ size_t valsz;
+ int flags = 0;
+
+ /* check if it begins with /dev/loop */
+ if (strncmp(devname, _PATH_DEV_LOOP, sizeof(_PATH_DEV_LOOP) - 1) != 0)
+ return 0;
+
+ src = mnt_fs_get_srcpath(fs);
+ if (!src)
+ return 0;
+
+ /* check for the offset option in @fs */
+ optstr = mnt_fs_get_user_options(fs);
+
+ if (optstr &&
+ mnt_optstr_get_option(optstr, "offset", &val, &valsz) == 0) {
+ flags |= LOOPDEV_FL_OFFSET;
+
+ if (mnt_parse_offset(val, valsz, &offset) != 0)
+ return 0;
+ }
+
+ return loopdev_is_used(devname, src, offset, 0, flags);
+}
+
+static int prepare_helper_from_options(struct libmnt_context *cxt,
+ const char *name)
+{
+ char *suffix = NULL;
+ const char *opts;
+ size_t valsz;
+ int rc;
+
+ if (mnt_context_is_nohelpers(cxt))
+ return 0;
+
+ opts = mnt_fs_get_user_options(cxt->fs);
+ if (!opts)
+ return 0;
+
+ if (mnt_optstr_get_option(opts, name, &suffix, &valsz))
+ return 0;
+
+ suffix = strndup(suffix, valsz);
+ if (!suffix)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "umount: umount.%s %s requested", suffix, name));
+
+ rc = mnt_context_prepare_helper(cxt, "umount", suffix);
+ free(suffix);
+
+ return rc;
+}
+
+static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
+{
+ struct libmnt_ns *ns_old;
+ const char *type = mnt_fs_get_fstype(cxt->fs);
+ const char *optstr;
+ uid_t uid, entry_uid;
+
+ *errsv = 0;
+
+ if (!type)
+ return 0;
+
+ if (strcmp(type, "fuse") != 0 &&
+ strcmp(type, "fuseblk") != 0 &&
+ strncmp(type, "fuse.", 5) != 0 &&
+ strncmp(type, "fuseblk.", 8) != 0)
+ return 0;
+
+ /* get user_id= from mount table */
+ optstr = mnt_fs_get_fs_options(cxt->fs);
+ if (!optstr)
+ return 0;
+ if (mnt_optstr_get_uid(optstr, "user_id", &entry_uid) != 0)
+ return 0;
+
+ /* get current user */
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old) {
+ *errsv = -MNT_ERR_NAMESPACE;
+ return 0;
+ }
+
+ uid = getuid();
+
+ if (!mnt_context_switch_ns(cxt, ns_old)) {
+ *errsv = -MNT_ERR_NAMESPACE;
+ return 0;
+ }
+
+ return uid == entry_uid;
+}
+
+/*
+ * Note that cxt->fs contains relevant mtab entry!
+ */
+static int evaluate_permissions(struct libmnt_context *cxt)
+{
+ struct libmnt_table *fstab;
+ unsigned long u_flags = 0;
+ const char *tgt, *src, *optstr;
+ int rc = 0, ok = 0;
+ struct libmnt_fs *fs;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!mnt_context_is_restricted(cxt))
+ return 0; /* superuser mount */
+
+ DBG(CXT, ul_debugobj(cxt, "umount: evaluating permissions"));
+
+ if (!mnt_context_tab_applied(cxt)) {
+ DBG(CXT, ul_debugobj(cxt,
+ "cannot find %s in mtab and you are not root",
+ mnt_fs_get_target(cxt->fs)));
+ goto eperm;
+ }
+
+ if (cxt->user_mountflags & MNT_MS_UHELPER) {
+ /* on uhelper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "uhelper");
+ if (rc)
+ return rc;
+ if (cxt->helper)
+ return 0; /* we'll call /sbin/umount.<uhelper> */
+ }
+
+ /*
+ * Check if this is a fuse mount for the current user,
+ * if so then unmounting is allowed
+ */
+ if (is_fuse_usermount(cxt, &rc)) {
+ DBG(CXT, ul_debugobj(cxt, "fuse user mount, umount is allowed"));
+ return 0;
+ }
+ if (rc)
+ return rc;
+
+ /*
+ * User mounts have to be in /etc/fstab
+ */
+ rc = mnt_context_get_fstab(cxt, &fstab);
+ if (rc)
+ return rc;
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ src = mnt_fs_get_source(cxt->fs);
+
+ if (mnt_fs_get_bindsrc(cxt->fs)) {
+ src = mnt_fs_get_bindsrc(cxt->fs);
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: using bind source: %s", src));
+ }
+
+ /* If fstab contains the two lines
+ * /dev/sda1 /mnt/zip auto user,noauto 0 0
+ * /dev/sda4 /mnt/zip auto user,noauto 0 0
+ * then "mount /dev/sda4" followed by "umount /mnt/zip" used to fail.
+ * So, we must not look for the file, but for the pair (dev,file) in fstab.
+ */
+ fs = mnt_table_find_pair(fstab, src, tgt, MNT_ITER_FORWARD);
+ if (!fs) {
+ /*
+ * It's possible that there is /path/file.img in fstab and
+ * /dev/loop0 in mtab -- then we have to check the relation
+ * between loopdev and the file.
+ */
+ fs = mnt_table_find_target(fstab, tgt, MNT_ITER_FORWARD);
+ if (fs) {
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+ const char *sp = mnt_fs_get_srcpath(cxt->fs); /* devname from mtab */
+ const char *dev = sp && cache ? mnt_resolve_path(sp, cache) : sp;
+
+ if (!dev || !is_associated_fs(dev, fs))
+ fs = NULL;
+ }
+ if (!fs) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount %s: mtab disagrees with fstab",
+ tgt));
+ goto eperm;
+ }
+ }
+
+ /*
+ * User mounting and unmounting is allowed only if fstab contains one
+ * of the options `user', `users' or `owner' or `group'.
+ *
+ * The option `users' allows arbitrary users to mount and unmount -
+ * this may be a security risk.
+ *
+ * The options `user', `owner' and `group' only allow unmounting by the
+ * user that mounted (visible in mtab).
+ */
+ optstr = mnt_fs_get_user_options(fs); /* FSTAB mount options! */
+ if (!optstr)
+ goto eperm;
+
+ if (mnt_optstr_get_flags(optstr, &u_flags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP)))
+ goto eperm;
+
+ if (u_flags & MNT_MS_USERS) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: promiscuous setting ('users') in fstab"));
+ return 0;
+ }
+ /*
+ * Check user=<username> setting from mtab if there is a user, owner or
+ * group option in /etc/fstab
+ */
+ if (u_flags & (MNT_MS_USER | MNT_MS_OWNER | MNT_MS_GROUP)) {
+
+ char *curr_user;
+ char *mtab_user = NULL;
+ size_t sz;
+ struct libmnt_ns *ns_old;
+
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: checking user=<username> from mtab"));
+
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ curr_user = mnt_get_username(getuid());
+
+ if (!mnt_context_switch_ns(cxt, ns_old)) {
+ free(curr_user);
+ return -MNT_ERR_NAMESPACE;
+ }
+ if (!curr_user) {
+ DBG(CXT, ul_debugobj(cxt, "umount %s: cannot "
+ "convert %d to username", tgt, getuid()));
+ goto eperm;
+ }
+
+ /* get options from mtab */
+ optstr = mnt_fs_get_user_options(cxt->fs);
+ if (optstr && !mnt_optstr_get_option(optstr,
+ "user", &mtab_user, &sz) && sz)
+ ok = !strncmp(curr_user, mtab_user, sz);
+
+ free(curr_user);
+ }
+
+ if (ok) {
+ DBG(CXT, ul_debugobj(cxt, "umount %s is allowed", tgt));
+ return 0;
+ }
+eperm:
+ DBG(CXT, ul_debugobj(cxt, "umount is not allowed for you"));
+ return -EPERM;
+}
+
+static int exec_helper(struct libmnt_context *cxt)
+{
+ char *namespace = NULL;
+ struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt);
+ int rc;
+ pid_t pid;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert(cxt->helper_exec_status == 1);
+
+ if (mnt_context_is_fake(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "fake mode: does not execute helper"));
+ cxt->helper_exec_status = rc = 0;
+ return rc;
+ }
+
+ if (ns_tgt->fd != -1
+ && asprintf(&namespace, "/proc/%i/fd/%i",
+ getpid(), ns_tgt->fd) == -1) {
+ return -ENOMEM;
+ }
+
+ DBG_FLUSH;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ {
+ const char *args[12], *type;
+ int i = 0;
+
+ if (drop_permissions() != 0)
+ _exit(EXIT_FAILURE);
+
+ if (!mnt_context_switch_origin_ns(cxt))
+ _exit(EXIT_FAILURE);
+
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ args[i++] = cxt->helper; /* 1 */
+ args[i++] = mnt_fs_get_target(cxt->fs); /* 2 */
+
+ if (mnt_context_is_nomtab(cxt))
+ args[i++] = "-n"; /* 3 */
+ if (mnt_context_is_lazy(cxt))
+ args[i++] = "-l"; /* 4 */
+ if (mnt_context_is_force(cxt))
+ args[i++] = "-f"; /* 5 */
+ if (mnt_context_is_verbose(cxt))
+ args[i++] = "-v"; /* 6 */
+ if (mnt_context_is_rdonly_umount(cxt))
+ args[i++] = "-r"; /* 7 */
+ if (type
+ && strchr(type, '.')
+ && !endswith(cxt->helper, type)) {
+ args[i++] = "-t"; /* 8 */
+ args[i++] = type; /* 9 */
+ }
+ if (namespace) {
+ args[i++] = "-N"; /* 10 */
+ args[i++] = namespace; /* 11 */
+ }
+
+ args[i] = NULL; /* 12 */
+ for (i = 0; args[i]; i++)
+ DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"",
+ i, args[i]));
+ DBG_FLUSH;
+ execv(cxt->helper, (char * const *) args);
+ _exit(EXIT_FAILURE);
+ }
+ default:
+ {
+ int st;
+
+ if (waitpid(pid, &st, 0) == (pid_t) -1) {
+ cxt->helper_status = -1;
+ rc = -errno;
+ } else {
+ cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1;
+ cxt->helper_exec_status = rc = 0;
+ }
+ DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]",
+ cxt->helper,
+ cxt->helper_status, rc,
+ rc ? " waitpid failed" : ""));
+ break;
+ }
+
+ case -1:
+ cxt->helper_exec_status = rc = -errno;
+ DBG(CXT, ul_debugobj(cxt, "fork() failed"));
+ break;
+ }
+
+ free(namespace);
+ return rc;
+}
+
+/*
+ * mnt_context_helper_setopt() backend.
+ *
+ * This function applies umount.type command line option (for example parsed
+ * by getopt() or getopt_long()) to @cxt. All unknown options are ignored and
+ * then 1 is returned.
+ *
+ * Returns: negative number on error, 1 if @c is unknown option, 0 on success.
+ */
+int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg)
+{
+ int rc = -EINVAL;
+
+ assert(cxt);
+ assert(cxt->action == MNT_ACT_UMOUNT);
+
+ switch(c) {
+ case 'n':
+ rc = mnt_context_disable_mtab(cxt, TRUE);
+ break;
+ case 'l':
+ rc = mnt_context_enable_lazy(cxt, TRUE);
+ break;
+ case 'f':
+ rc = mnt_context_enable_force(cxt, TRUE);
+ break;
+ case 'v':
+ rc = mnt_context_enable_verbose(cxt, TRUE);
+ break;
+ case 'r':
+ rc = mnt_context_enable_rdonly_umount(cxt, TRUE);
+ break;
+ case 't':
+ if (arg)
+ rc = mnt_context_set_fstype(cxt, arg);
+ break;
+ case 'N':
+ if (arg)
+ rc = mnt_context_set_target_ns(cxt, arg);
+ break;
+ default:
+ return 1;
+ }
+
+ return rc;
+}
+
+/* Check whether the kernel supports the UMOUNT_NOFOLLOW flag */
+static int umount_nofollow_support(void)
+{
+ int res = umount2("", UMOUNT_UNUSED);
+ if (res != -1 || errno != EINVAL)
+ return 0;
+
+ res = umount2("", UMOUNT_NOFOLLOW);
+ if (res != -1 || errno != ENOENT)
+ return 0;
+
+ return 1;
+}
+
+static int do_umount(struct libmnt_context *cxt)
+{
+ int rc = 0, flags = 0;
+ const char *src, *target;
+ char *tgtbuf = NULL;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert(cxt->syscall_status == 1);
+
+ if (cxt->helper)
+ return exec_helper(cxt);
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ target = mnt_fs_get_target(cxt->fs);
+
+ if (!target)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "do umount"));
+
+ if (mnt_context_is_restricted(cxt) && !mnt_context_is_fake(cxt)) {
+ /*
+ * extra paranoia for non-root users
+ * -- chdir to the parent of the mountpoint and use NOFOLLOW
+ * flag to avoid races and symlink attacks.
+ */
+ if (umount_nofollow_support())
+ flags |= UMOUNT_NOFOLLOW;
+
+ rc = mnt_chdir_to_parent(target, &tgtbuf);
+ if (rc)
+ return rc;
+ target = tgtbuf;
+ }
+
+ if (mnt_context_is_lazy(cxt))
+ flags |= MNT_DETACH;
+
+ if (mnt_context_is_force(cxt))
+ flags |= MNT_FORCE;
+
+ DBG(CXT, ul_debugobj(cxt, "umount(2) [target='%s', flags=0x%08x]%s",
+ target, flags,
+ mnt_context_is_fake(cxt) ? " (FAKE)" : ""));
+
+ if (mnt_context_is_fake(cxt))
+ rc = 0;
+ else {
+ rc = flags ? umount2(target, flags) : umount(target);
+ if (rc < 0)
+ cxt->syscall_status = -errno;
+ free(tgtbuf);
+ }
+
+ /*
+ * try remount read-only
+ */
+ if (rc < 0
+ && cxt->syscall_status == -EBUSY
+ && mnt_context_is_rdonly_umount(cxt)
+ && src) {
+
+ mnt_context_set_mflags(cxt, (cxt->mountflags |
+ MS_REMOUNT | MS_RDONLY));
+ mnt_context_enable_loopdel(cxt, FALSE);
+
+ DBG(CXT, ul_debugobj(cxt,
+ "umount(2) failed [errno=%d] -- trying to remount read-only",
+ -cxt->syscall_status));
+
+ rc = mount(src, mnt_fs_get_target(cxt->fs), NULL,
+ MS_REMOUNT | MS_RDONLY, NULL);
+ if (rc < 0) {
+ cxt->syscall_status = -errno;
+ DBG(CXT, ul_debugobj(cxt,
+ "read-only re-mount(2) failed [errno=%d]",
+ -cxt->syscall_status));
+
+ return -cxt->syscall_status;
+ }
+ cxt->syscall_status = 0;
+ DBG(CXT, ul_debugobj(cxt, "read-only re-mount(2) success"));
+ return 0;
+ }
+
+ if (rc < 0) {
+ DBG(CXT, ul_debugobj(cxt, "umount(2) failed [errno=%d]",
+ -cxt->syscall_status));
+ return -cxt->syscall_status;
+ }
+
+ cxt->syscall_status = 0;
+ DBG(CXT, ul_debugobj(cxt, "umount(2) success"));
+ return 0;
+}
+
+/**
+ * mnt_context_prepare_umount:
+ * @cxt: mount context
+ *
+ * Prepare context for umounting, unnecessary for mnt_context_umount().
+ *
+ * Returns: 0 on success, and negative number in case of error.
+ */
+int mnt_context_prepare_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ if (!cxt || !cxt->fs || mnt_fs_is_swaparea(cxt->fs))
+ return -EINVAL;
+ if (!mnt_context_get_source(cxt) && !mnt_context_get_target(cxt))
+ return -EINVAL;
+ if (cxt->flags & MNT_FL_PREPARED)
+ return 0;
+
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ free(cxt->helper); /* be paranoid */
+ cxt->helper = NULL;
+ cxt->action = MNT_ACT_UMOUNT;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = lookup_umount_fs(cxt);
+ if (!rc)
+ rc = mnt_context_merge_mflags(cxt);
+ if (!rc)
+ rc = evaluate_permissions(cxt);
+
+ if (!rc && !cxt->helper) {
+
+ if (cxt->user_mountflags & MNT_MS_HELPER)
+ /* on helper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "helper");
+
+ if (!rc && !cxt->helper)
+ /* on fstype based helper */
+ rc = mnt_context_prepare_helper(cxt, "umount", NULL);
+ }
+
+ if (!rc && (cxt->user_mountflags & MNT_MS_LOOP))
+ /* loop option explicitly specified in mtab, detach this loop */
+ mnt_context_enable_loopdel(cxt, TRUE);
+
+ if (!rc && mnt_context_is_loopdel(cxt) && cxt->fs) {
+ const char *src = mnt_fs_get_srcpath(cxt->fs);
+
+ if (src && (!is_loopdev(src) || loopdev_is_autoclear(src)))
+ mnt_context_enable_loopdel(cxt, FALSE);
+ }
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "umount: preparing failed"));
+ return rc;
+ }
+ cxt->flags |= MNT_FL_PREPARED;
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_do_umount:
+ * @cxt: mount context
+ *
+ * Umount filesystem by umount(2) or fork()+exec(/sbin/umount.type).
+ * Unnecessary for mnt_context_umount().
+ *
+ * See also mnt_context_disable_helpers().
+ *
+ * WARNING: non-zero return code does not mean that umount(2) syscall or
+ * umount.type helper wasn't successfully called.
+ *
+ * Check mnt_context_get_status() after error!
+*
+ * Returns: 0 on success;
+ * >0 in case of umount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_do_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->action == MNT_ACT_UMOUNT));
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = do_umount(cxt);
+ if (rc)
+ goto end;
+
+ if (mnt_context_get_status(cxt) && !mnt_context_is_fake(cxt)) {
+ /*
+ * Umounted, do some post-umount operations
+ * - remove loopdev
+ * - refresh in-memory mtab stuff if remount rather than
+ * umount has been performed
+ */
+ if (mnt_context_is_loopdel(cxt)
+ && !(cxt->mountflags & MS_REMOUNT))
+ rc = mnt_context_delete_loopdev(cxt);
+
+ if (!mnt_context_is_nomtab(cxt)
+ && mnt_context_get_status(cxt)
+ && !cxt->helper
+ && mnt_context_is_rdonly_umount(cxt)
+ && (cxt->mountflags & MS_REMOUNT)) {
+
+ /* use "remount" instead of "umount" in /etc/mtab */
+ if (!rc && cxt->update && mnt_context_mtab_writable(cxt))
+ rc = mnt_update_set_fs(cxt->update,
+ cxt->mountflags, NULL, cxt->fs);
+ }
+ }
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_finalize_umount:
+ * @cxt: context
+ *
+ * Mtab update, etc. Unnecessary for mnt_context_umount(), but should be called
+ * after mnt_context_do_umount(). See also mnt_context_set_syscall_status().
+ *
+ * Returns: negative number on error, 0 on success.
+ */
+int mnt_context_finalize_umount(struct libmnt_context *cxt)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+ return rc;
+}
+
+
+/**
+ * mnt_context_umount:
+ * @cxt: umount context
+ *
+ * High-level, umounts filesystem by umount(2) or fork()+exec(/sbin/umount.type).
+ *
+ * This is similar to:
+ *
+ * mnt_context_prepare_umount(cxt);
+ * mnt_context_do_umount(cxt);
+ * mnt_context_finalize_umount(cxt);
+ *
+ * See also mnt_context_disable_helpers().
+ *
+ * WARNING: non-zero return code does not mean that umount(2) syscall or
+ * umount.type helper wasn't successfully called.
+ *
+ * Check mnt_context_get_status() after error!
+ *
+ * Returns: 0 on success;
+ * >0 in case of umount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ DBG(CXT, ul_debugobj(cxt, "umount: %s", mnt_context_get_target(cxt)));
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = mnt_context_prepare_umount(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_do_umount(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+
+/**
+ * mnt_context_next_umount:
+ * @cxt: context
+ * @itr: iterator
+ * @fs: returns the current filesystem
+ * @mntrc: returns the return code from mnt_context_umount()
+ * @ignored: returns 1 for not matching
+ *
+ * This function tries to umount the next filesystem from mtab (as returned by
+ * mnt_context_get_mtab()).
+ *
+ * You can filter out filesystems by:
+ * mnt_context_set_options_pattern() to simulate umount -a -O pattern
+ * mnt_context_set_fstype_pattern() to simulate umount -a -t pattern
+ *
+ * If the filesystem is not mounted or does not match the defined criteria,
+ * then the function mnt_context_next_umount() returns zero, but the @ignored is
+ * non-zero. Note that the root filesystem is always ignored.
+ *
+ * If umount(2) syscall or umount.type helper failed, then the
+ * mnt_context_next_umount() function returns zero, but the @mntrc is non-zero.
+ * Use also mnt_context_get_status() to check if the filesystem was
+ * successfully umounted.
+ *
+ * Returns: 0 on success,
+ * <0 in case of error (!= umount(2) errors)
+ * 1 at the end of the list.
+ */
+int mnt_context_next_umount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc,
+ int *ignored)
+{
+ struct libmnt_table *mtab;
+ const char *tgt;
+ int rc;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ cxt->mtab = NULL; /* do not reset mtab */
+ mnt_reset_context(cxt);
+
+ if (rc)
+ return rc;
+
+ cxt->mtab = mtab;
+
+ do {
+ rc = mnt_table_next_fs(mtab, itr, fs);
+ if (rc != 0)
+ return rc; /* no more filesystems (or error) */
+
+ tgt = mnt_fs_get_target(*fs);
+ } while (!tgt);
+
+ DBG(CXT, ul_debugobj(cxt, "next-umount: trying %s [fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]", tgt,
+ mnt_fs_get_fstype(*fs), cxt->fstype_pattern, mnt_fs_get_options(*fs), cxt->optstr_pattern));
+
+ /* ignore filesystems which don't match options patterns */
+ if ((cxt->fstype_pattern && !mnt_fs_match_fstype(*fs,
+ cxt->fstype_pattern)) ||
+
+ /* ignore filesystems which don't match type patterns */
+ (cxt->optstr_pattern && !mnt_fs_match_options(*fs,
+ cxt->optstr_pattern))) {
+ if (ignored)
+ *ignored = 1;
+
+ DBG(CXT, ul_debugobj(cxt, "next-umount: not-match"));
+ return 0;
+ }
+
+ rc = mnt_context_set_fs(cxt, *fs);
+ if (rc)
+ return rc;
+ rc = mnt_context_umount(cxt);
+ if (mntrc)
+ *mntrc = rc;
+ return 0;
+}
+
+
+int mnt_context_get_umount_excode(
+ struct libmnt_context *cxt,
+ int rc,
+ char *buf,
+ size_t bufsz)
+{
+ if (mnt_context_helper_executed(cxt))
+ /*
+ * /sbin/umount.<type> called, return status
+ */
+ return mnt_context_get_helper_status(cxt);
+
+ if (rc == 0 && mnt_context_get_status(cxt) == 1)
+ /*
+ * Libmount success && syscall success.
+ */
+ return MNT_EX_SUCCESS;
+
+ if (!mnt_context_syscall_called(cxt)) {
+ /*
+ * libmount errors (extra library checks)
+ */
+ if (rc == -EPERM && !mnt_context_tab_applied(cxt)) {
+ /* failed to evaluate permissions because not found
+ * relevant entry in mtab */
+ if (buf)
+ snprintf(buf, bufsz, _("not mounted"));
+ return MNT_EX_USAGE;
+ }
+
+ if (rc == -MNT_ERR_LOCK) {
+ if (buf)
+ snprintf(buf, bufsz, _("locking failed"));
+ return MNT_EX_FILEIO;
+ }
+
+ if (rc == -MNT_ERR_NAMESPACE) {
+ if (buf)
+ snprintf(buf, bufsz, _("failed to switch namespace"));
+ return MNT_EX_SYSERR;
+ }
+ return mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("umount failed: %m"));
+
+ } if (mnt_context_get_syscall_errno(cxt) == 0) {
+ /*
+ * umount(2) syscall success, but something else failed
+ * (probably error in mtab processing).
+ */
+ if (rc == -MNT_ERR_LOCK) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was unmounted, but failed to update userspace mount table"));
+ return MNT_EX_FILEIO;
+ }
+
+ if (rc == -MNT_ERR_NAMESPACE) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was unmounted, but failed to switch namespace back"));
+ return MNT_EX_SYSERR;
+
+ }
+
+ if (rc < 0)
+ return mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("filesystem was unmounted, but any subsequent operation failed: %m"));
+
+ return MNT_EX_SOFTWARE; /* internal error */
+ }
+
+ /*
+ * umount(2) errors
+ */
+ if (buf) {
+ int syserr = mnt_context_get_syscall_errno(cxt);
+
+ switch (syserr) {
+ case ENXIO:
+ snprintf(buf, bufsz, _("invalid block device")); /* ??? */
+ break;
+ case EINVAL:
+ snprintf(buf, bufsz, _("not mounted"));
+ break;
+ case EIO:
+ snprintf(buf, bufsz, _("can't write superblock"));
+ break;
+ case EBUSY:
+ snprintf(buf, bufsz, _("target is busy"));
+ break;
+ case ENOENT:
+ snprintf(buf, bufsz, _("no mount point specified"));
+ break;
+ case EPERM:
+ snprintf(buf, bufsz, _("must be superuser to unmount"));
+ break;
+ case EACCES:
+ snprintf(buf, bufsz, _("block devices are not permitted on filesystem"));
+ break;
+ default:
+ return mnt_context_get_generic_excode(syserr, buf, bufsz,_("umount(2) system call failed: %m"));
+ }
+ }
+ return MNT_EX_FAIL;
+}
diff --git a/libmount/src/context_veritydev.c b/libmount/src/context_veritydev.c
new file mode 100644
index 0000000..a8cb1f6
--- /dev/null
+++ b/libmount/src/context_veritydev.c
@@ -0,0 +1,566 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2019 Microsoft Corporation
+ *
+ * libmount 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.
+ */
+
+#include "mountP.h"
+
+#if defined(HAVE_CRYPTSETUP)
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+#include <dlfcn.h>
+#endif
+#include <libcryptsetup.h>
+#include "path.h"
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+static void *get_symbol(struct libmnt_context *cxt, void *dl, const char *name, int *rc)
+{
+ char *dl_error = NULL;
+ void *sym = dlsym(dl, name);
+
+ *rc = 0;
+ if ((dl_error = dlerror()) == NULL)
+ return sym;
+
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen symbol %s: %s", name, dl_error));
+ *rc = -ENOTSUP;
+
+ return NULL;
+}
+#endif
+
+static void libcryptsetup_log(int level __attribute__((__unused__)), const char *msg, void *data)
+{
+ struct libmnt_context *cxt = (struct libmnt_context *)data;
+
+ DBG(VERITY, ul_debugobj(cxt, "cryptsetup: %s", msg));
+}
+
+/* Taken from https://gitlab.com/cryptsetup/cryptsetup/blob/master/lib/utils_crypt.c#L225 */
+static size_t crypt_hex_to_bytes(const char *hex, char **result)
+{
+ char buf[3] = "xx\0", *endp, *bytes;
+ size_t i, len;
+
+ len = strlen(hex);
+ if (len % 2)
+ return -EINVAL;
+ len /= 2;
+
+ bytes = malloc(len);
+ if (!bytes)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) {
+ memcpy(buf, &hex[i * 2], 2);
+ errno = 0;
+ bytes[i] = strtoul(buf, &endp, 16);
+ if (errno || endp != &buf[2]) {
+ free(bytes);
+ return -EINVAL;
+ }
+ }
+ *result = bytes;
+ return i;
+}
+
+
+int mnt_context_setup_veritydev(struct libmnt_context *cxt)
+{
+ const char *backing_file, *optstr;
+ char *val = NULL, *key = NULL, *root_hash_binary = NULL, *mapper_device = NULL,
+ *mapper_device_full = NULL, *backing_file_basename = NULL, *root_hash = NULL,
+ *hash_device = NULL, *root_hash_file = NULL, *fec_device = NULL, *hash_sig = NULL,
+ *root_hash_sig_file = NULL;
+ size_t len, hash_size, hash_sig_size = 0, keysize = 0;
+ struct crypt_params_verity crypt_params = {};
+ struct crypt_device *crypt_dev = NULL;
+ int rc = 0;
+ /* Use the same default for FEC parity bytes as cryptsetup uses */
+ uint64_t offset = 0, fec_offset = 0, fec_roots = 2;
+ uint32_t crypt_activate_flags = CRYPT_ACTIVATE_READONLY;
+ struct stat hash_sig_st;
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ /* To avoid linking libmount to libcryptsetup, and keep the default dependencies list down, use dlopen */
+ void *dl = NULL;
+ void (*sym_crypt_set_debug_level)(int) = NULL;
+ void (*sym_crypt_set_log_callback)(struct crypt_device *, void (*log)(int, const char *, void *), void *) = NULL;
+ int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = NULL;
+ int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = NULL;
+ int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = NULL;
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = NULL;
+#endif
+ int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = NULL;
+ void (*sym_crypt_free)(struct crypt_device *) = NULL;
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = NULL;
+ int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = NULL;
+ int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = NULL;
+#else
+ void (*sym_crypt_set_debug_level)(int) = &crypt_set_debug_level;
+ void (*sym_crypt_set_log_callback)(struct crypt_device *, void (*log)(int, const char *, void *), void *) = &crypt_set_log_callback;
+ int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = &crypt_init_data_device;
+ int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = &crypt_load;
+ int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = &crypt_get_volume_key_size;
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = &crypt_activate_by_signed_key;
+#endif
+ int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = &crypt_activate_by_volume_key;
+ void (*sym_crypt_free)(struct crypt_device *) = &crypt_free;
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = &crypt_init_by_name;
+ int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = &crypt_get_verity_info;
+ int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = &crypt_volume_key_get;
+#endif
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ /* dm-verity volumes are read-only, and mount will fail if not set */
+ mnt_context_set_mflags(cxt, (cxt->mountflags | MS_RDONLY));
+
+ backing_file = mnt_fs_get_srcpath(cxt->fs);
+ if (!backing_file)
+ return -EINVAL;
+
+ /* To avoid clashes, prefix libmnt_ to all mapper devices */
+ backing_file_basename = basename(backing_file);
+ mapper_device = calloc(strlen(backing_file_basename) + strlen("libmnt_") + 1, sizeof(char));
+ if (!mapper_device)
+ return -ENOMEM;
+ strcat(mapper_device, "libmnt_");
+ strcat(mapper_device, backing_file_basename);
+
+ DBG(VERITY, ul_debugobj(cxt, "trying to setup verity device for %s", backing_file));
+
+ optstr = mnt_fs_get_user_options(cxt->fs);
+
+ /*
+ * verity.hashdevice=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_DEVICE) &&
+ mnt_optstr_get_option(optstr, "verity.hashdevice", &val, &len) == 0 && val) {
+ hash_device = strndup(val, len);
+ rc = hash_device ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.roothash=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH) &&
+ mnt_optstr_get_option(optstr, "verity.roothash", &val, &len) == 0 && val) {
+ root_hash = strndup(val, len);
+ rc = root_hash ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.hashoffset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_OFFSET) &&
+ mnt_optstr_get_option(optstr, "verity.hashoffset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &offset);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.hashoffset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.roothashfile=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH_FILE) &&
+ mnt_optstr_get_option(optstr, "verity.roothashfile", &val, &len) == 0 && val) {
+ root_hash_file = strndup(val, len);
+ rc = root_hash_file ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.fecdevice=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_DEVICE) &&
+ mnt_optstr_get_option(optstr, "verity.fecdevice", &val, &len) == 0 && val) {
+ fec_device = strndup(val, len);
+ rc = fec_device ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.fecoffset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_OFFSET) &&
+ mnt_optstr_get_option(optstr, "verity.fecoffset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &fec_offset);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.fecoffset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.fecroots=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_ROOTS) &&
+ mnt_optstr_get_option(optstr, "verity.fecroots", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &fec_roots);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.fecroots="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.roothashsig=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH_SIG) &&
+ mnt_optstr_get_option(optstr, "verity.roothashsig", &val, &len) == 0 && val) {
+ root_hash_sig_file = strndup(val, len);
+ rc = root_hash_sig_file ? 0 : -ENOMEM;
+ if (rc == 0)
+ rc = ul_path_stat(NULL, &hash_sig_st, 0, root_hash_sig_file);
+ if (rc == 0)
+ rc = !S_ISREG(hash_sig_st.st_mode) || !hash_sig_st.st_size ? -EINVAL : 0;
+ if (rc == 0) {
+ hash_sig_size = hash_sig_st.st_size;
+ hash_sig = malloc(hash_sig_size);
+ rc = hash_sig ? 0 : -ENOMEM;
+ }
+ if (rc == 0) {
+ rc = ul_path_read(NULL, hash_sig, hash_sig_size, root_hash_sig_file);
+ rc = rc < (int)hash_sig_size ? -1 : 0;
+ }
+ }
+
+ /*
+ * verity.oncorruption=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_VERITY_ON_CORRUPTION) &&
+ mnt_optstr_get_option(optstr, "verity.oncorruption", &val, &len) == 0) {
+ if (!strncmp(val, "ignore", len))
+ crypt_activate_flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION;
+ else if (!strncmp(val, "restart", len))
+ crypt_activate_flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION;
+ else if (!strncmp(val, "panic", len))
+ /* Added by libcryptsetup v2.3.4 - ignore on lower versions, as with other optional features */
+#ifdef CRYPT_ACTIVATE_PANIC_ON_CORRUPTION
+ crypt_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION;
+#else
+ DBG(VERITY, ul_debugobj(cxt, "verity.oncorruption=panic not supported by libcryptsetup, ignoring"));
+#endif
+ else {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.oncorruption="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ if (!rc && root_hash && root_hash_file) {
+ DBG(VERITY, ul_debugobj(cxt, "verity.roothash and verity.roothashfile are mutually exclusive"));
+ rc = -EINVAL;
+ } else if (!rc && root_hash_file) {
+ rc = ul_path_read_string(NULL, &root_hash, root_hash_file);
+ rc = rc < 1 ? rc : 0;
+ }
+
+ if (!rc && (!hash_device || !root_hash)) {
+ DBG(VERITY, ul_debugobj(cxt, "verity.hashdevice and one of verity.roothash or verity.roothashfile are mandatory"));
+ rc = -EINVAL;
+ }
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ if (rc == 0) {
+ int dl_flags = RTLD_LAZY | RTLD_LOCAL;
+ /* glibc extension: mnt_context_deferred_delete_veritydev is called immediately after, don't unload on dl_close */
+#ifdef RTLD_NODELETE
+ dl_flags |= RTLD_NODELETE;
+#endif
+ /* glibc extension: might help to avoid further symbols clashes */
+#ifdef RTLD_DEEPBIND
+ dl_flags |= RTLD_DEEPBIND;
+#endif
+ dl = dlopen("libcryptsetup.so.12", dl_flags);
+ if (!dl) {
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
+ rc = -ENOTSUP;
+ }
+ }
+
+ /* clear errors first, then load all the libcryptsetup symbols */
+ dlerror();
+
+ if (rc == 0)
+ *(void **)(&sym_crypt_set_debug_level) = get_symbol(cxt, dl, "crypt_set_debug_level", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_set_log_callback) = get_symbol(cxt, dl, "crypt_set_log_callback", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_init_data_device) = get_symbol(cxt, dl, "crypt_init_data_device", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_load) = get_symbol(cxt, dl, "crypt_load", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_get_volume_key_size) = get_symbol(cxt, dl, "crypt_get_volume_key_size", &rc);
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ if (rc == 0)
+ *(void **)(&sym_crypt_activate_by_signed_key) = get_symbol(cxt, dl, "crypt_activate_by_signed_key", &rc);
+#endif
+ if (rc == 0)
+ *(void **)(&sym_crypt_activate_by_volume_key) = get_symbol(cxt, dl, "crypt_activate_by_volume_key", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_free) = get_symbol(cxt, dl, "crypt_free", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_init_by_name) = get_symbol(cxt, dl, "crypt_init_by_name", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_get_verity_info) = get_symbol(cxt, dl, "crypt_get_verity_info", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_volume_key_get) = get_symbol(cxt, dl, "crypt_volume_key_get", &rc);
+#endif
+ if (rc)
+ goto done;
+
+ if (mnt_context_is_verbose(cxt))
+ (*sym_crypt_set_debug_level)(CRYPT_DEBUG_ALL);
+ (*sym_crypt_set_log_callback)(NULL, libcryptsetup_log, cxt);
+
+ rc = (*sym_crypt_init_data_device)(&crypt_dev, hash_device, backing_file);
+ if (rc)
+ goto done;
+
+ memset(&crypt_params, 0, sizeof(struct crypt_params_verity));
+ crypt_params.hash_area_offset = offset;
+ crypt_params.fec_area_offset = fec_offset;
+ crypt_params.fec_roots = fec_roots;
+ crypt_params.fec_device = fec_device;
+ crypt_params.flags = 0;
+ rc = (*sym_crypt_load)(crypt_dev, CRYPT_VERITY, &crypt_params);
+ if (rc < 0)
+ goto done;
+
+ hash_size = (*sym_crypt_get_volume_key_size)(crypt_dev);
+ if (crypt_hex_to_bytes(root_hash, &root_hash_binary) != hash_size) {
+ DBG(VERITY, ul_debugobj(cxt, "root hash %s is not of length %zu", root_hash, hash_size));
+ rc = -EINVAL;
+ goto done;
+ }
+ if (hash_sig) {
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ rc = (*sym_crypt_activate_by_signed_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
+ hash_sig, hash_sig_size, crypt_activate_flags);
+#else
+ rc = -EINVAL;
+ DBG(VERITY, ul_debugobj(cxt, "verity.roothashsig=%s passed but libcryptsetup does not provide crypt_activate_by_signed_key()", hash_sig));
+#endif
+ } else
+ rc = (*sym_crypt_activate_by_volume_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
+ crypt_activate_flags);
+ /*
+ * If the mapper device already exists, and if libcryptsetup supports it, get the root
+ * hash associated with the existing one and compare it with the parameter passed by
+ * the user. If they match, then we can be sure the user intended to mount the exact
+ * same device, and simply reuse it and return success.
+ * The kernel does the refcounting for us.
+ * If libcryptsetup does not support getting the root hash out of an existing device,
+ * then return an error and tell the user that the device is already in use.
+ * Pass through only OOM errors or mismatching root hash errors.
+ */
+ if (rc == -EEXIST) {
+ DBG(VERITY, ul_debugobj(cxt, "%s already in use as /dev/mapper/%s", backing_file, mapper_device));
+ (*sym_crypt_free)(crypt_dev);
+ rc = (*sym_crypt_init_by_name)(&crypt_dev, mapper_device);
+ if (!rc) {
+ rc = (*sym_crypt_get_verity_info)(crypt_dev, &crypt_params);
+ if (!rc) {
+ key = calloc(hash_size, 1);
+ if (!key) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ }
+ if (!rc) {
+ keysize = hash_size;
+ rc = (*sym_crypt_volume_key_get)(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0);
+ }
+ if (!rc) {
+ DBG(VERITY, ul_debugobj(cxt, "comparing root hash of existing device with %s", root_hash));
+ if (memcmp(key, root_hash_binary, hash_size)) {
+ DBG(VERITY, ul_debugobj(cxt, "existing device's hash does not match with %s", root_hash));
+ rc = -EINVAL;
+ goto done;
+ }
+ } else {
+ DBG(VERITY, ul_debugobj(cxt, "libcryptsetup does not support extracting root hash of existing device"));
+ }
+ }
+ if (rc) {
+ rc = -EEXIST;
+ } else {
+ /*
+ * Ensure that, if signatures are supported, we only reuse the device if the previous mount
+ * used the same settings, so that a previous unsigned mount will not be reused if the user
+ * asks to use signing for the new one, and viceversa.
+ */
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ if (!!hash_sig != !!(crypt_params.flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE)) {
+ rc = -EINVAL;
+ DBG(VERITY, ul_debugobj(cxt, "existing device and new mount have to either be both opened with signature or both without"));
+ goto done;
+ }
+#endif
+ DBG(VERITY, ul_debugobj(cxt, "root hash of %s matches %s, reusing device", mapper_device, root_hash));
+ }
+ }
+
+ if (!rc) {
+ cxt->flags |= MNT_FL_VERITYDEV_READY;
+ mapper_device_full = calloc(strlen(mapper_device) + strlen("/dev/mapper/") + 1, sizeof(char));
+ if (!mapper_device_full)
+ rc = -ENOMEM;
+ else {
+ strcat(mapper_device_full, "/dev/mapper/");
+ strcat(mapper_device_full, mapper_device);
+ rc = mnt_fs_set_source(cxt->fs, mapper_device_full);
+ }
+ }
+
+done:
+ if (sym_crypt_free)
+ (*sym_crypt_free)(crypt_dev);
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ if (dl)
+ dlclose(dl);
+#endif
+ free(root_hash_binary);
+ free(mapper_device_full);
+ free(mapper_device);
+ free(hash_device);
+ free(root_hash);
+ free(root_hash_file);
+ free(root_hash_sig_file);
+ free(fec_device);
+ free(hash_sig);
+ free(key);
+ return rc;
+}
+
+int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt)
+{
+ const char *src;
+ /* If mounting failed delete immediately, otherwise setup auto cleanup for user umount */
+ uint32_t flags = mnt_context_get_status(cxt) ? CRYPT_DEACTIVATE_DEFERRED : 0;
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ void *dl = NULL;
+ int dl_flags = RTLD_LAZY | RTLD_LOCAL;
+ /* glibc extension: might help to avoid further symbols clashes */
+#ifdef RTLD_DEEPBIND
+ dl_flags |= RTLD_DEEPBIND;
+#endif
+ void (*sym_crypt_set_debug_level)(int) = NULL;
+ void (*sym_crypt_set_log_callback)(struct crypt_device *, void (*log)(int, const char *, void *), void *) = NULL;
+ int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = NULL;
+#else
+ void (*sym_crypt_set_debug_level)(int) = &crypt_set_debug_level;
+ void (*sym_crypt_set_log_callback)(struct crypt_device *, void (*log)(int, const char *, void *), void *) = &crypt_set_log_callback;
+ int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = &crypt_deactivate_by_name;
+#endif
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ if (!(cxt->flags & MNT_FL_VERITYDEV_READY))
+ return 0;
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return -EINVAL;
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ dl = dlopen("libcryptsetup.so.12", dl_flags);
+ if (!dl) {
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
+ return -ENOTSUP;
+ }
+
+ /* clear errors first */
+ dlerror();
+
+ if (!rc)
+ *(void **)(&sym_crypt_set_debug_level) = get_symbol(cxt, dl, "crypt_set_debug_level", &rc);
+ if (!rc)
+ *(void **)(&sym_crypt_set_log_callback) = get_symbol(cxt, dl, "crypt_set_log_callback", &rc);
+ if (!rc)
+ *(void **)(&sym_crypt_deactivate_by_name) = get_symbol(cxt, dl, "crypt_deactivate_by_name", &rc);
+#endif
+ if (!rc) {
+ if (mnt_context_is_verbose(cxt))
+ (*sym_crypt_set_debug_level)(CRYPT_DEBUG_ALL);
+ (*sym_crypt_set_log_callback)(NULL, libcryptsetup_log, cxt);
+
+ rc = (*sym_crypt_deactivate_by_name)(NULL, src, flags);
+ if (!rc)
+ cxt->flags &= ~MNT_FL_VERITYDEV_READY;
+ }
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ dlclose(dl);
+#endif
+ DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc));
+ return rc;
+}
+
+#else
+
+int mnt_context_setup_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
+{
+ return 0;
+}
+
+int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
+{
+ return 0;
+}
+#endif
+
+int mnt_context_is_veritydev(struct libmnt_context *cxt)
+{
+ const char *src;
+
+ assert(cxt);
+
+ /* The mount flags have to be merged, otherwise we have to use
+ * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return 0; /* backing file not set */
+
+ if (cxt->user_mountflags & (MNT_MS_HASH_DEVICE |
+ MNT_MS_ROOT_HASH |
+ MNT_MS_HASH_OFFSET)) {
+#ifndef HAVE_CRYPTSETUP
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but libmount built without libcryptsetup"));
+ return -ENOTSUP;
+#else
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected"));
+ return 1;
+#endif
+ }
+
+ if (!strncmp(src, "/dev/mapper/libmnt_", strlen("/dev/mapper/libmnt_"))) {
+#ifndef HAVE_CRYPTSETUP
+ DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device but libmount built without libcryptsetup"));
+ return -ENOTSUP;
+#else
+ DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device"));
+ return 1;
+#endif
+ }
+
+ return 0;
+}
diff --git a/libmount/src/fs.c b/libmount/src/fs.c
new file mode 100644
index 0000000..898ee64
--- /dev/null
+++ b/libmount/src/fs.c
@@ -0,0 +1,1671 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: fs
+ * @title: Filesystem
+ * @short_description: represents one entry from fstab, mtab, or mountinfo file
+ *
+ */
+#include <ctype.h>
+#include <blkid.h>
+#include <stddef.h>
+
+#include "mountP.h"
+#include "strutils.h"
+
+/**
+ * mnt_new_fs:
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the filesystem.
+ *
+ * Returns: newly allocated struct libmnt_fs.
+ */
+struct libmnt_fs *mnt_new_fs(void)
+{
+ struct libmnt_fs *fs = calloc(1, sizeof(*fs));
+ if (!fs)
+ return NULL;
+
+ fs->refcount = 1;
+ INIT_LIST_HEAD(&fs->ents);
+ DBG(FS, ul_debugobj(fs, "alloc"));
+ return fs;
+}
+
+/**
+ * mnt_free_fs:
+ * @fs: fs pointer
+ *
+ * Deallocates the fs. This function does not care about reference count. Don't
+ * use this function directly -- it's better to use mnt_unref_fs().
+ *
+ * The reference counting is supported since util-linux v2.24.
+ */
+void mnt_free_fs(struct libmnt_fs *fs)
+{
+ if (!fs)
+ return;
+
+ DBG(FS, ul_debugobj(fs, "free [refcount=%d]", fs->refcount));
+
+ mnt_reset_fs(fs);
+ free(fs);
+}
+
+/**
+ * mnt_reset_fs:
+ * @fs: fs pointer
+ *
+ * Resets (zeroize) @fs.
+ */
+void mnt_reset_fs(struct libmnt_fs *fs)
+{
+ int ref;
+
+ if (!fs)
+ return;
+
+ ref = fs->refcount;
+
+ list_del(&fs->ents);
+ free(fs->source);
+ free(fs->bindsrc);
+ free(fs->tagname);
+ free(fs->tagval);
+ free(fs->root);
+ free(fs->swaptype);
+ free(fs->target);
+ free(fs->fstype);
+ free(fs->optstr);
+ free(fs->vfs_optstr);
+ free(fs->fs_optstr);
+ free(fs->user_optstr);
+ free(fs->attrs);
+ free(fs->opt_fields);
+ free(fs->comment);
+
+ memset(fs, 0, sizeof(*fs));
+ INIT_LIST_HEAD(&fs->ents);
+ fs->refcount = ref;
+}
+
+/**
+ * mnt_ref_fs:
+ * @fs: fs pointer
+ *
+ * Increments reference counter.
+ */
+void mnt_ref_fs(struct libmnt_fs *fs)
+{
+ if (fs) {
+ fs->refcount++;
+ /*DBG(FS, ul_debugobj(fs, "ref=%d", fs->refcount));*/
+ }
+}
+
+/**
+ * mnt_unref_fs:
+ * @fs: fs pointer
+ *
+ * De-increments reference counter, on zero the @fs is automatically
+ * deallocated by mnt_free_fs().
+ */
+void mnt_unref_fs(struct libmnt_fs *fs)
+{
+ if (fs) {
+ fs->refcount--;
+ /*DBG(FS, ul_debugobj(fs, "unref=%d", fs->refcount));*/
+ if (fs->refcount <= 0)
+ mnt_free_fs(fs);
+ }
+}
+
+static inline int update_str(char **dest, const char *src)
+{
+ size_t sz;
+ char *x;
+
+ assert(dest);
+
+ if (!src) {
+ free(*dest);
+ *dest = NULL;
+ return 0; /* source (old) is empty */
+ }
+
+ sz = strlen(src) + 1;
+ x = realloc(*dest, sz);
+ if (!x)
+ return -ENOMEM;
+ *dest = x;
+ memcpy(*dest, src, sz);
+ return 0;
+}
+
+/* This function does NOT overwrite (replace) the string in @new, the string in
+ * @new has to be NULL otherwise this is no-op. */
+static inline int cpy_str_at_offset(void *new, const void *old, size_t offset)
+{
+ char **o = (char **) ((char *) old + offset);
+ char **n = (char **) ((char *) new + offset);
+
+ if (*n)
+ return 0; /* already set, don't overwrite */
+
+ return update_str(n, *o);
+}
+
+/**
+ * mnt_copy_fs:
+ * @dest: destination FS
+ * @src: source FS
+ *
+ * If @dest is NULL, then a new FS is allocated, if any @dest field is already
+ * set, then the field is NOT overwritten.
+ *
+ * This function does not copy userdata (se mnt_fs_set_userdata()). A new copy is
+ * not linked with any existing mnt_tab.
+ *
+ * Returns: @dest or NULL in case of error
+ */
+struct libmnt_fs *mnt_copy_fs(struct libmnt_fs *dest,
+ const struct libmnt_fs *src)
+{
+ const struct libmnt_fs *org = dest;
+
+ if (!src)
+ return NULL;
+ if (!dest) {
+ dest = mnt_new_fs();
+ if (!dest)
+ return NULL;
+
+ dest->tab = NULL;
+ }
+
+ dest->id = src->id;
+ dest->parent = src->parent;
+ dest->devno = src->devno;
+ dest->tid = src->tid;
+
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, source)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, tagname)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, tagval)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, root)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, swaptype)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, target)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, fstype)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, optstr)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, vfs_optstr)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, fs_optstr)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, user_optstr)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, attrs)))
+ goto err;
+ if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, bindsrc)))
+ goto err;
+
+ dest->freq = src->freq;
+ dest->passno = src->passno;
+ dest->flags = src->flags;
+ dest->size = src->size;
+ dest->usedsize = src->usedsize;
+ dest->priority = src->priority;
+
+ return dest;
+err:
+ if (!org)
+ mnt_free_fs(dest);
+ return NULL;
+}
+
+/*
+ * This function copies all @fs description except information that does not
+ * belong to /etc/mtab (e.g. VFS and userspace mount options with MNT_NOMTAB
+ * mask).
+ *
+ * Returns: copy of @fs.
+ */
+struct libmnt_fs *mnt_copy_mtab_fs(const struct libmnt_fs *fs)
+{
+ struct libmnt_fs *n = mnt_new_fs();
+
+ assert(fs);
+ if (!n)
+ return NULL;
+
+ if (strdup_between_structs(n, fs, source))
+ goto err;
+ if (strdup_between_structs(n, fs, target))
+ goto err;
+ if (strdup_between_structs(n, fs, fstype))
+ goto err;
+
+ if (fs->vfs_optstr) {
+ char *p = NULL;
+ mnt_optstr_get_options(fs->vfs_optstr, &p,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP),
+ MNT_NOMTAB);
+ n->vfs_optstr = p;
+ }
+
+ if (fs->user_optstr) {
+ char *p = NULL;
+ mnt_optstr_get_options(fs->user_optstr, &p,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP),
+ MNT_NOMTAB);
+ n->user_optstr = p;
+ }
+
+ if (strdup_between_structs(n, fs, fs_optstr))
+ goto err;
+
+ /* we cannot copy original optstr, the new optstr has to be without
+ * non-mtab options -- so, let's generate a new string */
+ n->optstr = mnt_fs_strdup_options(n);
+
+ n->freq = fs->freq;
+ n->passno = fs->passno;
+ n->flags = fs->flags;
+
+ return n;
+err:
+ mnt_free_fs(n);
+ return NULL;
+
+}
+
+/**
+ * mnt_fs_get_userdata:
+ * @fs: struct libmnt_file instance
+ *
+ * Returns: private data set by mnt_fs_set_userdata() or NULL.
+ */
+void *mnt_fs_get_userdata(struct libmnt_fs *fs)
+{
+ if (!fs)
+ return NULL;
+ return fs->userdata;
+}
+
+/**
+ * mnt_fs_set_userdata:
+ * @fs: struct libmnt_file instance
+ * @data: user data
+ *
+ * The "userdata" are library independent data.
+ *
+ * Returns: 0 or negative number in case of error (if @fs is NULL).
+ */
+int mnt_fs_set_userdata(struct libmnt_fs *fs, void *data)
+{
+ if (!fs)
+ return -EINVAL;
+ fs->userdata = data;
+ return 0;
+}
+
+/**
+ * mnt_fs_get_srcpath:
+ * @fs: struct libmnt_file (fstab/mtab/mountinfo) fs
+ *
+ * The mount "source path" is:
+ * - a directory for 'bind' mounts (in fstab or mtab only)
+ * - a device name for standard mounts
+ *
+ * See also mnt_fs_get_tag() and mnt_fs_get_source().
+ *
+ * Returns: mount source path or NULL in case of error or when the path
+ * is not defined.
+ */
+const char *mnt_fs_get_srcpath(struct libmnt_fs *fs)
+{
+ if (!fs)
+ return NULL;
+
+ /* fstab-like fs */
+ if (fs->tagname)
+ return NULL; /* the source contains a "NAME=value" */
+ return fs->source;
+}
+
+/**
+ * mnt_fs_get_source:
+ * @fs: struct libmnt_file (fstab/mtab/mountinfo) fs
+ *
+ * Returns: mount source. Note that the source could be unparsed TAG
+ * (LABEL/UUID). See also mnt_fs_get_srcpath() and mnt_fs_get_tag().
+ */
+const char *mnt_fs_get_source(struct libmnt_fs *fs)
+{
+ return fs ? fs->source : NULL;
+}
+
+/*
+ * Used by the parser ONLY (@source has to be freed on error)
+ */
+int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source)
+{
+ char *t = NULL, *v = NULL;
+
+ assert(fs);
+
+ if (source && blkid_parse_tag_string(source, &t, &v) == 0 &&
+ !mnt_valid_tagname(t)) {
+ /* parsable but unknown tag -- ignore */
+ free(t);
+ free(v);
+ t = v = NULL;
+ }
+
+ if (fs->source != source)
+ free(fs->source);
+
+ free(fs->tagname);
+ free(fs->tagval);
+
+ fs->source = source;
+ fs->tagname = t;
+ fs->tagval = v;
+ return 0;
+}
+
+/**
+ * mnt_fs_set_source:
+ * @fs: fstab/mtab/mountinfo entry
+ * @source: new source
+ *
+ * This function creates a private copy (strdup()) of @source.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_source(struct libmnt_fs *fs, const char *source)
+{
+ char *p = NULL;
+ int rc;
+
+ if (!fs)
+ return -EINVAL;
+
+ if (source) {
+ p = strdup(source);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ rc = __mnt_fs_set_source_ptr(fs, p);
+ if (rc)
+ free(p);
+ return rc;
+}
+
+/**
+ * mnt_fs_streq_srcpath:
+ * @fs: fs
+ * @path: source path
+ *
+ * Compares @fs source path with @path. The redundant slashes are ignored.
+ * This function compares strings and does not canonicalize the paths.
+ * See also more heavy and generic mnt_fs_match_source().
+ *
+ * Returns: 1 if @fs source path equal to @path, otherwise 0.
+ */
+int mnt_fs_streq_srcpath(struct libmnt_fs *fs, const char *path)
+{
+ const char *p;
+
+ if (!fs)
+ return 0;
+
+ p = mnt_fs_get_srcpath(fs);
+
+ if (!mnt_fs_is_pseudofs(fs))
+ return streq_paths(p, path);
+
+ if (!p && !path)
+ return 1;
+
+ return p && path && strcmp(p, path) == 0;
+}
+
+/**
+ * mnt_fs_get_table:
+ * @fs: table entry
+ * @tb: table that contains @fs
+ *
+ * Returns: 0 or negative number on error (if @fs or @tb is NULL).
+ *
+ * Since: 2.34
+ */
+int mnt_fs_get_table(struct libmnt_fs *fs, struct libmnt_table **tb)
+{
+ if (!fs || !tb)
+ return -EINVAL;
+
+ *tb = fs->tab;
+ return 0;
+}
+
+/**
+ * mnt_fs_streq_target:
+ * @fs: fs
+ * @path: mount point
+ *
+ * Compares @fs target path with @path. The redundant slashes are ignored.
+ * This function compares strings and does not canonicalize the paths.
+ * See also more generic mnt_fs_match_target().
+ *
+ * Returns: 1 if @fs target path equal to @path, otherwise 0.
+ */
+int mnt_fs_streq_target(struct libmnt_fs *fs, const char *path)
+{
+ return fs && streq_paths(mnt_fs_get_target(fs), path);
+}
+
+/**
+ * mnt_fs_get_tag:
+ * @fs: fs
+ * @name: returns pointer to NAME string
+ * @value: returns pointer to VALUE string
+ *
+ * "TAG" is NAME=VALUE (e.g. LABEL=foo)
+ *
+ * The TAG is the first column in the fstab file. The TAG or "srcpath" always has
+ * to be set for all entries.
+ *
+ * See also mnt_fs_get_source().
+ *
+ * <informalexample>
+ * <programlisting>
+ * char *src;
+ * struct libmnt_fs *fs = mnt_table_find_target(tb, "/home", MNT_ITER_FORWARD);
+ *
+ * if (!fs)
+ * goto err;
+ *
+ * src = mnt_fs_get_srcpath(fs);
+ * if (!src) {
+ * char *tag, *val;
+ * if (mnt_fs_get_tag(fs, &tag, &val) == 0)
+ * printf("%s: %s\n", tag, val); // LABEL or UUID
+ * } else
+ * printf("device: %s\n", src); // device or bind path
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: 0 on success or negative number in case a TAG is not defined.
+ */
+int mnt_fs_get_tag(struct libmnt_fs *fs, const char **name, const char **value)
+{
+ if (fs == NULL || !fs->tagname)
+ return -EINVAL;
+ if (name)
+ *name = fs->tagname;
+ if (value)
+ *value = fs->tagval;
+ return 0;
+}
+
+/**
+ * mnt_fs_get_target:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: pointer to mountpoint path or NULL
+ */
+const char *mnt_fs_get_target(struct libmnt_fs *fs)
+{
+ return fs ? fs->target : NULL;
+}
+
+/**
+ * mnt_fs_set_target:
+ * @fs: fstab/mtab/mountinfo entry
+ * @tgt: mountpoint
+ *
+ * This function creates a private copy (strdup()) of @tgt.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_target(struct libmnt_fs *fs, const char *tgt)
+{
+ return strdup_to_struct_member(fs, target, tgt);
+}
+
+static int mnt_fs_get_flags(struct libmnt_fs *fs)
+{
+ return fs ? fs->flags : 0;
+}
+
+/**
+ * mnt_fs_get_propagation:
+ * @fs: mountinfo entry
+ * @flags: returns propagation MS_* flags as present in the mountinfo file
+ *
+ * Note that this function sets @flags to zero if no propagation flags are found
+ * in the mountinfo file. The kernel default is MS_PRIVATE, this flag is not stored
+ * in the mountinfo file.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_get_propagation(struct libmnt_fs *fs, unsigned long *flags)
+{
+ if (!fs || !flags)
+ return -EINVAL;
+
+ *flags = 0;
+
+ if (!fs->opt_fields)
+ return 0;
+
+ /*
+ * The optional fields format is incompatible with mount options
+ * ... we have to parse the field here.
+ */
+ *flags |= strstr(fs->opt_fields, "shared:") ? MS_SHARED : MS_PRIVATE;
+
+ if (strstr(fs->opt_fields, "master:"))
+ *flags |= MS_SLAVE;
+ if (strstr(fs->opt_fields, "unbindable"))
+ *flags |= MS_UNBINDABLE;
+
+ return 0;
+}
+
+/**
+ * mnt_fs_is_kernel:
+ * @fs: filesystem
+ *
+ * Returns: 1 if the filesystem description is read from kernel e.g. /proc/mounts.
+ */
+int mnt_fs_is_kernel(struct libmnt_fs *fs)
+{
+ return mnt_fs_get_flags(fs) & MNT_FS_KERNEL ? 1 : 0;
+}
+
+/**
+ * mnt_fs_is_swaparea:
+ * @fs: filesystem
+ *
+ * Returns: 1 if the filesystem uses "swap" as a type
+ */
+int mnt_fs_is_swaparea(struct libmnt_fs *fs)
+{
+ return mnt_fs_get_flags(fs) & MNT_FS_SWAP ? 1 : 0;
+}
+
+/**
+ * mnt_fs_is_pseudofs:
+ * @fs: filesystem
+ *
+ * Returns: 1 if the filesystem is a pseudo fs type (proc, cgroups)
+ */
+int mnt_fs_is_pseudofs(struct libmnt_fs *fs)
+{
+ return mnt_fs_get_flags(fs) & MNT_FS_PSEUDO ? 1 : 0;
+}
+
+/**
+ * mnt_fs_is_netfs:
+ * @fs: filesystem
+ *
+ * Returns: 1 if the filesystem is a network filesystem
+ */
+int mnt_fs_is_netfs(struct libmnt_fs *fs)
+{
+ return mnt_fs_get_flags(fs) & MNT_FS_NET ? 1 : 0;
+}
+
+/**
+ * mnt_fs_is_regularfs:
+ * @fs: filesystem
+ *
+ * Returns: 1 if the filesystem is a regular filesystem (not network or pseudo filesystem).
+ *
+ * Since: 2.38
+ */
+int mnt_fs_is_regularfs(struct libmnt_fs *fs)
+{
+ return !(mnt_fs_is_pseudofs(fs)
+ || mnt_fs_is_netfs(fs)
+ || mnt_fs_is_swaparea(fs));
+}
+
+/**
+ * mnt_fs_get_fstype:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: pointer to filesystem type.
+ */
+const char *mnt_fs_get_fstype(struct libmnt_fs *fs)
+{
+ return fs ? fs->fstype : NULL;
+}
+
+/* Used by the struct libmnt_file parser only */
+int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype)
+{
+ assert(fs);
+
+ if (fstype != fs->fstype)
+ free(fs->fstype);
+
+ fs->fstype = fstype;
+ fs->flags &= ~MNT_FS_PSEUDO;
+ fs->flags &= ~MNT_FS_NET;
+ fs->flags &= ~MNT_FS_SWAP;
+
+ /* save info about pseudo filesystems */
+ if (fs->fstype) {
+ if (mnt_fstype_is_pseudofs(fs->fstype))
+ fs->flags |= MNT_FS_PSEUDO;
+ else if (mnt_fstype_is_netfs(fs->fstype))
+ fs->flags |= MNT_FS_NET;
+ else if (!strcmp(fs->fstype, "swap"))
+ fs->flags |= MNT_FS_SWAP;
+ }
+ return 0;
+}
+
+/**
+ * mnt_fs_set_fstype:
+ * @fs: fstab/mtab/mountinfo entry
+ * @fstype: filesystem type
+ *
+ * This function creates a private copy (strdup()) of @fstype.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_fstype(struct libmnt_fs *fs, const char *fstype)
+{
+ char *p = NULL;
+
+ if (!fs)
+ return -EINVAL;
+ if (fstype) {
+ p = strdup(fstype);
+ if (!p)
+ return -ENOMEM;
+ }
+ return __mnt_fs_set_fstype_ptr(fs, p);
+}
+
+/*
+ * Merges @vfs and @fs options strings into a new string.
+ * This function cares about 'ro/rw' options. The 'ro' is
+ * always used if @vfs or @fs is read-only.
+ * For example:
+ *
+ * mnt_merge_optstr("rw,noexec", "ro,journal=update")
+ *
+ * returns: "ro,noexec,journal=update"
+ *
+ * mnt_merge_optstr("rw,noexec", "rw,journal=update")
+ *
+ * returns: "rw,noexec,journal=update"
+ */
+static char *merge_optstr(const char *vfs, const char *fs)
+{
+ char *res, *p;
+ size_t sz;
+ int ro = 0, rw = 0;
+
+ if (!vfs && !fs)
+ return NULL;
+ if (!vfs || !fs)
+ return strdup(fs ? fs : vfs);
+ if (!strcmp(vfs, fs))
+ return strdup(vfs); /* e.g. "aaa" and "aaa" */
+
+ /* leave space for the leading "r[ow],", "," and the trailing zero */
+ sz = strlen(vfs) + strlen(fs) + 5;
+ res = malloc(sz);
+ if (!res)
+ return NULL;
+ p = res + 3; /* make a room for rw/ro flag */
+
+ snprintf(p, sz - 3, "%s,%s", vfs, fs);
+
+ /* remove 'rw' flags */
+ rw += !mnt_optstr_remove_option(&p, "rw"); /* from vfs */
+ rw += !mnt_optstr_remove_option(&p, "rw"); /* from fs */
+
+ /* remove 'ro' flags if necessary */
+ if (rw != 2) {
+ ro += !mnt_optstr_remove_option(&p, "ro");
+ if (ro + rw < 2)
+ ro += !mnt_optstr_remove_option(&p, "ro");
+ }
+
+ if (!strlen(p))
+ memcpy(res, ro ? "ro" : "rw", 3);
+ else
+ memcpy(res, ro ? "ro," : "rw,", 3);
+ return res;
+}
+
+/**
+ * mnt_fs_strdup_options:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Merges all mount options (VFS, FS and userspace) to one options string
+ * and returns the result. This function does not modify @fs.
+ *
+ * Returns: pointer to string (can be freed by free(3)) or NULL in case of error.
+ */
+char *mnt_fs_strdup_options(struct libmnt_fs *fs)
+{
+ char *res;
+
+ if (!fs)
+ return NULL;
+
+ errno = 0;
+ if (fs->optstr)
+ return strdup(fs->optstr);
+
+ res = merge_optstr(fs->vfs_optstr, fs->fs_optstr);
+ if (!res && errno)
+ return NULL;
+ if (fs->user_optstr &&
+ mnt_optstr_append_option(&res, fs->user_optstr, NULL)) {
+ free(res);
+ res = NULL;
+ }
+ return res;
+}
+
+/**
+ * mnt_fs_get_options:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: pointer to string or NULL in case of error.
+ */
+const char *mnt_fs_get_options(struct libmnt_fs *fs)
+{
+ return fs ? fs->optstr : NULL;
+}
+
+/**
+ * mnt_fs_get_optional_fields
+ * @fs: mountinfo entry pointer
+ *
+ * Returns: pointer to string with mountinfo optional fields
+ * or NULL in case of error.
+ */
+const char *mnt_fs_get_optional_fields(struct libmnt_fs *fs)
+{
+ return fs ? fs->opt_fields : NULL;
+}
+
+/**
+ * mnt_fs_set_options:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ * @optstr: options string
+ *
+ * Splits @optstr to VFS, FS and userspace mount options and updates relevant
+ * parts of @fs.
+ *
+ * Returns: 0 on success, or negative number in case of error.
+ */
+int mnt_fs_set_options(struct libmnt_fs *fs, const char *optstr)
+{
+ char *v = NULL, *f = NULL, *u = NULL, *n = NULL;
+
+ if (!fs)
+ return -EINVAL;
+ if (optstr) {
+ int rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0);
+ if (rc)
+ return rc;
+ n = strdup(optstr);
+ if (!n) {
+ free(u);
+ free(v);
+ free(f);
+ return -ENOMEM;
+ }
+ }
+
+ free(fs->fs_optstr);
+ free(fs->vfs_optstr);
+ free(fs->user_optstr);
+ free(fs->optstr);
+
+ fs->fs_optstr = f;
+ fs->vfs_optstr = v;
+ fs->user_optstr = u;
+ fs->optstr = n;
+
+ return 0;
+}
+
+/**
+ * mnt_fs_append_options:
+ * @fs: fstab/mtab/mountinfo entry
+ * @optstr: mount options
+ *
+ * Parses (splits) @optstr and appends results to VFS, FS and userspace lists
+ * of options.
+ *
+ * If @optstr is NULL, then @fs is not modified and 0 is returned.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_append_options(struct libmnt_fs *fs, const char *optstr)
+{
+ char *v = NULL, *f = NULL, *u = NULL;
+ int rc;
+
+ if (!fs)
+ return -EINVAL;
+ if (!optstr)
+ return 0;
+
+ rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0);
+ if (rc)
+ return rc;
+
+ if (!rc && v)
+ rc = mnt_optstr_append_option(&fs->vfs_optstr, v, NULL);
+ if (!rc && f)
+ rc = mnt_optstr_append_option(&fs->fs_optstr, f, NULL);
+ if (!rc && u)
+ rc = mnt_optstr_append_option(&fs->user_optstr, u, NULL);
+ if (!rc)
+ rc = mnt_optstr_append_option(&fs->optstr, optstr, NULL);
+
+ free(v);
+ free(f);
+ free(u);
+
+ return rc;
+}
+
+/**
+ * mnt_fs_prepend_options:
+ * @fs: fstab/mtab/mountinfo entry
+ * @optstr: mount options
+ *
+ * Parses (splits) @optstr and prepends the results to VFS, FS and userspace lists
+ * of options.
+ *
+ * If @optstr is NULL, then @fs is not modified and 0 is returned.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_prepend_options(struct libmnt_fs *fs, const char *optstr)
+{
+ char *v = NULL, *f = NULL, *u = NULL;
+ int rc;
+
+ if (!fs)
+ return -EINVAL;
+ if (!optstr)
+ return 0;
+
+ rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0);
+ if (rc)
+ return rc;
+
+ if (!rc && v)
+ rc = mnt_optstr_prepend_option(&fs->vfs_optstr, v, NULL);
+ if (!rc && f)
+ rc = mnt_optstr_prepend_option(&fs->fs_optstr, f, NULL);
+ if (!rc && u)
+ rc = mnt_optstr_prepend_option(&fs->user_optstr, u, NULL);
+ if (!rc)
+ rc = mnt_optstr_prepend_option(&fs->optstr, optstr, NULL);
+
+ free(v);
+ free(f);
+ free(u);
+
+ return rc;
+}
+
+/*
+ * mnt_fs_get_fs_options:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: pointer to superblock (fs-depend) mount option string or NULL.
+ */
+const char *mnt_fs_get_fs_options(struct libmnt_fs *fs)
+{
+ return fs ? fs->fs_optstr : NULL;
+}
+
+/**
+ * mnt_fs_get_vfs_options:
+ * @fs: fstab/mtab entry pointer
+ *
+ * Returns: pointer to fs-independent (VFS) mount option string or NULL.
+ */
+const char *mnt_fs_get_vfs_options(struct libmnt_fs *fs)
+{
+ return fs ? fs->vfs_optstr : NULL;
+}
+
+/**
+ * mnt_fs_get_vfs_options_all:
+ * @fs: fstab/mtab entry pointer
+ *
+ * Returns: pointer to newlly allocated string (can be freed by free(3)) or
+ * NULL in case of error. The string contains all (including defaults) mount
+ * options.
+ */
+char *mnt_fs_get_vfs_options_all(struct libmnt_fs *fs)
+{
+ const struct libmnt_optmap *map = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ const struct libmnt_optmap *ent;
+ const char *opts = mnt_fs_get_options(fs);
+ char *result = NULL;
+ unsigned long flags = 0;
+
+ if (!opts || mnt_optstr_get_flags(opts, &flags, map))
+ return NULL;
+
+ for (ent = map ; ent && ent->name ; ent++){
+ if (ent->id & flags) { /* non-default value */
+ if (!(ent->mask & MNT_INVERT))
+ mnt_optstr_append_option(&result, ent->name, NULL);
+ else
+ continue;
+ } else if (ent->mask & MNT_INVERT)
+ mnt_optstr_append_option(&result, ent->name, NULL);
+ }
+
+ return result;
+}
+
+/**
+ * mnt_fs_get_user_options:
+ * @fs: fstab/mtab entry pointer
+ *
+ * Returns: pointer to userspace mount option string or NULL.
+ */
+const char *mnt_fs_get_user_options(struct libmnt_fs *fs)
+{
+ return fs ? fs->user_optstr : NULL;
+}
+
+/**
+ * mnt_fs_get_attributes:
+ * @fs: fstab/mtab entry pointer
+ *
+ * Returns: pointer to attributes string or NULL.
+ */
+const char *mnt_fs_get_attributes(struct libmnt_fs *fs)
+{
+ return fs ? fs->attrs : NULL;
+}
+
+/**
+ * mnt_fs_set_attributes:
+ * @fs: fstab/mtab/mountinfo entry
+ * @optstr: options string
+ *
+ * Sets mount attributes. The attributes are mount(2) and mount(8) independent
+ * options, these options are not sent to the kernel and are not interpreted by
+ * libmount. The attributes are stored in /run/mount/utab only.
+ *
+ * The attributes are managed by libmount in userspace only. It's possible
+ * that information stored in userspace will not be available for libmount
+ * after CLONE_FS unshare. Be careful, and don't use attributes if possible.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_attributes(struct libmnt_fs *fs, const char *optstr)
+{
+ return strdup_to_struct_member(fs, attrs, optstr);
+}
+
+/**
+ * mnt_fs_append_attributes
+ * @fs: fstab/mtab/mountinfo entry
+ * @optstr: options string
+ *
+ * Appends mount attributes. (See mnt_fs_set_attributes()).
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_append_attributes(struct libmnt_fs *fs, const char *optstr)
+{
+ if (!fs)
+ return -EINVAL;
+ if (!optstr)
+ return 0;
+ return mnt_optstr_append_option(&fs->attrs, optstr, NULL);
+}
+
+/**
+ * mnt_fs_prepend_attributes
+ * @fs: fstab/mtab/mountinfo entry
+ * @optstr: options string
+ *
+ * Prepends mount attributes. (See mnt_fs_set_attributes()).
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_prepend_attributes(struct libmnt_fs *fs, const char *optstr)
+{
+ if (!fs)
+ return -EINVAL;
+ if (!optstr)
+ return 0;
+ return mnt_optstr_prepend_option(&fs->attrs, optstr, NULL);
+}
+
+
+/**
+ * mnt_fs_get_freq:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: dump frequency in days.
+ */
+int mnt_fs_get_freq(struct libmnt_fs *fs)
+{
+ return fs ? fs->freq : 0;
+}
+
+/**
+ * mnt_fs_set_freq:
+ * @fs: fstab/mtab entry pointer
+ * @freq: dump frequency in days
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_freq(struct libmnt_fs *fs, int freq)
+{
+ if (!fs)
+ return -EINVAL;
+ fs->freq = freq;
+ return 0;
+}
+
+/**
+ * mnt_fs_get_passno:
+ * @fs: fstab/mtab entry pointer
+ *
+ * Returns: "pass number on parallel fsck".
+ */
+int mnt_fs_get_passno(struct libmnt_fs *fs)
+{
+ return fs ? fs->passno: 0;
+}
+
+/**
+ * mnt_fs_set_passno:
+ * @fs: fstab/mtab entry pointer
+ * @passno: pass number
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_passno(struct libmnt_fs *fs, int passno)
+{
+ if (!fs)
+ return -EINVAL;
+ fs->passno = passno;
+ return 0;
+}
+
+/**
+ * mnt_fs_get_root:
+ * @fs: /proc/self/mountinfo entry
+ *
+ * Returns: root of the mount within the filesystem or NULL
+ */
+const char *mnt_fs_get_root(struct libmnt_fs *fs)
+{
+ return fs ? fs->root : NULL;
+}
+
+/**
+ * mnt_fs_set_root:
+ * @fs: mountinfo entry
+ * @path: root path
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_root(struct libmnt_fs *fs, const char *path)
+{
+ return strdup_to_struct_member(fs, root, path);
+}
+
+/**
+ * mnt_fs_get_swaptype:
+ * @fs: /proc/swaps entry
+ *
+ * Returns: swap type or NULL
+ */
+const char *mnt_fs_get_swaptype(struct libmnt_fs *fs)
+{
+ return fs ? fs->swaptype : NULL;
+}
+
+/**
+ * mnt_fs_get_size:
+ * @fs: /proc/swaps entry
+ *
+ * Returns: size
+ */
+off_t mnt_fs_get_size(struct libmnt_fs *fs)
+{
+ return fs ? fs->size : 0;
+}
+
+/**
+ * mnt_fs_get_usedsize:
+ * @fs: /proc/swaps entry
+ *
+ * Returns: used size
+ */
+off_t mnt_fs_get_usedsize(struct libmnt_fs *fs)
+{
+ return fs ? fs->usedsize : 0;
+}
+
+/**
+ * mnt_fs_get_priority:
+ * @fs: /proc/swaps entry
+ *
+ * Returns: priority
+ */
+int mnt_fs_get_priority(struct libmnt_fs *fs)
+{
+ return fs ? fs->priority : 0;
+}
+
+/**
+ * mnt_fs_set_priority:
+ * @fs: /proc/swaps entry
+ * @prio: priority
+ *
+ * Since: 2.28
+ *
+ * Returns: 0 or -1 in case of error
+ */
+int mnt_fs_set_priority(struct libmnt_fs *fs, int prio)
+{
+ if (!fs)
+ return -EINVAL;
+ fs->priority = prio;
+ return 0;
+}
+
+/**
+ * mnt_fs_get_bindsrc:
+ * @fs: /run/mount/utab entry
+ *
+ * Returns: full path that was used for mount(2) on MS_BIND
+ */
+const char *mnt_fs_get_bindsrc(struct libmnt_fs *fs)
+{
+ return fs ? fs->bindsrc : NULL;
+}
+
+/**
+ * mnt_fs_set_bindsrc:
+ * @fs: filesystem
+ * @src: path
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src)
+{
+ return strdup_to_struct_member(fs, bindsrc, src);
+}
+
+/**
+ * mnt_fs_get_id:
+ * @fs: /proc/self/mountinfo entry
+ *
+ * Returns: mount ID (unique identifier of the mount) or negative number in case of error.
+ */
+int mnt_fs_get_id(struct libmnt_fs *fs)
+{
+ return fs ? fs->id : -EINVAL;
+}
+
+/**
+ * mnt_fs_get_parent_id:
+ * @fs: /proc/self/mountinfo entry
+ *
+ * Returns: parent mount ID or negative number in case of error.
+ */
+int mnt_fs_get_parent_id(struct libmnt_fs *fs)
+{
+ return fs ? fs->parent : -EINVAL;
+}
+
+/**
+ * mnt_fs_get_devno:
+ * @fs: /proc/self/mountinfo entry
+ *
+ * Returns: value of st_dev for files on filesystem or 0 in case of error.
+ */
+dev_t mnt_fs_get_devno(struct libmnt_fs *fs)
+{
+ return fs ? fs->devno : 0;
+}
+
+/**
+ * mnt_fs_get_tid:
+ * @fs: /proc/tid/mountinfo entry
+ *
+ * Returns: TID (task ID) for filesystems read from the mountinfo file
+ */
+pid_t mnt_fs_get_tid(struct libmnt_fs *fs)
+{
+ return fs ? fs->tid : 0;
+}
+
+/**
+ * mnt_fs_get_option:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ * @name: option name
+ * @value: returns pointer to the beginning of the value (e.g. name=VALUE) or NULL
+ * @valsz: returns size of options value or 0
+ *
+ * Returns: 0 on success, 1 when @name not found or negative number in case of error.
+ */
+int mnt_fs_get_option(struct libmnt_fs *fs, const char *name,
+ char **value, size_t *valsz)
+{
+ char rc = 1;
+
+ if (!fs)
+ return -EINVAL;
+ if (fs->fs_optstr)
+ rc = mnt_optstr_get_option(fs->fs_optstr, name, value, valsz);
+ if (rc == 1 && fs->vfs_optstr)
+ rc = mnt_optstr_get_option(fs->vfs_optstr, name, value, valsz);
+ if (rc == 1 && fs->user_optstr)
+ rc = mnt_optstr_get_option(fs->user_optstr, name, value, valsz);
+ return rc;
+}
+
+/**
+ * mnt_fs_get_attribute:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ * @name: option name
+ * @value: returns pointer to the beginning of the value (e.g. name=VALUE) or NULL
+ * @valsz: returns size of options value or 0
+ *
+ * Returns: 0 on success, 1 when @name not found or negative number in case of error.
+ */
+int mnt_fs_get_attribute(struct libmnt_fs *fs, const char *name,
+ char **value, size_t *valsz)
+{
+ char rc = 1;
+
+ if (!fs)
+ return -EINVAL;
+ if (fs->attrs)
+ rc = mnt_optstr_get_option(fs->attrs, name, value, valsz);
+ return rc;
+}
+
+/**
+ * mnt_fs_get_comment:
+ * @fs: fstab/mtab/mountinfo entry pointer
+ *
+ * Returns: 0 on success, 1 when not found the @name or negative number in case of error.
+ */
+const char *mnt_fs_get_comment(struct libmnt_fs *fs)
+{
+ if (!fs)
+ return NULL;
+ return fs->comment;
+}
+
+/**
+ * mnt_fs_set_comment:
+ * @fs: fstab entry pointer
+ * @comm: comment string
+ *
+ * Note that the comment has to be terminated by '\n' (new line), otherwise
+ * the whole filesystem entry will be written as a comment to the tabfile (e.g.
+ * fstab).
+ *
+ * Returns: 0 on success or <0 in case of error.
+ */
+int mnt_fs_set_comment(struct libmnt_fs *fs, const char *comm)
+{
+ return strdup_to_struct_member(fs, comment, comm);
+}
+
+/**
+ * mnt_fs_append_comment:
+ * @fs: fstab entry pointer
+ * @comm: comment string
+ *
+ * See also mnt_fs_set_comment().
+ *
+ * Returns: 0 on success or <0 in case of error.
+ */
+int mnt_fs_append_comment(struct libmnt_fs *fs, const char *comm)
+{
+ if (!fs)
+ return -EINVAL;
+
+ return strappend(&fs->comment, comm);
+}
+
+/**
+ * mnt_fs_match_target:
+ * @fs: filesystem
+ * @target: mountpoint path
+ * @cache: tags/paths cache or NULL
+ *
+ * Possible are three attempts:
+ * 1) compare @target with @fs->target
+ *
+ * 2) realpath(@target) with @fs->target
+ *
+ * 3) realpath(@target) with realpath(@fs->target) if @fs is not from
+ * /proc/self/mountinfo.
+ *
+ * However, if mnt_cache_set_targets(cache, mtab) was called, and the
+ * path @target or @fs->target is found in the @mtab, the canonicalization is
+ * is not performed (see mnt_resolve_target()).
+ *
+ * The 2nd and 3rd attempts are not performed when @cache is NULL.
+ *
+ * Returns: 1 if @fs target is equal to @target, else 0.
+ */
+int mnt_fs_match_target(struct libmnt_fs *fs, const char *target,
+ struct libmnt_cache *cache)
+{
+ int rc = 0;
+
+ if (!fs || !target || !fs->target)
+ return 0;
+
+ /* 1) native paths */
+ rc = mnt_fs_streq_target(fs, target);
+
+ if (!rc && cache) {
+ /* 2) - canonicalized and non-canonicalized */
+ char *cn = mnt_resolve_target(target, cache);
+ rc = (cn && mnt_fs_streq_target(fs, cn));
+
+ /* 3) - canonicalized and canonicalized */
+ if (!rc && cn && !mnt_fs_is_kernel(fs) && !mnt_fs_is_swaparea(fs)) {
+ char *tcn = mnt_resolve_target(fs->target, cache);
+ rc = (tcn && strcmp(cn, tcn) == 0);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * mnt_fs_match_source:
+ * @fs: filesystem
+ * @source: tag or path (device or so) or NULL
+ * @cache: tags/paths cache or NULL
+ *
+ * Four attempts are possible:
+ * 1) compare @source with @fs->source
+ * 2) compare realpath(@source) with @fs->source
+ * 3) compare realpath(@source) with realpath(@fs->source)
+ * 4) compare realpath(@source) with evaluated tag from @fs->source
+ *
+ * The 2nd, 3rd and 4th attempts are not performed when @cache is NULL. The
+ * 2nd and 3rd attempts are not performed if @fs->source is tag.
+ *
+ * Returns: 1 if @fs source is equal to @source, else 0.
+ */
+int mnt_fs_match_source(struct libmnt_fs *fs, const char *source,
+ struct libmnt_cache *cache)
+{
+ char *cn;
+ const char *src, *t, *v;
+
+ if (!fs)
+ return 0;
+
+ /* 1) native paths... */
+ if (mnt_fs_streq_srcpath(fs, source) == 1)
+ return 1;
+
+ if (!source || !fs->source)
+ return 0;
+
+ /* ... and tags */
+ if (fs->tagname && strcmp(source, fs->source) == 0)
+ return 1;
+
+ if (!cache)
+ return 0;
+ if (fs->flags & (MNT_FS_NET | MNT_FS_PSEUDO))
+ return 0;
+
+ cn = mnt_resolve_spec(source, cache);
+ if (!cn)
+ return 0;
+
+ /* 2) canonicalized and native */
+ src = mnt_fs_get_srcpath(fs);
+ if (src && mnt_fs_streq_srcpath(fs, cn))
+ return 1;
+
+ /* 3) canonicalized and canonicalized */
+ if (src) {
+ src = mnt_resolve_path(src, cache);
+ if (src && !strcmp(cn, src))
+ return 1;
+ }
+ if (src || mnt_fs_get_tag(fs, &t, &v))
+ /* src path does not match and the tag is not defined */
+ return 0;
+
+ /* read @source's tags to the cache */
+ if (mnt_cache_read_tags(cache, cn) < 0) {
+ if (errno == EACCES) {
+ /* we don't have permissions to read TAGs from
+ * @source, but can translate the @fs tag to devname.
+ *
+ * (because libblkid uses udev symlinks and this is
+ * accessible for non-root uses)
+ */
+ char *x = mnt_resolve_tag(t, v, cache);
+ if (x && !strcmp(x, cn))
+ return 1;
+ }
+ return 0;
+ }
+
+ /* 4) has the @source a tag that matches with the tag from @fs ? */
+ if (mnt_cache_device_has_tag(cache, cn, t, v))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * mnt_fs_match_fstype:
+ * @fs: filesystem
+ * @types: filesystem name or comma delimited list of filesystems
+ *
+ * For more details see mnt_match_fstype().
+ *
+ * Returns: 1 if @fs type is matching to @types, else 0. The function returns
+ * 0 when types is NULL.
+ */
+int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types)
+{
+ return mnt_match_fstype(fs->fstype, types);
+}
+
+/**
+ * mnt_fs_match_options:
+ * @fs: filesystem
+ * @options: comma delimited list of options (and nooptions)
+ *
+ * For more details see mnt_match_options().
+ *
+ * Returns: 1 if @fs type is matching to @options, else 0. The function returns
+ * 0 when types is NULL.
+ */
+int mnt_fs_match_options(struct libmnt_fs *fs, const char *options)
+{
+ return mnt_match_options(mnt_fs_get_options(fs), options);
+}
+
+/**
+ * mnt_fs_print_debug
+ * @fs: fstab/mtab/mountinfo entry
+ * @file: file stream
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file)
+{
+ if (!fs || !file)
+ return -EINVAL;
+ fprintf(file, "------ fs:\n");
+ fprintf(file, "source: %s\n", mnt_fs_get_source(fs));
+ fprintf(file, "target: %s\n", mnt_fs_get_target(fs));
+ fprintf(file, "fstype: %s\n", mnt_fs_get_fstype(fs));
+
+ if (mnt_fs_get_options(fs))
+ fprintf(file, "optstr: %s\n", mnt_fs_get_options(fs));
+ if (mnt_fs_get_vfs_options(fs))
+ fprintf(file, "VFS-optstr: %s\n", mnt_fs_get_vfs_options(fs));
+ if (mnt_fs_get_fs_options(fs))
+ fprintf(file, "FS-opstr: %s\n", mnt_fs_get_fs_options(fs));
+ if (mnt_fs_get_user_options(fs))
+ fprintf(file, "user-optstr: %s\n", mnt_fs_get_user_options(fs));
+ if (mnt_fs_get_optional_fields(fs))
+ fprintf(file, "optional-fields: '%s'\n", mnt_fs_get_optional_fields(fs));
+ if (mnt_fs_get_attributes(fs))
+ fprintf(file, "attributes: %s\n", mnt_fs_get_attributes(fs));
+
+ if (mnt_fs_get_root(fs))
+ fprintf(file, "root: %s\n", mnt_fs_get_root(fs));
+
+ if (mnt_fs_get_swaptype(fs))
+ fprintf(file, "swaptype: %s\n", mnt_fs_get_swaptype(fs));
+ if (mnt_fs_get_size(fs))
+ fprintf(file, "size: %jd\n", mnt_fs_get_size(fs));
+ if (mnt_fs_get_usedsize(fs))
+ fprintf(file, "usedsize: %jd\n", mnt_fs_get_usedsize(fs));
+ if (mnt_fs_get_priority(fs))
+ fprintf(file, "priority: %d\n", mnt_fs_get_priority(fs));
+
+ if (mnt_fs_get_bindsrc(fs))
+ fprintf(file, "bindsrc: %s\n", mnt_fs_get_bindsrc(fs));
+ if (mnt_fs_get_freq(fs))
+ fprintf(file, "freq: %d\n", mnt_fs_get_freq(fs));
+ if (mnt_fs_get_passno(fs))
+ fprintf(file, "pass: %d\n", mnt_fs_get_passno(fs));
+ if (mnt_fs_get_id(fs))
+ fprintf(file, "id: %d\n", mnt_fs_get_id(fs));
+ if (mnt_fs_get_parent_id(fs))
+ fprintf(file, "parent: %d\n", mnt_fs_get_parent_id(fs));
+ if (mnt_fs_get_devno(fs))
+ fprintf(file, "devno: %d:%d\n", major(mnt_fs_get_devno(fs)),
+ minor(mnt_fs_get_devno(fs)));
+ if (mnt_fs_get_tid(fs))
+ fprintf(file, "tid: %d\n", mnt_fs_get_tid(fs));
+ if (mnt_fs_get_comment(fs))
+ fprintf(file, "comment: '%s'\n", mnt_fs_get_comment(fs));
+
+ return 0;
+}
+
+/**
+ * mnt_free_mntent:
+ * @mnt: mount entry
+ *
+ * Deallocates the "mntent.h" mount entry.
+ */
+void mnt_free_mntent(struct mntent *mnt)
+{
+ if (mnt) {
+ free(mnt->mnt_fsname);
+ free(mnt->mnt_dir);
+ free(mnt->mnt_type);
+ free(mnt->mnt_opts);
+ free(mnt);
+ }
+}
+
+/**
+ * mnt_fs_to_mntent:
+ * @fs: filesystem
+ * @mnt: mount description (as described in mntent.h)
+ *
+ * Copies the information from @fs to struct mntent @mnt. If @mnt is already set,
+ * then the struct mntent items are reallocated and updated. See also
+ * mnt_free_mntent().
+ *
+ * Returns: 0 on success and a negative number in case of error.
+ */
+int mnt_fs_to_mntent(struct libmnt_fs *fs, struct mntent **mnt)
+{
+ int rc;
+ struct mntent *m;
+
+ if (!fs || !mnt)
+ return -EINVAL;
+
+ m = *mnt;
+ if (!m) {
+ m = calloc(1, sizeof(*m));
+ if (!m)
+ return -ENOMEM;
+ }
+
+ if ((rc = update_str(&m->mnt_fsname, mnt_fs_get_source(fs))))
+ goto err;
+ if ((rc = update_str(&m->mnt_dir, mnt_fs_get_target(fs))))
+ goto err;
+ if ((rc = update_str(&m->mnt_type, mnt_fs_get_fstype(fs))))
+ goto err;
+
+ errno = 0;
+ m->mnt_opts = mnt_fs_strdup_options(fs);
+ if (!m->mnt_opts && errno) {
+ rc = -errno;
+ goto err;
+ }
+
+ m->mnt_freq = mnt_fs_get_freq(fs);
+ m->mnt_passno = mnt_fs_get_passno(fs);
+
+ if (!m->mnt_fsname) {
+ m->mnt_fsname = strdup("none");
+ if (!m->mnt_fsname)
+ goto err;
+ }
+ *mnt = m;
+
+ return 0;
+err:
+ if (m != *mnt)
+ mnt_free_mntent(m);
+ return rc;
+}
diff --git a/libmount/src/fuzz.c b/libmount/src/fuzz.c
new file mode 100644
index 0000000..2c84714
--- /dev/null
+++ b/libmount/src/fuzz.c
@@ -0,0 +1,35 @@
+#include "fuzz.h"
+#include "xalloc.h"
+#include "mountP.h"
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ struct libmnt_table *tb = NULL;
+ FILE *f = NULL;
+
+ if (size == 0)
+ return 0;
+
+ // 128Kb should be enough to trigger all the issues we're interested in
+ if (size > 131072)
+ return 0;
+
+ tb = mnt_new_table();
+ if (!tb)
+ err_oom();
+
+ f = fmemopen((char*) data, size, "re");
+ if (!f)
+ err(EXIT_FAILURE, "fmemopen() failed");
+
+ mnt_table_enable_comments(tb, TRUE);
+ (void) mnt_table_parse_stream(tb, f, "mountinfo");
+
+ mnt_unref_table(tb);
+ fclose(f);
+
+ return 0;
+}
diff --git a/libmount/src/init.c b/libmount/src/init.c
new file mode 100644
index 0000000..2410fcc
--- /dev/null
+++ b/libmount/src/init.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debugging
+ */
+
+#include <stdarg.h>
+
+#include "mountP.h"
+
+UL_DEBUG_DEFINE_MASK(libmount);
+UL_DEBUG_DEFINE_MASKNAMES(libmount) =
+{
+ { "all", MNT_DEBUG_ALL, "info about all subsystems" },
+ { "cache", MNT_DEBUG_CACHE, "paths and tags cache" },
+ { "cxt", MNT_DEBUG_CXT, "library context (handler)" },
+ { "diff", MNT_DEBUG_DIFF, "mountinfo changes tracking" },
+ { "fs", MNT_DEBUG_FS, "FS abstraction" },
+ { "help", MNT_DEBUG_HELP, "this help" },
+ { "locks", MNT_DEBUG_LOCKS, "mtab and utab locking" },
+ { "loop", MNT_DEBUG_LOOP, "loop devices routines" },
+ { "options", MNT_DEBUG_OPTIONS, "mount options parsing" },
+ { "tab", MNT_DEBUG_TAB, "fstab, mtab, mountinfo routines" },
+ { "update", MNT_DEBUG_UPDATE, "mtab, utab updates" },
+ { "utils", MNT_DEBUG_UTILS, "misc library utils" },
+ { "monitor", MNT_DEBUG_MONITOR, "mount tables monitor" },
+ { "btrfs", MNT_DEBUG_BTRFS, "btrfs specific routines" },
+ { "verity", MNT_DEBUG_VERITY, "verity specific routines" },
+
+ { NULL, 0 }
+};
+
+/**
+ * mnt_init_debug:
+ * @mask: debug mask (0xffff to enable full debugging)
+ *
+ * If the @mask is not specified, then this function reads
+ * the LIBMOUNT_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. Calling
+ * this function twice has no effect.
+ */
+void mnt_init_debug(int mask)
+{
+ if (libmount_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(libmount, MNT_DEBUG_, mask, LIBMOUNT_DEBUG);
+
+ if (libmount_debug_mask != MNT_DEBUG_INIT
+ && libmount_debug_mask != (MNT_DEBUG_HELP|MNT_DEBUG_INIT)) {
+ const char *ver = NULL;
+ const char **features = NULL, **p;
+
+ mnt_get_library_version(&ver);
+ mnt_get_library_features(&features);
+
+ DBG(INIT, ul_debug("library debug mask: 0x%04x", libmount_debug_mask));
+ DBG(INIT, ul_debug("library version: %s", ver));
+ p = features;
+ while (p && *p)
+ DBG(INIT, ul_debug(" feature: %s", *p++));
+ }
+
+ ON_DBG(HELP, ul_debug_print_masks("LIBMOUNT_DEBUG",
+ UL_DEBUG_MASKNAMES(libmount)));
+}
+
+#ifdef TEST_PROGRAM
+
+#include <errno.h>
+#include <stdlib.h>
+int main(int argc, char *argv[])
+{
+ if (argc == 2) {
+ int mask;
+
+ errno = 0;
+ mask = strtoul(argv[1], 0, 0);
+
+ if (errno)
+ return 1;
+
+ mnt_init_debug(mask);
+ }
+ else if (argc == 1) {
+ mnt_init_debug(0);
+ } else
+ return 1;
+
+ return 0;
+}
+#endif /* TEST_PROGRAM */
+
diff --git a/libmount/src/iter.c b/libmount/src/iter.c
new file mode 100644
index 0000000..891e2c8
--- /dev/null
+++ b/libmount/src/iter.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: iter
+ * @title: Iterator
+ * @short_description: unified iterator
+ *
+ * The iterator keeps the direction and the last position
+ * for access to the internal library tables/lists.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "mountP.h"
+
+/**
+ * mnt_new_iter:
+ * @direction: MNT_INTER_{FOR,BACK}WARD direction
+ *
+ * Returns: newly allocated generic libmount iterator.
+ */
+struct libmnt_iter *mnt_new_iter(int direction)
+{
+ struct libmnt_iter *itr = calloc(1, sizeof(*itr));
+ if (!itr)
+ return NULL;
+ itr->direction = direction;
+ return itr;
+}
+
+/**
+ * mnt_free_iter:
+ * @itr: iterator pointer
+ *
+ * Deallocates the iterator.
+ */
+void mnt_free_iter(struct libmnt_iter *itr)
+{
+ free(itr);
+}
+
+/**
+ * mnt_reset_iter:
+ * @itr: iterator pointer
+ * @direction: MNT_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged
+ *
+ * Resets the iterator.
+ */
+void mnt_reset_iter(struct libmnt_iter *itr, int direction)
+{
+ if (direction == -1)
+ direction = itr->direction;
+
+ memset(itr, 0, sizeof(*itr));
+ itr->direction = direction;
+}
+
+/**
+ * mnt_iter_get_direction:
+ * @itr: iterator pointer
+ *
+ * Returns: MNT_INTER_{FOR,BACK}WARD
+ */
+int mnt_iter_get_direction(struct libmnt_iter *itr)
+{
+ return itr->direction;
+}
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in
new file mode 100644
index 0000000..3bcf746
--- /dev/null
+++ b/libmount/src/libmount.h.in
@@ -0,0 +1,1021 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * libmount.h - libmount API
+ *
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 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 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.
+ */
+
+#ifndef _LIBMOUNT_MOUNT_H
+#define _LIBMOUNT_MOUNT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/types.h>
+
+/* Make sure libc MS_* definitions are used by default. Note that MS_* flags
+ * may be already defined by linux/fs.h or another file -- in this case we
+ * don't want to include sys/mount.h at all to avoid collisions.
+ */
+#if defined(__linux__) && !defined(MS_RDONLY)
+# include <sys/mount.h>
+#endif
+
+#define LIBMOUNT_VERSION "@LIBMOUNT_VERSION@"
+#define LIBMOUNT_MAJOR_VERSION @LIBMOUNT_MAJOR_VERSION@
+#define LIBMOUNT_MINOR_VERSION @LIBMOUNT_MINOR_VERSION@
+#define LIBMOUNT_PATCH_VERSION @LIBMOUNT_PATCH_VERSION@
+
+/**
+ * libmnt_cache:
+ *
+ * Stores canonicalized paths and evaluated tags
+ */
+struct libmnt_cache;
+
+/**
+ * libmnt_lock:
+ *
+ * Stores information about the locked file (e.g. /etc/mtab)
+ */
+struct libmnt_lock;
+
+/**
+ * libmnt_iter:
+ *
+ * Generic iterator (stores state about lists)
+ */
+struct libmnt_iter;
+
+/**
+ * libmnt_optmap:
+ * @name: option name[=type] where type is printf-like type specifier")
+ * @id: option ID or MS_* flags (e.g MS_RDONLY)
+ * @mask: MNT_{NOMTAB,INVERT,...} mask
+ *
+ * Mount options description (map)
+ */
+struct libmnt_optmap
+{
+ const char *name;
+ int id;
+ int mask;
+};
+
+/*
+ * mount options map masks
+ */
+#define MNT_INVERT (1 << 1) /* invert the mountflag */
+#define MNT_NOMTAB (1 << 2) /* skip in the mtab option string */
+#define MNT_PREFIX (1 << 3) /* prefix used for some options (e.g. "x-foo") */
+#define MNT_NOHLPS (1 << 4) /* don't add the option to mount.<type> helpers command line */
+
+/**
+ * libmnt_fs:
+ *
+ * Parsed fstab/mtab/mountinfo entry
+ */
+struct libmnt_fs;
+
+/**
+ * libmnt_table:
+ *
+ * List of struct libmnt_fs entries (parsed fstab/mtab/mountinfo)
+ */
+struct libmnt_table;
+
+/**
+ * libmnt_update
+ *
+ * /etc/mtab or utab update description
+ */
+struct libmnt_update;
+
+/**
+ * libmnt_context
+ *
+ * Mount/umount status
+ */
+struct libmnt_context;
+
+/**
+ * libmnt_monitor
+ *
+ * Mount tables monitor
+ */
+struct libmnt_monitor;
+
+/**
+ * libmnt_tabdiff:
+ *
+ * Stores mountinfo state
+ */
+struct libmnt_tabdiff;
+
+/**
+ * libmnt_ns:
+ *
+ * Describes mount namespace
+ */
+struct libmnt_ns;
+
+/*
+ * Actions
+ */
+enum {
+ MNT_ACT_MOUNT = 1,
+ MNT_ACT_UMOUNT
+};
+
+/*
+ * Errors -- by default libmount returns -errno for generic errors (ENOMEM,
+ * EINVAL, ...) and for mount(2) errors, but for some specific operations it
+ * returns private error codes. Note that maximum system errno value should be
+ * 4095 on UNIXes.
+ *
+ * See also mnt_context_get_syscall_errno() and mnt_context_get_helper_status().
+ */
+/**
+ * MNT_ERR_NOFSTAB:
+ *
+ * not found required entry in fstab
+ */
+#define MNT_ERR_NOFSTAB 5000
+/**
+ * MNT_ERR_NOFSTYPE:
+ *
+ * failed to detect filesystem type
+ */
+#define MNT_ERR_NOFSTYPE 5001
+/**
+ * MNT_ERR_NOSOURCE:
+ *
+ * required mount source undefined
+ */
+#define MNT_ERR_NOSOURCE 5002
+/**
+ * MNT_ERR_LOOPDEV:
+ *
+ * loopdev setup failed, errno set by libc
+ */
+#define MNT_ERR_LOOPDEV 5003
+/**
+ * MNT_ERR_MOUNTOPT:
+ *
+ * failed to parse/use userspace mount options
+ */
+#define MNT_ERR_MOUNTOPT 5004
+/**
+ * MNT_ERR_APPLYFLAGS:
+ *
+ * failed to apply MS_PROPAGATION flags
+ */
+#define MNT_ERR_APPLYFLAGS 5005
+/**
+ * MNT_ERR_AMBIFS:
+ *
+ * libblkid detected more filesystems on the device
+ */
+#define MNT_ERR_AMBIFS 5006
+/**
+ * MNT_ERR_LOOPOVERLAP:
+ *
+ * detected overlapping loop device that cannot be re-used
+ */
+#define MNT_ERR_LOOPOVERLAP 5007
+/**
+ * MNT_ERR_LOCK:
+ *
+ * failed to lock mtab/utab or so.
+ */
+#define MNT_ERR_LOCK 5008
+/**
+ * MNT_ERR_NAMESPACE:
+ *
+ * failed to switch namespace
+ */
+#define MNT_ERR_NAMESPACE 5009
+
+
+/*
+ * Overall return codes -- based on mount(8) and umount(8) return codes.
+ * See mnt_context_get_excode() for more details.
+ */
+
+/**
+ * MNT_EX_SUCCESS:
+ *
+ * [u]mount(8) exit code: no errors
+ */
+#define MNT_EX_SUCCESS 0
+
+/**
+ * MNT_EX_USAGE:
+ *
+ * [u]mount(8) exit code: incorrect invocation or permission
+ */
+#define MNT_EX_USAGE 1
+
+/**
+ * MNT_EX_SYSERR:
+ *
+ * [u]mount(8) exit code: out of memory, cannot fork, ...
+ */
+
+#define MNT_EX_SYSERR 2
+
+/**
+ * MNT_EX_SOFTWARE:
+ *
+ * [u]mount(8) exit code: internal mount bug or wrong version
+ */
+#define MNT_EX_SOFTWARE 4
+
+/**
+ * MNT_EX_USER:
+ *
+ * [u]mount(8) exit code: user interrupt
+ */
+#define MNT_EX_USER 8
+
+/**
+ * MNT_EX_FILEIO:
+ *
+ * [u]mount(8) exit code: problems writing, locking, ... mtab/utab
+ */
+#define MNT_EX_FILEIO 16
+
+/**
+ * MNT_EX_FAIL:
+ *
+ * [u]mount(8) exit code: mount failure
+ */
+#define MNT_EX_FAIL 32
+
+/**
+ * MNT_EX_SOMEOK:
+ *
+ * [u]mount(8) exit code: some mount succeeded; usually when executed with
+ * --all options. Never returned by libmount.
+ */
+#define MNT_EX_SOMEOK 64
+
+
+
+#ifndef __GNUC_PREREQ
+# if defined __GNUC__ && defined __GNUC_MINOR__
+# define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+# else
+# define __GNUC_PREREQ(maj, min) 0
+# endif
+#endif
+
+#ifndef __ul_attribute__
+# if __GNUC_PREREQ (3, 4)
+# define __ul_attribute__(_a_) __attribute__(_a_)
+# else
+# define __ul_attribute__(_a_)
+# endif
+#endif
+
+
+/* init.c */
+extern void mnt_init_debug(int mask);
+
+/* version.c */
+extern int mnt_parse_version_string(const char *ver_string);
+extern int mnt_get_library_version(const char **ver_string);
+extern int mnt_get_library_features(const char ***features);
+
+/* utils.c */
+extern char *mnt_mangle(const char *str)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_unmangle(const char *str)
+ __ul_attribute__((warn_unused_result));
+
+extern int mnt_tag_is_valid(const char *tag);
+extern int mnt_fstype_is_netfs(const char *type);
+extern int mnt_fstype_is_pseudofs(const char *type);
+
+extern int mnt_match_fstype(const char *type, const char *pattern)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_match_options(const char *optstr, const char *pattern)
+ __ul_attribute__((warn_unused_result));
+extern const char *mnt_get_fstab_path(void);
+extern const char *mnt_get_swaps_path(void);
+extern const char *mnt_get_mtab_path(void);
+extern int mnt_has_regular_mtab(const char **mtab, int *writable);
+extern char *mnt_get_mountpoint(const char *path)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path)
+ __ul_attribute__((nonnull(3)));
+
+/* cache.c */
+extern struct libmnt_cache *mnt_new_cache(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_cache(struct libmnt_cache *cache);
+
+extern void mnt_ref_cache(struct libmnt_cache *cache);
+extern void mnt_unref_cache(struct libmnt_cache *cache);
+
+extern int mnt_cache_set_targets(struct libmnt_cache *cache,
+ struct libmnt_table *mtab);
+extern int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname);
+
+extern int mnt_cache_device_has_tag(struct libmnt_cache *cache,
+ const char *devname,
+ const char *token,
+ const char *value);
+
+extern char *mnt_cache_find_tag_value(struct libmnt_cache *cache,
+ const char *devname, const char *token);
+
+extern char *mnt_get_fstype(const char *devname, int *ambi,
+ struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_resolve_path(const char *path, struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_resolve_target(const char *path, struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_resolve_tag(const char *token, const char *value,
+ struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+extern char *mnt_pretty_path(const char *path, struct libmnt_cache *cache)
+ __ul_attribute__((warn_unused_result));
+
+/* optstr.c */
+extern int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valuesz);
+extern int mnt_optstr_append_option(char **optstr, const char *name,
+ const char *value);
+extern int mnt_optstr_prepend_option(char **optstr, const char *name,
+ const char *value);
+
+extern int mnt_optstr_get_option(const char *optstr, const char *name,
+ char **value, size_t *valsz);
+extern int mnt_optstr_set_option(char **optstr, const char *name,
+ const char *value);
+extern int mnt_optstr_remove_option(char **optstr, const char *name);
+extern int mnt_optstr_deduplicate_option(char **optstr, const char *name);
+
+extern int mnt_split_optstr(const char *optstr,
+ char **user, char **vfs, char **fs,
+ int ignore_user, int ignore_vfs);
+
+extern int mnt_optstr_get_options(const char *optstr, char **subset,
+ const struct libmnt_optmap *map, int ignore);
+
+extern int mnt_optstr_get_flags(const char *optstr, unsigned long *flags,
+ const struct libmnt_optmap *map);
+
+extern int mnt_optstr_apply_flags(char **optstr, unsigned long flags,
+ const struct libmnt_optmap *map);
+
+/* iter.c */
+enum {
+
+ MNT_ITER_FORWARD = 0,
+ MNT_ITER_BACKWARD
+};
+extern struct libmnt_iter *mnt_new_iter(int direction)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_iter(struct libmnt_iter *itr);
+
+extern void mnt_reset_iter(struct libmnt_iter *itr, int direction)
+ __ul_attribute__((nonnull));
+extern int mnt_iter_get_direction(struct libmnt_iter *itr)
+ __ul_attribute__((nonnull));
+
+/* optmap.c */
+enum {
+ MNT_LINUX_MAP = 1,
+ MNT_USERSPACE_MAP
+};
+extern const struct libmnt_optmap *mnt_get_builtin_optmap(int id);
+
+/* lock.c */
+extern struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_lock(struct libmnt_lock *ml);
+
+extern void mnt_unlock_file(struct libmnt_lock *ml);
+extern int mnt_lock_file(struct libmnt_lock *ml);
+extern int mnt_lock_block_signals(struct libmnt_lock *ml, int enable);
+
+/* fs.c */
+extern struct libmnt_fs *mnt_new_fs(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_fs(struct libmnt_fs *fs);
+extern void mnt_ref_fs(struct libmnt_fs *fs);
+extern void mnt_unref_fs(struct libmnt_fs *fs);
+
+extern void mnt_reset_fs(struct libmnt_fs *fs);
+extern struct libmnt_fs *mnt_copy_fs(struct libmnt_fs *dest,
+ const struct libmnt_fs *src)
+ __ul_attribute__((warn_unused_result));
+extern void *mnt_fs_get_userdata(struct libmnt_fs *fs);
+extern int mnt_fs_set_userdata(struct libmnt_fs *fs, void *data);
+extern const char *mnt_fs_get_source(struct libmnt_fs *fs);
+extern int mnt_fs_set_source(struct libmnt_fs *fs, const char *source);
+extern const char *mnt_fs_get_srcpath(struct libmnt_fs *fs);
+extern int mnt_fs_get_table(struct libmnt_fs *fs, struct libmnt_table **tb);
+
+extern int mnt_fs_get_tag(struct libmnt_fs *fs, const char **name,
+ const char **value);
+extern const char *mnt_fs_get_target(struct libmnt_fs *fs);
+extern int mnt_fs_set_target(struct libmnt_fs *fs, const char *tgt);
+extern const char *mnt_fs_get_fstype(struct libmnt_fs *fs);
+extern int mnt_fs_set_fstype(struct libmnt_fs *fs, const char *fstype);
+
+extern int mnt_fs_streq_srcpath(struct libmnt_fs *fs, const char *path)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_fs_streq_target(struct libmnt_fs *fs, const char *path)
+ __ul_attribute__((warn_unused_result));
+
+extern char *mnt_fs_strdup_options(struct libmnt_fs *fs)
+ __ul_attribute__((warn_unused_result));
+extern const char *mnt_fs_get_options(struct libmnt_fs *fs)
+ __ul_attribute__((warn_unused_result));
+extern const char *mnt_fs_get_optional_fields(struct libmnt_fs *fs)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_fs_get_propagation(struct libmnt_fs *fs, unsigned long *flags);
+
+extern int mnt_fs_set_options(struct libmnt_fs *fs, const char *optstr);
+extern int mnt_fs_append_options(struct libmnt_fs *fs, const char *optstr);
+extern int mnt_fs_prepend_options(struct libmnt_fs *fs, const char *optstr);
+
+extern int mnt_fs_get_option(struct libmnt_fs *fs, const char *name,
+ char **value, size_t *valsz);
+
+extern const char *mnt_fs_get_fs_options(struct libmnt_fs *fs);
+extern const char *mnt_fs_get_vfs_options(struct libmnt_fs *fs);
+extern const char *mnt_fs_get_user_options(struct libmnt_fs *fs);
+extern char *mnt_fs_get_vfs_options_all(struct libmnt_fs *fs);
+
+extern const char *mnt_fs_get_attributes(struct libmnt_fs *fs);
+extern int mnt_fs_set_attributes(struct libmnt_fs *fs, const char *optstr);
+extern int mnt_fs_get_attribute(struct libmnt_fs *fs, const char *name,
+ char **value, size_t *valsz);
+extern int mnt_fs_append_attributes(struct libmnt_fs *fs, const char *optstr);
+extern int mnt_fs_prepend_attributes(struct libmnt_fs *fs, const char *optstr);
+
+extern int mnt_fs_get_freq(struct libmnt_fs *fs);
+extern int mnt_fs_set_freq(struct libmnt_fs *fs, int freq);
+extern int mnt_fs_get_passno(struct libmnt_fs *fs);
+extern int mnt_fs_set_passno(struct libmnt_fs *fs, int passno);
+extern const char *mnt_fs_get_root(struct libmnt_fs *fs);
+extern int mnt_fs_set_root(struct libmnt_fs *fs, const char *path);
+extern const char *mnt_fs_get_bindsrc(struct libmnt_fs *fs);
+extern int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src);
+extern int mnt_fs_get_id(struct libmnt_fs *fs);
+extern int mnt_fs_get_parent_id(struct libmnt_fs *fs);
+extern dev_t mnt_fs_get_devno(struct libmnt_fs *fs);
+extern pid_t mnt_fs_get_tid(struct libmnt_fs *fs);
+
+extern const char *mnt_fs_get_swaptype(struct libmnt_fs *fs);
+extern off_t mnt_fs_get_size(struct libmnt_fs *fs);
+extern off_t mnt_fs_get_usedsize(struct libmnt_fs *fs);
+extern int mnt_fs_get_priority(struct libmnt_fs *fs);
+extern int mnt_fs_set_priority(struct libmnt_fs *fs, int prio);
+
+extern const char *mnt_fs_get_comment(struct libmnt_fs *fs);
+extern int mnt_fs_set_comment(struct libmnt_fs *fs, const char *comm);
+extern int mnt_fs_append_comment(struct libmnt_fs *fs, const char *comm);
+
+extern int mnt_fs_match_target(struct libmnt_fs *fs, const char *target,
+ struct libmnt_cache *cache);
+extern int mnt_fs_match_source(struct libmnt_fs *fs, const char *source,
+ struct libmnt_cache *cache);
+extern int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types);
+extern int mnt_fs_match_options(struct libmnt_fs *fs, const char *options);
+extern int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file);
+
+extern int mnt_fs_is_kernel(struct libmnt_fs *fs);
+extern int mnt_fs_is_swaparea(struct libmnt_fs *fs);
+extern int mnt_fs_is_netfs(struct libmnt_fs *fs);
+extern int mnt_fs_is_pseudofs(struct libmnt_fs *fs);
+extern int mnt_fs_is_regularfs(struct libmnt_fs *fs);
+
+extern void mnt_free_mntent(struct mntent *mnt);
+extern int mnt_fs_to_mntent(struct libmnt_fs *fs, struct mntent **mnt);
+
+/* tab-parse.c */
+extern struct libmnt_table *mnt_new_table_from_file(const char *filename)
+ __ul_attribute__((warn_unused_result));
+extern struct libmnt_table *mnt_new_table_from_dir(const char *dirname)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f,
+ const char *filename);
+extern int mnt_table_parse_file(struct libmnt_table *tb, const char *filename);
+extern int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname);
+
+extern int mnt_table_parse_fstab(struct libmnt_table *tb, const char *filename);
+extern int mnt_table_parse_swaps(struct libmnt_table *tb, const char *filename);
+extern int mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename);
+extern int mnt_table_set_parser_errcb(struct libmnt_table *tb,
+ int (*cb)(struct libmnt_table *tb, const char *filename, int line));
+
+/* tab.c */
+extern struct libmnt_table *mnt_new_table(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_table(struct libmnt_table *tb);
+
+extern void mnt_ref_table(struct libmnt_table *tb);
+extern void mnt_unref_table(struct libmnt_table *tb);
+
+extern int mnt_reset_table(struct libmnt_table *tb);
+extern int mnt_table_get_nents(struct libmnt_table *tb);
+extern int mnt_table_is_empty(struct libmnt_table *tb);
+
+extern int mnt_table_set_userdata(struct libmnt_table *tb, void *data);
+extern void *mnt_table_get_userdata(struct libmnt_table *tb);
+
+extern void mnt_table_enable_comments(struct libmnt_table *tb, int enable);
+extern int mnt_table_with_comments(struct libmnt_table *tb);
+extern const char *mnt_table_get_intro_comment(struct libmnt_table *tb);
+extern int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm);
+extern int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm);
+extern int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm);
+extern const char *mnt_table_get_trailing_comment(struct libmnt_table *tb);
+extern int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm);
+
+extern int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc);
+extern struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb);
+extern int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs);
+extern int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs);
+extern int mnt_table_insert_fs(struct libmnt_table *tb, int before,
+ struct libmnt_fs *pos, struct libmnt_fs *fs);
+extern int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst,
+ int before, struct libmnt_fs *pos, struct libmnt_fs *fs);
+extern int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs);
+extern int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs);
+extern int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs);
+extern int mnt_table_over_fs(struct libmnt_table *tb, struct libmnt_fs *parent,
+ struct libmnt_fs **child);
+extern int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
+ struct libmnt_fs **fs);
+extern int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
+ struct libmnt_fs *parent, struct libmnt_fs **chld);
+extern int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root);
+extern int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr,
+ struct libmnt_fs *fs);
+
+enum {
+ MNT_UNIQ_FORWARD = (1 << 1), /* default is backward */
+ MNT_UNIQ_KEEPTREE = (1 << 2)
+};
+extern int mnt_table_uniq_fs(struct libmnt_table *tb, int flags,
+ int (*cmp)(struct libmnt_table *,
+ struct libmnt_fs *,
+ struct libmnt_fs *));
+
+extern struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
+ const char *path, int direction);
+extern struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb,
+ const char *path, int direction);
+extern struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb,
+ const char *path, int direction);
+extern struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
+ const char *val, int direction);
+extern struct libmnt_fs *mnt_table_find_target_with_option(struct libmnt_table *tb, const char *path,
+ const char *option, const char *val, int direction);
+extern struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb,
+ const char *source, int direction);
+extern struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb,
+ const char *source,
+ const char *target, int direction);
+extern struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
+ dev_t devno, int direction);
+
+extern int mnt_table_find_next_fs(struct libmnt_table *tb,
+ struct libmnt_iter *itr,
+ int (*match_func)(struct libmnt_fs *, void *),
+ void *userdata,
+ struct libmnt_fs **fs);
+
+extern int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs);
+
+/* tab_update.c */
+extern struct libmnt_update *mnt_new_update(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_update(struct libmnt_update *upd);
+
+extern int mnt_table_replace_file(struct libmnt_table *tb, const char *filename);
+extern int mnt_table_write_file(struct libmnt_table *tb, FILE *file);
+
+extern int mnt_update_is_ready(struct libmnt_update *upd);
+extern int mnt_update_set_fs(struct libmnt_update *upd, unsigned long mountflags,
+ const char *target, struct libmnt_fs *fs);
+extern int mnt_update_table(struct libmnt_update *upd, struct libmnt_lock *lc);
+extern unsigned long mnt_update_get_mflags(struct libmnt_update *upd);
+extern int mnt_update_force_rdonly(struct libmnt_update *upd, int rdonly);
+extern const char *mnt_update_get_filename(struct libmnt_update *upd);
+extern struct libmnt_fs *mnt_update_get_fs(struct libmnt_update *upd);
+
+/* tab_diff.c */
+enum {
+ MNT_TABDIFF_MOUNT = 1,
+ MNT_TABDIFF_UMOUNT,
+ MNT_TABDIFF_MOVE,
+ MNT_TABDIFF_REMOUNT,
+ MNT_TABDIFF_PROPAGATION, /* not implemented yet (TODO) */
+};
+
+extern struct libmnt_tabdiff *mnt_new_tabdiff(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_tabdiff(struct libmnt_tabdiff *df);
+
+extern int mnt_diff_tables(struct libmnt_tabdiff *df,
+ struct libmnt_table *old_tab,
+ struct libmnt_table *new_tab);
+
+extern int mnt_tabdiff_next_change(struct libmnt_tabdiff *df,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **old_fs,
+ struct libmnt_fs **new_fs,
+ int *oper);
+
+/* monitor.c */
+enum {
+ MNT_MONITOR_TYPE_USERSPACE = 1, /* userspace mount options */
+ MNT_MONITOR_TYPE_KERNEL /* kernel mount table */
+};
+
+extern struct libmnt_monitor *mnt_new_monitor(void);
+extern void mnt_ref_monitor(struct libmnt_monitor *mn);
+extern void mnt_unref_monitor(struct libmnt_monitor *mn);
+
+extern int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable);
+extern int mnt_monitor_enable_userspace(struct libmnt_monitor *mn,
+ int enable, const char *filename);
+
+extern int mnt_monitor_get_fd(struct libmnt_monitor *mn);
+extern int mnt_monitor_close_fd(struct libmnt_monitor *mn);
+extern int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout);
+
+extern int mnt_monitor_next_change(struct libmnt_monitor *mn,
+ const char **filename, int *type);
+extern int mnt_monitor_event_cleanup(struct libmnt_monitor *mn);
+
+
+/* context.c */
+
+/*
+ * Mode for mount options from fstab (or mtab), see mnt_context_set_optsmode().
+ */
+enum {
+ MNT_OMODE_IGNORE = (1 << 1), /* ignore mtab/fstab options */
+ MNT_OMODE_APPEND = (1 << 2), /* append mtab/fstab options to existing options */
+ MNT_OMODE_PREPEND = (1 << 3), /* prepend mtab/fstab options to existing options */
+ MNT_OMODE_REPLACE = (1 << 4), /* replace existing options with options from mtab/fstab */
+
+ MNT_OMODE_FORCE = (1 << 5), /* always read mtab/fstab options */
+
+ MNT_OMODE_FSTAB = (1 << 10), /* read from fstab */
+ MNT_OMODE_MTAB = (1 << 11), /* read from mtab if fstab not enabled or failed */
+ MNT_OMODE_NOTAB = (1 << 12), /* do not read fstab/mtab at all */
+
+ /* default */
+ MNT_OMODE_AUTO = (MNT_OMODE_PREPEND | MNT_OMODE_FSTAB | MNT_OMODE_MTAB),
+ /* non-root users */
+ MNT_OMODE_USER = (MNT_OMODE_REPLACE | MNT_OMODE_FORCE | MNT_OMODE_FSTAB)
+};
+
+extern struct libmnt_context *mnt_new_context(void)
+ __ul_attribute__((warn_unused_result));
+extern void mnt_free_context(struct libmnt_context *cxt);
+
+extern int mnt_reset_context(struct libmnt_context *cxt);
+extern int mnt_context_is_restricted(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_force_unrestricted(struct libmnt_context *cxt);
+
+extern int mnt_context_init_helper(struct libmnt_context *cxt,
+ int action, int flags);
+extern int mnt_context_helper_setopt(struct libmnt_context *cxt, int c, char *arg);
+
+extern int mnt_context_set_optsmode(struct libmnt_context *cxt, int mode);
+extern int mnt_context_disable_canonicalize(struct libmnt_context *cxt, int disable);
+extern int mnt_context_enable_lazy(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_rdonly_umount(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_rwonly_mount(struct libmnt_context *cxt, int enable);
+extern int mnt_context_disable_helpers(struct libmnt_context *cxt, int disable);
+extern int mnt_context_enable_sloppy(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_fake(struct libmnt_context *cxt, int enable);
+extern int mnt_context_disable_mtab(struct libmnt_context *cxt, int disable);
+extern int mnt_context_enable_force(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_verbose(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_loopdel(struct libmnt_context *cxt, int enable);
+extern int mnt_context_enable_fork(struct libmnt_context *cxt, int enable);
+extern int mnt_context_disable_swapmatch(struct libmnt_context *cxt, int disable);
+
+extern int mnt_context_get_optsmode(struct libmnt_context *cxt);
+
+extern int mnt_context_is_lazy(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_rdonly_umount(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_rwonly_mount(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_sloppy(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_fake(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_nomtab(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_force(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_verbose(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_loopdel(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_nohelpers(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_nocanonicalize(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_swapmatch(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_forced_rdonly(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+
+extern int mnt_context_is_fork(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_parent(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+extern int mnt_context_is_child(struct libmnt_context *cxt)
+ __ul_attribute__((nonnull));
+
+extern int mnt_context_wait_for_children(struct libmnt_context *cxt,
+ int *nchildren, int *nerrs);
+
+extern int mnt_context_is_fs_mounted(struct libmnt_context *cxt,
+ struct libmnt_fs *fs, int *mounted);
+extern int mnt_context_set_fs(struct libmnt_context *cxt, struct libmnt_fs *fs);
+extern struct libmnt_fs *mnt_context_get_fs(struct libmnt_context *cxt);
+
+extern int mnt_context_set_source(struct libmnt_context *cxt, const char *source);
+extern int mnt_context_set_target(struct libmnt_context *cxt, const char *target);
+extern int mnt_context_set_fstype(struct libmnt_context *cxt, const char *fstype);
+extern int mnt_context_set_target_prefix(struct libmnt_context *cxt, const char *path);
+
+extern const char *mnt_context_get_source(struct libmnt_context *cxt);
+extern const char *mnt_context_get_target(struct libmnt_context *cxt);
+extern const char *mnt_context_get_fstype(struct libmnt_context *cxt);
+extern const char *mnt_context_get_target_prefix(struct libmnt_context *cxt);
+
+extern void *mnt_context_get_mtab_userdata(struct libmnt_context *cxt);
+extern void *mnt_context_get_fstab_userdata(struct libmnt_context *cxt);
+extern void *mnt_context_get_fs_userdata(struct libmnt_context *cxt);
+
+extern int mnt_context_set_options(struct libmnt_context *cxt, const char *optstr);
+extern int mnt_context_append_options(struct libmnt_context *cxt, const char *optstr);
+
+extern const char *mnt_context_get_options(struct libmnt_context *cxt);
+
+extern int mnt_context_set_fstype_pattern(struct libmnt_context *cxt, const char *pattern);
+extern int mnt_context_set_options_pattern(struct libmnt_context *cxt, const char *pattern);
+
+extern int mnt_context_set_passwd_cb(struct libmnt_context *cxt,
+ char *(*get)(struct libmnt_context *),
+ void (*release)(struct libmnt_context *, char *))
+ __ul_attribute__((deprecated));
+
+extern int mnt_context_set_tables_errcb(struct libmnt_context *cxt,
+ int (*cb)(struct libmnt_table *tb, const char *filename, int line));
+extern int mnt_context_set_fstab(struct libmnt_context *cxt,
+ struct libmnt_table *tb);
+extern int mnt_context_get_fstab(struct libmnt_context *cxt,
+ struct libmnt_table **tb);
+
+extern int mnt_context_get_mtab(struct libmnt_context *cxt,
+ struct libmnt_table **tb);
+extern int mnt_context_get_table(struct libmnt_context *cxt,
+ const char *filename,
+ struct libmnt_table **tb);
+extern int mnt_context_set_cache(struct libmnt_context *cxt,
+ struct libmnt_cache *cache);
+extern struct libmnt_cache *mnt_context_get_cache(struct libmnt_context *cxt);
+extern struct libmnt_lock *mnt_context_get_lock(struct libmnt_context *cxt);
+extern int mnt_context_set_mflags(struct libmnt_context *cxt,
+ unsigned long flags);
+extern int mnt_context_get_mflags(struct libmnt_context *cxt,
+ unsigned long *flags);
+extern int mnt_context_set_user_mflags(struct libmnt_context *cxt,
+ unsigned long flags);
+extern int mnt_context_get_user_mflags(struct libmnt_context *cxt,
+ unsigned long *flags);
+
+extern int mnt_context_set_mountdata(struct libmnt_context *cxt, void *data);
+extern int mnt_context_apply_fstab(struct libmnt_context *cxt);
+
+extern int mnt_context_reset_status(struct libmnt_context *cxt);
+extern int mnt_context_get_status(struct libmnt_context *cxt);
+
+extern int mnt_context_helper_executed(struct libmnt_context *cxt);
+extern int mnt_context_get_helper_status(struct libmnt_context *cxt);
+
+extern int mnt_context_syscall_called(struct libmnt_context *cxt);
+
+extern int mnt_context_get_syscall_errno(struct libmnt_context *cxt);
+
+extern int mnt_context_strerror(struct libmnt_context *cxt, char *buf,
+ size_t bufsiz)
+ __ul_attribute__((deprecated));
+
+extern int mnt_context_get_excode(struct libmnt_context *cxt,
+ int rc, char *buf, size_t bufsz);
+
+extern int mnt_context_set_target_ns(struct libmnt_context *cxt, const char *path);
+extern struct libmnt_ns *mnt_context_get_target_ns(struct libmnt_context *cxt);
+extern struct libmnt_ns *mnt_context_get_origin_ns(struct libmnt_context *cxt);
+extern struct libmnt_ns *mnt_context_switch_ns(struct libmnt_context *cxt, struct libmnt_ns *ns);
+extern struct libmnt_ns *mnt_context_switch_origin_ns(struct libmnt_context *cxt);
+extern struct libmnt_ns *mnt_context_switch_target_ns(struct libmnt_context *cxt);
+
+
+/* context_mount.c */
+extern int mnt_context_mount(struct libmnt_context *cxt);
+extern int mnt_context_umount(struct libmnt_context *cxt);
+extern int mnt_context_next_mount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc, int *ignored);
+
+extern int mnt_context_next_remount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc,
+ int *ignored);
+
+extern int mnt_context_prepare_mount(struct libmnt_context *cxt)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_context_do_mount(struct libmnt_context *cxt);
+extern int mnt_context_finalize_mount(struct libmnt_context *cxt);
+
+/* context_umount.c */
+extern int mnt_context_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs);
+extern int mnt_context_next_umount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc, int *ignored);
+
+extern int mnt_context_prepare_umount(struct libmnt_context *cxt)
+ __ul_attribute__((warn_unused_result));
+extern int mnt_context_do_umount(struct libmnt_context *cxt);
+extern int mnt_context_finalize_umount(struct libmnt_context *cxt);
+
+extern int mnt_context_tab_applied(struct libmnt_context *cxt);
+extern int mnt_context_set_syscall_status(struct libmnt_context *cxt, int status);
+
+/*
+ * mount(8) userspace options masks (MNT_MAP_USERSPACE map)
+ */
+#define MNT_MS_NOAUTO (1 << 2)
+#define MNT_MS_USER (1 << 3)
+#define MNT_MS_USERS (1 << 4)
+#define MNT_MS_OWNER (1 << 5)
+#define MNT_MS_GROUP (1 << 6)
+#define MNT_MS_NETDEV (1 << 7)
+#define MNT_MS_COMMENT (1 << 8)
+#define MNT_MS_LOOP (1 << 9)
+#define MNT_MS_NOFAIL (1 << 10)
+#define MNT_MS_UHELPER (1 << 11)
+#define MNT_MS_HELPER (1 << 12)
+#define MNT_MS_XCOMMENT (1 << 13)
+#define MNT_MS_OFFSET (1 << 14)
+#define MNT_MS_SIZELIMIT (1 << 15)
+#define MNT_MS_ENCRYPTION (1 << 16)
+#define MNT_MS_XFSTABCOMM (1 << 17)
+#define MNT_MS_HASH_DEVICE (1 << 18)
+#define MNT_MS_ROOT_HASH (1 << 19)
+#define MNT_MS_HASH_OFFSET (1 << 20)
+#define MNT_MS_ROOT_HASH_FILE (1 << 21)
+#define MNT_MS_FEC_DEVICE (1 << 22)
+#define MNT_MS_FEC_OFFSET (1 << 23)
+#define MNT_MS_FEC_ROOTS (1 << 24)
+#define MNT_MS_ROOT_HASH_SIG (1 << 25)
+#define MNT_MS_VERITY_ON_CORRUPTION (1 << 26)
+
+/*
+ * mount(2) MS_* masks (MNT_MAP_LINUX map)
+ */
+#ifndef MS_RDONLY
+#define MS_RDONLY 1 /* Mount read-only */
+#endif
+#ifndef MS_NOSUID
+#define MS_NOSUID 2 /* Ignore suid and sgid bits */
+#endif
+#ifndef MS_NODEV
+#define MS_NODEV 4 /* Disallow access to device special files */
+#endif
+#ifndef MS_NOEXEC
+#define MS_NOEXEC 8 /* Disallow program execution */
+#endif
+#ifndef MS_SYNCHRONOUS
+#define MS_SYNCHRONOUS 16 /* Writes are synced at once */
+#endif
+#ifndef MS_REMOUNT
+#define MS_REMOUNT 32 /* Alter flags of a mounted FS */
+#endif
+#ifndef MS_MANDLOCK
+#define MS_MANDLOCK 64 /* Allow mandatory locks on an FS */
+#endif
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC 128 /* Directory modifications are synchronous */
+#endif
+#ifndef MS_NOSYMFOLLOW
+#define MS_NOSYMFOLLOW 256 /* Don't follow symlinks */
+#endif
+#ifndef MS_NOATIME
+#define MS_NOATIME 0x400 /* 1024: Do not update access times. */
+#endif
+#ifndef MS_NODIRATIME
+#define MS_NODIRATIME 0x800 /* 2048: Don't update directory access times */
+#endif
+#ifndef MS_BIND
+#define MS_BIND 0x1000 /* 4096: Mount existing tree elsewhere as well */
+#endif
+#ifndef MS_MOVE
+#define MS_MOVE 0x2000 /* 8192: Atomically move the tree */
+#endif
+#ifndef MS_REC
+#define MS_REC 0x4000 /* 16384: Recursive loopback */
+#endif
+#ifndef MS_SILENT
+#define MS_SILENT 0x8000 /* 32768: Don't emit certain kernel messages */
+#endif
+#ifndef MS_UNBINDABLE
+#define MS_UNBINDABLE (1<<17) /* 131072: Make unbindable */
+#endif
+#ifndef MS_PRIVATE
+#define MS_PRIVATE (1<<18) /* 262144: Make private */
+#endif
+#ifndef MS_SLAVE
+#define MS_SLAVE (1<<19) /* 524288: Make slave */
+#endif
+#ifndef MS_SHARED
+#define MS_SHARED (1<<20) /* 1048576: Make shared */
+#endif
+#ifndef MS_RELATIME
+#define MS_RELATIME (1<<21) /* 2097152: Update atime relative to mtime/ctime */
+#endif
+#ifndef MS_I_VERSION
+#define MS_I_VERSION (1<<23) /* Update the inode I_version field */
+#endif
+#ifndef MS_STRICTATIME
+#define MS_STRICTATIME (1<<24) /* Always perform atime updates */
+#endif
+#ifndef MS_LAZYTIME
+#define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */
+#endif
+
+
+/*
+ * Magic mount flag number. Had to be or-ed to the flag values. Deprecated and
+ * no more used since libmount v2.33; required for Linux <= 2.4.
+ */
+#ifndef MS_MGC_VAL
+#define MS_MGC_VAL 0xC0ED0000 /* magic flag number to indicate "new" flags */
+#endif
+#ifndef MS_MGC_MSK
+#define MS_MGC_MSK 0xffff0000 /* magic flag number mask */
+#endif
+
+
+/* Shared-subtree options */
+#define MS_PROPAGATION (MS_SHARED|MS_SLAVE|MS_UNBINDABLE|MS_PRIVATE)
+
+/* Options that we make ordinary users have by default. */
+#define MS_SECURE (MS_NOEXEC|MS_NOSUID|MS_NODEV)
+
+/* Options that we make owner-mounted devices have by default */
+#define MS_OWNERSECURE (MS_NOSUID|MS_NODEV)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMOUNT_MOUNT_H */
diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym
new file mode 100644
index 0000000..aa96091
--- /dev/null
+++ b/libmount/src/libmount.sym
@@ -0,0 +1,368 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ *
+ * The symbol versioning ensures that a new application requiring symbol foo,
+ * can't run with old library.so not providing foo.
+ *
+ * Version info can't enforce this since we never change the SONAME.
+ */
+MOUNT_2.19 {
+global:
+ mnt_cache_device_has_tag;
+ mnt_cache_find_tag_value;
+ mnt_cache_read_tags;
+ mnt_context_append_options;
+ mnt_context_apply_fstab;
+ mnt_context_disable_canonicalize;
+ mnt_context_disable_helpers;
+ mnt_context_disable_mtab;
+ mnt_context_do_mount;
+ mnt_context_do_umount;
+ mnt_context_enable_fake;
+ mnt_context_enable_force;
+ mnt_context_enable_lazy;
+ mnt_context_enable_loopdel;
+ mnt_context_enable_rdonly_umount;
+ mnt_context_enable_sloppy;
+ mnt_context_enable_verbose;
+ mnt_context_finalize_mount;
+ mnt_context_finalize_umount;
+ mnt_context_get_cache;
+ mnt_context_get_fs;
+ mnt_context_get_fstab;
+ mnt_context_get_fstype;
+ mnt_context_get_lock;
+ mnt_context_get_mflags;
+ mnt_context_get_mtab;
+ mnt_context_get_optsmode;
+ mnt_context_get_source;
+ mnt_context_get_status;
+ mnt_context_get_target;
+ mnt_context_get_user_mflags;
+ mnt_context_helper_setopt;
+ mnt_context_init_helper;
+ mnt_context_is_fake;
+ mnt_context_is_force;
+ mnt_context_is_lazy;
+ mnt_context_is_nomtab;
+ mnt_context_is_rdonly_umount;
+ mnt_context_is_restricted;
+ mnt_context_is_sloppy;
+ mnt_context_is_verbose;
+ mnt_context_mount;
+ mnt_context_prepare_mount;
+ mnt_context_prepare_umount;
+ mnt_context_set_cache;
+ mnt_context_set_fs;
+ mnt_context_set_fstab;
+ mnt_context_set_fstype;
+ mnt_context_set_fstype_pattern;
+ mnt_context_set_mflags;
+ mnt_context_set_mountdata;
+ mnt_context_set_options;
+ mnt_context_set_options_pattern;
+ mnt_context_set_optsmode;
+ mnt_context_set_source;
+ mnt_context_set_syscall_status;
+ mnt_context_set_target;
+ mnt_context_set_user_mflags;
+ mnt_context_strerror;
+ mnt_context_umount;
+ mnt_copy_fs;
+ mnt_free_cache;
+ mnt_free_context;
+ mnt_free_fs;
+ mnt_free_iter;
+ mnt_free_lock;
+ mnt_free_mntent;
+ mnt_free_table;
+ mnt_free_update;
+ mnt_fs_append_attributes;
+ mnt_fs_append_options;
+ mnt_fs_get_attribute;
+ mnt_fs_get_attributes;
+ mnt_fs_get_bindsrc;
+ mnt_fs_get_devno;
+ mnt_fs_get_freq;
+ mnt_fs_get_fs_options;
+ mnt_fs_get_fstype;
+ mnt_fs_get_id;
+ mnt_fs_get_option;
+ mnt_fs_get_parent_id;
+ mnt_fs_get_passno;
+ mnt_fs_get_root;
+ mnt_fs_get_source;
+ mnt_fs_get_srcpath;
+ mnt_fs_get_tag;
+ mnt_fs_get_target;
+ mnt_fs_get_userdata;
+ mnt_fs_get_user_options;
+ mnt_fs_get_vfs_options;
+ mnt_fs_is_kernel;
+ mnt_fs_match_fstype;
+ mnt_fs_match_options;
+ mnt_fs_match_source;
+ mnt_fs_match_target;
+ mnt_fs_prepend_attributes;
+ mnt_fs_prepend_options;
+ mnt_fs_print_debug;
+ mnt_fs_set_attributes;
+ mnt_fs_set_bindsrc;
+ mnt_fs_set_freq;
+ mnt_fs_set_fstype;
+ mnt_fs_set_options;
+ mnt_fs_set_passno;
+ mnt_fs_set_root;
+ mnt_fs_set_source;
+ mnt_fs_set_target;
+ mnt_fs_set_userdata;
+ mnt_fs_strdup_options;
+ mnt_fs_to_mntent;
+ mnt_fstype_is_netfs;
+ mnt_fstype_is_pseudofs;
+ mnt_get_builtin_optmap;
+ mnt_get_fstab_path;
+ mnt_get_fstype;
+ mnt_get_library_version;
+ mnt_get_mtab_path;
+ mnt_has_regular_mtab;
+ mnt_init_debug;
+ mnt_iter_get_direction;
+ mnt_lock_file;
+ mnt_mangle;
+ mnt_match_fstype;
+ mnt_match_options;
+ mnt_new_cache;
+ mnt_new_context;
+ mnt_new_fs;
+ mnt_new_iter;
+ mnt_new_lock;
+ mnt_new_table;
+ mnt_new_table_from_dir;
+ mnt_new_table_from_file;
+ mnt_new_update;
+ mnt_optstr_append_option;
+ mnt_optstr_apply_flags;
+ mnt_optstr_get_flags;
+ mnt_optstr_get_option;
+ mnt_optstr_get_options;
+ mnt_optstr_next_option;
+ mnt_optstr_prepend_option;
+ mnt_optstr_remove_option;
+ mnt_optstr_set_option;
+ mnt_parse_version_string;
+ mnt_reset_context;
+ mnt_reset_fs;
+ mnt_reset_iter;
+ mnt_resolve_path;
+ mnt_resolve_spec;
+ mnt_resolve_tag;
+ mnt_split_optstr;
+ mnt_table_add_fs;
+ mnt_table_find_next_fs;
+ mnt_table_find_pair;
+ mnt_table_find_source;
+ mnt_table_find_srcpath;
+ mnt_table_find_tag;
+ mnt_table_find_target;
+ mnt_table_get_cache;
+ mnt_table_get_nents;
+ mnt_table_get_root_fs;
+ mnt_table_next_child_fs;
+ mnt_table_next_fs;
+ mnt_table_parse_file;
+ mnt_table_parse_fstab;
+ mnt_table_parse_mtab;
+ mnt_table_parse_stream;
+ mnt_table_remove_fs;
+ mnt_table_set_cache;
+ mnt_table_set_iter;
+ mnt_table_set_parser_errcb;
+ mnt_unlock_file;
+ mnt_unmangle;
+ mnt_update_force_rdonly;
+ mnt_update_get_filename;
+ mnt_update_get_fs;
+ mnt_update_get_mflags;
+ mnt_update_is_ready;
+ mnt_update_set_fs;
+ mnt_update_table;
+local:
+ *;
+};
+
+MOUNT_2.20 {
+global:
+ mnt_context_get_table;
+ mnt_context_is_fs_mounted;
+ mnt_context_next_mount;
+ mnt_context_set_tables_errcb;
+ mnt_diff_tables;
+ mnt_free_tabdiff;
+ mnt_fs_get_options;
+ mnt_lock_block_signals;
+ mnt_new_tabdiff;
+ mnt_pretty_path;
+ mnt_reset_table;
+ mnt_tabdiff_next_change;
+ mnt_table_is_fs_mounted;
+} MOUNT_2.19;
+
+MOUNT_2.21 {
+global:
+ mnt_context_enable_fork;
+ mnt_context_get_helper_status;
+ mnt_context_get_syscall_errno;
+ mnt_context_helper_executed;
+ mnt_context_is_child;
+ mnt_context_is_fork;
+ mnt_context_is_parent;
+ mnt_context_next_umount;
+ mnt_context_reset_status;
+ mnt_context_set_passwd_cb;
+ mnt_context_syscall_called;
+ mnt_context_wait_for_children;
+ mnt_fs_is_netfs;
+ mnt_fs_is_pseudofs;
+ mnt_fs_is_swaparea;
+ mnt_get_library_features;
+ mnt_table_parse_dir;
+} MOUNT_2.20;
+
+MOUNT_2.22 {
+global:
+ mnt_context_disable_swapmatch;
+ mnt_context_get_options;
+ mnt_context_is_loopdel;
+ mnt_context_is_nocanonicalize;
+ mnt_context_is_nohelpers;
+ mnt_context_is_swapmatch;
+ mnt_context_tab_applied;
+ mnt_fs_get_priority;
+ mnt_fs_get_size;
+ mnt_fs_get_swaptype;
+ mnt_fs_get_tid;
+ mnt_fs_get_usedsize;
+ mnt_fs_streq_srcpath;
+ mnt_fs_streq_target;
+ mnt_get_mountpoint;
+ mnt_get_swaps_path;
+ mnt_optstr_deduplicate_option;
+ mnt_table_find_devno;
+ mnt_table_parse_swaps;
+} MOUNT_2.21;
+
+MOUNT_2.23 {
+global:
+ mnt_fs_get_optional_fields;
+ mnt_fs_get_propagation;
+ mnt_context_find_umount_fs;
+ mnt_table_find_mountpoint;
+} MOUNT_2.22;
+
+MOUNT_2.24 {
+global:
+ mnt_context_get_fstab_userdata;
+ mnt_context_get_fs_userdata;
+ mnt_context_get_mtab_userdata;
+ mnt_fs_append_comment;
+ mnt_fs_get_comment;
+ mnt_fs_set_comment;
+ mnt_ref_cache;
+ mnt_ref_fs;
+ mnt_ref_table;
+ mnt_table_append_intro_comment;
+ mnt_table_append_trailing_comment;
+ mnt_table_enable_comments;
+ mnt_table_first_fs;
+ mnt_table_get_intro_comment;
+ mnt_table_get_trailing_comment;
+ mnt_table_get_userdata;
+ mnt_table_is_empty;
+ mnt_table_last_fs;
+ mnt_table_replace_file;
+ mnt_table_set_intro_comment;
+ mnt_table_set_trailing_comment;
+ mnt_table_set_userdata;
+ mnt_table_with_comments;
+ mnt_table_write_file;
+ mnt_unref_cache;
+ mnt_unref_fs;
+ mnt_unref_table;
+} MOUNT_2.23;
+
+MOUNT_2.25 {
+ mnt_cache_set_targets;
+ mnt_resolve_target;
+ mnt_table_uniq_fs;
+ mnt_tag_is_valid;
+} MOUNT_2.24;
+
+MOUNT_2.26 {
+ mnt_monitor_close_fd;
+ mnt_monitor_enable_userspace;
+ mnt_monitor_enable_kernel;
+ mnt_monitor_event_cleanup;
+ mnt_monitor_get_fd;
+ mnt_monitor_next_change;
+ mnt_monitor_wait;
+ mnt_new_monitor;
+ mnt_ref_monitor;
+ mnt_unref_monitor;
+} MOUNT_2.25;
+
+MOUNT_2.28 {
+ mnt_table_find_target_with_option;
+ mnt_fs_set_priority;
+} MOUNT_2.26;
+
+MOUNT_2.30 {
+ mnt_context_is_rwonly_mount;
+ mnt_context_forced_rdonly;
+ mnt_context_enable_rwonly_mount;
+ mnt_context_get_excode;
+} MOUNT_2.28;
+
+MOUNT_2.33 {
+ mnt_context_get_origin_ns;
+ mnt_context_get_target_ns;
+ mnt_context_set_target_ns;
+ mnt_context_switch_ns;
+ mnt_context_switch_origin_ns;
+ mnt_context_switch_target_ns;
+} MOUNT_2.30;
+
+MOUNT_2.34 {
+ mnt_context_next_remount;
+ mnt_fs_get_table;
+ mnt_guess_system_root;
+ mnt_table_find_fs;
+ mnt_table_insert_fs;
+ mnt_table_move_fs;
+} MOUNT_2.33;
+
+MOUNT_2_35 {
+ mnt_context_force_unrestricted;
+ mnt_context_get_target_prefix;
+ mnt_context_set_target_prefix;
+} MOUNT_2.34;
+
+MOUNT_2_37 {
+ mnt_fs_get_vfs_options_all;
+ mnt_table_over_fs;
+} MOUNT_2_35;
+
+
+MOUNT_2_38 {
+ mnt_fs_is_regularfs;
+} MOUNT_2_37;
diff --git a/libmount/src/lock.c b/libmount/src/lock.c
new file mode 100644
index 0000000..f730af1
--- /dev/null
+++ b/libmount/src/lock.c
@@ -0,0 +1,729 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: lock
+ * @title: Locking
+ * @short_description: locking methods for /etc/mtab or another libmount files
+ *
+ * The mtab lock is backwards compatible with the standard linux /etc/mtab
+ * locking. Note, it's necessary to use the same locking schema in all
+ * applications that access the file.
+ */
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/file.h>
+
+#include "strutils.h"
+#include "closestream.h"
+#include "pathnames.h"
+#include "mountP.h"
+#include "monotonic.h"
+
+/*
+ * lock handler
+ */
+struct libmnt_lock {
+ char *lockfile; /* path to lock file (e.g. /etc/mtab~) */
+ char *linkfile; /* path to link file (e.g. /etc/mtab~.<id>) */
+ int lockfile_fd; /* lock file descriptor */
+
+ unsigned int locked :1, /* do we own the lock? */
+ sigblock :1, /* block signals when locked */
+ simplelock :1; /* use flock rather than normal mtab lock */
+
+ sigset_t oldsigmask;
+};
+
+
+/**
+ * mnt_new_lock:
+ * @datafile: the file that should be covered by the lock
+ * @id: unique linkfile identifier or 0 (default is getpid())
+ *
+ * Returns: newly allocated lock handler or NULL on case of error.
+ */
+struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id)
+{
+ struct libmnt_lock *ml = NULL;
+ char *lo = NULL, *ln = NULL;
+ size_t losz;
+
+ if (!datafile)
+ return NULL;
+
+ /* for flock we use "foo.lock, for mtab "foo~"
+ */
+ losz = strlen(datafile) + sizeof(".lock");
+ lo = malloc(losz);
+ if (!lo)
+ goto err;
+
+ /* default is mtab~ lock */
+ snprintf(lo, losz, "%s~", datafile);
+
+ if (asprintf(&ln, "%s~.%d", datafile, id ? : getpid()) == -1) {
+ ln = NULL;
+ goto err;
+ }
+ ml = calloc(1, sizeof(*ml) );
+ if (!ml)
+ goto err;
+
+ ml->lockfile_fd = -1;
+ ml->linkfile = ln;
+ ml->lockfile = lo;
+
+ DBG(LOCKS, ul_debugobj(ml, "alloc: default linkfile=%s, lockfile=%s", ln, lo));
+ return ml;
+err:
+ free(lo);
+ free(ln);
+ free(ml);
+ return NULL;
+}
+
+
+/**
+ * mnt_free_lock:
+ * @ml: struct libmnt_lock handler
+ *
+ * Deallocates mnt_lock.
+ */
+void mnt_free_lock(struct libmnt_lock *ml)
+{
+ if (!ml)
+ return;
+ DBG(LOCKS, ul_debugobj(ml, "free%s", ml->locked ? " !!! LOCKED !!!" : ""));
+ free(ml->lockfile);
+ free(ml->linkfile);
+ free(ml);
+}
+
+/**
+ * mnt_lock_block_signals:
+ * @ml: struct libmnt_lock handler
+ * @enable: TRUE/FALSE
+ *
+ * Block/unblock signals when the lock is locked, the signals are not blocked
+ * by default.
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int mnt_lock_block_signals(struct libmnt_lock *ml, int enable)
+{
+ if (!ml)
+ return -EINVAL;
+ DBG(LOCKS, ul_debugobj(ml, "signals: %s", enable ? "BLOCKED" : "UNBLOCKED"));
+ ml->sigblock = enable ? 1 : 0;
+ return 0;
+}
+
+/* don't export this to API
+ */
+int mnt_lock_use_simplelock(struct libmnt_lock *ml, int enable)
+{
+ size_t sz;
+
+ if (!ml)
+ return -EINVAL;
+
+ assert(ml->lockfile);
+
+ DBG(LOCKS, ul_debugobj(ml, "flock: %s", enable ? "ENABLED" : "DISABLED"));
+ ml->simplelock = enable ? 1 : 0;
+
+ sz = strlen(ml->lockfile);
+ assert(sz);
+
+ /* Change lock name:
+ *
+ * flock: "<name>.lock"
+ * mtab lock: "<name>~"
+ */
+ if (ml->simplelock && endswith(ml->lockfile, "~"))
+ memcpy(ml->lockfile + sz - 1, ".lock", 6);
+
+ else if (!ml->simplelock && endswith(ml->lockfile, ".lock"))
+ memcpy(ml->lockfile + sz - 5, "~", 2);
+
+ DBG(LOCKS, ul_debugobj(ml, "new lock filename: '%s'", ml->lockfile));
+ return 0;
+}
+
+/*
+ * Returns path to lockfile.
+ */
+static const char *mnt_lock_get_lockfile(struct libmnt_lock *ml)
+{
+ return ml ? ml->lockfile : NULL;
+}
+
+/*
+ * Note that the filename is generated by mnt_new_lock() and depends on
+ * getpid() or 'id' argument of the mnt_new_lock() function.
+ *
+ * Returns: unique (per process/thread) path to linkfile.
+ */
+static const char *mnt_lock_get_linkfile(struct libmnt_lock *ml)
+{
+ return ml ? ml->linkfile : NULL;
+}
+
+/*
+ * Simple flocking
+ */
+static void unlock_simplelock(struct libmnt_lock *ml)
+{
+ assert(ml);
+ assert(ml->simplelock);
+
+ if (ml->lockfile_fd >= 0) {
+ DBG(LOCKS, ul_debugobj(ml, "%s: unflocking",
+ mnt_lock_get_lockfile(ml)));
+ close(ml->lockfile_fd);
+ }
+}
+
+static int lock_simplelock(struct libmnt_lock *ml)
+{
+ const char *lfile;
+ int rc;
+ struct stat sb;
+ const mode_t lock_mask = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
+
+ assert(ml);
+ assert(ml->simplelock);
+
+ lfile = mnt_lock_get_lockfile(ml);
+
+ DBG(LOCKS, ul_debugobj(ml, "%s: locking", lfile));
+
+ if (ml->sigblock) {
+ sigset_t sigs;
+ sigemptyset(&ml->oldsigmask);
+ sigfillset(&sigs);
+ sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask);
+ }
+
+ ml->lockfile_fd = open(lfile, O_RDONLY|O_CREAT|O_CLOEXEC,
+ S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
+ if (ml->lockfile_fd < 0) {
+ rc = -errno;
+ goto err;
+ }
+
+ rc = fstat(ml->lockfile_fd, &sb);
+ if (rc < 0) {
+ rc = -errno;
+ goto err;
+ }
+
+ if ((sb.st_mode & lock_mask) != lock_mask) {
+ rc = fchmod(ml->lockfile_fd, lock_mask);
+ if (rc < 0) {
+ rc = -errno;
+ goto err;
+ }
+ }
+
+ while (flock(ml->lockfile_fd, LOCK_EX) < 0) {
+ int errsv;
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ errsv = errno;
+ close(ml->lockfile_fd);
+ ml->lockfile_fd = -1;
+ rc = -errsv;
+ goto err;
+ }
+ ml->locked = 1;
+ return 0;
+err:
+ if (ml->sigblock)
+ sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
+ return rc;
+}
+
+/*
+ * traditional mtab locking
+ */
+
+static void mnt_lockalrm_handler(int sig __attribute__((__unused__)))
+{
+ /* do nothing, say nothing, be nothing */
+}
+
+/*
+ * Waits for F_SETLKW, unfortunately we have to use SIGALRM here to interrupt
+ * fcntl() to avoid neverending waiting.
+ *
+ * Returns: 0 on success, 1 on timeout, -errno on error.
+ */
+static int mnt_wait_mtab_lock(struct libmnt_lock *ml, struct flock *fl, time_t maxtime)
+{
+ struct timeval now = { 0 };
+ struct sigaction sa, osa;
+ int ret = 0;
+
+ gettime_monotonic(&now);
+ DBG(LOCKS, ul_debugobj(ml, "(%d) waiting for F_SETLKW (now=%lu, maxtime=%lu, diff=%lu)",
+ getpid(),
+ (unsigned long) now.tv_sec,
+ (unsigned long) maxtime,
+ (unsigned long) (maxtime - now.tv_sec)));
+
+ if (now.tv_sec >= maxtime)
+ return 1; /* timeout */
+
+ /* setup ALARM handler -- we don't want to wait forever */
+ sa.sa_flags = 0;
+ sa.sa_handler = mnt_lockalrm_handler;
+ sigfillset (&sa.sa_mask);
+
+ sigaction(SIGALRM, &sa, &osa);
+
+
+ alarm(maxtime - now.tv_sec);
+ if (fcntl(ml->lockfile_fd, F_SETLKW, fl) == -1)
+ ret = errno == EINTR ? 1 : -errno;
+ alarm(0);
+
+ /* restore old sigaction */
+ sigaction(SIGALRM, &osa, NULL);
+
+ DBG(LOCKS, ul_debugobj(ml, "(%d) leaving mnt_wait_setlkw(), rc=%d",
+ getpid(), ret));
+ return ret;
+}
+
+/*
+ * Create the mtab lock file.
+ *
+ * The old code here used flock on a lock file /etc/mtab~ and deleted
+ * this lock file afterwards. However, as rgooch remarks, that has a
+ * race: a second mount may be waiting on the lock and proceed as
+ * soon as the lock file is deleted by the first mount, and immediately
+ * afterwards a third mount comes, creates a new /etc/mtab~, applies
+ * flock to that, and also proceeds, so that the second and third mount
+ * are now both scribbling in /etc/mtab.
+ *
+ * The new code uses a link() instead of a creat(), where we proceed
+ * only if it was us that created the lock, and hence we always have
+ * to delete the lock afterwards. Now the use of flock() is in principle
+ * superfluous, but avoids an arbitrary sleep().
+ *
+ * Where does the link point to? Obvious choices are mtab and mtab~~.
+ * HJLu points out that the latter leads to races. Right now we use
+ * mtab~.pid instead.
+ *
+ *
+ * The original mount locking code has used sleep(1) between attempts and
+ * maximal number of attempts has been 5.
+ *
+ * There was a very small number of attempts and extremely long waiting (1s)
+ * that is useless on machines with large number of mount processes.
+ *
+ * Now we wait for a few thousand microseconds between attempts and we have a global
+ * time limit (30s) rather than a limit for the number of attempts. The advantage
+ * is that this method also counts time which we spend in fcntl(F_SETLKW) and
+ * the number of attempts is not restricted.
+ * -- kzak@redhat.com [Mar-2007]
+ *
+ *
+ * This mtab locking code has been refactored and moved to libmount. The mtab
+ * locking is really not perfect (e.g. SIGALRM), but it's stable, reliable and
+ * backwardly compatible code.
+ *
+ * Don't forget that this code has to be compatible with 3rd party mounts
+ * (/sbin/mount.foo) and has to work with NFS.
+ * -- kzak@redhat.com [May-2009]
+ */
+
+/* maximum seconds between the first and the last attempt */
+#define MOUNTLOCK_MAXTIME 30
+
+/* sleep time (in microseconds, max=999999) between attempts */
+#define MOUNTLOCK_WAITTIME 5000
+
+static void unlock_mtab(struct libmnt_lock *ml)
+{
+ if (!ml)
+ return;
+
+ if (!ml->locked && ml->lockfile && ml->linkfile)
+ {
+ /* We (probably) have all the files, but we don't own the lock,
+ * Really? Check it! Maybe ml->locked wasn't set properly
+ * because the code was interrupted by a signal. Paranoia? Yes.
+ *
+ * We own the lock when linkfile == lockfile.
+ */
+ struct stat lo, li;
+
+ if (!stat(ml->lockfile, &lo) && !stat(ml->linkfile, &li) &&
+ lo.st_dev == li.st_dev && lo.st_ino == li.st_ino)
+ ml->locked = 1;
+ }
+
+ if (ml->linkfile)
+ unlink(ml->linkfile);
+ if (ml->lockfile_fd >= 0)
+ close(ml->lockfile_fd);
+ if (ml->locked && ml->lockfile) {
+ unlink(ml->lockfile);
+ DBG(LOCKS, ul_debugobj(ml, "unlink %s", ml->lockfile));
+ }
+}
+
+static int lock_mtab(struct libmnt_lock *ml)
+{
+ int i, rc = -1;
+ struct timespec waittime = { 0 };;
+ struct timeval maxtime = { 0 };
+ const char *lockfile, *linkfile;
+
+ if (!ml)
+ return -EINVAL;
+ if (ml->locked)
+ return 0;
+
+ lockfile = mnt_lock_get_lockfile(ml);
+ if (!lockfile)
+ return -EINVAL;
+ linkfile = mnt_lock_get_linkfile(ml);
+ if (!linkfile)
+ return -EINVAL;
+
+ if (ml->sigblock) {
+ /*
+ * Block all signals when locked, mnt_unlock_file() will
+ * restore the old mask.
+ */
+ sigset_t sigs;
+
+ sigemptyset(&ml->oldsigmask);
+ sigfillset(&sigs);
+ sigdelset(&sigs, SIGTRAP);
+ sigdelset(&sigs, SIGALRM);
+ sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask);
+ }
+
+ i = open(linkfile, O_WRONLY|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR);
+ if (i < 0) {
+ /* linkfile does not exist (as a file) and we cannot create it.
+ * Read-only or full filesystem? Too many files open in the system?
+ */
+ if (errno > 0)
+ rc = -errno;
+ goto failed;
+ }
+ close(i);
+
+ gettime_monotonic(&maxtime);
+ maxtime.tv_sec += MOUNTLOCK_MAXTIME;
+
+ waittime.tv_sec = 0;
+ waittime.tv_nsec = (1000 * MOUNTLOCK_WAITTIME);
+
+ /* Repeat until it was us who made the link */
+ while (!ml->locked) {
+ struct timeval now = { 0 };
+ struct flock flock;
+ int j;
+
+ j = link(linkfile, lockfile);
+ if (j == 0)
+ ml->locked = 1;
+
+ if (j < 0 && errno != EEXIST) {
+ if (errno > 0)
+ rc = -errno;
+ goto failed;
+ }
+ ml->lockfile_fd = open(lockfile, O_WRONLY|O_CLOEXEC);
+
+ if (ml->lockfile_fd < 0) {
+ /* Strange... Maybe the file was just deleted? */
+ int errsv = errno;
+ gettime_monotonic(&now);
+ if (errsv == ENOENT && now.tv_sec < maxtime.tv_sec) {
+ ml->locked = 0;
+ continue;
+ }
+ if (errsv > 0)
+ rc = -errsv;
+ goto failed;
+ }
+
+ flock.l_type = F_WRLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+
+ if (ml->locked) {
+ /* We made the link. Now claim the lock. */
+ if (fcntl (ml->lockfile_fd, F_SETLK, &flock) == -1) {
+ DBG(LOCKS, ul_debugobj(ml,
+ "%s: can't F_SETLK lockfile, errno=%d\n",
+ lockfile, errno));
+ /* proceed, since it was us who created the lockfile anyway */
+ }
+ break;
+ }
+
+ /* Someone else made the link. Wait. */
+ int err = mnt_wait_mtab_lock(ml, &flock, maxtime.tv_sec);
+
+ if (err == 1) {
+ DBG(LOCKS, ul_debugobj(ml,
+ "%s: can't create link: time out (perhaps "
+ "there is a stale lock file?)", lockfile));
+ rc = -ETIMEDOUT;
+ goto failed;
+
+ } else if (err < 0) {
+ rc = err;
+ goto failed;
+ }
+ nanosleep(&waittime, NULL);
+ close(ml->lockfile_fd);
+ ml->lockfile_fd = -1;
+ }
+ DBG(LOCKS, ul_debugobj(ml, "%s: (%d) successfully locked",
+ lockfile, getpid()));
+ unlink(linkfile);
+ return 0;
+
+failed:
+ mnt_unlock_file(ml);
+ return rc;
+}
+
+
+/**
+ * mnt_lock_file
+ * @ml: pointer to struct libmnt_lock instance
+ *
+ * Creates a lock file (e.g. /etc/mtab~). Note that this function may
+ * use alarm().
+ *
+ * Your application always has to call mnt_unlock_file() before exit.
+ *
+ * Traditional mtab locking scheme:
+ *
+ * 1. create linkfile (e.g. /etc/mtab~.$PID)
+ * 2. link linkfile --> lockfile (e.g. /etc/mtab~.$PID --> /etc/mtab~)
+ * 3. a) link() success: setups F_SETLK lock (see fcntl(2))
+ * b) link() failed: wait (max 30s) on F_SETLKW lock, goto 2.
+ *
+ * Note that when the lock is used by mnt_update_table() interface then libmount
+ * uses flock() for private library file /run/mount/utab. The fcntl(2) is used only
+ * for backwardly compatible stuff like /etc/mtab.
+ *
+ * Returns: 0 on success or negative number in case of error (-ETIMEOUT is case
+ * of stale lock file).
+ */
+int mnt_lock_file(struct libmnt_lock *ml)
+{
+ if (!ml)
+ return -EINVAL;
+
+ if (ml->simplelock)
+ return lock_simplelock(ml);
+
+ return lock_mtab(ml);
+}
+
+/**
+ * mnt_unlock_file:
+ * @ml: lock struct
+ *
+ * Unlocks the file. The function could be called independently of the
+ * lock status (for example from exit(3)).
+ */
+void mnt_unlock_file(struct libmnt_lock *ml)
+{
+ if (!ml)
+ return;
+
+ DBG(LOCKS, ul_debugobj(ml, "(%d) %s", getpid(),
+ ml->locked ? "unlocking" : "cleaning"));
+
+ if (ml->simplelock)
+ unlock_simplelock(ml);
+ else
+ unlock_mtab(ml);
+
+ ml->locked = 0;
+ ml->lockfile_fd = -1;
+
+ if (ml->sigblock) {
+ DBG(LOCKS, ul_debugobj(ml, "restoring sigmask"));
+ sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL);
+ }
+}
+
+#ifdef TEST_PROGRAM
+
+static struct libmnt_lock *lock;
+
+/*
+ * read number from @filename, increment the number and
+ * write the number back to the file
+ */
+static void increment_data(const char *filename, int verbose, int loopno)
+{
+ long num;
+ FILE *f;
+ char buf[256];
+
+ if (!(f = fopen(filename, "r" UL_CLOEXECSTR)))
+ err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
+
+ if (!fgets(buf, sizeof(buf), f))
+ err(EXIT_FAILURE, "%d failed read: %s", getpid(), filename);
+
+ fclose(f);
+ num = atol(buf) + 1;
+
+ if (!(f = fopen(filename, "w" UL_CLOEXECSTR)))
+ err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename);
+
+ fprintf(f, "%ld", num);
+
+ if (close_stream(f) != 0)
+ err(EXIT_FAILURE, "write failed: %s", filename);
+
+ if (verbose)
+ fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(),
+ filename, num - 1, num, loopno);
+}
+
+static void clean_lock(void)
+{
+ if (!lock)
+ return;
+ mnt_unlock_file(lock);
+ mnt_free_lock(lock);
+}
+
+static void __attribute__((__noreturn__)) sig_handler(int sig)
+{
+ errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig));
+}
+
+static int test_lock(struct libmnt_test *ts, int argc, char *argv[])
+{
+ time_t synctime = 0;
+ unsigned int usecs;
+ const char *datafile = NULL;
+ int verbose = 0, loops = 0, l, idx = 1;
+
+ if (argc < 3)
+ return -EINVAL;
+
+ if (strcmp(argv[idx], "--synctime") == 0) {
+ synctime = (time_t) atol(argv[idx + 1]);
+ idx += 2;
+ }
+ if (idx < argc && strcmp(argv[idx], "--verbose") == 0) {
+ verbose = 1;
+ idx++;
+ }
+
+ if (idx < argc)
+ datafile = argv[idx++];
+ if (idx < argc)
+ loops = atoi(argv[idx++]);
+
+ if (!datafile || !loops)
+ return -EINVAL;
+
+ if (verbose)
+ fprintf(stderr, "%d: start: synctime=%u, datafile=%s, loops=%d\n",
+ getpid(), (int) synctime, datafile, loops);
+
+ atexit(clean_lock);
+
+ /* be paranoid and call exit() (=clean_lock()) for all signals */
+ {
+ int sig = 0;
+ struct sigaction sa;
+
+ sa.sa_handler = sig_handler;
+ sa.sa_flags = 0;
+ sigfillset(&sa.sa_mask);
+
+ while (sigismember(&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD)
+ sigaction (sig, &sa, (struct sigaction *) 0);
+ }
+
+ /* start the test in exactly defined time */
+ if (synctime) {
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ if (synctime && synctime - tv.tv_sec > 1) {
+ usecs = ((synctime - tv.tv_sec) * 1000000UL) -
+ (1000000UL - tv.tv_usec);
+ xusleep(usecs);
+ }
+ }
+
+ for (l = 0; l < loops; l++) {
+ lock = mnt_new_lock(datafile, 0);
+ if (!lock)
+ return -1;
+
+ if (mnt_lock_file(lock) != 0) {
+ fprintf(stderr, "%d: failed to lock %s file\n",
+ getpid(), datafile);
+ return -1;
+ }
+
+ increment_data(datafile, verbose, l);
+
+ mnt_unlock_file(lock);
+ mnt_free_lock(lock);
+ lock = NULL;
+
+ /* The mount command usually finishes after a mtab update. We
+ * simulate this via short sleep -- it's also enough to make
+ * concurrent processes happy.
+ */
+ if (synctime)
+ xusleep(25000);
+ }
+
+ return 0;
+}
+
+/*
+ * Note that this test should be executed from a script that creates many
+ * parallel processes, otherwise this test does not make sense.
+ */
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--lock", test_lock, " [--synctime <time_t>] [--verbose] <datafile> <loops> "
+ "increment a number in datafile" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/monitor.c b/libmount/src/monitor.c
new file mode 100644
index 0000000..dada02e
--- /dev/null
+++ b/libmount/src/monitor.c
@@ -0,0 +1,980 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2014-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: monitor
+ * @title: Monitor
+ * @short_description: interface to monitor mount tables
+ *
+ * For example monitor VFS (/proc/self/mountinfo) for changes:
+ *
+ * <informalexample>
+ * <programlisting>
+ * const char *filename;
+ * struct libmount_monitor *mn = mnt_new_monitor();
+ *
+ * mnt_monitor_enable_kernel(mn, TRUE));
+ *
+ * printf("waiting for changes...\n");
+ * while (mnt_monitor_wait(mn, -1) > 0) {
+ * while (mnt_monitor_next_change(mn, &filename, NULL) == 0)
+ * printf(" %s: change detected\n", filename);
+ * }
+ * mnt_unref_monitor(mn);
+ * </programlisting>
+ * </informalexample>
+ *
+ */
+
+#include "fileutils.h"
+#include "mountP.h"
+#include "pathnames.h"
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+
+
+struct monitor_opers;
+
+struct monitor_entry {
+ int fd; /* private entry file descriptor */
+ char *path; /* path to the monitored file */
+ int type; /* MNT_MONITOR_TYPE_* */
+ uint32_t events; /* wanted epoll events */
+
+ const struct monitor_opers *opers;
+
+ unsigned int enable : 1,
+ changed : 1;
+
+ struct list_head ents;
+};
+
+struct libmnt_monitor {
+ int refcount;
+ int fd; /* public monitor file descriptor */
+
+ struct list_head ents;
+};
+
+struct monitor_opers {
+ int (*op_get_fd)(struct libmnt_monitor *, struct monitor_entry *);
+ int (*op_close_fd)(struct libmnt_monitor *, struct monitor_entry *);
+ int (*op_event_verify)(struct libmnt_monitor *, struct monitor_entry *);
+};
+
+static int monitor_modify_epoll(struct libmnt_monitor *mn,
+ struct monitor_entry *me, int enable);
+
+/**
+ * mnt_new_monitor:
+ *
+ * The initial refcount is 1, and needs to be decremented to
+ * release the resources of the filesystem.
+ *
+ * Returns: newly allocated struct libmnt_monitor.
+ */
+struct libmnt_monitor *mnt_new_monitor(void)
+{
+ struct libmnt_monitor *mn = calloc(1, sizeof(*mn));
+ if (!mn)
+ return NULL;
+
+ mn->refcount = 1;
+ mn->fd = -1;
+ INIT_LIST_HEAD(&mn->ents);
+
+ DBG(MONITOR, ul_debugobj(mn, "alloc"));
+ return mn;
+}
+
+/**
+ * mnt_ref_monitor:
+ * @mn: monitor pointer
+ *
+ * Increments reference counter.
+ */
+void mnt_ref_monitor(struct libmnt_monitor *mn)
+{
+ if (mn)
+ mn->refcount++;
+}
+
+static void free_monitor_entry(struct monitor_entry *me)
+{
+ if (!me)
+ return;
+ list_del(&me->ents);
+ if (me->fd >= 0)
+ close(me->fd);
+ free(me->path);
+ free(me);
+}
+
+/**
+ * mnt_unref_monitor:
+ * @mn: monitor pointer
+ *
+ * Decrements the reference counter, on zero the @mn is automatically
+ * deallocated.
+ */
+void mnt_unref_monitor(struct libmnt_monitor *mn)
+{
+ if (!mn)
+ return;
+
+ mn->refcount--;
+ if (mn->refcount <= 0) {
+ mnt_monitor_close_fd(mn); /* destroys all file descriptors */
+
+ while (!list_empty(&mn->ents)) {
+ struct monitor_entry *me = list_entry(mn->ents.next,
+ struct monitor_entry, ents);
+ free_monitor_entry(me);
+ }
+
+ free(mn);
+ }
+}
+
+static struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn)
+{
+ struct monitor_entry *me;
+
+ assert(mn);
+
+ me = calloc(1, sizeof(*me));
+ if (!me)
+ return NULL;
+ INIT_LIST_HEAD(&me->ents);
+ list_add_tail(&me->ents, &mn->ents);
+
+ me->fd = -1;
+
+ return me;
+}
+
+static int monitor_next_entry(struct libmnt_monitor *mn,
+ struct libmnt_iter *itr,
+ struct monitor_entry **me)
+{
+ int rc = 1;
+
+ assert(mn);
+ assert(itr);
+ assert(me);
+
+ *me = NULL;
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &mn->ents);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, *me, struct monitor_entry, ents);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/* returns entry by type */
+static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type)
+{
+ struct libmnt_iter itr;
+ struct monitor_entry *me;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while (monitor_next_entry(mn, &itr, &me) == 0) {
+ if (me->type == type)
+ return me;
+ }
+ return NULL;
+}
+
+
+/*
+ * Userspace monitor
+ */
+
+static int userspace_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)),
+ struct monitor_entry *me)
+{
+ assert(me);
+
+ if (me->fd >= 0)
+ close(me->fd);
+ me->fd = -1;
+ return 0;
+}
+
+static int userspace_add_watch(struct monitor_entry *me, int *final, int *fd)
+{
+ char *filename = NULL;
+ int wd, rc = -EINVAL;
+
+ assert(me);
+ assert(me->path);
+
+ /*
+ * libmount uses rename(2) to atomically update utab, monitor
+ * rename changes is too tricky. It seems better to monitor utab
+ * lockfile close.
+ */
+ if (asprintf(&filename, "%s.lock", me->path) <= 0) {
+ rc = -errno;
+ goto done;
+ }
+
+ /* try lock file if already exists */
+ errno = 0;
+ wd = inotify_add_watch(me->fd, filename, IN_CLOSE_NOWRITE);
+ if (wd >= 0) {
+ DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd));
+ rc = 0;
+ if (final)
+ *final = 1;
+ if (fd)
+ *fd = wd;
+ goto done;
+ } else if (errno != ENOENT) {
+ rc = -errno;
+ goto done;
+ }
+
+ while (strchr(filename, '/')) {
+ stripoff_last_component(filename);
+ if (!*filename)
+ break;
+
+ /* try directory where is the lock file */
+ errno = 0;
+ wd = inotify_add_watch(me->fd, filename, IN_CREATE|IN_ISDIR);
+ if (wd >= 0) {
+ DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd));
+ rc = 0;
+ if (fd)
+ *fd = wd;
+ break;
+ }
+
+ if (errno != ENOENT) {
+ rc = -errno;
+ break;
+ }
+ }
+done:
+ free(filename);
+ return rc;
+}
+
+static int userspace_monitor_get_fd(struct libmnt_monitor *mn,
+ struct monitor_entry *me)
+{
+ int rc;
+
+ if (!me || me->enable == 0) /* not-initialized or disabled */
+ return -EINVAL;
+ if (me->fd >= 0)
+ return me->fd; /* already initialized */
+
+ assert(me->path);
+ DBG(MONITOR, ul_debugobj(mn, " open userspace monitor for %s", me->path));
+
+ me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+ if (me->fd < 0)
+ goto err;
+
+ if (userspace_add_watch(me, NULL, NULL) < 0)
+ goto err;
+
+ return me->fd;
+err:
+ rc = -errno;
+ if (me->fd >= 0)
+ close(me->fd);
+ me->fd = -1;
+ DBG(MONITOR, ul_debugobj(mn, "failed to create userspace monitor [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * verify and drain inotify buffer
+ */
+static int userspace_event_verify(struct libmnt_monitor *mn,
+ struct monitor_entry *me)
+{
+ char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
+ int status = 0;
+
+ if (!me || me->fd < 0)
+ return 0;
+
+ DBG(MONITOR, ul_debugobj(mn, "drain and verify userspace monitor inotify"));
+
+ /* the me->fd is non-blocking */
+ do {
+ ssize_t len;
+ char *p;
+ const struct inotify_event *e;
+
+ len = read(me->fd, buf, sizeof(buf));
+ if (len < 0)
+ break;
+
+ for (p = buf; p < buf + len;
+ p += sizeof(struct inotify_event) + e->len) {
+
+ int fd = -1;
+
+ e = (const struct inotify_event *) p;
+ DBG(MONITOR, ul_debugobj(mn, " inotify event 0x%x [%s]\n", e->mask, e->len ? e->name : ""));
+
+ if (e->mask & IN_CLOSE_NOWRITE)
+ status = 1;
+ else {
+ /* event on lock file */
+ userspace_add_watch(me, &status, &fd);
+
+ if (fd != e->wd) {
+ DBG(MONITOR, ul_debugobj(mn, " removing watch [fd=%d]", e->wd));
+ inotify_rm_watch(me->fd, e->wd);
+ }
+ }
+ }
+ } while (1);
+
+ DBG(MONITOR, ul_debugobj(mn, "%s", status == 1 ? " success" : " nothing"));
+ return status;
+}
+
+/*
+ * userspace monitor operations
+ */
+static const struct monitor_opers userspace_opers = {
+ .op_get_fd = userspace_monitor_get_fd,
+ .op_close_fd = userspace_monitor_close_fd,
+ .op_event_verify = userspace_event_verify
+};
+
+/**
+ * mnt_monitor_enable_userspace:
+ * @mn: monitor
+ * @enable: 0 or 1
+ * @filename: overwrites default
+ *
+ * Enables or disables userspace monitoring. If the userspace monitor does not
+ * exist and enable=1 then allocates new resources necessary for the monitor.
+ *
+ * If the top-level monitor has been already created (by mnt_monitor_get_fd()
+ * or mnt_monitor_wait()) then it's updated according to @enable.
+ *
+ * The @filename is used only the first time when you enable the monitor. It's
+ * impossible to have more than one userspace monitor. The recommended is to
+ * use NULL as filename.
+ *
+ * The userspace monitor is unsupported for systems with classic regular
+ * /etc/mtab file.
+ *
+ * Return: 0 on success and <0 on error
+ */
+int mnt_monitor_enable_userspace(struct libmnt_monitor *mn, int enable, const char *filename)
+{
+ struct monitor_entry *me;
+ int rc = 0;
+
+ if (!mn)
+ return -EINVAL;
+
+ me = monitor_get_entry(mn, MNT_MONITOR_TYPE_USERSPACE);
+ if (me) {
+ rc = monitor_modify_epoll(mn, me, enable);
+ if (!enable)
+ userspace_monitor_close_fd(mn, me);
+ return rc;
+ }
+ if (!enable)
+ return 0;
+
+ DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor"));
+
+ if (!filename)
+ filename = mnt_get_utab_path(); /* /run/mount/utab */
+ if (!filename) {
+ DBG(MONITOR, ul_debugobj(mn, "failed to get userspace mount table path"));
+ return -EINVAL;
+ }
+
+ me = monitor_new_entry(mn);
+ if (!me)
+ goto err;
+
+ me->type = MNT_MONITOR_TYPE_USERSPACE;
+ me->opers = &userspace_opers;
+ me->events = EPOLLIN;
+ me->path = strdup(filename);
+ if (!me->path)
+ goto err;
+
+ return monitor_modify_epoll(mn, me, TRUE);
+err:
+ rc = -errno;
+ free_monitor_entry(me);
+ DBG(MONITOR, ul_debugobj(mn, "failed to allocate userspace monitor [rc=%d]", rc));
+ return rc;
+}
+
+
+/*
+ * Kernel monitor
+ */
+
+static int kernel_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)),
+ struct monitor_entry *me)
+{
+ assert(me);
+
+ if (me->fd >= 0)
+ close(me->fd);
+ me->fd = -1;
+ return 0;
+}
+
+static int kernel_monitor_get_fd(struct libmnt_monitor *mn,
+ struct monitor_entry *me)
+{
+ int rc;
+
+ if (!me || me->enable == 0) /* not-initialized or disabled */
+ return -EINVAL;
+ if (me->fd >= 0)
+ return me->fd; /* already initialized */
+
+ assert(me->path);
+ DBG(MONITOR, ul_debugobj(mn, " open kernel monitor for %s", me->path));
+
+ me->fd = open(me->path, O_RDONLY|O_CLOEXEC);
+ if (me->fd < 0)
+ goto err;
+
+ return me->fd;
+err:
+ rc = -errno;
+ DBG(MONITOR, ul_debugobj(mn, "failed to create kernel monitor [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * kernel monitor operations
+ */
+static const struct monitor_opers kernel_opers = {
+ .op_get_fd = kernel_monitor_get_fd,
+ .op_close_fd = kernel_monitor_close_fd,
+};
+
+/**
+ * mnt_monitor_enable_kernel:
+ * @mn: monitor
+ * @enable: 0 or 1
+ *
+ * Enables or disables kernel VFS monitoring. If the monitor does not exist and
+ * enable=1 then allocates new resources necessary for the monitor.
+ *
+ * If the top-level monitor has been already created (by mnt_monitor_get_fd()
+ * or mnt_monitor_wait()) then it's updated according to @enable.
+ *
+ * Return: 0 on success and <0 on error
+ */
+int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable)
+{
+ struct monitor_entry *me;
+ int rc = 0;
+
+ if (!mn)
+ return -EINVAL;
+
+ me = monitor_get_entry(mn, MNT_MONITOR_TYPE_KERNEL);
+ if (me) {
+ rc = monitor_modify_epoll(mn, me, enable);
+ if (!enable)
+ kernel_monitor_close_fd(mn, me);
+ return rc;
+ }
+ if (!enable)
+ return 0;
+
+ DBG(MONITOR, ul_debugobj(mn, "allocate new kernel monitor"));
+
+ /* create a new entry */
+ me = monitor_new_entry(mn);
+ if (!me)
+ goto err;
+
+ /* If you want to use epoll FD in another epoll then top level
+ * epoll_wait() will drain all events from low-level FD if the
+ * low-level FD is not added with EPOLLIN. It means without EPOLLIN it
+ * it's impossible to detect which low-level FD has been active.
+ *
+ * Unfortunately, use EPOLLIN for mountinfo is tricky because in this
+ * case kernel returns events all time (we don't read from the FD).
+ * The solution is to use also edge-triggered (EPOLLET) flag, then
+ * kernel generate events on mountinfo changes only. The disadvantage is
+ * that we have to drain initial event generated by EPOLLIN after
+ * epoll_ctl(ADD). See monitor_modify_epoll().
+ */
+ me->events = EPOLLIN | EPOLLET;
+
+ me->type = MNT_MONITOR_TYPE_KERNEL;
+ me->opers = &kernel_opers;
+ me->path = strdup(_PATH_PROC_MOUNTINFO);
+ if (!me->path)
+ goto err;
+
+ return monitor_modify_epoll(mn, me, TRUE);
+err:
+ rc = -errno;
+ free_monitor_entry(me);
+ DBG(MONITOR, ul_debugobj(mn, "failed to allocate kernel monitor [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Add/Remove monitor entry to/from monitor epoll.
+ */
+static int monitor_modify_epoll(struct libmnt_monitor *mn,
+ struct monitor_entry *me, int enable)
+{
+ assert(mn);
+ assert(me);
+
+ me->enable = enable ? 1 : 0;
+ me->changed = 0;
+
+ if (mn->fd < 0)
+ return 0; /* no epoll, ignore request */
+
+ if (enable) {
+ struct epoll_event ev = { .events = me->events };
+ int fd = me->opers->op_get_fd(mn, me);
+
+ if (fd < 0)
+ goto err;
+
+ DBG(MONITOR, ul_debugobj(mn, " add fd=%d (for %s)", fd, me->path));
+
+ ev.data.ptr = (void *) me;
+
+ if (epoll_ctl(mn->fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ if (errno != EEXIST)
+ goto err;
+ }
+ if (me->events & (EPOLLIN | EPOLLET)) {
+ /* Drain initial events generated for /proc/self/mountinfo */
+ struct epoll_event events[1];
+ while (epoll_wait(mn->fd, events, 1, 0) > 0);
+ }
+ } else if (me->fd) {
+ DBG(MONITOR, ul_debugobj(mn, " remove fd=%d (for %s)", me->fd, me->path));
+ if (epoll_ctl(mn->fd, EPOLL_CTL_DEL, me->fd, NULL) < 0) {
+ if (errno != ENOENT)
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ return -errno;
+}
+
+/**
+ * mnt_monitor_close_fd:
+ * @mn: monitor
+ *
+ * Close monitor file descriptor. This is usually unnecessary, because
+ * mnt_unref_monitor() cleanups all.
+ *
+ * The function is necessary only if you want to reset monitor setting. The
+ * next mnt_monitor_get_fd() or mnt_monitor_wait() will use newly initialized
+ * monitor. This restart is unnecessary for mnt_monitor_enable_*() functions.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int mnt_monitor_close_fd(struct libmnt_monitor *mn)
+{
+ struct libmnt_iter itr;
+ struct monitor_entry *me;
+
+ if (!mn)
+ return -EINVAL;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ /* disable all monitor entries */
+ while (monitor_next_entry(mn, &itr, &me) == 0) {
+
+ /* remove entry from epoll */
+ if (mn->fd >= 0)
+ monitor_modify_epoll(mn, me, FALSE);
+
+ /* close entry FD */
+ me->opers->op_close_fd(mn, me);
+ }
+
+ if (mn->fd >= 0) {
+ DBG(MONITOR, ul_debugobj(mn, "closing top-level monitor fd"));
+ close(mn->fd);
+ }
+ mn->fd = -1;
+ return 0;
+}
+
+/**
+ * mnt_monitor_get_fd:
+ * @mn: monitor
+ *
+ * The file descriptor is associated with all monitored files and it's usable
+ * for example for epoll. You have to call mnt_monitor_event_cleanup() or
+ * mnt_monitor_next_change() after each event.
+ *
+ * Returns: >=0 (fd) on success, <0 on error
+ */
+int mnt_monitor_get_fd(struct libmnt_monitor *mn)
+{
+ struct libmnt_iter itr;
+ struct monitor_entry *me;
+ int rc = 0;
+
+ if (!mn)
+ return -EINVAL;
+ if (mn->fd >= 0)
+ return mn->fd;
+
+ DBG(MONITOR, ul_debugobj(mn, "create top-level monitor fd"));
+ mn->fd = epoll_create1(EPOLL_CLOEXEC);
+ if (mn->fd < 0)
+ return -errno;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ DBG(MONITOR, ul_debugobj(mn, "adding monitor entries to epoll (fd=%d)", mn->fd));
+ while (monitor_next_entry(mn, &itr, &me) == 0) {
+ if (!me->enable)
+ continue;
+ rc = monitor_modify_epoll(mn, me, TRUE);
+ if (rc)
+ goto err;
+ }
+
+ DBG(MONITOR, ul_debugobj(mn, "successfully created monitor"));
+ return mn->fd;
+err:
+ rc = errno ? -errno : -EINVAL;
+ close(mn->fd);
+ mn->fd = -1;
+ DBG(MONITOR, ul_debugobj(mn, "failed to create monitor [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * mnt_monitor_wait:
+ * @mn: monitor
+ * @timeout: number of milliseconds, -1 block indefinitely, 0 return immediately
+ *
+ * Waits for the next change, after the event it's recommended to use
+ * mnt_monitor_next_change() to get more details about the change and to
+ * avoid false positive events.
+ *
+ * Returns: 1 success (something changed), 0 timeout, <0 error.
+ */
+int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout)
+{
+ int rc;
+ struct monitor_entry *me;
+ struct epoll_event events[1];
+
+ if (!mn)
+ return -EINVAL;
+
+ if (mn->fd < 0) {
+ rc = mnt_monitor_get_fd(mn);
+ if (rc < 0)
+ return rc;
+ }
+
+ do {
+ DBG(MONITOR, ul_debugobj(mn, "calling epoll_wait(), timeout=%d", timeout));
+ rc = epoll_wait(mn->fd, events, 1, timeout);
+ if (rc < 0)
+ return -errno; /* error */
+ if (rc == 0)
+ return 0; /* timeout */
+
+ me = (struct monitor_entry *) events[0].data.ptr;
+ if (!me)
+ return -EINVAL;
+
+ if (me->opers->op_event_verify == NULL ||
+ me->opers->op_event_verify(mn, me) == 1) {
+ me->changed = 1;
+ break;
+ }
+ } while (1);
+
+ return 1; /* success */
+}
+
+
+static struct monitor_entry *get_changed(struct libmnt_monitor *mn)
+{
+ struct libmnt_iter itr;
+ struct monitor_entry *me;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while (monitor_next_entry(mn, &itr, &me) == 0) {
+ if (me->changed)
+ return me;
+ }
+ return NULL;
+}
+
+/**
+ * mnt_monitor_next_change:
+ * @mn: monitor
+ * @filename: returns changed file (optional argument)
+ * @type: returns MNT_MONITOR_TYPE_* (optional argument)
+ *
+ * The function does not wait and it's designed to provide details about changes.
+ * It's always recommended to use this function to avoid false positives.
+ *
+ * Returns: 0 on success, 1 no change, <0 on error
+ */
+int mnt_monitor_next_change(struct libmnt_monitor *mn,
+ const char **filename,
+ int *type)
+{
+ int rc;
+ struct monitor_entry *me;
+
+ if (!mn || mn->fd < 0)
+ return -EINVAL;
+
+ /*
+ * if we previously called epoll_wait() (e.g. mnt_monitor_wait()) then
+ * info about unread change is already stored in monitor_entry.
+ *
+ * If we get nothing, then ask kernel.
+ */
+ me = get_changed(mn);
+ while (!me) {
+ struct epoll_event events[1];
+
+ DBG(MONITOR, ul_debugobj(mn, "asking for next changed"));
+
+ rc = epoll_wait(mn->fd, events, 1, 0); /* no timeout! */
+ if (rc < 0) {
+ DBG(MONITOR, ul_debugobj(mn, " *** error"));
+ return -errno;
+ }
+ if (rc == 0) {
+ DBG(MONITOR, ul_debugobj(mn, " *** nothing"));
+ return 1;
+ }
+
+ me = (struct monitor_entry *) events[0].data.ptr;
+ if (!me)
+ return -EINVAL;
+
+ if (me->opers->op_event_verify != NULL &&
+ me->opers->op_event_verify(mn, me) != 1)
+ me = NULL;
+ }
+
+ me->changed = 0;
+
+ if (filename)
+ *filename = me->path;
+ if (type)
+ *type = me->type;
+
+ DBG(MONITOR, ul_debugobj(mn, " *** success [changed: %s]", me->path));
+ return 0;
+}
+
+/**
+ * mnt_monitor_event_cleanup:
+ * @mn: monitor
+ *
+ * This function cleanups (drain) internal buffers. It's necessary to call
+ * this function after event if you do not call mnt_monitor_next_change().
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int mnt_monitor_event_cleanup(struct libmnt_monitor *mn)
+{
+ int rc;
+
+ if (!mn || mn->fd < 0)
+ return -EINVAL;
+
+ while ((rc = mnt_monitor_next_change(mn, NULL, NULL)) == 0);
+ return rc < 0 ? rc : 0;
+}
+
+#ifdef TEST_PROGRAM
+
+static struct libmnt_monitor *create_test_monitor(int argc, char *argv[])
+{
+ struct libmnt_monitor *mn;
+ int i;
+
+ mn = mnt_new_monitor();
+ if (!mn) {
+ warn("failed to allocate monitor");
+ goto err;
+ }
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "userspace") == 0) {
+ if (mnt_monitor_enable_userspace(mn, TRUE, NULL)) {
+ warn("failed to initialize userspace monitor");
+ goto err;
+ }
+
+ } else if (strcmp(argv[i], "kernel") == 0) {
+ if (mnt_monitor_enable_kernel(mn, TRUE)) {
+ warn("failed to initialize kernel monitor");
+ goto err;
+ }
+ }
+ }
+ if (i == 1) {
+ warnx("No monitor type specified");
+ goto err;
+ }
+
+ return mn;
+err:
+ mnt_unref_monitor(mn);
+ return NULL;
+}
+
+/*
+ * create a monitor and add the monitor fd to epoll
+ */
+static int __test_epoll(struct libmnt_test *ts, int argc, char *argv[], int cleanup)
+{
+ int fd, efd = -1, rc = -1;
+ struct epoll_event ev;
+ struct libmnt_monitor *mn = create_test_monitor(argc, argv);
+
+ if (!mn)
+ return -1;
+
+ fd = mnt_monitor_get_fd(mn);
+ if (fd < 0) {
+ warn("failed to initialize monitor fd");
+ goto done;
+ }
+
+ efd = epoll_create1(EPOLL_CLOEXEC);
+ if (efd < 0) {
+ warn("failed to create epoll");
+ goto done;
+ }
+
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+
+ rc = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
+ if (rc < 0) {
+ warn("failed to add fd to epoll");
+ goto done;
+ }
+
+ printf("waiting for changes...\n");
+ do {
+ const char *filename = NULL;
+ struct epoll_event events[1];
+ int n = epoll_wait(efd, events, 1, -1);
+
+ if (n < 0) {
+ rc = -errno;
+ warn("polling error");
+ goto done;
+ }
+ if (n == 0 || events[0].data.fd != fd)
+ continue;
+
+ printf(" top-level FD active\n");
+ if (cleanup)
+ mnt_monitor_event_cleanup(mn);
+ else {
+ while (mnt_monitor_next_change(mn, &filename, NULL) == 0)
+ printf(" %s: change detected\n", filename);
+ }
+ } while (1);
+
+ rc = 0;
+done:
+ if (efd >= 0)
+ close(efd);
+ mnt_unref_monitor(mn);
+ return rc;
+}
+
+/*
+ * create a monitor and add the monitor fd to epoll
+ */
+static int test_epoll(struct libmnt_test *ts, int argc, char *argv[])
+{
+ return __test_epoll(ts, argc, argv, 0);
+}
+
+static int test_epoll_cleanup(struct libmnt_test *ts, int argc, char *argv[])
+{
+ return __test_epoll(ts, argc, argv, 1);
+}
+
+/*
+ * create a monitor and wait for a change
+ */
+static int test_wait(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *filename;
+ struct libmnt_monitor *mn = create_test_monitor(argc, argv);
+
+ if (!mn)
+ return -1;
+
+ printf("waiting for changes...\n");
+ while (mnt_monitor_wait(mn, -1) > 0) {
+ printf("notification detected\n");
+
+ while (mnt_monitor_next_change(mn, &filename, NULL) == 0)
+ printf(" %s: change detected\n", filename);
+
+ }
+ mnt_unref_monitor(mn);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--epoll", test_epoll, "<userspace kernel ...> monitor in epoll" },
+ { "--epoll-clean", test_epoll_cleanup, "<userspace kernel ...> monitor in epoll and clean events" },
+ { "--wait", test_wait, "<userspace kernel ...> monitor wait function" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
new file mode 100644
index 0000000..908bf90
--- /dev/null
+++ b/libmount/src/mountP.h
@@ -0,0 +1,485 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * mountP.h - private library header file
+ *
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+#ifndef _LIBMOUNT_PRIVATE_H
+#define _LIBMOUNT_PRIVATE_H
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "c.h"
+#include "list.h"
+#include "debug.h"
+#include "libmount.h"
+
+/*
+ * Debug
+ */
+#define MNT_DEBUG_HELP (1 << 0)
+#define MNT_DEBUG_INIT (1 << 1)
+#define MNT_DEBUG_CACHE (1 << 2)
+#define MNT_DEBUG_OPTIONS (1 << 3)
+#define MNT_DEBUG_LOCKS (1 << 4)
+#define MNT_DEBUG_TAB (1 << 5)
+#define MNT_DEBUG_FS (1 << 6)
+#define MNT_DEBUG_UPDATE (1 << 7)
+#define MNT_DEBUG_UTILS (1 << 8)
+#define MNT_DEBUG_CXT (1 << 9)
+#define MNT_DEBUG_DIFF (1 << 10)
+#define MNT_DEBUG_MONITOR (1 << 11)
+#define MNT_DEBUG_BTRFS (1 << 12)
+#define MNT_DEBUG_LOOP (1 << 13)
+#define MNT_DEBUG_VERITY (1 << 14)
+
+#define MNT_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(libmount);
+#define DBG(m, x) __UL_DBG(libmount, MNT_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(libmount, MNT_DEBUG_, m, x)
+#define DBG_FLUSH __UL_DBG_FLUSH(libmount, MNT_DEBUG_)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libmount)
+#include "debugobj.h"
+
+/*
+ * NLS -- the library has to be independent on main program, so define
+ * UL_TEXTDOMAIN_EXPLICIT before you include nls.h.
+ *
+ * Now we use util-linux.po (=PACKAGE), rather than maintain the texts
+ * in the separate libmount.po file.
+ */
+#define LIBMOUNT_TEXTDOMAIN PACKAGE
+#define UL_TEXTDOMAIN_EXPLICIT LIBMOUNT_TEXTDOMAIN
+#include "nls.h"
+
+
+/* extension for files in the directory */
+#define MNT_MNTTABDIR_EXT ".fstab"
+
+/* library private paths */
+#define MNT_RUNTIME_TOPDIR "/run"
+/* private userspace mount table */
+#define MNT_PATH_UTAB MNT_RUNTIME_TOPDIR "/mount/utab"
+/* temporary mount target */
+#define MNT_PATH_TMPTGT MNT_RUNTIME_TOPDIR "/mount/tmptgt"
+
+#define MNT_UTAB_HEADER "# libmount utab file\n"
+
+#ifdef TEST_PROGRAM
+struct libmnt_test {
+ const char *name;
+ int (*body)(struct libmnt_test *ts, int argc, char *argv[]);
+ const char *usage;
+};
+
+/* test.c */
+extern int mnt_run_test(struct libmnt_test *tests, int argc, char *argv[]);
+#endif
+
+/* utils.c */
+extern int mnt_valid_tagname(const char *tagname);
+
+extern const char *mnt_statfs_get_fstype(struct statfs *vfs);
+extern int is_file_empty(const char *name);
+
+extern int mnt_is_readonly(const char *path)
+ __attribute__((nonnull));
+
+extern int mnt_parse_offset(const char *str, size_t len, uintmax_t *res);
+
+extern int mnt_chdir_to_parent(const char *target, char **filename);
+
+extern char *mnt_get_username(const uid_t uid);
+extern int mnt_get_uid(const char *username, uid_t *uid);
+extern int mnt_get_gid(const char *groupname, gid_t *gid);
+extern int mnt_in_group(gid_t gid);
+
+extern int mnt_open_uniq_filename(const char *filename, char **name);
+
+extern int mnt_has_regular_utab(const char **utab, int *writable);
+extern const char *mnt_get_utab_path(void);
+
+extern int mnt_get_filesystems(char ***filesystems, const char *pattern);
+extern void mnt_free_filesystems(char **filesystems);
+
+extern char *mnt_get_kernel_cmdline_option(const char *name);
+extern int mnt_stat_mountpoint(const char *target, struct stat *st);
+extern int mnt_lstat_mountpoint(const char *target, struct stat *st);
+
+extern int mnt_tmptgt_unshare(int *old_ns_fd);
+extern int mnt_tmptgt_cleanup(int old_ns_fd);
+
+/* tab.c */
+extern int is_mountinfo(struct libmnt_table *tb);
+extern int mnt_table_set_parser_fltrcb( struct libmnt_table *tb,
+ int (*cb)(struct libmnt_fs *, void *),
+ void *data);
+
+extern int __mnt_table_parse_mtab(struct libmnt_table *tb,
+ const char *filename,
+ struct libmnt_table *u_tb);
+
+extern struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
+ struct libmnt_fs *fs,
+ unsigned long mountflags,
+ char **fsroot);
+
+extern int __mnt_table_is_fs_mounted( struct libmnt_table *tb,
+ struct libmnt_fs *fstab_fs,
+ const char *tgt_prefix);
+
+/*
+ * Generic iterator
+ */
+struct libmnt_iter {
+ struct list_head *p; /* current position */
+ struct list_head *head; /* start position */
+ int direction; /* MNT_ITER_{FOR,BACK}WARD */
+};
+
+#define IS_ITER_FORWARD(_i) ((_i)->direction == MNT_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i) ((_i)->direction == MNT_ITER_BACKWARD)
+
+#define MNT_ITER_INIT(itr, list) \
+ do { \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (list)->next : (list)->prev; \
+ (itr)->head = (list); \
+ } while(0)
+
+#define MNT_ITER_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+
+/*
+ * This struct represents one entry in a mtab/fstab/mountinfo file.
+ * (note that fstab[1] means the first column from fstab, and so on...)
+ */
+struct libmnt_fs {
+ struct list_head ents;
+ struct libmnt_table *tab;
+
+ int refcount; /* reference counter */
+ int id; /* mountinfo[1]: ID */
+ int parent; /* mountinfo[2]: parent */
+ dev_t devno; /* mountinfo[3]: st_dev */
+
+ char *bindsrc; /* utab, full path from fstab[1] for bind mounts */
+
+ char *source; /* fstab[1], mountinfo[10], swaps[1]:
+ * source dev, file, dir or TAG */
+ char *tagname; /* fstab[1]: tag name - "LABEL", "UUID", ..*/
+ char *tagval; /* tag value */
+
+ char *root; /* mountinfo[4]: root of the mount within the FS */
+ char *target; /* mountinfo[5], fstab[2]: mountpoint */
+ char *fstype; /* mountinfo[9], fstab[3]: filesystem type */
+
+ char *optstr; /* fstab[4], merged options */
+ char *vfs_optstr; /* mountinfo[6]: fs-independent (VFS) options */
+ char *opt_fields; /* mountinfo[7]: optional fields */
+ char *fs_optstr; /* mountinfo[11]: fs-dependent options */
+ char *user_optstr; /* userspace mount options */
+ char *attrs; /* mount attributes */
+
+ int freq; /* fstab[5]: dump frequency in days */
+ int passno; /* fstab[6]: pass number on parallel fsck */
+
+ /* /proc/swaps */
+ char *swaptype; /* swaps[2]: device type (partition, file, ...) */
+ off_t size; /* swaps[3]: swaparea size */
+ off_t usedsize; /* swaps[4]: used size */
+ int priority; /* swaps[5]: swap priority */
+
+ int flags; /* MNT_FS_* flags */
+ pid_t tid; /* /proc/<tid>/mountinfo otherwise zero */
+
+ char *comment; /* fstab comment */
+
+ void *userdata; /* library independent data */
+};
+
+/*
+ * fs flags
+ */
+#define MNT_FS_PSEUDO (1 << 1) /* pseudo filesystem */
+#define MNT_FS_NET (1 << 2) /* network filesystem */
+#define MNT_FS_SWAP (1 << 3) /* swap device */
+#define MNT_FS_KERNEL (1 << 4) /* data from /proc/{mounts,self/mountinfo} */
+#define MNT_FS_MERGED (1 << 5) /* already merged data from /run/mount/utab */
+
+/*
+ * mtab/fstab/mountinfo file
+ */
+struct libmnt_table {
+ int fmt; /* MNT_FMT_* file format */
+ int nents; /* number of entries */
+ int refcount; /* reference counter */
+ int comms; /* enable/disable comment parsing */
+ char *comm_intro; /* First comment in file */
+ char *comm_tail; /* Last comment in file */
+
+ struct libmnt_cache *cache; /* canonicalized paths/tags cache */
+
+ int (*errcb)(struct libmnt_table *tb,
+ const char *filename, int line);
+
+ int (*fltrcb)(struct libmnt_fs *fs, void *data);
+ void *fltrcb_data;
+
+
+ struct list_head ents; /* list of entries (libmnt_fs) */
+ void *userdata;
+};
+
+extern struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent);
+
+/*
+ * Tab file format
+ */
+enum {
+ MNT_FMT_GUESS,
+ MNT_FMT_FSTAB, /* /etc/{fs,m}tab */
+ MNT_FMT_MTAB = MNT_FMT_FSTAB, /* alias */
+ MNT_FMT_MOUNTINFO, /* /proc/#/mountinfo */
+ MNT_FMT_UTAB, /* /run/mount/utab */
+ MNT_FMT_SWAPS /* /proc/swaps */
+};
+
+/*
+ * Additional mounts
+ */
+struct libmnt_addmount {
+ unsigned long mountflags;
+
+ struct list_head mounts;
+};
+
+struct libmnt_ns {
+ int fd; /* file descriptor of namespace, -1 when inactive */
+ struct libmnt_cache *cache; /* paths cache associated with NS */
+};
+
+/*
+ * Mount context -- high-level API
+ */
+struct libmnt_context
+{
+ int action; /* MNT_ACT_{MOUNT,UMOUNT} */
+ int restricted; /* root or not? */
+
+ char *fstype_pattern; /* for mnt_match_fstype() */
+ char *optstr_pattern; /* for mnt_match_options() */
+
+ char *subdir; /* X-mount.subdir= */
+
+ struct libmnt_fs *fs; /* filesystem description (type, mountpoint, device, ...) */
+ struct libmnt_fs *fs_template; /* used for @fs on mnt_reset_context() */
+
+ struct libmnt_table *fstab; /* fstab (or mtab for some remounts) entries */
+ struct libmnt_table *mtab; /* mtab entries */
+ struct libmnt_table *utab; /* rarely used by umount only */
+
+ int (*table_errcb)(struct libmnt_table *tb, /* callback for libmnt_table structs */
+ const char *filename, int line);
+
+ int (*table_fltrcb)(struct libmnt_fs *fs, void *data); /* callback for libmnt_table structs */
+ void *table_fltrcb_data;
+
+ char *(*pwd_get_cb)(struct libmnt_context *); /* get encryption password */
+ void (*pwd_release_cb)(struct libmnt_context *, char *); /* release password */
+
+ int optsmode; /* fstab optstr mode MNT_OPTSMODE_{AUTO,FORCE,IGNORE} */
+ int loopdev_fd; /* open loopdev */
+
+ unsigned long mountflags; /* final mount(2) flags */
+ const void *mountdata; /* final mount(2) data, string or binary data */
+
+ unsigned long user_mountflags; /* MNT_MS_* (loop=, user=, ...) */
+
+ struct list_head addmounts; /* additional mounts */
+
+ struct libmnt_cache *cache; /* paths cache */
+ struct libmnt_lock *lock; /* mtab lock */
+ struct libmnt_update *update;/* mtab/utab update */
+
+ const char *mtab_path; /* path to mtab */
+ int mtab_writable; /* is mtab writable */
+
+ const char *utab_path; /* path to utab */
+ int utab_writable; /* is utab writable */
+
+ char *tgt_prefix; /* path used for all targets */
+
+ int flags; /* private context flags */
+
+ char *helper; /* name of the used /sbin/[u]mount.<type> helper */
+ int helper_status; /* helper wait(2) status */
+ int helper_exec_status; /* 1: not called yet, 0: success, <0: -errno */
+
+ char *orig_user; /* original (non-fixed) user= option */
+
+ pid_t *children; /* "mount -a --fork" PIDs */
+ int nchildren; /* number of children */
+ pid_t pid; /* 0=parent; PID=child */
+
+
+ int syscall_status; /* 1: not called yet, 0: success, <0: -errno */
+
+ struct libmnt_ns ns_orig; /* original namespace */
+ struct libmnt_ns ns_tgt; /* target namespace */
+ struct libmnt_ns *ns_cur; /* pointer to current namespace */
+
+ unsigned int enabled_textdomain : 1; /* bindtextdomain() called */
+};
+
+/* flags */
+#define MNT_FL_NOMTAB (1 << 1)
+#define MNT_FL_FAKE (1 << 2)
+#define MNT_FL_SLOPPY (1 << 3)
+#define MNT_FL_VERBOSE (1 << 4)
+#define MNT_FL_NOHELPERS (1 << 5)
+#define MNT_FL_LOOPDEL (1 << 6)
+#define MNT_FL_LAZY (1 << 7)
+#define MNT_FL_FORCE (1 << 8)
+#define MNT_FL_NOCANONICALIZE (1 << 9)
+#define MNT_FL_RDONLY_UMOUNT (1 << 11) /* remount,ro after EBUSY umount(2) */
+#define MNT_FL_FORK (1 << 12)
+#define MNT_FL_NOSWAPMATCH (1 << 13)
+#define MNT_FL_RWONLY_MOUNT (1 << 14) /* explicit mount -w; never try read-only */
+
+#define MNT_FL_MOUNTDATA (1 << 20)
+#define MNT_FL_TAB_APPLIED (1 << 21) /* mtab/fstab merged to cxt->fs */
+#define MNT_FL_MOUNTFLAGS_MERGED (1 << 22) /* MS_* flags was read from optstr */
+#define MNT_FL_SAVED_USER (1 << 23)
+#define MNT_FL_PREPARED (1 << 24)
+#define MNT_FL_HELPER (1 << 25) /* [u]mount.<type> */
+#define MNT_FL_LOOPDEV_READY (1 << 26) /* /dev/loop<N> initialized by the library */
+#define MNT_FL_MOUNTOPTS_FIXED (1 << 27)
+#define MNT_FL_TABPATHS_CHECKED (1 << 28)
+#define MNT_FL_FORCED_RDONLY (1 << 29) /* mounted read-only on write-protected device */
+#define MNT_FL_VERITYDEV_READY (1 << 30) /* /dev/mapper/<FOO> initialized by the library */
+
+/* default flags */
+#define MNT_FL_DEFAULT 0
+
+/* Flags usable with MS_BIND|MS_REMOUNT */
+#define MNT_BIND_SETTABLE (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_NOATIME|MS_NODIRATIME|MS_RELATIME|MS_RDONLY|MS_NOSYMFOLLOW)
+
+/* lock.c */
+extern int mnt_lock_use_simplelock(struct libmnt_lock *ml, int enable);
+
+/* optmap.c */
+extern const struct libmnt_optmap *mnt_optmap_get_entry(
+ struct libmnt_optmap const **maps,
+ int nmaps,
+ const char *name,
+ size_t namelen,
+ const struct libmnt_optmap **mapent);
+
+/* optstr.c */
+extern int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid);
+extern int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end);
+extern int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_secontext(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_user(char **optstr);
+
+/* fs.c */
+extern struct libmnt_fs *mnt_copy_mtab_fs(const struct libmnt_fs *fs)
+ __attribute__((nonnull));
+extern int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source)
+ __attribute__((nonnull(1)));
+extern int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype)
+ __attribute__((nonnull(1)));
+
+/* context.c */
+extern struct libmnt_context *mnt_copy_context(struct libmnt_context *o);
+extern int mnt_context_mtab_writable(struct libmnt_context *cxt);
+extern int mnt_context_utab_writable(struct libmnt_context *cxt);
+extern const char *mnt_context_get_writable_tabpath(struct libmnt_context *cxt);
+
+extern int mnt_context_get_mtab_for_target(struct libmnt_context *cxt,
+ struct libmnt_table **mtab, const char *tgt);
+
+extern int mnt_context_prepare_srcpath(struct libmnt_context *cxt);
+extern int mnt_context_prepare_target(struct libmnt_context *cxt);
+extern int mnt_context_guess_srcpath_fstype(struct libmnt_context *cxt, char **type);
+extern int mnt_context_guess_fstype(struct libmnt_context *cxt);
+extern int mnt_context_prepare_helper(struct libmnt_context *cxt,
+ const char *name, const char *type);
+extern int mnt_context_prepare_update(struct libmnt_context *cxt);
+extern int mnt_context_merge_mflags(struct libmnt_context *cxt);
+extern int mnt_context_update_tabs(struct libmnt_context *cxt);
+
+extern int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg);
+extern int mnt_context_mount_setopt(struct libmnt_context *cxt, int c, char *arg);
+
+extern int mnt_context_is_loopdev(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+
+extern int mnt_context_propagation_only(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+
+extern struct libmnt_addmount *mnt_new_addmount(void);
+extern void mnt_free_addmount(struct libmnt_addmount *ad);
+
+extern int mnt_context_setup_loopdev(struct libmnt_context *cxt);
+extern int mnt_context_delete_loopdev(struct libmnt_context *cxt);
+extern int mnt_context_clear_loopdev(struct libmnt_context *cxt);
+
+extern int mnt_fork_context(struct libmnt_context *cxt);
+
+extern int mnt_context_set_tabfilter(struct libmnt_context *cxt,
+ int (*fltr)(struct libmnt_fs *, void *),
+ void *data);
+
+extern int mnt_context_get_generic_excode(int rc, char *buf, size_t bufsz, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 4, 5)));
+extern int mnt_context_get_mount_excode(struct libmnt_context *cxt, int mntrc, char *buf, size_t bufsz);
+extern int mnt_context_get_umount_excode(struct libmnt_context *cxt, int mntrc, char *buf, size_t bufsz);
+
+extern int mnt_context_has_template(struct libmnt_context *cxt);
+extern int mnt_context_apply_template(struct libmnt_context *cxt);
+extern int mnt_context_save_template(struct libmnt_context *cxt);
+
+extern int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs);
+
+extern int mnt_context_is_veritydev(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+extern int mnt_context_setup_veritydev(struct libmnt_context *cxt);
+extern int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt);
+
+/* tab_update.c */
+extern int mnt_update_set_filename(struct libmnt_update *upd,
+ const char *filename, int userspace_only);
+extern int mnt_update_already_done(struct libmnt_update *upd,
+ struct libmnt_lock *lc);
+
+#if __linux__
+/* btrfs.c */
+extern uint64_t btrfs_get_default_subvol_id(const char *path);
+#endif
+
+#endif /* _LIBMOUNT_PRIVATE_H */
diff --git a/libmount/src/optmap.c b/libmount/src/optmap.c
new file mode 100644
index 0000000..6d0db2b
--- /dev/null
+++ b/libmount/src/optmap.c
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: optmap
+ * @title: Option maps
+ * @short_description: description for mount options
+ *
+ * The mount(2) linux syscall uses two arguments for mount options:
+ *
+ * @mountflags: (see MS_* macros in linux/fs.h)
+ *
+ * @mountdata: (usually a comma separated string of options)
+ *
+ * The libmount uses options-map(s) to describe mount options.
+ *
+ * The option description (map entry) includes:
+ *
+ * @name: and argument name
+ *
+ * @id: (in the map unique identifier or a mountflags, e.g MS_RDONLY)
+ *
+ * @mask: (MNT_INVERT, MNT_NOMTAB)
+ *
+ * The option argument value is defined by:
+ *
+ * "=" -- required argument, e.g "comment="
+ *
+ * "[=]" -- optional argument, e.g. "loop[=]"
+ *
+ * Example:
+ *
+ * <informalexample>
+ * <programlisting>
+ * #define MY_MS_FOO (1 << 1)
+ * #define MY_MS_BAR (1 << 2)
+ *
+ * libmnt_optmap myoptions[] = {
+ * { "foo", MY_MS_FOO },
+ * { "nofoo", MY_MS_FOO | MNT_INVERT },
+ * { "bar=", MY_MS_BAR },
+ * { NULL }
+ * };
+ * </programlisting>
+ * </informalexample>
+ *
+ * The libmount defines two basic built-in options maps:
+ *
+ * @MNT_LINUX_MAP: fs-independent kernel mount options (usually MS_* flags)
+ *
+ * @MNT_USERSPACE_MAP: userspace specific mount options (e.g. "user", "loop")
+ *
+ * For more details about option map struct see "struct mnt_optmap" in
+ * mount/mount.h.
+ */
+#include "mountP.h"
+#include "strutils.h"
+
+/*
+ * fs-independent mount flags (built-in MNT_LINUX_MAP)
+ */
+static const struct libmnt_optmap linux_flags_map[] =
+{
+ { "ro", MS_RDONLY }, /* read-only */
+ { "rw", MS_RDONLY, MNT_INVERT }, /* read-write */
+ { "exec", MS_NOEXEC, MNT_INVERT }, /* permit execution of binaries */
+ { "noexec", MS_NOEXEC }, /* don't execute binaries */
+ { "suid", MS_NOSUID, MNT_INVERT }, /* honor suid executables */
+ { "nosuid", MS_NOSUID }, /* don't honor suid executables */
+ { "dev", MS_NODEV, MNT_INVERT }, /* interpret device files */
+ { "nodev", MS_NODEV }, /* don't interpret devices */
+
+ { "sync", MS_SYNCHRONOUS }, /* synchronous I/O */
+ { "async", MS_SYNCHRONOUS, MNT_INVERT },/* asynchronous I/O */
+
+ { "dirsync", MS_DIRSYNC }, /* synchronous directory modifications */
+ { "remount", MS_REMOUNT, MNT_NOMTAB }, /* alter flags of mounted FS */
+ { "bind", MS_BIND }, /* Remount part of the tree elsewhere */
+ { "rbind", MS_BIND | MS_REC }, /* Idem, plus mounted subtrees */
+#ifdef MS_NOSUB
+ { "sub", MS_NOSUB, MNT_INVERT }, /* allow submounts */
+ { "nosub", MS_NOSUB }, /* don't allow submounts */
+#endif
+#ifdef MS_SILENT
+ { "silent", MS_SILENT }, /* be quiet */
+ { "loud", MS_SILENT, MNT_INVERT }, /* print out messages. */
+#endif
+#ifdef MS_MANDLOCK
+ { "mand", MS_MANDLOCK }, /* Allow mandatory locks on this FS */
+ { "nomand", MS_MANDLOCK, MNT_INVERT }, /* Forbid mandatory locks on this FS */
+#endif
+#ifdef MS_NOATIME
+ { "atime", MS_NOATIME, MNT_INVERT }, /* Update access time */
+ { "noatime", MS_NOATIME }, /* Do not update access time */
+#endif
+#ifdef MS_I_VERSION
+ { "iversion", MS_I_VERSION }, /* Update inode I_version time */
+ { "noiversion", MS_I_VERSION, MNT_INVERT},/* Don't update inode I_version time */
+#endif
+#ifdef MS_NODIRATIME
+ { "diratime", MS_NODIRATIME, MNT_INVERT }, /* Update dir access times */
+ { "nodiratime", MS_NODIRATIME }, /* Do not update dir access times */
+#endif
+#ifdef MS_RELATIME
+ { "relatime", MS_RELATIME }, /* Update access times relative to mtime/ctime */
+ { "norelatime", MS_RELATIME, MNT_INVERT }, /* Update access time without regard to mtime/ctime */
+#endif
+#ifdef MS_STRICTATIME
+ { "strictatime", MS_STRICTATIME }, /* Strict atime semantics */
+ { "nostrictatime", MS_STRICTATIME, MNT_INVERT }, /* kernel default atime */
+#endif
+#ifdef MS_LAZYTIME
+ { "lazytime", MS_LAZYTIME }, /* Update {a,m,c}time on the in-memory inode only */
+ { "nolazytime", MS_LAZYTIME, MNT_INVERT },
+#endif
+#ifdef MS_PROPAGATION
+ { "unbindable", MS_UNBINDABLE, MNT_NOHLPS | MNT_NOMTAB }, /* Unbindable */
+ { "runbindable", MS_UNBINDABLE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
+ { "private", MS_PRIVATE, MNT_NOHLPS | MNT_NOMTAB }, /* Private */
+ { "rprivate", MS_PRIVATE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
+ { "slave", MS_SLAVE, MNT_NOHLPS | MNT_NOMTAB }, /* Slave */
+ { "rslave", MS_SLAVE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
+ { "shared", MS_SHARED, MNT_NOHLPS | MNT_NOMTAB }, /* Shared */
+ { "rshared", MS_SHARED | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
+#endif
+#ifdef MS_NOSYMFOLLOW
+ { "symfollow", MS_NOSYMFOLLOW, MNT_INVERT }, /* Don't follow symlinks */
+ { "nosymfollow", MS_NOSYMFOLLOW },
+#endif
+ { NULL, 0, 0 }
+};
+
+/*
+ * userspace mount option (built-in MNT_USERSPACE_MAP)
+ */
+static const struct libmnt_optmap userspace_opts_map[] =
+{
+ { "defaults", 0, 0 }, /* default options */
+
+ { "auto", MNT_MS_NOAUTO, MNT_NOHLPS | MNT_INVERT | MNT_NOMTAB }, /* Can be mounted using -a */
+ { "noauto", MNT_MS_NOAUTO, MNT_NOHLPS | MNT_NOMTAB }, /* Can only be mounted explicitly */
+
+ { "user[=]", MNT_MS_USER }, /* Allow ordinary user to mount (mtab) */
+ { "nouser", MNT_MS_USER, MNT_INVERT | MNT_NOMTAB }, /* Forbid ordinary user to mount */
+
+ { "users", MNT_MS_USERS, MNT_NOMTAB }, /* Allow ordinary users to mount */
+ { "nousers", MNT_MS_USERS, MNT_INVERT | MNT_NOMTAB }, /* Forbid ordinary users to mount */
+
+ { "owner", MNT_MS_OWNER, MNT_NOMTAB }, /* Let the owner of the device mount */
+ { "noowner", MNT_MS_OWNER, MNT_INVERT | MNT_NOMTAB }, /* Device owner has no special privs */
+
+ { "group", MNT_MS_GROUP, MNT_NOMTAB }, /* Let the group of the device mount */
+ { "nogroup", MNT_MS_GROUP, MNT_INVERT | MNT_NOMTAB }, /* Device group has no special privs */
+
+ /*
+ * Note that traditional init scripts assume the _netdev option in /etc/mtab to
+ * umount network block devices on shutdown.
+ */
+ { "_netdev", MNT_MS_NETDEV }, /* Device requires network */
+
+ { "comment=", MNT_MS_COMMENT, MNT_NOHLPS | MNT_NOMTAB },/* fstab comment only */
+
+ { "x-", MNT_MS_XCOMMENT, MNT_NOHLPS | MNT_PREFIX }, /* persistent comments (utab) */
+ { "X-", MNT_MS_XFSTABCOMM, MNT_NOHLPS | MNT_NOMTAB | MNT_PREFIX }, /* fstab only comments */
+
+ { "loop[=]", MNT_MS_LOOP, MNT_NOHLPS }, /* use the loop device */
+ { "offset=", MNT_MS_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* loop device offset */
+ { "sizelimit=", MNT_MS_SIZELIMIT, MNT_NOHLPS | MNT_NOMTAB }, /* loop device size limit */
+ { "encryption=", MNT_MS_ENCRYPTION, MNT_NOHLPS | MNT_NOMTAB }, /* loop device encryption */
+
+ { "nofail", MNT_MS_NOFAIL, MNT_NOMTAB }, /* Do not fail if ENOENT on dev */
+
+ { "uhelper=", MNT_MS_UHELPER }, /* /sbin/umount.<helper> */
+
+ { "helper=", MNT_MS_HELPER }, /* /sbin/mount.<helper> */
+
+ { "verity.hashdevice=", MNT_MS_HASH_DEVICE, MNT_NOHLPS | MNT_NOMTAB }, /* mount a verity device */
+ { "verity.roothash=", MNT_MS_ROOT_HASH, MNT_NOHLPS | MNT_NOMTAB }, /* verity device root hash */
+ { "verity.hashoffset=", MNT_MS_HASH_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* verity device hash offset */
+ { "verity.roothashfile=", MNT_MS_ROOT_HASH_FILE, MNT_NOHLPS | MNT_NOMTAB },/* verity device root hash (read from file) */
+ { "verity.fecdevice=", MNT_MS_FEC_DEVICE, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC device */
+ { "verity.fecoffset=", MNT_MS_FEC_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC area offset */
+ { "verity.fecroots=", MNT_MS_FEC_ROOTS, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC roots */
+ { "verity.roothashsig=", MNT_MS_ROOT_HASH_SIG, MNT_NOHLPS | MNT_NOMTAB }, /* verity device root hash signature file */
+ { "verity.oncorruption=", MNT_MS_VERITY_ON_CORRUPTION, MNT_NOHLPS | MNT_NOMTAB }, /* verity: action the kernel takes on corruption */
+
+ { NULL, 0, 0 }
+};
+
+/**
+ * mnt_get_builtin_map:
+ * @id: map id -- MNT_LINUX_MAP or MNT_USERSPACE_MAP
+ *
+ * MNT_LINUX_MAP - Linux kernel fs-independent mount options
+ * (usually MS_* flags, see linux/fs.h)
+ *
+ * MNT_USERSPACE_MAP - userspace mount(8) specific mount options
+ * (e.g user=, _netdev, ...)
+ *
+ * Returns: static built-in libmount map.
+ */
+const struct libmnt_optmap *mnt_get_builtin_optmap(int id)
+{
+ assert(id);
+
+ if (id == MNT_LINUX_MAP)
+ return linux_flags_map;
+ if (id == MNT_USERSPACE_MAP)
+ return userspace_opts_map;
+ return NULL;
+}
+
+/*
+ * Looks up the @name in @maps and returns a map and in @mapent
+ * returns the map entry
+ */
+const struct libmnt_optmap *mnt_optmap_get_entry(
+ struct libmnt_optmap const **maps,
+ int nmaps,
+ const char *name,
+ size_t namelen,
+ const struct libmnt_optmap **mapent)
+{
+ int i;
+
+ assert(maps);
+ assert(nmaps);
+ assert(name);
+ assert(namelen);
+
+ if (mapent)
+ *mapent = NULL;
+
+ for (i = 0; i < nmaps; i++) {
+ const struct libmnt_optmap *map = maps[i];
+ const struct libmnt_optmap *ent;
+ const char *p;
+
+ for (ent = map; ent && ent->name; ent++) {
+ if (ent->mask & MNT_PREFIX) {
+ if (startswith(name, ent->name)) {
+ if (mapent)
+ *mapent = ent;
+ return map;
+ }
+ continue;
+ }
+ if (strncmp(ent->name, name, namelen) != 0)
+ continue;
+ p = ent->name + namelen;
+ if (*p == '\0' || *p == '=' || *p == '[') {
+ if (mapent)
+ *mapent = ent;
+ return map;
+ }
+ }
+ }
+ return NULL;
+}
+
diff --git a/libmount/src/optstr.c b/libmount/src/optstr.c
new file mode 100644
index 0000000..5acc94e
--- /dev/null
+++ b/libmount/src/optstr.c
@@ -0,0 +1,1482 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: optstr
+ * @title: Options string
+ * @short_description: low-level API for working with mount options
+ *
+ * This is a simple and low-level API to working with mount options that are stored
+ * in a string.
+ */
+#include <ctype.h>
+
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#endif
+
+#include "strutils.h"
+#include "mountP.h"
+#include "buffer.h"
+
+/*
+ * Option location
+ */
+struct libmnt_optloc {
+ char *begin;
+ char *end;
+ char *value;
+ size_t valsz;
+ size_t namesz;
+};
+
+#define MNT_INIT_OPTLOC { .begin = NULL }
+
+#define mnt_optmap_entry_novalue(e) \
+ (e && (e)->name && !strchr((e)->name, '=') && !((e)->mask & MNT_PREFIX))
+
+/*
+ * Parses the first option from @optstr. The @optstr pointer is set to the beginning
+ * of the next option.
+ *
+ * Returns -EINVAL on parse error, 1 at the end of optstr and 0 on success.
+ */
+static int mnt_optstr_parse_next(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valsz)
+{
+ int open_quote = 0;
+ char *start = NULL, *stop = NULL, *p, *sep = NULL;
+ char *optstr0;
+
+ assert(optstr);
+ assert(*optstr);
+
+ optstr0 = *optstr;
+
+ if (name)
+ *name = NULL;
+ if (namesz)
+ *namesz = 0;
+ if (value)
+ *value = NULL;
+ if (valsz)
+ *valsz = 0;
+
+ /* trim leading commas as to not invalidate option
+ * strings with multiple consecutive commas */
+ while (optstr0 && *optstr0 == ',')
+ optstr0++;
+
+ for (p = optstr0; p && *p; p++) {
+ if (!start)
+ start = p; /* beginning of the option item */
+ if (*p == '"')
+ open_quote ^= 1; /* reverse the status */
+ if (open_quote)
+ continue; /* still in quoted block */
+ if (!sep && p > start && *p == '=')
+ sep = p; /* name and value separator */
+ if (*p == ',')
+ stop = p; /* terminate the option item */
+ else if (*(p + 1) == '\0')
+ stop = p + 1; /* end of optstr */
+ if (!start || !stop)
+ continue;
+ if (stop <= start)
+ goto error;
+
+ if (name)
+ *name = start;
+ if (namesz)
+ *namesz = sep ? sep - start : stop - start;
+ *optstr = *stop ? stop + 1 : stop;
+
+ if (sep) {
+ if (value)
+ *value = sep + 1;
+ if (valsz)
+ *valsz = stop - sep - 1;
+ }
+ return 0;
+ }
+
+ return 1; /* end of optstr */
+
+error:
+ DBG(OPTIONS, ul_debug("parse error: \"%s\"", optstr0));
+ return -EINVAL;
+}
+
+/*
+ * Locates the first option that matches @name. The @end is set to the
+ * char behind the option (it means ',' or \0).
+ *
+ * Returns negative number on parse error, 1 when not found and 0 on success.
+ */
+static int mnt_optstr_locate_option(char *optstr, const char *name,
+ struct libmnt_optloc *ol)
+{
+ char *n;
+ size_t namesz, nsz;
+ int rc;
+
+ if (!optstr)
+ return 1;
+
+ assert(name);
+
+ namesz = strlen(name);
+
+ do {
+ rc = mnt_optstr_parse_next(&optstr, &n, &nsz,
+ &ol->value, &ol->valsz);
+ if (rc)
+ break;
+
+ if (namesz == nsz && strncmp(n, name, nsz) == 0) {
+ ol->begin = n;
+ ol->end = *(optstr - 1) == ',' ? optstr - 1 : optstr;
+ ol->namesz = nsz;
+ return 0;
+ }
+ } while(1);
+
+ return rc;
+}
+
+/**
+ * mnt_optstr_next_option:
+ * @optstr: option string, returns the position of the next option
+ * @name: returns the option name
+ * @namesz: returns the option name length
+ * @value: returns the option value or NULL
+ * @valuesz: returns the option value length or zero
+ *
+ * Parses the first option in @optstr.
+ *
+ * Returns: 0 on success, 1 at the end of @optstr or negative number in case of
+ * error.
+ */
+int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valuesz)
+{
+ if (!optstr || !*optstr)
+ return -EINVAL;
+ return mnt_optstr_parse_next(optstr, name, namesz, value, valuesz);
+}
+
+static int __buffer_append_option(struct ul_buffer *buf,
+ const char *name, size_t namesz,
+ const char *val, size_t valsz)
+{
+ int rc = 0;
+
+ if (!ul_buffer_is_empty(buf))
+ rc = ul_buffer_append_data(buf, ",", 1);
+ if (!rc)
+ rc = ul_buffer_append_data(buf, name, namesz);
+ if (val && !rc) {
+ /* we need to append '=' is value is empty string, see
+ * 727c689908c5e68c92aa1dd65e0d3bdb6d91c1e5 */
+ rc = ul_buffer_append_data(buf, "=", 1);
+ if (!rc && valsz)
+ rc = ul_buffer_append_data(buf, val, valsz);
+ }
+ return rc;
+}
+
+/**
+ * mnt_optstr_append_option:
+ * @optstr: option string or NULL, returns a reallocated string
+ * @name: value name
+ * @value: value
+ *
+ * Returns: 0 on success or <0 in case of error. After an error the @optstr should
+ * be unmodified.
+ */
+int mnt_optstr_append_option(char **optstr, const char *name, const char *value)
+{
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ int rc;
+ size_t nsz, vsz, osz;
+
+ if (!optstr)
+ return -EINVAL;
+ if (!name || !*name)
+ return 0;
+
+ nsz = strlen(name);
+ osz = *optstr ? strlen(*optstr) : 0;
+ vsz = value ? strlen(value) : 0;
+
+ ul_buffer_refer_string(&buf, *optstr);
+ ul_buffer_set_chunksize(&buf, osz + nsz + vsz + 3); /* to call realloc() only once */
+
+ rc = __buffer_append_option(&buf, name, nsz, value, vsz);
+
+ *optstr = ul_buffer_get_data(&buf, NULL, NULL);
+ return rc;
+}
+/**
+ * mnt_optstr_prepend_option:
+ * @optstr: option string or NULL, returns a reallocated string
+ * @name: value name
+ * @value: value
+ *
+ * Returns: 0 on success or <0 in case of error. After an error the @optstr should
+ * be unmodified.
+ */
+int mnt_optstr_prepend_option(char **optstr, const char *name, const char *value)
+{
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ size_t nsz, vsz, osz;
+ int rc;
+
+ if (!optstr)
+ return -EINVAL;
+ if (!name || !*name)
+ return 0;
+
+ nsz = strlen(name);
+ osz = *optstr ? strlen(*optstr) : 0;
+ vsz = value ? strlen(value) : 0;
+
+ ul_buffer_set_chunksize(&buf, osz + nsz + vsz + 3); /* to call realloc() only once */
+
+ rc = __buffer_append_option(&buf, name, nsz, value, vsz);
+ if (*optstr && !rc) {
+ rc = ul_buffer_append_data(&buf, ",", 1);
+ if (!rc)
+ rc = ul_buffer_append_data(&buf, *optstr, osz);
+ free(*optstr);
+ }
+
+ *optstr = ul_buffer_get_data(&buf, NULL, NULL);
+ return rc;
+}
+
+/**
+ * mnt_optstr_get_option:
+ * @optstr: string with a comma separated list of options
+ * @name: requested option name
+ * @value: returns a pointer to the beginning of the value (e.g. name=VALUE) or NULL
+ * @valsz: returns size of the value or 0
+ *
+ * Returns: 0 on success, 1 when not found the @name or negative number in case
+ * of error.
+ */
+int mnt_optstr_get_option(const char *optstr, const char *name,
+ char **value, size_t *valsz)
+{
+ struct libmnt_optloc ol = MNT_INIT_OPTLOC;
+ int rc;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ rc = mnt_optstr_locate_option((char *) optstr, name, &ol);
+ if (!rc) {
+ if (value)
+ *value = ol.value;
+ if (valsz)
+ *valsz = ol.valsz;
+ }
+ return rc;
+}
+
+/**
+ * mnt_optstr_deduplicate_option:
+ * @optstr: string with a comma separated list of options
+ * @name: requested option name
+ *
+ * Removes all instances of @name except the last one.
+ *
+ * Returns: 0 on success, 1 when not found the @name or negative number in case
+ * of error.
+ */
+int mnt_optstr_deduplicate_option(char **optstr, const char *name)
+{
+ int rc;
+ char *begin = NULL, *end = NULL, *opt;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ opt = *optstr;
+ do {
+ struct libmnt_optloc ol = MNT_INIT_OPTLOC;
+
+ rc = mnt_optstr_locate_option(opt, name, &ol);
+ if (!rc) {
+ if (begin) {
+ /* remove the previous instance */
+ size_t shift = strlen(*optstr);
+
+ mnt_optstr_remove_option_at(optstr, begin, end);
+
+ /* now all the offsets are not valid anymore - recount */
+ shift -= strlen(*optstr);
+ ol.begin -= shift;
+ ol.end -= shift;
+ }
+ begin = ol.begin;
+ end = ol.end;
+ opt = end && *end ? end + 1 : NULL;
+ }
+ if (opt == NULL)
+ break;
+ } while (rc == 0 && *opt);
+
+ return rc < 0 ? rc : begin ? 0 : 1;
+}
+
+/*
+ * The result never starts or ends with a comma or contains two commas
+ * (e.g. ",aaa,bbb" or "aaa,,bbb" or "aaa,")
+ */
+int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end)
+{
+ size_t sz;
+
+ if (!optstr || !begin || !end)
+ return -EINVAL;
+
+ if ((begin == *optstr || *(begin - 1) == ',') && *end == ',')
+ end++;
+
+ sz = strlen(end);
+
+ memmove(begin, end, sz + 1);
+ if (!*begin && (begin > *optstr) && *(begin - 1) == ',')
+ *(begin - 1) = '\0';
+
+ return 0;
+}
+
+/* insert 'substr' or '=substr' to @str on position @pos */
+static int __attribute__((nonnull(1,2,3)))
+insert_value(char **str, char *pos, const char *substr, char **next)
+{
+ size_t subsz = strlen(substr); /* substring size */
+ size_t strsz = strlen(*str);
+ size_t possz = strlen(pos);
+ size_t posoff;
+ char *p;
+ int sep;
+
+ /* is it necessary to prepend '=' before the substring ? */
+ sep = !(pos > *str && *(pos - 1) == '=');
+
+ /* save an offset of the place where we need to add substr */
+ posoff = pos - *str;
+
+ p = realloc(*str, strsz + sep + subsz + 1);
+ if (!p)
+ return -ENOMEM;
+
+ /* zeroize the newly allocated memory -- valgrind loves us... */
+ memset(p + strsz, 0, sep + subsz + 1);
+
+ /* set pointers to the reallocated string */
+ *str = p;
+ pos = p + posoff;
+
+ if (possz)
+ /* create a room for the new substring */
+ memmove(pos + subsz + sep, pos, possz + 1);
+ if (sep)
+ *pos++ = '=';
+
+ memcpy(pos, substr, subsz);
+
+ if (next) {
+ /* set pointer to the next option */
+ *next = pos + subsz;
+ if (**next == ',')
+ (*next)++;
+ }
+ return 0;
+}
+
+/**
+ * mnt_optstr_set_option:
+ * @optstr: string with a comma separated list of options
+ * @name: requested option
+ * @value: new value or NULL
+ *
+ * Set or unset the option @value.
+ *
+ * Returns: 0 on success, 1 when not found the @name or negative number in case
+ * of error.
+ */
+int mnt_optstr_set_option(char **optstr, const char *name, const char *value)
+{
+ struct libmnt_optloc ol = MNT_INIT_OPTLOC;
+ char *nameend;
+ int rc = 1;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ if (*optstr)
+ rc = mnt_optstr_locate_option(*optstr, name, &ol);
+ if (rc < 0)
+ return rc; /* parse error */
+ if (rc == 1)
+ return mnt_optstr_append_option(optstr, name, value); /* not found */
+
+ nameend = ol.begin + ol.namesz;
+
+ if (value == NULL && ol.value && ol.valsz)
+ /* remove unwanted "=value" */
+ mnt_optstr_remove_option_at(optstr, nameend, ol.end);
+
+ else if (value && ol.value == NULL)
+ /* insert "=value" */
+ rc = insert_value(optstr, nameend, value, NULL);
+
+ else if (value && ol.value && strlen(value) == ol.valsz)
+ /* simply replace =value */
+ memcpy(ol.value, value, ol.valsz);
+
+ else if (value && ol.value) {
+ mnt_optstr_remove_option_at(optstr, nameend, ol.end);
+ rc = insert_value(optstr, nameend, value, NULL);
+ }
+ return rc;
+}
+
+/**
+ * mnt_optstr_remove_option:
+ * @optstr: string with a comma separated list of options
+ * @name: requested option name
+ *
+ * Returns: 0 on success, 1 when not found the @name or negative number in case
+ * of error.
+ */
+int mnt_optstr_remove_option(char **optstr, const char *name)
+{
+ struct libmnt_optloc ol = MNT_INIT_OPTLOC;
+ int rc;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ rc = mnt_optstr_locate_option(*optstr, name, &ol);
+ if (rc != 0)
+ return rc;
+
+ mnt_optstr_remove_option_at(optstr, ol.begin, ol.end);
+ return 0;
+}
+
+/**
+ * mnt_split_optstr:
+ * @optstr: string with comma separated list of options
+ * @user: returns newly allocated string with userspace options
+ * @vfs: returns newly allocated string with VFS options
+ * @fs: returns newly allocated string with FS options
+ * @ignore_user: option mask for options that should be ignored
+ * @ignore_vfs: option mask for options that should be ignored
+ *
+ * For example:
+ *
+ * mnt_split_optstr(optstr, &u, NULL, NULL, MNT_NOMTAB, 0);
+ *
+ * returns all userspace options, the options that do not belong to
+ * mtab are ignored.
+ *
+ * Note that FS options are all options that are undefined in MNT_USERSPACE_MAP
+ * or MNT_LINUX_MAP.
+ *
+ * Returns: 0 on success, or a negative number in case of error.
+ */
+int mnt_split_optstr(const char *optstr, char **user, char **vfs,
+ char **fs, int ignore_user, int ignore_vfs)
+{
+ int rc = 0;
+ char *name, *val, *str = (char *) optstr;
+ size_t namesz, valsz, chunsz;
+ struct libmnt_optmap const *maps[2];
+ struct ul_buffer xvfs = UL_INIT_BUFFER,
+ xfs = UL_INIT_BUFFER,
+ xuser = UL_INIT_BUFFER;
+
+ if (!optstr)
+ return -EINVAL;
+
+ maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ maps[1] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
+
+ chunsz = strlen(optstr) / 2;
+
+ while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) {
+ struct ul_buffer *buf = NULL;
+ const struct libmnt_optmap *ent = NULL;
+ const struct libmnt_optmap *m =
+ mnt_optmap_get_entry(maps, 2, name, namesz, &ent);
+
+ if (ent && !ent->id)
+ continue; /* ignore undefined options (comments) */
+
+ /* ignore name=<value> if options map expects <name> only */
+ if (valsz && mnt_optmap_entry_novalue(ent))
+ m = NULL;
+
+ if (ent && m && m == maps[0] && vfs) {
+ if (ignore_vfs && (ent->mask & ignore_vfs))
+ continue;
+ if (vfs)
+ buf = &xvfs;
+ } else if (ent && m && m == maps[1] && user) {
+ if (ignore_user && (ent->mask & ignore_user))
+ continue;
+ if (user)
+ buf = &xuser;
+ } else if (!m && fs) {
+ if (fs)
+ buf = &xfs;
+ }
+
+ if (buf) {
+ if (ul_buffer_is_empty(buf))
+ ul_buffer_set_chunksize(buf, chunsz);
+ rc = __buffer_append_option(buf, name, namesz, val, valsz);
+ }
+ if (rc)
+ break;
+ }
+
+ if (vfs)
+ *vfs = rc ? NULL : ul_buffer_get_data(&xvfs, NULL, NULL);
+ if (fs)
+ *fs = rc ? NULL : ul_buffer_get_data(&xfs, NULL, NULL);
+ if (user)
+ *user = rc ? NULL : ul_buffer_get_data(&xuser, NULL, NULL);
+ if (rc) {
+ ul_buffer_free_data(&xvfs);
+ ul_buffer_free_data(&xfs);
+ ul_buffer_free_data(&xuser);
+ }
+
+ return rc;
+}
+
+/**
+ * mnt_optstr_get_options
+ * @optstr: string with a comma separated list of options
+ * @subset: returns newly allocated string with options
+ * @map: options map
+ * @ignore: mask of the options that should be ignored
+ *
+ * Extracts options from @optstr that belong to the @map, for example:
+ *
+ * mnt_optstr_get_options(optstr, &p,
+ * mnt_get_builtin_optmap(MNT_LINUX_MAP),
+ * MNT_NOMTAB);
+ *
+ * the 'p' returns all VFS options, the options that do not belong to mtab
+ * are ignored.
+ *
+ * Returns: 0 on success, or a negative number in case of error.
+ */
+int mnt_optstr_get_options(const char *optstr, char **subset,
+ const struct libmnt_optmap *map, int ignore)
+{
+ struct libmnt_optmap const *maps[1];
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ char *name, *val, *str = (char *) optstr;
+ size_t namesz, valsz;
+ int rc = 0;
+
+ if (!optstr || !subset)
+ return -EINVAL;
+
+ maps[0] = map;
+
+ ul_buffer_set_chunksize(&buf, strlen(optstr)/2);
+
+ while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) {
+ const struct libmnt_optmap *ent;
+
+ mnt_optmap_get_entry(maps, 1, name, namesz, &ent);
+
+ if (!ent || !ent->id)
+ continue; /* ignore undefined options (comments) */
+
+ if (ignore && (ent->mask & ignore))
+ continue;
+
+ /* ignore name=<value> if options map expects <name> only */
+ if (valsz && mnt_optmap_entry_novalue(ent))
+ continue;
+
+ rc = __buffer_append_option(&buf, name, namesz, val, valsz);
+ if (rc)
+ break;
+ }
+
+ *subset = rc ? NULL : ul_buffer_get_data(&buf, NULL, NULL);
+ if (rc)
+ ul_buffer_free_data(&buf);
+ return rc;
+}
+
+
+/**
+ * mnt_optstr_get_flags:
+ * @optstr: string with comma separated list of options
+ * @flags: returns mount flags
+ * @map: options map
+ *
+ * Returns in @flags IDs of options from @optstr as defined in the @map.
+ *
+ * For example:
+ *
+ * "bind,exec,foo,bar" --returns-> MS_BIND
+ *
+ * "bind,noexec,foo,bar" --returns-> MS_BIND|MS_NOEXEC
+ *
+ * Note that @flags are not zeroized by this function! This function sets/unsets
+ * bits in the @flags only.
+ *
+ * Returns: 0 on success or negative number in case of error
+ */
+int mnt_optstr_get_flags(const char *optstr, unsigned long *flags,
+ const struct libmnt_optmap *map)
+{
+ struct libmnt_optmap const *maps[2];
+ char *name, *str = (char *) optstr;
+ size_t namesz = 0, valsz = 0;
+ int nmaps = 0;
+
+ if (!optstr || !flags || !map)
+ return -EINVAL;
+
+ maps[nmaps++] = map;
+
+ if (map == mnt_get_builtin_optmap(MNT_LINUX_MAP))
+ /*
+ * Add userspace map -- the "user" is interpreted as
+ * MS_NO{EXEC,SUID,DEV}.
+ */
+ maps[nmaps++] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
+
+ while(!mnt_optstr_next_option(&str, &name, &namesz, NULL, &valsz)) {
+ const struct libmnt_optmap *ent;
+ const struct libmnt_optmap *m;
+
+ m = mnt_optmap_get_entry(maps, nmaps, name, namesz, &ent);
+ if (!m || !ent || !ent->id)
+ continue;
+
+ /* ignore name=<value> if options map expects <name> only */
+ if (valsz && mnt_optmap_entry_novalue(ent))
+ continue;
+
+ if (m == map) { /* requested map */
+ if (ent->mask & MNT_INVERT)
+ *flags &= ~ent->id;
+ else
+ *flags |= ent->id;
+
+ } else if (nmaps == 2 && m == maps[1] && valsz == 0) {
+ /*
+ * Special case -- translate "user" (but no user=) to
+ * MS_ options
+ */
+ if (ent->mask & MNT_INVERT)
+ continue;
+ if (ent->id & (MNT_MS_OWNER | MNT_MS_GROUP))
+ *flags |= MS_OWNERSECURE;
+ else if (ent->id & (MNT_MS_USER | MNT_MS_USERS))
+ *flags |= MS_SECURE;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * mnt_optstr_apply_flags:
+ * @optstr: string with comma separated list of options
+ * @flags: returns mount flags
+ * @map: options map
+ *
+ * Removes/adds options to the @optstr according to flags. For example:
+ *
+ * MS_NOATIME and "foo,bar,noexec" --returns-> "foo,bar,noatime"
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_optstr_apply_flags(char **optstr, unsigned long flags,
+ const struct libmnt_optmap *map)
+{
+ struct libmnt_optmap const *maps[1];
+ char *name, *next, *val;
+ size_t namesz = 0, valsz = 0, multi = 0;
+ unsigned long fl;
+ int rc = 0;
+
+ if (!optstr || !map)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("applying 0x%08lx flags to '%s'", flags, *optstr));
+
+ maps[0] = map;
+ next = *optstr;
+ fl = flags;
+
+ /*
+ * There is a convention that 'rw/ro' flags are always at the beginning of
+ * the string (although the 'rw' is unnecessary).
+ */
+ if (map == mnt_get_builtin_optmap(MNT_LINUX_MAP)) {
+ const char *o = (fl & MS_RDONLY) ? "ro" : "rw";
+
+ if (next &&
+ (!strncmp(next, "rw", 2) || !strncmp(next, "ro", 2)) &&
+ (*(next + 2) == '\0' || *(next + 2) == ',')) {
+
+ /* already set, be paranoid and fix it */
+ memcpy(next, o, 2);
+ } else {
+ rc = mnt_optstr_prepend_option(optstr, o, NULL);
+ if (rc)
+ goto err;
+ next = *optstr; /* because realloc() */
+ }
+ fl &= ~MS_RDONLY;
+ next += 2;
+ if (*next == ',')
+ next++;
+ }
+
+ if (next && *next) {
+ /*
+ * scan @optstr and remove options that are missing in
+ * @flags
+ */
+ while(!mnt_optstr_next_option(&next, &name, &namesz,
+ &val, &valsz)) {
+ const struct libmnt_optmap *ent;
+
+ if (mnt_optmap_get_entry(maps, 1, name, namesz, &ent)) {
+ /*
+ * remove unwanted option (rw/ro is already set)
+ */
+ if (!ent || !ent->id)
+ continue;
+ /* ignore name=<value> if options map expects <name> only */
+ if (valsz && mnt_optmap_entry_novalue(ent))
+ continue;
+
+ if (ent->id == MS_RDONLY ||
+ (ent->mask & MNT_INVERT) ||
+ (fl & ent->id) != (unsigned long) ent->id) {
+
+ char *end = val ? val + valsz :
+ name + namesz;
+ next = name;
+ rc = mnt_optstr_remove_option_at(
+ optstr, name, end);
+ if (rc)
+ goto err;
+ }
+ if (!(ent->mask & MNT_INVERT)) {
+ /* allow options with prefix (X-mount.foo,X-mount.bar) more than once */
+ if (ent->mask & MNT_PREFIX)
+ multi |= ent->id;
+ else
+ fl &= ~ent->id;
+ if (ent->id & MS_REC)
+ fl |= MS_REC;
+ }
+ }
+ }
+ }
+
+ /* remove from flags options which are allowed more than once */
+ fl &= ~multi;
+
+ /* add missing options (but ignore fl if contains MS_REC only) */
+ if (fl && fl != MS_REC) {
+
+ const struct libmnt_optmap *ent;
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ size_t sz;
+ char *p;
+
+ ul_buffer_refer_string(&buf, *optstr);
+
+ for (ent = map; ent && ent->name; ent++) {
+ if ((ent->mask & MNT_INVERT)
+ || ent->id == 0
+ || (fl & ent->id) != (unsigned long) ent->id)
+ continue;
+
+ /* don't add options which require values (e.g. offset=%d) */
+ p = strchr(ent->name, '=');
+ if (p) {
+ if (p > ent->name && *(p - 1) == '[')
+ p--; /* name[=] */
+ else
+ continue; /* name= */
+ sz = p - ent->name;
+ } else
+ sz = strlen(ent->name);
+
+ rc = __buffer_append_option(&buf, ent->name, sz, NULL, 0);
+ if (rc)
+ goto err;
+ }
+
+ *optstr = ul_buffer_get_data(&buf, NULL, NULL);
+ }
+
+ DBG(CXT, ul_debug("new optstr '%s'", *optstr));
+ return rc;
+err:
+ DBG(CXT, ul_debug("failed to apply flags [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * @optstr: string with comma separated list of options
+ * @value: pointer to the begin of the context value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+ *
+ * Translates SELinux context from human to raw format. The function does not
+ * modify @optstr and returns zero if libmount is compiled without SELinux
+ * support.
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+#ifndef HAVE_LIBSELINUX
+int mnt_optstr_fix_secontext(char **optstr __attribute__ ((__unused__)),
+ char *value __attribute__ ((__unused__)),
+ size_t valsz __attribute__ ((__unused__)),
+ char **next __attribute__ ((__unused__)))
+{
+ return 0;
+}
+#else
+int mnt_optstr_fix_secontext(char **optstr,
+ char *value,
+ size_t valsz,
+ char **next)
+{
+ int rc = 0;
+ char *p, *val, *begin, *end, *raw = NULL;
+ size_t sz;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing SELinux context"));
+
+ begin = value;
+ end = value + valsz;
+
+ /* the selinux contexts are quoted */
+ if (*value == '"') {
+ if (valsz <= 2 || *(value + valsz - 1) != '"')
+ return -EINVAL; /* improperly quoted option string */
+ value++;
+ valsz -= 2;
+ }
+
+ p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+
+
+ /* translate the context */
+ rc = selinux_trans_to_raw_context(p, &raw);
+
+ DBG(CXT, ul_debug("SELinux context '%s' translated to '%s'",
+ p, rc == -1 ? "FAILED" : (char *) raw));
+
+ free(p);
+ if (rc == -1 || !raw)
+ return -EINVAL;
+
+
+ /* create a quoted string from the raw context */
+ sz = strlen((char *) raw);
+ if (!sz) {
+ freecon(raw);
+ return -EINVAL;
+ }
+
+ p = val = malloc(valsz + 3);
+ if (!val) {
+ freecon(raw);
+ return -ENOMEM;
+ }
+
+ *p++ = '"';
+ memcpy(p, raw, sz);
+ p += sz;
+ *p++ = '"';
+ *p = '\0';
+
+ freecon(raw);
+
+ /* set new context */
+ mnt_optstr_remove_option_at(optstr, begin, end);
+ rc = insert_value(optstr, begin, val, next);
+ free(val);
+
+ return rc;
+}
+#endif
+
+static int set_uint_value(char **optstr, unsigned int num,
+ char *begin, char *end, char **next)
+{
+ char buf[40];
+ snprintf(buf, sizeof(buf), "%u", num);
+
+ mnt_optstr_remove_option_at(optstr, begin, end);
+ return insert_value(optstr, begin, buf, next);
+}
+
+/*
+ * @optstr: string with a comma separated list of options
+ * @value: pointer to the beginning of the uid value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+
+ * Translates "username" or "useruid" to the real UID.
+ *
+ * For example:
+ * if (!mnt_optstr_get_option(optstr, "uid", &val, &valsz))
+ * mnt_optstr_fix_uid(&optstr, val, valsz, NULL);
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next)
+{
+ char *end;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing uid"));
+
+ end = value + valsz;
+
+ if (valsz == 7 && !strncmp(value, "useruid", 7) &&
+ (*(value + 7) == ',' || !*(value + 7)))
+ return set_uint_value(optstr, getuid(), value, end, next);
+
+ if (!isdigit(*value)) {
+ uid_t id;
+ int rc;
+ char *p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+ rc = mnt_get_uid(p, &id);
+ free(p);
+
+ if (!rc)
+ return set_uint_value(optstr, id, value, end, next);
+ }
+
+ if (next) {
+ /* no change, let's keep the original value */
+ *next = value + valsz;
+ if (**next == ',')
+ (*next)++;
+ }
+
+ return 0;
+}
+
+/*
+ * @optstr: string with a comma separated list of options
+ * @value: pointer to the beginning of the uid value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+
+ * Translates "groupname" or "usergid" to the real GID.
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next)
+{
+ char *end;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing gid"));
+
+ end = value + valsz;
+
+ if (valsz == 7 && !strncmp(value, "usergid", 7) &&
+ (*(value + 7) == ',' || !*(value + 7)))
+ return set_uint_value(optstr, getgid(), value, end, next);
+
+ if (!isdigit(*value)) {
+ int rc;
+ gid_t id;
+ char *p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+ rc = mnt_get_gid(p, &id);
+ free(p);
+
+ if (!rc)
+ return set_uint_value(optstr, id, value, end, next);
+
+ }
+
+ if (next) {
+ /* nothing */
+ *next = value + valsz;
+ if (**next == ',')
+ (*next)++;
+ }
+ return 0;
+}
+
+/*
+ * Converts "user" to "user=<username>".
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_optstr_fix_user(char **optstr)
+{
+ char *username;
+ struct libmnt_optloc ol = MNT_INIT_OPTLOC;
+ int rc = 0;
+
+ DBG(CXT, ul_debug("fixing user"));
+
+ rc = mnt_optstr_locate_option(*optstr, "user", &ol);
+ if (rc)
+ return rc == 1 ? 0 : rc; /* 1: user= not found */
+
+ username = mnt_get_username(getuid());
+ if (!username)
+ return -ENOMEM;
+
+ if (!ol.valsz || (ol.value && strncmp(ol.value, username, ol.valsz) != 0)) {
+ if (ol.valsz)
+ /* remove old value */
+ mnt_optstr_remove_option_at(optstr, ol.value, ol.end);
+
+ rc = insert_value(optstr, ol.value ? ol.value : ol.end,
+ username, NULL);
+ }
+
+ free(username);
+ return rc;
+}
+
+/*
+ * Converts value from @optstr addressed by @name to uid.
+ *
+ * Returns: 0 on success, 1 if not found, <0 on error
+ */
+int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid)
+{
+ char *value = NULL;
+ size_t valsz = 0;
+ char buf[sizeof(stringify_value(UINT64_MAX))];
+ int rc;
+ uint64_t num;
+
+ assert(optstr);
+ assert(name);
+ assert(uid);
+
+ rc = mnt_optstr_get_option(optstr, name, &value, &valsz);
+ if (rc != 0)
+ goto fail;
+
+ if (valsz > sizeof(buf) - 1) {
+ rc = -ERANGE;
+ goto fail;
+ }
+ mem2strcpy(buf, value, valsz, sizeof(buf));
+
+ rc = ul_strtou64(buf, &num, 10);
+ if (rc != 0)
+ goto fail;
+ if (num > ULONG_MAX || (uid_t) num != num) {
+ rc = -ERANGE;
+ goto fail;
+ }
+ *uid = (uid_t) num;
+
+ return 0;
+fail:
+ DBG(UTILS, ul_debug("failed to convert '%s'= to number [rc=%d]", name, rc));
+ return rc;
+}
+
+/**
+ * mnt_match_options:
+ * @optstr: options string
+ * @pattern: comma delimited list of options
+ *
+ * The "no" could be used for individual items in the @options list. The "no"
+ * prefix does not have a global meaning.
+ *
+ * Unlike fs type matching, nonetdev,user and nonetdev,nouser have
+ * DIFFERENT meanings; each option is matched explicitly as specified.
+ *
+ * The "no" prefix interpretation could be disabled by the "+" prefix, for example
+ * "+noauto" matches if @optstr literally contains the "noauto" string.
+ *
+ * "xxx,yyy,zzz" : "nozzz" -> False
+ *
+ * "xxx,yyy,zzz" : "xxx,noeee" -> True
+ *
+ * "bar,zzz" : "nofoo" -> True (does not contain "foo")
+ *
+ * "nofoo,bar" : "nofoo" -> True (does not contain "foo")
+ *
+ * "nofoo,bar" : "+nofoo" -> True (contains "nofoo")
+ *
+ * "bar,zzz" : "+nofoo" -> False (does not contain "nofoo")
+ *
+ *
+ * Returns: 1 if pattern is matching, else 0. This function also returns 0
+ * if @pattern is NULL and @optstr is non-NULL.
+ */
+int mnt_match_options(const char *optstr, const char *pattern)
+{
+ char *name, *pat = (char *) pattern;
+ char *buf, *patval;
+ size_t namesz = 0, patvalsz = 0;
+ int match = 1;
+
+ if (!pattern && !optstr)
+ return 1;
+ if (!pattern)
+ return 0;
+
+ buf = malloc(strlen(pattern) + 1);
+ if (!buf)
+ return 0;
+
+ /* walk on pattern string
+ */
+ while (match && !mnt_optstr_next_option(&pat, &name, &namesz,
+ &patval, &patvalsz)) {
+ char *val;
+ size_t sz;
+ int no = 0, rc;
+
+ if (*name == '+')
+ name++, namesz--;
+ else if ((no = (startswith(name, "no") != NULL)))
+ name += 2, namesz -= 2;
+
+ xstrncpy(buf, name, namesz + 1);
+
+ rc = mnt_optstr_get_option(optstr, buf, &val, &sz);
+
+ /* check also value (if the pattern is "foo=value") */
+ if (rc == 0 && patvalsz > 0 &&
+ (patvalsz != sz || strncmp(patval, val, sz) != 0))
+ rc = 1;
+
+ switch (rc) {
+ case 0: /* found */
+ match = no == 0 ? 1 : 0;
+ break;
+ case 1: /* not found */
+ match = no == 1 ? 1 : 0;
+ break;
+ default: /* parse error */
+ match = 0;
+ break;
+ }
+
+ }
+
+ free(buf);
+ return match;
+}
+
+#ifdef TEST_PROGRAM
+#include "xalloc.h"
+
+static int test_append(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *value = NULL, *name;
+ char *optstr;
+ int rc;
+
+ if (argc < 3)
+ return -EINVAL;
+ optstr = xstrdup(argv[1]);
+ name = argv[2];
+
+ if (argc == 4)
+ value = argv[3];
+
+ rc = mnt_optstr_append_option(&optstr, name, value);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_prepend(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *value = NULL, *name;
+ char *optstr;
+ int rc;
+
+ if (argc < 3)
+ return -EINVAL;
+ optstr = xstrdup(argv[1]);
+ name = argv[2];
+
+ if (argc == 4)
+ value = argv[3];
+
+ rc = mnt_optstr_prepend_option(&optstr, name, value);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_split(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr, *user = NULL, *fs = NULL, *vfs = NULL;
+ int rc;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ optstr = xstrdup(argv[1]);
+
+ rc = mnt_split_optstr(optstr, &user, &vfs, &fs, 0, 0);
+ if (!rc) {
+ printf("user : %s\n", user);
+ printf("vfs : %s\n", vfs);
+ printf("fs : %s\n", fs);
+ }
+
+ free(user);
+ free(vfs);
+ free(fs);
+ free(optstr);
+ return rc;
+}
+
+static int test_flags(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr;
+ int rc;
+ unsigned long fl = 0;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ optstr = xstrdup(argv[1]);
+
+ rc = mnt_optstr_get_flags(optstr, &fl, mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ if (rc)
+ return rc;
+ printf("mountflags: 0x%08lx\n", fl);
+
+ fl = 0;
+ rc = mnt_optstr_get_flags(optstr, &fl, mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
+ if (rc)
+ return rc;
+ printf("userspace-mountflags: 0x%08lx\n", fl);
+
+ free(optstr);
+ return rc;
+}
+
+static int test_apply(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr;
+ int rc, map;
+ unsigned long flags;
+
+ if (argc < 4)
+ return -EINVAL;
+
+ if (!strcmp(argv[1], "--user"))
+ map = MNT_USERSPACE_MAP;
+ else if (!strcmp(argv[1], "--linux"))
+ map = MNT_LINUX_MAP;
+ else {
+ fprintf(stderr, "unknown option '%s'\n", argv[1]);
+ return -EINVAL;
+ }
+
+ optstr = xstrdup(argv[2]);
+ flags = strtoul(argv[3], NULL, 16);
+
+ printf("flags: 0x%08lx\n", flags);
+
+ rc = mnt_optstr_apply_flags(&optstr, flags, mnt_get_builtin_optmap(map));
+ printf("optstr: %s\n", optstr);
+
+ free(optstr);
+ return rc;
+}
+
+static int test_set(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *value = NULL, *name;
+ char *optstr;
+ int rc;
+
+ if (argc < 3)
+ return -EINVAL;
+ optstr = xstrdup(argv[1]);
+ name = argv[2];
+
+ if (argc == 4)
+ value = argv[3];
+
+ rc = mnt_optstr_set_option(&optstr, name, value);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_get(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr;
+ const char *name;
+ char *val = NULL;
+ size_t sz = 0;
+ int rc;
+
+ if (argc < 2)
+ return -EINVAL;
+ optstr = argv[1];
+ name = argv[2];
+
+ rc = mnt_optstr_get_option(optstr, name, &val, &sz);
+ if (rc == 0) {
+ printf("found; name: %s", name);
+ if (sz) {
+ printf(", argument: size=%zd data=", sz);
+ if (fwrite(val, 1, sz, stdout) != sz)
+ return -1;
+ }
+ printf("\n");
+ } else if (rc == 1)
+ printf("%s: not found\n", name);
+ else
+ printf("parse error: %s\n", optstr);
+ return rc;
+}
+
+static int test_remove(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *name;
+ char *optstr;
+ int rc;
+
+ if (argc < 3)
+ return -EINVAL;
+ optstr = xstrdup(argv[1]);
+ name = argv[2];
+
+ rc = mnt_optstr_remove_option(&optstr, name);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_dedup(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *name;
+ char *optstr;
+ int rc;
+
+ if (argc < 3)
+ return -EINVAL;
+ optstr = xstrdup(argv[1]);
+ name = argv[2];
+
+ rc = mnt_optstr_deduplicate_option(&optstr, name);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_fix(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr;
+ int rc = 0;
+ char *name, *val, *next;
+ size_t valsz, namesz;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ next = optstr = xstrdup(argv[1]);
+
+ printf("optstr: %s\n", optstr);
+
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+
+ if (!strncmp(name, "uid", 3))
+ rc = mnt_optstr_fix_uid(&optstr, val, valsz, &next);
+ else if (!strncmp(name, "gid", 3))
+ rc = mnt_optstr_fix_gid(&optstr, val, valsz, &next);
+ else if (!strncmp(name, "context", 7))
+ rc = mnt_optstr_fix_secontext(&optstr, val, valsz, &next);
+ if (rc)
+ break;
+ }
+ if (rc)
+ rc = mnt_optstr_fix_user(&optstr);
+
+ printf("fixed: %s\n", optstr);
+
+ free(optstr);
+ return rc;
+
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--append", test_append, "<optstr> <name> [<value>] append value to optstr" },
+ { "--prepend",test_prepend,"<optstr> <name> [<value>] prepend value to optstr" },
+ { "--set", test_set, "<optstr> <name> [<value>] (un)set value" },
+ { "--get", test_get, "<optstr> <name> search name in optstr" },
+ { "--remove", test_remove, "<optstr> <name> remove name in optstr" },
+ { "--dedup", test_dedup, "<optstr> <name> deduplicate name in optstr" },
+ { "--split", test_split, "<optstr> split into FS, VFS and userspace" },
+ { "--flags", test_flags, "<optstr> convert options to MS_* flags" },
+ { "--apply", test_apply, "--{linux,user} <optstr> <mask> apply mask to optstr" },
+ { "--fix", test_fix, "<optstr> fix uid=, gid=, user, and context=" },
+
+ { NULL }
+ };
+ return mnt_run_test(tss, argc, argv);
+}
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/tab.c b/libmount/src/tab.c
new file mode 100644
index 0000000..e1e308d
--- /dev/null
+++ b/libmount/src/tab.c
@@ -0,0 +1,2261 @@
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: table
+ * @title: Table of filesystems
+ * @short_description: container for entries from fstab, mtab or mountinfo
+ *
+ * Note that mnt_table_find_* functions are mount(8) compatible. These functions
+ * try to find an entry in more iterations, where the first attempt is always
+ * based on comparison with unmodified (non-canonicalized or un-evaluated)
+ * paths or tags. For example a fstab with two entries:
+ * <informalexample>
+ * <programlisting>
+ * LABEL=foo /foo auto rw
+ * /dev/foo /foo auto rw
+ * </programlisting>
+ * </informalexample>
+ *
+ * where both lines are used for the *same* device, then
+ * <informalexample>
+ * <programlisting>
+ * mnt_table_find_source(tb, "/dev/foo", &fs);
+ * </programlisting>
+ * </informalexample>
+ * will returns the second line, and
+ * <informalexample>
+ * <programlisting>
+ * mnt_table_find_source(tb, "LABEL=foo", &fs);
+ * </programlisting>
+ * </informalexample>
+ * will returns the first entry, and
+ * <informalexample>
+ * <programlisting>
+ * mnt_table_find_source(tb, "UUID=anyuuid", &fs);
+ * </programlisting>
+ * </informalexample>
+ * will return the first entry (if UUID matches with the device).
+ */
+#include <blkid.h>
+
+#include "mountP.h"
+#include "strutils.h"
+#include "loopdev.h"
+#include "fileutils.h"
+#include "canonicalize.h"
+
+int is_mountinfo(struct libmnt_table *tb)
+{
+ struct libmnt_fs *fs;
+
+ if (!tb)
+ return 0;
+
+ fs = list_first_entry(&tb->ents, struct libmnt_fs, ents);
+ if (fs && mnt_fs_is_kernel(fs) && mnt_fs_get_root(fs))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * mnt_new_table:
+ *
+ * The tab is a container for struct libmnt_fs entries that usually represents a fstab,
+ * mtab or mountinfo file from your system.
+ *
+ * See also mnt_table_parse_file().
+ *
+ * Returns: newly allocated tab struct.
+ */
+struct libmnt_table *mnt_new_table(void)
+{
+ struct libmnt_table *tb = NULL;
+
+ tb = calloc(1, sizeof(*tb));
+ if (!tb)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "alloc"));
+ tb->refcount = 1;
+ INIT_LIST_HEAD(&tb->ents);
+ return tb;
+}
+
+/**
+ * mnt_reset_table:
+ * @tb: tab pointer
+ *
+ * Removes all entries (filesystems) from the table. The filesystems with zero
+ * reference count will be deallocated.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_reset_table(struct libmnt_table *tb)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "reset"));
+
+ while (!list_empty(&tb->ents)) {
+ struct libmnt_fs *fs = list_entry(tb->ents.next,
+ struct libmnt_fs, ents);
+ mnt_table_remove_fs(tb, fs);
+ }
+
+ tb->nents = 0;
+ return 0;
+}
+
+/**
+ * mnt_ref_table:
+ * @tb: table pointer
+ *
+ * Increments reference counter.
+ */
+void mnt_ref_table(struct libmnt_table *tb)
+{
+ if (tb) {
+ tb->refcount++;
+ /*DBG(FS, ul_debugobj(tb, "ref=%d", tb->refcount));*/
+ }
+}
+
+/**
+ * mnt_unref_table:
+ * @tb: table pointer
+ *
+ * De-increments reference counter, on zero the @tb is automatically
+ * deallocated by mnt_free_table().
+ */
+void mnt_unref_table(struct libmnt_table *tb)
+{
+ if (tb) {
+ tb->refcount--;
+ /*DBG(FS, ul_debugobj(tb, "unref=%d", tb->refcount));*/
+ if (tb->refcount <= 0)
+ mnt_free_table(tb);
+ }
+}
+
+
+/**
+ * mnt_free_table:
+ * @tb: tab pointer
+ *
+ * Deallocates the table. This function does not care about reference count. Don't
+ * use this function directly -- it's better to use mnt_unref_table().
+ *
+ * The table entries (filesystems) are unreferenced by mnt_reset_table() and
+ * cache by mnt_unref_cache().
+ */
+void mnt_free_table(struct libmnt_table *tb)
+{
+ if (!tb)
+ return;
+
+ mnt_reset_table(tb);
+ DBG(TAB, ul_debugobj(tb, "free [refcount=%d]", tb->refcount));
+
+ mnt_unref_cache(tb->cache);
+ free(tb->comm_intro);
+ free(tb->comm_tail);
+ free(tb);
+}
+
+/**
+ * mnt_table_get_nents:
+ * @tb: pointer to tab
+ *
+ * Returns: number of entries in table.
+ */
+int mnt_table_get_nents(struct libmnt_table *tb)
+{
+ return tb ? tb->nents : 0;
+}
+
+/**
+ * mnt_table_is_empty:
+ * @tb: pointer to tab
+ *
+ * Returns: 1 if the table is without filesystems, or 0.
+ */
+int mnt_table_is_empty(struct libmnt_table *tb)
+{
+ return tb == NULL || list_empty(&tb->ents) ? 1 : 0;
+}
+
+/**
+ * mnt_table_set_userdata:
+ * @tb: pointer to tab
+ * @data: pointer to user data
+ *
+ * Sets pointer to the private user data.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_set_userdata(struct libmnt_table *tb, void *data)
+{
+ if (!tb)
+ return -EINVAL;
+
+ tb->userdata = data;
+ return 0;
+}
+
+/**
+ * mnt_table_get_userdata:
+ * @tb: pointer to tab
+ *
+ * Returns: pointer to user's data.
+ */
+void *mnt_table_get_userdata(struct libmnt_table *tb)
+{
+ return tb ? tb->userdata : NULL;
+}
+
+/**
+ * mnt_table_enable_comments:
+ * @tb: pointer to tab
+ * @enable: TRUE or FALSE
+ *
+ * Enables parsing of comments.
+ *
+ * The initial (intro) file comment is accessible by
+ * mnt_table_get_intro_comment(). The intro and the comment of the first fstab
+ * entry has to be separated by blank line. The filesystem comments are
+ * accessible by mnt_fs_get_comment(). The trailing fstab comment is accessible
+ * by mnt_table_get_trailing_comment().
+ *
+ * <informalexample>
+ * <programlisting>
+ * #
+ * # Intro comment
+ * #
+ *
+ * # this comments belongs to the first fs
+ * LABEL=foo /mnt/foo auto defaults 1 2
+ * # this comments belongs to the second fs
+ * LABEL=bar /mnt/bar auto defaults 1 2
+ * # tailing comment
+ * </programlisting>
+ * </informalexample>
+ */
+void mnt_table_enable_comments(struct libmnt_table *tb, int enable)
+{
+ if (tb)
+ tb->comms = enable;
+}
+
+/**
+ * mnt_table_with_comments:
+ * @tb: pointer to table
+ *
+ * Returns: 1 if comments parsing is enabled, or 0.
+ */
+int mnt_table_with_comments(struct libmnt_table *tb)
+{
+ assert(tb);
+ return tb ? tb->comms : 0;
+}
+
+/**
+ * mnt_table_get_intro_comment:
+ * @tb: pointer to tab
+ *
+ * Returns: initial comment in tb
+ */
+const char *mnt_table_get_intro_comment(struct libmnt_table *tb)
+{
+ return tb ? tb->comm_intro : NULL;
+}
+
+/**
+ * mnt_table_set_into_comment:
+ * @tb: pointer to tab
+ * @comm: comment or NULL
+ *
+ * Sets the initial comment in tb.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm)
+{
+ return strdup_to_struct_member(tb, comm_intro, comm);
+}
+
+/**
+ * mnt_table_append_into_comment:
+ * @tb: pointer to tab
+ * @comm: comment of NULL
+ *
+ * Appends the initial comment in tb.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm)
+{
+ if (!tb)
+ return -EINVAL;
+ return strappend(&tb->comm_intro, comm);
+}
+
+/**
+ * mnt_table_get_trailing_comment:
+ * @tb: pointer to tab
+ *
+ * Returns: table trailing comment
+ */
+const char *mnt_table_get_trailing_comment(struct libmnt_table *tb)
+{
+ return tb ? tb->comm_tail : NULL;
+}
+
+/**
+ * mnt_table_set_trailing_comment
+ * @tb: pointer to tab
+ * @comm: comment string
+ *
+ * Sets the trailing comment in table.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm)
+{
+ return strdup_to_struct_member(tb, comm_tail, comm);
+}
+
+/**
+ * mnt_table_append_trailing_comment:
+ * @tb: pointer to tab
+ * @comm: comment of NULL
+ *
+ * Appends to the trailing table comment.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm)
+{
+ if (!tb)
+ return -EINVAL;
+ return strappend(&tb->comm_tail, comm);
+}
+
+/**
+ * mnt_table_set_cache:
+ * @tb: pointer to tab
+ * @mpc: pointer to struct libmnt_cache instance
+ *
+ * Sets up a cache for canonicalized paths and evaluated tags (LABEL/UUID). The
+ * cache is recommended for mnt_table_find_*() functions.
+ *
+ * The cache could be shared between more tabs. Be careful when you share the
+ * same cache between more threads -- currently the cache does not provide any
+ * locking method.
+ *
+ * This function increments cache reference counter. It's recommended to use
+ * mnt_unref_cache() after mnt_table_set_cache() if you want to keep the cache
+ * referenced by @tb only.
+ *
+ * See also mnt_new_cache().
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc)
+{
+ if (!tb)
+ return -EINVAL;
+
+ mnt_ref_cache(mpc); /* new */
+ mnt_unref_cache(tb->cache); /* old */
+ tb->cache = mpc;
+ return 0;
+}
+
+/**
+ * mnt_table_get_cache:
+ * @tb: pointer to tab
+ *
+ * Returns: pointer to struct libmnt_cache instance or NULL.
+ */
+struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb)
+{
+ return tb ? tb->cache : NULL;
+}
+
+/**
+ * mnt_table_find_fs:
+ * @tb: tab pointer
+ * @fs: entry to look for
+ *
+ * Checks if @fs is part of table @tb.
+ *
+ * Returns: index of @fs in table, 0 if not found or negative number in case of error.
+ *
+ * Since: 2.34
+ */
+int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
+{
+ struct list_head *p;
+ int i = 0;
+
+ if (!tb || !fs)
+ return -EINVAL;
+
+ if (list_empty(&fs->ents))
+ return 0;
+
+ /* Let's use directly list rather than mnt_table_next_fs() as we
+ * compare list entry with fs only.
+ */
+ list_for_each(p, &tb->ents) {
+ ++i;
+ if (list_entry(p, struct libmnt_fs, ents) == fs)
+ return i;
+ }
+
+ return 0;
+}
+
+/**
+ * mnt_table_add_fs:
+ * @tb: tab pointer
+ * @fs: new entry
+ *
+ * Adds a new entry to tab and increment @fs reference counter. Don't forget to
+ * use mnt_unref_fs() after mnt_table_add_fs() you want to keep the @fs
+ * referenced by the table only.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
+{
+ if (!tb || !fs)
+ return -EINVAL;
+
+ if (fs->tab)
+ return -EBUSY;
+
+ mnt_ref_fs(fs);
+ list_add_tail(&fs->ents, &tb->ents);
+ fs->tab = tb;
+ tb->nents++;
+
+ DBG(TAB, ul_debugobj(tb, "add entry: %s %s",
+ mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
+ return 0;
+}
+
+static int __table_insert_fs(
+ struct libmnt_table *tb, int before,
+ struct libmnt_fs *pos, struct libmnt_fs *fs)
+{
+ struct list_head *head = pos ? &pos->ents : &tb->ents;
+
+ if (before)
+ list_add(&fs->ents, head);
+ else
+ list_add_tail(&fs->ents, head);
+
+ fs->tab = tb;
+ tb->nents++;
+
+ DBG(TAB, ul_debugobj(tb, "insert entry: %s %s",
+ mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
+ return 0;
+}
+
+/**
+ * mnt_table_insert_fs:
+ * @tb: tab pointer
+ * @before: 1 to insert before pos, 0 to insert after pos
+ * @pos: entry to specify position or NULL
+ * @fs: new entry
+ *
+ * Adds a new entry to @tb before or after a specific table entry @pos. If the
+ * @pos is NULL than add the begin of the @tab if @before is 1; or to the tail
+ * of the @tb if @before is 0.
+ *
+ * This function increments reference to @fs. Don't forget to use
+ * mnt_unref_fs() after mnt_table_insert_fs() if you want to keep the @fs
+ * referenced by the table only.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ *
+ * Since: 2.34
+ */
+int mnt_table_insert_fs(struct libmnt_table *tb, int before,
+ struct libmnt_fs *pos, struct libmnt_fs *fs)
+{
+ if (!tb || !fs)
+ return -EINVAL;
+
+ if (fs->tab)
+ return -EBUSY;
+
+ if (pos && pos->tab != tb)
+ return -ENOENT;
+
+ mnt_ref_fs(fs);
+ return __table_insert_fs(tb, before, pos, fs);
+}
+
+/**
+ * mnt_table_move_fs:
+ * @src: tab pointer of source table
+ * @dst: tab pointer of destination table
+ * @before: 1 to move before position, 0 to move after position
+ * @pos: entry to specify position or NULL
+ * @fs: entry to move
+ *
+ * Removes @fs from @src table and adds it before/after a specific entry @pos
+ * of @dst table. If the @pos is NULL than add the begin of the @dst if @before
+ * is 1; or to the tail of the @dst if @before is 0.
+ *
+ * The reference counter of @fs is not modified.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ *
+ * Since: 2.34
+ */
+int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst,
+ int before, struct libmnt_fs *pos, struct libmnt_fs *fs)
+{
+ if (!src || !dst || !fs)
+ return -EINVAL;
+
+ if (fs->tab != src || (pos && pos->tab != dst))
+ return -ENOENT;
+
+ /* remove from source */
+ list_del_init(&fs->ents);
+ src->nents--;
+
+ /* insert to the destination */
+ return __table_insert_fs(dst, before, pos, fs);
+}
+
+
+/**
+ * mnt_table_remove_fs:
+ * @tb: tab pointer
+ * @fs: new entry
+ *
+ * Removes the @fs from the table and de-increment reference counter of the @fs. The
+ * filesystem with zero reference counter will be deallocated. Don't forget to use
+ * mnt_ref_fs() before call mnt_table_remove_fs() if you want to use @fs later.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
+{
+ if (!tb || !fs || fs->tab != tb)
+ return -EINVAL;
+
+ fs->tab = NULL;
+ list_del_init(&fs->ents);
+
+ mnt_unref_fs(fs);
+ tb->nents--;
+ return 0;
+}
+
+static inline struct libmnt_fs *get_parent_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *x;
+ int parent_id = mnt_fs_get_parent_id(fs);
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while (mnt_table_next_fs(tb, &itr, &x) == 0) {
+ if (mnt_fs_get_id(x) == parent_id)
+ return x;
+ }
+
+ return NULL;
+}
+
+/**
+ * mnt_table_get_root_fs:
+ * @tb: mountinfo file (/proc/self/mountinfo)
+ * @root: returns pointer to the root filesystem (/)
+ *
+ * The function uses the parent ID from the mountinfo file to determine the
+ * root filesystem (the filesystem with the smallest ID with parent ID missing
+ * in the table). The function is designed mostly for applications where it is
+ * necessary to sort mountpoints by IDs to get the tree of the mountpoints
+ * (e.g. findmnt default output).
+ *
+ * If you're not sure, then use
+ *
+ * mnt_table_find_target(tb, "/", MNT_ITER_BACKWARD);
+ *
+ * this is more robust and usable for arbitrary tab files (including fstab).
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+ int root_id = 0;
+
+ if (!tb || !root || !is_mountinfo(tb))
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup root fs"));
+
+ *root = NULL;
+
+ /* get smallest possible ID from the table */
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ int id = mnt_fs_get_parent_id(fs);
+
+ if (!*root || id < root_id) {
+ *root = fs;
+ root_id = id;
+ }
+ }
+
+ /* go to the root node by "parent_id -> id" relation */
+ while (*root) {
+ struct libmnt_fs *x = get_parent_fs(tb, *root);
+ if (!x || x == *root)
+ break;
+ DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x)));
+ *root = x;
+ }
+
+ return *root ? 0 : -EINVAL;
+}
+
+/**
+ * mnt_table_next_child_fs:
+ * @tb: mountinfo file (/proc/self/mountinfo)
+ * @itr: iterator
+ * @parent: parental FS
+ * @chld: returns the next child filesystem
+ *
+ * Note that filesystems are returned in the order of mounting (according to
+ * IDs in /proc/self/mountinfo).
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ */
+int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
+ struct libmnt_fs *parent, struct libmnt_fs **chld)
+{
+ struct libmnt_fs *fs;
+ int parent_id, lastchld_id = 0, chld_id = 0;
+
+ if (!tb || !itr || !parent || !is_mountinfo(tb))
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup next child of '%s'",
+ mnt_fs_get_target(parent)));
+
+ parent_id = mnt_fs_get_id(parent);
+
+ /* get ID of the previously returned child */
+ if (itr->head && itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, fs, struct libmnt_fs, ents);
+ lastchld_id = mnt_fs_get_id(fs);
+ }
+
+ *chld = NULL;
+
+ mnt_reset_iter(itr, MNT_ITER_FORWARD);
+ while(mnt_table_next_fs(tb, itr, &fs) == 0) {
+ int id;
+
+ if (mnt_fs_get_parent_id(fs) != parent_id)
+ continue;
+
+ id = mnt_fs_get_id(fs);
+
+ /* avoid an infinite loop. This only happens in rare cases
+ * such as in early userspace when the rootfs is its own parent */
+ if (id == parent_id)
+ continue;
+
+ if ((!lastchld_id || id > lastchld_id) &&
+ (!*chld || id < chld_id)) {
+ *chld = fs;
+ chld_id = id;
+ }
+ }
+
+ if (!*chld)
+ return 1; /* end of iterator */
+
+ /* set the iterator to the @chld for the next call */
+ mnt_table_set_iter(tb, itr, *chld);
+
+ return 0;
+}
+
+/**
+ * mnt_table_over_fs:
+ * @tb: tab pointer
+ * @parent: pointer to parental FS
+ * @child: returns pointer to FS which over-mounting parent (optional)
+ *
+ * This function returns by @child the first filesystenm which is over-mounted
+ * on @parent. It means the mountpoint of @child is the same as for @parent and
+ * parent->id is the same as child->parent_id.
+ *
+ * Note that you need to call this function in loop until it returns 1 to get
+ * the highest filesystem.
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * while (mnt_table_over_fs(tb, cur, &over) == 0) {
+ * printf("%s overmounted by %d\n", mnt_fs_get_target(cur), mnt_fs_get_id(over));
+ * cur = over;
+ * }
+ * </programlisting>
+ * </informalexample>
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ */
+int mnt_table_over_fs(struct libmnt_table *tb, struct libmnt_fs *parent,
+ struct libmnt_fs **child)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+ int id;
+ const char *tgt;
+
+ if (!tb || !parent || !is_mountinfo(tb))
+ return -EINVAL;
+
+ if (child)
+ *child = NULL;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ id = mnt_fs_get_id(parent);
+ tgt = mnt_fs_get_target(parent);
+
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_get_parent_id(fs) == id &&
+ mnt_fs_streq_target(fs, tgt) == 1) {
+ if (child)
+ *child = fs;
+ return 0;
+ }
+ }
+
+ return 1; /* nothing */
+}
+
+/**
+ * mnt_table_next_fs:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @fs: returns the next tab entry
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * while(mnt_table_next_fs(tb, itr, &fs) == 0) {
+ * const char *dir = mnt_fs_get_target(fs);
+ * printf("mount point: %s\n", dir);
+ * }
+ * </programlisting>
+ * </informalexample>
+ *
+ * lists all mountpoints from fstab in reverse order.
+ */
+int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !fs)
+ return -EINVAL;
+ *fs = NULL;
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &tb->ents);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * mnt_table_first_fs:
+ * @tb: tab pointer
+ * @fs: returns the first tab entry
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ */
+int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs)
+{
+ if (!tb || !fs)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 1;
+ *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents);
+ return 0;
+}
+
+/**
+ * mnt_table_last_fs:
+ * @tb: tab pointer
+ * @fs: returns the last tab entry
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ */
+int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs)
+{
+ if (!tb || !fs)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 1;
+ *fs = list_last_entry(&tb->ents, struct libmnt_fs, ents);
+ return 0;
+}
+
+/**
+ * mnt_table_find_next_fs:
+ * @tb: table
+ * @itr: iterator
+ * @match_func: function returning 1 or 0
+ * @userdata: extra data for match_func
+ * @fs: returns pointer to the next matching table entry
+ *
+ * This function allows searching in @tb.
+ *
+ * Returns: negative number in case of error, 1 at end of table or 0 o success.
+ */
+int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
+ int (*match_func)(struct libmnt_fs *, void *), void *userdata,
+ struct libmnt_fs **fs)
+{
+ if (!tb || !itr || !fs || !match_func)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup next fs"));
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &tb->ents);
+
+ do {
+ if (itr->p != itr->head)
+ MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
+ else
+ break; /* end */
+
+ if (match_func(*fs, userdata))
+ return 0;
+ } while(1);
+
+ *fs = NULL;
+ return 1;
+}
+
+static int mnt_table_move_parent(struct libmnt_table *tb, int oldid, int newid)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+
+ if (!tb)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 0;
+
+ DBG(TAB, ul_debugobj(tb, "moving parent ID from %d -> %d", oldid, newid));
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (fs->parent == oldid)
+ fs->parent = newid;
+ }
+ return 0;
+}
+
+/**
+ * mnt_table_uniq_fs:
+ * @tb: table
+ * @flags: MNT_UNIQ_*
+ * @cmp: function to compare filesystems
+ *
+ * This function de-duplicate the @tb, but does not change order of the
+ * filesystems. The @cmp function has to return 0 if the filesystems are
+ * equal, otherwise non-zero.
+ *
+ * The default is to keep in the table later mounted filesystems (function uses
+ * backward mode iterator).
+ *
+ * @MNT_UNIQ_FORWARD: remove later mounted filesystems
+ * @MNT_UNIQ_KEEPTREE: keep parent->id relationship still valid
+ *
+ * Returns: negative number in case of error, or 0 o success.
+ */
+int mnt_table_uniq_fs(struct libmnt_table *tb, int flags,
+ int (*cmp)(struct libmnt_table *,
+ struct libmnt_fs *,
+ struct libmnt_fs *))
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+ int direction = MNT_ITER_BACKWARD;
+
+ if (!tb || !cmp)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 0;
+
+ if (flags & MNT_UNIQ_FORWARD)
+ direction = MNT_ITER_FORWARD;
+
+ DBG(TAB, ul_debugobj(tb, "de-duplicate"));
+ mnt_reset_iter(&itr, direction);
+
+ if ((flags & MNT_UNIQ_KEEPTREE) && !is_mountinfo(tb))
+ flags &= ~MNT_UNIQ_KEEPTREE;
+
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ int want = 1;
+ struct libmnt_iter xtr;
+ struct libmnt_fs *x;
+
+ mnt_reset_iter(&xtr, direction);
+ while (want && mnt_table_next_fs(tb, &xtr, &x) == 0) {
+ if (fs == x)
+ break;
+ want = cmp(tb, x, fs) != 0;
+ }
+
+ if (!want) {
+ if (flags & MNT_UNIQ_KEEPTREE)
+ mnt_table_move_parent(tb, mnt_fs_get_id(fs),
+ mnt_fs_get_parent_id(fs));
+
+ DBG(TAB, ul_debugobj(tb, "remove duplicate %s",
+ mnt_fs_get_target(fs)));
+ mnt_table_remove_fs(tb, fs);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * mnt_table_set_iter:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @fs: tab entry
+ *
+ * Sets @iter to the position of @fs in the file @tb.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs *fs)
+{
+ if (!tb || !itr || !fs)
+ return -EINVAL;
+
+ if (fs->tab != tb)
+ return -ENOENT;
+
+ MNT_ITER_INIT(itr, &tb->ents);
+ itr->p = &fs->ents;
+
+ return 0;
+}
+
+/**
+ * mnt_table_find_mountpoint:
+ * @tb: tab pointer
+ * @path: directory
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Same as mnt_get_mountpoint(), except this function does not rely on
+ * st_dev numbers.
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
+ const char *path,
+ int direction)
+{
+ char *mnt;
+ struct stat st;
+
+ if (!tb || !path || !*path)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup MOUNTPOINT: '%s'", path));
+
+ if (mnt_stat_mountpoint(path, &st))
+ return NULL;
+
+ mnt = strdup(path);
+ if (!mnt)
+ return NULL;
+
+ do {
+ char *p;
+ struct libmnt_fs *fs;
+
+ fs = mnt_table_find_target(tb, mnt, direction);
+ if (fs) {
+ free(mnt);
+ return fs;
+ }
+
+ p = stripoff_last_component(mnt);
+ if (!p)
+ break;
+ } while (mnt && *(mnt + 1) != '\0');
+
+ free(mnt);
+ return mnt_table_find_target(tb, "/", direction);
+}
+
+/**
+ * mnt_table_find_target:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in the given tab, three iterations are possible, the first
+ * with @path, the second with realpath(@path) and the third with realpath(@path)
+ * against realpath(fs->target). The 2nd and 3rd iterations are not performed when
+ * the @tb cache is not set (see mnt_table_set_cache()). If
+ * mnt_cache_set_targets(cache, mtab) was called, the 3rd iteration skips any
+ * @fs->target found in @mtab (see mnt_resolve_target()).
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *path, int direction)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+ char *cn;
+
+ if (!tb || !path || !*path)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s'", path));
+
+ /* native @target */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, path))
+ return fs;
+ }
+
+ /* try absolute path */
+ if (is_relative_path(path) && (cn = absolute_path(path))) {
+ DBG(TAB, ul_debugobj(tb, "lookup absolute TARGET: '%s'", cn));
+ mnt_reset_iter(&itr, direction);
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, cn)) {
+ free(cn);
+ return fs;
+ }
+ }
+ free(cn);
+ }
+
+ if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup canonical TARGET: '%s'", cn));
+
+ /* canonicalized paths in struct libmnt_table */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, cn))
+ return fs;
+ }
+
+ /* non-canonical path in struct libmnt_table
+ * -- note that mountpoint in /proc/self/mountinfo is already
+ * canonicalized by the kernel
+ */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ char *p;
+
+ if (!fs->target
+ || mnt_fs_is_swaparea(fs)
+ || mnt_fs_is_kernel(fs)
+ || (*fs->target == '/' && *(fs->target + 1) == '\0'))
+ continue;
+
+ p = mnt_resolve_target(fs->target, tb->cache);
+ /* both canonicalized, strcmp() is fine here */
+ if (p && strcmp(cn, p) == 0)
+ return fs;
+ }
+ return NULL;
+}
+
+/**
+ * mnt_table_find_srcpath:
+ * @tb: tab pointer
+ * @path: source path (devname or dirname) or NULL
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in the given tab, four iterations are possible, the first
+ * with @path, the second with realpath(@path), the third with tags (LABEL, UUID, ..)
+ * from @path and the fourth with realpath(@path) against realpath(entry->srcpath).
+ *
+ * The 2nd, 3rd and 4th iterations are not performed when the @tb cache is not
+ * set (see mnt_table_set_cache()).
+ *
+ * For btrfs returns tab entry for default id.
+ *
+ * Note that NULL is a valid source path; it will be replaced with "none". The
+ * "none" is used in /proc/{mounts,self/mountinfo} for pseudo filesystems.
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *path, int direction)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+ int ntags = 0, nents;
+ char *cn;
+ const char *p;
+
+ if (!tb || !path || !*path)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup SRCPATH: '%s'", path));
+
+ /* native paths */
+ mnt_reset_iter(&itr, direction);
+
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+
+ if (mnt_fs_streq_srcpath(fs, path)) {
+#ifdef HAVE_BTRFS_SUPPORT
+ if (fs->fstype && !strcmp(fs->fstype, "btrfs")) {
+ uint64_t default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
+ char *val;
+ size_t len;
+
+ if (default_id == UINT64_MAX)
+ DBG(TAB, ul_debug("not found btrfs volume setting"));
+
+ else if (mnt_fs_get_option(fs, "subvolid", &val, &len) == 0) {
+ uint64_t subvol_id;
+
+ if (mnt_parse_offset(val, len, &subvol_id)) {
+ DBG(TAB, ul_debugobj(tb, "failed to parse subvolid="));
+ continue;
+ }
+ if (subvol_id != default_id)
+ continue;
+ }
+ }
+#endif /* HAVE_BTRFS_SUPPORT */
+ return fs;
+ }
+ if (mnt_fs_get_tag(fs, NULL, NULL) == 0)
+ ntags++;
+ }
+
+ if (!path || !tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup canonical SRCPATH: '%s'", cn));
+
+ nents = mnt_table_get_nents(tb);
+
+ /* canonicalized paths in struct libmnt_table */
+ if (ntags < nents) {
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_streq_srcpath(fs, cn))
+ return fs;
+ }
+ }
+
+ /* evaluated tag */
+ if (ntags) {
+ int rc = mnt_cache_read_tags(tb->cache, cn);
+
+ mnt_reset_iter(&itr, direction);
+
+ if (rc == 0) {
+ /* @path's TAGs are in the cache */
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ const char *t, *v;
+
+ if (mnt_fs_get_tag(fs, &t, &v))
+ continue;
+
+ if (mnt_cache_device_has_tag(tb->cache, cn, t, v))
+ return fs;
+ }
+ } else if (rc < 0 && errno == EACCES) {
+ /* @path is inaccessible, try evaluating all TAGs in @tb
+ * by udev symlinks -- this could be expensive on systems
+ * with a huge fstab/mtab */
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ const char *t, *v, *x;
+ if (mnt_fs_get_tag(fs, &t, &v))
+ continue;
+ x = mnt_resolve_tag(t, v, tb->cache);
+
+ /* both canonicalized, strcmp() is fine here */
+ if (x && strcmp(x, cn) == 0)
+ return fs;
+ }
+ }
+ }
+
+ /* non-canonicalized paths in struct libmnt_table */
+ if (ntags <= nents) {
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs))
+ continue;
+ p = mnt_fs_get_srcpath(fs);
+ if (p)
+ p = mnt_resolve_path(p, tb->cache);
+
+ /* both canonicalized, strcmp() is fine here */
+ if (p && strcmp(p, cn) == 0)
+ return fs;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * mnt_table_find_tag:
+ * @tb: tab pointer
+ * @tag: tag name (e.g "LABEL", "UUID", ...)
+ * @val: tag value
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in the given tab, the first attempt is to lookup by @tag and
+ * @val, for the second attempt the tag is evaluated (converted to the device
+ * name) and mnt_table_find_srcpath() is performed. The second attempt is not
+ * performed when @tb cache is not set (see mnt_table_set_cache()).
+
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
+ const char *val, int direction)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+
+ if (!tb || !tag || !*tag || !val)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup by TAG: %s %s", tag, val));
+
+ /* look up by TAG */
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (fs->tagname && fs->tagval &&
+ strcmp(fs->tagname, tag) == 0 &&
+ strcmp(fs->tagval, val) == 0)
+ return fs;
+ }
+
+ if (tb->cache) {
+ /* look up by device name */
+ char *cn = mnt_resolve_tag(tag, val, tb->cache);
+ if (cn)
+ return mnt_table_find_srcpath(tb, cn, direction);
+ }
+ return NULL;
+}
+
+/**
+ * mnt_table_find_target_with_option:
+ * @tb: tab pointer
+ * @path: mountpoint directory
+ * @option: option name (e.g "subvol", "subvolid", ...)
+ * @val: option value or NULL
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Try to lookup an entry in the given tab that matches combination of @path
+ * and @option. In difference to mnt_table_find_target(), only @path iteration
+ * is done. No lookup by device name, no canonicalization.
+ *
+ * Returns: a tab entry or NULL.
+ *
+ * Since: 2.28
+ */
+struct libmnt_fs *mnt_table_find_target_with_option(
+ struct libmnt_table *tb, const char *path,
+ const char *option, const char *val, int direction)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs = NULL;
+ char *optval = NULL;
+ size_t optvalsz = 0, valsz = val ? strlen(val) : 0;
+
+ if (!tb || !path || !*path || !option || !*option || !val)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s' with OPTION %s %s", path, option, val));
+
+ /* look up by native @target with OPTION */
+ mnt_reset_iter(&itr, direction);
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, path)
+ && mnt_fs_get_option(fs, option, &optval, &optvalsz) == 0
+ && (!val || (optvalsz == valsz
+ && strncmp(optval, val, optvalsz) == 0)))
+ return fs;
+ }
+ return NULL;
+}
+
+/**
+ * mnt_table_find_source:
+ * @tb: tab pointer
+ * @source: TAG or path
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * This is a high-level API for mnt_table_find_{srcpath,tag}. You needn't care
+ * about the @source format (device, LABEL, UUID, ...). This function parses
+ * the @source and calls mnt_table_find_tag() or mnt_table_find_srcpath().
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb,
+ const char *source, int direction)
+{
+ struct libmnt_fs *fs;
+ char *t = NULL, *v = NULL;
+
+ if (!tb)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup SOURCE: '%s'", source));
+
+ if (blkid_parse_tag_string(source, &t, &v) || !mnt_valid_tagname(t))
+ fs = mnt_table_find_srcpath(tb, source, direction);
+ else
+ fs = mnt_table_find_tag(tb, t, v, direction);
+
+ free(t);
+ free(v);
+
+ return fs;
+}
+
+/**
+ * mnt_table_find_pair
+ * @tb: tab pointer
+ * @source: TAG or path
+ * @target: mountpoint
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * This function is implemented by mnt_fs_match_source() and
+ * mnt_fs_match_target() functions. It means that this is more expensive than
+ * others mnt_table_find_* function, because every @tab entry is fully evaluated.
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb, const char *source,
+ const char *target, int direction)
+{
+ struct libmnt_fs *fs = NULL;
+ struct libmnt_iter itr;
+
+ if (!tb || !target || !*target || !source || !*source)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup SOURCE: %s TARGET: %s", source, target));
+
+ mnt_reset_iter(&itr, direction);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+
+ if (mnt_fs_match_target(fs, target, tb->cache) &&
+ mnt_fs_match_source(fs, source, tb->cache))
+ return fs;
+ }
+
+ return NULL;
+}
+
+/**
+ * mnt_table_find_devno
+ * @tb: /proc/self/mountinfo
+ * @devno: device number
+ * @direction: MNT_ITER_{FORWARD,BACKWARD}
+ *
+ * Note that zero could be a valid device number for the root pseudo filesystem (e.g.
+ * tmpfs).
+ *
+ * Returns: a tab entry or NULL.
+ */
+struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
+ dev_t devno, int direction)
+{
+ struct libmnt_fs *fs = NULL;
+ struct libmnt_iter itr;
+
+ if (!tb)
+ return NULL;
+ if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup DEVNO: %d", (int) devno));
+
+ mnt_reset_iter(&itr, direction);
+
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (mnt_fs_get_devno(fs) == devno)
+ return fs;
+ }
+
+ return NULL;
+}
+
+static char *remove_mountpoint_from_path(const char *path, const char *mnt)
+{
+ char *res;
+ const char *p;
+ size_t sz;
+
+ sz = strlen(mnt);
+ p = sz > 1 ? path + sz : path;
+
+ res = *p ? strdup(p) : strdup("/");
+ DBG(UTILS, ul_debug("%s fs-root is %s", path, res));
+ return res;
+}
+
+#ifdef HAVE_BTRFS_SUPPORT
+static int get_btrfs_fs_root(struct libmnt_table *tb, struct libmnt_fs *fs, char **root)
+{
+ char *vol = NULL, *p;
+ size_t sz, volsz = 0;
+
+ DBG(BTRFS, ul_debug("lookup for btrfs FS root"));
+ *root = NULL;
+
+ if (mnt_fs_get_option(fs, "subvolid", &vol, &volsz) == 0) {
+ char *target;
+ struct libmnt_fs *f;
+ char subvolidstr[sizeof(stringify_value(UINT64_MAX))];
+
+ DBG(BTRFS, ul_debug(" found subvolid=%s, checking", vol));
+
+ assert (volsz + 1 < sizeof(stringify_value(UINT64_MAX)));
+ memcpy(subvolidstr, vol, volsz);
+ subvolidstr[volsz] = '\0';
+
+ target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache);
+ if (!target)
+ goto err;
+
+ DBG(BTRFS, ul_debug(" trying target=%s subvolid=%s", target, subvolidstr));
+ f = mnt_table_find_target_with_option(tb, target,
+ "subvolid", subvolidstr,
+ MNT_ITER_BACKWARD);
+ if (!tb->cache)
+ free(target);
+ if (!f)
+ goto not_found;
+
+ /* Instead of set of BACKREF queries constructing subvol path
+ * corresponding to a particular subvolid, use the one in
+ * mountinfo. Kernel keeps subvol path up to date.
+ */
+ if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0)
+ goto not_found;
+
+ } else if (mnt_fs_get_option(fs, "subvol", &vol, &volsz) != 0) {
+ /* If fstab entry does not contain "subvol", we have to
+ * check, whether btrfs has default subvolume defined.
+ */
+ uint64_t default_id;
+ char *target;
+ struct libmnt_fs *f;
+ char default_id_str[sizeof(stringify_value(UINT64_MAX))];
+
+ DBG(BTRFS, ul_debug(" subvolid/subvol not found, checking default"));
+
+ default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
+ if (default_id == UINT64_MAX)
+ goto not_found;
+
+ /* Volume has default subvolume. Check if it matches to
+ * the one in mountinfo.
+ *
+ * Only kernel >= 4.2 reports subvolid. On older
+ * kernels, there is no reasonable way to detect which
+ * subvolume was mounted.
+ */
+ target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache);
+ if (!target)
+ goto err;
+
+ snprintf(default_id_str, sizeof(default_id_str), "%llu",
+ (unsigned long long int) default_id);
+
+ DBG(BTRFS, ul_debug(" trying target=%s default subvolid=%s",
+ target, default_id_str));
+
+ f = mnt_table_find_target_with_option(tb, target,
+ "subvolid", default_id_str,
+ MNT_ITER_BACKWARD);
+ if (!tb->cache)
+ free(target);
+ if (!f)
+ goto not_found;
+
+ /* Instead of set of BACKREF queries constructing
+ * subvol path, use the one in mountinfo. Kernel does
+ * the evaluation for us.
+ */
+ DBG(BTRFS, ul_debug("setting FS root: btrfs default subvolid = %s",
+ default_id_str));
+
+ if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0)
+ goto not_found;
+ }
+
+ DBG(BTRFS, ul_debug(" using subvol=%s", vol));
+ sz = volsz;
+ if (*vol != '/')
+ sz++;
+ *root = malloc(sz + 1);
+ if (!*root)
+ goto err;
+ p = *root;
+ if (*vol != '/')
+ *p++ = '/';
+ memcpy(p, vol, volsz);
+ *(*root + sz) = '\0';
+ return 0;
+
+not_found:
+ DBG(BTRFS, ul_debug(" not found btrfs volume setting"));
+ return 1;
+err:
+ DBG(BTRFS, ul_debug(" error on btrfs volume setting evaluation"));
+ return errno ? -errno : -1;
+}
+#endif /* HAVE_BTRFS_SUPPORT */
+
+static const char *get_cifs_unc_subdir_path (const char *unc)
+{
+ /*
+ * 1 or more slash: %*[/]
+ * 1 or more non-slash: %*[^/]
+ * number of byte read: %n
+ */
+ int share_end = 0;
+ int r = sscanf(unc, "%*[/]%*[^/]%*[/]%*[^/]%n", &share_end);
+ if (r == EOF || share_end == 0)
+ return NULL;
+ return unc + share_end;
+}
+
+/*
+ * tb: /proc/self/mountinfo
+ * fs: filesystem
+ * mountflags: MS_BIND or 0
+ * fsroot: fs-root that will probably be used in the mountinfo file
+ * for @fs after mount(2)
+ *
+ * For btrfs subvolumes this function returns NULL, but @fsroot properly set.
+ *
+ * If @tb is NULL then defaults to '/'.
+ *
+ * Returns: entry from @tb that will be used as a source for @fs if the @fs is
+ * bindmount.
+ *
+ * Don't export to library API!
+ */
+struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
+ struct libmnt_fs *fs,
+ unsigned long mountflags,
+ char **fsroot)
+{
+ char *root = NULL;
+ const char *mnt = NULL;
+ struct libmnt_fs *src_fs = NULL;
+
+ assert(fs);
+ assert(fsroot);
+
+ DBG(TAB, ul_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs)));
+
+ if (tb && (mountflags & MS_BIND)) {
+ const char *src, *src_root;
+ char *xsrc = NULL;
+
+ DBG(TAB, ul_debug("fs-root for bind"));
+
+ src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache);
+ if (src) {
+ struct libmnt_fs *f = mnt_table_find_mountpoint(tb,
+ src, MNT_ITER_BACKWARD);
+ if (f)
+ mnt = mnt_fs_get_target(f);
+ }
+ if (mnt)
+ root = remove_mountpoint_from_path(src, mnt);
+
+ if (xsrc && !tb->cache) {
+ free(xsrc);
+ src = NULL;
+ }
+ if (!mnt)
+ goto err;
+
+ src_fs = mnt_table_find_target(tb, mnt, MNT_ITER_BACKWARD);
+ if (!src_fs) {
+ DBG(TAB, ul_debug("not found '%s' in mountinfo -- using default", mnt));
+ goto dflt;
+ }
+
+ /* It's possible that fstab_fs source is subdirectory on btrfs
+ * subvolume or another bind mount. For example:
+ *
+ * /dev/sdc /mnt/test btrfs subvol=/anydir
+ * /dev/sdc /mnt/test btrfs defaults
+ * /mnt/test/foo /mnt/test2 auto bind
+ *
+ * in this case, the root for /mnt/test2 will be /anydir/foo on
+ * /dev/sdc. It means we have to compose the final root from
+ * root and src_root.
+ */
+ src_root = mnt_fs_get_root(src_fs);
+
+ DBG(FS, ul_debugobj(fs, "source root: %s, source FS root: %s", root, src_root));
+
+ if (src_root && root && !startswith(root, src_root)) {
+ if (strcmp(root, "/") == 0) {
+ free(root);
+ root = strdup(src_root);
+ if (!root)
+ goto err;
+ } else {
+ char *tmp;
+ if (asprintf(&tmp, "%s%s", src_root, root) < 0)
+ goto err;
+ free(root);
+ root = tmp;
+ }
+ }
+ }
+
+#ifdef HAVE_BTRFS_SUPPORT
+ /*
+ * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path
+ */
+ else if (tb && fs->fstype &&
+ (!strcmp(fs->fstype, "btrfs") || !strcmp(fs->fstype, "auto"))) {
+ if (get_btrfs_fs_root(tb, fs, &root) < 0)
+ goto err;
+ }
+#endif /* HAVE_BTRFS_SUPPORT */
+
+dflt:
+ if (!root) {
+ root = strdup("/");
+ if (!root)
+ goto err;
+ }
+ *fsroot = root;
+
+ DBG(TAB, ul_debug("FS root result: %s", root));
+
+ return src_fs;
+err:
+ free(root);
+ return NULL;
+}
+
+
+int __mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs,
+ const char *tgt_prefix)
+{
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+
+ char *root = NULL;
+ char *src2 = NULL;
+ const char *src = NULL, *tgt = NULL;
+ char *xtgt = NULL, *tgt_buf = NULL;
+ int rc = 0;
+ dev_t devno = 0;
+
+ DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: target=%s, source=%s",
+ mnt_fs_get_target(fstab_fs),
+ mnt_fs_get_source(fstab_fs)));
+
+ if (mnt_fs_is_swaparea(fstab_fs) || mnt_table_is_empty(tb)) {
+ DBG(FS, ul_debugobj(fstab_fs, "- ignore (swap or no data)"));
+ return 0;
+ }
+
+ if (is_mountinfo(tb)) {
+ /* @tb is mountinfo, so we can try to use fs-roots */
+ struct libmnt_fs *rootfs;
+ int flags = 0;
+
+ if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0 ||
+ mnt_fs_get_option(fstab_fs, "rbind", NULL, NULL) == 0)
+ flags = MS_BIND;
+
+ rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root);
+ if (rootfs) {
+ const char *fstype = mnt_fs_get_fstype(rootfs);
+
+ src = mnt_fs_get_srcpath(rootfs);
+ if (fstype && strncmp(fstype, "nfs", 3) == 0 && root) {
+ /* NFS stores the root at the end of the source */
+ src = src2 = strconcat(src, root);
+ free(root);
+ root = NULL;
+ }
+ }
+ }
+
+ if (!src)
+ src = mnt_fs_get_source(fstab_fs);
+
+ if (src && tb->cache && !mnt_fs_is_pseudofs(fstab_fs))
+ src = mnt_resolve_spec(src, tb->cache);
+
+ if (src && root) {
+ struct stat st;
+
+ devno = mnt_fs_get_devno(fstab_fs);
+ if (!devno && stat(src, &st) == 0 && S_ISBLK(st.st_mode))
+ devno = st.st_rdev;
+ }
+
+ tgt = mnt_fs_get_target(fstab_fs);
+
+ if (!tgt || !src) {
+ DBG(FS, ul_debugobj(fstab_fs, "- ignore (no source/target)"));
+ goto done;
+ }
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: src=%s, tgt=%s, root=%s", src, tgt, root));
+
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+
+ int eq = mnt_fs_streq_srcpath(fs, src);
+
+ if (!eq && devno && mnt_fs_get_devno(fs) == devno)
+ eq = 1;
+
+ if (!eq) {
+ /* The source does not match. Maybe the source is a loop
+ * device backing file.
+ */
+ uint64_t offset = 0;
+ char *val;
+ size_t len;
+ int flags = 0;
+
+ if (!mnt_fs_get_srcpath(fs) ||
+ !startswith(mnt_fs_get_srcpath(fs), "/dev/loop"))
+ continue; /* does not look like loopdev */
+
+ if (mnt_fs_get_option(fstab_fs, "offset", &val, &len) == 0) {
+ if (mnt_parse_offset(val, len, &offset)) {
+ DBG(FS, ul_debugobj(fstab_fs, "failed to parse offset="));
+ continue;
+ }
+ flags = LOOPDEV_FL_OFFSET;
+ }
+
+ DBG(FS, ul_debugobj(fs, "checking for loop: src=%s", mnt_fs_get_srcpath(fs)));
+#if __linux__
+ if (!loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, 0, flags))
+ continue;
+
+ DBG(FS, ul_debugobj(fs, "used loop"));
+#endif
+ }
+
+ if (root) {
+ const char *fstype = mnt_fs_get_fstype(fs);
+
+ if (fstype && (strcmp(fstype, "cifs") == 0 ||
+ strcmp(fstype, "smb3") == 0)) {
+
+ const char *sub = get_cifs_unc_subdir_path(src);
+ const char *r = mnt_fs_get_root(fs);
+
+ if (!sub || !r || (!streq_paths(sub, r) &&
+ !streq_paths("/", r)))
+ continue;
+ } else {
+ const char *r = mnt_fs_get_root(fs);
+ if (!r || strcmp(r, root) != 0)
+ continue;
+ }
+ }
+
+ /*
+ * Compare target, try to minimize the number of situations when we
+ * need to canonicalize the path to avoid readlink() on
+ * mountpoints.
+ */
+ if (!xtgt) {
+ if (tgt_prefix) {
+ const char *p = *tgt == '/' ? tgt + 1 : tgt;
+ if (!*p)
+ tgt = tgt_prefix; /* target is '/' */
+ else {
+ if (asprintf(&tgt_buf, "%s/%s", tgt_prefix, p) <= 0) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ tgt = tgt_buf;
+ }
+ }
+
+ if (mnt_fs_streq_target(fs, tgt))
+ break;
+ if (tb->cache)
+ xtgt = mnt_resolve_path(tgt, tb->cache);
+ }
+ if (xtgt && mnt_fs_streq_target(fs, xtgt))
+ break;
+ }
+
+ if (fs)
+ rc = 1; /* success */
+done:
+ free(root);
+ free(tgt_buf);
+
+ DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc));
+ free(src2);
+ return rc;
+}
+
+/**
+ * mnt_table_is_fs_mounted:
+ * @tb: /proc/self/mountinfo file
+ * @fstab_fs: /etc/fstab entry
+ *
+ * Checks if the @fstab_fs entry is already in the @tb table. The "swap" is
+ * ignored. This function explicitly compares the source, target and root of the
+ * filesystems.
+ *
+ * Note that source and target are canonicalized only if a cache for @tb is
+ * defined (see mnt_table_set_cache()). The target canonicalization may
+ * trigger automount on autofs mountpoints!
+ *
+ * Don't use it if you want to know if a device is mounted, just use
+ * mnt_table_find_source() on the device.
+ *
+ * This function is designed mostly for "mount -a".
+ *
+ * Returns: 0 or 1
+ */
+int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
+{
+ return __mnt_table_is_fs_mounted(tb, fstab_fs, NULL);
+}
+
+
+#ifdef TEST_PROGRAM
+#include "pathnames.h"
+
+static int parser_errcb(struct libmnt_table *tb, const char *filename, int line)
+{
+ fprintf(stderr, "%s:%d: parse error\n", filename, line);
+
+ return 1; /* all errors are recoverable -- this is the default */
+}
+
+static struct libmnt_table *create_table(const char *file, int comments)
+{
+ struct libmnt_table *tb;
+
+ if (!file)
+ return NULL;
+ tb = mnt_new_table();
+ if (!tb)
+ goto err;
+
+ mnt_table_enable_comments(tb, comments);
+ mnt_table_set_parser_errcb(tb, parser_errcb);
+
+ if (mnt_table_parse_file(tb, file) != 0)
+ goto err;
+ return tb;
+err:
+ fprintf(stderr, "%s: parsing failed\n", file);
+ mnt_unref_table(tb);
+ return NULL;
+}
+
+static int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ int rc = -1;
+
+ tb = create_table(argv[1], FALSE);
+ if (!tb)
+ return -1;
+
+ fs = mnt_table_find_target(tb, "/", MNT_ITER_FORWARD);
+ if (!fs)
+ goto done;
+
+ printf("ORIGINAL:\n");
+ mnt_fs_print_debug(fs, stdout);
+
+ fs = mnt_copy_fs(NULL, fs);
+ if (!fs)
+ goto done;
+
+ printf("COPY:\n");
+ mnt_fs_print_debug(fs, stdout);
+ mnt_unref_fs(fs);
+ rc = 0;
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_parse(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb = NULL;
+ struct libmnt_iter *itr = NULL;
+ struct libmnt_fs *fs;
+ int rc = -1;
+ int parse_comments = FALSE;
+
+ if (argc == 3 && !strcmp(argv[2], "--comments"))
+ parse_comments = TRUE;
+
+ tb = create_table(argv[1], parse_comments);
+ if (!tb)
+ return -1;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ goto done;
+
+ if (mnt_table_get_intro_comment(tb))
+ fprintf(stdout, "Initial comment:\n\"%s\"\n",
+ mnt_table_get_intro_comment(tb));
+
+ while(mnt_table_next_fs(tb, itr, &fs) == 0)
+ mnt_fs_print_debug(fs, stdout);
+
+ if (mnt_table_get_trailing_comment(tb))
+ fprintf(stdout, "Trailing comment:\n\"%s\"\n",
+ mnt_table_get_trailing_comment(tb));
+ rc = 0;
+done:
+ mnt_free_iter(itr);
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_find_idx(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs = NULL;
+ struct libmnt_cache *mpc = NULL;
+ const char *file, *what;
+ int rc = -1;
+
+ if (argc != 3) {
+ fprintf(stderr, "try --help\n");
+ return -EINVAL;
+ }
+
+ file = argv[1], what = argv[2];
+
+ tb = create_table(file, FALSE);
+ if (!tb)
+ goto done;
+
+ /* create a cache for canonicalized paths */
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto done;
+ mnt_table_set_cache(tb, mpc);
+ mnt_unref_cache(mpc);
+
+ fs = mnt_table_find_target(tb, what, MNT_ITER_BACKWARD);
+
+ if (!fs)
+ fprintf(stderr, "%s: not found '%s'\n", file, what);
+ else {
+ int idx = mnt_table_find_fs(tb, fs);
+
+ if (idx < 1)
+ fprintf(stderr, "%s: not found '%s' fs pointer", file, what);
+ else {
+ printf("%s index is %d\n", what, idx);
+ rc = 0;
+ }
+ }
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs = NULL;
+ struct libmnt_cache *mpc = NULL;
+ const char *file, *find, *what;
+ int rc = -1;
+
+ if (argc != 4) {
+ fprintf(stderr, "try --help\n");
+ return -EINVAL;
+ }
+
+ file = argv[1], find = argv[2], what = argv[3];
+
+ tb = create_table(file, FALSE);
+ if (!tb)
+ goto done;
+
+ /* create a cache for canonicalized paths */
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto done;
+ mnt_table_set_cache(tb, mpc);
+ mnt_unref_cache(mpc);
+
+ if (strcasecmp(find, "source") == 0)
+ fs = mnt_table_find_source(tb, what, dr);
+ else if (strcasecmp(find, "target") == 0)
+ fs = mnt_table_find_target(tb, what, dr);
+
+ if (!fs)
+ fprintf(stderr, "%s: not found %s '%s'\n", file, find, what);
+ else {
+ mnt_fs_print_debug(fs, stdout);
+ rc = 0;
+ }
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_find_bw(struct libmnt_test *ts, int argc, char *argv[])
+{
+ return test_find(ts, argc, argv, MNT_ITER_BACKWARD);
+}
+
+static int test_find_fw(struct libmnt_test *ts, int argc, char *argv[])
+{
+ return test_find(ts, argc, argv, MNT_ITER_FORWARD);
+}
+
+static int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ struct libmnt_cache *mpc = NULL;
+ int rc = -1;
+
+ tb = create_table(argv[1], FALSE);
+ if (!tb)
+ return -1;
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto done;
+ mnt_table_set_cache(tb, mpc);
+ mnt_unref_cache(mpc);
+
+ fs = mnt_table_find_pair(tb, argv[2], argv[3], MNT_ITER_FORWARD);
+ if (!fs)
+ goto done;
+
+ mnt_fs_print_debug(fs, stdout);
+ rc = 0;
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb;
+ struct libmnt_fs *fs;
+ struct libmnt_cache *mpc = NULL;
+ int rc = -1;
+
+ tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+ if (!tb)
+ return -1;
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto done;
+ mnt_table_set_cache(tb, mpc);
+ mnt_unref_cache(mpc);
+
+ fs = mnt_table_find_mountpoint(tb, argv[1], MNT_ITER_BACKWARD);
+ if (!fs)
+ goto done;
+
+ mnt_fs_print_debug(fs, stdout);
+ rc = 0;
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb = NULL, *fstab = NULL;
+ struct libmnt_fs *fs;
+ struct libmnt_iter *itr = NULL;
+ struct libmnt_cache *mpc = NULL;
+ int writable = 0;
+ const char *path = NULL;
+
+ if (mnt_has_regular_mtab(&path, &writable) == 1 && writable == 0)
+ tb = mnt_new_table_from_file(path);
+ else
+ tb = mnt_new_table_from_file("/proc/self/mountinfo");
+
+ if (!tb) {
+ fprintf(stderr, "failed to parse mountinfo\n");
+ return -1;
+ }
+
+ fstab = create_table(argv[1], FALSE);
+ if (!fstab)
+ goto done;
+
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+ if (!itr)
+ goto done;
+
+ mpc = mnt_new_cache();
+ if (!mpc)
+ goto done;
+ mnt_table_set_cache(tb, mpc);
+ mnt_unref_cache(mpc);
+
+ while (mnt_table_next_fs(fstab, itr, &fs) == 0) {
+ if (mnt_table_is_fs_mounted(tb, fs))
+ printf("%s already mounted on %s\n",
+ mnt_fs_get_source(fs),
+ mnt_fs_get_target(fs));
+ else
+ printf("%s not mounted on %s\n",
+ mnt_fs_get_source(fs),
+ mnt_fs_get_target(fs));
+ }
+
+done:
+ mnt_unref_table(tb);
+ mnt_unref_table(fstab);
+ mnt_free_iter(itr);
+ return 0;
+}
+
+/* returns 0 if @a and @b targets are the same */
+static int test_uniq_cmp(struct libmnt_table *tb __attribute__((__unused__)),
+ struct libmnt_fs *a,
+ struct libmnt_fs *b)
+{
+ assert(a);
+ assert(b);
+
+ return mnt_fs_streq_target(a, mnt_fs_get_target(b)) ? 0 : 1;
+}
+
+static int test_uniq(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb;
+ int rc = -1;
+
+ if (argc != 2) {
+ fprintf(stderr, "try --help\n");
+ return -EINVAL;
+ }
+
+ tb = create_table(argv[1], FALSE);
+ if (!tb)
+ goto done;
+
+ if (mnt_table_uniq_fs(tb, 0, test_uniq_cmp) == 0) {
+ struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
+ struct libmnt_fs *fs;
+ if (!itr)
+ goto done;
+ while (mnt_table_next_fs(tb, itr, &fs) == 0)
+ mnt_fs_print_debug(fs, stdout);
+ mnt_free_iter(itr);
+ rc = 0;
+ }
+done:
+ mnt_unref_table(tb);
+ return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--parse", test_parse, "<file> [--comments] parse and print tab" },
+ { "--find-forward", test_find_fw, "<file> <source|target> <string>" },
+ { "--find-backward", test_find_bw, "<file> <source|target> <string>" },
+ { "--uniq-target", test_uniq, "<file>" },
+ { "--find-pair", test_find_pair, "<file> <source> <target>" },
+ { "--find-fs", test_find_idx, "<file> <target>" },
+ { "--find-mountpoint", test_find_mountpoint, "<path>" },
+ { "--copy-fs", test_copy_fs, "<file> copy root FS from the file" },
+ { "--is-mounted", test_is_mounted, "<fstab> check what from fstab is already mounted" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/tab_diff.c b/libmount/src/tab_diff.c
new file mode 100644
index 0000000..81694bc
--- /dev/null
+++ b/libmount/src/tab_diff.c
@@ -0,0 +1,377 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: tabdiff
+ * @title: Compare changes in mount tables
+ * @short_description: compare changes in the list of the mounted filesystems
+ */
+#include "mountP.h"
+
+struct tabdiff_entry {
+ int oper; /* MNT_TABDIFF_* flags; */
+
+ struct libmnt_fs *old_fs; /* pointer to the old FS */
+ struct libmnt_fs *new_fs; /* pointer to the new FS */
+
+ struct list_head changes;
+};
+
+struct libmnt_tabdiff {
+ int nchanges; /* number of changes */
+
+ struct list_head changes; /* list with modified entries */
+ struct list_head unused; /* list with unused entries */
+};
+
+/**
+ * mnt_new_tabdiff:
+ *
+ * Allocates a new table diff struct.
+ *
+ * Returns: new diff handler or NULL.
+ */
+struct libmnt_tabdiff *mnt_new_tabdiff(void)
+{
+ struct libmnt_tabdiff *df = calloc(1, sizeof(*df));
+
+ if (!df)
+ return NULL;
+
+ DBG(DIFF, ul_debugobj(df, "alloc"));
+
+ INIT_LIST_HEAD(&df->changes);
+ INIT_LIST_HEAD(&df->unused);
+ return df;
+}
+
+static void free_tabdiff_entry(struct tabdiff_entry *de)
+{
+ if (!de)
+ return;
+ list_del(&de->changes);
+ mnt_unref_fs(de->new_fs);
+ mnt_unref_fs(de->old_fs);
+ free(de);
+}
+
+/**
+ * mnt_free_tabdiff:
+ * @df: tab diff
+ *
+ * Deallocates tab diff struct and all entries.
+ */
+void mnt_free_tabdiff(struct libmnt_tabdiff *df)
+{
+ if (!df)
+ return;
+
+ DBG(DIFF, ul_debugobj(df, "free"));
+
+ while (!list_empty(&df->changes)) {
+ struct tabdiff_entry *de = list_entry(df->changes.next,
+ struct tabdiff_entry, changes);
+ free_tabdiff_entry(de);
+ }
+
+ free(df);
+}
+
+/**
+ * mnt_tabdiff_next_change:
+ * @df: tabdiff pointer
+ * @itr: iterator
+ * @old_fs: returns the old entry or NULL if new entry added
+ * @new_fs: returns the new entry or NULL if old entry removed
+ * @oper: MNT_TABDIFF_{MOVE,UMOUNT,REMOUNT,MOUNT} flags
+ *
+ * The options @old_fs, @new_fs and @oper are optional.
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ */
+int mnt_tabdiff_next_change(struct libmnt_tabdiff *df, struct libmnt_iter *itr,
+ struct libmnt_fs **old_fs, struct libmnt_fs **new_fs, int *oper)
+{
+ int rc = 1;
+ struct tabdiff_entry *de = NULL;
+
+ if (!df || !itr)
+ return -EINVAL;
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &df->changes);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, de, struct tabdiff_entry, changes);
+ rc = 0;
+ }
+
+ if (old_fs)
+ *old_fs = de ? de->old_fs : NULL;
+ if (new_fs)
+ *new_fs = de ? de->new_fs : NULL;
+ if (oper)
+ *oper = de ? de->oper : 0;
+
+ return rc;
+}
+
+static int tabdiff_reset(struct libmnt_tabdiff *df)
+{
+ assert(df);
+
+ DBG(DIFF, ul_debugobj(df, "resetting"));
+
+ /* zeroize all entries and move them to the list of unused
+ */
+ while (!list_empty(&df->changes)) {
+ struct tabdiff_entry *de = list_entry(df->changes.next,
+ struct tabdiff_entry, changes);
+
+ list_del_init(&de->changes);
+ list_add_tail(&de->changes, &df->unused);
+
+ mnt_unref_fs(de->new_fs);
+ mnt_unref_fs(de->old_fs);
+
+ de->new_fs = de->old_fs = NULL;
+ de->oper = 0;
+ }
+
+ df->nchanges = 0;
+ return 0;
+}
+
+static int tabdiff_add_entry(struct libmnt_tabdiff *df, struct libmnt_fs *old,
+ struct libmnt_fs *new, int oper)
+{
+ struct tabdiff_entry *de;
+
+ assert(df);
+
+ DBG(DIFF, ul_debugobj(df, "add change on %s",
+ mnt_fs_get_target(new ? new : old)));
+
+ if (!list_empty(&df->unused)) {
+ de = list_entry(df->unused.next, struct tabdiff_entry, changes);
+ list_del(&de->changes);
+ } else {
+ de = calloc(1, sizeof(*de));
+ if (!de)
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&de->changes);
+
+ mnt_ref_fs(new);
+ mnt_ref_fs(old);
+
+ mnt_unref_fs(de->new_fs);
+ mnt_unref_fs(de->old_fs);
+
+ de->old_fs = old;
+ de->new_fs = new;
+ de->oper = oper;
+
+ list_add_tail(&de->changes, &df->changes);
+ df->nchanges++;
+ return 0;
+}
+
+static struct tabdiff_entry *tabdiff_get_mount(struct libmnt_tabdiff *df,
+ const char *src,
+ int id)
+{
+ struct list_head *p;
+
+ assert(df);
+
+ list_for_each(p, &df->changes) {
+ struct tabdiff_entry *de;
+
+ de = list_entry(p, struct tabdiff_entry, changes);
+
+ if (de->oper == MNT_TABDIFF_MOUNT && de->new_fs &&
+ mnt_fs_get_id(de->new_fs) == id) {
+
+ const char *s = mnt_fs_get_source(de->new_fs);
+
+ if (s == NULL && src == NULL)
+ return de;
+ if (s && src && strcmp(s, src) == 0)
+ return de;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * mnt_diff_tables:
+ * @df: diff handler
+ * @old_tab: old table
+ * @new_tab: new table
+ *
+ * Compares @old_tab and @new_tab, the result is stored in @df and accessible by
+ * mnt_tabdiff_next_change().
+ *
+ * Returns: number of changes, negative number in case of error.
+ */
+int mnt_diff_tables(struct libmnt_tabdiff *df, struct libmnt_table *old_tab,
+ struct libmnt_table *new_tab)
+{
+ struct libmnt_fs *fs;
+ struct libmnt_iter itr;
+ int no, nn;
+
+ if (!df || !old_tab || !new_tab)
+ return -EINVAL;
+
+ tabdiff_reset(df);
+
+ no = mnt_table_get_nents(old_tab);
+ nn = mnt_table_get_nents(new_tab);
+
+ if (!no && !nn) /* both tables are empty */
+ return 0;
+
+ DBG(DIFF, ul_debugobj(df, "analyze new (%d entries), "
+ "old (%d entries)",
+ nn, no));
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ /* all mounted or umounted */
+ if (!no && nn) {
+ while(mnt_table_next_fs(new_tab, &itr, &fs) == 0)
+ tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
+ goto done;
+
+ } else if (no && !nn) {
+ while(mnt_table_next_fs(old_tab, &itr, &fs) == 0)
+ tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
+ goto done;
+ }
+
+ /* search newly mounted or modified */
+ while(mnt_table_next_fs(new_tab, &itr, &fs) == 0) {
+ struct libmnt_fs *o_fs;
+ const char *src = mnt_fs_get_source(fs),
+ *tgt = mnt_fs_get_target(fs);
+
+ o_fs = mnt_table_find_pair(old_tab, src, tgt, MNT_ITER_FORWARD);
+ if (!o_fs)
+ /* 'fs' is not in the old table -- so newly mounted */
+ tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
+ else {
+ /* is modified? */
+ const char *v1 = mnt_fs_get_vfs_options(o_fs),
+ *v2 = mnt_fs_get_vfs_options(fs),
+ *f1 = mnt_fs_get_fs_options(o_fs),
+ *f2 = mnt_fs_get_fs_options(fs);
+
+ if ((v1 && v2 && strcmp(v1, v2) != 0) || (f1 && f2 && strcmp(f1, f2) != 0))
+ tabdiff_add_entry(df, o_fs, fs, MNT_TABDIFF_REMOUNT);
+ }
+ }
+
+ /* search umounted or moved */
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while(mnt_table_next_fs(old_tab, &itr, &fs) == 0) {
+ const char *src = mnt_fs_get_source(fs),
+ *tgt = mnt_fs_get_target(fs);
+
+ if (!mnt_table_find_pair(new_tab, src, tgt, MNT_ITER_FORWARD)) {
+ struct tabdiff_entry *de;
+
+ de = tabdiff_get_mount(df, src, mnt_fs_get_id(fs));
+ if (de) {
+ mnt_ref_fs(fs);
+ mnt_unref_fs(de->old_fs);
+ de->oper = MNT_TABDIFF_MOVE;
+ de->old_fs = fs;
+ } else
+ tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
+ }
+ }
+done:
+ DBG(DIFF, ul_debugobj(df, "%d changes detected", df->nchanges));
+ return df->nchanges;
+}
+
+#ifdef TEST_PROGRAM
+
+static int test_diff(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_table *tb_old, *tb_new;
+ struct libmnt_tabdiff *diff;
+ struct libmnt_iter *itr;
+ struct libmnt_fs *old, *new;
+ int rc = -1, change;
+
+ tb_old = mnt_new_table_from_file(argv[1]);
+ tb_new = mnt_new_table_from_file(argv[2]);
+ diff = mnt_new_tabdiff();
+ itr = mnt_new_iter(MNT_ITER_FORWARD);
+
+ if (!tb_old || !tb_new || !diff || !itr) {
+ warnx("failed to allocate resources");
+ goto done;
+ }
+
+ rc = mnt_diff_tables(diff, tb_old, tb_new);
+ if (rc < 0)
+ goto done;
+
+ while(mnt_tabdiff_next_change(diff, itr, &old, &new, &change) == 0) {
+
+ printf("%s on %s: ", mnt_fs_get_source(new ? new : old),
+ mnt_fs_get_target(new ? new : old));
+
+ switch(change) {
+ case MNT_TABDIFF_MOVE:
+ printf("MOVED to %s\n", mnt_fs_get_target(new));
+ break;
+ case MNT_TABDIFF_UMOUNT:
+ printf("UMOUNTED\n");
+ break;
+ case MNT_TABDIFF_REMOUNT:
+ printf("REMOUNTED from '%s' to '%s'\n",
+ mnt_fs_get_options(old),
+ mnt_fs_get_options(new));
+ break;
+ case MNT_TABDIFF_MOUNT:
+ printf("MOUNTED\n");
+ break;
+ default:
+ printf("unknown change!\n");
+ }
+ }
+
+ rc = 0;
+done:
+ mnt_unref_table(tb_old);
+ mnt_unref_table(tb_new);
+ mnt_free_tabdiff(diff);
+ mnt_free_iter(itr);
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--diff", test_diff, "<old> <new> prints change" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/tab_parse.c b/libmount/src/tab_parse.c
new file mode 100644
index 0000000..4407f9c
--- /dev/null
+++ b/libmount/src/tab_parse.c
@@ -0,0 +1,1281 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+#ifdef HAVE_SCANDIRAT
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif /* !__USE_GNU */
+#endif /* HAVE_SCANDIRAT */
+
+#include <ctype.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "fileutils.h"
+#include "mangle.h"
+#include "mountP.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+struct libmnt_parser {
+ FILE *f; /* fstab, mtab, swaps or mountinfo ... */
+ const char *filename; /* file name or NULL */
+ char *buf; /* buffer (the current line content) */
+ size_t bufsiz; /* size of the buffer */
+ size_t line; /* current line */
+};
+
+static void parser_cleanup(struct libmnt_parser *pa)
+{
+ if (!pa)
+ return;
+ free(pa->buf);
+ memset(pa, 0, sizeof(*pa));
+}
+
+static const char *next_s32(const char *s, int *num, int *rc)
+{
+ char *end = NULL;
+
+ if (!s || !*s)
+ return s;
+
+ errno = 0;
+ *rc = -EINVAL;
+ *num = strtol(s, &end, 10);
+ if (end == NULL || s == end)
+ return s;
+ if (errno == 0 && (*end == ' ' || *end == '\t' || *end == '\0'))
+ *rc = 0;
+ return end;
+}
+
+static const char *next_u64(const char *s, uint64_t *num, int *rc)
+{
+ char *end = NULL;
+
+ if (!s || !*s)
+ return s;
+
+ errno = 0;
+ *rc = -EINVAL;
+ *num = (uint64_t) strtoumax(s, &end, 10);
+ if (end == NULL || s == end)
+ return s;
+ if (errno == 0 && (*end == ' ' || *end == '\t' || *end == '\0'))
+ *rc = 0;
+ return end;
+}
+
+static inline const char *skip_separator(const char *p)
+{
+ while (p && (*p == ' ' || *p == '\t'))
+ ++p;
+ return p;
+}
+
+static inline const char *skip_nonspearator(const char *p)
+{
+ while (p && *p && !(*p == ' ' || *p == '\t'))
+ p++;
+ return p;
+}
+
+/*
+ * Parses one line from {fs,m}tab
+ */
+static int mnt_parse_table_line(struct libmnt_fs *fs, const char *s)
+{
+ int rc = 0;
+ char *p = NULL;
+
+ fs->passno = fs->freq = 0;
+
+ /* (1) source */
+ p = unmangle(s, &s);
+ if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [source]"));
+ free(p);
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (2) target */
+ fs->target = unmangle(s, &s);
+ if (!fs->target) {
+ DBG(TAB, ul_debug("tab parse error: [target]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (3) FS type */
+ p = unmangle(s, &s);
+ if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [fstype]"));
+ free(p);
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (4) options (optional) */
+ p = unmangle(s, &s);
+ if (p && (rc = mnt_fs_set_options(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [options]"));
+ free(p);
+ goto fail;
+ }
+ if (!p)
+ goto done;
+ free(p);
+
+ s = skip_separator(s);
+ if (!s || !*s)
+ goto done;
+
+ /* (5) freq (optional) */
+ s = next_s32(s, &fs->freq, &rc);
+ if (s && *s && rc) {
+ DBG(TAB, ul_debug("tab parse error: [freq]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+ if (!s || !*s)
+ goto done;
+
+ /* (6) passno (optional) */
+ s = next_s32(s, &fs->passno, &rc);
+ if (s && *s && rc) {
+ DBG(TAB, ul_debug("tab parse error: [passno]"));
+ goto fail;
+ }
+
+done:
+ return 0;
+fail:
+ if (rc == 0)
+ rc = -EINVAL;
+ DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc));
+ return rc;
+}
+
+
+/*
+ * Parses one line from a mountinfo file
+ */
+static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s)
+{
+ int rc = 0;
+ unsigned int maj, min;
+ char *p;
+
+ fs->flags |= MNT_FS_KERNEL;
+
+ /* (1) id */
+ s = next_s32(s, &fs->id, &rc);
+ if (!s || !*s || rc) {
+ DBG(TAB, ul_debug("tab parse error: [id]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (2) parent */
+ s = next_s32(s, &fs->parent, &rc);
+ if (!s || !*s || rc) {
+ DBG(TAB, ul_debug("tab parse error: [parent]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (3) maj:min */
+ if (sscanf(s, "%u:%u", &maj, &min) != 2) {
+ DBG(TAB, ul_debug("tab parse error: [maj:min]"));
+ goto fail;
+ }
+ fs->devno = makedev(maj, min);
+ s = skip_nonspearator(s);
+ s = skip_separator(s);
+
+ /* (4) mountroot */
+ fs->root = unmangle(s, &s);
+ if (!fs->root) {
+ DBG(TAB, ul_debug("tab parse error: [mountroot]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (5) target */
+ fs->target = unmangle(s, &s);
+ if (!fs->target) {
+ DBG(TAB, ul_debug("tab parse error: [target]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (6) vfs options (fs-independent) */
+ fs->vfs_optstr = unmangle(s, &s);
+ if (!fs->vfs_optstr) {
+ DBG(TAB, ul_debug("tab parse error: [VFS options]"));
+ goto fail;
+ }
+
+ /* (7) optional fields, terminated by " - " */
+ p = strstr(s, " - ");
+ if (!p) {
+ DBG(TAB, ul_debug("mountinfo parse error: separator not found"));
+ return -EINVAL;
+ }
+ if (p > s + 1)
+ fs->opt_fields = strndup(s + 1, p - s - 1);
+
+ s = skip_separator(p + 3);
+
+ /* (8) FS type */
+ p = unmangle(s, &s);
+ if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [fstype]"));
+ free(p);
+ goto fail;
+ }
+
+ /* (9) source -- maybe empty string */
+ if (!s || !*s) {
+ DBG(TAB, ul_debug("tab parse error: [source]"));
+ goto fail;
+ } else if (*s == ' ' && *(s+1) == ' ') {
+ if ((rc = mnt_fs_set_source(fs, ""))) {
+ DBG(TAB, ul_debug("tab parse error: [empty source]"));
+ goto fail;
+ }
+ } else {
+ s = skip_separator(s);
+ p = unmangle(s, &s);
+ if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [regular source]"));
+ free(p);
+ goto fail;
+ }
+ }
+
+ s = skip_separator(s);
+
+ /* (10) fs options (fs specific) */
+ fs->fs_optstr = unmangle(s, &s);
+ if (!fs->fs_optstr) {
+ DBG(TAB, ul_debug("tab parse error: [FS options]"));
+ goto fail;
+ }
+
+ /* merge VFS and FS options to one string */
+ fs->optstr = mnt_fs_strdup_options(fs);
+ if (!fs->optstr) {
+ rc = -ENOMEM;
+ DBG(TAB, ul_debug("tab parse error: [merge VFS and FS options]"));
+ goto fail;
+ }
+
+ return 0;
+fail:
+ if (rc == 0)
+ rc = -EINVAL;
+ DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc));
+ return rc;
+}
+
+/*
+ * Parses one line from utab file
+ */
+static int mnt_parse_utab_line(struct libmnt_fs *fs, const char *s)
+{
+ const char *p = s;
+
+ assert(fs);
+ assert(s);
+ assert(!fs->source);
+ assert(!fs->target);
+
+ while (p && *p) {
+ const char *end = NULL;
+
+ while (*p == ' ') p++;
+ if (!*p)
+ break;
+
+ if (!fs->source && !strncmp(p, "SRC=", 4)) {
+ char *v = unmangle(p + 4, &end);
+ if (!v)
+ goto enomem;
+ if (__mnt_fs_set_source_ptr(fs, v))
+ free(v);
+
+ } else if (!fs->target && !strncmp(p, "TARGET=", 7)) {
+ fs->target = unmangle(p + 7, &end);
+ if (!fs->target)
+ goto enomem;
+
+ } else if (!fs->root && !strncmp(p, "ROOT=", 5)) {
+ fs->root = unmangle(p + 5, &end);
+ if (!fs->root)
+ goto enomem;
+
+ } else if (!fs->bindsrc && !strncmp(p, "BINDSRC=", 8)) {
+ fs->bindsrc = unmangle(p + 8, &end);
+ if (!fs->bindsrc)
+ goto enomem;
+
+ } else if (!fs->user_optstr && !strncmp(p, "OPTS=", 5)) {
+ fs->user_optstr = unmangle(p + 5, &end);
+ if (!fs->user_optstr)
+ goto enomem;
+
+ } else if (!fs->attrs && !strncmp(p, "ATTRS=", 6)) {
+ fs->attrs = unmangle(p + 6, &end);
+ if (!fs->attrs)
+ goto enomem;
+
+ } else {
+ /* unknown variable */
+ while (*p && *p != ' ') p++;
+ }
+ if (end)
+ p = end;
+ }
+
+ return 0;
+enomem:
+ DBG(TAB, ul_debug("utab parse error: ENOMEM"));
+ return -ENOMEM;
+}
+
+/*
+ * Parses one line from /proc/swaps
+ */
+static int mnt_parse_swaps_line(struct libmnt_fs *fs, const char *s)
+{
+ uint64_t num;
+ int rc = 0;
+ char *p;
+
+ /* (1) source */
+ p = unmangle(s, &s);
+ if (p) {
+ char *x = (char *) endswith(p, PATH_DELETED_SUFFIX);
+ if (x && *x)
+ *x = '\0';
+ }
+ if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) {
+ DBG(TAB, ul_debug("tab parse error: [source]"));
+ free(p);
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (2) type */
+ fs->swaptype = unmangle(s, &s);
+ if (!fs->swaptype) {
+ DBG(TAB, ul_debug("tab parse error: [swaptype]"));
+ goto fail;
+ }
+
+ s = skip_separator(s);
+
+ /* (3) size */
+ s = next_u64(s, &num, &rc);
+ if (!s || !*s || rc) {
+ DBG(TAB, ul_debug("tab parse error: [size]"));
+ goto fail;
+ }
+ fs->size = num;
+
+ s = skip_separator(s);
+
+ /* (4) size */
+ s = next_u64(s, &num, &rc);
+ if (!s || !*s || rc) {
+ DBG(TAB, ul_debug("tab parse error: [used size]"));
+ goto fail;
+ }
+ fs->usedsize = num;
+
+ s = skip_separator(s);
+
+ /* (5) priority */
+ s = next_s32(s, &fs->priority, &rc);
+ if (rc) {
+ DBG(TAB, ul_debug("tab parse error: [priority]"));
+ goto fail;
+ }
+
+ mnt_fs_set_fstype(fs, "swap");
+ return 0;
+fail:
+ if (rc == 0)
+ rc = -EINVAL;
+ DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc));
+ return rc;
+}
+
+
+/*
+ * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*)
+ *
+ * Note that we aren't trying to guess the utab file format, because this file
+ * always has to be parsed by private libmount routines with an explicitly defined
+ * format.
+ *
+ * mountinfo: "<number> <number> ... "
+ */
+static int guess_table_format(const char *line)
+{
+ unsigned int a, b;
+
+ DBG(TAB, ul_debug("trying to guess table type"));
+
+ if (sscanf(line, "%u %u", &a, &b) == 2)
+ return MNT_FMT_MOUNTINFO;
+
+ if (strncmp(line, "Filename\t", 9) == 0)
+ return MNT_FMT_SWAPS;
+
+ return MNT_FMT_FSTAB; /* fstab, mtab or /proc/mounts */
+}
+
+static int is_comment_line(const char *line)
+{
+ const char *p = skip_blank(line);
+
+ if (p && (*p == '#' || *p == '\n'))
+ return 1;
+ return 0;
+}
+
+/* returns 1 if the last line in the @str is blank */
+static int is_terminated_by_blank(const char *str)
+{
+ size_t sz = str ? strlen(str) : 0;
+ const char *p = sz ? str + (sz - 1) : NULL;
+
+ if (!sz || !p || *p != '\n')
+ return 0; /* empty or not terminated by '\n' */
+ if (p == str)
+ return 1; /* only '\n' */
+ p--;
+ while (p > str && (*p == ' ' || *p == '\t'))
+ p--;
+ return *p == '\n' ? 1 : 0;
+}
+
+/*
+ * Reads the next line from the file.
+ *
+ * Returns 0 if the line is a comment
+ * 1 if the line is not a comment
+ * <0 on error
+ */
+static int next_comment_line(struct libmnt_parser *pa, char **last)
+{
+ if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0)
+ return feof(pa->f) ? 1 : -errno;
+
+ pa->line++;
+ *last = strchr(pa->buf, '\n');
+
+ return is_comment_line(pa->buf) ? 0 : 1;
+}
+
+static int append_comment(struct libmnt_table *tb,
+ struct libmnt_fs *fs,
+ const char *comm,
+ int eof)
+{
+ int rc, intro = mnt_table_get_nents(tb) == 0;
+
+ if (intro && is_terminated_by_blank(mnt_table_get_intro_comment(tb)))
+ intro = 0;
+
+ DBG(TAB, ul_debugobj(tb, "appending %s comment",
+ intro ? "intro" :
+ eof ? "trailing" : "fs"));
+ if (intro)
+ rc = mnt_table_append_intro_comment(tb, comm);
+ else if (eof) {
+ rc = mnt_table_set_trailing_comment(tb,
+ mnt_fs_get_comment(fs));
+ if (!rc)
+ rc = mnt_table_append_trailing_comment(tb, comm);
+ if (!rc)
+ rc = mnt_fs_set_comment(fs, NULL);
+ } else
+ rc = mnt_fs_append_comment(fs, comm);
+ return rc;
+}
+
+/*
+ * Read and parse the next line from {fs,m}tab or mountinfo
+ */
+static int mnt_table_parse_next(struct libmnt_parser *pa,
+ struct libmnt_table *tb,
+ struct libmnt_fs *fs)
+{
+ char *s;
+ int rc;
+
+ assert(tb);
+ assert(pa);
+ assert(fs);
+
+ /* read the next non-blank non-comment line */
+next_line:
+ do {
+ if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0)
+ return -EINVAL;
+ pa->line++;
+ s = strchr(pa->buf, '\n');
+ if (!s) {
+ DBG(TAB, ul_debugobj(tb, "%s:%zu: no final newline",
+ pa->filename, pa->line));
+
+ /* Missing final newline? Otherwise an extremely */
+ /* long line - assume file was corrupted */
+ if (feof(pa->f))
+ s = memchr(pa->buf, '\0', pa->bufsiz);
+
+ /* comments parser */
+ } else if (tb->comms
+ && (tb->fmt == MNT_FMT_GUESS || tb->fmt == MNT_FMT_FSTAB)
+ && is_comment_line(pa->buf)) {
+ do {
+ rc = append_comment(tb, fs, pa->buf, feof(pa->f));
+ if (!rc)
+ rc = next_comment_line(pa, &s);
+ } while (rc == 0);
+
+ if (rc == 1 && feof(pa->f))
+ rc = append_comment(tb, fs, NULL, 1);
+ if (rc < 0)
+ return rc;
+
+ }
+
+ if (!s)
+ goto err;
+ *s = '\0';
+ if (s > pa->buf && *(s - 1) == '\r')
+ *(--s) = '\0';
+ s = (char *) skip_blank(pa->buf);
+ } while (*s == '\0' || *s == '#');
+
+ if (tb->fmt == MNT_FMT_GUESS) {
+ tb->fmt = guess_table_format(s);
+ if (tb->fmt == MNT_FMT_SWAPS)
+ goto next_line; /* skip swap header */
+ }
+
+ switch (tb->fmt) {
+ case MNT_FMT_FSTAB:
+ rc = mnt_parse_table_line(fs, s);
+ break;
+ case MNT_FMT_MOUNTINFO:
+ rc = mnt_parse_mountinfo_line(fs, s);
+ break;
+ case MNT_FMT_UTAB:
+ rc = mnt_parse_utab_line(fs, s);
+ break;
+ case MNT_FMT_SWAPS:
+ if (strncmp(s, "Filename\t", 9) == 0)
+ goto next_line; /* skip swap header */
+ rc = mnt_parse_swaps_line(fs, s);
+ break;
+ default:
+ rc = -1; /* unknown format */
+ break;
+ }
+
+ if (rc == 0)
+ return 0;
+err:
+ DBG(TAB, ul_debugobj(tb, "%s:%zu: %s parse error", pa->filename, pa->line,
+ tb->fmt == MNT_FMT_MOUNTINFO ? "mountinfo" :
+ tb->fmt == MNT_FMT_SWAPS ? "swaps" :
+ tb->fmt == MNT_FMT_FSTAB ? "tab" : "utab"));
+
+ /* by default all errors are recoverable, otherwise behavior depends on
+ * the errcb() function. See mnt_table_set_parser_errcb().
+ */
+ return tb->errcb ? tb->errcb(tb, pa->filename, pa->line) : 1;
+}
+
+static pid_t path_to_tid(const char *filename)
+{
+ char *path = mnt_resolve_path(filename, NULL);
+ char *p, *end = NULL;
+ pid_t tid = 0;
+
+ if (!path)
+ goto done;
+ p = strrchr(path, '/');
+ if (!p)
+ goto done;
+ *p = '\0';
+ p = strrchr(path, '/');
+ if (!p)
+ goto done;
+ p++;
+
+ errno = 0;
+ tid = strtol(p, &end, 10);
+ if (errno || p == end || (end && *end)) {
+ tid = 0;
+ goto done;
+ }
+ DBG(TAB, ul_debug("TID for %s is %d", filename, tid));
+done:
+ free(path);
+ return tid;
+}
+
+static int kernel_fs_postparse(struct libmnt_table *tb,
+ struct libmnt_fs *fs, pid_t *tid,
+ const char *filename)
+{
+ int rc = 0;
+ const char *src = mnt_fs_get_srcpath(fs);
+
+ /* This is a filesystem description from /proc, so we're in some process
+ * namespace. Let's remember the process PID.
+ */
+ if (filename && *tid == -1)
+ *tid = path_to_tid(filename);
+
+ fs->tid = *tid;
+
+ /*
+ * Convert obscure /dev/root to something more usable
+ */
+ if (src && strcmp(src, "/dev/root") == 0) {
+ char *real = NULL;
+
+ rc = mnt_guess_system_root(fs->devno, tb->cache, &real);
+ if (rc < 0)
+ return rc;
+
+ if (rc == 0 && real) {
+ DBG(TAB, ul_debugobj(tb, "canonical root FS: %s", real));
+ rc = __mnt_fs_set_source_ptr(fs, real);
+
+ } else if (rc == 1) {
+ /* mnt_guess_system_root() returns 1 if not able to convert to
+ * the real devname; ignore this problem */
+ rc = 0;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * mnt_table_parse_stream:
+ * @tb: tab pointer
+ * @f: file stream
+ * @filename: filename used for debug and error messages
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f, const char *filename)
+{
+ int rc = -1;
+ int flags = 0;
+ pid_t tid = -1;
+ struct libmnt_parser pa = { .line = 0 };
+
+ assert(tb);
+ assert(f);
+ assert(filename);
+
+ DBG(TAB, ul_debugobj(tb, "%s: start parsing [entries=%d, filter=%s]",
+ filename, mnt_table_get_nents(tb),
+ tb->fltrcb ? "yes" : "not"));
+
+ pa.filename = filename;
+ pa.f = f;
+
+ /* necessary for /proc/mounts only, the /proc/self/mountinfo
+ * parser sets the flag properly
+ */
+ if (tb->fmt == MNT_FMT_SWAPS)
+ flags = MNT_FS_SWAP;
+ else if (filename && strcmp(filename, _PATH_PROC_MOUNTS) == 0)
+ flags = MNT_FS_KERNEL;
+
+ do {
+ struct libmnt_fs *fs;
+
+ if (feof(f)) {
+ DBG(TAB, ul_debugobj(tb, "end-of-file"));
+ break;
+ }
+ fs = mnt_new_fs();
+ if (!fs)
+ goto err;
+
+ /* parse */
+ rc = mnt_table_parse_next(&pa, tb, fs);
+
+ if (rc == 0 && tb->fltrcb && tb->fltrcb(fs, tb->fltrcb_data))
+ rc = 1; /* filtered out by callback... */
+
+ /* add to the table */
+ if (rc == 0) {
+ rc = mnt_table_add_fs(tb, fs);
+ fs->flags |= flags;
+
+ if (rc == 0 && tb->fmt == MNT_FMT_MOUNTINFO) {
+ rc = kernel_fs_postparse(tb, fs, &tid, filename);
+ if (rc)
+ mnt_table_remove_fs(tb, fs);
+ }
+ }
+
+ /* remove reference (or deallocate on error) */
+ mnt_unref_fs(fs);
+
+ /* recoverable error */
+ if (rc > 0) {
+ DBG(TAB, ul_debugobj(tb, "recoverable error (continue)"));
+ continue;
+ }
+
+ /* fatal errors */
+ if (rc < 0 && !feof(f)) {
+ DBG(TAB, ul_debugobj(tb, "fatal error"));
+ goto err;
+ }
+ } while (1);
+
+ DBG(TAB, ul_debugobj(tb, "%s: stop parsing (%d entries)",
+ filename, mnt_table_get_nents(tb)));
+ parser_cleanup(&pa);
+ return 0;
+err:
+ DBG(TAB, ul_debugobj(tb, "%s: parse error (rc=%d)", filename, rc));
+ parser_cleanup(&pa);
+ return rc;
+}
+
+/**
+ * mnt_table_parse_file:
+ * @tb: tab pointer
+ * @filename: file
+ *
+ * Parses the whole table (e.g. /etc/fstab) and appends new records to the @tab.
+ *
+ * The libmount parser ignores broken (syntax error) lines, these lines are
+ * reported to the caller by the errcb() function (see mnt_table_set_parser_errcb()).
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_table_parse_file(struct libmnt_table *tb, const char *filename)
+{
+ FILE *f;
+ int rc;
+
+ if (!filename || !tb)
+ return -EINVAL;
+
+ f = fopen(filename, "r" UL_CLOEXECSTR);
+ if (f) {
+ rc = mnt_table_parse_stream(tb, f, filename);
+ fclose(f);
+ } else
+ rc = -errno;
+
+ DBG(TAB, ul_debugobj(tb, "parsing done [filename=%s, rc=%d]", filename, rc));
+ return rc;
+}
+
+static int mnt_table_parse_dir_filter(const struct dirent *d)
+{
+ size_t namesz;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG &&
+ d->d_type != DT_LNK)
+ return 0;
+#endif
+ if (*d->d_name == '.')
+ return 0;
+
+#define MNT_MNTTABDIR_EXTSIZ (sizeof(MNT_MNTTABDIR_EXT) - 1)
+
+ namesz = strlen(d->d_name);
+ if (!namesz || namesz < MNT_MNTTABDIR_EXTSIZ + 1 ||
+ strcmp(d->d_name + (namesz - MNT_MNTTABDIR_EXTSIZ),
+ MNT_MNTTABDIR_EXT) != 0)
+ return 0;
+
+ /* Accept this */
+ return 1;
+}
+
+#ifdef HAVE_SCANDIRAT
+static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname)
+{
+ int n = 0, i;
+ int dd;
+ struct dirent **namelist = NULL;
+
+ dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (dd < 0)
+ return -errno;
+
+ n = scandirat(dd, ".", &namelist, mnt_table_parse_dir_filter, versionsort);
+ if (n <= 0) {
+ close(dd);
+ return 0;
+ }
+
+ for (i = 0; i < n; i++) {
+ struct dirent *d = namelist[i];
+ struct stat st;
+ FILE *f;
+
+ if (fstatat(dd, d->d_name, &st, 0) ||
+ !S_ISREG(st.st_mode))
+ continue;
+
+ f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
+ if (f) {
+ mnt_table_parse_stream(tb, f, d->d_name);
+ fclose(f);
+ }
+ }
+
+ for (i = 0; i < n; i++)
+ free(namelist[i]);
+ free(namelist);
+ close(dd);
+ return 0;
+}
+#else
+static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname)
+{
+ int n = 0, i, r = 0;
+ DIR *dir = NULL;
+ struct dirent **namelist = NULL;
+
+ n = scandir(dirname, &namelist, mnt_table_parse_dir_filter, versionsort);
+ if (n <= 0)
+ return 0;
+
+ /* let's use "at" functions rather than playing crazy games with paths... */
+ dir = opendir(dirname);
+ if (!dir) {
+ r = -errno;
+ goto out;
+ }
+
+ for (i = 0; i < n; i++) {
+ struct dirent *d = namelist[i];
+ struct stat st;
+ FILE *f;
+
+ if (fstatat(dirfd(dir), d->d_name, &st, 0) ||
+ !S_ISREG(st.st_mode))
+ continue;
+
+ f = fopen_at(dirfd(dir), d->d_name,
+ O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
+ if (f) {
+ mnt_table_parse_stream(tb, f, d->d_name);
+ fclose(f);
+ }
+ }
+
+out:
+ for (i = 0; i < n; i++)
+ free(namelist[i]);
+ free(namelist);
+ if (dir)
+ closedir(dir);
+ return r;
+}
+#endif
+
+/**
+ * mnt_table_parse_dir:
+ * @tb: mount table
+ * @dirname: directory
+ *
+ * The directory:
+ * - files are sorted by strverscmp(3)
+ * - files that start with "." are ignored (e.g. ".10foo.fstab")
+ * - files without the ".fstab" extension are ignored
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname)
+{
+ return __mnt_table_parse_dir(tb, dirname);
+}
+
+struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent)
+{
+ struct libmnt_table *tb;
+ struct stat st;
+
+ if (!filename)
+ return NULL;
+ if (stat(filename, &st))
+ return empty_for_enoent ? mnt_new_table() : NULL;
+
+ tb = mnt_new_table();
+ if (tb) {
+ DBG(TAB, ul_debugobj(tb, "new tab for file: %s", filename));
+ tb->fmt = fmt;
+ if (mnt_table_parse_file(tb, filename) != 0) {
+ mnt_unref_table(tb);
+ tb = NULL;
+ }
+ }
+ return tb;
+}
+
+/**
+ * mnt_new_table_from_file:
+ * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path
+ *
+ * Same as mnt_new_table() + mnt_table_parse_file(). Use this function for private
+ * files only. This function does not allow using the error callback, so you
+ * cannot provide any feedback to end-users about broken records in files (e.g.
+ * fstab).
+ *
+ * Returns: newly allocated tab on success and NULL in case of error.
+ */
+struct libmnt_table *mnt_new_table_from_file(const char *filename)
+{
+ if (!filename)
+ return NULL;
+
+ return __mnt_new_table_from_file(filename, MNT_FMT_GUESS, 0);
+}
+
+/**
+ * mnt_new_table_from_dir
+ * @dirname: directory with *.fstab files
+ *
+ * Returns: newly allocated tab on success and NULL in case of error.
+ */
+struct libmnt_table *mnt_new_table_from_dir(const char *dirname)
+{
+ struct libmnt_table *tb;
+
+ if (!dirname)
+ return NULL;
+ tb = mnt_new_table();
+ if (tb && mnt_table_parse_dir(tb, dirname) != 0) {
+ mnt_unref_table(tb);
+ tb = NULL;
+ }
+ return tb;
+}
+
+/**
+ * mnt_table_set_parser_errcb:
+ * @tb: pointer to table
+ * @cb: pointer to callback function
+ *
+ * The error callback function is called by table parser (mnt_table_parse_file())
+ * in case of a syntax error. The callback function could be used for error
+ * evaluation, libmount will continue/stop parsing according to callback return
+ * codes:
+ *
+ * <0 : fatal error (abort parsing)
+ * 0 : success (parsing continues)
+ * >0 : recoverable error (the line is ignored, parsing continues).
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_set_parser_errcb(struct libmnt_table *tb,
+ int (*cb)(struct libmnt_table *tb, const char *filename, int line))
+{
+ if (!tb)
+ return -EINVAL;
+ tb->errcb = cb;
+ return 0;
+}
+
+/*
+ * Filter out entries during tab file parsing. If @cb returns 1, then the entry
+ * is ignored.
+ */
+int mnt_table_set_parser_fltrcb(struct libmnt_table *tb,
+ int (*cb)(struct libmnt_fs *, void *),
+ void *data)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "%s table parser filter", cb ? "set" : "unset"));
+ tb->fltrcb = cb;
+ tb->fltrcb_data = data;
+ return 0;
+}
+
+/**
+ * mnt_table_parse_swaps:
+ * @tb: table
+ * @filename: overwrites default (/proc/swaps or $LIBMOUNT_SWAPS) or NULL
+ *
+ * This function parses /proc/swaps and appends new lines to the @tab.
+ *
+ * See also mnt_table_set_parser_errcb().
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_parse_swaps(struct libmnt_table *tb, const char *filename)
+{
+ if (!tb)
+ return -EINVAL;
+ if (!filename) {
+ filename = mnt_get_swaps_path();
+ if (!filename)
+ return -EINVAL;
+ }
+
+ tb->fmt = MNT_FMT_SWAPS;
+
+ return mnt_table_parse_file(tb, filename);
+}
+
+/**
+ * mnt_table_parse_fstab:
+ * @tb: table
+ * @filename: overwrites default (/etc/fstab or $LIBMOUNT_FSTAB) or NULL
+ *
+ * This function parses /etc/fstab and appends new lines to the @tab. If the
+ * @filename is a directory, then mnt_table_parse_dir() is called.
+ *
+ * See also mnt_table_set_parser_errcb().
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_parse_fstab(struct libmnt_table *tb, const char *filename)
+{
+ struct stat st;
+ int rc = 0;
+
+ if (!tb)
+ return -EINVAL;
+ if (!filename)
+ filename = mnt_get_fstab_path();
+ if (!filename)
+ return -EINVAL;
+ if (stat(filename, &st) != 0)
+ return -errno;
+
+ tb->fmt = MNT_FMT_FSTAB;
+
+ if (S_ISREG(st.st_mode))
+ rc = mnt_table_parse_file(tb, filename);
+ else if (S_ISDIR(st.st_mode))
+ rc = mnt_table_parse_dir(tb, filename);
+ else
+ rc = -EINVAL;
+
+ return rc;
+}
+
+/*
+ * This function uses @uf to find a corresponding record in @tb, then the record
+ * from @tb is updated (user specific mount options are added).
+ *
+ * Note that @uf must contain only user specific mount options instead of
+ * VFS options (note that FS options are ignored).
+ *
+ * Returns modified filesystem (from @tb) or NULL.
+ */
+static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct libmnt_fs *uf)
+{
+ struct libmnt_fs *fs;
+ struct libmnt_iter itr;
+ const char *optstr, *src, *target, *root, *attrs;
+
+ if (!tb || !uf)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "merging user fs"));
+
+ src = mnt_fs_get_srcpath(uf);
+ target = mnt_fs_get_target(uf);
+ optstr = mnt_fs_get_user_options(uf);
+ attrs = mnt_fs_get_attributes(uf);
+ root = mnt_fs_get_root(uf);
+
+ if (!src || !target || !root || (!attrs && !optstr))
+ return NULL;
+
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ const char *r = mnt_fs_get_root(fs);
+
+ if (fs->flags & MNT_FS_MERGED)
+ continue;
+
+ if (r && strcmp(r, root) == 0
+ && mnt_fs_streq_target(fs, target)
+ && mnt_fs_streq_srcpath(fs, src))
+ break;
+ }
+
+ if (fs) {
+ DBG(TAB, ul_debugobj(tb, "found fs -- appending user optstr"));
+ mnt_fs_append_options(fs, optstr);
+ mnt_fs_append_attributes(fs, attrs);
+ mnt_fs_set_bindsrc(fs, mnt_fs_get_bindsrc(uf));
+ fs->flags |= MNT_FS_MERGED;
+
+ DBG(TAB, ul_debugobj(tb, "found fs:"));
+ DBG(TAB, mnt_fs_print_debug(fs, stderr));
+ }
+ return fs;
+}
+
+/* default filename is /proc/self/mountinfo
+ */
+int __mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename,
+ struct libmnt_table *u_tb)
+{
+ int rc = 0, priv_utab = 0;
+ int explicit_file = filename ? 1 : 0;
+
+ assert(tb);
+
+ if (filename)
+ DBG(TAB, ul_debugobj(tb, "%s requested as mtab", filename));
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (mnt_has_regular_mtab(&filename, NULL)) {
+
+ DBG(TAB, ul_debugobj(tb, "force mtab usage [filename=%s]", filename));
+
+ rc = mnt_table_parse_file(tb, filename);
+
+ /*
+ * If @filename forces us to read from /proc then also read
+ * utab file to merge userspace mount options.
+ */
+ if (rc == 0 && is_mountinfo(tb))
+ goto read_utab;
+
+ if (!rc)
+ return 0;
+ filename = NULL; /* failed */
+ } else
+ filename = NULL; /* mtab useless */
+#endif
+
+ if (!filename || strcmp(filename, _PATH_PROC_MOUNTINFO) == 0) {
+ filename = _PATH_PROC_MOUNTINFO;
+ tb->fmt = MNT_FMT_MOUNTINFO;
+ DBG(TAB, ul_debugobj(tb, "mtab parse: #1 read mountinfo"));
+ } else
+ tb->fmt = MNT_FMT_GUESS;
+
+ rc = mnt_table_parse_file(tb, filename);
+ if (rc) {
+ if (explicit_file)
+ return rc;
+
+ /* hmm, old kernel? ...try /proc/mounts */
+ tb->fmt = MNT_FMT_MTAB;
+ return mnt_table_parse_file(tb, _PATH_PROC_MOUNTS);
+ }
+
+ if (!is_mountinfo(tb))
+ return 0;
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+read_utab:
+#endif
+ DBG(TAB, ul_debugobj(tb, "mtab parse: #2 read utab"));
+
+ if (mnt_table_get_nents(tb) == 0)
+ return 0; /* empty, ignore utab */
+ /*
+ * try to read the user specific information from /run/mount/utabs
+ */
+ if (!u_tb) {
+ const char *utab = mnt_get_utab_path();
+
+ if (!utab || is_file_empty(utab))
+ return 0;
+
+ u_tb = mnt_new_table();
+ if (!u_tb)
+ return -ENOMEM;
+
+ u_tb->fmt = MNT_FMT_UTAB;
+ mnt_table_set_parser_fltrcb(u_tb, tb->fltrcb, tb->fltrcb_data);
+
+ rc = mnt_table_parse_file(u_tb, utab);
+ priv_utab = 1;
+ }
+
+ DBG(TAB, ul_debugobj(tb, "mtab parse: #3 merge utab"));
+
+ if (rc == 0) {
+ struct libmnt_fs *u_fs;
+ struct libmnt_iter itr;
+
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+
+ /* merge user options into mountinfo from the kernel */
+ while(mnt_table_next_fs(u_tb, &itr, &u_fs) == 0)
+ mnt_table_merge_user_fs(tb, u_fs);
+ }
+
+
+ if (priv_utab)
+ mnt_unref_table(u_tb);
+ return 0;
+}
+/**
+ * mnt_table_parse_mtab:
+ * @tb: table
+ * @filename: overwrites default or NULL
+ *
+ * The default filename is /proc/self/mountinfo. If the mount table is a
+ * mountinfo file then /run/mount/utabs is parsed too and both files are merged
+ * to the one libmnt_table.
+ *
+ * If libmount is compiled with classic mtab file support, and the /etc/mtab is
+ * a regular file then this file is parsed.
+ *
+ * It's strongly recommended to use NULL as a @filename to keep code portable.
+ *
+ * See also mnt_table_set_parser_errcb().
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename)
+{
+ return __mnt_table_parse_mtab(tb, filename, NULL);
+}
diff --git a/libmount/src/tab_update.c b/libmount/src/tab_update.c
new file mode 100644
index 0000000..3a22e71
--- /dev/null
+++ b/libmount/src/tab_update.c
@@ -0,0 +1,1100 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: update
+ * @title: Tables update
+ * @short_description: userspace mount information management
+ *
+ * The struct libmnt_update provides an abstraction to manage mount options in
+ * userspace independently of system configuration. This low-level API works on
+ * systems both with and without /etc/mtab. On systems without the regular /etc/mtab
+ * file, the userspace mount options (e.g. user=) are stored in the /run/mount/utab
+ * file.
+ *
+ * It's recommended to use high-level struct libmnt_context API.
+ */
+#include <sys/file.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "mountP.h"
+#include "mangle.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+struct libmnt_update {
+ char *target;
+ struct libmnt_fs *fs;
+ char *filename;
+ unsigned long mountflags;
+ int userspace_only;
+ int ready;
+
+ struct libmnt_table *mountinfo;
+};
+
+static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags);
+static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags);
+
+/**
+ * mnt_new_update:
+ *
+ * Returns: newly allocated update handler
+ */
+struct libmnt_update *mnt_new_update(void)
+{
+ struct libmnt_update *upd;
+
+ upd = calloc(1, sizeof(*upd));
+ if (!upd)
+ return NULL;
+
+ DBG(UPDATE, ul_debugobj(upd, "allocate"));
+ return upd;
+}
+
+/**
+ * mnt_free_update:
+ * @upd: update
+ *
+ * Deallocates struct libmnt_update handler.
+ */
+void mnt_free_update(struct libmnt_update *upd)
+{
+ if (!upd)
+ return;
+
+ DBG(UPDATE, ul_debugobj(upd, "free"));
+
+ mnt_unref_fs(upd->fs);
+ mnt_unref_table(upd->mountinfo);
+ free(upd->target);
+ free(upd->filename);
+ free(upd);
+}
+
+/*
+ * Returns 0 on success, <0 in case of error.
+ */
+int mnt_update_set_filename(struct libmnt_update *upd, const char *filename,
+ int userspace_only)
+{
+ const char *path = NULL;
+ int rw = 0;
+
+ if (!upd)
+ return -EINVAL;
+
+ /* filename explicitly defined */
+ if (filename) {
+ char *p = strdup(filename);
+ if (!p)
+ return -ENOMEM;
+
+ upd->userspace_only = userspace_only;
+ free(upd->filename);
+ upd->filename = p;
+ }
+
+ if (upd->filename)
+ return 0;
+
+ /* detect tab filename -- /etc/mtab or /run/mount/utab
+ */
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ mnt_has_regular_mtab(&path, &rw);
+#endif
+ if (!rw) {
+ path = NULL;
+ mnt_has_regular_utab(&path, &rw);
+ if (!rw)
+ return -EACCES;
+ upd->userspace_only = TRUE;
+ }
+ upd->filename = strdup(path);
+ if (!upd->filename)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * mnt_update_get_filename:
+ * @upd: update
+ *
+ * This function returns the file name (e.g. /etc/mtab) of the up-dated file.
+ *
+ * Returns: pointer to filename that will be updated or NULL in case of error.
+ */
+const char *mnt_update_get_filename(struct libmnt_update *upd)
+{
+ return upd ? upd->filename : NULL;
+}
+
+/**
+ * mnt_update_is_ready:
+ * @upd: update handler
+ *
+ * Returns: 1 if entry described by @upd is successfully prepared and will be
+ * written to the mtab/utab file.
+ */
+int mnt_update_is_ready(struct libmnt_update *upd)
+{
+ return upd ? upd->ready : FALSE;
+}
+
+/**
+ * mnt_update_set_fs:
+ * @upd: update handler
+ * @mountflags: MS_* flags
+ * @target: umount target, must be NULL for mount
+ * @fs: mount filesystem description, must be NULL for umount
+ *
+ * Returns: <0 in case on error, 0 on success, 1 if update is unnecessary.
+ */
+int mnt_update_set_fs(struct libmnt_update *upd, unsigned long mountflags,
+ const char *target, struct libmnt_fs *fs)
+{
+ int rc;
+
+ if (!upd)
+ return -EINVAL;
+ if ((mountflags & MS_MOVE) && (!fs || !mnt_fs_get_srcpath(fs)))
+ return -EINVAL;
+ if (target && fs)
+ return -EINVAL;
+
+ DBG(UPDATE, ul_debugobj(upd,
+ "resetting FS [target=%s, flags=0x%08lx]",
+ target, mountflags));
+ if (fs) {
+ DBG(UPDATE, ul_debugobj(upd, "FS template:"));
+ DBG(UPDATE, mnt_fs_print_debug(fs, stderr));
+ }
+
+ mnt_unref_fs(upd->fs);
+ free(upd->target);
+ upd->ready = FALSE;
+ upd->fs = NULL;
+ upd->target = NULL;
+ upd->mountflags = 0;
+
+ if (mountflags & MS_PROPAGATION)
+ return 1;
+
+ upd->mountflags = mountflags;
+
+ rc = mnt_update_set_filename(upd, NULL, 0);
+ if (rc) {
+ DBG(UPDATE, ul_debugobj(upd, "no writable file available [rc=%d]", rc));
+ return rc; /* error or no file available (rc = 1) */
+ }
+ if (target) {
+ upd->target = strdup(target);
+ if (!upd->target)
+ return -ENOMEM;
+
+ } else if (fs) {
+ if (upd->userspace_only && !(mountflags & MS_MOVE)) {
+ rc = utab_new_entry(upd, fs, mountflags);
+ if (rc)
+ return rc;
+ } else {
+ upd->fs = mnt_copy_mtab_fs(fs);
+ if (!upd->fs)
+ return -ENOMEM;
+
+ }
+ }
+
+
+ DBG(UPDATE, ul_debugobj(upd, "ready"));
+ upd->ready = TRUE;
+ return 0;
+}
+
+/**
+ * mnt_update_get_fs:
+ * @upd: update
+ *
+ * Returns: update filesystem entry or NULL
+ */
+struct libmnt_fs *mnt_update_get_fs(struct libmnt_update *upd)
+{
+ return upd ? upd->fs : NULL;
+}
+
+/**
+ * mnt_update_get_mflags:
+ * @upd: update
+ *
+ * Returns: mount flags as was set by mnt_update_set_fs()
+ */
+unsigned long mnt_update_get_mflags(struct libmnt_update *upd)
+{
+ return upd ? upd->mountflags : 0;
+}
+
+/**
+ * mnt_update_force_rdonly:
+ * @upd: update
+ * @rdonly: is read-only?
+ *
+ * Returns: 0 on success and negative number in case of error.
+ */
+int mnt_update_force_rdonly(struct libmnt_update *upd, int rdonly)
+{
+ int rc = 0;
+
+ if (!upd || !upd->fs)
+ return -EINVAL;
+
+ if (rdonly && (upd->mountflags & MS_RDONLY))
+ return 0;
+ if (!rdonly && !(upd->mountflags & MS_RDONLY))
+ return 0;
+
+ if (!upd->userspace_only) {
+ /* /etc/mtab -- we care about VFS options there */
+ const char *o = mnt_fs_get_options(upd->fs);
+ char *n = o ? strdup(o) : NULL;
+
+ if (n)
+ mnt_optstr_remove_option(&n, rdonly ? "rw" : "ro");
+ if (!mnt_optstr_prepend_option(&n, rdonly ? "ro" : "rw", NULL))
+ rc = mnt_fs_set_options(upd->fs, n);
+
+ free(n);
+ }
+
+ if (rdonly)
+ upd->mountflags &= ~MS_RDONLY;
+ else
+ upd->mountflags |= MS_RDONLY;
+
+ return rc;
+}
+
+
+/*
+ * Allocates an utab entry (upd->fs) for mount/remount. This function should be
+ * called *before* mount(2) syscall. The @fs is used as a read-only template.
+ *
+ * Returns: 0 on success, negative number on error, 1 if utab's update is
+ * unnecessary.
+ */
+static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs,
+ unsigned long mountflags)
+{
+ int rc = 0;
+ const char *o, *a;
+ char *u = NULL;
+
+ assert(fs);
+ assert(upd);
+ assert(upd->fs == NULL);
+ assert(!(mountflags & MS_MOVE));
+
+ DBG(UPDATE, ul_debug("prepare utab entry"));
+
+ o = mnt_fs_get_user_options(fs);
+ a = mnt_fs_get_attributes(fs);
+ upd->fs = NULL;
+
+ if (o) {
+ /* remove non-mtab options */
+ rc = mnt_optstr_get_options(o, &u,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP),
+ MNT_NOMTAB);
+ if (rc)
+ goto err;
+ }
+
+ if (!u && !a) {
+ DBG(UPDATE, ul_debug("utab entry unnecessary (no options)"));
+ return 1;
+ }
+
+ /* allocate the entry */
+ upd->fs = mnt_copy_fs(NULL, fs);
+ if (!upd->fs) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ rc = mnt_fs_set_options(upd->fs, u);
+ if (rc)
+ goto err;
+ rc = mnt_fs_set_attributes(upd->fs, a);
+ if (rc)
+ goto err;
+
+ if (!(mountflags & MS_REMOUNT)) {
+ rc = set_fs_root(upd, fs, mountflags);
+ if (rc)
+ goto err;
+ }
+
+ free(u);
+ DBG(UPDATE, ul_debug("utab entry OK"));
+ return 0;
+err:
+ free(u);
+ mnt_unref_fs(upd->fs);
+ upd->fs = NULL;
+ return rc;
+}
+
+/*
+ * Sets fs-root and fs-type to @upd->fs according to the @fs template and
+ * @mountfalgs. For MS_BIND mountflag it reads information about the source
+ * filesystem from /proc/self/mountinfo.
+ */
+static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs,
+ unsigned long mountflags)
+{
+ struct libmnt_fs *src_fs;
+ char *fsroot = NULL;
+ const char *src, *fstype;
+ int rc = 0;
+
+ DBG(UPDATE, ul_debug("setting FS root"));
+
+ assert(upd);
+ assert(upd->fs);
+ assert(fs);
+
+ fstype = mnt_fs_get_fstype(fs);
+
+ if (mountflags & MS_BIND) {
+ if (!upd->mountinfo)
+ upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+ src = mnt_fs_get_srcpath(fs);
+ if (src) {
+ rc = mnt_fs_set_bindsrc(upd->fs, src);
+ if (rc)
+ goto err;
+ }
+
+ } else if (fstype && (strcmp(fstype, "btrfs") == 0 || strcmp(fstype, "auto") == 0)) {
+ if (!upd->mountinfo)
+ upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+ }
+
+ src_fs = mnt_table_get_fs_root(upd->mountinfo, fs,
+ mountflags, &fsroot);
+ if (src_fs) {
+ src = mnt_fs_get_srcpath(src_fs);
+ rc = mnt_fs_set_source(upd->fs, src);
+ if (rc)
+ goto err;
+
+ mnt_fs_set_fstype(upd->fs, mnt_fs_get_fstype(src_fs));
+ }
+
+ upd->fs->root = fsroot;
+ return 0;
+err:
+ free(fsroot);
+ return rc;
+}
+
+/* mtab and fstab update -- returns zero on success
+ */
+static int fprintf_mtab_fs(FILE *f, struct libmnt_fs *fs)
+{
+ const char *o, *src, *fstype, *comm;
+ char *m1, *m2, *m3, *m4;
+ int rc;
+
+ assert(fs);
+ assert(f);
+
+ comm = mnt_fs_get_comment(fs);
+ src = mnt_fs_get_source(fs);
+ fstype = mnt_fs_get_fstype(fs);
+ o = mnt_fs_get_options(fs);
+
+ m1 = src ? mangle(src) : "none";
+ m2 = mangle(mnt_fs_get_target(fs));
+ m3 = fstype ? mangle(fstype) : "none";
+ m4 = o ? mangle(o) : "rw";
+
+ if (m1 && m2 && m3 && m4) {
+ if (comm)
+ fputs(comm, f);
+ rc = fprintf(f, "%s %s %s %s %d %d\n",
+ m1, m2, m3, m4,
+ mnt_fs_get_freq(fs),
+ mnt_fs_get_passno(fs));
+ if (rc > 0)
+ rc = 0;
+ } else
+ rc = -ENOMEM;
+
+ if (src)
+ free(m1);
+ free(m2);
+ if (fstype)
+ free(m3);
+ if (o)
+ free(m4);
+
+ return rc;
+}
+
+static int fprintf_utab_fs(FILE *f, struct libmnt_fs *fs)
+{
+ char *p;
+ int rc = 0;
+
+ if (!fs || !f)
+ return -EINVAL;
+
+ p = mangle(mnt_fs_get_source(fs));
+ if (p) {
+ rc = fprintf(f, "SRC=%s ", p);
+ free(p);
+ }
+ if (rc >= 0) {
+ p = mangle(mnt_fs_get_target(fs));
+ if (p) {
+ rc = fprintf(f, "TARGET=%s ", p);
+ free(p);
+ }
+ }
+ if (rc >= 0) {
+ p = mangle(mnt_fs_get_root(fs));
+ if (p) {
+ rc = fprintf(f, "ROOT=%s ", p);
+ free(p);
+ }
+ }
+ if (rc >= 0) {
+ p = mangle(mnt_fs_get_bindsrc(fs));
+ if (p) {
+ rc = fprintf(f, "BINDSRC=%s ", p);
+ free(p);
+ }
+ }
+ if (rc >= 0) {
+ p = mangle(mnt_fs_get_attributes(fs));
+ if (p) {
+ rc = fprintf(f, "ATTRS=%s ", p);
+ free(p);
+ }
+ }
+ if (rc >= 0) {
+ p = mangle(mnt_fs_get_user_options(fs));
+ if (p) {
+ rc = fprintf(f, "OPTS=%s", p);
+ free(p);
+ }
+ }
+ if (rc >= 0)
+ rc = fprintf(f, "\n");
+
+ if (rc > 0)
+ rc = 0; /* success */
+ return rc;
+}
+
+static int update_table(struct libmnt_update *upd, struct libmnt_table *tb)
+{
+ FILE *f;
+ int rc, fd;
+ char *uq = NULL;
+
+ if (!tb || !upd->filename)
+ return -EINVAL;
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: updating", upd->filename));
+
+ fd = mnt_open_uniq_filename(upd->filename, &uq);
+ if (fd < 0)
+ return fd; /* error */
+
+ f = fdopen(fd, "w" UL_CLOEXECSTR);
+ if (f) {
+ struct stat st;
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+
+ if (tb->comms && mnt_table_get_intro_comment(tb))
+ fputs(mnt_table_get_intro_comment(tb), f);
+
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ if (upd->userspace_only)
+ rc = fprintf_utab_fs(f, fs);
+ else
+ rc = fprintf_mtab_fs(f, fs);
+ if (rc) {
+ DBG(UPDATE, ul_debugobj(upd,
+ "%s: write entry failed: %m", uq));
+ goto leave;
+ }
+ }
+ if (tb->comms && mnt_table_get_trailing_comment(tb))
+ fputs(mnt_table_get_trailing_comment(tb), f);
+
+ if (fflush(f) != 0) {
+ rc = -errno;
+ DBG(UPDATE, ul_debugobj(upd, "%s: fflush failed: %m", uq));
+ goto leave;
+ }
+
+ rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0;
+
+ if (!rc && stat(upd->filename, &st) == 0)
+ /* Copy uid/gid from the present file before renaming. */
+ rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0;
+
+ fclose(f);
+ f = NULL;
+
+ if (!rc)
+ rc = rename(uq, upd->filename) ? -errno : 0;
+ } else {
+ rc = -errno;
+ close(fd);
+ }
+
+leave:
+ if (f)
+ fclose(f);
+
+ unlink(uq); /* be paranoid */
+ free(uq);
+ DBG(UPDATE, ul_debugobj(upd, "%s: done [rc=%d]", upd->filename, rc));
+ return rc;
+}
+
+/**
+ * mnt_table_write_file
+ * @tb: parsed file (e.g. fstab)
+ * @file: target
+ *
+ * This function writes @tb to @file.
+ *
+ * Returns: 0 on success, negative number on error.
+ */
+int mnt_table_write_file(struct libmnt_table *tb, FILE *file)
+{
+ int rc = 0;
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+
+ if (tb->comms && mnt_table_get_intro_comment(tb))
+ fputs(mnt_table_get_intro_comment(tb), file);
+
+ mnt_reset_iter(&itr, MNT_ITER_FORWARD);
+ while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ rc = fprintf_mtab_fs(file, fs);
+ if (rc)
+ return rc;
+ }
+ if (tb->comms && mnt_table_get_trailing_comment(tb))
+ fputs(mnt_table_get_trailing_comment(tb), file);
+
+ if (fflush(file) != 0)
+ rc = -errno;
+
+ DBG(TAB, ul_debugobj(tb, "write file done [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * mnt_table_replace_file
+ * @tb: parsed file (e.g. fstab)
+ * @filename: target
+ *
+ * This function replaces @file by the new content from @tb.
+ *
+ * Returns: 0 on success, negative number on error.
+ */
+int mnt_table_replace_file(struct libmnt_table *tb, const char *filename)
+{
+ int fd, rc = 0;
+ FILE *f;
+ char *uq = NULL;
+
+ DBG(TAB, ul_debugobj(tb, "%s: replacing", filename));
+
+ fd = mnt_open_uniq_filename(filename, &uq);
+ if (fd < 0)
+ return fd; /* error */
+
+ f = fdopen(fd, "w" UL_CLOEXECSTR);
+ if (f) {
+ struct stat st;
+
+ mnt_table_write_file(tb, f);
+
+ if (fflush(f) != 0) {
+ rc = -errno;
+ DBG(UPDATE, ul_debug("%s: fflush failed: %m", uq));
+ goto leave;
+ }
+
+ rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0;
+
+ if (!rc && stat(filename, &st) == 0)
+ /* Copy uid/gid from the present file before renaming. */
+ rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0;
+
+ fclose(f);
+ f = NULL;
+
+ if (!rc)
+ rc = rename(uq, filename) ? -errno : 0;
+ } else {
+ rc = -errno;
+ close(fd);
+ }
+
+leave:
+ if (f)
+ fclose(f);
+ unlink(uq);
+ free(uq);
+
+ DBG(TAB, ul_debugobj(tb, "replace done [rc=%d]", rc));
+ return rc;
+}
+
+static int add_file_entry(struct libmnt_table *tb, struct libmnt_update *upd)
+{
+ struct libmnt_fs *fs;
+
+ assert(upd);
+
+ fs = mnt_copy_fs(NULL, upd->fs);
+ if (!fs)
+ return -ENOMEM;
+
+ mnt_table_add_fs(tb, fs);
+ mnt_unref_fs(fs);
+
+ return update_table(upd, tb);
+}
+
+static int update_add_entry(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_table *tb;
+ int rc = 0;
+
+ assert(upd);
+ assert(upd->fs);
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: add entry", upd->filename));
+
+ if (lc)
+ rc = mnt_lock_file(lc);
+ if (rc)
+ return -MNT_ERR_LOCK;
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb)
+ rc = add_file_entry(tb, upd);
+ if (lc)
+ mnt_unlock_file(lc);
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int update_remove_entry(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_table *tb;
+ int rc = 0;
+
+ assert(upd);
+ assert(upd->target);
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: remove entry", upd->filename));
+
+ if (lc)
+ rc = mnt_lock_file(lc);
+ if (rc)
+ return -MNT_ERR_LOCK;
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb) {
+ struct libmnt_fs *rem = mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD);
+ if (rem) {
+ mnt_table_remove_fs(tb, rem);
+ rc = update_table(upd, tb);
+ }
+ }
+ if (lc)
+ mnt_unlock_file(lc);
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int update_modify_target(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_table *tb = NULL;
+ int rc = 0;
+
+ assert(upd);
+ DBG(UPDATE, ul_debugobj(upd, "%s: modify target", upd->filename));
+
+ if (lc)
+ rc = mnt_lock_file(lc);
+ if (rc)
+ return -MNT_ERR_LOCK;
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb) {
+ const char *upd_source = mnt_fs_get_srcpath(upd->fs);
+ const char *upd_target = mnt_fs_get_target(upd->fs);
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+ char *cn_target = mnt_resolve_path(upd_target, NULL);
+
+ if (!cn_target) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+ while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
+ char *p;
+ const char *e;
+
+ e = startswith(mnt_fs_get_target(fs), upd_source);
+ if (!e || (*e && *e != '/'))
+ continue;
+ if (*e == '/')
+ e++; /* remove extra '/' */
+
+ /* no subdirectory, replace entire path */
+ if (!*e)
+ rc = mnt_fs_set_target(fs, cn_target);
+
+ /* update start of the path, keep subdirectory */
+ else if (asprintf(&p, "%s/%s", cn_target, e) > 0) {
+ rc = mnt_fs_set_target(fs, p);
+ free(p);
+ } else
+ rc = -ENOMEM;
+
+ if (rc < 0)
+ break;
+ }
+
+ if (!rc)
+ rc = update_table(upd, tb);
+ free(cn_target);
+ }
+
+done:
+ if (lc)
+ mnt_unlock_file(lc);
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+static int update_modify_options(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_table *tb = NULL;
+ int rc = 0;
+ struct libmnt_fs *fs;
+
+ assert(upd);
+ assert(upd->fs);
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: modify options", upd->filename));
+
+ fs = upd->fs;
+
+ if (lc)
+ rc = mnt_lock_file(lc);
+ if (rc)
+ return -MNT_ERR_LOCK;
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb) {
+ struct libmnt_fs *cur = mnt_table_find_target(tb,
+ mnt_fs_get_target(fs),
+ MNT_ITER_BACKWARD);
+ if (cur) {
+ if (upd->userspace_only)
+ rc = mnt_fs_set_attributes(cur, mnt_fs_get_attributes(fs));
+ if (!rc)
+ rc = mnt_fs_set_options(cur, mnt_fs_get_options(fs));
+ if (!rc)
+ rc = update_table(upd, tb);
+ } else
+ rc = add_file_entry(tb, upd); /* not found, add new */
+ }
+
+ if (lc)
+ mnt_unlock_file(lc);
+
+ mnt_unref_table(tb);
+ return rc;
+}
+
+/**
+ * mnt_update_table:
+ * @upd: update
+ * @lc: lock or NULL
+ *
+ * High-level API to update /etc/mtab (or private /run/mount/utab file).
+ *
+ * The @lc lock is optional and will be created if necessary. Note that
+ * an automatically created lock blocks all signals.
+ *
+ * See also mnt_lock_block_signals() and mnt_context_get_lock().
+ *
+ * Returns: 0 on success, negative number on error.
+ */
+int mnt_update_table(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_lock *lc0 = lc;
+ int rc = -EINVAL;
+
+ if (!upd || !upd->filename)
+ return -EINVAL;
+ if (!upd->ready)
+ return 0;
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: update tab", upd->filename));
+ if (upd->fs) {
+ DBG(UPDATE, mnt_fs_print_debug(upd->fs, stderr));
+ }
+ if (!lc) {
+ lc = mnt_new_lock(upd->filename, 0);
+ if (lc)
+ mnt_lock_block_signals(lc, TRUE);
+ }
+ if (lc && upd->userspace_only)
+ mnt_lock_use_simplelock(lc, TRUE); /* use flock */
+
+ if (!upd->fs && upd->target)
+ rc = update_remove_entry(upd, lc); /* umount */
+ else if (upd->mountflags & MS_MOVE)
+ rc = update_modify_target(upd, lc); /* move */
+ else if (upd->mountflags & MS_REMOUNT)
+ rc = update_modify_options(upd, lc); /* remount */
+ else if (upd->fs)
+ rc = update_add_entry(upd, lc); /* mount */
+
+ upd->ready = FALSE;
+ DBG(UPDATE, ul_debugobj(upd, "%s: update tab: done [rc=%d]",
+ upd->filename, rc));
+ if (lc != lc0)
+ mnt_free_lock(lc);
+ return rc;
+}
+
+int mnt_update_already_done(struct libmnt_update *upd, struct libmnt_lock *lc)
+{
+ struct libmnt_table *tb = NULL;
+ struct libmnt_lock *lc0 = lc;
+ int rc = 0;
+
+ if (!upd || !upd->filename || (!upd->fs && !upd->target))
+ return -EINVAL;
+
+ DBG(UPDATE, ul_debugobj(upd, "%s: checking for previous update", upd->filename));
+
+ if (!lc) {
+ lc = mnt_new_lock(upd->filename, 0);
+ if (lc)
+ mnt_lock_block_signals(lc, TRUE);
+ }
+ if (lc && upd->userspace_only)
+ mnt_lock_use_simplelock(lc, TRUE); /* use flock */
+ if (lc) {
+ rc = mnt_lock_file(lc);
+ if (rc) {
+ rc = -MNT_ERR_LOCK;
+ goto done;
+ }
+ }
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (lc)
+ mnt_unlock_file(lc);
+ if (!tb)
+ goto done;
+
+ if (upd->fs) {
+ /* mount */
+ const char *tgt = mnt_fs_get_target(upd->fs);
+ const char *src = mnt_fs_get_bindsrc(upd->fs) ?
+ mnt_fs_get_bindsrc(upd->fs) :
+ mnt_fs_get_source(upd->fs);
+
+ if (mnt_table_find_pair(tb, src, tgt, MNT_ITER_BACKWARD)) {
+ DBG(UPDATE, ul_debugobj(upd, "%s: found %s %s",
+ upd->filename, src, tgt));
+ rc = 1;
+ }
+ } else if (upd->target) {
+ /* umount */
+ if (!mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD)) {
+ DBG(UPDATE, ul_debugobj(upd, "%s: not-found (umounted) %s",
+ upd->filename, upd->target));
+ rc = 1;
+ }
+ }
+
+ mnt_unref_table(tb);
+done:
+ if (lc && lc != lc0)
+ mnt_free_lock(lc);
+ DBG(UPDATE, ul_debugobj(upd, "%s: previous update check done [rc=%d]",
+ upd->filename, rc));
+ return rc;
+}
+
+
+#ifdef TEST_PROGRAM
+
+static int update(const char *target, struct libmnt_fs *fs, unsigned long mountflags)
+{
+ int rc;
+ struct libmnt_update *upd;
+
+ DBG(UPDATE, ul_debug("update test"));
+
+ upd = mnt_new_update();
+ if (!upd)
+ return -ENOMEM;
+
+ rc = mnt_update_set_fs(upd, mountflags, target, fs);
+ if (rc == 1) {
+ /* update is unnecessary */
+ rc = 0;
+ goto done;
+ }
+ if (rc) {
+ fprintf(stderr, "failed to set FS\n");
+ goto done;
+ }
+
+ /* [... mount(2) call should be here...] */
+
+ rc = mnt_update_table(upd, NULL);
+done:
+ mnt_free_update(upd);
+ return rc;
+}
+
+static int test_add(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_fs *fs = mnt_new_fs();
+ int rc;
+
+ if (argc < 5 || !fs)
+ return -1;
+ mnt_fs_set_source(fs, argv[1]);
+ mnt_fs_set_target(fs, argv[2]);
+ mnt_fs_set_fstype(fs, argv[3]);
+ mnt_fs_set_options(fs, argv[4]);
+
+ rc = update(NULL, fs, 0);
+ mnt_unref_fs(fs);
+ return rc;
+}
+
+static int test_remove(struct libmnt_test *ts, int argc, char *argv[])
+{
+ if (argc < 2)
+ return -1;
+ return update(argv[1], NULL, 0);
+}
+
+static int test_move(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_fs *fs = mnt_new_fs();
+ int rc;
+
+ if (argc < 3)
+ return -1;
+ mnt_fs_set_source(fs, argv[1]);
+ mnt_fs_set_target(fs, argv[2]);
+
+ rc = update(NULL, fs, MS_MOVE);
+
+ mnt_unref_fs(fs);
+ return rc;
+}
+
+static int test_remount(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_fs *fs = mnt_new_fs();
+ int rc;
+
+ if (argc < 3)
+ return -1;
+ mnt_fs_set_target(fs, argv[1]);
+ mnt_fs_set_options(fs, argv[2]);
+
+ rc = update(NULL, fs, MS_REMOUNT);
+ mnt_unref_fs(fs);
+ return rc;
+}
+
+static int test_replace(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct libmnt_fs *fs = mnt_new_fs();
+ struct libmnt_table *tb = mnt_new_table();
+ int rc;
+
+ if (argc < 3)
+ return -1;
+
+ mnt_table_enable_comments(tb, TRUE);
+ mnt_table_parse_fstab(tb, NULL);
+
+ mnt_fs_set_source(fs, argv[1]);
+ mnt_fs_set_target(fs, argv[2]);
+ mnt_fs_append_comment(fs, "# this is new filesystem\n");
+
+ mnt_table_add_fs(tb, fs);
+ mnt_unref_fs(fs);
+
+ rc = mnt_table_replace_file(tb, mnt_get_fstab_path());
+ mnt_unref_table(tb);
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--add", test_add, "<src> <target> <type> <options> add a line to mtab" },
+ { "--remove", test_remove, "<target> MS_REMOUNT mtab change" },
+ { "--move", test_move, "<old_target> <target> MS_MOVE mtab change" },
+ { "--remount",test_remount, "<target> <options> MS_REMOUNT mtab change" },
+ { "--replace",test_replace, "<src> <target> Add a line to LIBMOUNT_FSTAB and replace the original file" },
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/test.c b/libmount/src/test.c
new file mode 100644
index 0000000..0e3388c
--- /dev/null
+++ b/libmount/src/test.c
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ *
+ *
+ * Routines for TEST_PROGRAMs
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM
+#endif
+
+#include "mountP.h"
+
+int mnt_run_test(struct libmnt_test *tests, int argc, char *argv[])
+{
+ int rc = -1;
+ struct libmnt_test *ts;
+
+ assert(tests);
+ assert(argc);
+ assert(argv);
+
+ if (argc < 2 ||
+ strcmp(argv[1], "--help") == 0 ||
+ strcmp(argv[1], "-h") == 0)
+ goto usage;
+
+ mnt_init_debug(0);
+
+ for (ts = tests; ts->name; ts++) {
+ if (strcmp(ts->name, argv[1]) == 0) {
+ rc = ts->body(ts, argc - 1, argv + 1);
+ if (rc)
+ printf("FAILED [rc=%d]", rc);
+ break;
+ }
+ }
+
+ if (rc < 0 && ts->name == NULL)
+ goto usage;
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+usage:
+ printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n",
+ program_invocation_short_name);
+ for (ts = tests; ts->name; ts++) {
+ printf("\t%-15s", ts->name);
+ if (ts->usage)
+ printf(" %s\n", ts->usage);
+ }
+ printf("\n");
+ return EXIT_FAILURE;
+}
diff --git a/libmount/src/utils.c b/libmount/src/utils.c
new file mode 100644
index 0000000..7481505
--- /dev/null
+++ b/libmount/src/utils.c
@@ -0,0 +1,1389 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: utils
+ * @title: Utils
+ * @short_description: misc utils.
+ */
+#include <ctype.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+#include <blkid.h>
+
+#include "strutils.h"
+#include "pathnames.h"
+#include "mountP.h"
+#include "mangle.h"
+#include "canonicalize.h"
+#include "env.h"
+#include "match.h"
+#include "fileutils.h"
+#include "statfs_magic.h"
+#include "sysfs.h"
+
+/*
+ * Return 1 if the file is not accessible or empty
+ */
+int is_file_empty(const char *name)
+{
+ struct stat st;
+ assert(name);
+
+ return (stat(name, &st) != 0 || st.st_size == 0);
+}
+
+int mnt_valid_tagname(const char *tagname)
+{
+ if (tagname && *tagname && (
+ strcmp("ID", tagname) == 0 ||
+ strcmp("UUID", tagname) == 0 ||
+ strcmp("LABEL", tagname) == 0 ||
+ strcmp("PARTUUID", tagname) == 0 ||
+ strcmp("PARTLABEL", tagname) == 0))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * mnt_tag_is_valid:
+ * @tag: NAME=value string
+ *
+ * Returns: 1 if the @tag is parsable and tag NAME= is supported by libmount, or 0.
+ */
+int mnt_tag_is_valid(const char *tag)
+{
+ char *t = NULL;
+ int rc = tag && blkid_parse_tag_string(tag, &t, NULL) == 0
+ && mnt_valid_tagname(t);
+
+ free(t);
+ return rc;
+}
+
+int mnt_parse_offset(const char *str, size_t len, uintmax_t *res)
+{
+ char *p;
+ int rc = 0;
+
+ if (!str || !*str)
+ return -EINVAL;
+
+ p = strndup(str, len);
+ if (!p)
+ return -errno;
+
+ if (strtosize(p, res))
+ rc = -EINVAL;
+ free(p);
+ return rc;
+}
+
+/* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
+static int fstype_cmp(const void *v1, const void *v2)
+{
+ const char *s1 = *(char * const *)v1;
+ const char *s2 = *(char * const *)v2;
+
+ return strcmp(s1, s2);
+}
+
+int mnt_stat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
+#else
+ return stat(target, st);
+#endif
+}
+
+int mnt_lstat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
+#else
+ return lstat(target, st);
+#endif
+}
+
+
+/*
+ * Note that the @target has to be an absolute path (so at least "/"). The
+ * @filename returns an allocated buffer with the last path component, for example:
+ *
+ * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
+ */
+int mnt_chdir_to_parent(const char *target, char **filename)
+{
+ char *buf, *parent, *last = NULL;
+ char cwd[PATH_MAX];
+ int rc = -EINVAL;
+
+ if (!target || *target != '/')
+ return -EINVAL;
+
+ DBG(UTILS, ul_debug("moving to %s parent", target));
+
+ buf = strdup(target);
+ if (!buf)
+ return -ENOMEM;
+
+ if (*(buf + 1) != '\0') {
+ last = stripoff_last_component(buf);
+ if (!last)
+ goto err;
+ }
+
+ parent = buf && *buf ? buf : "/";
+
+ if (chdir(parent) == -1) {
+ DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent));
+ rc = -errno;
+ goto err;
+ }
+ if (!getcwd(cwd, sizeof(cwd))) {
+ DBG(UTILS, ul_debug("failed to obtain current directory: %m"));
+ rc = -errno;
+ goto err;
+ }
+ if (strcmp(cwd, parent) != 0) {
+ DBG(UTILS, ul_debug(
+ "unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
+ goto err;
+ }
+
+ DBG(CXT, ul_debug(
+ "current directory moved to %s [last_component='%s']",
+ parent, last));
+
+ if (filename) {
+ *filename = buf;
+
+ if (!last || !*last)
+ memcpy(*filename, ".", 2);
+ else
+ memmove(*filename, last, strlen(last) + 1);
+ } else
+ free(buf);
+ return 0;
+err:
+ free(buf);
+ return rc;
+}
+
+/*
+ * Check if @path is on a read-only filesystem independently of file permissions.
+ */
+int mnt_is_readonly(const char *path)
+{
+ if (access(path, W_OK) == 0)
+ return 0;
+ if (errno == EROFS)
+ return 1;
+ if (errno != EACCES)
+ return 0;
+
+#ifdef HAVE_UTIMENSAT
+ /*
+ * access(2) returns EACCES on read-only FS:
+ *
+ * - for set-uid application if one component of the path is not
+ * accessible for the current rUID. (Note that euidaccess(2) does not
+ * check for EROFS at all).
+ *
+ * - for a read-write filesystem with a read-only VFS node (aka -o remount,ro,bind)
+ */
+ {
+ struct timespec times[2];
+
+ DBG(UTILS, ul_debug(" doing utimensat() based write test"));
+
+ times[0].tv_nsec = UTIME_NOW; /* atime */
+ times[1].tv_nsec = UTIME_OMIT; /* mtime */
+
+ if (utimensat(AT_FDCWD, path, times, 0) == -1)
+ return errno == EROFS;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * mnt_mangle:
+ * @str: string
+ *
+ * Encode @str to be compatible with fstab/mtab
+ *
+ * Returns: newly allocated string or NULL in case of error.
+ */
+char *mnt_mangle(const char *str)
+{
+ return mangle(str);
+}
+
+/**
+ * mnt_unmangle:
+ * @str: string
+ *
+ * Decode @str from fstab/mtab
+ *
+ * Returns: newly allocated string or NULL in case of error.
+ */
+char *mnt_unmangle(const char *str)
+{
+ return unmangle(str, NULL);
+}
+
+/**
+ * mnt_fstype_is_pseudofs:
+ * @type: filesystem name
+ *
+ * Returns: 1 for filesystems like proc, sysfs, ... or 0.
+ */
+int mnt_fstype_is_pseudofs(const char *type)
+{
+ /* This array must remain sorted when adding new fstypes */
+ static const char *pseudofs[] = {
+ "anon_inodefs",
+ "apparmorfs",
+ "autofs",
+ "bdev",
+ "binder",
+ "binfmt_misc",
+ "bpf",
+ "cgroup",
+ "cgroup2",
+ "configfs",
+ "cpuset",
+ "debugfs",
+ "devfs",
+ "devpts",
+ "devtmpfs",
+ "dlmfs",
+ "dmabuf",
+ "drm",
+ "efivarfs",
+ "fuse", /* Fallback name of fuse used by many poorly written drivers. */
+ "fuse.archivemount", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.avfsd", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.dumpfs", /* In fact, it is a netfs, but source is not reported. */
+ "fuse.encfs", /* Not a true pseudofs (has source), but source is not reported. */
+ "fuse.gvfs-fuse-daemon", /* Old name, not used by gvfs any more. */
+ "fuse.gvfsd-fuse",
+ "fuse.lxcfs",
+ "fuse.rofiles-fuse",
+ "fuse.vmware-vmblock",
+ "fuse.xwmfs",
+ "fusectl",
+ "hugetlbfs",
+ "ipathfs",
+ "mqueue",
+ "nfsd",
+ "none",
+ "nsfs",
+ "overlay",
+ "pipefs",
+ "proc",
+ "pstore",
+ "ramfs",
+ "resctrl",
+ "rootfs",
+ "rpc_pipefs",
+ "securityfs",
+ "selinuxfs",
+ "smackfs",
+ "sockfs",
+ "spufs",
+ "sysfs",
+ "tmpfs",
+ "tracefs",
+ "vboxsf",
+ "virtiofs"
+ };
+
+ assert(type);
+
+ return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs),
+ sizeof(char*), fstype_cmp) == NULL);
+}
+
+/**
+ * mnt_fstype_is_netfs:
+ * @type: filesystem name
+ *
+ * Returns: 1 for filesystems like cifs, nfs, ... or 0.
+ */
+int mnt_fstype_is_netfs(const char *type)
+{
+ if (strcmp(type, "cifs") == 0 ||
+ strcmp(type, "smb3") == 0 ||
+ strcmp(type, "smbfs") == 0 ||
+ strncmp(type,"nfs", 3) == 0 ||
+ strcmp(type, "afs") == 0 ||
+ strcmp(type, "ncpfs") == 0 ||
+ strcmp(type, "glusterfs") == 0 ||
+ strcmp(type, "fuse.curlftpfs") == 0 ||
+ strcmp(type, "fuse.sshfs") == 0 ||
+ strncmp(type,"9p", 2) == 0)
+ return 1;
+ return 0;
+}
+
+const char *mnt_statfs_get_fstype(struct statfs *vfs)
+{
+ assert(vfs);
+
+ switch (vfs->f_type) {
+ case STATFS_ADFS_MAGIC: return "adfs";
+ case STATFS_AFFS_MAGIC: return "affs";
+ case STATFS_AFS_MAGIC: return "afs";
+ case STATFS_AUTOFS_MAGIC: return "autofs";
+ case STATFS_BDEVFS_MAGIC: return "bdev";
+ case STATFS_BEFS_MAGIC: return "befs";
+ case STATFS_BFS_MAGIC: return "befs";
+ case STATFS_BINFMTFS_MAGIC: return "binfmt_misc";
+ case STATFS_BTRFS_MAGIC: return "btrfs";
+ case STATFS_CEPH_MAGIC: return "ceph";
+ case STATFS_CGROUP_MAGIC: return "cgroup";
+ case STATFS_CIFS_MAGIC: return "cifs";
+ case STATFS_CODA_MAGIC: return "coda";
+ case STATFS_CONFIGFS_MAGIC: return "configfs";
+ case STATFS_CRAMFS_MAGIC: return "cramfs";
+ case STATFS_DEBUGFS_MAGIC: return "debugfs";
+ case STATFS_DEVPTS_MAGIC: return "devpts";
+ case STATFS_ECRYPTFS_MAGIC: return "ecryptfs";
+ case STATFS_EFIVARFS_MAGIC: return "efivarfs";
+ case STATFS_EFS_MAGIC: return "efs";
+ case STATFS_EXOFS_MAGIC: return "exofs";
+ case STATFS_EXT4_MAGIC: return "ext4"; /* all extN use the same magic */
+ case STATFS_F2FS_MAGIC: return "f2fs";
+ case STATFS_FUSE_MAGIC: return "fuse";
+ case STATFS_FUTEXFS_MAGIC: return "futexfs";
+ case STATFS_GFS2_MAGIC: return "gfs2";
+ case STATFS_HFSPLUS_MAGIC: return "hfsplus";
+ case STATFS_HOSTFS_MAGIC: return "hostfs";
+ case STATFS_HPFS_MAGIC: return "hpfs";
+ case STATFS_HPPFS_MAGIC: return "hppfs";
+ case STATFS_HUGETLBFS_MAGIC: return "hugetlbfs";
+ case STATFS_ISOFS_MAGIC: return "iso9660";
+ case STATFS_JFFS2_MAGIC: return "jffs2";
+ case STATFS_JFS_MAGIC: return "jfs";
+ case STATFS_LOGFS_MAGIC: return "logfs";
+ case STATFS_MINIX2_MAGIC:
+ case STATFS_MINIX2_MAGIC2:
+ case STATFS_MINIX3_MAGIC:
+ case STATFS_MINIX_MAGIC:
+ case STATFS_MINIX_MAGIC2: return "minix";
+ case STATFS_MQUEUE_MAGIC: return "mqueue";
+ case STATFS_MSDOS_MAGIC: return "vfat";
+ case STATFS_NCP_MAGIC: return "ncp";
+ case STATFS_NFS_MAGIC: return "nfs";
+ case STATFS_NILFS_MAGIC: return "nilfs2";
+ case STATFS_NTFS_MAGIC: return "ntfs";
+ case STATFS_OCFS2_MAGIC: return "ocfs2";
+ case STATFS_OMFS_MAGIC: return "omfs";
+ case STATFS_OPENPROMFS_MAGIC: return "openpromfs";
+ case STATFS_PIPEFS_MAGIC: return "pipefs";
+ case STATFS_PROC_MAGIC: return "proc";
+ case STATFS_PSTOREFS_MAGIC: return "pstore";
+ case STATFS_QNX4_MAGIC: return "qnx4";
+ case STATFS_QNX6_MAGIC: return "qnx6";
+ case STATFS_RAMFS_MAGIC: return "ramfs";
+ case STATFS_REISERFS_MAGIC: return "reiser4";
+ case STATFS_ROMFS_MAGIC: return "romfs";
+ case STATFS_SECURITYFS_MAGIC: return "securityfs";
+ case STATFS_SELINUXFS_MAGIC: return "selinuxfs";
+ case STATFS_SMACKFS_MAGIC: return "smackfs";
+ case STATFS_SMB_MAGIC: return "smb";
+ case STATFS_SOCKFS_MAGIC: return "sockfs";
+ case STATFS_SQUASHFS_MAGIC: return "squashfs";
+ case STATFS_SYSFS_MAGIC: return "sysfs";
+ case STATFS_TMPFS_MAGIC: return "tmpfs";
+ case STATFS_UBIFS_MAGIC: return "ubifs";
+ case STATFS_UDF_MAGIC: return "udf";
+ case STATFS_UFS2_MAGIC:
+ case STATFS_UFS_MAGIC: return "ufs";
+ case STATFS_V9FS_MAGIC: return "9p";
+ case STATFS_VXFS_MAGIC: return "vxfs";
+ case STATFS_XENFS_MAGIC: return "xenfs";
+ case STATFS_XFS_MAGIC: return "xfs";
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+/**
+ * mnt_match_fstype:
+ * @type: filesystem type
+ * @pattern: filesystem name or comma delimited list of names
+ *
+ * The @pattern list of filesystems can be prefixed with a global
+ * "no" prefix to invert matching of the whole list. The "no" could
+ * also be used for individual items in the @pattern list. So,
+ * "nofoo,bar" has the same meaning as "nofoo,nobar".
+ *
+ * "bar" : "nofoo,bar" -> False (global "no" prefix)
+ *
+ * "bar" : "foo,bar" -> True
+ *
+ * "bar" : "foo,nobar" -> False
+ *
+ * Returns: 1 if type is matching, else 0. This function also returns
+ * 0 if @pattern is NULL and @type is non-NULL.
+ */
+int mnt_match_fstype(const char *type, const char *pattern)
+{
+ return match_fstype(type, pattern);
+}
+
+void mnt_free_filesystems(char **filesystems)
+{
+ char **p;
+
+ if (!filesystems)
+ return;
+ for (p = filesystems; *p; p++)
+ free(*p);
+ free(filesystems);
+}
+
+static int add_filesystem(char ***filesystems, char *name)
+{
+ int n = 0;
+
+ assert(filesystems);
+ assert(name);
+
+ if (*filesystems) {
+ char **p;
+ for (n = 0, p = *filesystems; *p; p++, n++) {
+ if (strcmp(*p, name) == 0)
+ return 0;
+ }
+ }
+
+ #define MYCHUNK 16
+
+ if (n == 0 || !((n + 1) % MYCHUNK)) {
+ size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK;
+ char **x = realloc(*filesystems, items * sizeof(char *));
+
+ if (!x)
+ goto err;
+ *filesystems = x;
+ }
+ name = strdup(name);
+ (*filesystems)[n] = name;
+ (*filesystems)[n + 1] = NULL;
+ if (!name)
+ goto err;
+ return 0;
+err:
+ mnt_free_filesystems(*filesystems);
+ return -ENOMEM;
+}
+
+static int get_filesystems(const char *filename, char ***filesystems, const char *pattern)
+{
+ int rc = 0;
+ FILE *f;
+ char line[129];
+
+ f = fopen(filename, "r" UL_CLOEXECSTR);
+ if (!f)
+ return 1;
+
+ DBG(UTILS, ul_debug("reading filesystems list from: %s", filename));
+
+ while (fgets(line, sizeof(line), f)) {
+ char name[sizeof(line)];
+
+ if (*line == '#' || strncmp(line, "nodev", 5) == 0)
+ continue;
+ if (sscanf(line, " %128[^\n ]\n", name) != 1)
+ continue;
+ if (strcmp(name, "*") == 0) {
+ rc = 1;
+ break; /* end of the /etc/filesystems */
+ }
+ if (pattern && !mnt_match_fstype(name, pattern))
+ continue;
+ rc = add_filesystem(filesystems, name);
+ if (rc)
+ break;
+ }
+
+ fclose(f);
+ return rc;
+}
+
+/*
+ * Always check the @filesystems pointer!
+ *
+ * man mount:
+ *
+ * ...mount will try to read the file /etc/filesystems, or, if that does not
+ * exist, /proc/filesystems. All of the filesystem types listed there will
+ * be tried, except for those that are labeled "nodev" (e.g., devpts,
+ * proc and nfs). If /etc/filesystems ends in a line with a single * only,
+ * mount will read /proc/filesystems afterwards.
+ */
+int mnt_get_filesystems(char ***filesystems, const char *pattern)
+{
+ int rc;
+
+ if (!filesystems)
+ return -EINVAL;
+
+ *filesystems = NULL;
+
+ rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern);
+ if (rc != 1)
+ return rc;
+
+ rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
+ if (rc == 1 && *filesystems)
+ rc = 0; /* /proc/filesystems not found */
+
+ return rc;
+}
+
+/*
+ * Returns an allocated string with username or NULL.
+ */
+char *mnt_get_username(const uid_t uid)
+{
+ struct passwd pwd;
+ struct passwd *res;
+ char *buf, *username = NULL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return NULL;
+
+ if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res)
+ username = strdup(pwd.pw_name);
+
+ free(buf);
+ return username;
+}
+
+int mnt_get_uid(const char *username, uid_t *uid)
+{
+ int rc = -1;
+ struct passwd pwd;
+ struct passwd *pw;
+ char *buf;
+
+ if (!username || !uid)
+ return -EINVAL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return -ENOMEM;
+
+ if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) {
+ *uid= pw->pw_uid;
+ rc = 0;
+ } else {
+ DBG(UTILS, ul_debug(
+ "cannot convert '%s' username to UID", username));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ free(buf);
+ return rc;
+}
+
+int mnt_get_gid(const char *groupname, gid_t *gid)
+{
+ int rc = -1;
+ struct group grp;
+ struct group *gr;
+ char *buf;
+
+ if (!groupname || !gid)
+ return -EINVAL;
+
+ buf = malloc(UL_GETPW_BUFSIZ);
+ if (!buf)
+ return -ENOMEM;
+
+ if (!getgrnam_r(groupname, &grp, buf, UL_GETPW_BUFSIZ, &gr) && gr) {
+ *gid= gr->gr_gid;
+ rc = 0;
+ } else {
+ DBG(UTILS, ul_debug(
+ "cannot convert '%s' groupname to GID", groupname));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ free(buf);
+ return rc;
+}
+
+int mnt_in_group(gid_t gid)
+{
+ int rc = 0, n, i;
+ gid_t *grps = NULL;
+
+ if (getgid() == gid)
+ return 1;
+
+ n = getgroups(0, NULL);
+ if (n <= 0)
+ goto done;
+
+ grps = malloc(n * sizeof(*grps));
+ if (!grps)
+ goto done;
+
+ if (getgroups(n, grps) == n) {
+ for (i = 0; i < n; i++) {
+ if (grps[i] == gid) {
+ rc = 1;
+ break;
+ }
+ }
+ }
+done:
+ free(grps);
+ return rc;
+}
+
+static int try_write(const char *filename, const char *directory)
+{
+ int rc = 0;
+
+ if (!filename)
+ return -EINVAL;
+
+ DBG(UTILS, ul_debug("try write %s dir: %s", filename, directory));
+
+#ifdef HAVE_EACCESS
+ /* Try eaccess() first, because open() is overkill, may be monitored by
+ * audit and we don't want to fill logs by our checks...
+ */
+ if (eaccess(filename, R_OK|W_OK) == 0) {
+ DBG(UTILS, ul_debug(" access OK"));
+ return 0;
+ }
+
+ if (errno != ENOENT) {
+ DBG(UTILS, ul_debug(" access FAILED"));
+ return -errno;
+ }
+
+ if (directory) {
+ /* file does not exist; try if directory is writable */
+ if (eaccess(directory, R_OK|W_OK) != 0)
+ rc = -errno;
+
+ DBG(UTILS, ul_debug(" access %s [%s]", rc ? "FAILED" : "OK", directory));
+ return rc;
+ }
+#endif
+
+ DBG(UTILS, ul_debug(" doing open-write test"));
+
+ int fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
+ S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
+ if (fd < 0)
+ rc = -errno;
+ else
+ close(fd);
+
+ return rc;
+}
+
+/**
+ * mnt_has_regular_mtab:
+ * @mtab: returns path to mtab
+ * @writable: returns 1 if the file is writable
+ *
+ * If the file does not exist and @writable argument is not NULL, then it will
+ * try to create the file.
+ *
+ * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
+ * errno for more details).
+ */
+int mnt_has_regular_mtab(const char **mtab, int *writable)
+{
+ struct stat st;
+ int rc;
+ const char *filename = mtab && *mtab ? *mtab : mnt_get_mtab_path();
+
+ if (writable)
+ *writable = 0;
+ if (mtab && !*mtab)
+ *mtab = filename;
+
+ DBG(UTILS, ul_debug("mtab: %s", filename));
+
+ rc = lstat(filename, &st);
+
+ if (rc == 0) {
+ /* file exists */
+ if (S_ISREG(st.st_mode)) {
+ if (writable)
+ *writable = !try_write(filename, NULL);
+ DBG(UTILS, ul_debug("%s: writable", filename));
+ return 1;
+ }
+ goto done;
+ }
+
+ /* try to create the file */
+ if (writable) {
+ *writable = !try_write(filename, NULL);
+ if (*writable) {
+ DBG(UTILS, ul_debug("%s: writable", filename));
+ return 1;
+ }
+ }
+
+done:
+ DBG(UTILS, ul_debug("%s: irregular/non-writable", filename));
+ return 0;
+}
+
+/*
+ * Don't export this to libmount API -- utab is private library stuff.
+ *
+ * If the file does not exist and @writable argument is not NULL, then it will
+ * try to create the directory (e.g. /run/mount) and the file.
+ *
+ * Returns: 1 if utab is a regular file, and 0 in case of
+ * error (check errno for more details).
+ */
+int mnt_has_regular_utab(const char **utab, int *writable)
+{
+ struct stat st;
+ int rc;
+ const char *filename = utab && *utab ? *utab : mnt_get_utab_path();
+
+ if (writable)
+ *writable = 0;
+ if (utab && !*utab)
+ *utab = filename;
+
+ DBG(UTILS, ul_debug("utab: %s", filename));
+
+ rc = lstat(filename, &st);
+
+ if (rc == 0) {
+ /* file exists */
+ if (S_ISREG(st.st_mode)) {
+ if (writable)
+ *writable = !try_write(filename, NULL);
+ return 1;
+ }
+ goto done; /* it's not a regular file */
+ }
+
+ if (writable) {
+ char *dirname = strdup(filename);
+
+ if (!dirname)
+ goto done;
+
+ stripoff_last_component(dirname); /* remove filename */
+
+ rc = mkdir(dirname, S_IWUSR|
+ S_IRUSR|S_IRGRP|S_IROTH|
+ S_IXUSR|S_IXGRP|S_IXOTH);
+ if (rc && errno != EEXIST) {
+ free(dirname);
+ goto done; /* probably EACCES */
+ }
+
+ *writable = !try_write(filename, dirname);
+ free(dirname);
+ if (*writable)
+ return 1;
+ }
+done:
+ DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename));
+ return 0;
+}
+
+/**
+ * mnt_get_swaps_path:
+ *
+ * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS.
+ */
+const char *mnt_get_swaps_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_SWAPS");
+ return p ? : _PATH_PROC_SWAPS;
+}
+
+/**
+ * mnt_get_fstab_path:
+ *
+ * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB.
+ */
+const char *mnt_get_fstab_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_FSTAB");
+ return p ? : _PATH_MNTTAB;
+}
+
+/**
+ * mnt_get_mtab_path:
+ *
+ * This function returns the *default* location of the mtab file. The result does
+ * not have to be writable. See also mnt_has_regular_mtab().
+ *
+ * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
+ */
+const char *mnt_get_mtab_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_MTAB");
+ return p ? : _PATH_MOUNTED;
+}
+
+/*
+ * Don't export this to libmount API -- utab is private library stuff.
+ *
+ * Returns: path to /run/mount/utab or $LIBMOUNT_UTAB.
+ */
+const char *mnt_get_utab_path(void)
+{
+ const char *p = safe_getenv("LIBMOUNT_UTAB");
+ return p ? : MNT_PATH_UTAB;
+}
+
+
+/* returns file descriptor or -errno, @name returns a unique filename
+ */
+int mnt_open_uniq_filename(const char *filename, char **name)
+{
+ int rc, fd;
+ char *n;
+ mode_t oldmode;
+
+ if (!filename)
+ return -EINVAL;
+ if (name)
+ *name = NULL;
+
+ rc = asprintf(&n, "%s.XXXXXX", filename);
+ if (rc <= 0)
+ return -errno;
+
+ /* This is for very old glibc and for compatibility with Posix, which says
+ * nothing about mkstemp() mode. All sane glibc use secure mode (0600).
+ */
+ oldmode = umask(S_IRGRP|S_IWGRP|S_IXGRP|
+ S_IROTH|S_IWOTH|S_IXOTH);
+
+ fd = mkstemp_cloexec(n);
+ if (fd < 0)
+ fd = -errno;
+ umask(oldmode);
+
+ if (fd >= 0 && name)
+ *name = n;
+ else
+ free(n);
+
+ return fd;
+}
+
+/**
+ * mnt_get_mountpoint:
+ * @path: pathname
+ *
+ * This function finds the mountpoint that a given path resides in. @path
+ * should be canonicalized. The returned pointer should be freed by the caller.
+ *
+ * WARNING: the function compares st_dev of the @path elements. This traditional
+ * way may be insufficient on filesystems like Linux "overlay". See also
+ * mnt_table_find_target().
+ *
+ * Returns: allocated string with the target of the mounted device or NULL on error
+ */
+char *mnt_get_mountpoint(const char *path)
+{
+ char *mnt;
+ struct stat st;
+ dev_t dir, base;
+
+ if (!path)
+ return NULL;
+
+ mnt = strdup(path);
+ if (!mnt)
+ return NULL;
+ if (*mnt == '/' && *(mnt + 1) == '\0')
+ goto done;
+
+ if (mnt_stat_mountpoint(mnt, &st))
+ goto err;
+ base = st.st_dev;
+
+ do {
+ char *p = stripoff_last_component(mnt);
+
+ if (!p)
+ break;
+ if (mnt_stat_mountpoint(*mnt ? mnt : "/", &st))
+ goto err;
+ dir = st.st_dev;
+ if (dir != base) {
+ if (p > mnt)
+ *(p - 1) = '/';
+ goto done;
+ }
+ base = dir;
+ } while (mnt && *(mnt + 1) != '\0');
+
+ memcpy(mnt, "/", 2);
+done:
+ DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt));
+ return mnt;
+err:
+ free(mnt);
+ return NULL;
+}
+
+/*
+ * Search for @name kernel command parameter.
+ *
+ * Returns newly allocated string with a parameter argument if the @name is
+ * specified as "name=" or returns pointer to @name or returns NULL if not
+ * found. If it is specified more than once, we grab the last copy.
+ *
+ * For example cmdline: "aaa bbb=BBB ccc"
+ *
+ * @name is "aaa" --returns--> "aaa" (pointer to @name)
+ * @name is "bbb=" --returns--> "BBB" (allocated)
+ * @name is "foo" --returns--> NULL
+ *
+ * Note: It is not really feasible to parse the command line exactly the same
+ * as the kernel does since we don't know which options are valid. We can use
+ * the -- marker though and not walk past that.
+ */
+char *mnt_get_kernel_cmdline_option(const char *name)
+{
+ FILE *f;
+ size_t len;
+ int val = 0;
+ char *p, *res = NULL, *mem = NULL;
+ char buf[BUFSIZ]; /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */
+ const char *path = _PATH_PROC_CMDLINE;
+
+ if (!name || !name[0])
+ return NULL;
+
+#ifdef TEST_PROGRAM
+ path = getenv("LIBMOUNT_KERNEL_CMDLINE");
+ if (!path)
+ path = _PATH_PROC_CMDLINE;
+#endif
+ f = fopen(path, "r" UL_CLOEXECSTR);
+ if (!f)
+ return NULL;
+
+ p = fgets(buf, sizeof(buf), f);
+ fclose(f);
+
+ if (!p || !*p || *p == '\n')
+ return NULL;
+
+ p = strstr(p, " -- ");
+ if (p) {
+ /* no more kernel args after this */
+ *p = '\0';
+ } else {
+ len = strlen(buf);
+ buf[len - 1] = '\0'; /* remove last '\n' */
+ }
+
+ len = strlen(name);
+ if (name[len - 1] == '=')
+ val = 1;
+
+ for (p = buf; p && *p; p++) {
+ if (!(p = strstr(p, name)))
+ break; /* not found the option */
+ if (p != buf && !isblank(*(p - 1)))
+ continue; /* no space before the option */
+ if (!val && *(p + len) != '\0' && !isblank(*(p + len)))
+ continue; /* no space after the option */
+ if (val) {
+ char *v = p + len;
+ int end;
+
+ while (*p && !isblank(*p)) /* jump to the end of the argument */
+ p++;
+ end = (*p == '\0');
+ *p = '\0';
+ free(mem);
+ res = mem = strdup(v);
+ if (end)
+ break;
+ } else
+ res = (char *) name; /* option without '=' */
+ /* don't break -- keep scanning for more options */
+ }
+
+ return res;
+}
+
+/**
+ * mnt_guess_system_root:
+ * @devno: device number or zero
+ * @cache: paths cache or NULL
+ * @path: returns allocated path
+ *
+ * Converts @devno to the real device name if devno major number is greater
+ * than zero, otherwise use root= kernel cmdline option to get device name.
+ *
+ * The function uses /sys to convert devno to device name.
+ *
+ * Returns: 0 = success, 1 = not found, <0 = error
+ *
+ * Since: 2.34
+ */
+int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path)
+{
+ char buf[PATH_MAX];
+ char *dev = NULL, *spec = NULL;
+ unsigned int x, y;
+ int allocated = 0;
+
+ assert(path);
+
+ DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno)));
+
+ /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it
+ * usually matches with the source device, let's try to use it.
+ */
+ if (major(devno) > 0) {
+ dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" devno converted to %s", dev));
+ goto done;
+ }
+ }
+
+ /* Let's try to use root= kernel command line option
+ */
+ spec = mnt_get_kernel_cmdline_option("root=");
+ if (!spec)
+ goto done;
+
+ /* maj:min notation */
+ if (sscanf(spec, "%u:%u", &x, &y) == 2) {
+ dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
+ goto done;
+ }
+
+ /* hexhex notation */
+ } else if (isxdigit_string(spec)) {
+ char *end = NULL;
+ uint32_t n;
+
+ errno = 0;
+ n = strtoul(spec, &end, 16);
+
+ if (errno || spec == end || (end && *end))
+ DBG(UTILS, ul_debug(" failed to parse root='%s'", spec));
+ else {
+ /* kernel new_decode_dev() */
+ x = (n & 0xfff00) >> 8;
+ y = (n & 0xff) | ((n >> 12) & 0xfff00);
+ dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
+ if (dev) {
+ DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
+ goto done;
+ }
+ }
+
+ /* devname or PARTUUID= etc. */
+ } else {
+ DBG(UTILS, ul_debug(" converting root='%s'", spec));
+
+ dev = mnt_resolve_spec(spec, cache);
+ if (dev && !cache)
+ allocated = 1;
+ }
+done:
+ free(spec);
+ if (dev) {
+ *path = allocated ? dev : strdup(dev);
+ if (!*path)
+ return -ENOMEM;
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and
+ * mark (bind mount) the directory as private.
+ */
+int mnt_tmptgt_unshare(int *old_ns_fd)
+{
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ int rc = 0, fd = -1;
+
+ assert(old_ns_fd);
+
+ *old_ns_fd = -1;
+
+ /* remember the current namespace */
+ fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ goto fail;
+
+ /* create new namespace */
+ if (unshare(CLONE_NEWNS) != 0)
+ goto fail;
+
+ /* create directory */
+ rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU);
+ if (rc)
+ goto fail;
+
+ /* try to set top-level directory as private, this is possible if
+ * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */
+ if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) {
+
+ /* failed; create a mountpoint from MNT_PATH_TMPTGT */
+ if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0)
+ goto fail;
+ if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0)
+ goto fail;
+ }
+
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared"));
+ *old_ns_fd = fd;
+ return 0;
+fail:
+ if (rc == 0)
+ rc = errno ? -errno : -EINVAL;
+
+ mnt_tmptgt_cleanup(fd);
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed"));
+ return rc;
+#else
+ return -ENOSYS;
+#endif
+}
+
+/*
+ * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace
+ */
+int mnt_tmptgt_cleanup(int old_ns_fd)
+{
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ umount(MNT_PATH_TMPTGT);
+
+ if (old_ns_fd >= 0) {
+ setns(old_ns_fd, CLONE_NEWNS);
+ close(old_ns_fd);
+ }
+
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done"));
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+#ifdef TEST_PROGRAM
+static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *type = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH");
+ return 0;
+}
+
+static int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH");
+ return 0;
+}
+
+static int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT");
+ return 0;
+}
+
+static int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr = argv[1];
+ char *pattern = argv[2];
+
+ printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT");
+ return 0;
+}
+
+static int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *path = canonicalize_path(argv[1]),
+ *mnt = path ? mnt_get_mountpoint(path) : NULL;
+
+ printf("%s: %s\n", argv[1], mnt ? : "unknown");
+ free(mnt);
+ free(path);
+ return 0;
+}
+
+static int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char **filesystems = NULL;
+ int rc;
+
+ rc = mnt_get_filesystems(&filesystems, argc ? argv[1] : NULL);
+ if (!rc) {
+ char **p;
+ for (p = filesystems; *p; p++)
+ printf("%s\n", *p);
+ mnt_free_filesystems(filesystems);
+ }
+ return rc;
+}
+
+static int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int rc;
+ char *path = canonicalize_path(argv[1]),
+ *last = NULL;
+
+ if (!path)
+ return -errno;
+
+ rc = mnt_chdir_to_parent(path, &last);
+ if (!rc) {
+ printf("path='%s', abs='%s', last='%s'\n",
+ argv[1], path, last);
+ }
+ free(path);
+ free(last);
+ return rc;
+}
+
+static int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *name = argv[1];
+ char *res;
+
+ res = mnt_get_kernel_cmdline_option(name);
+ if (!res)
+ printf("'%s' not found\n", name);
+ else if (res == name)
+ printf("'%s' found\n", name);
+ else {
+ printf("'%s' found, argument: '%s'\n", name, res);
+ free(res);
+ }
+
+ return 0;
+}
+
+
+static int test_guess_root(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int rc;
+ char *real;
+ dev_t devno = 0;
+
+ if (argc) {
+ unsigned int x, y;
+
+ if (sscanf(argv[1], "%u:%u", &x, &y) != 2)
+ return -EINVAL;
+ devno = makedev(x, y);
+ }
+
+ rc = mnt_guess_system_root(devno, NULL, &real);
+ if (rc < 0)
+ return rc;
+ if (rc == 1)
+ fputs("not found\n", stdout);
+ else {
+ printf("%s\n", real);
+ free(real);
+ }
+ return 0;
+}
+
+static int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
+{
+ int rc;
+
+ rc = ul_mkdir_p(argv[1], S_IRWXU |
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH);
+ if (rc)
+ printf("mkdir %s failed\n", argv[1]);
+ return rc;
+}
+
+static int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[])
+{
+ struct statfs vfs;
+ int rc;
+
+ rc = statfs(argv[1], &vfs);
+ if (rc)
+ printf("%s: statfs failed: %m\n", argv[1]);
+ else
+ printf("%-30s: statfs type: %-12s [0x%lx]\n", argv[1],
+ mnt_statfs_get_fstype(&vfs),
+ (long) vfs.f_type);
+ return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test tss[] = {
+ { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" },
+ { "--match-options", test_match_options, "<options> <pattern> options matching" },
+ { "--filesystems", test_filesystems, "[<pattern>] list /{etc,proc}/filesystems" },
+ { "--starts-with", test_startswith, "<string> <prefix>" },
+ { "--ends-with", test_endswith, "<string> <prefix>" },
+ { "--mountpoint", test_mountpoint, "<path>" },
+ { "--cd-parent", test_chdir, "<path>" },
+ { "--kernel-cmdline",test_kernel_cmdline, "<option> | <option>=" },
+ { "--guess-root", test_guess_root, "[<maj:min>]" },
+ { "--mkdir", test_mkdir, "<path>" },
+ { "--statfs-type", test_statfs_type, "<path>" },
+
+ { NULL }
+ };
+
+ return mnt_run_test(tss, argc, argv);
+}
+
+#endif /* TEST_PROGRAM */
diff --git a/libmount/src/version.c b/libmount/src/version.c
new file mode 100644
index 0000000..b69b09a
--- /dev/null
+++ b/libmount/src/version.c
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount 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.
+ */
+
+/**
+ * SECTION: version-utils
+ * @title: Version functions
+ * @short_description: functions to get the library version.
+ */
+
+#include <ctype.h>
+
+#include "mountP.h"
+
+static const char *lib_version = LIBMOUNT_VERSION;
+static const char *lib_features[] = {
+#ifdef HAVE_LIBSELINUX
+ "selinux",
+#endif
+#ifdef HAVE_SMACK
+ "smack",
+#endif
+#ifdef HAVE_BTRFS_SUPPORT
+ "btrfs",
+#endif
+#ifdef HAVE_CRYPTSETUP
+ "verity",
+#endif
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ "mtab",
+#endif
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ "namespaces",
+#endif
+#if !defined(NDEBUG)
+ "assert", /* libc assert.h stuff */
+#endif
+ "debug", /* always enabled */
+ NULL
+};
+
+/**
+ * mnt_parse_version_string:
+ * @ver_string: version string (e.g "2.18.0")
+ *
+ * Returns: release version code.
+ */
+int mnt_parse_version_string(const char *ver_string)
+{
+ const char *cp;
+ int version = 0;
+
+ assert(ver_string);
+
+ for (cp = ver_string; *cp; cp++) {
+ if (*cp == '.')
+ continue;
+ if (!isdigit(*cp))
+ break;
+ version = (version * 10) + (*cp - '0');
+ }
+ return version;
+}
+
+/**
+ * mnt_get_library_version:
+ * @ver_string: return pointer to the static library version string if not NULL
+ *
+ * Returns: release version number.
+ */
+int mnt_get_library_version(const char **ver_string)
+{
+ if (ver_string)
+ *ver_string = lib_version;
+
+ return mnt_parse_version_string(lib_version);
+}
+
+/**
+ * mnt_get_library_features:
+ * @features: returns a pointer to the static array of strings, the array is
+ * terminated by NULL.
+ *
+ * Returns: number of items in the features array not including the last NULL,
+ * or less than zero in case of error
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * const char **features;
+ *
+ * mnt_get_library_features(&features);
+ * while (features && *features)
+ * printf("%s\n", *features++);
+ * </programlisting>
+ * </informalexample>
+ *
+ */
+int mnt_get_library_features(const char ***features)
+{
+ if (!features)
+ return -EINVAL;
+
+ *features = lib_features;
+ return ARRAY_SIZE(lib_features) - 1;
+}
+
+#ifdef TEST_PROGRAM
+static int test_version(struct libmnt_test *ts, int argc, char *argv[])
+{
+ const char *ver;
+ const char **features;
+
+ if (argc == 2)
+ printf("Your version: %d\n",
+ mnt_parse_version_string(argv[1]));
+
+ mnt_get_library_version(&ver);
+
+ printf("Library version: %s\n", ver);
+ printf("Library API version: " LIBMOUNT_VERSION "\n");
+ printf("Library features:");
+
+ mnt_get_library_features(&features);
+ while (features && *features)
+ printf(" %s", *features++);
+
+ printf("\n");
+
+ if (mnt_get_library_version(NULL) ==
+ mnt_parse_version_string(LIBMOUNT_VERSION))
+ return 0;
+
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libmnt_test ts[] = {
+ { "--print", test_version, "print versions" },
+ { NULL }
+ };
+
+ return mnt_run_test(ts, argc, argv);
+}
+#endif
diff --git a/libsmartcols/COPYING b/libsmartcols/COPYING
new file mode 100644
index 0000000..1c4252a
--- /dev/null
+++ b/libsmartcols/COPYING
@@ -0,0 +1,8 @@
+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.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPL-2.1-or-later file.
diff --git a/libsmartcols/Makemodule.am b/libsmartcols/Makemodule.am
new file mode 100644
index 0000000..012848b
--- /dev/null
+++ b/libsmartcols/Makemodule.am
@@ -0,0 +1,15 @@
+if BUILD_LIBSMARTCOLS
+
+include libsmartcols/src/Makemodule.am
+include libsmartcols/samples/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libsmartcols/docs
+endif
+
+pkgconfig_DATA += libsmartcols/smartcols.pc
+PATHFILES += libsmartcols/smartcols.pc
+EXTRA_DIST += libsmartcols/COPYING
+
+endif # BUILD_LIBSMARTCOLS
diff --git a/libsmartcols/docs/Makefile.am b/libsmartcols/docs/Makefile.am
new file mode 100644
index 0000000..e8a7600
--- /dev/null
+++ b/libsmartcols/docs/Makefile.am
@@ -0,0 +1,93 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libsmartcols
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space scols
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libsmartcols/src/libsmartcols.h
+CFILE_GLOB=$(top_srcdir)/libsmartcols/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=smartcolsP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
diff --git a/libsmartcols/docs/Makefile.in b/libsmartcols/docs/Makefile.in
new file mode 100644
index 0000000..06d601f
--- /dev/null
+++ b/libsmartcols/docs/Makefile.in
@@ -0,0 +1,894 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# WARNING: this is not gtk-doc.make file from gtk-doc project. This
+# file has been modified to match with util-linux requirements:
+#
+# * install files to $datadir
+# * don't maintain generated files in git repository
+# * don't distribute the final html files
+# * don't require --enable-gtk-doc for "make dist"
+# * support out-of-tree build ($srcdir != $builddir)
+#
+# -- kzak, Nov 2009
+#
+
+####################################
+# Everything below here is generic #
+####################################
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = libsmartcols/docs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_vscript.m4 \
+ $(top_srcdir)/m4/compiler.m4 $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/tls.m4 \
+ $(top_srcdir)/m4/ul.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = version.xml
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/version.xml.in \
+ $(top_srcdir)/config/gtk-doc.make
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ADJTIME_PATH = @ADJTIME_PATH@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+ASCIIDOCTOR = @ASCIIDOCTOR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BSD_WARN_CFLAGS = @BSD_WARN_CFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTSETUP_CFLAGS = @CRYPTSETUP_CFLAGS@
+CRYPTSETUP_LIBS = @CRYPTSETUP_LIBS@
+CRYPTSETUP_LIBS_STATIC = @CRYPTSETUP_LIBS_STATIC@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DAEMON_CFLAGS = @DAEMON_CFLAGS@
+DAEMON_LDFLAGS = @DAEMON_LDFLAGS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+ECONF_CFLAGS = @ECONF_CFLAGS@
+ECONF_LIBS = @ECONF_LIBS@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZING_ENGINE_LDFLAGS = @FUZZING_ENGINE_LDFLAGS@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBBLKID_DATE = @LIBBLKID_DATE@
+LIBBLKID_VERSION = @LIBBLKID_VERSION@
+LIBBLKID_VERSION_INFO = @LIBBLKID_VERSION_INFO@
+LIBFDISK_MAJOR_VERSION = @LIBFDISK_MAJOR_VERSION@
+LIBFDISK_MINOR_VERSION = @LIBFDISK_MINOR_VERSION@
+LIBFDISK_PATCH_VERSION = @LIBFDISK_PATCH_VERSION@
+LIBFDISK_PC_REQUIRES = @LIBFDISK_PC_REQUIRES@
+LIBFDISK_VERSION = @LIBFDISK_VERSION@
+LIBFDISK_VERSION_INFO = @LIBFDISK_VERSION_INFO@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMOUNT_MAJOR_VERSION = @LIBMOUNT_MAJOR_VERSION@
+LIBMOUNT_MINOR_VERSION = @LIBMOUNT_MINOR_VERSION@
+LIBMOUNT_PATCH_VERSION = @LIBMOUNT_PATCH_VERSION@
+LIBMOUNT_VERSION = @LIBMOUNT_VERSION@
+LIBMOUNT_VERSION_INFO = @LIBMOUNT_VERSION_INFO@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSMARTCOLS_VERSION = @LIBSMARTCOLS_VERSION@
+LIBSMARTCOLS_VERSION_INFO = @LIBSMARTCOLS_VERSION_INFO@
+LIBTOOL = @LIBTOOL@
+LIBUSER_CFLAGS = @LIBUSER_CFLAGS@
+LIBUSER_LIBS = @LIBUSER_LIBS@
+LIBUUID_VERSION = @LIBUUID_VERSION@
+LIBUUID_VERSION_INFO = @LIBUUID_VERSION_INFO@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAGIC_LIBS = @MAGIC_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MATH_LIBS = @MATH_LIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NCURSES5_CONFIG = @NCURSES5_CONFIG@
+NCURSES6_CONFIG = @NCURSES6_CONFIG@
+NCURSESW5_CONFIG = @NCURSESW5_CONFIG@
+NCURSESW6_CONFIG = @NCURSESW6_CONFIG@
+NCURSESW_CFLAGS = @NCURSESW_CFLAGS@
+NCURSESW_LIBS = @NCURSESW_LIBS@
+NCURSES_CFLAGS = @NCURSES_CFLAGS@
+NCURSES_LIBS = @NCURSES_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NO_UNUSED_WARN_CFLAGS = @NO_UNUSED_WARN_CFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PO4A = @PO4A@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_CFLAGS = @PYTHON_CFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_LIBS = @PYTHON_LIBS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+PYTHON_WARN_CFLAGS = @PYTHON_WARN_CFLAGS@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+READLINE_LIBS_STATIC = @READLINE_LIBS_STATIC@
+REALTIME_LIBS = @REALTIME_LIBS@
+RTAS_LIBS = @RTAS_LIBS@
+SED = @SED@
+SELINUX_CFLAGS = @SELINUX_CFLAGS@
+SELINUX_LIBS = @SELINUX_LIBS@
+SELINUX_LIBS_STATIC = @SELINUX_LIBS_STATIC@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SOLIB_CFLAGS = @SOLIB_CFLAGS@
+SOLIB_LDFLAGS = @SOLIB_LDFLAGS@
+STRIP = @STRIP@
+SUID_CFLAGS = @SUID_CFLAGS@
+SUID_LDFLAGS = @SUID_LDFLAGS@
+SYSCONFSTATICDIR = @SYSCONFSTATICDIR@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@
+SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@
+SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@
+SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+TINFOW_CFLAGS = @TINFOW_CFLAGS@
+TINFOW_LIBS = @TINFOW_LIBS@
+TINFO_CFLAGS = @TINFO_CFLAGS@
+TINFO_LIBS = @TINFO_LIBS@
+TINFO_LIBS_STATIC = @TINFO_LIBS_STATIC@
+UBSAN_LDFLAGS = @UBSAN_LDFLAGS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+VSCRIPT_LDFLAGS = @VSCRIPT_LDFLAGS@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XSLTPROC = @XSLTPROC@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bashcompletiondir = @bashcompletiondir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+sysconfstaticdir = @sysconfstaticdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+usrbin_execdir = @usrbin_execdir@
+usrlib_execdir = @usrlib_execdir@
+usrsbin_execdir = @usrsbin_execdir@
+vendordir = @vendordir@
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE = libsmartcols
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR = ../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS =
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space scols
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS =
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS =
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS =
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB = $(top_builddir)/libsmartcols/src/libsmartcols.h
+CFILE_GLOB = $(top_srcdir)/libsmartcols/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES =
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES = smartcolsP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES =
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files =
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS =
+GTKDOC_LIBS =
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_CC = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_LD = $(LIBTOOL) --tag=CC --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS)
+@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_RUN =
+@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_RUN = $(LIBTOOL) --mode=execute
+
+# We set GPATH here; this gives us semantics for GNU make
+# which are more like other make's VPATH, when it comes to
+# whether a source that is a target of one rule is then
+# searched for in VPATH/GPATH.
+#
+GPATH = $(srcdir)
+TARGET_DIR = $(docdir)/$(DOC_MODULE)
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES = version.xml
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST = $(content_files) $(HTML_IMAGES) $(DOC_MAIN_SGML_FILE) \
+ $(DOC_MODULE)-sections.txt version.xml.in
+# $(DOC_MODULE)-overrides.txt
+DOC_STAMPS = scan-build.stamp sgml-build.stamp html-build.stamp \
+ $(srcdir)/setup.stamp $(srcdir)/sgml.stamp \
+ $(srcdir)/html.stamp
+
+SCANOBJ_FILES = \
+ $(DOC_MODULE).args \
+ $(DOC_MODULE).hierarchy \
+ $(DOC_MODULE).interfaces \
+ $(DOC_MODULE).prerequisites \
+ $(DOC_MODULE).signals \
+ $(DOC_MODULE).types # util-linux: we don't use types
+
+REPORT_FILES = \
+ $(DOC_MODULE)-undocumented.txt \
+ $(DOC_MODULE)-undeclared.txt \
+ $(DOC_MODULE)-unused.txt
+
+CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/config/gtk-doc.make $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libsmartcols/docs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign libsmartcols/docs/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/config/gtk-doc.make $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+version.xml: $(top_builddir)/config.status $(srcdir)/version.xml.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile all-local
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-local
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am all-local check check-am clean clean-generic \
+ clean-libtool clean-local cscopelist-am ctags-am distclean \
+ distclean-generic distclean-libtool distclean-local distdir \
+ dvi dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-data-local install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am uninstall-local
+
+.PRECIOUS: Makefile
+
+
+@ENABLE_GTK_DOC_TRUE@all-local: html-build.stamp
+@ENABLE_GTK_DOC_FALSE@all-local:
+
+docs: html-build.stamp
+
+$(REPORT_FILES): sgml-build.stamp
+
+#### setup ####
+
+setup-build.stamp:
+ -@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
+ echo 'gtk-doc: Preparing build'; \
+ files=`echo $(EXTRA_DIST) $(expand_content_files) $(srcdir)/$(DOC_MODULE).types`; \
+ if test "x$$files" != "x" ; then \
+ for file in $$files ; do \
+ test -f $(abs_srcdir)/$$file && \
+ cp -p $(abs_srcdir)/$$file $(abs_builddir)/; \
+ done \
+ fi \
+ fi
+ @touch setup-build.stamp
+
+setup.stamp: setup-build.stamp
+ @true
+
+#### scan ####
+
+scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB) $(srcdir)/$(DOC_MODULE)-*.txt $(content_files)
+
+ @test -f $(DOC_MODULE)-sections.txt || \
+ cp $(srcdir)/$(DOC_MODULE)-sections.txt $(builddir)
+
+ $(AM_V_GEN)gtkdoc-scan --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --ignore-decorators="__ul_attribute__\(.*\)" \
+ --ignore-headers="$(IGNORE_HFILES)" \
+ --output-dir=$(builddir) \
+ $(SCAN_OPTIONS) $(EXTRA_HFILES)
+
+ @ if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \
+ CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" RUN="$(GTKDOC_RUN)" \
+ CFLAGS="$(GTKDOC_CFLAGS) $(CFLAGS)" LDFLAGS="$(GTKDOC_LIBS) \
+ $(LDFLAGS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) \
+ --module=$(DOC_MODULE) --output-dir=$(builddir) ; \
+ else \
+ for i in $(SCANOBJ_FILES) ; do \
+ test -f $$i || touch $$i ; \
+ done \
+ fi
+ @ touch scan-build.stamp
+
+$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp
+ @true
+
+#### templates ####
+#
+#tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(srcdir)/$(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt
+# @echo 'gtk-doc: Rebuilding template files'
+# test -z $(builddir)/tmpl || $(MKDIR_P) $(builddir)/tmpl
+# gtkdoc-mktmpl --module=$(DOC_MODULE) \
+# $(MKTMPL_OPTIONS)
+# touch tmpl-build.stamp
+#
+#tmpl.stamp: tmpl-build.stamp
+# @true
+#
+#tmpl/*.sgml:
+# @true
+#
+
+#### xml ####
+
+sgml-build.stamp: setup.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-decl.txt $(DOC_MODULE)-sections.txt $(expand_content_files)
+ $(AM_V_GEN)gtkdoc-mkdb --module=$(DOC_MODULE) \
+ --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \
+ --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \
+ --output-format=xml \
+ --ignore-files="$(IGNORE_HFILES)" \
+ --expand-content-files="$(expand_content_files)" \
+ --main-sgml-file=$(srcdir)/$(DOC_MAIN_SGML_FILE) \
+ $(MKDB_OPTIONS)
+ @touch sgml-build.stamp
+
+sgml.stamp: sgml-build.stamp
+ @true
+
+#### html ####
+
+html-build.stamp: sgml.stamp $(srcdir)/$(DOC_MAIN_SGML_FILE) $(content_files)
+ @rm -rf $(builddir)/html
+ @$(MKDIR_P) $(builddir)/html
+ $(AM_V_GEN)cd $(builddir)/html && \
+ gtkdoc-mkhtml --path="$(abs_builddir):$(abs_builddir)/xml:$(abs_srcdir)" \
+ $(MKHTML_OPTIONS) \
+ $(DOC_MODULE) \
+ $(abs_srcdir)/$(DOC_MAIN_SGML_FILE)
+
+ @test "x$(HTML_IMAGES)" = "x" || \
+ ( cd $(srcdir) && cp $(HTML_IMAGES) $(abs_builddir)/html )
+
+ $(AM_V_GEN)gtkdoc-fixxref --module-dir=html \
+ --module=$(DOC_MODULE) \
+ --html-dir=$(HTML_DIR) \
+ $(FIXXREF_OPTIONS)
+ @touch html-build.stamp
+
+##############
+
+clean-local:
+ rm -f *~ *.bak
+ rm -rf .libs
+
+distclean-local:
+ rm -rf xml html $(REPORT_FILES) *.stamp \
+ $(DOC_MODULE)-overrides.txt \
+ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
+ test $(abs_builddir) == $(abs_srcdir) || \
+ rm -f $(DOC_MODULE)-*.txt $(DOC_MODULE)-*.xml *.xml.in
+
+install-data-local:
+ installfiles=`echo $(builddir)/html/*`; \
+ if test "$$installfiles" = '$(builddir)/html/*'; \
+ then echo '-- Nothing to install' ; \
+ else \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ $(mkinstalldirs) $${installdir} ; \
+ for i in $$installfiles; do \
+ echo '-- Installing '$$i ; \
+ $(INSTALL_DATA) $$i $${installdir}; \
+ done; \
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp2 \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp2; \
+ mv -f $${installdir}/$(DOC_MODULE).devhelp \
+ $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp; \
+ fi; \
+ ! which gtkdoc-rebase >/dev/null 2>&1 || \
+ gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$${installdir} ; \
+ fi
+
+uninstall-local:
+ if test -n "$(DOC_MODULE_VERSION)"; then \
+ installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \
+ else \
+ installdir="$(DESTDIR)$(TARGET_DIR)"; \
+ fi; \
+ rm -rf $${installdir}
+
+#
+# Require gtk-doc when making dist
+#
+@ENABLE_GTK_DOC_TRUE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@dist-check-gtkdoc:
+@ENABLE_GTK_DOC_FALSE@ @echo "*** gtk-doc must be installed and enabled in order to make dist"
+@ENABLE_GTK_DOC_FALSE@ @false
+
+#dist-hook: dist-check-gtkdoc dist-hook-local sgml.stamp html-build.stamp
+# mkdir $(distdir)/tmpl
+# mkdir $(distdir)/xml
+# mkdir $(distdir)/html
+# -cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl
+# -cp $(srcdir)/xml/*.xml $(distdir)/xml
+# cp $(srcdir)/html/* $(distdir)/html
+# -cp $(srcdir)/$(DOC_MODULE).types $(distdir)/
+# -cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/
+# cd $(distdir) && rm -f $(DISTCLEANFILES)
+# ! which gtkdoc-rebase >/dev/null 2>&1 || \
+# gtkdoc-rebase --online --relative --html-dir=$(distdir)/html
+#
+#.PHONY : dist-hook-local docs
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/libsmartcols/docs/libsmartcols-docs.xml b/libsmartcols/docs/libsmartcols-docs.xml
new file mode 100644
index 0000000..9ded584
--- /dev/null
+++ b/libsmartcols/docs/libsmartcols-docs.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>libsmartcols Reference Manual</title>
+ <releaseinfo>for libsmartcols version &version;</releaseinfo>
+ <copyright>
+ <year>2014-2022</year>
+ <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+ </copyright>
+ </bookinfo>
+
+ <part id="overview">
+ <title>libsmartcols Overview</title>
+ <partintro>
+ <para>
+The libsmartcols library is used for smart adaptive formatting of tabular data.
+ </para>
+ <para>
+The library is part of the util-linux package since version 2.25 and is
+available from https://www.kernel.org/pub/linux/utils/util-linux/.
+ </para>
+ </partintro>
+ </part>
+
+ <part>
+ <title>Data manipulation</title>
+ <xi:include href="xml/table.xml"/>
+ <xi:include href="xml/column.xml"/>
+ <xi:include href="xml/line.xml"/>
+ <xi:include href="xml/cell.xml"/>
+ <xi:include href="xml/symbols.xml"/>
+ <xi:include href="xml/grouping.xml"/>
+ </part>
+ <part>
+ <title>Printing</title>
+ <xi:include href="xml/table_print.xml"/>
+ </part>
+ <part>
+ <title>Misc</title>
+ <xi:include href="xml/iter.xml"/>
+ <xi:include href="xml/version-utils.xml"/>
+ <xi:include href="xml/init.xml"/>
+ </part>
+ <index id="api-index">
+ <title>API Index</title>
+ <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.27">
+ <title>Index of new symbols in 2.27</title>
+ <xi:include href="xml/api-index-2.27.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.28">
+ <title>Index of new symbols in 2.28</title>
+ <xi:include href="xml/api-index-2.28.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.29">
+ <title>Index of new symbols in 2.29</title>
+ <xi:include href="xml/api-index-2.29.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.30">
+ <title>Index of new symbols in 2.30</title>
+ <xi:include href="xml/api-index-2.30.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.31">
+ <title>Index of new symbols in 2.31</title>
+ <xi:include href="xml/api-index-2.31.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.33">
+ <title>Index of new symbols in 2.33</title>
+ <xi:include href="xml/api-index-2.33.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.34">
+ <title>Index of new symbols in 2.34</title>
+ <xi:include href="xml/api-index-2.34.xml"><xi:fallback /></xi:include>
+ </index>
+ <index role="2.35">
+ <title>Index of new symbols in 2.35</title>
+ <xi:include href="xml/api-index-2.35.xml"><xi:fallback /></xi:include>
+ </index>
+</book>
diff --git a/libsmartcols/docs/libsmartcols-sections.txt b/libsmartcols/docs/libsmartcols-sections.txt
new file mode 100644
index 0000000..aa7bb15
--- /dev/null
+++ b/libsmartcols/docs/libsmartcols-sections.txt
@@ -0,0 +1,213 @@
+<SECTION>
+<FILE>cell</FILE>
+libscols_cell
+scols_cell_copy_content
+scols_cell_get_alignment
+scols_cell_get_color
+scols_cell_get_data
+scols_cell_get_flags
+scols_cell_get_userdata
+scols_cell_refer_data
+scols_cell_set_color
+scols_cell_set_data
+scols_cell_set_flags
+scols_cell_set_userdata
+scols_cmpstr_cells
+scols_reset_cell
+</SECTION>
+
+<SECTION>
+<FILE>column</FILE>
+libscols_column
+scols_column_get_color
+scols_column_get_flags
+scols_column_get_header
+scols_column_get_json_type
+scols_column_get_name
+scols_column_get_name_as_shellvar
+scols_column_get_safechars
+scols_column_get_table
+scols_column_get_whint
+scols_column_get_width
+scols_column_is_customwrap
+scols_column_is_hidden
+scols_column_is_noextremes
+scols_column_is_right
+scols_column_is_strict_width
+scols_column_is_tree
+scols_column_is_trunc
+scols_column_is_wrap
+scols_column_set_cmpfunc
+scols_column_set_color
+scols_column_set_flags
+scols_column_set_json_type
+scols_column_set_name
+scols_column_set_safechars
+scols_column_set_whint
+scols_column_set_wrapfunc
+scols_copy_column
+scols_new_column
+scols_ref_column
+scols_unref_column
+scols_wrapnl_chunksize
+scols_wrapnl_nextchunk
+</SECTION>
+
+<SECTION>
+<FILE>iter</FILE>
+libscols_iter
+scols_free_iter
+scols_iter_get_direction
+scols_new_iter
+scols_reset_iter
+</SECTION>
+
+<SECTION>
+<FILE>line</FILE>
+libscols_line
+scols_copy_line
+scols_line_add_child
+scols_line_alloc_cells
+scols_line_free_cells
+scols_line_get_cell
+scols_line_get_color
+scols_line_get_column_cell
+scols_line_get_column_data
+scols_line_get_ncells
+scols_line_get_parent
+scols_line_get_userdata
+scols_line_has_children
+scols_line_is_ancestor
+scols_line_next_child
+scols_line_refer_column_data
+scols_line_refer_data
+scols_line_remove_child
+scols_line_set_color
+scols_line_set_column_data
+scols_line_set_data
+scols_line_set_userdata
+scols_new_line
+scols_ref_line
+scols_unref_line
+</SECTION>
+
+<SECTION>
+<FILE>grouping</FILE>
+scols_line_link_group
+scols_table_group_lines
+</SECTION>
+
+<SECTION>
+<FILE>symbols</FILE>
+libscols_symbols
+scols_copy_symbols
+scols_new_symbols
+scols_ref_symbols
+scols_symbols_set_branch
+scols_symbols_set_right
+scols_symbols_set_vertical
+scols_symbols_set_title_padding
+scols_symbols_set_cell_padding
+scols_symbols_set_group_vertical
+scols_symbols_set_group_horizontal
+scols_symbols_set_group_first_member
+scols_symbols_set_group_last_member
+scols_symbols_set_group_middle_member
+scols_symbols_set_group_last_child
+scols_symbols_set_group_middle_child
+scols_unref_symbols
+</SECTION>
+
+<SECTION>
+<FILE>table</FILE>
+libscols_table
+scols_copy_table
+scols_new_table
+scols_ref_table
+scols_sort_table
+scols_sort_table_by_tree
+scols_table_add_column
+scols_table_add_line
+scols_table_colors_wanted
+scols_table_enable_ascii
+scols_table_enable_colors
+scols_table_enable_export
+scols_table_enable_header_repeat
+scols_table_enable_json
+scols_table_enable_maxout
+scols_table_enable_minout
+scols_table_enable_noencoding
+scols_table_enable_noheadings
+scols_table_enable_nolinesep
+scols_table_enable_nowrap
+scols_table_enable_raw
+scols_table_enable_shellvar
+scols_table_get_column
+scols_table_get_column_separator
+scols_table_get_line
+scols_table_get_line_separator
+scols_table_get_name
+scols_table_get_ncols
+scols_table_get_nlines
+scols_table_get_stream
+scols_table_get_symbols
+scols_table_get_termforce
+scols_table_get_termheight
+scols_table_get_termwidth
+scols_table_get_title
+scols_table_is_ascii
+scols_table_is_empty
+scols_table_is_export
+scols_table_is_header_repeat
+scols_table_is_json
+scols_table_is_maxout
+scols_table_is_minout
+scols_table_is_noencoding
+scols_table_is_noheadings
+scols_table_is_nolinesep
+scols_table_is_nowrap
+scols_table_is_raw
+scols_table_is_shellvar
+scols_table_is_tree
+scols_table_move_column
+scols_table_new_column
+scols_table_new_line
+scols_table_next_column
+scols_table_next_line
+scols_table_reduce_termwidth
+scols_table_remove_column
+scols_table_remove_columns
+scols_table_remove_line
+scols_table_remove_lines
+scols_table_set_column_separator
+scols_table_set_columns_iter
+scols_table_set_default_symbols
+scols_table_set_line_separator
+scols_table_set_name
+scols_table_set_stream
+scols_table_set_symbols
+scols_table_set_termforce
+scols_table_set_termheight
+scols_table_set_termwidth
+scols_unref_table
+</SECTION>
+
+<SECTION>
+<FILE>table_print</FILE>
+scols_print_table
+scols_print_table_to_string
+scols_table_print_range
+scols_table_print_range_to_string
+</SECTION>
+
+<SECTION>
+<FILE>version-utils</FILE>
+scols_get_library_version
+scols_parse_version_string
+LIBSMARTCOLS_VERSION
+</SECTION>
+
+<SECTION>
+<FILE>init</FILE>
+scols_init_debug
+</SECTION>
diff --git a/libsmartcols/docs/version.xml b/libsmartcols/docs/version.xml
new file mode 100644
index 0000000..d8c7561
--- /dev/null
+++ b/libsmartcols/docs/version.xml
@@ -0,0 +1 @@
+2.38.1
diff --git a/libsmartcols/docs/version.xml.in b/libsmartcols/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libsmartcols/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libsmartcols/meson.build b/libsmartcols/meson.build
new file mode 100644
index 0000000..11c4f08
--- /dev/null
+++ b/libsmartcols/meson.build
@@ -0,0 +1,53 @@
+dir_libsmartcols = include_directories('.', 'src')
+
+defs = configuration_data()
+defs.set('LIBSMARTCOLS_VERSION', pc_version)
+
+libsmartcols_h = configure_file(
+ input : 'src/libsmartcols.h.in',
+ output : 'libsmartcols.h',
+ configuration : defs,
+ install : build_libsmartcols,
+ install_dir : join_paths(get_option('includedir'), 'libsmartcols'),
+)
+
+lib_smartcols_sources = '''
+ src/smartcolsP.h
+ src/iter.c
+ src/symbols.c
+ src/cell.c
+ src/column.c
+ src/line.c
+ src/table.c
+ src/print.c
+ src/print-api.c
+ src/version.c
+ src/calculate.c
+ src/grouping.c
+ src/walk.c
+ src/init.c
+'''.split()
+
+libsmartcols_sym = 'src/libsmartcols.sym'
+libsmartcols_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libsmartcols_sym)
+
+lib_smartcols = both_libraries(
+ 'smartcols',
+ list_h,
+ lib_smartcols_sources,
+ include_directories : [dir_include, dir_libsmartcols],
+ link_depends : libsmartcols_sym,
+ version : libsmartcols_version,
+ link_args : ['-Wl,--version-script=@0@'.format(libsmartcols_sym_path)],
+ link_with : lib_common,
+ dependencies : build_libsmartcols ? [] : disabler(),
+ install : build_libsmartcols)
+
+lib_smartcols_static = lib_smartcols.get_static_lib()
+
+if build_libsmartcols
+ pkgconfig.generate(lib_smartcols,
+ description : 'table or tree library',
+ subdirs : 'libsmartcols',
+ version : pc_version)
+endif
diff --git a/libsmartcols/samples/Makemodule.am b/libsmartcols/samples/Makemodule.am
new file mode 100644
index 0000000..c0130b9
--- /dev/null
+++ b/libsmartcols/samples/Makemodule.am
@@ -0,0 +1,53 @@
+
+check_PROGRAMS += \
+ sample-scols-colors \
+ sample-scols-title \
+ sample-scols-wrap \
+ sample-scols-continuous \
+ sample-scols-fromfile \
+ sample-scols-grouping-simple \
+ sample-scols-grouping-overlay \
+ sample-scols-maxout
+
+sample_scols_cflags = $(AM_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) \
+ -I$(ul_libsmartcols_incdir)
+sample_scols_ldadd = libsmartcols.la $(LDADD)
+
+if HAVE_OPENAT
+check_PROGRAMS += sample-scols-tree
+sample_scols_tree_SOURCES = libsmartcols/samples/tree.c
+sample_scols_tree_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_tree_CFLAGS = $(sample_scols_cflags)
+endif
+
+sample_scols_colors_SOURCES = libsmartcols/samples/colors.c
+sample_scols_colors_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_colors_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_title_SOURCES = libsmartcols/samples/title.c
+sample_scols_title_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_title_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_wrap_SOURCES = libsmartcols/samples/wrap.c
+sample_scols_wrap_LDADD = $(sample_scols_ldadd)
+sample_scols_wrap_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_continuous_SOURCES = libsmartcols/samples/continuous.c
+sample_scols_continuous_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_continuous_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_maxout_SOURCES = libsmartcols/samples/maxout.c
+sample_scols_maxout_LDADD = $(sample_scols_ldadd)
+sample_scols_maxout_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_fromfile_SOURCES = libsmartcols/samples/fromfile.c
+sample_scols_fromfile_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_fromfile_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_grouping_simple_SOURCES = libsmartcols/samples/grouping-simple.c
+sample_scols_grouping_simple_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_grouping_simple_CFLAGS = $(sample_scols_cflags)
+
+sample_scols_grouping_overlay_SOURCES = libsmartcols/samples/grouping-overlay.c
+sample_scols_grouping_overlay_LDADD = $(sample_scols_ldadd) libcommon.la
+sample_scols_grouping_overlay_CFLAGS = $(sample_scols_cflags)
diff --git a/libsmartcols/samples/colors.c b/libsmartcols/samples/colors.c
new file mode 100644
index 0000000..0e9ab66
--- /dev/null
+++ b/libsmartcols/samples/colors.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+
+enum { COL_NAME, COL_FOO, COL_BAR };
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ if (!scols_table_new_column(tb, "NAME", 0, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "BAR", 0, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "FOO", 0, 0))
+ goto fail;
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static struct libscols_line *add_line(struct libscols_table *tb, const char *name, const char *data)
+{
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ if (scols_line_set_data(ln, COL_NAME, name))
+ goto fail;
+ if (scols_line_set_data(ln, COL_FOO, data))
+ goto fail;
+ if (scols_line_set_data(ln, COL_BAR, data))
+ goto fail;
+ return ln;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ struct libscols_column *cl;
+ struct libscols_line *ln;
+ struct libscols_cell *ce;
+ int c;
+
+ static const struct option longopts[] = {
+ { "maxout", 0, NULL, 'm' },
+ { "width", 1, NULL, 'w' },
+ { "help", 1, NULL, 'h' },
+
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'h':
+ printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name);
+ break;
+ case 'm':
+ scols_table_enable_maxout(tb, TRUE);
+ break;
+ case 'w':
+ scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS);
+ scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width"));
+ break;
+ }
+ }
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb);
+ add_line(tb, "AAA", "bla bla bla");
+ add_line(tb, "BB", "b");
+ add_line(tb, "CCCC", "fooo");
+ add_line(tb, "D", "baaar");
+ add_line(tb, "EE", "eee");
+
+ cl = scols_table_get_column(tb, 1);
+ scols_column_set_color(cl, "red"); /* red column */
+
+ cl = scols_table_get_column(tb, 2);
+ scols_column_set_color(cl, "reverse"); /* reverse column */
+
+ ln = scols_table_get_line(tb, 0);
+ scols_line_set_color(ln, "\033[37;41m"); /* line with red bg */
+ ce = scols_line_get_cell(ln, 0);
+ scols_cell_set_color(ce, "\033[37;45m"); /* cell with purple bg */
+
+ ln = scols_table_get_line(tb, 3);
+ scols_line_set_color(ln, "\033[37;41m"); /* line with red bg */
+ ce = scols_line_get_cell(ln, 2);
+ scols_cell_set_color(ce, "\033[37;44m"); /* cell with blue bg */
+
+ scols_print_table(tb);
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/continuous.c b/libsmartcols/samples/continuous.c
new file mode 100644
index 0000000..7db3f84
--- /dev/null
+++ b/libsmartcols/samples/continuous.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+#define TIME_PERIOD 3.0 /* seconds */
+
+enum { COL_NUM, COL_DATA, COL_TIME };
+
+static double time_diff(struct timeval *a, struct timeval *b)
+{
+ return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6;
+}
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ scols_table_enable_maxout(tb, 1);
+ if (!scols_table_new_column(tb, "#NUM", 0.1, SCOLS_FL_RIGHT))
+ goto fail;
+ if (!scols_table_new_column(tb, "DATA", 0.7, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "TIME", 0.2, 0))
+ goto fail;
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static struct libscols_line *add_line(struct libscols_table *tb, size_t i)
+{
+ char *p;
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ xasprintf(&p, "%zu", i);
+ if (scols_line_refer_data(ln, COL_NUM, p))
+ goto fail;
+
+ xasprintf(&p, "data-%02zu-%02zu-%02zu-end", i + 1, i + 2, i + 3);
+ if (scols_line_refer_data(ln, COL_DATA, p))
+ goto fail;
+
+ return ln;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ size_t i;
+ const size_t timecellsz = sizeof(stringify_value(UINT_MAX));
+ struct timeval last;
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ setup_columns(tb);
+ gettimeofday(&last, NULL);
+
+ for (i = 0; i < 10; i++) {
+ struct libscols_line *line;
+ struct timeval now;
+ int done = 0;
+ char *timecell = xmalloc( timecellsz );
+
+ line = add_line(tb, i);
+
+ /* Make a reference from cell data to the buffer, then we can
+ * update cell data without any interaction with libsmartcols
+ */
+ if (scols_line_refer_data(line, COL_TIME, timecell) != 0)
+ err(EXIT_FAILURE, "failed to add data to table");
+
+ do {
+ double diff;
+
+ gettimeofday(&now, NULL);
+ diff = time_diff(&now, &last);
+
+ if (now.tv_sec == last.tv_sec + (long) TIME_PERIOD)
+ done = 1;
+ else
+ xusleep(100000);
+
+ /* update "TIME" cell data */
+ snprintf(timecell, timecellsz, "%f [%3d%%]", diff,
+ done ? 100 : (int)(diff / (TIME_PERIOD / 100.0)));
+
+ /* Note that libsmartcols don't print \n for last line
+ * in the table, but if you print a line somewhere in
+ * the midle of the table you need
+ *
+ * scols_table_enable_nolinesep(tb, !done);
+ *
+ * to disable line breaks. In this example it's
+ * unnecessary as we print the latest line only.
+ */
+
+ /* print the line */
+ scols_table_print_range(tb, line, NULL);
+
+ if (!done) {
+ /* terminal is waiting for \n, fflush() to force output */
+ fflush(scols_table_get_stream(tb));
+ /* move to the begin of the line */
+ fputc('\r', scols_table_get_stream(tb));
+ } else
+ fputc('\n', scols_table_get_stream(tb));
+ } while (!done);
+
+ last = now;
+ }
+
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/fromfile.c b/libsmartcols/samples/fromfile.c
new file mode 100644
index 0000000..0fdc929
--- /dev/null
+++ b/libsmartcols/samples/fromfile.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "optutils.h"
+
+#include "libsmartcols.h"
+
+struct column_flag {
+ const char *name;
+ int mask;
+};
+
+static const struct column_flag flags[] = {
+ { "trunc", SCOLS_FL_TRUNC },
+ { "tree", SCOLS_FL_TREE },
+ { "right", SCOLS_FL_RIGHT },
+ { "strictwidth",SCOLS_FL_STRICTWIDTH },
+ { "noextremes", SCOLS_FL_NOEXTREMES },
+ { "hidden", SCOLS_FL_HIDDEN },
+ { "wrap", SCOLS_FL_WRAP },
+ { "wrapnl", SCOLS_FL_WRAP },
+ { "none", 0 }
+};
+
+static long name_to_flag(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ const char *cn = flags[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return flags[i].mask;
+ }
+ warnx("unknown flag: %s", name);
+ return -1;
+}
+
+static int parse_column_flags(char *str)
+{
+ unsigned long num_flags = 0;
+
+ if (string_to_bitmask(str, &num_flags, name_to_flag))
+ err(EXIT_FAILURE, "failed to parse column flags");
+
+ return num_flags;
+}
+
+static struct libscols_column *parse_column(FILE *f)
+{
+ size_t len = 0;
+ char *line = NULL;
+ int nlines = 0;
+
+ struct libscols_column *cl = NULL;
+
+ while (getline(&line, &len, f) != -1) {
+
+ char *p = strrchr(line, '\n');
+ if (p)
+ *p = '\0';
+
+ switch (nlines) {
+ case 0: /* NAME */
+ cl = scols_new_column();
+ if (!cl)
+ goto fail;
+ if (scols_column_set_name(cl, line) != 0)
+ goto fail;
+ break;
+
+ case 1: /* WIDTH-HINT */
+ {
+ double whint = strtod_or_err(line, "failed to parse column whint");
+ if (scols_column_set_whint(cl, whint))
+ goto fail;
+ break;
+ }
+ case 2: /* FLAGS */
+ {
+ int num_flags = parse_column_flags(line);
+ if (scols_column_set_flags(cl, num_flags))
+ goto fail;
+ if (strcmp(line, "wrapnl") == 0) {
+ scols_column_set_wrapfunc(cl,
+ scols_wrapnl_chunksize,
+ scols_wrapnl_nextchunk,
+ NULL);
+ scols_column_set_safechars(cl, "\n");
+ }
+ break;
+ }
+ case 3: /* COLOR */
+ if (scols_column_set_color(cl, line))
+ goto fail;
+ break;
+ default:
+ break;
+ }
+
+ nlines++;
+ }
+
+ free(line);
+ return cl;
+fail:
+ free(line);
+ scols_unref_column(cl);
+ return NULL;
+}
+
+static int parse_column_data(FILE *f, struct libscols_table *tb, int col)
+{
+ size_t len = 0, nlines = 0;
+ int i;
+ char *str = NULL;
+
+ while ((i = getline(&str, &len, f)) != -1) {
+
+ struct libscols_line *ln;
+ char *p = strrchr(str, '\n');
+ if (p)
+ *p = '\0';
+
+ while ((p = strrchr(str, '\\')) && *(p + 1) == 'n') {
+ *p = '\n';
+ memmove(p + 1, p + 2, i - (p + 2 - str));
+ }
+
+ ln = scols_table_get_line(tb, nlines++);
+ if (!ln)
+ break;
+
+ if (*str && scols_line_set_data(ln, col, str) != 0)
+ err(EXIT_FAILURE, "failed to add output data");
+ }
+
+ free(str);
+ return 0;
+
+}
+
+static struct libscols_line *get_line_with_id(struct libscols_table *tb,
+ int col_id, const char *id)
+{
+ struct libscols_line *ln;
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+
+ while (scols_table_next_line(tb, itr, &ln) == 0) {
+ struct libscols_cell *ce = scols_line_get_cell(ln, col_id);
+ const char *data = ce ? scols_cell_get_data(ce) : NULL;
+
+ if (data && strcmp(data, id) == 0)
+ break;
+ }
+
+ scols_free_iter(itr);
+ return ln;
+}
+
+static void compose_tree(struct libscols_table *tb, int parent_col, int id_col)
+{
+ struct libscols_line *ln;
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+
+ while (scols_table_next_line(tb, itr, &ln) == 0) {
+ struct libscols_line *parent = NULL;
+ struct libscols_cell *ce = scols_line_get_cell(ln, parent_col);
+ const char *data = ce ? scols_cell_get_data(ce) : NULL;
+
+ if (data)
+ parent = get_line_with_id(tb, id_col, data);
+ if (parent)
+ scols_line_add_child(parent, ln);
+ }
+
+ scols_free_iter(itr);
+}
+
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fprintf(out,
+ "\n %s [options] <column-data-file> ...\n\n", program_invocation_short_name);
+
+ fputs(" -m, --maxout fill all terminal width\n", out);
+ fputs(" -M, --minout minimize tailing padding\n", out);
+ fputs(" -c, --column <file> column definition\n", out);
+ fputs(" -n, --nlines <num> number of lines\n", out);
+ fputs(" -J, --json JSON output format\n", out);
+ fputs(" -r, --raw RAW output format\n", out);
+ fputs(" -E, --export use key=\"value\" output format\n", out);
+ fputs(" -C, --colsep <str> set columns separator\n", out);
+ fputs(" -w, --width <num> hardcode terminal width\n", out);
+ fputs(" -p, --tree-parent-column <n> parent column\n", out);
+ fputs(" -i, --tree-id-column <n> id column\n", out);
+ fputs(" -h, --help this help\n", out);
+ fputs("\n", out);
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ int c, n, nlines = 0;
+ int parent_col = -1, id_col = -1;
+
+ static const struct option longopts[] = {
+ { "maxout", 0, NULL, 'm' },
+ { "minout", 0, NULL, 'M' },
+ { "column", 1, NULL, 'c' },
+ { "nlines", 1, NULL, 'n' },
+ { "width", 1, NULL, 'w' },
+ { "tree-parent-column", 1, NULL, 'p' },
+ { "tree-id-column", 1, NULL, 'i' },
+ { "json", 0, NULL, 'J' },
+ { "raw", 0, NULL, 'r' },
+ { "export", 0, NULL, 'E' },
+ { "colsep", 1, NULL, 'C' },
+ { "help", 0, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'E', 'J', 'r' },
+ { 'M', 'm' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "hCc:Ei:JMmn:p:rw:", longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch(c) {
+ case 'c': /* add column from file */
+ {
+ struct libscols_column *cl;
+ FILE *f = fopen(optarg, "r");
+
+ if (!f)
+ err(EXIT_FAILURE, "%s: open failed", optarg);
+ cl = parse_column(f);
+ if (cl && scols_table_add_column(tb, cl))
+ err(EXIT_FAILURE, "%s: failed to add column", optarg);
+ scols_unref_column(cl);
+ fclose(f);
+ break;
+ }
+ case 'p':
+ parent_col = strtou32_or_err(optarg, "failed to parse tree PARENT column");
+ break;
+ case 'i':
+ id_col = strtou32_or_err(optarg, "failed to parse tree ID column");
+ break;
+ case 'J':
+ scols_table_enable_json(tb, 1);
+ scols_table_set_name(tb, "testtable");
+ break;
+ case 'm':
+ scols_table_enable_maxout(tb, TRUE);
+ break;
+ case 'M':
+ scols_table_enable_minout(tb, TRUE);
+ break;
+ case 'r':
+ scols_table_enable_raw(tb, TRUE);
+ break;
+ case 'E':
+ scols_table_enable_export(tb, TRUE);
+ break;
+ case 'C':
+ scols_table_set_column_separator(tb, optarg);
+ break;
+ case 'n':
+ nlines = strtou32_or_err(optarg, "failed to parse number of lines");
+ break;
+ case 'w':
+ scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS);
+ scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width"));
+ break;
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (nlines <= 0)
+ errx(EXIT_FAILURE, "--nlines not set");
+
+ for (n = 0; n < nlines; n++) {
+ struct libscols_line *ln = scols_new_line();
+
+ if (!ln || scols_table_add_line(tb, ln))
+ err(EXIT_FAILURE, "failed to add a new line");
+
+ scols_unref_line(ln);
+ }
+
+ n = 0;
+
+ while (optind < argc) {
+ FILE *f = fopen(argv[optind], "r");
+
+ if (!f)
+ err(EXIT_FAILURE, "%s: open failed", argv[optind]);
+
+ parse_column_data(f, tb, n);
+ optind++;
+ n++;
+ }
+
+ if (scols_table_is_tree(tb) && parent_col >= 0 && id_col >= 0)
+ compose_tree(tb, parent_col, id_col);
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+
+ scols_print_table(tb);
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/grouping-overlay.c b/libsmartcols/samples/grouping-overlay.c
new file mode 100644
index 0000000..d55c57d
--- /dev/null
+++ b/libsmartcols/samples/grouping-overlay.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+
+enum { COL_NAME, COL_DATA };
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ struct libscols_column *cl;
+
+ if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE))
+ goto fail;
+ cl = scols_table_new_column(tb, "DATA", 0, SCOLS_FL_WRAP);
+ if (!cl)
+ goto fail;
+ scols_column_set_wrapfunc(cl, scols_wrapnl_chunksize,
+ scols_wrapnl_nextchunk, NULL);
+ scols_column_set_safechars(cl, "\n");
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data)
+{
+ struct libscols_line *ln = scols_table_new_line(tb, parent);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ if (scols_line_set_data(ln, COL_NAME, name))
+ goto fail;
+ if (scols_line_set_data(ln, COL_DATA, data))
+ goto fail;
+ return ln;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ struct libscols_line *ln; /* any line */
+ struct libscols_line *g1, *g2, *g3; /* groups */
+ struct libscols_line *p1; /* parents */
+ int c;
+
+ static const struct option longopts[] = {
+ { "maxout", 0, NULL, 'm' },
+ { "width", 1, NULL, 'w' },
+ { "help", 1, NULL, 'h' },
+
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'h':
+ printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name);
+ break;
+ case 'm':
+ scols_table_enable_maxout(tb, TRUE);
+ break;
+ case 'w':
+ scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS);
+ scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width"));
+ break;
+ }
+ }
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb);
+
+ add_line(tb, NULL, "Alone", "bla bla bla");
+
+ p1 = add_line(tb, NULL, "A", "bla bla\nbla");
+ add_line(tb, p1, "A:B", "bla bla\nbla");
+ add_line(tb, p1, "A:C", "bla bla\nbla");
+
+ g1 = add_line(tb, NULL, "B", "bla bla\nbla");
+
+ g2 = add_line(tb, NULL, "C", "bla bla\nbla");
+ ln = add_line(tb, NULL, "D", "bla bla\nbla");
+ scols_table_group_lines(tb, g2, ln, 0);
+
+ ln = add_line(tb, NULL, "G2:A", "alb alb\nalb");
+ scols_line_link_group(ln, g2, 0);
+
+ ln = add_line(tb, NULL, "E", "bla bla\nbla");
+ scols_table_group_lines(tb, g1, ln, 0);
+
+
+ ln = add_line(tb, NULL, "G1:A", "alb alb alb");
+ scols_line_link_group(ln, g1, 0);
+
+ add_line(tb, NULL, "G", "bla bla bla");
+
+ g3 = ln = add_line(tb, NULL, "G1:B", "alb alb\nalb");
+ scols_line_link_group(ln, g1, 0);
+
+ ln = add_line(tb, NULL, "F", "bla bla bla");
+ scols_table_group_lines(tb, g3, ln, 0);
+
+ ln = add_line(tb, NULL, "G3:A", "alb alb alb");
+ scols_line_link_group(ln, g3, 0);
+
+ add_line(tb, NULL, "foo", "bla bla bla");
+ add_line(tb, NULL, "bar", "bla bla bla");
+
+ scols_print_table(tb);
+
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/grouping-simple.c b/libsmartcols/samples/grouping-simple.c
new file mode 100644
index 0000000..6b9cbcf
--- /dev/null
+++ b/libsmartcols/samples/grouping-simple.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+
+enum { COL_NAME, COL_DATA };
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ struct libscols_column *cl;
+
+ if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE))
+ goto fail;
+ cl = scols_table_new_column(tb, "DATA", 0, SCOLS_FL_WRAP);
+ if (!cl)
+ goto fail;
+
+ scols_column_set_wrapfunc(cl, scols_wrapnl_chunksize,
+ scols_wrapnl_nextchunk,
+ NULL);
+ scols_column_set_safechars(cl, "\n");
+
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data)
+{
+ struct libscols_line *ln = scols_table_new_line(tb, parent);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ if (scols_line_set_data(ln, COL_NAME, name))
+ goto fail;
+ if (scols_line_set_data(ln, COL_DATA, data))
+ goto fail;
+ return ln;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ struct libscols_line *ln; /* any line */
+ struct libscols_line *g1; /* groups */
+ struct libscols_line *p1, *p2; /* parents */
+ int c;
+
+ static const struct option longopts[] = {
+ { "maxout", 0, NULL, 'm' },
+ { "width", 1, NULL, 'w' },
+ { "help", 1, NULL, 'h' },
+
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'h':
+ printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name);
+ break;
+ case 'm':
+ scols_table_enable_maxout(tb, TRUE);
+ break;
+ case 'w':
+ scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS);
+ scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width"));
+ break;
+ }
+ }
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb);
+
+ add_line(tb, NULL, "Alone", "bla bla bla");
+
+ p1 = add_line(tb, NULL, "A", "bla bla bla");
+ add_line(tb, p1, "A:B", "bla bla bla");
+ add_line(tb, p1, "A:C", "bla bla bla");
+
+ g1 = add_line(tb, NULL, "B", "bla bla bla");
+ add_line(tb, NULL, "C", "bla\nfoo");
+ p1 = add_line(tb, NULL, "D", "bla bla\nbar");
+
+ p2 = add_line(tb, p1, "D:A", "bla bla bla");
+
+ ln = add_line(tb, p2, "D:A:A", "bla\nbla\nbla");
+ scols_table_group_lines(tb, g1, ln, 0);
+
+ add_line(tb, p1, "D:B", "bla bla bla");
+ add_line(tb, p1, "D:C", "bla\nother bla");
+ add_line(tb, p1, "D:D", "bla bla bla");
+
+ ln = add_line(tb, NULL, "E", "bla bla bla");
+ scols_table_group_lines(tb, g1, ln, 0);
+
+ p1 = ln;
+ add_line(tb, p1, "E:A", "bla bla bla");
+ add_line(tb, p1, "E:B", "bla bla bla");
+ add_line(tb, p1, "E:C", "bla bla bla");
+
+ add_line(tb, NULL, "F", "bla bla bla");
+
+ ln = add_line(tb, NULL, "G1:A", "alb alb alb");
+ scols_line_link_group(ln, g1, 0);
+
+ p1 = ln;
+ add_line(tb, p1, "G1:A:A", "bla\nbla bla");
+ add_line(tb, p1, "G1:A:B", "bla bla bla");
+ add_line(tb, p1, "G1:A:C", "bla bla bla");
+
+ add_line(tb, NULL, "G", "bla bla bla");
+
+ ln = add_line(tb, NULL, "G1:B", "alb alb\nalb");
+ scols_line_link_group(ln, g1, 0);
+
+ add_line(tb, NULL, "foo", "bla bla bla");
+ add_line(tb, NULL, "bar", "bla bla bla");
+
+ scols_print_table(tb);
+
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/maxout.c b/libsmartcols/samples/maxout.c
new file mode 100644
index 0000000..263d4de
--- /dev/null
+++ b/libsmartcols/samples/maxout.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "libsmartcols.h"
+
+enum { COL_LEFT, COL_FOO, COL_RIGHT };
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ int rc = -1, nlines = 3;
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ scols_table_enable_maxout(tb, TRUE);
+ if (!scols_table_new_column(tb, "LEFT", 0, 0))
+ goto done;
+ if (!scols_table_new_column(tb, "FOO", 0, 0))
+ goto done;
+ if (!scols_table_new_column(tb, "RIGHT", 0, SCOLS_FL_RIGHT))
+ goto done;
+
+ while (nlines--) {
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+ int rc;
+
+ rc = scols_line_set_data(ln, COL_LEFT, "A");
+ if (!rc)
+ rc = scols_line_set_data(ln, COL_FOO, "B");
+ if (!rc)
+ rc = scols_line_set_data(ln, COL_RIGHT, "C");
+ if (rc)
+ err(EXIT_FAILURE, "failed to set line data");
+ }
+
+ scols_print_table(tb);
+ rc = 0;
+done:
+ scols_unref_table(tb);
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/libsmartcols/samples/title.c b/libsmartcols/samples/title.c
new file mode 100644
index 0000000..131400d
--- /dev/null
+++ b/libsmartcols/samples/title.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+
+enum { COL_NAME, COL_DATA };
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ if (!scols_table_new_column(tb, "NAME", 0, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "DATA", 0, 0))
+ goto fail;
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static void add_line(struct libscols_table *tb, const char *name, const char *data)
+{
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ if (scols_line_set_data(ln, COL_NAME, name))
+ goto fail;
+ if (scols_line_set_data(ln, COL_DATA, data))
+ goto fail;
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ struct libscols_symbols *sy;
+ struct libscols_cell *title;
+ int c;
+
+ static const struct option longopts[] = {
+ { "maxout", 0, NULL, 'm' },
+ { "width", 1, NULL, 'w' },
+ { "help", 1, NULL, 'h' },
+
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'h':
+ printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name);
+ break;
+ case 'm':
+ scols_table_enable_maxout(tb, TRUE);
+ break;
+ case 'w':
+ scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS);
+ scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width"));
+ break;
+ }
+ }
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb);
+ add_line(tb, "foo", "bla bla bla");
+ add_line(tb, "bar", "alb alb alb");
+
+ title = scols_table_get_title(tb);
+
+ /* right */
+ scols_cell_set_data(title, "This is right title");
+ scols_cell_set_color(title, "red");
+ scols_cell_set_flags(title, SCOLS_CELL_FL_RIGHT);
+ scols_print_table(tb);
+
+ /* left without padding */
+ scols_cell_set_data(title, "This is left title (without padding)");
+ scols_cell_set_color(title, "yellow");
+ scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT);
+ scols_print_table(tb);
+
+ /* center */
+ sy = scols_new_symbols();
+ if (!sy)
+ err_oom();
+ scols_table_set_symbols(tb, sy);
+ scols_unref_symbols(sy);
+
+ scols_symbols_set_title_padding(sy, "=");
+ scols_cell_set_data(title, "This is center title (with padding)");
+ scols_cell_set_color(title, "green");
+ scols_cell_set_flags(title, SCOLS_CELL_FL_CENTER);
+ scols_print_table(tb);
+
+ /* left with padding */
+ scols_symbols_set_title_padding(sy, "-");
+ scols_cell_set_data(title, "This is left title (with padding)");
+ scols_cell_set_color(title, "blue");
+ scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT);
+ scols_print_table(tb);
+
+
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/tree.c b/libsmartcols/samples/tree.c
new file mode 100644
index 0000000..e40ea9a
--- /dev/null
+++ b/libsmartcols/samples/tree.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+
+#include "libsmartcols.h"
+
+static int add_children(struct libscols_table *tb,
+ struct libscols_line *ln, int fd);
+
+
+enum { COL_MODE, COL_SIZE, COL_NAME };
+
+struct libscols_column *sort_column;
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb, int notree)
+{
+ if (!scols_table_new_column(tb, "MODE", 0.3, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "SIZE", 5, SCOLS_FL_RIGHT))
+ goto fail;
+
+ sort_column = scols_table_new_column(tb, "NAME", 0.5,
+ (notree ? 0 : SCOLS_FL_TREE) | SCOLS_FL_NOEXTREMES);
+ if (!sort_column)
+ goto fail;
+ scols_column_set_cmpfunc(sort_column, scols_cmpstr_cells, NULL);
+
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+/* add a new line to @tb, the content is based on @st */
+static int add_line_from_stat(struct libscols_table *tb,
+ struct libscols_line *parent,
+ int parent_fd,
+ struct stat *st,
+ const char *name)
+{
+ struct libscols_line *ln;
+ char modbuf[11], *p;
+ mode_t mode = st->st_mode;
+ int rc = 0;
+
+ ln = scols_table_new_line(tb, parent);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ /* MODE; local buffer, use scols_line_set_data() that calls strdup() */
+ xstrmode(mode, modbuf);
+ if (scols_line_set_data(ln, COL_MODE, modbuf))
+ goto fail;
+
+ /* SIZE; already allocated string, use scols_line_refer_data() */
+ p = size_to_human_string(0, st->st_size);
+ if (!p || scols_line_refer_data(ln, COL_SIZE, p))
+ goto fail;
+
+ /* NAME */
+ if (scols_line_set_data(ln, COL_NAME, name))
+ goto fail;
+
+ /* colors */
+ if (scols_table_colors_wanted(tb)) {
+ struct libscols_cell *ce = scols_line_get_cell(ln, COL_NAME);
+
+ if (S_ISDIR(mode))
+ scols_cell_set_color(ce, "blue");
+ else if (S_ISLNK(mode))
+ scols_cell_set_color(ce, "cyan");
+ else if (S_ISBLK(mode))
+ scols_cell_set_color(ce, "magenta");
+ else if ((mode & S_IXOTH) || (mode & S_IXGRP) || (mode & S_IXUSR))
+ scols_cell_set_color(ce, "green");
+ }
+
+ if (S_ISDIR(st->st_mode)) {
+ int fd;
+
+ if (parent_fd >= 0)
+ fd = openat(parent_fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
+ else
+ fd = open(name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
+ if (fd >= 0) {
+ rc = add_children(tb, ln, fd);
+ close(fd);
+ }
+ }
+ return rc;
+fail:
+ err(EXIT_FAILURE, "failed to create cell data");
+ return -1;
+}
+
+/* read all entries from directory addressed by @fd */
+static int add_children(struct libscols_table *tb,
+ struct libscols_line *ln,
+ int fd)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ dir = fdopendir(fd);
+ if (!dir)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ struct stat st;
+
+ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0)
+ continue;
+ if (fstatat(fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0)
+ continue;
+ add_line_from_stat(tb, ln, fd, &st, d->d_name);
+ }
+ closedir(dir);
+ return 0;
+}
+
+static void add_lines(struct libscols_table *tb, const char *dirname)
+{
+ struct stat st;
+
+ if (lstat(dirname, &st))
+ err(EXIT_FAILURE, "%s", dirname);
+
+ add_line_from_stat(tb, NULL, -1, &st, dirname);
+}
+
+static void __attribute__((__noreturn__)) usage(FILE *out)
+{
+ fprintf(out, " %s [options] [<dir> ...]\n\n", program_invocation_short_name);
+ fputs(" -c, --csv display a csv-like output\n", out);
+ fputs(" -i, --ascii use ascii characters only\n", out);
+ fputs(" -l, --list use list format output\n", out);
+ fputs(" -n, --noheadings don't print headings\n", out);
+ fputs(" -p, --pairs use key=\"value\" output format\n", out);
+ fputs(" -J, --json use JSON output format\n", out);
+ fputs(" -r, --raw use raw output format\n", out);
+ fputs(" -s, --sort sort by NAME\n", out);
+ fputs(" -x, --tree-sort keep tree-like order (also for --list)\n", out);
+ fputs(" -S, --range-start <n> first line to print\n", out);
+ fputs(" -E, --range-end <n> last line to print\n", out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ int c, notree = 0, nstart = -1, nend = -1, sort = 0, force_tree_sort = 0;
+
+
+ static const struct option longopts[] = {
+ { "ascii", 0, NULL, 'i' },
+ { "csv", 0, NULL, 'c' },
+ { "list", 0, NULL, 'l' },
+ { "noheadings", 0, NULL, 'n' },
+ { "pairs", 0, NULL, 'p' },
+ { "json", 0, NULL, 'J' },
+ { "raw", 0, NULL, 'r' },
+ { "range-start",1, NULL, 'S' },
+ { "range-end", 1, NULL, 'E' },
+ { "sort", 0, NULL, 's' },
+ { "tree-sort", 0, NULL, 'x' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ while((c = getopt_long(argc, argv, "ciJlnprS:sE:x", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'c':
+ scols_table_set_column_separator(tb, ",");
+ scols_table_enable_raw(tb, 1);
+ notree = 1;
+ break;
+ case 'i':
+ scols_table_enable_ascii(tb, 1);
+ break;
+ case 'J':
+ scols_table_set_name(tb, "scolstest");
+ scols_table_enable_json(tb, 1);
+ break;
+ case 'l':
+ notree = 1;
+ break;
+ case 'n':
+ scols_table_enable_noheadings(tb, 1);
+ break;
+ case 'p':
+ scols_table_enable_export(tb, 1);
+ notree = 1;
+ break;
+ case 'r':
+ scols_table_enable_raw(tb, 1);
+ notree = 1;
+ break;
+ case 'S':
+ nstart = strtos32_or_err(optarg, "failed to parse range start") - 1;
+ break;
+ case 's':
+ sort = 1;
+ break;
+ case 'E':
+ nend = strtos32_or_err(optarg, "failed to parse range end") - 1;
+ break;
+ case 'x':
+ force_tree_sort = 1;
+ break;
+ default:
+ usage(stderr);
+ }
+ }
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb, notree);
+
+ if (optind == argc)
+ add_lines(tb, ".");
+ else while (optind < argc)
+ add_lines(tb, argv[optind++]);
+
+ if (sort)
+ scols_sort_table(tb, sort_column);
+ if (force_tree_sort)
+ scols_sort_table_by_tree(tb);
+
+ if (nstart >= 0 || nend >= 0) {
+ /* print subset */
+ struct libscols_line *start = NULL, *end = NULL;
+
+ if (nstart >= 0)
+ start = scols_table_get_line(tb, nstart);
+ if (nend >= 0)
+ end = scols_table_get_line(tb, nend);
+
+ if (start || end)
+ scols_table_print_range(tb, start, end);
+ } else
+ /* print all table */
+ scols_print_table(tb);
+
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/samples/wrap.c b/libsmartcols/samples/wrap.c
new file mode 100644
index 0000000..795bef7
--- /dev/null
+++ b/libsmartcols/samples/wrap.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "libsmartcols.h"
+
+
+enum { COL_NAME, COL_DESC, COL_FOO, COL_LIKE, COL_TEXT };
+
+/* add columns to the @tb */
+static void setup_columns(struct libscols_table *tb)
+{
+ if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE))
+ goto fail;
+ if (!scols_table_new_column(tb, "DESC", 0, 0))
+ goto fail;
+ if (!scols_table_new_column(tb, "FOO", 0, SCOLS_FL_WRAP))
+ goto fail;
+ if (!scols_table_new_column(tb, "LIKE", 0, SCOLS_FL_RIGHT))
+ goto fail;
+ if (!scols_table_new_column(tb, "TEXT", 0, SCOLS_FL_WRAP))
+ goto fail;
+ return;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output columns");
+}
+
+static char *gen_text(const char *prefix, const char *sub_prefix, char *buf, size_t sz)
+{
+ int x = snprintf(buf, sz, "%s-%s-", prefix, sub_prefix);
+
+ for ( ; (size_t)x < sz - 1; x++)
+ buf[x] = *prefix;
+
+ buf[x++] = 'x';
+ buf[x] = '\0';
+ return buf;
+}
+
+static struct libscols_line * add_line( struct libscols_table *tb,
+ struct libscols_line *parent,
+ const char *prefix)
+{
+ char buf[BUFSIZ];
+ struct libscols_line *ln = scols_table_new_line(tb, parent);
+ if (!ln)
+ err(EXIT_FAILURE, "failed to create output line");
+
+ if (scols_line_set_data(ln, COL_NAME, gen_text(prefix, "N", buf, 15)))
+ goto fail;
+ if (scols_line_set_data(ln, COL_DESC, gen_text(prefix, "D", buf, 10)))
+ goto fail;
+ if (scols_line_set_data(ln, COL_FOO, gen_text(prefix, "U", buf, 55)))
+ goto fail;
+ if (scols_line_set_data(ln, COL_LIKE, "1"))
+ goto fail;
+ if (scols_line_set_data(ln, COL_TEXT, gen_text(prefix, "T", buf, 50)))
+ goto fail;
+ return ln;
+fail:
+ scols_unref_table(tb);
+ err(EXIT_FAILURE, "failed to create output line");
+}
+
+int main(int argc, char *argv[])
+{
+ struct libscols_table *tb;
+ struct libscols_line *ln, *xln;
+
+ setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ err(EXIT_FAILURE, "failed to create output table");
+
+ scols_table_enable_colors(tb, isatty(STDOUT_FILENO));
+ setup_columns(tb);
+
+ ln = add_line(tb, NULL, "A");
+ add_line(tb, ln, "aa");
+ add_line(tb, ln, "ab");
+
+ ln = add_line(tb, NULL, "B");
+ xln = add_line(tb, ln, "ba");
+ add_line(tb, xln, "baa");
+ add_line(tb, xln, "bab");
+ add_line(tb, ln, "bb");
+
+ scols_print_table(tb);
+ scols_unref_table(tb);
+ return EXIT_SUCCESS;
+}
diff --git a/libsmartcols/smartcols.pc.in b/libsmartcols/smartcols.pc.in
new file mode 100644
index 0000000..0b16739
--- /dev/null
+++ b/libsmartcols/smartcols.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: smartcols
+Description: table or tree library
+Version: @LIBSMARTCOLS_VERSION@
+Cflags: -I${includedir}/libsmartcols
+Libs: -L${libdir} -lsmartcols
diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am
new file mode 100644
index 0000000..2bb19fd
--- /dev/null
+++ b/libsmartcols/src/Makemodule.am
@@ -0,0 +1,62 @@
+
+
+# smartcols.h is generated, so it's stored in builddir!
+smartcolsincdir = $(includedir)/libsmartcols
+nodist_smartcolsinc_HEADERS = libsmartcols/src/libsmartcols.h
+
+usrlib_exec_LTLIBRARIES += libsmartcols.la
+libsmartcols_la_SOURCES= \
+ include/list.h \
+ \
+ libsmartcols/src/smartcolsP.h \
+ libsmartcols/src/iter.c \
+ libsmartcols/src/symbols.c \
+ libsmartcols/src/cell.c \
+ libsmartcols/src/column.c \
+ libsmartcols/src/line.c \
+ libsmartcols/src/table.c \
+ libsmartcols/src/print.c \
+ libsmartcols/src/print-api.c \
+ libsmartcols/src/version.c \
+ libsmartcols/src/calculate.c \
+ libsmartcols/src/grouping.c \
+ libsmartcols/src/walk.c \
+ libsmartcols/src/init.c
+
+libsmartcols_la_LIBADD = $(LDADD) libcommon.la
+
+libsmartcols_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_libsmartcols_incdir) \
+ -I$(top_srcdir)/libsmartcols/src
+
+EXTRA_libsmartcols_la_DEPENDENCIES = \
+ libsmartcols/src/libsmartcols.sym
+
+libsmartcols_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libsmartcols_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libsmartcols/src/libsmartcols.sym
+endif
+libsmartcols_la_LDFLAGS += -version-info $(LIBSMARTCOLS_VERSION_INFO)
+
+EXTRA_DIST += \
+ libsmartcols/src/libsmartcols.sym
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libsmartcols:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libsmartcols.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libsmartcols.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libsmartcols.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libsmartcols.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libsmartcols.so); \
+ fi
+
+uninstall-hook-libsmartcols:
+ rm -f $(DESTDIR)$(libdir)/libsmartcols.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libsmartcols
+UNINSTALL_HOOKS += uninstall-hook-libsmartcols
diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c
new file mode 100644
index 0000000..4f79408
--- /dev/null
+++ b/libsmartcols/src/calculate.c
@@ -0,0 +1,454 @@
+#include "smartcolsP.h"
+#include "mbsalign.h"
+
+static void dbg_column(struct libscols_table *tb, struct libscols_column *cl)
+{
+ if (scols_column_is_hidden(cl)) {
+ DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data));
+ return;
+ }
+
+ DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, "
+ "hint=%d, avg=%zu, max=%zu, min=%zu, "
+ "extreme=%s %s",
+
+ cl->header.data, cl->seqnum, cl->width,
+ cl->width_hint > 1 ? (int) cl->width_hint :
+ (int) (cl->width_hint * tb->termwidth),
+ cl->width_avg,
+ cl->width_max,
+ cl->width_min,
+ cl->is_extreme ? "yes" : "not",
+ cl->flags & SCOLS_FL_TRUNC ? "trunc" : ""));
+}
+
+static void dbg_columns(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_column *cl;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0)
+ dbg_column(tb, cl);
+}
+
+static int count_cell_width(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl,
+ struct ul_buffer *buf)
+{
+ size_t len;
+ char *data;
+ int rc;
+
+ rc = __cell_to_buffer(tb, ln, cl, buf);
+ if (rc)
+ return rc;
+
+ data = ul_buffer_get_data(buf, NULL, NULL);
+ if (!data)
+ len = 0;
+ else if (scols_column_is_customwrap(cl))
+ len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data);
+ else if (scols_table_is_noencoding(tb))
+ len = mbs_width(data);
+ else
+ len = mbs_safe_width(data);
+
+ if (len == (size_t) -1) /* ignore broken multibyte strings */
+ len = 0;
+ cl->width_max = max(len, cl->width_max);
+
+ if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2)
+ return 0;
+
+ if (scols_column_is_noextremes(cl)) {
+ cl->extreme_sum += len;
+ cl->extreme_count++;
+ }
+ cl->width = max(len, cl->width);
+ if (scols_column_is_tree(cl)) {
+ size_t treewidth = ul_buffer_get_safe_pointer_width(buf, SCOLS_BUFPTR_TREEEND);
+ cl->width_treeart = max(cl->width_treeart, treewidth);
+ }
+ return 0;
+}
+
+
+static int walk_count_cell_width(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl,
+ void *data)
+{
+ return count_cell_width(tb, ln, cl, (struct ul_buffer *) data);
+}
+
+/*
+ * This function counts column width.
+ *
+ * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function
+ * two times. The first pass counts the width and average width. If the column
+ * contains fields that are too large (a width greater than 2 * average) then
+ * the column is marked as "extreme". In the second pass all extreme fields
+ * are ignored and the column width is counted from non-extreme fields only.
+ */
+static int count_column_width(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct ul_buffer *buf)
+{
+ int rc = 0, no_header = 0;
+
+ assert(tb);
+ assert(cl);
+
+ cl->width = 0;
+ if (!cl->width_min) {
+ const char *data;
+
+ if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) {
+ cl->width_min = (size_t) (cl->width_hint * tb->termwidth);
+ if (cl->width_min && !is_last_column(cl))
+ cl->width_min--;
+ }
+
+ data = scols_cell_get_data(&cl->header);
+ if (data) {
+ size_t len = scols_table_is_noencoding(tb) ?
+ mbs_width(data) : mbs_safe_width(data);
+ cl->width_min = max(cl->width_min, len);
+ } else
+ no_header = 1;
+
+ if (!cl->width_min)
+ cl->width_min = 1;
+ }
+
+ if (scols_table_is_tree(tb)) {
+ /* Count width for tree */
+ rc = scols_walk_tree(tb, cl, walk_count_cell_width, (void *) buf);
+ if (rc)
+ goto done;
+ } else {
+ /* Count width for list */
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ rc = count_cell_width(tb, ln, cl, buf);
+ if (rc)
+ goto done;
+ }
+ }
+
+ if (scols_column_is_tree(cl) && has_groups(tb)) {
+ /* We don't fill buffer with groups tree ascii art during width
+ * calculation. The print function only enlarge grpset[] and we
+ * calculate final width from grpset_size.
+ */
+ size_t gprwidth = tb->grpset_size + 1;
+ cl->width_treeart += gprwidth;
+ cl->width_max += gprwidth;
+ cl->width += gprwidth;
+ if (cl->extreme_count)
+ cl->extreme_sum += gprwidth;
+ }
+
+ if (cl->extreme_count && cl->width_avg == 0) {
+ cl->width_avg = cl->extreme_sum / cl->extreme_count;
+ if (cl->width_avg && cl->width_max > cl->width_avg * 2)
+ cl->is_extreme = 1;
+ }
+
+ /* enlarge to minimal width */
+ if (cl->width < cl->width_min && !scols_column_is_strict_width(cl))
+ cl->width = cl->width_min;
+
+ /* use absolute size for large columns */
+ else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint
+ && cl->width_min < (size_t) cl->width_hint)
+
+ cl->width = (size_t) cl->width_hint;
+
+
+ /* Column without header and data, set minimal size to zero (default is 1) */
+ if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1)
+ cl->width = cl->width_min = 0;
+
+done:
+ ON_DBG(COL, dbg_column(tb, cl));
+ return rc;
+}
+
+/*
+ * This is core of the scols_* voodoo...
+ */
+int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ struct libscols_column *cl;
+ struct libscols_iter itr;
+ size_t width = 0, width_min = 0; /* output width */
+ int stage, rc = 0;
+ int extremes = 0, group_ncolumns = 0;
+ size_t colsepsz;
+
+
+ DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth));
+ tb->is_dummy_print = 1;
+
+ colsepsz = scols_table_is_noencoding(tb) ?
+ mbs_width(colsep(tb)) :
+ mbs_safe_width(colsep(tb));
+
+ if (has_groups(tb))
+ group_ncolumns = 1;
+
+ /* set basic columns width
+ */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ int is_last;
+
+ if (scols_column_is_hidden(cl))
+ continue;
+
+ /* we print groups chart only for the for the first tree column */
+ if (scols_column_is_tree(cl) && group_ncolumns == 1) {
+ cl->is_groups = 1;
+ group_ncolumns++;
+ }
+
+ rc = count_column_width(tb, cl, buf);
+ if (rc)
+ goto done;
+
+ is_last = is_last_column(cl);
+
+ width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */
+ width_min += cl->width_min + (is_last ? 0 : colsepsz);
+ if (cl->is_extreme)
+ extremes++;
+ }
+
+ if (!tb->is_term) {
+ DBG(TAB, ul_debugobj(tb, " non-terminal output"));
+ goto done;
+ }
+
+ /* be paranoid */
+ if (width_min > tb->termwidth && scols_table_is_maxout(tb)) {
+ DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (width_min > tb->termwidth
+ && scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+ width_min--;
+ cl->width_min--;
+ }
+ DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min));
+ }
+
+ /* reduce columns with extreme fields */
+ if (width > tb->termwidth && extremes) {
+ DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)"));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ size_t org_width;
+
+ if (!cl->is_extreme || scols_column_is_hidden(cl))
+ continue;
+
+ org_width = cl->width;
+ rc = count_column_width(tb, cl, buf);
+ if (rc)
+ goto done;
+
+ if (org_width > cl->width)
+ width -= org_width - cl->width;
+ else
+ extremes--; /* hmm... nothing reduced */
+ }
+ }
+
+ if (width < tb->termwidth) {
+ if (extremes) {
+ DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)"));
+
+ /* enlarge the first extreme column */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ size_t add;
+
+ if (!cl->is_extreme || scols_column_is_hidden(cl))
+ continue;
+
+ /* this column is too large, ignore?
+ if (cl->width_max - cl->width >
+ (tb->termwidth - width))
+ continue;
+ */
+
+ add = tb->termwidth - width;
+ if (add && cl->width + add > cl->width_max)
+ add = cl->width_max - cl->width;
+
+ cl->width += add;
+ width += add;
+
+ if (width == tb->termwidth)
+ break;
+ }
+ }
+
+ if (width < tb->termwidth && scols_table_is_maxout(tb)) {
+ DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)"));
+
+ /* try enlarging all columns */
+ while (width < tb->termwidth) {
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+ cl->width++;
+ width++;
+ if (width == tb->termwidth)
+ break;
+ }
+ }
+ } else if (width < tb->termwidth) {
+ /* enlarge the last column */
+ struct libscols_column *col = list_entry(
+ tb->tb_columns.prev, struct libscols_column, cl_columns);
+
+ DBG(TAB, ul_debugobj(tb, " enlarge width (last column)"));
+
+ if (!scols_column_is_right(col)) {
+ col->width += tb->termwidth - width;
+ width = tb->termwidth;
+ }
+ }
+ }
+
+ /* bad, we have to reduce output width, this is done in three stages:
+ *
+ * 1) trunc relative with trunc flag if the column width is greater than
+ * expected column width (it means "width_hint * terminal_width").
+ *
+ * 2) trunc all with trunc flag
+ *
+ * 3) trunc relative without trunc flag
+ *
+ * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is
+ * interpreted as SCOLS_FL_TRUNC.
+ */
+ for (stage = 1; width > tb->termwidth && stage <= 3; ) {
+ size_t org_width = width;
+
+ DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)",
+ stage, width, tb->termwidth));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+
+ int trunc_flag = 0;
+
+ DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)",
+ cl->header.data, cl->width, cl->width_treeart));
+ if (scols_column_is_hidden(cl))
+ continue;
+ if (width <= tb->termwidth)
+ break;
+
+ /* never truncate if already minimal width */
+ if (cl->width == cl->width_min)
+ continue;
+
+ /* never truncate the tree */
+ if (scols_column_is_tree(cl) && width <= cl->width_treeart)
+ continue;
+
+ /* nothing to truncate */
+ if (cl->width == 0)
+ continue;
+
+ trunc_flag = scols_column_is_trunc(cl)
+ || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl));
+
+ switch (stage) {
+ /* #1 stage - trunc relative with TRUNC flag */
+ case 1:
+ if (!trunc_flag) /* ignore: missing flag */
+ break;
+ if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */
+ break;
+ if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */
+ break;
+
+ DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)"));
+ cl->width--;
+ width--;
+ break;
+
+ /* #2 stage - trunc all with TRUNC flag */
+ case 2:
+ if (!trunc_flag) /* ignore: missing flag */
+ break;
+
+ DBG(TAB, ul_debugobj(tb, " reducing (all with flag)"));
+ cl->width--;
+ width--;
+ break;
+
+ /* #3 stage - trunc relative without flag */
+ case 3:
+ if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */
+ break;
+
+ DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)"));
+ cl->width--;
+ width--;
+ break;
+ }
+
+ /* hide zero width columns */
+ if (cl->width == 0)
+ cl->flags |= SCOLS_FL_HIDDEN;
+ }
+
+ /* the current stage is without effect, go to the next */
+ if (org_width == width)
+ stage++;
+ }
+
+ /* ignore last column(s) or force last column to be truncated if
+ * nowrap mode enabled */
+ if (tb->no_wrap && width > tb->termwidth) {
+ scols_reset_iter(&itr, SCOLS_ITER_BACKWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+
+ if (scols_column_is_hidden(cl))
+ continue;
+ if (width <= tb->termwidth)
+ break;
+ if (width - cl->width < tb->termwidth) {
+ size_t r = width - tb->termwidth;
+
+ cl->flags |= SCOLS_FL_TRUNC;
+ cl->width -= r;
+ width -= r;
+ } else {
+ cl->flags |= SCOLS_FL_HIDDEN;
+ width -= cl->width + colsepsz;
+ }
+ }
+ }
+done:
+ tb->is_dummy_print = 0;
+ DBG(TAB, ul_debugobj(tb, "-----final width: %zu (rc=%d)-----", width, rc));
+ ON_DBG(TAB, dbg_columns(tb));
+
+ return rc;
+}
diff --git a/libsmartcols/src/cell.c b/libsmartcols/src/cell.c
new file mode 100644
index 0000000..4cd6e59
--- /dev/null
+++ b/libsmartcols/src/cell.c
@@ -0,0 +1,257 @@
+/*
+ * cell.c - functions for table handling at the cell level
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: cell
+ * @title: Cell
+ * @short_description: container for your data
+ *
+ * An API to access and modify per-cell data and information. Note that cell is
+ * always part of the line. If you destroy (un-reference) a line than it
+ * destroys all line cells too.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "smartcolsP.h"
+
+/*
+ * The cell has no ref-counting, free() and new() functions. All is
+ * handled by libscols_line.
+ */
+
+/**
+ * scols_reset_cell:
+ * @ce: pointer to a struct libscols_cell instance
+ *
+ * Frees the cell's internal data and resets its status.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_reset_cell(struct libscols_cell *ce)
+{
+ if (!ce)
+ return -EINVAL;
+
+ /*DBG(CELL, ul_debugobj(ce, "reset"));*/
+ free(ce->data);
+ free(ce->color);
+ memset(ce, 0, sizeof(*ce));
+ return 0;
+}
+
+/**
+ * scols_cell_set_data:
+ * @ce: a pointer to a struct libscols_cell instance
+ * @data: data (used for scols_print_table())
+ *
+ * Stores a copy of the @str in @ce, the old data are deallocated by free().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_set_data(struct libscols_cell *ce, const char *data)
+{
+ return strdup_to_struct_member(ce, data, data);
+}
+
+/**
+ * scols_cell_refer_data:
+ * @ce: a pointer to a struct libscols_cell instance
+ * @data: data (used for scols_print_table())
+ *
+ * Adds a reference to @str to @ce. The pointer is deallocated by
+ * scols_reset_cell() or scols_unref_line(). This function is mostly designed
+ * for situations when the data for the cell are already composed in allocated
+ * memory (e.g. asprintf()) to avoid extra unnecessary strdup().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_refer_data(struct libscols_cell *ce, char *data)
+{
+ if (!ce)
+ return -EINVAL;
+ free(ce->data);
+ ce->data = data;
+ return 0;
+}
+
+/**
+ * scols_cell_get_data:
+ * @ce: a pointer to a struct libscols_cell instance
+ *
+ * Returns: data in @ce or NULL.
+ */
+const char *scols_cell_get_data(const struct libscols_cell *ce)
+{
+ return ce ? ce->data : NULL;
+}
+
+/**
+ * scols_cell_set_userdata:
+ * @ce: a pointer to a struct libscols_cell instance
+ * @data: private user data
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_set_userdata(struct libscols_cell *ce, void *data)
+{
+ if (!ce)
+ return -EINVAL;
+ ce->userdata = data;
+ return 0;
+}
+
+/**
+ * scols_cell_get_userdata
+ * @ce: a pointer to a struct libscols_cell instance
+ *
+ * Returns: user data
+ */
+void *scols_cell_get_userdata(struct libscols_cell *ce)
+{
+ return ce->userdata;
+}
+
+/**
+ * scols_cmpstr_cells:
+ * @a: pointer to cell
+ * @b: pointer to cell
+ * @data: unused pointer to private data (defined by API)
+ *
+ * Compares cells data by strcmp(). The function is designed for
+ * scols_column_set_cmpfunc() and scols_sort_table().
+ *
+ * Returns: follows strcmp() return values.
+ */
+int scols_cmpstr_cells(struct libscols_cell *a,
+ struct libscols_cell *b,
+ __attribute__((__unused__)) void *data)
+{
+ const char *adata, *bdata;
+
+ if (a == b)
+ return 0;
+
+ adata = scols_cell_get_data(a);
+ bdata = scols_cell_get_data(b);
+
+ if (adata == NULL && bdata == NULL)
+ return 0;
+ if (adata == NULL)
+ return -1;
+ if (bdata == NULL)
+ return 1;
+ return strcmp(adata, bdata);
+}
+
+/**
+ * scols_cell_set_color:
+ * @ce: a pointer to a struct libscols_cell instance
+ * @color: color name or ESC sequence
+ *
+ * Set the color of @ce to @color.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_set_color(struct libscols_cell *ce, const char *color)
+{
+ if (color && isalpha(*color)) {
+ color = color_sequence_from_colorname(color);
+ if (!color)
+ return -EINVAL;
+ }
+ return strdup_to_struct_member(ce, color, color);
+}
+
+/**
+ * scols_cell_get_color:
+ * @ce: a pointer to a struct libscols_cell instance
+ *
+ * Returns: the current color of @ce.
+ */
+const char *scols_cell_get_color(const struct libscols_cell *ce)
+{
+ return ce->color;
+}
+
+/**
+ * scols_cell_set_flags:
+ * @ce: a pointer to a struct libscols_cell instance
+ * @flags: SCOLS_CELL_FL_* flags
+ *
+ * Note that cells in the table are always aligned by column flags. The cell
+ * flags are used for table title only (now).
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_set_flags(struct libscols_cell *ce, int flags)
+{
+ if (!ce)
+ return -EINVAL;
+ ce->flags = flags;
+ return 0;
+}
+
+/**
+ * scols_cell_get_flags:
+ * @ce: a pointer to a struct libscols_cell instance
+ *
+ * Returns: the current flags
+ */
+int scols_cell_get_flags(const struct libscols_cell *ce)
+{
+ return ce->flags;
+}
+
+/**
+ * scols_cell_get_alignment:
+ * @ce: a pointer to a struct libscols_cell instance
+ *
+ * Since: 2.30
+ *
+ * Returns: SCOLS_CELL_FL_{RIGHT,CELNTER,LEFT}
+ */
+int scols_cell_get_alignment(const struct libscols_cell *ce)
+{
+ if (ce->flags & SCOLS_CELL_FL_RIGHT)
+ return SCOLS_CELL_FL_RIGHT;
+ if (ce->flags & SCOLS_CELL_FL_CENTER)
+ return SCOLS_CELL_FL_CENTER;
+
+ return SCOLS_CELL_FL_LEFT; /* default */
+}
+
+/**
+ * scols_cell_copy_content:
+ * @dest: a pointer to a struct libscols_cell instance
+ * @src: a pointer to an immutable struct libscols_cell instance
+ *
+ * Copy the contents of @src into @dest.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_cell_copy_content(struct libscols_cell *dest,
+ const struct libscols_cell *src)
+{
+ int rc;
+
+ rc = scols_cell_set_data(dest, scols_cell_get_data(src));
+ if (!rc)
+ rc = scols_cell_set_color(dest, scols_cell_get_color(src));
+ if (!rc)
+ dest->userdata = src->userdata;
+
+ DBG(CELL, ul_debugobj(src, "copy"));
+ return rc;
+}
diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c
new file mode 100644
index 0000000..d5a00f0
--- /dev/null
+++ b/libsmartcols/src/column.c
@@ -0,0 +1,643 @@
+/*
+ * column.c - functions for table handling at the column level
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: column
+ * @title: Column
+ * @short_description: defines output columns formats, headers, etc.
+ *
+ * An API to access and modify per-column data and information.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mbsalign.h"
+
+#include "smartcolsP.h"
+
+/**
+ * scols_new_column:
+ *
+ * Allocates space for a new column.
+ *
+ * Returns: a pointer to a new struct libscols_column instance, NULL in case of an ENOMEM error.
+ */
+struct libscols_column *scols_new_column(void)
+{
+ struct libscols_column *cl;
+
+ cl = calloc(1, sizeof(*cl));
+ if (!cl)
+ return NULL;
+ DBG(COL, ul_debugobj(cl, "alloc"));
+ cl->refcount = 1;
+ INIT_LIST_HEAD(&cl->cl_columns);
+ return cl;
+}
+
+/**
+ * scols_ref_column:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Increases the refcount of @cl.
+ */
+void scols_ref_column(struct libscols_column *cl)
+{
+ if (cl)
+ cl->refcount++;
+}
+
+/**
+ * scols_unref_column:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Decreases the refcount of @cl. When the count falls to zero, the instance
+ * is automatically deallocated.
+ */
+void scols_unref_column(struct libscols_column *cl)
+{
+ if (cl && --cl->refcount <= 0) {
+ DBG(COL, ul_debugobj(cl, "dealloc"));
+ list_del(&cl->cl_columns);
+ scols_reset_cell(&cl->header);
+ free(cl->color);
+ free(cl->safechars);
+ free(cl->pending_data_buf);
+ free(cl->shellvar);
+ free(cl);
+ }
+}
+
+/**
+ * scols_copy_column:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Creates a new column and copies @cl's data over to it.
+ *
+ * Returns: a pointer to a new struct libscols_column instance.
+ */
+struct libscols_column *scols_copy_column(const struct libscols_column *cl)
+{
+ struct libscols_column *ret;
+
+ if (!cl)
+ return NULL;
+ ret = scols_new_column();
+ if (!ret)
+ return NULL;
+
+ DBG(COL, ul_debugobj(cl, "copy"));
+
+ if (scols_column_set_color(ret, cl->color))
+ goto err;
+ if (scols_cell_copy_content(&ret->header, &cl->header))
+ goto err;
+
+ ret->width = cl->width;
+ ret->width_min = cl->width_min;
+ ret->width_max = cl->width_max;
+ ret->width_avg = cl->width_avg;
+ ret->width_hint = cl->width_hint;
+ ret->flags = cl->flags;
+ ret->is_extreme = cl->is_extreme;
+ ret->is_groups = cl->is_groups;
+
+ return ret;
+err:
+ scols_unref_column(ret);
+ return NULL;
+}
+
+/**
+ * scols_column_set_whint:
+ * @cl: a pointer to a struct libscols_column instance
+ * @whint: a width hint
+ *
+ * Sets the width hint of column @cl to @whint. See scols_table_new_column().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_column_set_whint(struct libscols_column *cl, double whint)
+{
+ if (!cl)
+ return -EINVAL;
+
+ cl->width_hint = whint;
+ return 0;
+}
+
+/**
+ * scols_column_get_whint:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: The width hint of column @cl, a negative value in case of an error.
+ */
+double scols_column_get_whint(const struct libscols_column *cl)
+{
+ return cl->width_hint;
+}
+
+/**
+ * scols_column_set_flags:
+ * @cl: a pointer to a struct libscols_column instance
+ * @flags: a flag mask
+ *
+ * Sets the flags of @cl to @flags.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_column_set_flags(struct libscols_column *cl, int flags)
+{
+ if (!cl)
+ return -EINVAL;
+
+ if (cl->table) {
+ if (!(cl->flags & SCOLS_FL_TREE) && (flags & SCOLS_FL_TREE))
+ cl->table->ntreecols++;
+ else if ((cl->flags & SCOLS_FL_TREE) && !(flags & SCOLS_FL_TREE))
+ cl->table->ntreecols--;
+ }
+
+ DBG(COL, ul_debugobj(cl, "setting flags from 0%x to 0%x", cl->flags, flags));
+ cl->flags = flags;
+ return 0;
+}
+
+/**
+ * scols_column_set_json_type:
+ * @cl: a pointer to a struct libscols_column instance
+ * @type: SCOLS_JSON_* type
+ *
+ * Sets the type used for JSON formatting, the default is SCOLS_JSON_STRING.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.33
+ */
+int scols_column_set_json_type(struct libscols_column *cl, int type)
+{
+ if (!cl)
+ return -EINVAL;
+
+ cl->json_type = type;
+ return 0;
+
+}
+
+/**
+ * scols_column_get_json_type:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Note that SCOLS_JSON_BOOLEAN interprets NULL, empty strings, '0', 'N' and
+ * 'n' as "false"; and everything else as "true".
+ *
+ * Returns: JSON type used for formatting or a negative value in case of an error.
+ *
+ * Since: 2.33
+ */
+int scols_column_get_json_type(const struct libscols_column *cl)
+{
+ return cl ? cl->json_type : -EINVAL;
+}
+
+
+/**
+ * scols_column_get_table:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: pointer to the table where columns is used
+ */
+struct libscols_table *scols_column_get_table(const struct libscols_column *cl)
+{
+ return cl->table;
+}
+
+/**
+ * scols_column_get_flags:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: The flag mask of @cl, a negative value in case of an error.
+ */
+int scols_column_get_flags(const struct libscols_column *cl)
+{
+ return cl->flags;
+}
+
+/**
+ * scols_column_get_header:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: A pointer to a struct libscols_cell instance, representing the
+ * header info of column @cl or NULL in case of an error.
+ */
+struct libscols_cell *scols_column_get_header(struct libscols_column *cl)
+{
+ return &cl->header;
+}
+
+/**
+ * scols_column_set_name:
+ * @cl: a pointer to a struct libscols_column instance
+ * @name: column name
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.38
+ */
+int scols_column_set_name(struct libscols_column *cl, const char *name)
+{
+ struct libscols_cell *hr = scols_column_get_header(cl);
+
+ if (!hr)
+ return -EINVAL;
+
+ free(cl->shellvar);
+ cl->shellvar = NULL;
+
+ return scols_cell_set_data(hr, name);
+}
+
+/**
+ * scols_column_get_name:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: A pointer to a column name, which is stored in column header
+ *
+ * Since: 2.38
+ */
+const char *scols_column_get_name(struct libscols_column *cl)
+{
+ return scols_cell_get_data(&cl->header);
+}
+
+/**
+ * scols_column_get_name_as_shellvar
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Like scols_column_get_name(), but column name is modified to be compatible with shells
+ * requirements for variable names.
+ *
+ * Since: 2.38
+ */
+const char *scols_column_get_name_as_shellvar(struct libscols_column *cl)
+{
+ if (!cl->shellvar) {
+ const char *s, *name = scols_column_get_name(cl);
+ char *p;
+ size_t sz;
+
+ if (!name || !*name)
+ return NULL;
+
+ /* "1FOO%" --> "_1FOO_PCT */
+ sz = strlen(name) + 1 + 3;
+ p = cl->shellvar = calloc(1, sz + 1);
+ if (!cl->shellvar)
+ return NULL;
+
+ /* convert "1FOO" to "_1FOO" */
+ if (!isalpha(*name))
+ *p++ = '_';
+
+ /* replace all "bad" chars with "_" */
+ for (s = name; *s; s++)
+ *p++ = !isalnum(*s) ? '_' : *s;
+
+ if (!*s && *(s - 1) == '%') {
+ *p++ = 'P';
+ *p++ = 'C';
+ *p++ = 'T';
+ }
+ }
+ return cl->shellvar;
+}
+
+
+/**
+ * scols_column_set_color:
+ * @cl: a pointer to a struct libscols_column instance
+ * @color: color name or ESC sequence
+ *
+ * The default color for data cells and column header.
+ *
+ * If you want to set header specific color then use scols_column_get_header()
+ * and scols_cell_set_color().
+ *
+ * If you want to set data cell specific color the use scols_line_get_cell() +
+ * scols_cell_set_color().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_column_set_color(struct libscols_column *cl, const char *color)
+{
+ if (color && isalpha(*color)) {
+ color = color_sequence_from_colorname(color);
+ if (!color)
+ return -EINVAL;
+ }
+ return strdup_to_struct_member(cl, color, color);
+}
+
+/**
+ * scols_column_get_color:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: The current color setting of the column @cl.
+ */
+const char *scols_column_get_color(const struct libscols_column *cl)
+{
+ return cl->color;
+}
+
+/**
+ * scols_wrapnl_nextchunk:
+ * @cl: a pointer to a struct libscols_column instance
+ * @data: string
+ * @userdata: callback private data
+ *
+ * This is built-in function for scols_column_set_wrapfunc(). This function
+ * terminates the current chunk by \0 and returns pointer to the begin of
+ * the next chunk. The chunks are based on \n.
+ *
+ * For example for data "AAA\nBBB\nCCC" the next chunk is "BBB".
+ *
+ * Returns: next chunk
+ *
+ * Since: 2.29
+ */
+char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unused)),
+ char *data,
+ void *userdata __attribute__((unused)))
+{
+ char *p = data ? strchr(data, '\n') : NULL;
+
+ if (p) {
+ *p = '\0';
+ return p + 1;
+ }
+ return NULL;
+}
+
+/**
+ * scols_wrapnl_chunksize:
+ * @cl: a pointer to a struct libscols_column instance
+ * @data: string
+ * @userdata: callback private data
+ *
+ * Analyzes @data and returns size of the largest chunk. The chunks are based
+ * on \n. For example for data "AAA\nBBB\nCCCC" the largest chunk size is 4.
+ *
+ * Note that the size has to be based on number of terminal cells rather than
+ * bytes to support multu-byte output.
+ *
+ * Returns: size of the largest chunk.
+ *
+ * Since: 2.29
+ */
+size_t scols_wrapnl_chunksize(const struct libscols_column *cl __attribute__((unused)),
+ const char *data,
+ void *userdata __attribute__((unused)))
+{
+ size_t sum = 0;
+
+ while (data && *data) {
+ const char *p;
+ size_t sz;
+
+ p = strchr(data, '\n');
+ if (p) {
+ sz = cl->table && scols_table_is_noencoding(cl->table) ?
+ mbs_nwidth(data, p - data) :
+ mbs_safe_nwidth(data, p - data, NULL);
+ p++;
+ } else {
+ sz = cl->table && scols_table_is_noencoding(cl->table) ?
+ mbs_width(data) :
+ mbs_safe_width(data);
+ }
+ sum = max(sum, sz);
+ data = p;
+ }
+
+ return sum;
+}
+
+/**
+ * scols_column_set_cmpfunc:
+ * @cl: column
+ * @cmp: pointer to compare function
+ * @data: private data for cmp function
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_column_set_cmpfunc(struct libscols_column *cl,
+ int (*cmp)(struct libscols_cell *,
+ struct libscols_cell *,
+ void *),
+ void *data)
+{
+ if (!cl)
+ return -EINVAL;
+
+ cl->cmpfunc = cmp;
+ cl->cmpfunc_data = data;
+ return 0;
+}
+
+/**
+ * scols_column_set_wrapfunc:
+ * @cl: a pointer to a struct libscols_column instance
+ * @wrap_chunksize: function to return size of the largest chink of data
+ * @wrap_nextchunk: function to return next zero terminated data
+ * @userdata: optional stuff for callbacks
+ *
+ * Extends SCOLS_FL_WRAP and can be used to set custom wrap function. The default
+ * is to wrap by column size, but you can create functions to wrap for example
+ * after \n or after words, etc.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_column_set_wrapfunc(struct libscols_column *cl,
+ size_t (*wrap_chunksize)(const struct libscols_column *,
+ const char *,
+ void *),
+ char * (*wrap_nextchunk)(const struct libscols_column *,
+ char *,
+ void *),
+ void *userdata)
+{
+ if (!cl)
+ return -EINVAL;
+
+ cl->wrap_nextchunk = wrap_nextchunk;
+ cl->wrap_chunksize = wrap_chunksize;
+ cl->wrapfunc_data = userdata;
+ return 0;
+}
+
+/**
+ * scols_column_set_safechars:
+ * @cl: a pointer to a struct libscols_column instance
+ * @safe: safe characters (e.g. "\n\t")
+ *
+ * Use for bytes you don't want to encode on output. This is for example
+ * necessary if you want to use custom wrap function based on \n, in this case
+ * you have to set "\n" as a safe char.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_column_set_safechars(struct libscols_column *cl, const char *safe)
+{
+ return strdup_to_struct_member(cl, safechars, safe);
+}
+
+/**
+ * scols_column_get_safechars:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: safe chars
+ *
+ * Since: 2.29
+ */
+const char *scols_column_get_safechars(const struct libscols_column *cl)
+{
+ return cl->safechars;
+}
+
+/**
+ * scols_column_get_width:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Important note: the column width is unknown until library starts printing
+ * (width is calculated before printing). The function is usable for example in
+ * nextchunk() callback specified by scols_column_set_wrapfunc().
+ *
+ * See also scols_column_get_whint(), it returns wanted size (!= final size).
+ *
+ * Returns: column width
+ *
+ * Since: 2.29
+ */
+size_t scols_column_get_width(const struct libscols_column *cl)
+{
+ return cl->width;
+}
+
+/**
+ * scols_column_is_hidden:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag hidden.
+ *
+ * Returns: 0 or 1
+ *
+ * Since: 2.27
+ */
+int scols_column_is_hidden(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_HIDDEN ? 1 : 0;
+}
+
+/**
+ * scols_column_is_trunc:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag trunc.
+ *
+ * Returns: 0 or 1
+ */
+int scols_column_is_trunc(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_TRUNC ? 1 : 0;
+}
+/**
+ * scols_column_is_tree:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag tree.
+ *
+ * Returns: 0 or 1
+ */
+int scols_column_is_tree(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_TREE ? 1 : 0;
+}
+/**
+ * scols_column_is_right:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag right.
+ *
+ * Returns: 0 or 1
+ */
+int scols_column_is_right(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_RIGHT ? 1 : 0;
+}
+/**
+ * scols_column_is_strict_width:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag strict_width.
+ *
+ * Returns: 0 or 1
+ */
+int scols_column_is_strict_width(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_STRICTWIDTH ? 1 : 0;
+}
+/**
+ * scols_column_is_noextremes:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag no_extremes.
+ *
+ * Returns: 0 or 1
+ */
+int scols_column_is_noextremes(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_NOEXTREMES ? 1 : 0;
+}
+/**
+ * scols_column_is_wrap:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Gets the value of @cl's flag wrap.
+ *
+ * Returns: 0 or 1
+ *
+ * Since: 2.28
+ */
+int scols_column_is_wrap(const struct libscols_column *cl)
+{
+ return cl->flags & SCOLS_FL_WRAP ? 1 : 0;
+}
+/**
+ * scols_column_is_customwrap:
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Returns: 0 or 1
+ *
+ * Since: 2.29
+ */
+int scols_column_is_customwrap(const struct libscols_column *cl)
+{
+ return (cl->flags & SCOLS_FL_WRAP)
+ && cl->wrap_chunksize
+ && cl->wrap_nextchunk ? 1 : 0;
+}
diff --git a/libsmartcols/src/grouping.c b/libsmartcols/src/grouping.c
new file mode 100644
index 0000000..0b27cb2
--- /dev/null
+++ b/libsmartcols/src/grouping.c
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2018 Karel Zak <kzak@redhat.com>
+ */
+#include "smartcolsP.h"
+
+/**
+ * SECTION: grouping
+ * @title: Grouping
+ * @short_description: lines grouing
+ *
+ * Lines groups manipulation API. The grouping API can be used to create M:N
+ * relations between lines and on tree-like output it prints extra chart to
+ * visualize these relations. The group has unlimited number of members and
+ * group childs. See libsmartcols/sample/grouping* for more details.
+ */
+
+/* Private API */
+void scols_ref_group(struct libscols_group *gr)
+{
+ if (gr)
+ gr->refcount++;
+}
+
+void scols_group_remove_children(struct libscols_group *gr)
+{
+ if (!gr)
+ return;
+
+ while (!list_empty(&gr->gr_children)) {
+ struct libscols_line *ln = list_entry(gr->gr_children.next,
+ struct libscols_line, ln_children);
+
+ DBG(GROUP, ul_debugobj(gr, "remove child"));
+ list_del_init(&ln->ln_children);
+ scols_ref_group(ln->parent_group);
+ ln->parent_group = NULL;
+ scols_unref_line(ln);
+ }
+}
+
+void scols_group_remove_members(struct libscols_group *gr)
+{
+ if (!gr)
+ return;
+
+ while (!list_empty(&gr->gr_members)) {
+ struct libscols_line *ln = list_entry(gr->gr_members.next,
+ struct libscols_line, ln_groups);
+
+ DBG(GROUP, ul_debugobj(gr, "remove member [%p]", ln));
+ list_del_init(&ln->ln_groups);
+
+ scols_unref_group(ln->group);
+ ln->group->nmembers++;
+ ln->group = NULL;
+
+ scols_unref_line(ln);
+ }
+}
+
+/* note group has to be already without members to deallocate */
+void scols_unref_group(struct libscols_group *gr)
+{
+ if (gr && --gr->refcount <= 0) {
+ DBG(GROUP, ul_debugobj(gr, "dealloc"));
+ scols_group_remove_children(gr);
+ list_del(&gr->gr_groups);
+ free(gr);
+ return;
+ }
+}
+
+
+static void groups_fix_members_order(struct libscols_line *ln)
+{
+ struct libscols_iter itr;
+ struct libscols_line *child;
+
+ if (ln->group) {
+ INIT_LIST_HEAD(&ln->ln_groups);
+ list_add_tail(&ln->ln_groups, &ln->group->gr_members);
+ DBG(GROUP, ul_debugobj(ln->group, "fixing member line=%p [%zu/%zu]",
+ ln, ln->group->nmembers,
+ list_count_entries(&ln->group->gr_members)));
+ }
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_child(ln, &itr, &child) == 0)
+ groups_fix_members_order(child);
+
+ /*
+ * We modify gr_members list, so is_last_group_member() does not have
+ * to provide reliable answer, we need to verify by list_count_entries().
+ */
+ if (ln->group
+ && is_last_group_member(ln)
+ && ln->group->nmembers == list_count_entries(&ln->group->gr_members)) {
+
+ DBG(GROUP, ul_debugobj(ln->group, "fixing childs"));
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_group_child(ln, &itr, &child) == 0)
+ groups_fix_members_order(child);
+ }
+}
+
+void scols_groups_fix_members_order(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+ struct libscols_group *gr;
+
+ /* remove all from groups lists */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0) {
+ while (!list_empty(&gr->gr_members)) {
+ struct libscols_line *line = list_entry(gr->gr_members.next,
+ struct libscols_line, ln_groups);
+ list_del_init(&line->ln_groups);
+ }
+ }
+
+ /* add again to the groups list in order we walk in tree */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->parent || ln->parent_group)
+ continue;
+ groups_fix_members_order(ln);
+ }
+
+ /* If group child is member of another group *
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0) {
+ struct libscols_iter xitr;
+ struct libscols_line *child;
+
+ scols_reset_iter(&xitr, SCOLS_ITER_FORWARD);
+ while (scols_line_next_group_child(ln, &xitr, &child) == 0)
+ groups_fix_members_order(child);
+ }
+ */
+}
+
+static inline const char *group_state_to_string(int state)
+{
+ static const char *grpstates[] = {
+ [SCOLS_GSTATE_NONE] = "none",
+ [SCOLS_GSTATE_FIRST_MEMBER] = "1st-member",
+ [SCOLS_GSTATE_MIDDLE_MEMBER] = "middle-member",
+ [SCOLS_GSTATE_LAST_MEMBER] = "last-member",
+ [SCOLS_GSTATE_MIDDLE_CHILD] = "middle-child",
+ [SCOLS_GSTATE_LAST_CHILD] = "last-child",
+ [SCOLS_GSTATE_CONT_MEMBERS] = "continue-members",
+ [SCOLS_GSTATE_CONT_CHILDREN] = "continue-children"
+ };
+
+ assert(state >= 0);
+ assert((size_t) state < ARRAY_SIZE(grpstates));
+
+ return grpstates[state];
+}
+/*
+static void grpset_debug(struct libscols_table *tb, struct libscols_line *ln)
+{
+ size_t i;
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (tb->grpset[i]) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (ln)
+ DBG(LINE, ul_debugobj(ln, "grpset[%zu]: %p %s", i,
+ gr, group_state_to_string(gr->state)));
+ else
+ DBG(LINE, ul_debug("grpset[%zu]: %p %s", i,
+ gr, group_state_to_string(gr->state)));
+ } else if (ln) {
+ DBG(LINE, ul_debugobj(ln, "grpset[%zu]: free", i));
+ } else
+ DBG(LINE, ul_debug("grpset[%zu]: free", i));
+ }
+}
+*/
+static int group_state_for_line(struct libscols_group *gr, struct libscols_line *ln)
+{
+ if (gr->state == SCOLS_GSTATE_NONE &&
+ (ln->group != gr || !is_first_group_member(ln)))
+ /*
+ * NONE is possible to translate to FIRST_MEMBER only, and only if
+ * line group matches with the current group.
+ */
+ return SCOLS_GSTATE_NONE;
+
+ if (ln->group != gr && ln->parent_group != gr) {
+ /* Not our line, continue */
+ if (gr->state == SCOLS_GSTATE_FIRST_MEMBER ||
+ gr->state == SCOLS_GSTATE_MIDDLE_MEMBER ||
+ gr->state == SCOLS_GSTATE_CONT_MEMBERS)
+ return SCOLS_GSTATE_CONT_MEMBERS;
+
+ if (gr->state == SCOLS_GSTATE_LAST_MEMBER ||
+ gr->state == SCOLS_GSTATE_MIDDLE_CHILD ||
+ gr->state == SCOLS_GSTATE_CONT_CHILDREN)
+ return SCOLS_GSTATE_CONT_CHILDREN;
+
+ } else if (ln->group == gr && is_first_group_member(ln)) {
+ return SCOLS_GSTATE_FIRST_MEMBER;
+
+ } else if (ln->group == gr && is_last_group_member(ln)) {
+ return SCOLS_GSTATE_LAST_MEMBER;
+
+ } else if (ln->group == gr && is_group_member(ln)) {
+ return SCOLS_GSTATE_MIDDLE_MEMBER;
+
+ } else if (ln->parent_group == gr && is_last_group_child(ln)) {
+ return SCOLS_GSTATE_LAST_CHILD;
+
+ } else if (ln->parent_group == gr && is_group_child(ln)) {
+ return SCOLS_GSTATE_MIDDLE_CHILD;
+ }
+
+ return SCOLS_GSTATE_NONE;
+}
+
+/*
+ * apply new @state to the chunk (addressed by @xx) of grpset used for the group (@gr)
+ */
+static void grpset_apply_group_state(struct libscols_group **xx, int state, struct libscols_group *gr)
+{
+ size_t i;
+
+ DBG(GROUP, ul_debugobj(gr, " applying state to grpset"));
+
+ /* gr->state holds the old state, @state is the new state
+ */
+ for (i = 0; i < SCOLS_GRPSET_CHUNKSIZ; i++)
+ xx[i] = state == SCOLS_GSTATE_NONE ? NULL : gr;
+
+ gr->state = state;
+}
+
+static struct libscols_group **grpset_locate_freespace(struct libscols_table *tb, int chunks, int prepend)
+{
+ size_t i, avail = 0;
+ struct libscols_group **tmp, **first = NULL;
+ const size_t wanted = chunks * SCOLS_GRPSET_CHUNKSIZ;
+
+ if (!tb->grpset_size)
+ prepend = 0;
+ /*
+ DBG(TAB, ul_debugobj(tb, "orig grpset:"));
+ grpset_debug(tb, NULL);
+ */
+ if (prepend) {
+ for (i = tb->grpset_size - 1; ; i--) {
+ if (tb->grpset[i] == NULL) {
+ first = &tb->grpset[i];
+ avail++;
+ } else
+ avail = 0;
+ if (avail == wanted)
+ goto done;
+ if (i == 0)
+ break;
+ }
+ } else {
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (tb->grpset[i] == NULL) {
+ if (avail == 0)
+ first = &tb->grpset[i];
+ avail++;
+ } else
+ avail = 0;
+ if (avail == wanted)
+ goto done;
+ }
+ }
+
+ DBG(TAB, ul_debugobj(tb, " realocate grpset [sz: old=%zu, new=%zu, new_chunks=%d]",
+ tb->grpset_size, tb->grpset_size + wanted, chunks));
+
+ tmp = realloc(tb->grpset, (tb->grpset_size + wanted) * sizeof(struct libscols_group *));
+ if (!tmp)
+ return NULL;
+
+ tb->grpset = tmp;
+
+ if (prepend) {
+ DBG(TAB, ul_debugobj(tb, " prepending free space"));
+ char *dest = (char *) tb->grpset;
+
+ memmove( dest + (wanted * sizeof(struct libscols_group *)),
+ tb->grpset,
+ tb->grpset_size * sizeof(struct libscols_group *));
+ first = tb->grpset;
+ } else {
+ first = tb->grpset + tb->grpset_size;
+ }
+
+ memset(first, 0, wanted * sizeof(struct libscols_group *));
+ tb->grpset_size += wanted;
+
+done:
+ /*
+ DBG(TAB, ul_debugobj(tb, "new grpset:"));
+ grpset_debug(tb, NULL);
+ */
+ return first;
+}
+
+static struct libscols_group **grpset_locate_group(struct libscols_table *tb, struct libscols_group *gr)
+{
+ size_t i;
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ if (gr == tb->grpset[i])
+ return &tb->grpset[i];
+ }
+
+ return NULL;
+}
+
+
+static int grpset_update(struct libscols_table *tb, struct libscols_line *ln, struct libscols_group *gr)
+{
+ struct libscols_group **xx;
+ int state;
+
+ DBG(LINE, ul_debugobj(ln, " group [%p] grpset update [grpset size=%zu]", gr, tb->grpset_size));
+
+ /* new state, note that gr->state still holds the original state */
+ state = group_state_for_line(gr, ln);
+ DBG(LINE, ul_debugobj(ln, " state %s --> %s",
+ group_state_to_string(gr->state),
+ group_state_to_string(state)));
+
+ if (state == SCOLS_GSTATE_FIRST_MEMBER && gr->state != SCOLS_GSTATE_NONE) {
+ DBG(LINE, ul_debugobj(ln, "wrong group initialization (%s)", group_state_to_string(gr->state)));
+ abort();
+ }
+ if (state != SCOLS_GSTATE_NONE && gr->state == SCOLS_GSTATE_LAST_CHILD) {
+ DBG(LINE, ul_debugobj(ln, "wrong group termination (%s)", group_state_to_string(gr->state)));
+ abort();
+ }
+ if (gr->state == SCOLS_GSTATE_LAST_MEMBER &&
+ !(state == SCOLS_GSTATE_LAST_CHILD ||
+ state == SCOLS_GSTATE_CONT_CHILDREN ||
+ state == SCOLS_GSTATE_MIDDLE_CHILD ||
+ state == SCOLS_GSTATE_NONE)) {
+ DBG(LINE, ul_debugobj(ln, "wrong group member->child order"));
+ abort();
+ }
+
+ /* should not happen; probably wrong line... */
+ if (gr->state == SCOLS_GSTATE_NONE && state == SCOLS_GSTATE_NONE)
+ return 0;
+
+ /* locate place in grpset where we draw the group */
+ if (!tb->grpset || gr->state == SCOLS_GSTATE_NONE)
+ xx = grpset_locate_freespace(tb, 1, 1);
+ else
+ xx = grpset_locate_group(tb, gr);
+ if (!xx) {
+ DBG(LINE, ul_debugobj(ln, "failed to locate group or reallocate grpset"));
+ return -ENOMEM;
+ }
+
+ grpset_apply_group_state(xx, state, gr);
+ /*ON_DBG(LINE, grpset_debug(tb, ln));*/
+ return 0;
+}
+
+static int grpset_update_active_groups(struct libscols_table *tb, struct libscols_line *ln)
+{
+ int rc = 0;
+ size_t i;
+ struct libscols_group *last = NULL;
+
+ DBG(LINE, ul_debugobj(ln, " update for active groups"));
+
+ for (i = 0; i < tb->grpset_size; i++) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (!gr || last == gr)
+ continue;
+ last = gr;
+ rc = grpset_update(tb, ln, gr);
+ if (rc)
+ break;
+ }
+
+ DBG(LINE, ul_debugobj(ln, " <- active groups updated [rc=%d]", rc));
+ return rc;
+}
+
+int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln)
+{
+ int rc = 0;
+
+ DBG(LINE, ul_debugobj(ln, " grpset update [line: group=%p, parent_group=%p",
+ ln->group, ln->parent_group));
+
+ rc = grpset_update_active_groups(tb, ln);
+ if (!rc && ln->group && ln->group->state == SCOLS_GSTATE_NONE) {
+ DBG(LINE, ul_debugobj(ln, " introduce a new group"));
+ rc = grpset_update(tb, ln, ln->group);
+ }
+ return rc;
+}
+
+void scols_groups_reset_state(struct libscols_table *tb)
+{
+ struct libscols_iter itr;
+ struct libscols_group *gr;
+
+ DBG(TAB, ul_debugobj(tb, "reset groups states"));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_group(tb, &itr, &gr) == 0) {
+ DBG(GROUP, ul_debugobj(gr, " reset to NONE"));
+ gr->state = SCOLS_GSTATE_NONE;
+ }
+
+ if (tb->grpset) {
+ DBG(TAB, ul_debugobj(tb, " zeroize grpset"));
+ memset(tb->grpset, 0, tb->grpset_size * sizeof(struct libscols_group *));
+ }
+ tb->ngrpchlds_pending = 0;
+}
+
+static void add_member(struct libscols_group *gr, struct libscols_line *ln)
+{
+ DBG(GROUP, ul_debugobj(gr, "add member %p", ln));
+
+ ln->group = gr;
+ gr->nmembers++;
+ scols_ref_group(gr);
+
+ INIT_LIST_HEAD(&ln->ln_groups);
+ list_add_tail(&ln->ln_groups, &gr->gr_members);
+ scols_ref_line(ln);
+}
+
+/*
+ * Returns first group which is ready to print group children.
+ *
+ * This function scans grpset[] in backward order and returns first group
+ * with SCOLS_GSTATE_CONT_CHILDREN or SCOLS_GSTATE_LAST_MEMBER state.
+ */
+struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb)
+{
+ size_t i;
+
+ for (i = tb->grpset_size; i > 0; i -= SCOLS_GRPSET_CHUNKSIZ) {
+ struct libscols_group *gr = tb->grpset[i-1];
+
+ if (gr == NULL)
+ continue;
+ if (gr->state == SCOLS_GSTATE_CONT_CHILDREN ||
+ gr->state == SCOLS_GSTATE_LAST_MEMBER)
+ return gr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * scols_table_group_lines:
+ * @tb: a pointer to a struct libscols_table instance
+ * @ln: new group member
+ * @member: group member
+ * @id: group identifier (unused, not implemented yet), use zero.
+ *
+ * This function add line @ln to group of lines represented by @member. If the
+ * group is not yet defined (@member is not member of any group) than a new one
+ * is allocated.
+ *
+ * The @ln maybe a NULL -- in this case only a new group is allocated if not
+ * defined yet.
+ *
+ * Note that the same line cannot be member of more groups (not implemented
+ * yet). The child of any group can be member of another group.
+ *
+ * The @id is not used for now, use 0. The plan is to use it to support
+ * multi-group membership in future.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_table_group_lines( struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_line *member,
+ __attribute__((__unused__)) int id)
+{
+ struct libscols_group *gr = NULL;
+
+ if (!tb || !member) {
+ DBG(GROUP, ul_debugobj(gr, "failed group lines (no table or member)"));
+ return -EINVAL;
+ }
+ if (ln) {
+ if (ln->group && !member->group) {
+ DBG(GROUP, ul_debugobj(gr, "failed group lines (new group, line member of another)"));
+ return -EINVAL;
+ }
+ if (ln->group && member->group && ln->group != member->group) {
+ DBG(GROUP, ul_debugobj(gr, "failed group lines (groups mismatch bwteen member and line"));
+ return -EINVAL;
+ }
+ }
+
+ gr = member->group;
+
+ /* create a new group */
+ if (!gr) {
+ gr = calloc(1, sizeof(*gr));
+ if (!gr)
+ return -ENOMEM;
+ DBG(GROUP, ul_debugobj(gr, "alloc"));
+ gr->refcount = 1;
+ INIT_LIST_HEAD(&gr->gr_members);
+ INIT_LIST_HEAD(&gr->gr_children);
+ INIT_LIST_HEAD(&gr->gr_groups);
+
+ /* add group to the table */
+ list_add_tail(&gr->gr_groups, &tb->tb_groups);
+
+ /* add the first member */
+ add_member(gr, member);
+ }
+
+ /* add to group */
+ if (ln && !ln->group)
+ add_member(gr, ln);
+
+ return 0;
+}
+
+/**
+ * scols_line_link_group:
+ * @ln: line instance
+ * @member: group member
+ * @id: group identifier (unused, not implemented yet))
+ *
+ * Define @ln as child of group represented by group @member. The line @ln
+ * cannot be child of any other line. It's possible to create group->child or
+ * parent->child relationship, but no both for the same line (child).
+ *
+ * The @id is not used for now, use 0. The plan is to use it to support
+ * multi-group membership in future.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member,
+ __attribute__((__unused__)) int id)
+{
+ if (!ln || !member || !member->group || ln->parent)
+ return -EINVAL;
+
+ if (!list_empty(&ln->ln_children))
+ return -EINVAL;
+
+ DBG(GROUP, ul_debugobj(member->group, "add child"));
+
+ list_add_tail(&ln->ln_children, &member->group->gr_children);
+ scols_ref_line(ln);
+
+ ln->parent_group = member->group;
+ scols_ref_group(member->group);
+
+ return 0;
+}
diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c
new file mode 100644
index 0000000..dfd7510
--- /dev/null
+++ b/libsmartcols/src/init.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debugging
+ *
+ * The library debug stuff.
+ */
+
+#include <stdarg.h>
+
+#include "smartcolsP.h"
+
+UL_DEBUG_DEFINE_MASK(libsmartcols);
+UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) =
+{
+ { "all", SCOLS_DEBUG_ALL, "info about all subsystems" },
+ { "buff", SCOLS_DEBUG_BUFF, "output buffer utils" },
+ { "cell", SCOLS_DEBUG_CELL, "table cell utils" },
+ { "col", SCOLS_DEBUG_COL, "cols utils" },
+ { "help", SCOLS_DEBUG_HELP, "this help" },
+ { "group", SCOLS_DEBUG_GROUP, "lines grouping utils" },
+ { "line", SCOLS_DEBUG_LINE, "table line utils" },
+ { "tab", SCOLS_DEBUG_TAB, "table utils" },
+ { NULL, 0, NULL }
+};
+
+/**
+ * scols_init_debug:
+ * @mask: debug mask (0xffff to enable full debugging)
+ *
+ * If the @mask is not specified, then this function reads
+ * the LIBSMARTCOLS_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. Calling
+ * this function twice has no effect.
+ */
+void scols_init_debug(int mask)
+{
+ if (libsmartcols_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(libsmartcols, SCOLS_DEBUG_, mask, LIBSMARTCOLS_DEBUG);
+
+ if (libsmartcols_debug_mask != SCOLS_DEBUG_INIT
+ && libsmartcols_debug_mask != (SCOLS_DEBUG_HELP|SCOLS_DEBUG_INIT)) {
+ const char *ver = NULL;
+
+ scols_get_library_version(&ver);
+
+ DBG(INIT, ul_debug("library debug mask: 0x%04x", libsmartcols_debug_mask));
+ DBG(INIT, ul_debug("library version: %s", ver));
+ }
+ ON_DBG(HELP, ul_debug_print_masks("LIBSMARTCOLS_DEBUG",
+ UL_DEBUG_MASKNAMES(libsmartcols)));
+}
diff --git a/libsmartcols/src/iter.c b/libsmartcols/src/iter.c
new file mode 100644
index 0000000..91cc080
--- /dev/null
+++ b/libsmartcols/src/iter.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: iter
+ * @title: Iterator
+ * @short_description: unified iterator
+ *
+ * The iterator keeps the direction and the last position
+ * for access to the internal library tables/lists.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "smartcolsP.h"
+
+/**
+ * scols_new_iter:
+ * @direction: SCOLS_INTER_{FOR,BACK}WARD direction
+ *
+ * Returns: newly allocated generic libmount iterator.
+ */
+struct libscols_iter *scols_new_iter(int direction)
+{
+ struct libscols_iter *itr = calloc(1, sizeof(*itr));
+ if (!itr)
+ return NULL;
+ itr->direction = direction;
+ return itr;
+}
+
+/**
+ * scols_free_iter:
+ * @itr: iterator pointer
+ *
+ * Deallocates the iterator.
+ */
+void scols_free_iter(struct libscols_iter *itr)
+{
+ free(itr);
+}
+
+/**
+ * scols_reset_iter:
+ * @itr: iterator pointer
+ * @direction: SCOLS_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged
+ *
+ * Resets the iterator.
+ */
+void scols_reset_iter(struct libscols_iter *itr, int direction)
+{
+ if (direction == -1)
+ direction = itr->direction;
+
+ memset(itr, 0, sizeof(*itr));
+ itr->direction = direction;
+}
+
+/**
+ * scols_iter_get_direction:
+ * @itr: iterator pointer
+ *
+ * Returns: SCOLS_INTER_{FOR,BACK}WARD
+ */
+int scols_iter_get_direction(const struct libscols_iter *itr)
+{
+ return itr->direction;
+}
diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in
new file mode 100644
index 0000000..6b64bc8
--- /dev/null
+++ b/libsmartcols/src/libsmartcols.h.in
@@ -0,0 +1,346 @@
+/*
+ * Prints table or tree.
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef _LIBSMARTCOLS_H
+#define _LIBSMARTCOLS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+/**
+ * LIBSMARTCOLS_VERSION:
+ *
+ * Library version string
+ */
+#define LIBSMARTCOLS_VERSION "@LIBSMARTCOLS_VERSION@"
+
+/**
+ * libscols_iter:
+ *
+ * Generic iterator
+ */
+struct libscols_iter;
+
+/**
+ * libscols_symbols:
+ *
+ * Symbol groups for printing tree hierarchies
+ */
+struct libscols_symbols;
+
+/**
+ * libscols_cell:
+ *
+ * A cell - the smallest library object
+ */
+struct libscols_cell;
+
+/**
+ * libscols_line:
+ *
+ * A line - an array of cells
+ */
+struct libscols_line;
+
+/**
+ * libscols_table:
+ *
+ * A table - The most abstract object, encapsulating lines, columns, symbols and cells
+ */
+struct libscols_table;
+
+/**
+ * libscols_column:
+ *
+ * A column - defines the number of columns and column names
+ */
+struct libscols_column;
+
+/* iter.c */
+enum {
+
+ SCOLS_ITER_FORWARD = 0,
+ SCOLS_ITER_BACKWARD
+};
+
+/*
+ * Column flags
+ */
+enum {
+ SCOLS_FL_TRUNC = (1 << 0), /* truncate fields data if necessary */
+ SCOLS_FL_TREE = (1 << 1), /* use tree "ascii art" */
+ SCOLS_FL_RIGHT = (1 << 2), /* align to the right */
+ SCOLS_FL_STRICTWIDTH = (1 << 3), /* don't reduce width if column is empty */
+ SCOLS_FL_NOEXTREMES = (1 << 4), /* ignore extreme fields when count column width*/
+ SCOLS_FL_HIDDEN = (1 << 5), /* maintain data, but don't print */
+ SCOLS_FL_WRAP = (1 << 6) /* wrap long lines to multi-line cells */
+};
+
+/*
+ * Column JSON types
+ */
+enum {
+ SCOLS_JSON_STRING = 0, /* default */
+ SCOLS_JSON_NUMBER = 1,
+ SCOLS_JSON_BOOLEAN = 2,
+ SCOLS_JSON_ARRAY_STRING = 3, /* e.g. for multi-line (SCOLS_FL_WRAP) cells */
+ SCOLS_JSON_ARRAY_NUMBER = 4,
+ SCOLS_JSON_BOOLEAN_OPTIONAL = 5,
+};
+
+/*
+ * Cell flags, see scols_cell_set_flags() before use
+ */
+enum {
+ /* alignment evaluated in order: right,center,left */
+ SCOLS_CELL_FL_LEFT = 0,
+ SCOLS_CELL_FL_CENTER = (1 << 0),
+ SCOLS_CELL_FL_RIGHT = (1 << 1)
+};
+
+extern struct libscols_iter *scols_new_iter(int direction);
+extern void scols_free_iter(struct libscols_iter *itr);
+extern void scols_reset_iter(struct libscols_iter *itr, int direction);
+extern int scols_iter_get_direction(const struct libscols_iter *itr);
+
+/* init.c */
+extern void scols_init_debug(int mask);
+
+/* version.c */
+extern int scols_parse_version_string(const char *ver_string);
+extern int scols_get_library_version(const char **ver_string);
+
+/* symbols.c */
+extern struct libscols_symbols *scols_new_symbols(void);
+extern void scols_ref_symbols(struct libscols_symbols *sy);
+extern void scols_unref_symbols(struct libscols_symbols *sy);
+extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy);
+extern int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_right(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str);
+
+extern int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str);
+extern int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str);
+
+/* cell.c */
+extern int scols_reset_cell(struct libscols_cell *ce);
+extern int scols_cell_copy_content(struct libscols_cell *dest,
+ const struct libscols_cell *src);
+extern int scols_cell_set_data(struct libscols_cell *ce, const char *data);
+extern int scols_cell_refer_data(struct libscols_cell *ce, char *data);
+extern const char *scols_cell_get_data(const struct libscols_cell *ce);
+extern int scols_cell_set_color(struct libscols_cell *ce, const char *color);
+extern const char *scols_cell_get_color(const struct libscols_cell *ce);
+
+extern int scols_cell_set_flags(struct libscols_cell *ce, int flags);
+extern int scols_cell_get_flags(const struct libscols_cell *ce);
+extern int scols_cell_get_alignment(const struct libscols_cell *ce);
+
+extern void *scols_cell_get_userdata(struct libscols_cell *ce);
+extern int scols_cell_set_userdata(struct libscols_cell *ce, void *data);
+
+extern int scols_cmpstr_cells(struct libscols_cell *a,
+ struct libscols_cell *b, void *data);
+/* column.c */
+extern int scols_column_is_tree(const struct libscols_column *cl);
+extern int scols_column_is_trunc(const struct libscols_column *cl);
+extern int scols_column_is_right(const struct libscols_column *cl);
+extern int scols_column_is_strict_width(const struct libscols_column *cl);
+extern int scols_column_is_hidden(const struct libscols_column *cl);
+extern int scols_column_is_noextremes(const struct libscols_column *cl);
+extern int scols_column_is_wrap(const struct libscols_column *cl);
+extern int scols_column_is_customwrap(const struct libscols_column *cl);
+
+extern size_t scols_column_get_width(const struct libscols_column *cl);
+
+extern int scols_column_set_safechars(struct libscols_column *cl, const char *safe);
+extern const char *scols_column_get_safechars(const struct libscols_column *cl);
+
+extern int scols_column_set_json_type(struct libscols_column *cl, int type);
+extern int scols_column_get_json_type(const struct libscols_column *cl);
+
+extern int scols_column_set_flags(struct libscols_column *cl, int flags);
+extern int scols_column_get_flags(const struct libscols_column *cl);
+extern struct libscols_column *scols_new_column(void);
+extern void scols_ref_column(struct libscols_column *cl);
+extern void scols_unref_column(struct libscols_column *cl);
+extern struct libscols_column *scols_copy_column(const struct libscols_column *cl);
+extern int scols_column_set_whint(struct libscols_column *cl, double whint);
+extern double scols_column_get_whint(const struct libscols_column *cl);
+extern struct libscols_cell *scols_column_get_header(struct libscols_column *cl);
+extern int scols_column_set_color(struct libscols_column *cl, const char *color);
+extern const char *scols_column_get_color(const struct libscols_column *cl);
+extern struct libscols_table *scols_column_get_table(const struct libscols_column *cl);
+
+extern int scols_column_set_name(struct libscols_column *cl, const char *name);
+extern const char *scols_column_get_name(struct libscols_column *cl);
+extern const char *scols_column_get_name_as_shellvar(struct libscols_column *cl);
+
+extern int scols_column_set_cmpfunc(struct libscols_column *cl,
+ int (*cmp)(struct libscols_cell *a,
+ struct libscols_cell *b, void *),
+ void *data);
+
+extern int scols_column_set_wrapfunc(struct libscols_column *cl,
+ size_t (*wrap_chunksize)(const struct libscols_column *,
+ const char *, void *),
+ char * (*wrap_nextchunk)(const struct libscols_column *,
+ char *, void *),
+ void *userdata);
+
+extern char *scols_wrapnl_nextchunk(const struct libscols_column *cl, char *data, void *userdata);
+extern size_t scols_wrapnl_chunksize(const struct libscols_column *cl, const char *data, void *userdata);
+
+/* line.c */
+extern struct libscols_line *scols_new_line(void);
+extern void scols_ref_line(struct libscols_line *ln);
+extern void scols_unref_line(struct libscols_line *ln);
+extern int scols_line_alloc_cells(struct libscols_line *ln, size_t n);
+extern void scols_line_free_cells(struct libscols_line *ln);
+extern int scols_line_set_userdata(struct libscols_line *ln, void *data);
+extern void *scols_line_get_userdata(struct libscols_line *ln);
+extern int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child);
+extern int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child);
+extern int scols_line_has_children(struct libscols_line *ln);
+extern int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent);
+extern int scols_line_next_child(struct libscols_line *ln,
+ struct libscols_iter *itr, struct libscols_line **chld);
+extern struct libscols_line *scols_line_get_parent(const struct libscols_line *ln);
+extern int scols_line_set_color(struct libscols_line *ln, const char *color);
+extern const char *scols_line_get_color(const struct libscols_line *ln);
+extern size_t scols_line_get_ncells(const struct libscols_line *ln);
+extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n);
+extern struct libscols_cell *scols_line_get_column_cell(
+ struct libscols_line *ln,
+ struct libscols_column *cl);
+extern int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data);
+extern int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data);
+extern int scols_line_set_column_data(struct libscols_line *ln, struct libscols_column *cl, const char *data);
+extern const char *scols_line_get_column_data(struct libscols_line *ln, struct libscols_column *cl);
+extern int scols_line_refer_column_data(struct libscols_line *ln, struct libscols_column *cl, char *data);
+extern struct libscols_line *scols_copy_line(const struct libscols_line *ln);
+
+/* table */
+extern int scols_table_colors_wanted(const struct libscols_table *tb);
+extern int scols_table_set_name(struct libscols_table *tb, const char *name);
+extern const char *scols_table_get_name(const struct libscols_table *tb);
+extern struct libscols_cell *scols_table_get_title(struct libscols_table *tb);
+extern int scols_table_is_raw(const struct libscols_table *tb);
+extern int scols_table_is_ascii(const struct libscols_table *tb);
+extern int scols_table_is_json(const struct libscols_table *tb);
+extern int scols_table_is_noheadings(const struct libscols_table *tb);
+extern int scols_table_is_header_repeat(const struct libscols_table *tb);
+extern int scols_table_is_empty(const struct libscols_table *tb);
+extern int scols_table_is_export(const struct libscols_table *tb);
+extern int scols_table_is_shellvar(const struct libscols_table *tb);
+extern int scols_table_is_maxout(const struct libscols_table *tb);
+extern int scols_table_is_minout(const struct libscols_table *tb);
+extern int scols_table_is_nowrap(const struct libscols_table *tb);
+extern int scols_table_is_nolinesep(const struct libscols_table *tb);
+extern int scols_table_is_tree(const struct libscols_table *tb);
+extern int scols_table_is_noencoding(const struct libscols_table *tb);
+
+extern int scols_table_enable_colors(struct libscols_table *tb, int enable);
+extern int scols_table_enable_raw(struct libscols_table *tb, int enable);
+extern int scols_table_enable_ascii(struct libscols_table *tb, int enable);
+extern int scols_table_enable_json(struct libscols_table *tb, int enable);
+extern int scols_table_enable_noheadings(struct libscols_table *tb, int enable);
+extern int scols_table_enable_header_repeat(struct libscols_table *tb, int enable);
+extern int scols_table_enable_export(struct libscols_table *tb, int enable);
+extern int scols_table_enable_shellvar(struct libscols_table *tb, int enable);
+extern int scols_table_enable_maxout(struct libscols_table *tb, int enable);
+extern int scols_table_enable_minout(struct libscols_table *tb, int enable);
+extern int scols_table_enable_nowrap(struct libscols_table *tb, int enable);
+extern int scols_table_enable_nolinesep(struct libscols_table *tb, int enable);
+extern int scols_table_enable_noencoding(struct libscols_table *tb, int enable);
+
+extern int scols_table_set_column_separator(struct libscols_table *tb, const char *sep);
+extern int scols_table_set_line_separator(struct libscols_table *tb, const char *sep);
+
+extern struct libscols_table *scols_new_table(void);
+extern void scols_ref_table(struct libscols_table *tb);
+extern void scols_unref_table(struct libscols_table *tb);
+extern int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl);
+extern int scols_table_remove_column(struct libscols_table *tb, struct libscols_column *cl);
+extern int scols_table_remove_columns(struct libscols_table *tb);
+extern int scols_table_move_column(struct libscols_table *tb, struct libscols_column *pre, struct libscols_column *cl);
+extern struct libscols_column *scols_table_new_column(struct libscols_table *tb, const char *name, double whint, int flags);
+extern int scols_table_next_column(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column **cl);
+extern int scols_table_set_columns_iter(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column *cl);
+extern const char *scols_table_get_column_separator(const struct libscols_table *tb);
+extern const char *scols_table_get_line_separator(const struct libscols_table *tb);
+extern size_t scols_table_get_ncols(const struct libscols_table *tb);
+extern size_t scols_table_get_nlines(const struct libscols_table *tb);
+extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n);
+extern int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln);
+extern int scols_table_remove_line(struct libscols_table *tb, struct libscols_line *ln);
+extern void scols_table_remove_lines(struct libscols_table *tb);
+extern int scols_table_next_line(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_line **ln);
+extern struct libscols_line *scols_table_new_line(struct libscols_table *tb, struct libscols_line *parent);
+extern struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n);
+extern struct libscols_table *scols_copy_table(struct libscols_table *tb);
+extern int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy);
+extern int scols_table_set_default_symbols(struct libscols_table *tb);
+extern struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb);
+
+extern int scols_table_set_stream(struct libscols_table *tb, FILE *stream);
+extern FILE *scols_table_get_stream(const struct libscols_table *tb);
+extern int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce);
+
+extern int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl);
+extern int scols_sort_table_by_tree(struct libscols_table *tb);
+/*
+ *
+ */
+enum {
+ SCOLS_TERMFORCE_AUTO = 0,
+ SCOLS_TERMFORCE_NEVER,
+ SCOLS_TERMFORCE_ALWAYS
+};
+extern int scols_table_set_termforce(struct libscols_table *tb, int force);
+extern int scols_table_get_termforce(const struct libscols_table *tb);
+extern int scols_table_set_termwidth(struct libscols_table *tb, size_t width);
+extern size_t scols_table_get_termwidth(const struct libscols_table *tb);
+extern int scols_table_set_termheight(struct libscols_table *tb, size_t height);
+extern size_t scols_table_get_termheight(const struct libscols_table *tb);
+
+
+/* table_print.c */
+extern int scols_print_table(struct libscols_table *tb);
+extern int scols_print_table_to_string(struct libscols_table *tb, char **data);
+
+extern int scols_table_print_range( struct libscols_table *tb,
+ struct libscols_line *start,
+ struct libscols_line *end);
+extern int scols_table_print_range_to_string( struct libscols_table *tb,
+ struct libscols_line *start,
+ struct libscols_line *end,
+ char **data);
+
+/* grouping.c */
+int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, int id);
+int scols_table_group_lines(struct libscols_table *tb, struct libscols_line *ln,
+ struct libscols_line *member, int id);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBSMARTCOLS_H */
diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym
new file mode 100644
index 0000000..309535f
--- /dev/null
+++ b/libsmartcols/src/libsmartcols.sym
@@ -0,0 +1,211 @@
+/*
+ * symbols since util-linux 2.25
+ *
+ * Copyright (C) 2014-2016 Karel Zak <kzak@redhat.com>
+ */
+SMARTCOLS_2.25 {
+global:
+ scols_cell_copy_content;
+ scols_cell_get_color;
+ scols_cell_get_data;
+ scols_cell_get_userdata;
+ scols_cell_refer_data;
+ scols_cell_set_color;
+ scols_cell_set_data;
+ scols_cell_set_userdata;
+ scols_cmpstr_cells;
+ scols_column_get_color;
+ scols_column_get_flags;
+ scols_column_get_header;
+ scols_column_get_whint;
+ scols_column_is_noextremes;
+ scols_column_is_right;
+ scols_column_is_strict_width;
+ scols_column_is_tree;
+ scols_column_is_trunc;
+ scols_column_set_cmpfunc;
+ scols_column_set_color;
+ scols_column_set_flags;
+ scols_column_set_whint;
+ scols_copy_column;
+ scols_copy_line;
+ scols_copy_symbols;
+ scols_copy_table;
+ scols_free_iter;
+ scols_get_library_version;
+ scols_init_debug;
+ scols_iter_get_direction;
+ scols_line_add_child;
+ scols_line_alloc_cells;
+ scols_line_free_cells;
+ scols_line_get_cell;
+ scols_line_get_color;
+ scols_line_get_column_cell;
+ scols_line_get_ncells;
+ scols_line_get_parent;
+ scols_line_get_userdata;
+ scols_line_has_children;
+ scols_line_next_child;
+ scols_line_refer_data;
+ scols_line_remove_child;
+ scols_line_set_color;
+ scols_line_set_data;
+ scols_line_set_userdata;
+ scols_new_column;
+ scols_new_iter;
+ scols_new_line;
+ scols_new_symbols;
+ scols_new_table;
+ scols_parse_version_string;
+ scols_print_table;
+ scols_print_table_to_string;
+ scols_ref_column;
+ scols_ref_line;
+ scols_ref_symbols;
+ scols_ref_table;
+ scols_reset_cell;
+ scols_reset_iter;
+ scols_sort_table;
+ scols_symbols_set_branch;
+ scols_symbols_set_right;
+ scols_symbols_set_vertical;
+ scols_table_add_column;
+ scols_table_add_line;
+ scols_table_colors_wanted;
+ scols_table_enable_ascii;
+ scols_table_enable_colors;
+ scols_table_enable_export;
+ scols_table_enable_maxout;
+ scols_table_enable_noheadings;
+ scols_table_enable_raw;
+ scols_table_get_column;
+ scols_table_get_column_separator;
+ scols_table_get_line;
+ scols_table_get_line_separator;
+ scols_table_get_ncols;
+ scols_table_get_nlines;
+ scols_table_get_stream;
+ scols_table_is_ascii;
+ scols_table_is_empty;
+ scols_table_is_export;
+ scols_table_is_maxout;
+ scols_table_is_noheadings;
+ scols_table_is_raw;
+ scols_table_is_tree;
+ scols_table_new_column;
+ scols_table_new_line;
+ scols_table_next_column;
+ scols_table_next_line;
+ scols_table_reduce_termwidth;
+ scols_table_remove_column;
+ scols_table_remove_columns;
+ scols_table_remove_line;
+ scols_table_remove_lines;
+ scols_table_set_column_separator;
+ scols_table_set_line_separator;
+ scols_table_set_stream;
+ scols_table_set_symbols;
+ scols_unref_column;
+ scols_unref_line;
+ scols_unref_symbols;
+ scols_unref_table;
+local:
+ *;
+};
+
+SMARTCOLS_2.27 {
+global:
+ scols_column_is_hidden;
+ scols_table_enable_json;
+ scols_table_is_json;
+ scols_table_set_name;
+} SMARTCOLS_2.25;
+
+SMARTCOLS_2.28 {
+global:
+ scols_column_is_wrap;
+ scols_line_refer_column_data;
+ scols_line_set_column_data;
+ scols_symbols_set_title_padding;
+ scols_table_enable_nowrap;
+ scols_table_get_title;
+ scols_cell_get_flags;
+ scols_cell_set_flags;
+ scols_table_print_range;
+ scols_table_print_range_to_string;
+ scols_table_enable_nolinesep;
+} SMARTCOLS_2.27;
+
+SMARTCOLS_2.29 {
+global:
+ scols_column_get_safechars;
+ scols_column_get_table;
+ scols_column_get_width;
+ scols_column_is_customwrap;
+ scols_column_set_safechars;
+ scols_column_set_wrapfunc;
+ scols_symbols_set_cell_padding;
+ scols_table_get_name;
+ scols_table_get_symbols;
+ scols_table_get_termforce;
+ scols_table_get_termwidth;
+ scols_table_is_nolinesep;
+ scols_table_is_nowrap;
+ scols_table_set_default_symbols;
+ scols_table_set_termforce;
+ scols_table_set_termwidth;
+ scols_wrapnl_chunksize;
+ scols_wrapnl_nextchunk;
+} SMARTCOLS_2.28;
+
+
+SMARTCOLS_2.30 {
+global:
+ scols_cell_get_alignment;
+ scols_table_move_column;
+ scols_sort_table_by_tree;
+ scols_line_is_ancestor;
+} SMARTCOLS_2.29;
+
+
+SMARTCOLS_2.31 {
+ scols_table_set_termheight;
+ scols_table_get_termheight;
+ scols_table_is_header_repeat;
+ scols_table_enable_header_repeat;
+ scols_table_enable_noencoding;
+ scols_table_is_noencoding;
+} SMARTCOLS_2.30;
+
+
+SMARTCOLS_2.33 {
+ scols_column_set_json_type;
+ scols_column_get_json_type;
+} SMARTCOLS_2.31;
+
+SMARTCOLS_2.34 {
+ scols_table_group_lines;
+ scols_line_link_group;
+ scols_symbols_set_group_vertical;
+ scols_symbols_set_group_horizontal;
+ scols_symbols_set_group_first_member;
+ scols_symbols_set_group_last_member;
+ scols_symbols_set_group_middle_member;
+ scols_symbols_set_group_last_child;
+ scols_symbols_set_group_middle_child;
+} SMARTCOLS_2.33;
+
+SMARTCOLS_2.35 {
+ scols_table_enable_minout;
+ scols_table_is_minout;
+ scols_table_set_columns_iter;
+} SMARTCOLS_2.34;
+
+SMARTCOLS_2.38 {
+ scols_line_get_column_data;
+ scols_column_set_name;
+ scols_column_get_name;
+ scols_column_get_name_as_shellvar;
+ scols_table_is_shellvar;
+ scols_table_enable_shellvar;
+} SMARTCOLS_2.35;
diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c
new file mode 100644
index 0000000..a5d39b4
--- /dev/null
+++ b/libsmartcols/src/line.c
@@ -0,0 +1,560 @@
+/*
+ * line.c - functions for table handling at the line level
+ *
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: line
+ * @title: Line
+ * @short_description: cells container, also keeps tree (parent->child) information
+ *
+ * An API to access and modify per-line data and information.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "smartcolsP.h"
+
+/**
+ * scols_new_line:
+ *
+ * Note that the line is allocated without cells, the cells will be allocated
+ * later when you add the line to the table. If you want to use the line
+ * without table then you have to explicitly allocate the cells by
+ * scols_line_alloc_cells().
+ *
+ * Returns: a pointer to a new struct libscols_line instance.
+ */
+struct libscols_line *scols_new_line(void)
+{
+ struct libscols_line *ln;
+
+ ln = calloc(1, sizeof(*ln));
+ if (!ln)
+ return NULL;
+
+ DBG(LINE, ul_debugobj(ln, "alloc"));
+ ln->refcount = 1;
+ INIT_LIST_HEAD(&ln->ln_lines);
+ INIT_LIST_HEAD(&ln->ln_children);
+ INIT_LIST_HEAD(&ln->ln_branch);
+ INIT_LIST_HEAD(&ln->ln_groups);
+ return ln;
+}
+
+/**
+ * scols_ref_line:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Increases the refcount of @ln.
+ */
+void scols_ref_line(struct libscols_line *ln)
+{
+ if (ln)
+ ln->refcount++;
+}
+
+/**
+ * scols_unref_line:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Decreases the refcount of @ln. When the count falls to zero, the instance
+ * is automatically deallocated.
+ */
+void scols_unref_line(struct libscols_line *ln)
+{
+ if (ln && --ln->refcount <= 0) {
+ DBG(CELL, ul_debugobj(ln, "dealloc"));
+ list_del(&ln->ln_lines);
+ list_del(&ln->ln_children);
+ list_del(&ln->ln_groups);
+ scols_unref_group(ln->group);
+ scols_line_free_cells(ln);
+ free(ln->color);
+ free(ln);
+ return;
+ }
+}
+
+/**
+ * scols_line_free_cells:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Frees the allocated cells referenced to by @ln.
+ */
+void scols_line_free_cells(struct libscols_line *ln)
+{
+ size_t i;
+
+ if (!ln || !ln->cells)
+ return;
+
+ DBG(LINE, ul_debugobj(ln, "free cells"));
+
+ for (i = 0; i < ln->ncells; i++)
+ scols_reset_cell(&ln->cells[i]);
+
+ free(ln->cells);
+ ln->ncells = 0;
+ ln->cells = NULL;
+}
+
+/**
+ * scols_line_alloc_cells:
+ * @ln: a pointer to a struct libscols_line instance
+ * @n: the number of elements
+ *
+ * Allocates space for @n cells. This function is optional,
+ * and libsmartcols automatically allocates necessary cells
+ * according to number of columns in the table when you add
+ * the line to the table. See scols_table_add_line().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_alloc_cells(struct libscols_line *ln, size_t n)
+{
+ struct libscols_cell *ce;
+
+ if (!ln)
+ return -EINVAL;
+ if (ln->ncells == n)
+ return 0;
+
+ if (!n) {
+ scols_line_free_cells(ln);
+ return 0;
+ }
+
+ DBG(LINE, ul_debugobj(ln, "alloc %zu cells", n));
+
+ ce = realloc(ln->cells, n * sizeof(struct libscols_cell));
+ if (!ce)
+ return -errno;
+
+ if (n > ln->ncells)
+ memset(ce + ln->ncells, 0,
+ (n - ln->ncells) * sizeof(struct libscols_cell));
+
+ ln->cells = ce;
+ ln->ncells = n;
+ return 0;
+}
+
+int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn)
+{
+ struct libscols_cell ce;
+
+ if (!ln || newn >= ln->ncells || oldn >= ln->ncells)
+ return -EINVAL;
+ if (oldn == newn)
+ return 0;
+
+ DBG(LINE, ul_debugobj(ln, "move cells[%zu] -> cells[%zu]", oldn, newn));
+
+ /* remember data from old position */
+ memcpy(&ce, &ln->cells[oldn], sizeof(struct libscols_cell));
+
+ /* remove old position (move data behind oldn to oldn) */
+ if (oldn + 1 < ln->ncells)
+ memmove(ln->cells + oldn, ln->cells + oldn + 1,
+ (ln->ncells - oldn - 1) * sizeof(struct libscols_cell));
+
+ /* create a space for new position */
+ if (newn + 1 < ln->ncells)
+ memmove(ln->cells + newn + 1, ln->cells + newn,
+ (ln->ncells - newn - 1) * sizeof(struct libscols_cell));
+
+ /* copy original data to new position */
+ memcpy(&ln->cells[newn], &ce, sizeof(struct libscols_cell));
+ return 0;
+}
+
+/**
+ * scols_line_set_userdata:
+ * @ln: a pointer to a struct libscols_line instance
+ * @data: user data
+ *
+ * Binds @data to @ln.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_set_userdata(struct libscols_line *ln, void *data)
+{
+ if (!ln)
+ return -EINVAL;
+ ln->userdata = data;
+ return 0;
+}
+
+/**
+ * scols_line_get_userdata:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: user data
+ */
+void *scols_line_get_userdata(struct libscols_line *ln)
+{
+ return ln->userdata;
+}
+
+/**
+ * scols_line_remove_child:
+ * @ln: a pointer to a struct libscols_line instance
+ * @child: a pointer to a struct libscols_line instance
+ *
+ * Removes @child as a child of @ln.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child)
+{
+ if (!ln || !child)
+ return -EINVAL;
+
+ DBG(LINE, ul_debugobj(ln, "remove child"));
+
+ list_del_init(&child->ln_children);
+ child->parent = NULL;
+ scols_unref_line(child);
+
+ scols_unref_line(ln);
+ return 0;
+}
+
+/**
+ * scols_line_add_child:
+ * @ln: a pointer to a struct libscols_line instance
+ * @child: a pointer to a struct libscols_line instance
+ *
+ * Sets @child as a child of @ln.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child)
+{
+ if (!ln || !child)
+ return -EINVAL;
+
+ DBG(LINE, ul_debugobj(ln, "add child"));
+ scols_ref_line(child);
+ scols_ref_line(ln);
+
+ /* unref old<->parent */
+ if (child->parent)
+ scols_line_remove_child(child->parent, child);
+
+ /* new reference from parent to child */
+ list_add_tail(&child->ln_children, &ln->ln_branch);
+
+ /* new reference from child to parent */
+ child->parent = ln;
+ return 0;
+}
+
+/**
+ * scols_line_get_parent:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: a pointer to @ln's parent, NULL in case it has no parent or if there was an error.
+ */
+struct libscols_line *scols_line_get_parent(const struct libscols_line *ln)
+{
+ return ln ? ln->parent : NULL;
+}
+
+/**
+ * scols_line_has_children:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: 1 if @ln has any children, otherwise 0.
+ */
+int scols_line_has_children(struct libscols_line *ln)
+{
+ return ln ? !list_empty(&ln->ln_branch) : 0;
+}
+
+/**
+ * scols_line_next_child:
+ * @ln: a pointer to a struct libscols_line instance
+ * @itr: a pointer to a struct libscols_iter instance
+ * @chld: a pointer to a pointer to a struct libscols_line instance
+ *
+ * Finds the next child and returns a pointer to it via @chld.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_next_child(struct libscols_line *ln,
+ struct libscols_iter *itr,
+ struct libscols_line **chld)
+{
+ int rc = 1;
+
+ if (!ln || !itr || !chld)
+ return -EINVAL;
+ *chld = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &ln->ln_branch);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/* private API */
+int scols_line_next_group_child(struct libscols_line *ln,
+ struct libscols_iter *itr,
+ struct libscols_line **chld)
+{
+ int rc = 1;
+
+ if (!ln || !itr || !chld || !ln->group)
+ return -EINVAL;
+ *chld = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &ln->group->gr_children);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * scols_line_is_ancestor:
+ * @ln: line
+ * @parent: potential parent
+ *
+ * The function is designed to detect circular dependencies between @ln and
+ * @parent. It checks if @ln is not any (grand) parent in the @parent's tree.
+ *
+ * Since: 2.30
+ *
+ * Returns: 0 or 1
+ */
+int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent)
+{
+ while (parent) {
+ if (parent == ln)
+ return 1;
+ parent = scols_line_get_parent(parent);
+ };
+ return 0;
+}
+
+/**
+ * scols_line_set_color:
+ * @ln: a pointer to a struct libscols_line instance
+ * @color: color name or ESC sequence
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_set_color(struct libscols_line *ln, const char *color)
+{
+ if (color && isalnum(*color)) {
+ color = color_sequence_from_colorname(color);
+ if (!color)
+ return -EINVAL;
+ }
+ return strdup_to_struct_member(ln, color, color);
+}
+
+/**
+ * scols_line_get_color:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: @ln's color string, NULL in case of an error.
+ */
+const char *scols_line_get_color(const struct libscols_line *ln)
+{
+ return ln->color;
+}
+
+/**
+ * scols_line_get_ncells:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: number of cells
+ */
+size_t scols_line_get_ncells(const struct libscols_line *ln)
+{
+ return ln->ncells;
+}
+
+/**
+ * scols_line_get_cell:
+ * @ln: a pointer to a struct libscols_line instance
+ * @n: cell number to retrieve
+ *
+ * Returns: the @n-th cell in @ln, NULL in case of an error.
+ */
+struct libscols_cell *scols_line_get_cell(struct libscols_line *ln,
+ size_t n)
+{
+ if (!ln || n >= ln->ncells)
+ return NULL;
+ return &ln->cells[n];
+}
+
+/**
+ * scols_line_get_column_cell:
+ * @ln: a pointer to a struct libscols_line instance
+ * @cl: pointer to cell
+ *
+ * Like scols_line_get_cell() by cell is referenced by column.
+ *
+ * Returns: the @n-th cell in @ln, NULL in case of an error.
+ */
+struct libscols_cell *scols_line_get_column_cell(
+ struct libscols_line *ln,
+ struct libscols_column *cl)
+{
+ if (!ln || !cl)
+ return NULL;
+
+ return scols_line_get_cell(ln, cl->seqnum);
+}
+
+/**
+ * scols_line_set_data:
+ * @ln: a pointer to a struct libscols_line instance
+ * @n: number of the cell, whose data is to be set
+ * @data: actual data to set
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data)
+{
+ struct libscols_cell *ce = scols_line_get_cell(ln, n);
+
+ if (!ce)
+ return -EINVAL;
+ return scols_cell_set_data(ce, data);
+}
+
+/**
+ * scols_line_set_column_data:
+ * @ln: a pointer to a struct libscols_line instance
+ * @cl: column, whose data is to be set
+ * @data: actual data to set
+ *
+ * The same as scols_line_set_data() but cell is referenced by column object.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.28
+ */
+int scols_line_set_column_data(struct libscols_line *ln,
+ struct libscols_column *cl,
+ const char *data)
+{
+ return scols_line_set_data(ln, cl->seqnum, data);
+}
+
+/**
+ * scols_line_get_column_data:
+ * @ln: a pointer to a struct libscols_line instance
+ * @cl: column, whose data is to be get
+ *
+ * See also scols_cell_get_data()
+ *
+ * Returns: cell data or NULL.
+ *
+ * Since: 2.38
+ */
+const char *scols_line_get_column_data(struct libscols_line *ln,
+ struct libscols_column *cl)
+{
+ struct libscols_cell *cell = scols_line_get_column_cell(ln, cl);
+
+ return cell ? scols_cell_get_data(cell) : NULL;
+}
+
+
+/**
+ * scols_line_refer_data:
+ * @ln: a pointer to a struct libscols_line instance
+ * @n: number of the cell which will refer to @data
+ * @data: actual data to refer to
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data)
+{
+ struct libscols_cell *ce = scols_line_get_cell(ln, n);
+
+ if (!ce)
+ return -EINVAL;
+ return scols_cell_refer_data(ce, data);
+}
+
+/**
+ * scols_line_refer_column_data:
+ * @ln: a pointer to a struct libscols_line instance
+ * @cl: column, whose data is to be set
+ * @data: actual data to refer to
+ *
+ * The same as scols_line_refer_data() but cell is referenced by column object.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.28
+ */
+int scols_line_refer_column_data(struct libscols_line *ln,
+ struct libscols_column *cl,
+ char *data)
+{
+ return scols_line_refer_data(ln, cl->seqnum, data);
+}
+
+/**
+ * scols_copy_line:
+ * @ln: a pointer to a struct libscols_line instance
+ *
+ * Returns: A newly allocated copy of @ln, NULL in case of an error.
+ */
+struct libscols_line *scols_copy_line(const struct libscols_line *ln)
+{
+ struct libscols_line *ret;
+ size_t i;
+
+ if (!ln)
+ return NULL;
+
+ ret = scols_new_line();
+ if (!ret)
+ return NULL;
+ if (scols_line_set_color(ret, ln->color))
+ goto err;
+ if (scols_line_alloc_cells(ret, ln->ncells))
+ goto err;
+
+ ret->userdata = ln->userdata;
+ ret->ncells = ln->ncells;
+ ret->seqnum = ln->seqnum;
+
+ DBG(LINE, ul_debugobj(ln, "copy"));
+
+ for (i = 0; i < ret->ncells; ++i) {
+ if (scols_cell_copy_content(&ret->cells[i], &ln->cells[i]))
+ goto err;
+ }
+
+ return ret;
+err:
+ scols_unref_line(ret);
+ return NULL;
+}
diff --git a/libsmartcols/src/print-api.c b/libsmartcols/src/print-api.c
new file mode 100644
index 0000000..52b2664
--- /dev/null
+++ b/libsmartcols/src/print-api.c
@@ -0,0 +1,220 @@
+#include "smartcolsP.h"
+
+/**
+ * scola_table_print_range:
+ * @tb: table
+ * @start: first printed line or NULL to print from the begin of the table
+ * @end: last printed line or NULL to print all from start.
+ *
+ * If the start is the first line in the table than prints table header too.
+ * The header is printed only once. This does not work for trees.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_print_range( struct libscols_table *tb,
+ struct libscols_line *start,
+ struct libscols_line *end)
+{
+ struct ul_buffer buf = UL_INIT_BUFFER;
+ struct libscols_iter itr;
+ int rc;
+
+ if (scols_table_is_tree(tb))
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "printing range from API"));
+
+ rc = __scols_initialize_printing(tb, &buf);
+ if (rc)
+ return rc;
+
+ if (start) {
+ itr.direction = SCOLS_ITER_FORWARD;
+ itr.head = &tb->tb_lines;
+ itr.p = &start->ln_lines;
+ } else
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+
+ if (!start || itr.p == tb->tb_lines.next) {
+ rc = __scols_print_header(tb, &buf);
+ if (rc)
+ goto done;
+ }
+
+ rc = __scols_print_range(tb, &buf, &itr, end);
+done:
+ __scols_cleanup_printing(tb, &buf);
+ return rc;
+}
+
+/**
+ * scols_table_print_range_to_string:
+ * @tb: table
+ * @start: first printed line or NULL to print from the beginning of the table
+ * @end: last printed line or NULL to print all from start.
+ * @data: pointer to the beginning of a memory area to print to
+ *
+ * The same as scols_table_print_range(), but prints to @data instead of
+ * stream.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+#ifdef HAVE_OPEN_MEMSTREAM
+int scols_table_print_range_to_string( struct libscols_table *tb,
+ struct libscols_line *start,
+ struct libscols_line *end,
+ char **data)
+{
+ FILE *stream, *old_stream;
+ size_t sz;
+ int rc;
+
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "printing range to string"));
+
+ /* create a stream for output */
+ stream = open_memstream(data, &sz);
+ if (!stream)
+ return -ENOMEM;
+
+ old_stream = scols_table_get_stream(tb);
+ scols_table_set_stream(tb, stream);
+ rc = scols_table_print_range(tb, start, end);
+ fclose(stream);
+ scols_table_set_stream(tb, old_stream);
+
+ return rc;
+}
+#else
+int scols_table_print_range_to_string(
+ struct libscols_table *tb __attribute__((__unused__)),
+ struct libscols_line *start __attribute__((__unused__)),
+ struct libscols_line *end __attribute__((__unused__)),
+ char **data __attribute__((__unused__)))
+{
+ return -ENOSYS;
+}
+#endif
+
+static int do_print_table(struct libscols_table *tb, int *is_empty)
+{
+ int rc = 0;
+ struct ul_buffer buf = UL_INIT_BUFFER;
+
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "printing"));
+ if (is_empty)
+ *is_empty = 0;
+
+ if (list_empty(&tb->tb_columns)) {
+ DBG(TAB, ul_debugobj(tb, "error -- no columns"));
+ return -EINVAL;
+ }
+ if (list_empty(&tb->tb_lines)) {
+ DBG(TAB, ul_debugobj(tb, "ignore -- no lines"));
+ if (scols_table_is_json(tb)) {
+ ul_jsonwrt_init(&tb->json, tb->out, 0);
+ ul_jsonwrt_root_open(&tb->json);
+ ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : "");
+ ul_jsonwrt_array_close(&tb->json);
+ ul_jsonwrt_root_close(&tb->json);
+ } else if (is_empty)
+ *is_empty = 1;
+ return 0;
+ }
+
+ tb->header_printed = 0;
+ rc = __scols_initialize_printing(tb, &buf);
+ if (rc)
+ return rc;
+
+ if (scols_table_is_json(tb)) {
+ ul_jsonwrt_root_open(&tb->json);
+ ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : "");
+ }
+
+ if (tb->format == SCOLS_FMT_HUMAN)
+ __scols_print_title(tb);
+
+ rc = __scols_print_header(tb, &buf);
+ if (rc)
+ goto done;
+
+ if (scols_table_is_tree(tb))
+ rc = __scols_print_tree(tb, &buf);
+ else
+ rc = __scols_print_table(tb, &buf);
+
+ if (scols_table_is_json(tb)) {
+ ul_jsonwrt_array_close(&tb->json);
+ ul_jsonwrt_root_close(&tb->json);
+ }
+done:
+ __scols_cleanup_printing(tb, &buf);
+ return rc;
+}
+
+/**
+ * scols_print_table:
+ * @tb: table
+ *
+ * Prints the table to the output stream and terminate by \n.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_print_table(struct libscols_table *tb)
+{
+ int empty = 0;
+ int rc = do_print_table(tb, &empty);
+
+ if (rc == 0 && !empty && !scols_table_is_json(tb))
+ fputc('\n', tb->out);
+ return rc;
+}
+
+/**
+ * scols_print_table_to_string:
+ * @tb: table
+ * @data: pointer to the beginning of a memory area to print to
+ *
+ * Prints the table to @data.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+#ifdef HAVE_OPEN_MEMSTREAM
+int scols_print_table_to_string(struct libscols_table *tb, char **data)
+{
+ FILE *stream, *old_stream;
+ size_t sz;
+ int rc;
+
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "printing to string"));
+
+ /* create a stream for output */
+ stream = open_memstream(data, &sz);
+ if (!stream)
+ return -ENOMEM;
+
+ old_stream = scols_table_get_stream(tb);
+ scols_table_set_stream(tb, stream);
+ rc = do_print_table(tb, NULL);
+ fclose(stream);
+ scols_table_set_stream(tb, old_stream);
+
+ return rc;
+}
+#else
+int scols_print_table_to_string(
+ struct libscols_table *tb __attribute__((__unused__)),
+ char **data __attribute__((__unused__)))
+{
+ return -ENOSYS;
+}
+#endif
diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c
new file mode 100644
index 0000000..906df7e
--- /dev/null
+++ b/libsmartcols/src/print.c
@@ -0,0 +1,1243 @@
+ /* print.c - functions to print table
+ *
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: table_print
+ * @title: Table print
+ * @short_description: output functions
+ *
+ * Table output API.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#include "mbsalign.h"
+#include "carefulputc.h"
+#include "smartcolsP.h"
+
+/* Fallback for symbols
+ *
+ * Note that by default library define all the symbols, but in case user does
+ * not define all symbols or if we extended the symbols struct then we need
+ * fallback to be more robust and backwardly compatible.
+ */
+#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ")
+#define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-")
+#define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ")
+#define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-")
+
+#define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|")
+#define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-")
+#define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->")
+#define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->")
+#define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->")
+#define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-")
+#define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-")
+
+#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \
+ ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " "))
+
+#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used)
+
+static int is_next_columns_empty(
+ struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln)
+{
+ struct libscols_iter itr;
+
+ if (!tb || !cl)
+ return 0;
+ if (is_last_column(cl))
+ return 1;
+ if (!ln)
+ return 0;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ scols_table_set_columns_iter(tb, &itr, cl);
+
+ /* skip current column */
+ scols_table_next_column(tb, &itr, &cl);
+
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ struct libscols_cell *ce;
+ const char *data = NULL;
+
+ if (scols_column_is_hidden(cl))
+ continue;
+ if (scols_column_is_tree(cl))
+ return 0;
+
+ ce = scols_line_get_cell(ln, cl->seqnum);
+ if (ce)
+ data = scols_cell_get_data(ce);
+ if (data && *data)
+ return 0;
+ }
+ return 1;
+}
+
+/* returns pointer to the end of used data */
+static int tree_ascii_art_to_buffer(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct ul_buffer *buf)
+{
+ const char *art;
+ int rc;
+
+ assert(ln);
+ assert(buf);
+
+ if (!ln->parent)
+ return 0;
+
+ rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
+ if (rc)
+ return rc;
+
+ if (is_last_child(ln))
+ art = " ";
+ else
+ art = vertical_symbol(tb);
+
+ return ul_buffer_append_string(buf, art);
+}
+
+static int grpset_is_empty( struct libscols_table *tb,
+ size_t idx,
+ size_t *rest)
+{
+ size_t i;
+
+ for (i = idx; i < tb->grpset_size; i++) {
+ if (tb->grpset[i] == NULL) {
+ if (rest)
+ (*rest)++;
+ } else
+ return 0;
+ }
+ return 1;
+}
+
+static int groups_ascii_art_to_buffer( struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct ul_buffer *buf,
+ int empty)
+{
+ int filled = 0;
+ size_t i, rest = 0;
+ const char *filler = cellpadding_symbol(tb);
+
+ if (!has_groups(tb))
+ return 0;
+
+ DBG(LINE, ul_debugobj(ln, "printing groups chart"));
+
+ if (tb->is_dummy_print)
+ return 0; /* allocate grpset[] only */
+
+ for (i = 0; i < tb->grpset_size; i += SCOLS_GRPSET_CHUNKSIZ) {
+ struct libscols_group *gr = tb->grpset[i];
+
+ if (!gr) {
+ ul_buffer_append_ntimes(buf, SCOLS_GRPSET_CHUNKSIZ, cellpadding_symbol(tb));
+ continue;
+ }
+
+ /*
+ * Empty cells (multi-line entries, etc.), print vertical symbols only
+ * to show that the group continues.
+ */
+ if (empty) {
+ switch (gr->state) {
+ case SCOLS_GSTATE_FIRST_MEMBER:
+ case SCOLS_GSTATE_MIDDLE_MEMBER:
+ case SCOLS_GSTATE_CONT_MEMBERS:
+ ul_buffer_append_string(buf, grp_vertical_symbol(tb));
+ ul_buffer_append_ntimes(buf, 2, filler);
+ break;
+
+ case SCOLS_GSTATE_LAST_MEMBER:
+ case SCOLS_GSTATE_MIDDLE_CHILD:
+ case SCOLS_GSTATE_CONT_CHILDREN:
+ ul_buffer_append_string(buf, filler);
+ ul_buffer_append_string(buf, grp_vertical_symbol(tb));
+ ul_buffer_append_string(buf, filler);
+ break;
+ case SCOLS_GSTATE_LAST_CHILD:
+ ul_buffer_append_ntimes(buf, 3, filler);
+ break;
+ }
+ continue;
+ }
+
+ /*
+ * Regular cell
+ */
+ switch (gr->state) {
+ case SCOLS_GSTATE_FIRST_MEMBER:
+ ul_buffer_append_string(buf, grp_m_first_symbol(tb));
+ break;
+ case SCOLS_GSTATE_MIDDLE_MEMBER:
+ ul_buffer_append_string(buf, grp_m_middle_symbol(tb));
+ break;
+ case SCOLS_GSTATE_LAST_MEMBER:
+ ul_buffer_append_string(buf, grp_m_last_symbol(tb));
+ break;
+ case SCOLS_GSTATE_CONT_MEMBERS:
+ ul_buffer_append_string(buf, grp_vertical_symbol(tb));
+ ul_buffer_append_ntimes(buf, 2, filler);
+ break;
+ case SCOLS_GSTATE_MIDDLE_CHILD:
+ ul_buffer_append_string(buf, filler);
+ ul_buffer_append_string(buf, grp_c_middle_symbol(tb));
+ if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) {
+ ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
+ filled = 1;
+ }
+ filler = grp_horizontal_symbol(tb);
+ break;
+ case SCOLS_GSTATE_LAST_CHILD:
+ ul_buffer_append_string(buf, cellpadding_symbol(tb));
+ ul_buffer_append_string(buf, grp_c_last_symbol(tb));
+ if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) {
+ ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
+ filled = 1;
+ }
+ filler = grp_horizontal_symbol(tb);
+ break;
+ case SCOLS_GSTATE_CONT_CHILDREN:
+ ul_buffer_append_string(buf, filler);
+ ul_buffer_append_string(buf, grp_vertical_symbol(tb));
+ ul_buffer_append_string(buf, filler);
+ break;
+ }
+
+ if (filled)
+ break;
+ }
+
+ if (!filled)
+ ul_buffer_append_string(buf, filler);
+ return 0;
+}
+
+static int has_pending_data(struct libscols_table *tb)
+{
+ struct libscols_column *cl;
+ struct libscols_iter itr;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+ if (cl->pending_data)
+ return 1;
+ }
+ return 0;
+}
+
+static void fputs_color_reset(struct libscols_table *tb)
+{
+ if (tb->cur_color) {
+ fputs(UL_COLOR_RESET, tb->out);
+ tb->cur_color = NULL;
+ }
+}
+
+static void fputs_color(struct libscols_table *tb, const char *color)
+{
+ if (tb->cur_color)
+ fputs_color_reset(tb);
+
+ tb->cur_color = color;
+ if (color)
+ fputs(color, tb->out);
+}
+
+static const char *get_cell_color(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln,
+ struct libscols_cell *ce)
+{
+ const char *color = NULL;
+
+ if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
+ return NULL;
+ if (ce)
+ color = ce->color;
+ if (!color && (!ln || !ln->color) && cl)
+ color = cl->color;
+ return color;
+}
+
+/* switch from line color to cell/column color */
+static void fputs_color_cell_open(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln,
+ struct libscols_cell *ce)
+{
+ const char *color = get_cell_color(tb, cl, ln, ce);
+
+ if (color)
+ fputs_color(tb, color);
+}
+
+/* switch from cell/column color to line color or reset */
+static void fputs_color_cell_close(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln,
+ struct libscols_cell *ce)
+{
+ const char *color = get_cell_color(tb, cl, ln, ce);
+
+ if (color)
+ fputs_color(tb, ln ? ln->color : NULL);
+}
+
+/* switch to line color */
+static void fputs_color_line_open(struct libscols_table *tb,
+ struct libscols_line *ln)
+{
+ if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
+ return;
+ fputs_color(tb, ln ? ln->color : NULL);
+}
+
+/* switch off all colors */
+static void fputs_color_line_close(struct libscols_table *tb)
+{
+ if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
+ return;
+ fputs_color_reset(tb);
+}
+
+/* print padding or ASCII-art instead of data of @cl */
+static void print_empty_cell(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln, /* optional */
+ struct libscols_cell *ce,
+ size_t bufsz)
+{
+ size_t len_pad = 0; /* in screen cells as opposed to bytes */
+
+ DBG(COL, ul_debugobj(cl, " printing empty cell"));
+
+ fputs_color_cell_open(tb, cl, ln, ce);
+
+ /* generate tree/group ASCII-art rather than padding
+ */
+ if (ln && scols_column_is_tree(cl)) {
+ struct ul_buffer art = UL_INIT_BUFFER;
+ char *data;
+
+ if (ul_buffer_alloc_data(&art, bufsz) != 0)
+ goto done;
+
+ if (cl->is_groups)
+ groups_ascii_art_to_buffer(tb, ln, &art, 1);
+
+ tree_ascii_art_to_buffer(tb, ln, &art);
+
+ if (!list_empty(&ln->ln_branch) && has_pending_data(tb))
+ ul_buffer_append_string(&art, vertical_symbol(tb));
+
+ if (scols_table_is_noencoding(tb))
+ data = ul_buffer_get_data(&art, NULL, &len_pad);
+ else
+ data = ul_buffer_get_safe_data(&art, NULL, &len_pad, NULL);
+
+ if (data && len_pad)
+ fputs(data, tb->out);
+ ul_buffer_free_data(&art);
+ }
+
+done:
+ /* minout -- don't fill */
+ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return;
+ }
+
+ /* default -- fill except last column */
+ if (!scols_table_is_maxout(tb) && is_last_column(cl)) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return;
+ }
+
+ /* fill rest of cell with space */
+ for(; len_pad < cl->width; ++len_pad)
+ fputs(cellpadding_symbol(tb), tb->out);
+
+ fputs_color_cell_close(tb, cl, ln, ce);
+
+ if (!is_last_column(cl))
+ fputs(colsep(tb), tb->out);
+}
+
+
+
+/* Fill the start of a line with padding (or with tree ascii-art).
+ *
+ * This is necessary after a long non-truncated column, as this requires the
+ * next column to be printed on the next line. For example (see 'DDD'):
+ *
+ * aaa bbb ccc ddd eee
+ * AAA BBB CCCCCCC
+ * DDD EEE
+ * ^^^^^^^^^^^^
+ * new line padding
+ */
+static void print_newline_padding(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln, /* optional */
+ struct libscols_cell *ce,
+ size_t bufsz)
+{
+ size_t i;
+
+ assert(tb);
+ assert(cl);
+
+ DBG(LINE, ul_debugobj(ln, "printing newline padding"));
+
+ fputs(linesep(tb), tb->out); /* line break */
+ tb->termlines_used++;
+
+ fputs_color_line_open(tb, ln);
+
+ /* fill cells after line break */
+ for (i = 0; i <= (size_t) cl->seqnum; i++)
+ print_empty_cell(tb, scols_table_get_column(tb, i), ln, ce, bufsz);
+
+ fputs_color_line_close(tb);
+}
+
+/*
+ * Pending data
+ *
+ * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is
+ * printed as usually and output is truncated to match column width.
+ *
+ * The rest of the long text is printed on next extra line(s). The extra lines
+ * don't exist in the table (not represented by libscols_line). The data for
+ * the extra lines are stored in libscols_column->pending_data_buf and the
+ * function print_line() adds extra lines until the buffer is not empty in all
+ * columns.
+ */
+
+/* set data that will be printed by extra lines */
+static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz)
+{
+ char *p = NULL;
+
+ if (data && *data) {
+ DBG(COL, ul_debugobj(cl, "setting pending data"));
+ assert(sz);
+ p = strdup(data);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ free(cl->pending_data_buf);
+ cl->pending_data_buf = p;
+ cl->pending_data_sz = sz;
+ cl->pending_data = cl->pending_data_buf;
+ return 0;
+}
+
+/* the next extra line has been printed, move pending data cursor */
+static int step_pending_data(struct libscols_column *cl, size_t bytes)
+{
+ DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes));
+
+ if (bytes >= cl->pending_data_sz)
+ return set_pending_data(cl, NULL, 0);
+
+ cl->pending_data += bytes;
+ cl->pending_data_sz -= bytes;
+ return 0;
+}
+
+/* print next pending data for the column @cl */
+static int print_pending_data(
+ struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln, /* optional */
+ struct libscols_cell *ce)
+{
+ size_t width = cl->width, bytes;
+ size_t len = width, i;
+ char *data;
+ char *nextchunk = NULL;
+
+ if (!cl->pending_data)
+ return 0;
+ if (!width)
+ return -EINVAL;
+
+ DBG(COL, ul_debugobj(cl, "printing pending data"));
+
+ data = strdup(cl->pending_data);
+ if (!data)
+ goto err;
+
+ if (scols_column_is_customwrap(cl)
+ && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) {
+ bytes = nextchunk - data;
+
+ len = scols_table_is_noencoding(tb) ?
+ mbs_nwidth(data, bytes) :
+ mbs_safe_nwidth(data, bytes, NULL);
+ } else
+ bytes = mbs_truncate(data, &len);
+
+ if (bytes == (size_t) -1)
+ goto err;
+
+ if (bytes)
+ step_pending_data(cl, bytes);
+
+ fputs_color_cell_open(tb, cl, ln, ce);
+
+ fputs(data, tb->out);
+ free(data);
+
+ /* minout -- don't fill */
+ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return 0;
+ }
+
+ /* default -- fill except last column */
+ if (!scols_table_is_maxout(tb) && is_last_column(cl)) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return 0;
+ }
+
+ /* fill rest of cell with space */
+ for(i = len; i < width; i++)
+ fputs(cellpadding_symbol(tb), tb->out);
+
+ fputs_color_cell_close(tb, cl, ln, ce);
+
+ if (!is_last_column(cl))
+ fputs(colsep(tb), tb->out);
+
+ return 0;
+err:
+ free(data);
+ return -errno;
+}
+
+static void print_json_data(struct libscols_table *tb,
+ struct libscols_column *cl,
+ const char *name,
+ char *data)
+{
+ switch (cl->json_type) {
+ case SCOLS_JSON_STRING:
+ /* name: "aaa" */
+ ul_jsonwrt_value_s(&tb->json, name, data);
+ break;
+ case SCOLS_JSON_NUMBER:
+ /* name: 123 */
+ ul_jsonwrt_value_raw(&tb->json, name, data);
+ break;
+ case SCOLS_JSON_BOOLEAN:
+ case SCOLS_JSON_BOOLEAN_OPTIONAL:
+ /* name: true|false|null */
+ if (cl->json_type == SCOLS_JSON_BOOLEAN_OPTIONAL && (!*data || !strcmp(data, "-"))) {
+ ul_jsonwrt_value_null(&tb->json, name);
+ } else {
+ ul_jsonwrt_value_boolean(&tb->json, name,
+ !*data ? 0 :
+ *data == '0' ? 0 :
+ *data == 'N' || *data == 'n' ? 0 : 1);
+ }
+ break;
+ case SCOLS_JSON_ARRAY_STRING:
+ case SCOLS_JSON_ARRAY_NUMBER:
+ /* name: [ "aaa", "bbb", "ccc" ] */
+ ul_jsonwrt_array_open(&tb->json, name);
+
+ if (!scols_column_is_customwrap(cl))
+ ul_jsonwrt_value_s(&tb->json, NULL, data);
+ else do {
+ char *next = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data);
+
+ if (cl->json_type == SCOLS_JSON_ARRAY_STRING)
+ ul_jsonwrt_value_s(&tb->json, NULL, data);
+ else
+ ul_jsonwrt_value_raw(&tb->json, NULL, data);
+ data = next;
+ } while (data);
+
+ ul_jsonwrt_array_close(&tb->json);
+ break;
+ }
+}
+
+static int print_data(struct libscols_table *tb,
+ struct libscols_column *cl,
+ struct libscols_line *ln, /* optional */
+ struct libscols_cell *ce, /* optional */
+ struct ul_buffer *buf)
+{
+ size_t len = 0, i, width, bytes;
+ char *data, *nextchunk;
+ const char *name = NULL;
+ int is_last;
+
+ assert(tb);
+ assert(cl);
+
+ data = ul_buffer_get_data(buf, NULL, NULL);
+ if (!data)
+ data = "";
+
+ if (tb->format != SCOLS_FMT_HUMAN) {
+ name = scols_table_is_shellvar(tb) ?
+ scols_column_get_name_as_shellvar(cl) :
+ scols_column_get_name(cl);
+ }
+
+ is_last = is_last_column(cl);
+
+ if (is_last && scols_table_is_json(tb) &&
+ scols_table_is_tree(tb) && has_children(ln))
+ /* "children": [] is the real last value */
+ is_last = 0;
+
+ switch (tb->format) {
+ case SCOLS_FMT_RAW:
+ fputs_nonblank(data, tb->out);
+ if (!is_last)
+ fputs(colsep(tb), tb->out);
+ return 0;
+
+ case SCOLS_FMT_EXPORT:
+ fputs(name, tb->out);
+ fputc('=', tb->out);
+ fputs_quoted(data, tb->out);
+ if (!is_last)
+ fputs(colsep(tb), tb->out);
+ return 0;
+
+ case SCOLS_FMT_JSON:
+ print_json_data(tb, cl, name, data);
+ return 0;
+
+ case SCOLS_FMT_HUMAN:
+ break; /* continue below */
+ }
+
+ /* Encode. Note that 'len' and 'width' are number of cells, not bytes.
+ */
+ if (scols_table_is_noencoding(tb))
+ data = ul_buffer_get_data(buf, &bytes, &len);
+ else
+ data = ul_buffer_get_safe_data(buf, &bytes, &len, scols_column_get_safechars(cl));
+
+ if (!data)
+ data = "";
+ width = cl->width;
+
+ /* custom multi-line cell based */
+ if (*data && scols_column_is_customwrap(cl)
+ && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) {
+ set_pending_data(cl, nextchunk, bytes - (nextchunk - data));
+ bytes = nextchunk - data;
+
+ len = scols_table_is_noencoding(tb) ?
+ mbs_nwidth(data, bytes) :
+ mbs_safe_nwidth(data, bytes, NULL);
+ }
+
+ if (is_last
+ && len < width
+ && !scols_table_is_maxout(tb)
+ && !scols_column_is_right(cl))
+ width = len;
+
+ /* truncate data */
+ if (len > width && scols_column_is_trunc(cl)) {
+ len = width;
+ bytes = mbs_truncate(data, &len); /* updates 'len' */
+ }
+
+ /* standard multi-line cell */
+ if (len > width && scols_column_is_wrap(cl)
+ && !scols_column_is_customwrap(cl)) {
+ set_pending_data(cl, data, bytes);
+
+ len = width;
+ bytes = mbs_truncate(data, &len);
+ if (bytes != (size_t) -1 && bytes > 0)
+ step_pending_data(cl, bytes);
+ }
+
+ if (bytes == (size_t) -1) {
+ bytes = len = 0;
+ data = NULL;
+ }
+
+ fputs_color_cell_open(tb, cl, ln, ce);
+
+ if (data && *data) {
+ if (scols_column_is_right(cl)) {
+ for (i = len; i < width; i++)
+ fputs(cellpadding_symbol(tb), tb->out);
+ len = width;
+ }
+ fputs(data, tb->out);
+
+ }
+
+ /* minout -- don't fill */
+ if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return 0;
+ }
+
+ /* default -- fill except last column */
+ if (!scols_table_is_maxout(tb) && is_last) {
+ fputs_color_cell_close(tb, cl, ln, ce);
+ return 0;
+ }
+
+ /* fill rest of cell with space */
+ for(i = len; i < width; i++)
+ fputs(cellpadding_symbol(tb), tb->out);
+
+ fputs_color_cell_close(tb, cl, ln, ce);
+
+ if (len > width && !scols_column_is_trunc(cl)) {
+ DBG(COL, ul_debugobj(cl, "*** data len=%zu > column width=%zu", len, width));
+ print_newline_padding(tb, cl, ln, ce, ul_buffer_get_bufsiz(buf)); /* next column starts on next line */
+
+ } else if (!is_last)
+ fputs(colsep(tb), tb->out); /* columns separator */
+
+ return 0;
+}
+
+int __cell_to_buffer(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl,
+ struct ul_buffer *buf)
+{
+ const char *data;
+ struct libscols_cell *ce;
+ int rc = 0;
+
+ assert(tb);
+ assert(ln);
+ assert(cl);
+ assert(buf);
+ assert(cl->seqnum <= tb->ncols);
+
+ ul_buffer_reset_data(buf);
+
+ ce = scols_line_get_cell(ln, cl->seqnum);
+ data = ce ? scols_cell_get_data(ce) : NULL;
+
+ if (!scols_column_is_tree(cl))
+ return data ? ul_buffer_append_string(buf, data) : 0;
+
+ /*
+ * Group stuff
+ */
+ if (!scols_table_is_json(tb) && cl->is_groups)
+ rc = groups_ascii_art_to_buffer(tb, ln, buf, 0);
+
+ /*
+ * Tree stuff
+ */
+ if (!rc && ln->parent && !scols_table_is_json(tb)) {
+ rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
+
+ if (!rc && is_last_child(ln))
+ rc = ul_buffer_append_string(buf, right_symbol(tb));
+ else if (!rc)
+ rc = ul_buffer_append_string(buf, branch_symbol(tb));
+ }
+
+ if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb))
+ ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND);
+
+ if (!rc && data)
+ rc = ul_buffer_append_string(buf, data);
+ return rc;
+}
+
+/*
+ * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and
+ * control and non-printable characters can be encoded in the \x?? encoding.
+ */
+static int print_line(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct ul_buffer *buf)
+{
+ int rc = 0, pending = 0;
+ struct libscols_column *cl;
+ struct libscols_iter itr;
+
+ assert(ln);
+
+ DBG(LINE, ul_debugobj(ln, " printing line"));
+
+ fputs_color_line_open(tb, ln);
+
+ /* regular line */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+ rc = __cell_to_buffer(tb, ln, cl, buf);
+ if (rc == 0)
+ rc = print_data(tb, cl, ln,
+ scols_line_get_cell(ln, cl->seqnum),
+ buf);
+ if (rc == 0 && cl->pending_data)
+ pending = 1;
+ }
+ fputs_color_line_close(tb);
+
+ /* extra lines of the multi-line cells */
+ while (rc == 0 && pending) {
+ DBG(LINE, ul_debugobj(ln, "printing pending data"));
+ pending = 0;
+ fputs(linesep(tb), tb->out);
+ fputs_color_line_open(tb, ln);
+ tb->termlines_used++;
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+ if (cl->pending_data) {
+ rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum));
+ if (rc == 0 && cl->pending_data)
+ pending = 1;
+ } else
+ print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf));
+ }
+ fputs_color_line_close(tb);
+ }
+
+ return 0;
+}
+
+int __scols_print_title(struct libscols_table *tb)
+{
+ int rc;
+ mbs_align_t align;
+ size_t width, len = 0, bufsz, titlesz;
+ char *title = NULL, *buf = NULL;
+
+ assert(tb);
+
+ if (!tb->title.data)
+ return 0;
+
+ DBG(TAB, ul_debugobj(tb, "printing title"));
+
+ /* encode data */
+ if (tb->no_encode) {
+ len = bufsz = strlen(tb->title.data) + 1;
+ buf = strdup(tb->title.data);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ } else {
+ bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1;
+ if (bufsz == 1) {
+ DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore"));
+ return 0;
+ }
+ buf = malloc(bufsz);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) ||
+ !len || len == (size_t) -1) {
+ rc = -EINVAL;
+ goto done;
+ }
+ }
+
+ /* truncate and align */
+ width = tb->is_term ? tb->termwidth : 80;
+ titlesz = width + bufsz;
+
+ title = malloc(titlesz);
+ if (!title) {
+ rc = -EINVAL;
+ goto done;
+ }
+
+ switch (scols_cell_get_alignment(&tb->title)) {
+ case SCOLS_CELL_FL_RIGHT:
+ align = MBS_ALIGN_RIGHT;
+ break;
+ case SCOLS_CELL_FL_CENTER:
+ align = MBS_ALIGN_CENTER;
+ break;
+ case SCOLS_CELL_FL_LEFT:
+ default:
+ align = MBS_ALIGN_LEFT;
+ /*
+ * Don't print extra blank chars after the title if on left
+ * (that's same as we use for the last column in the table).
+ */
+ if (len < width
+ && !scols_table_is_maxout(tb)
+ && isblank(*titlepadding_symbol(tb)))
+ width = len;
+ break;
+
+ }
+
+ /* copy from buf to title and align to width with title_padding */
+ rc = mbsalign_with_padding(buf, title, titlesz,
+ &width, align,
+ 0, (int) *titlepadding_symbol(tb));
+
+ if (rc == -1) {
+ rc = -EINVAL;
+ goto done;
+ }
+
+
+ if (tb->colors_wanted)
+ fputs_color(tb, tb->title.color);
+
+ fputs(title, tb->out);
+
+ if (tb->colors_wanted)
+ fputs_color_reset(tb);
+
+ fputc('\n', tb->out);
+ rc = 0;
+done:
+ free(buf);
+ free(title);
+ DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc));
+ return rc;
+}
+
+int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ int rc = 0;
+ struct libscols_column *cl;
+ struct libscols_iter itr;
+
+ assert(tb);
+
+ if ((tb->header_printed == 1 && tb->header_repeat == 0) ||
+ scols_table_is_noheadings(tb) ||
+ scols_table_is_export(tb) ||
+ scols_table_is_json(tb) ||
+ list_empty(&tb->tb_lines))
+ return 0;
+
+ DBG(TAB, ul_debugobj(tb, "printing header"));
+
+ /* set the width according to the size of the data */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+
+ ul_buffer_reset_data(buf);
+
+ if (cl->is_groups
+ && scols_table_is_tree(tb) && scols_column_is_tree(cl)) {
+ size_t i;
+ for (i = 0; i < tb->grpset_size + 1; i++) {
+ rc = ul_buffer_append_data(buf, " ", 1);
+ if (rc)
+ break;
+ }
+ }
+ if (!rc)
+ rc = ul_buffer_append_string(buf,
+ scols_table_is_shellvar(tb) ?
+ scols_column_get_name_as_shellvar(cl) :
+ scols_column_get_name(cl));
+ if (!rc)
+ rc = print_data(tb, cl, NULL, &cl->header, buf);
+ }
+
+ if (rc == 0) {
+ fputs(linesep(tb), tb->out);
+ tb->termlines_used++;
+ }
+
+ tb->header_printed = 1;
+ tb->header_next = tb->termlines_used + tb->termheight;
+ if (tb->header_repeat)
+ DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]",
+ tb->header_next, tb->termlines_used, rc));
+ return rc;
+}
+
+
+int __scols_print_range(struct libscols_table *tb,
+ struct ul_buffer *buf,
+ struct libscols_iter *itr,
+ struct libscols_line *end)
+{
+ int rc = 0;
+ struct libscols_line *ln;
+
+ assert(tb);
+ DBG(TAB, ul_debugobj(tb, "printing range"));
+
+ while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) {
+
+ int last = scols_iter_is_last(itr);
+
+ if (scols_table_is_json(tb))
+ ul_jsonwrt_object_open(&tb->json, NULL);
+
+ rc = print_line(tb, ln, buf);
+
+ if (scols_table_is_json(tb))
+ ul_jsonwrt_object_close(&tb->json);
+ else if (last == 0 && tb->no_linesep == 0) {
+ fputs(linesep(tb), tb->out);
+ tb->termlines_used++;
+ }
+
+ if (end && ln == end)
+ break;
+
+ if (!last && want_repeat_header(tb))
+ __scols_print_header(tb, buf);
+ }
+
+ return rc;
+
+}
+
+int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ struct libscols_iter itr;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ return __scols_print_range(tb, buf, &itr, NULL);
+}
+
+/* scols_walk_tree() callback to print tree line */
+static int print_tree_line(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl __attribute__((__unused__)),
+ void *data)
+{
+ struct ul_buffer *buf = (struct ul_buffer *) data;
+ int rc;
+
+ DBG(LINE, ul_debugobj(ln, " printing tree line"));
+
+ if (scols_table_is_json(tb))
+ ul_jsonwrt_object_open(&tb->json, NULL);
+
+ rc = print_line(tb, ln, buf);
+ if (rc)
+ return rc;
+
+ if (has_children(ln)) {
+ if (scols_table_is_json(tb))
+ ul_jsonwrt_array_open(&tb->json, "children");
+ else {
+ /* between parent and child is separator */
+ fputs(linesep(tb), tb->out);
+ tb->termlines_used++;
+ }
+ } else {
+ int last;
+
+ /* terminate all open last children for JSON */
+ if (scols_table_is_json(tb)) {
+ do {
+ last = (is_child(ln) && is_last_child(ln)) ||
+ (is_tree_root(ln) && is_last_tree_root(tb, ln));
+
+ ul_jsonwrt_object_close(&tb->json);
+ if (last && is_child(ln))
+ ul_jsonwrt_array_close(&tb->json);
+ ln = ln->parent;
+ } while(ln && last);
+
+ } else if (tb->no_linesep == 0) {
+ int last_in_tree = scols_walk_is_last(tb, ln);
+
+ if (last_in_tree == 0) {
+ /* standard output */
+ fputs(linesep(tb), tb->out);
+ tb->termlines_used++;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ assert(tb);
+ DBG(TAB, ul_debugobj(tb, "----printing-tree-----"));
+
+ return scols_walk_tree(tb, NULL, print_tree_line, (void *) buf);
+}
+
+static size_t strlen_line(struct libscols_line *ln)
+{
+ size_t i, sz = 0;
+
+ assert(ln);
+
+ for (i = 0; i < ln->ncells; i++) {
+ struct libscols_cell *ce = scols_line_get_cell(ln, i);
+ const char *data = ce ? scols_cell_get_data(ce) : NULL;
+
+ sz += data ? strlen(data) : 0;
+ }
+
+ return sz;
+}
+
+void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ if (!tb)
+ return;
+
+ ul_buffer_free_data(buf);
+
+ if (tb->priv_symbols) {
+ scols_table_set_symbols(tb, NULL);
+ tb->priv_symbols = 0;
+ }
+}
+
+int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf)
+{
+ size_t bufsz, extra_bufsz = 0;
+ struct libscols_line *ln;
+ struct libscols_iter itr;
+ int rc;
+
+ DBG(TAB, ul_debugobj(tb, "initialize printing"));
+
+ if (!tb->symbols) {
+ rc = scols_table_set_default_symbols(tb);
+ if (rc)
+ goto err;
+ tb->priv_symbols = 1;
+ } else
+ tb->priv_symbols = 0;
+
+ if (tb->format == SCOLS_FMT_HUMAN)
+ tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 :
+ tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 :
+ isatty(STDOUT_FILENO);
+
+ if (tb->is_term) {
+ size_t width = (size_t) scols_table_get_termwidth(tb);
+
+ if (tb->termreduce > 0 && tb->termreduce < width) {
+ width -= tb->termreduce;
+ scols_table_set_termwidth(tb, width);
+ }
+ bufsz = width;
+ } else
+ bufsz = BUFSIZ;
+
+ if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb))
+ tb->header_repeat = 0;
+
+ /*
+ * Estimate extra space necessary for tree, JSON or another output
+ * decoration.
+ */
+ if (scols_table_is_tree(tb))
+ extra_bufsz += tb->nlines * strlen(vertical_symbol(tb));
+
+ switch (tb->format) {
+ case SCOLS_FMT_RAW:
+ extra_bufsz += tb->ncols; /* separator between columns */
+ break;
+ case SCOLS_FMT_JSON:
+ ul_jsonwrt_init(&tb->json, tb->out, 0);
+ extra_bufsz += tb->nlines * 3; /* indentation */
+ /* fallthrough */
+ case SCOLS_FMT_EXPORT:
+ {
+ struct libscols_column *cl;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (scols_column_is_hidden(cl))
+ continue;
+
+ if (scols_column_get_name(cl))
+ extra_bufsz += strlen(scols_column_get_name(cl)); /* data */
+ extra_bufsz += 2; /* separators */
+ }
+ break;
+ }
+ case SCOLS_FMT_HUMAN:
+ break;
+ }
+
+ /*
+ * Enlarge buffer if necessary, the buffer should be large enough to
+ * store line data and tree ascii art (or another decoration).
+ */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ size_t sz;
+
+ sz = strlen_line(ln) + extra_bufsz;
+ if (sz > bufsz)
+ bufsz = sz;
+ }
+
+ /* pre-allocate space for data */
+ rc = ul_buffer_alloc_data(buf, bufsz + 1); /* data + space for \0 */
+ if (rc)
+ goto err;
+
+ /*
+ * Make sure groups members are in the same orders as the tree
+ */
+ if (has_groups(tb) && scols_table_is_tree(tb))
+ scols_groups_fix_members_order(tb);
+
+ if (tb->format == SCOLS_FMT_HUMAN) {
+ rc = __scols_calculate(tb, buf);
+ if (rc != 0)
+ goto err;
+ }
+
+ return 0;
+err:
+ __scols_cleanup_printing(tb, buf);
+ return rc;
+}
+
diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h
new file mode 100644
index 0000000..8b83a14
--- /dev/null
+++ b/libsmartcols/src/smartcolsP.h
@@ -0,0 +1,449 @@
+/*
+ * smartcolsP.h - private library header file
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#ifndef _LIBSMARTCOLS_PRIVATE_H
+#define _LIBSMARTCOLS_PRIVATE_H
+
+#include "c.h"
+#include "list.h"
+#include "strutils.h"
+#include "color-names.h"
+#include "jsonwrt.h"
+#include "debug.h"
+#include "buffer.h"
+
+#include "libsmartcols.h"
+
+/*
+ * Debug
+ */
+#define SCOLS_DEBUG_HELP (1 << 0)
+#define SCOLS_DEBUG_INIT (1 << 1)
+#define SCOLS_DEBUG_CELL (1 << 2)
+#define SCOLS_DEBUG_LINE (1 << 3)
+#define SCOLS_DEBUG_TAB (1 << 4)
+#define SCOLS_DEBUG_COL (1 << 5)
+#define SCOLS_DEBUG_BUFF (1 << 6)
+#define SCOLS_DEBUG_GROUP (1 << 7)
+#define SCOLS_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(libsmartcols);
+#define DBG(m, x) __UL_DBG(libsmartcols, SCOLS_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(libsmartcols, SCOLS_DEBUG_, m, x)
+#define DBG_FLUSH __UL_DBG_FLUSH(libsmartcols, SCOLS_DEBUG_)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols)
+#include "debugobj.h"
+
+#define SCOLS_BUFPTR_TREEEND 0
+
+/*
+ * Generic iterator
+ */
+struct libscols_iter {
+ struct list_head *p; /* current position */
+ struct list_head *head; /* start position */
+ int direction; /* SCOLS_ITER_{FOR,BACK}WARD */
+};
+
+/*
+ * Tree symbols
+ */
+struct libscols_symbols {
+ int refcount;
+
+ char *tree_branch;
+ char *tree_vert;
+ char *tree_right;
+
+ char *group_vert;
+ char *group_horz;
+ char *group_first_member;
+ char *group_last_member;
+ char *group_middle_member;
+ char *group_last_child;
+ char *group_middle_child;
+
+ char *title_padding;
+ char *cell_padding;
+};
+
+/*
+ * Table cells
+ */
+struct libscols_cell {
+ char *data;
+ char *color;
+ void *userdata;
+ int flags;
+};
+
+extern int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn);
+
+/*
+ * Table column
+ */
+struct libscols_column {
+ int refcount; /* reference counter */
+ size_t seqnum; /* column index */
+
+ size_t width; /* real column width */
+ size_t width_min; /* minimal width (usually header width) */
+ size_t width_max; /* maximal width */
+ size_t width_avg; /* average width, used to detect extreme fields */
+ size_t width_treeart; /* size of the tree ascii art */
+ double width_hint; /* hint (N < 1 is in percent of termwidth) */
+
+ size_t extreme_sum;
+ int extreme_count;
+
+ int json_type; /* SCOLS_JSON_* */
+
+ int flags;
+ char *color; /* default column color */
+ char *safechars; /* do not encode this bytes */
+
+ char *pending_data;
+ size_t pending_data_sz;
+ char *pending_data_buf;
+
+ int (*cmpfunc)(struct libscols_cell *,
+ struct libscols_cell *,
+ void *); /* cells comparison function */
+ void *cmpfunc_data;
+
+ size_t (*wrap_chunksize)(const struct libscols_column *,
+ const char *, void *);
+ char *(*wrap_nextchunk)(const struct libscols_column *,
+ char *, void *);
+ void *wrapfunc_data;
+
+
+ struct libscols_cell header; /* column name with color etc. */
+ char *shellvar; /* raw colum name in shell compatible format */
+
+ struct list_head cl_columns; /* member of table->tb_columns */
+
+ struct libscols_table *table;
+
+ unsigned int is_extreme : 1, /* extreme width in the column */
+ is_groups : 1; /* print group chart */
+
+};
+
+#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ")
+#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n")
+
+enum {
+ SCOLS_GSTATE_NONE = 0, /* not activate yet */
+ SCOLS_GSTATE_FIRST_MEMBER,
+ SCOLS_GSTATE_MIDDLE_MEMBER,
+ SCOLS_GSTATE_LAST_MEMBER,
+ SCOLS_GSTATE_MIDDLE_CHILD,
+ SCOLS_GSTATE_LAST_CHILD,
+ SCOLS_GSTATE_CONT_MEMBERS,
+ SCOLS_GSTATE_CONT_CHILDREN
+};
+
+/*
+ * Every group needs at least 3 columns
+ */
+#define SCOLS_GRPSET_CHUNKSIZ 3
+
+struct libscols_group {
+ int refcount;
+
+ size_t nmembers;
+
+ struct list_head gr_members; /* head of line->ln_group */
+ struct list_head gr_children; /* head of line->ln_children */
+ struct list_head gr_groups; /* member of table->tb_groups */
+
+ int state; /* SCOLS_GSTATE_* */
+};
+
+/*
+ * Table line
+ */
+struct libscols_line {
+ int refcount;
+ size_t seqnum;
+
+ void *userdata;
+ char *color; /* default line color */
+
+ struct libscols_cell *cells; /* array with data */
+ size_t ncells; /* number of cells */
+
+ struct list_head ln_lines; /* member of table->tb_lines */
+ struct list_head ln_branch; /* head of line->ln_children */
+ struct list_head ln_children; /* member of line->ln_children or group->gr_children */
+ struct list_head ln_groups; /* member of group->gr_groups */
+
+ struct libscols_line *parent;
+ struct libscols_group *parent_group; /* for group childs */
+ struct libscols_group *group; /* for group members */
+};
+
+enum {
+ SCOLS_FMT_HUMAN = 0, /* default, human readable */
+ SCOLS_FMT_RAW, /* space separated */
+ SCOLS_FMT_EXPORT, /* COLNAME="data" ... */
+ SCOLS_FMT_JSON /* http://en.wikipedia.org/wiki/JSON */
+};
+
+/*
+ * The table
+ */
+struct libscols_table {
+ int refcount;
+ char *name; /* optional table name (for JSON) */
+ size_t ncols; /* number of columns */
+ size_t ntreecols; /* number of columns with SCOLS_FL_TREE */
+ size_t nlines; /* number of lines */
+ size_t termwidth; /* terminal width (number of columns) */
+ size_t termheight; /* terminal height (number of lines) */
+ size_t termreduce; /* extra blank space */
+ int termforce; /* SCOLS_TERMFORCE_* */
+ FILE *out; /* output stream */
+
+ char *colsep; /* column separator */
+ char *linesep; /* line separator */
+
+ struct list_head tb_columns; /* list of columns, items: column->cl_columns */
+ struct list_head tb_lines; /* list of lines; items: line->ln_lines */
+
+ struct list_head tb_groups; /* all defined groups */
+ struct libscols_group **grpset;
+ size_t grpset_size;
+
+ size_t ngrpchlds_pending; /* groups with not yet printed children */
+ struct libscols_line *walk_last_tree_root; /* last root, used by scols_walk_() */
+
+ struct libscols_column *dflt_sort_column; /* default sort column, set by scols_sort_table() */
+
+ struct libscols_symbols *symbols;
+ struct libscols_cell title; /* optional table title (for humans) */
+
+ struct ul_jsonwrt json; /* JSON formatting */
+
+ int format; /* SCOLS_FMT_* */
+
+ size_t termlines_used; /* printed line counter */
+ size_t header_next; /* where repeat header */
+
+ const char *cur_color; /* current active color when printing */
+
+ /* flags */
+ unsigned int ascii :1, /* don't use unicode */
+ colors_wanted :1, /* enable colors */
+ is_term :1, /* isatty() */
+ padding_debug :1, /* output visible padding chars */
+ is_dummy_print :1, /* printing used for width calculation only */
+ is_shellvar :1, /* shell compatible column names */
+ maxout :1, /* maximize output */
+ minout :1, /* minimize output (mutually exclusive to maxout) */
+ header_repeat :1, /* print header after libscols_table->termheight */
+ header_printed :1, /* header already printed */
+ priv_symbols :1, /* default private symbols */
+ walk_last_done :1, /* last tree root walked */
+ no_headings :1, /* don't print header */
+ no_encode :1, /* don't care about control and non-printable chars */
+ no_linesep :1, /* don't print line separator */
+ no_wrap :1; /* never wrap lines */
+};
+
+#define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i) ((_i)->direction == SCOLS_ITER_BACKWARD)
+
+#define SCOLS_ITER_INIT(itr, list) \
+ do { \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (list)->next : (list)->prev; \
+ (itr)->head = (list); \
+ } while(0)
+
+#define SCOLS_ITER_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+
+static inline int scols_iter_is_last(const struct libscols_iter *itr)
+{
+ if (!itr || !itr->head || !itr->p)
+ return 0;
+
+ return itr->p == itr->head;
+}
+
+/*
+ * line.c
+ */
+int scols_line_next_group_child(struct libscols_line *ln,
+ struct libscols_iter *itr,
+ struct libscols_line **chld);
+
+
+/*
+ * table.c
+ */
+int scols_table_next_group(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_group **gr);
+
+/*
+ * grouping.c
+ */
+void scols_ref_group(struct libscols_group *gr);
+void scols_group_remove_children(struct libscols_group *gr);
+void scols_group_remove_members(struct libscols_group *gr);
+void scols_unref_group(struct libscols_group *gr);
+void scols_groups_fix_members_order(struct libscols_table *tb);
+int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln);
+void scols_groups_reset_state(struct libscols_table *tb);
+struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb);
+
+/*
+ * walk.c
+ */
+extern int scols_walk_tree(struct libscols_table *tb,
+ struct libscols_column *cl,
+ int (*callback)(struct libscols_table *,
+ struct libscols_line *,
+ struct libscols_column *,
+ void *),
+ void *data);
+extern int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln);
+
+/*
+ * calculate.c
+ */
+extern int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf);
+
+/*
+ * print.c
+ */
+extern int __cell_to_buffer(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl,
+ struct ul_buffer *buf);
+
+void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf);
+int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf);
+int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf);
+int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf);
+int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf);
+int __scols_print_title(struct libscols_table *tb);
+int __scols_print_range(struct libscols_table *tb,
+ struct ul_buffer *buf,
+ struct libscols_iter *itr,
+ struct libscols_line *end);
+
+static inline int is_tree_root(struct libscols_line *ln)
+{
+ return ln && !ln->parent && !ln->parent_group;
+}
+
+static inline int is_last_tree_root(struct libscols_table *tb, struct libscols_line *ln)
+{
+ if (!ln || !tb || tb->walk_last_tree_root != ln)
+ return 0;
+
+ return 1;
+}
+
+static inline int is_child(struct libscols_line *ln)
+{
+ return ln && ln->parent;
+}
+
+static inline int is_last_child(struct libscols_line *ln)
+{
+ if (!ln || !ln->parent)
+ return 0;
+
+ return list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch);
+}
+
+static inline int is_first_child(struct libscols_line *ln)
+{
+ if (!ln || !ln->parent)
+ return 0;
+
+ return list_entry_is_first(&ln->ln_children, &ln->parent->ln_branch);
+}
+
+
+static inline int is_last_column(struct libscols_column *cl)
+{
+ struct libscols_column *next;
+
+ if (list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns))
+ return 1;
+
+ next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns);
+ if (next && scols_column_is_hidden(next) && is_last_column(next))
+ return 1;
+ return 0;
+}
+
+static inline int is_last_group_member(struct libscols_line *ln)
+{
+ if (!ln || !ln->group)
+ return 0;
+
+ return list_entry_is_last(&ln->ln_groups, &ln->group->gr_members);
+}
+
+static inline int is_first_group_member(struct libscols_line *ln)
+{
+ if (!ln || !ln->group)
+ return 0;
+
+ return list_entry_is_first(&ln->ln_groups, &ln->group->gr_members);
+}
+
+static inline int is_group_member(struct libscols_line *ln)
+{
+ return ln && ln->group;
+}
+
+static inline int is_last_group_child(struct libscols_line *ln)
+{
+ if (!ln || !ln->parent_group)
+ return 0;
+
+ return list_entry_is_last(&ln->ln_children, &ln->parent_group->gr_children);
+}
+
+static inline int is_group_child(struct libscols_line *ln)
+{
+ return ln && ln->parent_group;
+}
+
+static inline int has_groups(struct libscols_table *tb)
+{
+ return tb && !list_empty(&tb->tb_groups);
+}
+
+static inline int has_children(struct libscols_line *ln)
+{
+ return ln && !list_empty(&ln->ln_branch);
+}
+
+static inline int has_group_children(struct libscols_line *ln)
+{
+ return ln && ln->group && !list_empty(&ln->group->gr_children);
+}
+
+#endif /* _LIBSMARTCOLS_PRIVATE_H */
diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c
new file mode 100644
index 0000000..2fadfc7
--- /dev/null
+++ b/libsmartcols/src/symbols.c
@@ -0,0 +1,293 @@
+/*
+ * symbols.c - routines for symbol handling
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: symbols
+ * @title: Symbols
+ * @short_description: can be used to overwrite default output chars (for ascii art)
+ *
+ * An API to access and modify data and information per symbol/symbol group.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "smartcolsP.h"
+
+/**
+ * scols_new_symbols:
+ *
+ * Returns: a pointer to a newly allocated struct libscols_symbols instance.
+ */
+struct libscols_symbols *scols_new_symbols(void)
+{
+ struct libscols_symbols *sy = calloc(1, sizeof(struct libscols_symbols));
+
+ if (!sy)
+ return NULL;
+ sy->refcount = 1;
+ return sy;
+}
+
+/**
+ * scols_ref_symbols:
+ * @sy: a pointer to a struct libscols_symbols instance
+ *
+ * Increases the refcount of @sy.
+ */
+void scols_ref_symbols(struct libscols_symbols *sy)
+{
+ if (sy)
+ sy->refcount++;
+}
+
+/**
+ * scols_unref_symbols:
+ * @sy: a pointer to a struct libscols_symbols instance
+ *
+ * Decreases the refcount of @sy.
+ */
+void scols_unref_symbols(struct libscols_symbols *sy)
+{
+ if (sy && --sy->refcount <= 0) {
+ free(sy->tree_branch);
+ free(sy->tree_vert);
+ free(sy->tree_right);
+ free(sy->group_last_member);
+ free(sy->group_middle_member);
+ free(sy->group_first_member);
+ free(sy->group_vert);
+ free(sy->group_horz);
+ free(sy->group_last_child);
+ free(sy->group_middle_child);
+ free(sy->title_padding);
+ free(sy->cell_padding);
+ free(sy);
+ }
+}
+
+/**
+ * scols_symbols_set_branch:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the branch part of a tree output
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, tree_branch, str);
+}
+
+/**
+ * scols_symbols_set_vertical:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the vertical part of a tree output
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, tree_vert, str);
+}
+
+/**
+ * scols_symbols_set_right:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the right part of a tree output
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_symbols_set_right(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, tree_right, str);
+}
+
+/**
+ * scols_symbols_set_title_padding:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the symbols which fill title output
+ *
+ * The current implementation uses only the first byte from the padding string.
+ * A multibyte chars are not supported yet.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.28
+ */
+int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, title_padding, str);
+}
+
+/**
+ * scols_symbols_set_cell_padding:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the symbols which fill cells
+ *
+ * The padding char has to take up just one cell on the terminal.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, cell_padding, str);
+}
+
+
+/**
+ * scols_symbols_set_group_vertical:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the vertival line
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_vert, str);
+}
+
+/**
+ * scols_symbols_set_group_horizontal:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent the horizontal line
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_horz, str);
+}
+
+/**
+ * scols_symbols_set_group_first_member:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent first member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_first_member, str);
+}
+
+/**
+ * scols_symbols_set_group_last_member:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_last_member, str);
+}
+
+/**
+ * scols_symbols_set_group_middle:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent middle member
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_middle_member, str);
+}
+
+/**
+ * scols_symbols_set_group_last_child:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last child
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_last_child, str);
+}
+
+/**
+ * scols_symbols_set_group_middle_child:
+ * @sy: a pointer to a struct libscols_symbols instance
+ * @str: a string which will represent last child
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.34
+ */
+int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str)
+{
+ return strdup_to_struct_member(sy, group_middle_child, str);
+}
+
+/**
+ * scols_copy_symbols:
+ * @sy: a pointer to a struct libscols_symbols instance
+ *
+ * Returns: a newly allocated copy of the @sy symbol group or NULL in case of an error.
+ */
+struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy)
+{
+ struct libscols_symbols *ret;
+ int rc;
+
+ assert(sy);
+ if (!sy)
+ return NULL;
+
+ ret = scols_new_symbols();
+ if (!ret)
+ return NULL;
+
+ rc = scols_symbols_set_branch(ret, sy->tree_branch);
+ if (!rc)
+ rc = scols_symbols_set_vertical(ret, sy->tree_vert);
+ if (!rc)
+ rc = scols_symbols_set_right(ret, sy->tree_right);
+ if (!rc)
+ rc = scols_symbols_set_group_vertical(ret, sy->group_vert);
+ if (!rc)
+ rc = scols_symbols_set_group_horizontal(ret, sy->group_horz);
+ if (!rc)
+ rc = scols_symbols_set_group_first_member(ret, sy->group_first_member);
+ if (!rc)
+ rc = scols_symbols_set_group_last_member(ret, sy->group_last_member);
+ if (!rc)
+ rc = scols_symbols_set_group_middle_member(ret, sy->group_middle_member);
+ if (!rc)
+ rc = scols_symbols_set_group_middle_child(ret, sy->group_middle_child);
+ if (!rc)
+ rc = scols_symbols_set_group_last_child(ret, sy->group_last_child);
+ if (!rc)
+ rc = scols_symbols_set_title_padding(ret, sy->title_padding);
+ if (!rc)
+ rc = scols_symbols_set_cell_padding(ret, sy->cell_padding);
+ if (!rc)
+ return ret;
+
+ scols_unref_symbols(ret);
+ return NULL;
+}
diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c
new file mode 100644
index 0000000..30e9194
--- /dev/null
+++ b/libsmartcols/src/table.c
@@ -0,0 +1,1754 @@
+/*
+ * table.c - functions handling the data at the table level
+ *
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: table
+ * @title: Table
+ * @short_description: container for rows and columns
+ *
+ * Table data manipulation API.
+ */
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#include "nls.h"
+#include "ttyutils.h"
+#include "smartcolsP.h"
+
+#ifdef HAVE_WIDECHAR
+#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char | */
+#define UTF_VR "\342\224\234" /* U+251C, Vertical and right |- */
+#define UTF_H "\342\224\200" /* U+2500, Horizontal - */
+#define UTF_UR "\342\224\224" /* U+2514, Up and right '- */
+
+#define UTF_V3 "\342\224\206" /* U+2506 Triple Dash Vertical | */
+#define UTF_H3 "\342\224\210" /* U+2504 Triple Dash Horizontal - */
+#define UTF_DR "\342\224\214" /* U+250C Down and Right ,- */
+#define UTF_DH "\342\224\254" /* U+252C Down and Horizontal |' */
+
+#define UTF_TR "\342\226\266" /* U+25B6 Black Right-Pointing Triangle > */
+#endif /* !HAVE_WIDECHAR */
+
+#define is_last_column(_tb, _cl) \
+ list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns)
+
+
+static void check_padding_debug(struct libscols_table *tb)
+{
+ const char *str;
+
+ assert(libsmartcols_debug_mask); /* debug has to be already initialized! */
+
+ str = getenv("LIBSMARTCOLS_DEBUG_PADDING");
+ if (!str || (strcmp(str, "on") != 0 && strcmp(str, "1") != 0))
+ return;
+
+ DBG(INIT, ul_debugobj(tb, "padding debug: ENABLE"));
+ tb->padding_debug = 1;
+}
+
+/**
+ * scols_new_table:
+ *
+ * Returns: A newly allocated table.
+ */
+struct libscols_table *scols_new_table(void)
+{
+ struct libscols_table *tb;
+ int c, l;
+
+ tb = calloc(1, sizeof(struct libscols_table));
+ if (!tb)
+ return NULL;
+
+ tb->refcount = 1;
+ tb->out = stdout;
+
+ get_terminal_dimension(&c, &l);
+ tb->termwidth = c > 0 ? c : 80;
+ tb->termheight = l > 0 ? l : 24;
+
+ INIT_LIST_HEAD(&tb->tb_lines);
+ INIT_LIST_HEAD(&tb->tb_columns);
+ INIT_LIST_HEAD(&tb->tb_groups);
+
+ DBG(TAB, ul_debugobj(tb, "alloc"));
+ ON_DBG(INIT, check_padding_debug(tb));
+
+ return tb;
+}
+
+/**
+ * scols_ref_table:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Increases the refcount of @tb.
+ */
+void scols_ref_table(struct libscols_table *tb)
+{
+ if (tb)
+ tb->refcount++;
+}
+
+static void scols_table_remove_groups(struct libscols_table *tb)
+{
+ while (!list_empty(&tb->tb_groups)) {
+ struct libscols_group *gr = list_entry(tb->tb_groups.next,
+ struct libscols_group, gr_groups);
+ scols_group_remove_children(gr);
+ scols_group_remove_members(gr);
+ scols_unref_group(gr);
+ }
+}
+
+/**
+ * scols_unref_table:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Decreases the refcount of @tb. When the count falls to zero, the instance
+ * is automatically deallocated.
+ */
+void scols_unref_table(struct libscols_table *tb)
+{
+ if (tb && (--tb->refcount <= 0)) {
+ DBG(TAB, ul_debugobj(tb, "dealloc <-"));
+ scols_table_remove_groups(tb);
+ scols_table_remove_lines(tb);
+ scols_table_remove_columns(tb);
+ scols_unref_symbols(tb->symbols);
+ scols_reset_cell(&tb->title);
+ free(tb->grpset);
+ free(tb->linesep);
+ free(tb->colsep);
+ free(tb->name);
+ free(tb);
+ DBG(TAB, ul_debug("<- done"));
+ }
+}
+
+/* Private API */
+int scols_table_next_group(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_group **gr)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !gr)
+ return -EINVAL;
+ *gr = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &tb->tb_groups);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *gr, struct libscols_group, gr_groups);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * scols_table_set_name:
+ * @tb: a pointer to a struct libscols_table instance
+ * @name: a name
+ *
+ * The table name is used for example for JSON top level object name.
+ *
+ * Returns: 0, a negative number in case of an error.
+ *
+ * Since: 2.27
+ */
+int scols_table_set_name(struct libscols_table *tb, const char *name)
+{
+ return strdup_to_struct_member(tb, name, name);
+}
+
+/**
+ * scols_table_get_name:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Returns: The current name setting of the table @tb
+ *
+ * Since: 2.29
+ */
+const char *scols_table_get_name(const struct libscols_table *tb)
+{
+ return tb->name;
+}
+
+/**
+ * scols_table_get_title:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * The returned pointer is possible to modify by cell functions. Note that
+ * title output alignment on non-tty is hardcoded to 80 output chars. For the
+ * regular terminal it's based on terminal width.
+ *
+ * Returns: Title of the table, or NULL in case of blank title.
+ *
+ * Since: 2.28
+ */
+struct libscols_cell *scols_table_get_title(struct libscols_table *tb)
+{
+ return &tb->title;
+}
+
+/**
+ * scols_table_add_column:
+ * @tb: a pointer to a struct libscols_table instance
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Adds @cl to @tb's column list. The column cannot be shared between more
+ * tables.
+ *
+ * Returns: 0, a negative number in case of an error.
+ */
+int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl)
+{
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+ int rc = 0;
+
+ if (!tb || !cl || cl->table)
+ return -EINVAL;
+
+ if (!list_empty(&cl->cl_columns))
+ return -EINVAL;
+
+ if (cl->flags & SCOLS_FL_TREE)
+ tb->ntreecols++;
+
+ DBG(TAB, ul_debugobj(tb, "add column"));
+ list_add_tail(&cl->cl_columns, &tb->tb_columns);
+ cl->seqnum = tb->ncols++;
+ cl->table = tb;
+ scols_ref_column(cl);
+
+ if (list_empty(&tb->tb_lines))
+ return 0;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+
+ /* Realloc line cell arrays
+ */
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ rc = scols_line_alloc_cells(ln, tb->ncols);
+ if (rc)
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * scols_table_remove_column:
+ * @tb: a pointer to a struct libscols_table instance
+ * @cl: a pointer to a struct libscols_column instance
+ *
+ * Removes @cl from @tb.
+ *
+ * Returns: 0, a negative number in case of an error.
+ */
+int scols_table_remove_column(struct libscols_table *tb,
+ struct libscols_column *cl)
+{
+ if (!tb || !cl || !list_empty(&tb->tb_lines))
+ return -EINVAL;
+
+ if (cl->flags & SCOLS_FL_TREE)
+ tb->ntreecols--;
+ if (tb->dflt_sort_column == cl)
+ tb->dflt_sort_column = NULL;
+
+ DBG(TAB, ul_debugobj(tb, "remove column"));
+ list_del_init(&cl->cl_columns);
+ tb->ncols--;
+ cl->table = NULL;
+ scols_unref_column(cl);
+ return 0;
+}
+
+/**
+ * scols_table_remove_columns:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Removes all of @tb's columns.
+ *
+ * Returns: 0, a negative number in case of an error.
+ */
+int scols_table_remove_columns(struct libscols_table *tb)
+{
+ if (!tb || !list_empty(&tb->tb_lines))
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "remove all columns"));
+ while (!list_empty(&tb->tb_columns)) {
+ struct libscols_column *cl = list_entry(tb->tb_columns.next,
+ struct libscols_column, cl_columns);
+ scols_table_remove_column(tb, cl);
+ }
+ return 0;
+}
+
+/**
+ * scols_table_move_column:
+ * @tb: table
+ * @pre: column before the column
+ * @cl: column to move
+ *
+ * Move the @cl behind @pre. If the @pre is NULL then the @col is the first
+ * column in the table.
+ *
+ * Since: 2.30
+ *
+ * Returns: 0, a negative number in case of an error.
+ */
+int scols_table_move_column(struct libscols_table *tb,
+ struct libscols_column *pre,
+ struct libscols_column *cl)
+{
+ struct list_head *head;
+ struct libscols_iter itr;
+ struct libscols_column *p;
+ struct libscols_line *ln;
+ size_t n = 0, oldseq;
+
+ if (!tb || !cl)
+ return -EINVAL;
+
+ if (pre && pre->seqnum + 1 == cl->seqnum)
+ return 0;
+ if (pre == NULL && cl->seqnum == 0)
+ return 0;
+
+ DBG(TAB, ul_debugobj(tb, "move column %zu behind %zu",
+ cl->seqnum, pre? pre->seqnum : 0));
+
+ list_del_init(&cl->cl_columns); /* remove from old position */
+
+ head = pre ? &pre->cl_columns : &tb->tb_columns;
+ list_add(&cl->cl_columns, head); /* add to the new place */
+
+ oldseq = cl->seqnum;
+
+ /* fix seq. numbers */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &p) == 0)
+ p->seqnum = n++;
+
+ /* move data in lines */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0)
+ scols_line_move_cells(ln, cl->seqnum, oldseq);
+ return 0;
+}
+
+/**
+ * scols_table_new_column:
+ * @tb: table
+ * @name: column header
+ * @whint: column width hint (absolute width: N > 1; relative width: 0 < N < 1)
+ * @flags: flags integer
+ *
+ * This is shortcut for
+ *
+ * cl = scols_new_column();
+ * scols_column_set_....(cl, ...);
+ * scols_table_add_column(tb, cl);
+ *
+ * The column width is possible to define by:
+ *
+ * @whint: 0 < N < 1 : relative width, percent of terminal width
+ *
+ * @whint: N >= 1 : absolute width, empty column will be truncated to
+ * the column header width if no specified STRICTWIDTH flag
+ *
+ * Note that if table has disabled "maxout" flag (disabled by default) than
+ * relative width is used as a hint only. It's possible that column will be
+ * narrow if the specified size is too large for column data.
+ *
+ *
+ * If the width of all columns is greater than terminal width then library
+ * tries to reduce width of the individual columns. It's done in three stages:
+ *
+ * #1 reduce columns with SCOLS_FL_TRUNC flag and with relative width if the
+ * width is greater than width defined by @whint (@whint * terminal_width)
+ *
+ * #2 reduce all columns with SCOLS_FL_TRUNC flag
+ *
+ * #3 reduce all columns with relative width
+ *
+ * The next stage is always used if the previous stage is unsuccessful. Note
+ * that SCOLS_FL_WRAP is interpreted as SCOLS_FL_TRUNC when calculate column
+ * width (if custom wrap function is not specified), but the final text is not
+ * truncated, but wrapped to multi-line cell.
+ *
+ *
+ * The column is necessary to address by sequential number. The first defined
+ * column has the colnum = 0. For example:
+ *
+ * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0
+ * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1
+ * .
+ * .
+ * scols_line_get_cell(line, 0); // FOO column
+ * scols_line_get_cell(line, 1); // BAR column
+ *
+ * Returns: newly allocated column
+ */
+struct libscols_column *scols_table_new_column(struct libscols_table *tb,
+ const char *name,
+ double whint,
+ int flags)
+{
+ struct libscols_column *cl;
+
+ if (!tb)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "new column name=%s, whint=%g, flags=%d",
+ name, whint, flags));
+ cl = scols_new_column();
+ if (!cl)
+ return NULL;
+
+ if (scols_column_set_name(cl, name))
+ goto err;
+ scols_column_set_whint(cl, whint);
+ scols_column_set_flags(cl, flags);
+
+ if (scols_table_add_column(tb, cl)) /* this increments column ref-counter */
+ goto err;
+
+ scols_unref_column(cl);
+ return cl;
+err:
+ scols_unref_column(cl);
+ return NULL;
+}
+
+/**
+ * scols_table_next_column:
+ * @tb: a pointer to a struct libscols_table instance
+ * @itr: a pointer to a struct libscols_iter instance
+ * @cl: a pointer to a pointer to a struct libscols_column instance
+ *
+ * Returns the next column of @tb via @cl.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_next_column(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_column **cl)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !cl)
+ return -EINVAL;
+ *cl = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &tb->tb_columns);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *cl, struct libscols_column, cl_columns);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * scols_table_set_columns_iter:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @cl: tab entry
+ *
+ * Sets @iter to the position of @cl in the file @tb.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ *
+ * Since: 2.35
+ */
+int scols_table_set_columns_iter(
+ struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_column *cl)
+{
+ if (!tb || !itr || !cl)
+ return -EINVAL;
+
+ if (cl->table != tb)
+ return -EINVAL;
+
+ SCOLS_ITER_INIT(itr, &tb->tb_columns);
+ itr->p = &cl->cl_columns;
+
+ return 0;
+}
+
+/**
+ * scols_table_get_ncols:
+ * @tb: table
+ *
+ * Returns: the ncols table member.
+ */
+size_t scols_table_get_ncols(const struct libscols_table *tb)
+{
+ return tb->ncols;
+}
+
+/**
+ * scols_table_get_nlines:
+ * @tb: table
+ *
+ * Returns: the nlines table member.
+ */
+size_t scols_table_get_nlines(const struct libscols_table *tb)
+{
+ return tb->nlines;
+}
+
+/**
+ * scols_table_set_stream:
+ * @tb: table
+ * @stream: output stream
+ *
+ * Sets the output stream for table @tb.
+ *
+ * Returns: 0, a negative number in case of an error.
+ */
+int scols_table_set_stream(struct libscols_table *tb, FILE *stream)
+{
+ assert(tb);
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "setting alternative stream"));
+ tb->out = stream;
+ return 0;
+}
+
+/**
+ * scols_table_get_stream:
+ * @tb: table
+ *
+ * Gets the output stream for table @tb.
+ *
+ * Returns: stream pointer, NULL in case of an error or an unset stream.
+ */
+FILE *scols_table_get_stream(const struct libscols_table *tb)
+{
+ return tb->out;
+}
+
+/**
+ * scols_table_reduce_termwidth:
+ * @tb: table
+ * @reduce: width
+ *
+ * If necessary then libsmartcols use all terminal width, the @reduce setting
+ * provides extra space (for example for borders in ncurses applications).
+ *
+ * The @reduce must be smaller than terminal width, otherwise it's silently
+ * ignored. The reduction is not applied when STDOUT_FILENO is not terminal.
+ *
+ * Note that after output initialization (scols_table_print_* calls) the width
+ * will be reduced, this behavior affects subsequenced scols_table_get_termwidth()
+ * calls.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "reduce terminal width: %zu", reduce));
+ tb->termreduce = reduce;
+ return 0;
+}
+
+/**
+ * scols_table_get_column:
+ * @tb: table
+ * @n: number of column (0..N)
+ *
+ * Returns: pointer to column or NULL
+ */
+struct libscols_column *scols_table_get_column(struct libscols_table *tb,
+ size_t n)
+{
+ struct libscols_iter itr;
+ struct libscols_column *cl;
+
+ if (!tb)
+ return NULL;
+ if (n >= tb->ncols)
+ return NULL;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ if (cl->seqnum == n)
+ return cl;
+ }
+ return NULL;
+}
+
+/**
+ * scols_table_add_line:
+ * @tb: table
+ * @ln: line
+ *
+ * Note that this function calls scols_line_alloc_cells() if number
+ * of the cells in the line is too small for @tb.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln)
+{
+ if (!tb || !ln)
+ return -EINVAL;
+
+ if (!list_empty(&ln->ln_lines))
+ return -EINVAL;
+
+ if (tb->ncols > ln->ncells) {
+ int rc = scols_line_alloc_cells(ln, tb->ncols);
+ if (rc)
+ return rc;
+ }
+
+ DBG(TAB, ul_debugobj(tb, "add line"));
+ list_add_tail(&ln->ln_lines, &tb->tb_lines);
+ ln->seqnum = tb->nlines++;
+ scols_ref_line(ln);
+ return 0;
+}
+
+/**
+ * scols_table_remove_line:
+ * @tb: table
+ * @ln: line
+ *
+ * Note that this function does not destroy the parent<->child relationship between lines.
+ * You have to call scols_line_remove_child()
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_remove_line(struct libscols_table *tb,
+ struct libscols_line *ln)
+{
+ if (!tb || !ln)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "remove line"));
+ list_del_init(&ln->ln_lines);
+ tb->nlines--;
+ scols_unref_line(ln);
+ return 0;
+}
+
+/**
+ * scols_table_remove_lines:
+ * @tb: table
+ *
+ * This empties the table and also destroys all the parent<->child relationships.
+ */
+void scols_table_remove_lines(struct libscols_table *tb)
+{
+ if (!tb)
+ return;
+
+ DBG(TAB, ul_debugobj(tb, "remove all lines"));
+ while (!list_empty(&tb->tb_lines)) {
+ struct libscols_line *ln = list_entry(tb->tb_lines.next,
+ struct libscols_line, ln_lines);
+ if (ln->parent)
+ scols_line_remove_child(ln->parent, ln);
+ scols_table_remove_line(tb, ln);
+ }
+}
+
+/**
+ * scols_table_next_line:
+ * @tb: a pointer to a struct libscols_table instance
+ * @itr: a pointer to a struct libscols_iter instance
+ * @ln: a pointer to a pointer to a struct libscols_line instance
+ *
+ * Finds the next line and returns a pointer to it via @ln.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_next_line(struct libscols_table *tb,
+ struct libscols_iter *itr,
+ struct libscols_line **ln)
+{
+ int rc = 1;
+
+ if (!tb || !itr || !ln)
+ return -EINVAL;
+ *ln = NULL;
+
+ if (!itr->head)
+ SCOLS_ITER_INIT(itr, &tb->tb_lines);
+ if (itr->p != itr->head) {
+ SCOLS_ITER_ITERATE(itr, *ln, struct libscols_line, ln_lines);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * scols_table_new_line:
+ * @tb: table
+ * @parent: parental line or NULL
+ *
+ * This is shortcut for
+ *
+ * ln = scols_new_line();
+ * scols_table_add_line(tb, ln);
+ * scols_line_add_child(parent, ln);
+ *
+ *
+ * Returns: newly allocate line
+ */
+struct libscols_line *scols_table_new_line(struct libscols_table *tb,
+ struct libscols_line *parent)
+{
+ struct libscols_line *ln;
+
+ if (!tb)
+ return NULL;
+
+ ln = scols_new_line();
+ if (!ln)
+ return NULL;
+
+ if (scols_table_add_line(tb, ln))
+ goto err;
+ if (parent)
+ scols_line_add_child(parent, ln);
+
+ scols_unref_line(ln); /* ref-counter incremented by scols_table_add_line() */
+ return ln;
+err:
+ scols_unref_line(ln);
+ return NULL;
+}
+
+/**
+ * scols_table_get_line:
+ * @tb: table
+ * @n: column number (0..N)
+ *
+ * Returns: a line or NULL
+ */
+struct libscols_line *scols_table_get_line(struct libscols_table *tb,
+ size_t n)
+{
+ struct libscols_iter itr;
+ struct libscols_line *ln;
+
+ if (!tb)
+ return NULL;
+ if (n >= tb->nlines)
+ return NULL;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->seqnum == n)
+ return ln;
+ }
+ return NULL;
+}
+
+/**
+ * scols_copy_table:
+ * @tb: table
+ *
+ * Creates a new independent table copy, except struct libscols_symbols that
+ * are shared between the tables.
+ *
+ * Returns: a newly allocated copy of @tb
+ */
+struct libscols_table *scols_copy_table(struct libscols_table *tb)
+{
+ struct libscols_table *ret;
+ struct libscols_line *ln;
+ struct libscols_column *cl;
+ struct libscols_iter itr;
+
+ if (!tb)
+ return NULL;
+ ret = scols_new_table();
+ if (!ret)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "copy"));
+
+ if (tb->symbols)
+ scols_table_set_symbols(ret, tb->symbols);
+
+ /* columns */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_column(tb, &itr, &cl) == 0) {
+ cl = scols_copy_column(cl);
+ if (!cl)
+ goto err;
+ if (scols_table_add_column(ret, cl))
+ goto err;
+ scols_unref_column(cl);
+ }
+
+ /* lines */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ struct libscols_line *newln = scols_copy_line(ln);
+ if (!newln)
+ goto err;
+ if (scols_table_add_line(ret, newln))
+ goto err;
+ if (ln->parent) {
+ struct libscols_line *p =
+ scols_table_get_line(ret, ln->parent->seqnum);
+ if (p)
+ scols_line_add_child(p, newln);
+ }
+ scols_unref_line(newln);
+ }
+
+ /* separators */
+ if (scols_table_set_column_separator(ret, tb->colsep) ||
+ scols_table_set_line_separator(ret, tb->linesep))
+ goto err;
+
+ return ret;
+err:
+ scols_unref_table(ret);
+ return NULL;
+}
+
+/**
+ * scols_table_set_default_symbols:
+ * @tb: table
+ *
+ * The library check the current environment to select ASCII or UTF8 symbols.
+ * This default behavior could be controlled by scols_table_enable_ascii().
+ *
+ * Use scols_table_set_symbols() to unset symbols or use your own setting.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_table_set_default_symbols(struct libscols_table *tb)
+{
+ struct libscols_symbols *sy;
+ int rc;
+
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "setting default symbols"));
+
+ sy = scols_new_symbols();
+ if (!sy)
+ return -ENOMEM;
+
+#if defined(HAVE_WIDECHAR)
+ if (!scols_table_is_ascii(tb) &&
+ !strcmp(nl_langinfo(CODESET), "UTF-8")) {
+ /* tree chart */
+ scols_symbols_set_branch(sy, UTF_VR UTF_H);
+ scols_symbols_set_vertical(sy, UTF_V " ");
+ scols_symbols_set_right(sy, UTF_UR UTF_H);
+ /* groups chart */
+ scols_symbols_set_group_horizontal(sy, UTF_H3);
+ scols_symbols_set_group_vertical(sy, UTF_V3);
+
+ scols_symbols_set_group_first_member(sy, UTF_DR UTF_H3 UTF_TR);
+ scols_symbols_set_group_last_member(sy, UTF_UR UTF_DH UTF_TR);
+ scols_symbols_set_group_middle_member(sy, UTF_VR UTF_H3 UTF_TR);
+ scols_symbols_set_group_last_child(sy, UTF_UR UTF_H3);
+ scols_symbols_set_group_middle_child(sy, UTF_VR UTF_H3);
+ } else
+#endif
+ {
+ /* tree chart */
+ scols_symbols_set_branch(sy, "|-");
+ scols_symbols_set_vertical(sy, "| ");
+ scols_symbols_set_right(sy, "`-");
+ /* groups chart */
+ scols_symbols_set_group_horizontal(sy, "-");
+ scols_symbols_set_group_vertical(sy, "|");
+
+ scols_symbols_set_group_first_member(sy, ",->");
+ scols_symbols_set_group_last_member(sy, "'->");
+ scols_symbols_set_group_middle_member(sy, "|->");
+ scols_symbols_set_group_last_child(sy, "`-");
+ scols_symbols_set_group_middle_child(sy, "|-");
+ }
+ scols_symbols_set_title_padding(sy, " ");
+ scols_symbols_set_cell_padding(sy, " ");
+
+ rc = scols_table_set_symbols(tb, sy);
+ scols_unref_symbols(sy);
+ return rc;
+}
+
+
+/**
+ * scols_table_set_symbols:
+ * @tb: table
+ * @sy: symbols or NULL
+ *
+ * Add a reference to @sy from the table. The symbols are used by library to
+ * draw tree output. If no symbols are used for the table then library creates
+ * default temporary symbols to draw output by scols_table_set_default_symbols().
+ *
+ * If @sy is NULL then remove reference from the currently used symbols.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_set_symbols(struct libscols_table *tb,
+ struct libscols_symbols *sy)
+{
+ if (!tb)
+ return -EINVAL;
+
+ /* remove old */
+ if (tb->symbols) {
+ DBG(TAB, ul_debugobj(tb, "remove symbols reference"));
+ scols_unref_symbols(tb->symbols);
+ tb->symbols = NULL;
+ }
+
+ /* set new */
+ if (sy) { /* ref user defined */
+ DBG(TAB, ul_debugobj(tb, "set symbols"));
+ tb->symbols = sy;
+ scols_ref_symbols(sy);
+ }
+ return 0;
+}
+
+/**
+ * scols_table_get_symbols:
+ * @tb: table
+ *
+ * Returns: pointer to symbols table.
+ *
+ * Since: 2.29
+ */
+struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb)
+{
+ return tb->symbols;
+}
+
+/**
+ * scols_table_enable_nolinesep:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable line separator printing. This is useful if you want to
+ * re-printing the same line more than once (e.g. progress bar). Don't use it
+ * if you're not sure.
+ *
+ * Note that for the last line in the table the separator is disabled at all.
+ * The library differentiate between table terminator and line terminator
+ * (although for standard output \n byte is used in both cases).
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_nolinesep(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "nolinesep: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->no_linesep = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_is_nolinesep:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Returns: 1 if line separator printing is disabled.
+ *
+ * Since: 2.29
+ */
+int scols_table_is_nolinesep(const struct libscols_table *tb)
+{
+ return tb->no_linesep;
+}
+
+/**
+ * scols_table_enable_colors:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable colors.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_colors(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "colors: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->colors_wanted = enable;
+ return 0;
+}
+
+/**
+ * scols_table_enable_raw:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable raw output format. The parsable output formats
+ * (export, raw, JSON, ...) are mutually exclusive.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_raw(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "raw: %s", enable ? "ENABLE" : "DISABLE"));
+ if (enable)
+ tb->format = SCOLS_FMT_RAW;
+ else if (tb->format == SCOLS_FMT_RAW)
+ tb->format = 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_json:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable JSON output format. The parsable output formats
+ * (export, raw, JSON, ...) are mutually exclusive.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.27
+ */
+int scols_table_enable_json(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "json: %s", enable ? "ENABLE" : "DISABLE"));
+ if (enable)
+ tb->format = SCOLS_FMT_JSON;
+ else if (tb->format == SCOLS_FMT_JSON)
+ tb->format = 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_export:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable export output format (COLUMNAME="value" ...).
+ * The parsable output formats (export and raw) are mutually exclusive.
+ *
+ * See also scols_table_enable_shellvar(). Note that in version 2.37 (and only
+ * in this version) scols_table_enable_shellvar() functionality has been
+ * automatically enabled for "export" format. This behavior has been reverted
+ * in version 2.38 due to backward compatibility issues. Now it's necessary to
+ * explicitly call scols_table_enable_shellvar().
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_export(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "export: %s", enable ? "ENABLE" : "DISABLE"));
+ if (enable)
+ tb->format = SCOLS_FMT_EXPORT;
+ else if (tb->format == SCOLS_FMT_EXPORT)
+ tb->format = 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_shellvar:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Force library to print column names to be compatible with shell requirements
+ * to variable names. For example "1FOO%" will be printed as "_1FOO_PCT".
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.38
+ */
+int scols_table_enable_shellvar(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "shellvar: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->is_shellvar = enable ? 1 : 0;
+ return 0;
+}
+
+
+/**
+ * scols_table_enable_ascii:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * The ASCII-only output is relevant for tree-like outputs. The library
+ * checks if the current environment is UTF8 compatible by default. This
+ * function overrides this check and force the library to use ASCII chars
+ * for the tree.
+ *
+ * If a custom libcols_symbols are specified (see scols_table_set_symbols()
+ * then ASCII flag setting is ignored.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_ascii(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "ascii: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->ascii = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_noheadings:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable header line.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_noheadings(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+ DBG(TAB, ul_debugobj(tb, "noheading: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->no_headings = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_header_repeat:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Enable/disable header line repeat. The header line is printed only once by
+ * default. Note that the flag will be silently ignored and disabled if the
+ * output is not on terminal or output format is JSON, raw, etc.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.31
+ */
+int scols_table_enable_header_repeat(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+ DBG(TAB, ul_debugobj(tb, "header-repeat: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->header_repeat = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_maxout:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * The extra space after last column is ignored by default. The output
+ * maximization add padding for all columns.
+ *
+ * This setting is mutually exclusive to scols_table_enable_minout().
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_maxout(struct libscols_table *tb, int enable)
+{
+ if (!tb || tb->minout)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->maxout = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_minout:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Force library to terminate line after last column with data. The extra
+ * padding is not added to the empty cells at the end of the line. The default is fill
+ * tailing empty cells except the last line cell.
+ *
+ * This setting is mutually exclusive to scols_table_enable_maxout().
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.35
+ */
+int scols_table_enable_minout(struct libscols_table *tb, int enable)
+{
+ if (!tb || tb->maxout)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "minout: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->minout = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_enable_nowrap:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * Never continue on next line, remove last column(s) when too large, truncate last column.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.28
+ */
+int scols_table_enable_nowrap(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+ DBG(TAB, ul_debugobj(tb, "nowrap: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->no_wrap = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_is_nowrap:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Returns: 1 if nowrap is enabled.
+ *
+ * Since: 2.29
+ */
+int scols_table_is_nowrap(const struct libscols_table *tb)
+{
+ return tb->no_wrap;
+}
+
+/**
+ * scols_table_enable_noencoding:
+ * @tb: table
+ * @enable: 1 or 0
+ *
+ * The library encode non-printable and control chars by \xHEX by default.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ *
+ * Since: 2.31
+ */
+int scols_table_enable_noencoding(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+ DBG(TAB, ul_debugobj(tb, "encoding: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->no_encode = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * scols_table_is_noencoding:
+ * @tb: a pointer to a struct libscols_table instance
+ *
+ * Returns: 1 if encoding is disabled.
+ *
+ * Since: 2.31
+ */
+int scols_table_is_noencoding(const struct libscols_table *tb)
+{
+ return tb->no_encode;
+}
+
+/**
+ * scols_table_colors_wanted:
+ * @tb: table
+ *
+ * Returns: 1 if colors are enabled.
+ */
+int scols_table_colors_wanted(const struct libscols_table *tb)
+{
+ return tb->colors_wanted;
+}
+
+/**
+ * scols_table_is_empty:
+ * @tb: table
+ *
+ * Returns: 1 if the table is empty.
+ */
+int scols_table_is_empty(const struct libscols_table *tb)
+{
+ return !tb->nlines;
+}
+
+/**
+ * scols_table_is_ascii:
+ * @tb: table
+ *
+ * Returns: 1 if ASCII tree is enabled.
+ */
+int scols_table_is_ascii(const struct libscols_table *tb)
+{
+ return tb->ascii;
+}
+
+/**
+ * scols_table_is_noheadings:
+ * @tb: table
+ *
+ * Returns: 1 if header output is disabled.
+ */
+int scols_table_is_noheadings(const struct libscols_table *tb)
+{
+ return tb->no_headings;
+}
+
+/**
+ * scols_table_is_header_repeat
+ * @tb: table
+ *
+ * Returns: 1 if header repeat is enabled.
+ *
+ * Since: 2.31
+ */
+int scols_table_is_header_repeat(const struct libscols_table *tb)
+{
+ return tb->header_repeat;
+}
+
+/**
+ * scols_table_is_export:
+ * @tb: table
+ *
+ * Returns: 1 if export output format is enabled.
+ */
+int scols_table_is_export(const struct libscols_table *tb)
+{
+ return tb->format == SCOLS_FMT_EXPORT;
+}
+
+/**
+ * scols_table_is_shellvar:
+ * @tb: table
+ *
+ * Returns: 1 if column names has to be compatible with shell requirements
+ * to variable names
+ *
+ * Since: 2.38
+ */
+int scols_table_is_shellvar(const struct libscols_table *tb)
+{
+ return tb->is_shellvar;
+}
+
+/**
+ * scols_table_is_raw:
+ * @tb: table
+ *
+ * Returns: 1 if raw output format is enabled.
+ */
+int scols_table_is_raw(const struct libscols_table *tb)
+{
+ return tb->format == SCOLS_FMT_RAW;
+}
+
+/**
+ * scols_table_is_json:
+ * @tb: table
+ *
+ * Returns: 1 if JSON output format is enabled.
+ *
+ * Since: 2.27
+ */
+int scols_table_is_json(const struct libscols_table *tb)
+{
+ return tb->format == SCOLS_FMT_JSON;
+}
+
+/**
+ * scols_table_is_maxout
+ * @tb: table
+ *
+ * Returns: 1 if output maximization is enabled or 0
+ */
+int scols_table_is_maxout(const struct libscols_table *tb)
+{
+ return tb->maxout;
+}
+
+/**
+ * scols_table_is_minout
+ * @tb: table
+ *
+ * Returns: 1 if output minimization is enabled or 0
+ *
+ * Since: 2.35
+ */
+int scols_table_is_minout(const struct libscols_table *tb)
+{
+ return tb->minout;
+}
+
+/**
+ * scols_table_is_tree:
+ * @tb: table
+ *
+ * Returns: returns 1 tree-like output is expected.
+ */
+int scols_table_is_tree(const struct libscols_table *tb)
+{
+ return tb->ntreecols > 0;
+}
+
+/**
+ * scols_table_set_column_separator:
+ * @tb: table
+ * @sep: separator
+ *
+ * Sets the column separator of @tb to @sep.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_set_column_separator(struct libscols_table *tb, const char *sep)
+{
+ return strdup_to_struct_member(tb, colsep, sep);
+}
+
+/**
+ * scols_table_set_line_separator:
+ * @tb: table
+ * @sep: separator
+ *
+ * Sets the line separator of @tb to @sep.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_table_set_line_separator(struct libscols_table *tb, const char *sep)
+{
+ return strdup_to_struct_member(tb, linesep, sep);
+}
+
+/**
+ * scols_table_get_column_separator:
+ * @tb: table
+ *
+ * Returns: @tb column separator, NULL in case of an error
+ */
+const char *scols_table_get_column_separator(const struct libscols_table *tb)
+{
+ return tb->colsep;
+}
+
+/**
+ * scols_table_get_line_separator:
+ * @tb: table
+ *
+ * Returns: @tb line separator, NULL in case of an error
+ */
+const char *scols_table_get_line_separator(const struct libscols_table *tb)
+{
+ return tb->linesep;
+}
+/* for lines in the struct libscols_line->ln_lines list */
+static int cells_cmp_wrapper_lines(struct list_head *a, struct list_head *b, void *data)
+{
+ struct libscols_column *cl = (struct libscols_column *) data;
+ struct libscols_line *ra, *rb;
+ struct libscols_cell *ca, *cb;
+
+ assert(a);
+ assert(b);
+ assert(cl);
+
+ ra = list_entry(a, struct libscols_line, ln_lines);
+ rb = list_entry(b, struct libscols_line, ln_lines);
+ ca = scols_line_get_cell(ra, cl->seqnum);
+ cb = scols_line_get_cell(rb, cl->seqnum);
+
+ return cl->cmpfunc(ca, cb, cl->cmpfunc_data);
+}
+
+/* for lines in the struct libscols_line->ln_children list */
+static int cells_cmp_wrapper_children(struct list_head *a, struct list_head *b, void *data)
+{
+ struct libscols_column *cl = (struct libscols_column *) data;
+ struct libscols_line *ra, *rb;
+ struct libscols_cell *ca, *cb;
+
+ assert(a);
+ assert(b);
+ assert(cl);
+
+ ra = list_entry(a, struct libscols_line, ln_children);
+ rb = list_entry(b, struct libscols_line, ln_children);
+ ca = scols_line_get_cell(ra, cl->seqnum);
+ cb = scols_line_get_cell(rb, cl->seqnum);
+
+ return cl->cmpfunc(ca, cb, cl->cmpfunc_data);
+}
+
+
+static int sort_line_children(struct libscols_line *ln, struct libscols_column *cl)
+{
+ struct list_head *p;
+
+ if (!list_empty(&ln->ln_branch)) {
+ list_for_each(p, &ln->ln_branch) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+ sort_line_children(chld, cl);
+ }
+
+ list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl);
+ }
+
+ if (is_first_group_member(ln)) {
+ list_for_each(p, &ln->group->gr_children) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+ sort_line_children(chld, cl);
+ }
+
+ list_sort(&ln->group->gr_children, cells_cmp_wrapper_children, cl);
+ }
+
+ return 0;
+}
+
+static int __scols_sort_tree(struct libscols_table *tb, struct libscols_column *cl)
+{
+ struct libscols_line *ln;
+ struct libscols_iter itr;
+
+ if (!tb || !cl || !cl->cmpfunc)
+ return -EINVAL;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0)
+ sort_line_children(ln, cl);
+ return 0;
+}
+
+/**
+ * scols_sort_table:
+ * @tb: table
+ * @cl: order by this column or NULL
+ *
+ * Orders the table by the column. See also scols_column_set_cmpfunc(). If the
+ * tree output is enabled then children in the tree are recursively sorted too.
+ *
+ * The column @cl is saved as the default sort column to the @tb and the next time
+ * is possible to call scols_sort_table(tb, NULL). The saved column is also used by
+ * scols_sort_table_by_tree().
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl)
+{
+ if (!tb)
+ return -EINVAL;
+ if (!cl)
+ cl = tb->dflt_sort_column;
+ if (!cl || !cl->cmpfunc)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "sorting table by %zu column", cl->seqnum));
+ list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl);
+
+ if (scols_table_is_tree(tb))
+ __scols_sort_tree(tb, cl);
+
+ if (cl && cl != tb->dflt_sort_column)
+ tb->dflt_sort_column = cl;
+
+ return 0;
+}
+
+/*
+ * Move all @ln's children after @ln in the table.
+ */
+static struct libscols_line *move_line_and_children(struct libscols_line *ln, struct libscols_line *pre)
+{
+ if (pre) {
+ list_del_init(&ln->ln_lines); /* remove from old position */
+ list_add(&ln->ln_lines, &pre->ln_lines); /* add to the new place (after @pre) */
+ }
+ pre = ln;
+
+ if (!list_empty(&ln->ln_branch)) {
+ struct list_head *p;
+
+ list_for_each(p, &ln->ln_branch) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+ pre = move_line_and_children(chld, pre);
+ }
+ }
+
+ return pre;
+}
+
+/**
+ * scols_sort_table_by_tree:
+ * @tb: table
+ *
+ * Reorders lines in the table by parent->child relation. Note that order of
+ * the lines in the table is independent on the tree hierarchy by default.
+ *
+ * The children of the lines are sorted according to the default sort column
+ * if scols_sort_table() has been previously called.
+ *
+ * Since: 2.30
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_sort_table_by_tree(struct libscols_table *tb)
+{
+ struct libscols_line *ln;
+ struct libscols_iter itr;
+
+ if (!tb)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "sorting table by tree"));
+
+ if (tb->dflt_sort_column)
+ __scols_sort_tree(tb, tb->dflt_sort_column);
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0)
+ move_line_and_children(ln, NULL);
+
+ return 0;
+}
+
+
+/**
+ * scols_table_set_termforce:
+ * @tb: table
+ * @force: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO}
+ *
+ * Forces library to use stdout as terminal, non-terminal or use automatic
+ * detection (default).
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_table_set_termforce(struct libscols_table *tb, int force)
+{
+ if (!tb)
+ return -EINVAL;
+ tb->termforce = force;
+ return 0;
+}
+
+/**
+ * scols_table_get_termforce:
+ * @tb: table
+ *
+ * Returns: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} or a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_table_get_termforce(const struct libscols_table *tb)
+{
+ return tb->termforce;
+}
+
+/**
+ * scols_table_set_termwidth
+ * @tb: table
+ * @width: terminal width
+ *
+ * The library automatically detects terminal width or defaults to 80 chars if
+ * detections is unsuccessful. This function override this behaviour.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.29
+ */
+int scols_table_set_termwidth(struct libscols_table *tb, size_t width)
+{
+ DBG(TAB, ul_debugobj(tb, "set terminatl width: %zu", width));
+ tb->termwidth = width;
+ return 0;
+}
+
+/**
+ * scols_table_get_termwidth
+ * @tb: table
+ *
+ * Returns: terminal width.
+ */
+size_t scols_table_get_termwidth(const struct libscols_table *tb)
+{
+ return tb->termwidth;
+}
+
+/**
+ * scols_table_set_termheight
+ * @tb: table
+ * @height: terminal height (number of lines)
+ *
+ * The library automatically detects terminal height or defaults to 24 lines if
+ * detections is unsuccessful. This function override this behaviour.
+ *
+ * Returns: 0, a negative value in case of an error.
+ *
+ * Since: 2.31
+ */
+int scols_table_set_termheight(struct libscols_table *tb, size_t height)
+{
+ DBG(TAB, ul_debugobj(tb, "set terminatl height: %zu", height));
+ tb->termheight = height;
+ return 0;
+}
+
+/**
+ * scols_table_get_termheight
+ * @tb: table
+ *
+ * Returns: terminal height (number of lines).
+ *
+ * Since: 2.31
+ */
+size_t scols_table_get_termheight(const struct libscols_table *tb)
+{
+ return tb->termheight;
+}
diff --git a/libsmartcols/src/version.c b/libsmartcols/src/version.c
new file mode 100644
index 0000000..e592ccc
--- /dev/null
+++ b/libsmartcols/src/version.c
@@ -0,0 +1,62 @@
+/*
+ * version.c - Return the version of the library
+ *
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * See COPYING.libmount for the License of this software.
+ */
+
+/**
+ * SECTION: version-utils
+ * @title: Version functions
+ * @short_description: functions to get the library version.
+ *
+ * Note that library version is not the same thing as SONAME version. The
+ * libsmarcols uses symbols versioning and SONAME is not modified for releases.
+ *
+ * The library version and symbols version follow util-linux package versioning.
+ */
+
+#include <ctype.h>
+
+#include "smartcolsP.h"
+
+static const char *lib_version = LIBSMARTCOLS_VERSION;
+
+/**
+ * scols_parse_version_string:
+ * @ver_string: version string (e.g "2.18.0")
+ *
+ * Returns: release version code.
+ */
+int scols_parse_version_string(const char *ver_string)
+{
+ const char *cp;
+ int version = 0;
+
+ assert(ver_string);
+
+ for (cp = ver_string; *cp; cp++) {
+ if (*cp == '.')
+ continue;
+ if (!isdigit(*cp))
+ break;
+ version = (version * 10) + (*cp - '0');
+ }
+ return version;
+}
+
+/**
+ * scols_get_library_version:
+ * @ver_string: return pointer to the static library version string if not NULL
+ *
+ * Returns: release version number.
+ */
+int scols_get_library_version(const char **ver_string)
+{
+ if (ver_string)
+ *ver_string = lib_version;
+
+ return scols_parse_version_string(lib_version);
+}
+
diff --git a/libsmartcols/src/walk.c b/libsmartcols/src/walk.c
new file mode 100644
index 0000000..a75fde6
--- /dev/null
+++ b/libsmartcols/src/walk.c
@@ -0,0 +1,152 @@
+#include "smartcolsP.h"
+
+static int walk_line(struct libscols_table *tb,
+ struct libscols_line *ln,
+ struct libscols_column *cl,
+ int (*callback)(struct libscols_table *,
+ struct libscols_line *,
+ struct libscols_column *,
+ void *),
+ void *data)
+{
+ int rc = 0;
+
+ DBG(LINE, ul_debugobj(ln, " wall line"));
+
+ /* we list group children in __scols_print_tree() after tree root node */
+ if (is_group_member(ln) && is_last_group_member(ln) && has_group_children(ln))
+ tb->ngrpchlds_pending++;
+
+ if (has_groups(tb))
+ rc = scols_groups_update_grpset(tb, ln);
+ if (rc == 0)
+ rc = callback(tb, ln, cl, data);
+
+ /* children */
+ if (rc == 0 && has_children(ln)) {
+ struct list_head *p;
+
+ DBG(LINE, ul_debugobj(ln, " children walk"));
+
+ list_for_each(p, &ln->ln_branch) {
+ struct libscols_line *chld = list_entry(p,
+ struct libscols_line, ln_children);
+
+ rc = walk_line(tb, chld, cl, callback, data);
+ if (rc)
+ break;
+ }
+ }
+
+ DBG(LINE, ul_debugobj(ln, "<- walk line done [rc=%d]", rc));
+ return rc;
+}
+
+/* last line in the tree? */
+int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln)
+{
+ if (tb->walk_last_done == 0)
+ return 0;
+ if (tb->ngrpchlds_pending > 0)
+ return 0;
+ if (has_children(ln))
+ return 0;
+ if (is_tree_root(ln) && !is_last_tree_root(tb, ln))
+ return 0;
+ if (is_group_member(ln) && (!is_last_group_member(ln) || has_group_children(ln)))
+ return 0;
+ if (is_child(ln)) {
+ struct libscols_line *parent = ln->parent;
+
+ if (!is_last_child(ln))
+ return 0;
+ while (parent) {
+ if (is_child(parent) && !is_last_child(parent))
+ return 0;
+ if (!parent->parent)
+ break;
+ parent = parent->parent;
+ }
+ if (is_tree_root(parent) && !is_last_tree_root(tb, parent))
+ return 0;
+ }
+ if (is_group_child(ln) && !is_last_group_child(ln))
+ return 0;
+
+ DBG(LINE, ul_debugobj(ln, "last in table"));
+ return 1;
+}
+
+int scols_walk_tree(struct libscols_table *tb,
+ struct libscols_column *cl,
+ int (*callback)(struct libscols_table *,
+ struct libscols_line *,
+ struct libscols_column *,
+ void *),
+ void *data)
+{
+ int rc = 0;
+ struct libscols_line *ln;
+ struct libscols_iter itr;
+
+ assert(tb);
+ DBG(TAB, ul_debugobj(tb, ">> walk start"));
+
+ /* init */
+ tb->ngrpchlds_pending = 0;
+ tb->walk_last_tree_root = NULL;
+ tb->walk_last_done = 0;
+
+ if (has_groups(tb))
+ scols_groups_reset_state(tb);
+
+ /* set pointer to last tree root */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (!tb->walk_last_tree_root)
+ tb->walk_last_tree_root = ln;
+ if (is_child(ln) || is_group_child(ln))
+ continue;
+ tb->walk_last_tree_root = ln;
+ }
+
+ /* walk */
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->parent || ln->parent_group)
+ continue;
+
+ if (tb->walk_last_tree_root == ln)
+ tb->walk_last_done = 1;
+ rc = walk_line(tb, ln, cl, callback, data);
+
+ /* walk group's children */
+ while (rc == 0 && tb->ngrpchlds_pending) {
+ struct libscols_group *gr = scols_grpset_get_printable_children(tb);
+ struct list_head *p;
+
+ DBG(LINE, ul_debugobj(ln, " walk group children [pending=%zu]", tb->ngrpchlds_pending));
+ if (!gr) {
+ DBG(LINE, ul_debugobj(ln, " *** ngrpchlds_pending counter invalid"));
+ tb->ngrpchlds_pending = 0;
+ break;
+ }
+
+ tb->ngrpchlds_pending--;
+
+ list_for_each(p, &gr->gr_children) {
+ struct libscols_line *chld =
+ list_entry(p, struct libscols_line, ln_children);
+
+ rc = walk_line(tb, chld, cl, callback, data);
+ if (rc)
+ break;
+ }
+ }
+ }
+
+ tb->ngrpchlds_pending = 0;
+ tb->walk_last_done = 0;
+ DBG(TAB, ul_debugobj(tb, "<< walk end [rc=%d]", rc));
+ return rc;
+}
diff --git a/libuuid/COPYING b/libuuid/COPYING
new file mode 100644
index 0000000..8a40936
--- /dev/null
+++ b/libuuid/COPYING
@@ -0,0 +1,5 @@
+This library is free software; you can redistribute it and/or
+modify it under the terms of the Modified BSD License.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.BSD-3-Clause file.
diff --git a/libuuid/Makemodule.am b/libuuid/Makemodule.am
new file mode 100644
index 0000000..166be5c
--- /dev/null
+++ b/libuuid/Makemodule.am
@@ -0,0 +1,10 @@
+if BUILD_LIBUUID
+
+include libuuid/man/Makemodule.am
+include libuuid/src/Makemodule.am
+
+pkgconfig_DATA += libuuid/uuid.pc
+PATHFILES += libuuid/uuid.pc
+EXTRA_DIST += libuuid/COPYING
+
+endif # BUILD_LIBUUID
diff --git a/libuuid/man/Makemodule.am b/libuuid/man/Makemodule.am
new file mode 100644
index 0000000..1cdc1b0
--- /dev/null
+++ b/libuuid/man/Makemodule.am
@@ -0,0 +1,27 @@
+
+MANPAGES += \
+ libuuid/man/uuid.3 \
+ libuuid/man/uuid_clear.3 \
+ libuuid/man/uuid_compare.3 \
+ libuuid/man/uuid_copy.3 \
+ libuuid/man/uuid_generate.3 \
+ libuuid/man/uuid_is_null.3 \
+ libuuid/man/uuid_parse.3 \
+ libuuid/man/uuid_time.3 \
+ libuuid/man/uuid_unparse.3
+
+dist_noinst_DATA += \
+ libuuid/man/uuid.3.adoc \
+ libuuid/man/uuid_clear.3.adoc \
+ libuuid/man/uuid_compare.3.adoc \
+ libuuid/man/uuid_copy.3.adoc \
+ libuuid/man/uuid_generate.3.adoc \
+ libuuid/man/uuid_is_null.3.adoc \
+ libuuid/man/uuid_parse.3.adoc \
+ libuuid/man/uuid_time.3.adoc \
+ libuuid/man/uuid_unparse.3.adoc
+
+MANLINKS += \
+ libuuid/man/uuid_generate_random.3 \
+ libuuid/man/uuid_generate_time.3 \
+ libuuid/man/uuid_generate_time_safe.3
diff --git a/libuuid/man/uuid.3 b/libuuid/man/uuid.3
new file mode 100644
index 0000000..3f9819a
--- /dev/null
+++ b/libuuid/man/uuid.3
@@ -0,0 +1,64 @@
+'\" t
+.\" Title: uuid
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid \- DCE compatible Universally Unique Identifier library
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.SH "DESCRIPTION"
+.sp
+The UUID library is used to generate unique identifiers for objects that may be accessible beyond the local system. This library generates UUIDs compatible with those created by the Open Software Foundation (OSF) Distributed Computing Environment (DCE) utility \fBuuidgen\fP(1).
+.sp
+The UUIDs generated by this library can be reasonably expected to be unique within a system, and unique across all systems. They could be used, for instance, to generate unique HTTP cookies across multiple web servers without communication between the servers, and without fear of a name clash.
+.SH "CONFORMING TO"
+.sp
+This library generates UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with \c
+.URL "https://tools.ietf.org/html/rfc4122" "RFC\-4122" "."
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_time\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid.3.adoc b/libuuid/man/uuid.3.adoc
new file mode 100644
index 0000000..c8c6d66
--- /dev/null
+++ b/libuuid/man/uuid.3.adoc
@@ -0,0 +1,80 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid - DCE compatible Universally Unique Identifier library
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+== DESCRIPTION
+
+The UUID library is used to generate unique identifiers for objects that may be accessible beyond the local system. This library generates UUIDs compatible with those created by the Open Software Foundation (OSF) Distributed Computing Environment (DCE) utility *uuidgen*(1).
+
+The UUIDs generated by this library can be reasonably expected to be unique within a system, and unique across all systems. They could be used, for instance, to generate unique HTTP cookies across multiple web servers without communication between the servers, and without fear of a name clash.
+
+== CONFORMING TO
+
+This library generates UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with link:https://tools.ietf.org/html/rfc4122[RFC-4122].
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_time*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_clear.3 b/libuuid/man/uuid_clear.3
new file mode 100644
index 0000000..ef4591b
--- /dev/null
+++ b/libuuid/man/uuid_clear.3
@@ -0,0 +1,59 @@
+'\" t
+.\" Title: uuid_clear
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_CLEAR" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_clear \- reset value of UUID variable to the NULL value
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBvoid uuid_clear(uuid_t \fIuu\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_clear\fP() function sets the value of the supplied uuid variable \fIuu\fP to the NULL value.
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_clear.3.adoc b/libuuid/man/uuid_clear.3.adoc
new file mode 100644
index 0000000..48c3a59
--- /dev/null
+++ b/libuuid/man/uuid_clear.3.adoc
@@ -0,0 +1,75 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_clear(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_clear - reset value of UUID variable to the NULL value
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*void uuid_clear(uuid_t __uu__);*
+
+== DESCRIPTION
+
+The *uuid_clear*() function sets the value of the supplied uuid variable _uu_ to the NULL value.
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_compare.3 b/libuuid/man/uuid_compare.3
new file mode 100644
index 0000000..d92eb24
--- /dev/null
+++ b/libuuid/man/uuid_compare.3
@@ -0,0 +1,58 @@
+'\" t
+.\" Title: uuid_compare
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_COMPARE" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_compare \- compare whether two UUIDs are the same
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBint uuid_compare(uuid_t \fIuu1\fP, uuid_t \fIuu2\fP)\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_compare\fP() function compares the two supplied uuid variables \fIuu1\fP and \fIuu2\fP to each other.
+.SH "RETURN VALUE"
+.sp
+Returns an integer less than, equal to, or greater than zero if \fIuu1\fP is found, respectively, to be lexicographically less than, equal, or greater than \fIuu2\fP.
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_compare.3.adoc b/libuuid/man/uuid_compare.3.adoc
new file mode 100644
index 0000000..1d32475
--- /dev/null
+++ b/libuuid/man/uuid_compare.3.adoc
@@ -0,0 +1,77 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_compare(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_compare - compare whether two UUIDs are the same
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*int uuid_compare(uuid_t __uu1__, uuid_t __uu2__)*
+
+== DESCRIPTION
+
+The *uuid_compare*() function compares the two supplied uuid variables _uu1_ and _uu2_ to each other.
+
+== RETURN VALUE
+
+Returns an integer less than, equal to, or greater than zero if _uu1_ is found, respectively, to be lexicographically less than, equal, or greater than _uu2_.
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_unparse*(3)
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_copy.3 b/libuuid/man/uuid_copy.3
new file mode 100644
index 0000000..bd856c0
--- /dev/null
+++ b/libuuid/man/uuid_copy.3
@@ -0,0 +1,62 @@
+'\" t
+.\" Title: uuid_copy
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-07-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_COPY" "3" "2022-07-20" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_copy \- copy a UUID value
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBvoid uuid_copy(uuid_t \fIdst\fP, uuid_t \fIsrc\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_copy\fP() function copies the UUID variable \fIsrc\fP to \fIdst\fP.
+.SH "RETURN VALUE"
+.sp
+The copied UUID is returned in the location pointed to by \fIdst\fP.
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_copy.3.adoc b/libuuid/man/uuid_copy.3.adoc
new file mode 100644
index 0000000..fee40d6
--- /dev/null
+++ b/libuuid/man/uuid_copy.3.adoc
@@ -0,0 +1,79 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_copy(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_copy - copy a UUID value
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*void uuid_copy(uuid_t __dst__, uuid_t __src__);*
+
+== DESCRIPTION
+
+The *uuid_copy*() function copies the UUID variable _src_ to _dst_.
+
+== RETURN VALUE
+
+The copied UUID is returned in the location pointed to by _dst_.
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_generate.3 b/libuuid/man/uuid_generate.3
new file mode 100644
index 0000000..3ab2770
--- /dev/null
+++ b/libuuid/man/uuid_generate.3
@@ -0,0 +1,90 @@
+'\" t
+.\" Title: uuid_generate
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_GENERATE" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_generate, uuid_generate_random, uuid_generate_time, uuid_generate_time_safe \- create a new unique UUID value
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBvoid uuid_generate(uuid_t \fIout\fP);\fP
+.br
+\fBvoid uuid_generate_random(uuid_t \fIout\fP);\fP
+.br
+\fBvoid uuid_generate_time(uuid_t \fIout\fP);\fP
+.br
+\fBint uuid_generate_time_safe(uuid_t \fIout\fP);\fP
+.br
+\fBvoid uuid_generate_md5(uuid_t \fIout\fP, const uuid_t \fIns\fP, const char \fI*name\fP, size_t \fIlen\fP);\fP
+.br
+\fBvoid uuid_generate_sha1(uuid_t \fIout\fP, const uuid_t \fIns\fP, const char \fI*name\fP, size_t \fIlen\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_generate\fP() function creates a new universally unique identifier (UUID). The uuid will be generated based on high\-quality randomness from \fBgetrandom\fP(2), \fI/dev/urandom\fP, or \fI/dev/random\fP if available. If it is not available, then \fBuuid_generate\fP() will use an alternative algorithm which uses the current time, the local ethernet MAC address (if available), and random data generated using a pseudo\-random generator.
+.sp
+The \fBuuid_generate_random\fP() function forces the use of the all\-random UUID format, even if a high\-quality random number generator is not available, in which case a pseudo\-random generator will be substituted. Note that the use of a pseudo\-random generator may compromise the uniqueness of UUIDs generated in this fashion.
+.sp
+The \fBuuid_generate_time\fP() function forces the use of the alternative algorithm which uses the current time and the local ethernet MAC address (if available). This algorithm used to be the default one used to generate UUIDs, but because of the use of the ethernet MAC address, it can leak information about when and where the UUID was generated. This can cause privacy problems in some applications, so the \fBuuid_generate\fP() function only uses this algorithm if a high\-quality source of randomness is not available. To guarantee uniqueness of UUIDs generated by concurrently running processes, the uuid library uses a global clock state counter (if the process has permissions to gain exclusive access to this file) and/or the \fBuuidd\fP(8) daemon, if it is running already or can be spawned by the process (if installed and the process has enough permissions to run it). If neither of these two synchronization mechanisms can be used, it is theoretically possible that two concurrently running processes obtain the same UUID(s). To tell whether the UUID has been generated in a safe manner, use \fBuuid_generate_time_safe\fP.
+.sp
+The \fBuuid_generate_time_safe\fP() function is similar to \fBuuid_generate_time\fP(), except that it returns a value which denotes whether any of the synchronization mechanisms (see above) has been used.
+.sp
+The UUID is 16 bytes (128 bits) long, which gives approximately 3.4x10^38 unique values (there are approximately 10^80 elementary particles in the universe according to Carl Sagan\(cqs \fICosmos\fP). The new UUID can reasonably be considered unique among all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future.
+.sp
+The \fBuuid_generate_md5\fP() and \fBuuid_generate_sha1\fP() functions generate an MD5 and SHA1 hashed (predictable) UUID based on a well\-known UUID providing the namespace and an arbitrary binary string. The UUIDs conform to V3 and V5 UUIDs per \c
+.URL "https://tools.ietf.org/html/rfc4122" "RFC\-4122" "."
+.SH "RETURN VALUE"
+.sp
+The newly created UUID is returned in the memory location pointed to by \fIout\fP. \fBuuid_generate_time_safe\fP() returns zero if the UUID has been generated in a safe manner, \-1 otherwise.
+.SH "CONFORMING TO"
+.sp
+This library generates UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with \c
+.URL "https://tools.ietf.org/html/rfc4122" "RFC\-4122" "."
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuidgen\fP(1),
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_time\fP(3),
+\fBuuid_unparse\fP(3),
+\fBuuidd\fP(8)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_generate.3.adoc b/libuuid/man/uuid_generate.3.adoc
new file mode 100644
index 0000000..420634d
--- /dev/null
+++ b/libuuid/man/uuid_generate.3.adoc
@@ -0,0 +1,103 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+%Begin-Header%
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+%End-Header%
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_generate(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_generate, uuid_generate_random, uuid_generate_time, uuid_generate_time_safe - create a new unique UUID value
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*void uuid_generate(uuid_t __out__);* +
+*void uuid_generate_random(uuid_t __out__);* +
+*void uuid_generate_time(uuid_t __out__);* +
+*int uuid_generate_time_safe(uuid_t __out__);* +
+*void uuid_generate_md5(uuid_t __out__, const uuid_t __ns__, const char __*name__, size_t __len__);* +
+*void uuid_generate_sha1(uuid_t __out__, const uuid_t __ns__, const char __*name__, size_t __len__);*
+
+== DESCRIPTION
+
+The *uuid_generate*() function creates a new universally unique identifier (UUID). The uuid will be generated based on high-quality randomness from *getrandom*(2), _/dev/urandom_, or _/dev/random_ if available. If it is not available, then *uuid_generate*() will use an alternative algorithm which uses the current time, the local ethernet MAC address (if available), and random data generated using a pseudo-random generator.
+
+The *uuid_generate_random*() function forces the use of the all-random UUID format, even if a high-quality random number generator is not available, in which case a pseudo-random generator will be substituted. Note that the use of a pseudo-random generator may compromise the uniqueness of UUIDs generated in this fashion.
+
+The *uuid_generate_time*() function forces the use of the alternative algorithm which uses the current time and the local ethernet MAC address (if available). This algorithm used to be the default one used to generate UUIDs, but because of the use of the ethernet MAC address, it can leak information about when and where the UUID was generated. This can cause privacy problems in some applications, so the *uuid_generate*() function only uses this algorithm if a high-quality source of randomness is not available. To guarantee uniqueness of UUIDs generated by concurrently running processes, the uuid library uses a global clock state counter (if the process has permissions to gain exclusive access to this file) and/or the *uuidd*(8) daemon, if it is running already or can be spawned by the process (if installed and the process has enough permissions to run it). If neither of these two synchronization mechanisms can be used, it is theoretically possible that two concurrently running processes obtain the same UUID(s). To tell whether the UUID has been generated in a safe manner, use *uuid_generate_time_safe*.
+
+The *uuid_generate_time_safe*() function is similar to *uuid_generate_time*(), except that it returns a value which denotes whether any of the synchronization mechanisms (see above) has been used.
+
+The UUID is 16 bytes (128 bits) long, which gives approximately 3.4x10^38 unique values (there are approximately 10^80 elementary particles in the universe according to Carl Sagan's _Cosmos_). The new UUID can reasonably be considered unique among all UUIDs created on the local system, and among UUIDs created on other systems in the past and in the future.
+
+The *uuid_generate_md5*() and *uuid_generate_sha1*() functions generate an MD5 and SHA1 hashed (predictable) UUID based on a well-known UUID providing the namespace and an arbitrary binary string. The UUIDs conform to V3 and V5 UUIDs per link:https://tools.ietf.org/html/rfc4122[RFC-4122].
+
+== RETURN VALUE
+
+The newly created UUID is returned in the memory location pointed to by _out_. *uuid_generate_time_safe*() returns zero if the UUID has been generated in a safe manner, -1 otherwise.
+
+== CONFORMING TO
+
+This library generates UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with link:https://tools.ietf.org/html/rfc4122[RFC-4122].
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuidgen*(1),
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_time*(3),
+*uuid_unparse*(3),
+*uuidd*(8)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_generate_random.3 b/libuuid/man/uuid_generate_random.3
new file mode 100644
index 0000000..1e51c4e
--- /dev/null
+++ b/libuuid/man/uuid_generate_random.3
@@ -0,0 +1 @@
+.so uuid_generate.3 \ No newline at end of file
diff --git a/libuuid/man/uuid_generate_time.3 b/libuuid/man/uuid_generate_time.3
new file mode 100644
index 0000000..1e51c4e
--- /dev/null
+++ b/libuuid/man/uuid_generate_time.3
@@ -0,0 +1 @@
+.so uuid_generate.3 \ No newline at end of file
diff --git a/libuuid/man/uuid_generate_time_safe.3 b/libuuid/man/uuid_generate_time_safe.3
new file mode 100644
index 0000000..1e51c4e
--- /dev/null
+++ b/libuuid/man/uuid_generate_time_safe.3
@@ -0,0 +1 @@
+.so uuid_generate.3 \ No newline at end of file
diff --git a/libuuid/man/uuid_is_null.3 b/libuuid/man/uuid_is_null.3
new file mode 100644
index 0000000..57ea5a0
--- /dev/null
+++ b/libuuid/man/uuid_is_null.3
@@ -0,0 +1,60 @@
+'\" t
+.\" Title: uuid_is_null
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_IS_NULL" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_is_null \- compare the value of the UUID to the NULL value
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBint uuid_is_null(uuid_t \fIuu\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_is_null\fP() function compares the value of the supplied UUID variable \fIuu\fP to the NULL value. If the value is equal to the NULL UUID, 1 is returned, otherwise 0 is returned.
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_time\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_is_null.3.adoc b/libuuid/man/uuid_is_null.3.adoc
new file mode 100644
index 0000000..6fed1d8
--- /dev/null
+++ b/libuuid/man/uuid_is_null.3.adoc
@@ -0,0 +1,76 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_is_null(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_is_null - compare the value of the UUID to the NULL value
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*int uuid_is_null(uuid_t __uu__);*
+
+== DESCRIPTION
+
+The *uuid_is_null*() function compares the value of the supplied UUID variable _uu_ to the NULL value. If the value is equal to the NULL UUID, 1 is returned, otherwise 0 is returned.
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_time*(3),
+*uuid_parse*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_parse.3 b/libuuid/man/uuid_parse.3
new file mode 100644
index 0000000..ae3ab2d
--- /dev/null
+++ b/libuuid/man/uuid_parse.3
@@ -0,0 +1,71 @@
+'\" t
+.\" Title: uuid_parse
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_PARSE" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_parse \- convert an input UUID string into binary representation
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBint uuid_parse(char *\fIin\fP, uuid_t \fIuu\fP);\fP
+.br
+\fBint uuid_parse_range(char *\fIin_start\fP, char *\fIin_end\fP, uuid_t \fIuu\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_parse\fP() function converts the UUID string given by \fIin\fP into the binary representation. The input UUID is a string of the form 1b4e28ba\-2fa1\-11d2\-883f\-b9a761bde3fb (in \fBprintf\fP(3) format "%08x\-%04x\-%04x\-%04x\-%012x", 36 bytes plus the trailing \(aq\(rs0\(aq).
+.sp
+The \fBuuid_parse_range\fP() function works like \fBuuid_parse\fP() but parses only range in string specified by \fIin_start\fP and \fIin_end\fP pointers.
+.SH "RETURN VALUE"
+.sp
+Upon successfully parsing the input string, 0 is returned, and the UUID is stored in the location pointed to by \fIuu\fP, otherwise \-1 is returned.
+.SH "CONFORMING TO"
+.sp
+This library parses UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with \c
+.URL "https://tools.ietf.org/html/rfc4122" "RFC\-4122" "."
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_time\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_parse.3.adoc b/libuuid/man/uuid_parse.3.adoc
new file mode 100644
index 0000000..8129aae
--- /dev/null
+++ b/libuuid/man/uuid_parse.3.adoc
@@ -0,0 +1,87 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_parse(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_parse - convert an input UUID string into binary representation
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*int uuid_parse(char *__in__, uuid_t __uu__);* +
+*int uuid_parse_range(char *__in_start__, char *__in_end__, uuid_t __uu__);*
+
+== DESCRIPTION
+
+The *uuid_parse*() function converts the UUID string given by _in_ into the binary representation. The input UUID is a string of the form 1b4e28ba-2fa1-11d2-883f-b9a761bde3fb (in *printf*(3) format "%08x-%04x-%04x-%04x-%012x", 36 bytes plus the trailing '\0').
+
+The *uuid_parse_range*() function works like *uuid_parse*() but parses only range in string specified by _in_start_ and _in_end_ pointers.
+
+== RETURN VALUE
+
+Upon successfully parsing the input string, 0 is returned, and the UUID is stored in the location pointed to by _uu_, otherwise -1 is returned.
+
+== CONFORMING TO
+
+This library parses UUIDs compatible with OSF DCE 1.1, and hash based UUIDs V3 and V5 compatible with link:https://tools.ietf.org/html/rfc4122[RFC-4122].
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_time*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_time.3 b/libuuid/man/uuid_time.3
new file mode 100644
index 0000000..3074b6e
--- /dev/null
+++ b/libuuid/man/uuid_time.3
@@ -0,0 +1,63 @@
+'\" t
+.\" Title: uuid_time
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_TIME" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_time \- extract the time at which the UUID was created
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBtime_t uuid_time(uuid_t \fIuu\fP, struct timeval *\fIret_tv\fP)\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_time\fP() function extracts the time at which the supplied time\-based UUID \fIuu\fP was created. Note that the UUID creation time is only encoded within certain types of UUIDs. This function can only reasonably expect to extract the creation time for UUIDs created with the \fBuuid_generate_time\fP(3) and \fBuuid_generate_time_safe\fP(3) functions. It may or may not work with UUIDs created by other mechanisms.
+.SH "RETURN VALUE"
+.sp
+The time at which the UUID was created, in seconds since January 1, 1970 GMT (the epoch), is returned (see \fBtime\fP(2)). The time at which the UUID was created, in seconds and microseconds since the epoch, is also stored in the location pointed to by \fIret_tv\fP (see \fBgettimeofday\fP(2)).
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3),
+\fBuuid_unparse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_time.3.adoc b/libuuid/man/uuid_time.3.adoc
new file mode 100644
index 0000000..5497583
--- /dev/null
+++ b/libuuid/man/uuid_time.3.adoc
@@ -0,0 +1,80 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_time(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_time - extract the time at which the UUID was created
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*time_t uuid_time(uuid_t __uu__, struct timeval *__ret_tv__)*
+
+== DESCRIPTION
+
+The *uuid_time*() function extracts the time at which the supplied time-based UUID _uu_ was created. Note that the UUID creation time is only encoded within certain types of UUIDs. This function can only reasonably expect to extract the creation time for UUIDs created with the *uuid_generate_time*(3) and *uuid_generate_time_safe*(3) functions. It may or may not work with UUIDs created by other mechanisms.
+
+== RETURN VALUE
+
+The time at which the UUID was created, in seconds since January 1, 1970 GMT (the epoch), is returned (see *time*(2)). The time at which the UUID was created, in seconds and microseconds since the epoch, is also stored in the location pointed to by _ret_tv_ (see *gettimeofday*(2)).
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3),
+*uuid_unparse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/man/uuid_unparse.3 b/libuuid/man/uuid_unparse.3
new file mode 100644
index 0000000..e40c5ee
--- /dev/null
+++ b/libuuid/man/uuid_unparse.3
@@ -0,0 +1,69 @@
+'\" t
+.\" Title: uuid_unparse
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.15
+.\" Date: 2022-05-11
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.38.1
+.\" Language: English
+.\"
+.TH "UUID_UNPARSE" "3" "2022-05-11" "util\-linux 2.38.1" "Programmer\(aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+uuid_unparse \- convert a UUID from binary representation to a string
+.SH "SYNOPSIS"
+.sp
+\fB#include <uuid.h>\fP
+.sp
+\fBvoid uuid_unparse(uuid_t \fIuu\fP, char *\fIout\fP);\fP
+.br
+\fBvoid uuid_unparse_upper(uuid_t \fIuu\fP, char *\fIout\fP);\fP
+.br
+\fBvoid uuid_unparse_lower(uuid_t \fIuu\fP, char *\fIout\fP);\fP
+.SH "DESCRIPTION"
+.sp
+The \fBuuid_unparse\fP() function converts the supplied UUID \fIuu\fP from the binary representation into a 36\-byte string (plus trailing \(aq\(rs0\(aq) of the form 1b4e28ba\-2fa1\-11d2\-883f\-0016d3cca427 and stores this value in the character string pointed to by \fIout\fP. The case of the hex digits returned by \fBuuid_unparse\fP() may be upper or lower case, and is dependent on the system\-dependent local default.
+.sp
+If the case of the hex digits is important then the functions \fBuuid_unparse_upper\fP() and \fBuuid_unparse_lower\fP() may be used.
+.SH "CONFORMING TO"
+.sp
+This library unparses UUIDs compatible with OSF DCE 1.1.
+.SH "AUTHORS"
+.sp
+Theodore Y. Ts\(cqo
+.SH "SEE ALSO"
+.sp
+\fBuuid\fP(3),
+\fBuuid_clear\fP(3),
+\fBuuid_compare\fP(3),
+\fBuuid_copy\fP(3),
+\fBuuid_generate\fP(3),
+\fBuuid_time\fP(3),
+\fBuuid_is_null\fP(3),
+\fBuuid_parse\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBlibuuid\fP library is part of the util\-linux package since version 2.15.1. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/libuuid/man/uuid_unparse.3.adoc b/libuuid/man/uuid_unparse.3.adoc
new file mode 100644
index 0000000..a14dc8c
--- /dev/null
+++ b/libuuid/man/uuid_unparse.3.adoc
@@ -0,0 +1,84 @@
+//po4a: entry man manual
+////
+Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+////
+= uuid_unparse(3)
+:doctype: manpage
+:man manual: Programmer's Manual
+:man source: util-linux {release-version}
+:page-layout: base
+:lib: libuuid
+:firstversion: 2.15.1
+
+== NAME
+
+uuid_unparse - convert a UUID from binary representation to a string
+
+== SYNOPSIS
+
+*#include <uuid.h>*
+
+*void uuid_unparse(uuid_t __uu__, char *__out__);* +
+*void uuid_unparse_upper(uuid_t __uu__, char *__out__);* +
+*void uuid_unparse_lower(uuid_t __uu__, char *__out__);*
+
+== DESCRIPTION
+
+The *uuid_unparse*() function converts the supplied UUID _uu_ from the binary representation into a 36-byte string (plus trailing '\0') of the form 1b4e28ba-2fa1-11d2-883f-0016d3cca427 and stores this value in the character string pointed to by _out_. The case of the hex digits returned by *uuid_unparse*() may be upper or lower case, and is dependent on the system-dependent local default.
+
+If the case of the hex digits is important then the functions *uuid_unparse_upper*() and *uuid_unparse_lower*() may be used.
+
+== CONFORMING TO
+
+This library unparses UUIDs compatible with OSF DCE 1.1.
+
+== AUTHORS
+
+Theodore Y. Ts'o
+
+== SEE ALSO
+
+*uuid*(3),
+*uuid_clear*(3),
+*uuid_compare*(3),
+*uuid_copy*(3),
+*uuid_generate*(3),
+*uuid_time*(3),
+*uuid_is_null*(3),
+*uuid_parse*(3)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer-lib.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/libuuid/meson.build b/libuuid/meson.build
new file mode 100644
index 0000000..7d43c61
--- /dev/null
+++ b/libuuid/meson.build
@@ -0,0 +1,47 @@
+dir_libuuid = include_directories('src')
+
+lib_uuid_sources = '''
+ src/uuidP.h
+ src/clear.c
+ src/compare.c
+ src/copy.c
+ src/gen_uuid.c
+ src/isnull.c
+ src/pack.c
+ src/parse.c
+ src/uuidd.h
+ src/uuid_time.c
+'''.split()
+
+predefined_c = files('src/predefined.c')
+unpack_c = files('src/unpack.c')
+unparse_c = files('src/unparse.c')
+
+libuuid_sym = 'src/libuuid.sym'
+libuuid_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libuuid_sym)
+
+lib_uuid = both_libraries(
+ 'uuid',
+ list_h,
+ lib_uuid_sources,
+ predefined_c,
+ unpack_c,
+ unparse_c,
+ randutils_c,
+ md5_c,
+ sha1_c,
+ include_directories : [dir_include, dir_libuuid],
+ link_depends : libuuid_sym,
+ version : libuuid_version,
+ link_args : ['-Wl,--version-script=@0@'.format(libuuid_sym_path)],
+ dependencies : [socket_libs,
+ build_libuuid ? [] : disabler()],
+ install : build_libuuid)
+
+if build_libuuid
+ pkgconfig.generate(lib_uuid,
+ description : 'Universally unique id library',
+ subdirs : 'uuid',
+ version : pc_version)
+ install_headers('src/uuid.h', subdir : 'uuid')
+endif
diff --git a/libuuid/src/Makemodule.am b/libuuid/src/Makemodule.am
new file mode 100644
index 0000000..e58fa26
--- /dev/null
+++ b/libuuid/src/Makemodule.am
@@ -0,0 +1,67 @@
+
+check_PROGRAMS += test_uuid_parser
+test_uuid_parser_SOURCES = libuuid/src/test_uuid.c
+test_uuid_parser_LDADD = libuuid.la $(SOCKET_LIBS) $(LDADD)
+test_uuid_parser_CFLAGS = $(AM_CFLAGS) -I$(ul_libuuid_incdir)
+
+# includes
+uuidincdir = $(includedir)/uuid
+uuidinc_HEADERS = libuuid/src/uuid.h
+
+usrlib_exec_LTLIBRARIES += libuuid.la
+
+libuuid_la_SOURCES = \
+ libuuid/src/uuidP.h \
+ libuuid/src/clear.c \
+ libuuid/src/compare.c \
+ libuuid/src/copy.c \
+ libuuid/src/gen_uuid.c \
+ libuuid/src/isnull.c \
+ libuuid/src/pack.c \
+ libuuid/src/parse.c \
+ libuuid/src/unpack.c \
+ libuuid/src/unparse.c \
+ libuuid/src/uuidd.h \
+ libuuid/src/uuid_time.c \
+ libuuid/src/predefined.c \
+ lib/randutils.c \
+ lib/md5.c \
+ lib/sha1.c
+
+EXTRA_libuuid_la_DEPENDENCIES = \
+ libuuid/src/libuuid.sym
+
+libuuid_la_LIBADD = $(LDADD) $(SOCKET_LIBS)
+
+libuuid_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_libuuid_incdir) \
+ -Ilibuuid/src
+
+libuuid_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libuuid_la_LDFLAGS += libuuid_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libuuid/src/libuuid.sym
+endif
+libuuid_la_LDFLAGS += -version-info $(LIBUUID_VERSION_INFO)
+
+
+EXTRA_DIST += libuuid/src/libuuid.sym
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libuuid:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libuuid.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libuuid.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libuuid.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libuuid.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libuuid.so); \
+ fi
+
+uninstall-hook-libuuid:
+ rm -f $(DESTDIR)$(libdir)/libuuid.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libuuid
+UNINSTALL_HOOKS += uninstall-hook-libuuid
diff --git a/libuuid/src/clear.c b/libuuid/src/clear.c
new file mode 100644
index 0000000..2d91fee
--- /dev/null
+++ b/libuuid/src/clear.c
@@ -0,0 +1,43 @@
+/*
+ * clear.c -- Clear a UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "string.h"
+
+#include "uuidP.h"
+
+void uuid_clear(uuid_t uu)
+{
+ memset(uu, 0, 16);
+}
+
diff --git a/libuuid/src/compare.c b/libuuid/src/compare.c
new file mode 100644
index 0000000..8f3437a
--- /dev/null
+++ b/libuuid/src/compare.c
@@ -0,0 +1,55 @@
+/*
+ * compare.c --- compare whether or not two UUIDs are the same
+ *
+ * Returns 0 if the two UUIDs are different, and 1 if they are the same.
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+#include <string.h>
+
+#define UUCMP(u1,u2) if (u1 != u2) return((u1 < u2) ? -1 : 1);
+
+int uuid_compare(const uuid_t uu1, const uuid_t uu2)
+{
+ struct uuid uuid1, uuid2;
+
+ uuid_unpack(uu1, &uuid1);
+ uuid_unpack(uu2, &uuid2);
+
+ UUCMP(uuid1.time_low, uuid2.time_low);
+ UUCMP(uuid1.time_mid, uuid2.time_mid);
+ UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version);
+ UUCMP(uuid1.clock_seq, uuid2.clock_seq);
+ return memcmp(uuid1.node, uuid2.node, 6);
+}
+
diff --git a/libuuid/src/copy.c b/libuuid/src/copy.c
new file mode 100644
index 0000000..ead33aa
--- /dev/null
+++ b/libuuid/src/copy.c
@@ -0,0 +1,45 @@
+/*
+ * copy.c --- copy UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+
+void uuid_copy(uuid_t dst, const uuid_t src)
+{
+ unsigned char *cp1;
+ const unsigned char *cp2;
+ int i;
+
+ for (i=0, cp1 = dst, cp2 = src; i < 16; i++)
+ *cp1++ = *cp2++;
+}
diff --git a/libuuid/src/gen_uuid.c b/libuuid/src/gen_uuid.c
new file mode 100644
index 0000000..805b40d
--- /dev/null
+++ b/libuuid/src/gen_uuid.c
@@ -0,0 +1,608 @@
+/*
+ * gen_uuid.c --- generate a DCE-compatible uuid
+ *
+ * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/stat.h>
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif
+#if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)
+#include <sys/syscall.h>
+#endif
+
+#include "all-io.h"
+#include "uuidP.h"
+#include "uuidd.h"
+#include "randutils.h"
+#include "strutils.h"
+#include "c.h"
+#include "md5.h"
+#include "sha1.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#ifdef _WIN32
+static void gettimeofday (struct timeval *tv, void *dummy)
+{
+ FILETIME ftime;
+ uint64_t n;
+
+ GetSystemTimeAsFileTime (&ftime);
+ n = (((uint64_t) ftime.dwHighDateTime << 32)
+ + (uint64_t) ftime.dwLowDateTime);
+ if (n) {
+ n /= 10;
+ n -= ((369 * 365 + 89) * (uint64_t) 86400) * 1000000;
+ }
+
+ tv->tv_sec = n / 1000000;
+ tv->tv_usec = n % 1000000;
+}
+
+static int getuid (void)
+{
+ return 1;
+}
+#endif
+
+/*
+ * Get the ethernet hardware address, if we can find it...
+ *
+ * XXX for a windows version, probably should use GetAdaptersInfo:
+ * http://www.codeguru.com/cpp/i-n/network/networkinformation/article.php/c5451
+ * commenting out get_node_id just to get gen_uuid to compile under windows
+ * is not the right way to go!
+ */
+static int get_node_id(unsigned char *node_id)
+{
+#ifdef HAVE_NET_IF_H
+ int sd;
+ struct ifreq ifr, *ifrp;
+ struct ifconf ifc;
+ char buf[1024];
+ int n, i;
+ unsigned char *a = NULL;
+#ifdef HAVE_NET_IF_DL_H
+ struct sockaddr_dl *sdlp;
+#endif
+
+/*
+ * BSD 4.4 defines the size of an ifreq to be
+ * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len
+ * However, under earlier systems, sa_len isn't present, so the size is
+ * just sizeof(struct ifreq)
+ */
+#ifdef HAVE_SA_LEN
+#define ifreq_size(i) max(sizeof(struct ifreq),\
+ sizeof((i).ifr_name)+(i).ifr_addr.sa_len)
+#else
+#define ifreq_size(i) sizeof(struct ifreq)
+#endif /* HAVE_SA_LEN */
+
+ sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sd < 0) {
+ return -1;
+ }
+ memset(buf, 0, sizeof(buf));
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) {
+ close(sd);
+ return -1;
+ }
+ n = ifc.ifc_len;
+ for (i = 0; i < n; i+= ifreq_size(*ifrp) ) {
+ ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i);
+ strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ);
+#ifdef SIOCGIFHWADDR
+ if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+#else
+#ifdef SIOCGENADDR
+ if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char *) ifr.ifr_enaddr;
+#else
+#ifdef HAVE_NET_IF_DL_H
+ sdlp = (struct sockaddr_dl *) &ifrp->ifr_addr;
+ if ((sdlp->sdl_family != AF_LINK) || (sdlp->sdl_alen != 6))
+ continue;
+ a = (unsigned char *) &sdlp->sdl_data[sdlp->sdl_nlen];
+#else
+ /*
+ * XXX we don't have a way of getting the hardware
+ * address
+ */
+ close(sd);
+ return 0;
+#endif /* HAVE_NET_IF_DL_H */
+#endif /* SIOCGENADDR */
+#endif /* SIOCGIFHWADDR */
+ if (a == NULL || (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]))
+ continue;
+ if (node_id) {
+ memcpy(node_id, a, 6);
+ close(sd);
+ return 1;
+ }
+ }
+ close(sd);
+#endif
+ return 0;
+}
+
+/* Assume that the gettimeofday() has microsecond granularity */
+#define MAX_ADJUSTMENT 10
+
+/*
+ * Get clock from global sequence clock counter.
+ *
+ * Return -1 if the clock counter could not be opened/locked (in this case
+ * pseudorandom value is returned in @ret_clock_seq), otherwise return 0.
+ */
+static int get_clock(uint32_t *clock_high, uint32_t *clock_low,
+ uint16_t *ret_clock_seq, int *num)
+{
+ THREAD_LOCAL int adjustment = 0;
+ THREAD_LOCAL struct timeval last = {0, 0};
+ THREAD_LOCAL int state_fd = -2;
+ THREAD_LOCAL FILE *state_f;
+ THREAD_LOCAL uint16_t clock_seq;
+ struct timeval tv;
+ uint64_t clock_reg;
+ mode_t save_umask;
+ int len;
+ int ret = 0;
+
+ if (state_fd == -1)
+ ret = -1;
+
+ if (state_fd == -2) {
+ save_umask = umask(0);
+ state_fd = open(LIBUUID_CLOCK_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0660);
+ (void) umask(save_umask);
+ if (state_fd != -1) {
+ state_f = fdopen(state_fd, "r+" UL_CLOEXECSTR);
+ if (!state_f) {
+ close(state_fd);
+ state_fd = -1;
+ ret = -1;
+ }
+ }
+ else
+ ret = -1;
+ }
+ if (state_fd >= 0) {
+ rewind(state_f);
+ while (flock(state_fd, LOCK_EX) < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ fclose(state_f);
+ close(state_fd);
+ state_fd = -1;
+ ret = -1;
+ break;
+ }
+ }
+ if (state_fd >= 0) {
+ unsigned int cl;
+ unsigned long tv1, tv2;
+ int a;
+
+ if (fscanf(state_f, "clock: %04x tv: %lu %lu adj: %d\n",
+ &cl, &tv1, &tv2, &a) == 4) {
+ clock_seq = cl & 0x3FFF;
+ last.tv_sec = tv1;
+ last.tv_usec = tv2;
+ adjustment = a;
+ }
+ }
+
+ if ((last.tv_sec == 0) && (last.tv_usec == 0)) {
+ ul_random_get_bytes(&clock_seq, sizeof(clock_seq));
+ clock_seq &= 0x3FFF;
+ gettimeofday(&last, NULL);
+ last.tv_sec--;
+ }
+
+try_again:
+ gettimeofday(&tv, NULL);
+ if ((tv.tv_sec < last.tv_sec) ||
+ ((tv.tv_sec == last.tv_sec) &&
+ (tv.tv_usec < last.tv_usec))) {
+ clock_seq = (clock_seq+1) & 0x3FFF;
+ adjustment = 0;
+ last = tv;
+ } else if ((tv.tv_sec == last.tv_sec) &&
+ (tv.tv_usec == last.tv_usec)) {
+ if (adjustment >= MAX_ADJUSTMENT)
+ goto try_again;
+ adjustment++;
+ } else {
+ adjustment = 0;
+ last = tv;
+ }
+
+ clock_reg = tv.tv_usec*10 + adjustment;
+ clock_reg += ((uint64_t) tv.tv_sec)*10000000;
+ clock_reg += (((uint64_t) 0x01B21DD2) << 32) + 0x13814000;
+
+ if (num && (*num > 1)) {
+ adjustment += *num - 1;
+ last.tv_usec += adjustment / 10;
+ adjustment = adjustment % 10;
+ last.tv_sec += last.tv_usec / 1000000;
+ last.tv_usec = last.tv_usec % 1000000;
+ }
+
+ if (state_fd >= 0) {
+ rewind(state_f);
+ len = fprintf(state_f,
+ "clock: %04x tv: %016ld %08ld adj: %08d\n",
+ clock_seq, (long)last.tv_sec, (long)last.tv_usec, adjustment);
+ fflush(state_f);
+ if (ftruncate(state_fd, len) < 0) {
+ fprintf(state_f, " \n");
+ fflush(state_f);
+ }
+ rewind(state_f);
+ flock(state_fd, LOCK_UN);
+ }
+
+ *clock_high = clock_reg >> 32;
+ *clock_low = clock_reg;
+ *ret_clock_seq = clock_seq;
+ return ret;
+}
+
+#if defined(HAVE_UUIDD) && defined(HAVE_SYS_UN_H)
+
+/*
+ * Try using the uuidd daemon to generate the UUID
+ *
+ * Returns 0 on success, non-zero on failure.
+ */
+static int get_uuid_via_daemon(int op, uuid_t out, int *num)
+{
+ char op_buf[64];
+ int op_len;
+ int s;
+ ssize_t ret;
+ int32_t reply_len = 0, expected = 16;
+ struct sockaddr_un srv_addr;
+
+ if (sizeof(UUIDD_SOCKET_PATH) > sizeof(srv_addr.sun_path))
+ return -1;
+
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -1;
+
+ srv_addr.sun_family = AF_UNIX;
+ xstrncpy(srv_addr.sun_path, UUIDD_SOCKET_PATH, sizeof(srv_addr.sun_path));
+
+ if (connect(s, (const struct sockaddr *) &srv_addr,
+ sizeof(struct sockaddr_un)) < 0)
+ goto fail;
+
+ op_buf[0] = op;
+ op_len = 1;
+ if (op == UUIDD_OP_BULK_TIME_UUID) {
+ memcpy(op_buf+1, num, sizeof(*num));
+ op_len += sizeof(*num);
+ expected += sizeof(*num);
+ }
+
+ ret = write(s, op_buf, op_len);
+ if (ret < 1)
+ goto fail;
+
+ ret = read_all(s, (char *) &reply_len, sizeof(reply_len));
+ if (ret < 0)
+ goto fail;
+
+ if (reply_len != expected)
+ goto fail;
+
+ ret = read_all(s, op_buf, reply_len);
+
+ if (op == UUIDD_OP_BULK_TIME_UUID)
+ memcpy(op_buf+16, num, sizeof(int));
+
+ memcpy(out, op_buf, 16);
+
+ close(s);
+ return ((ret == expected) ? 0 : -1);
+
+fail:
+ close(s);
+ return -1;
+}
+
+#else /* !defined(HAVE_UUIDD) && defined(HAVE_SYS_UN_H) */
+static int get_uuid_via_daemon(int op __attribute__((__unused__)),
+ uuid_t out __attribute__((__unused__)),
+ int *num __attribute__((__unused__)))
+{
+ return -1;
+}
+#endif
+
+int __uuid_generate_time(uuid_t out, int *num)
+{
+ static unsigned char node_id[6];
+ static int has_init = 0;
+ struct uuid uu;
+ uint32_t clock_mid;
+ int ret;
+
+ if (!has_init) {
+ if (get_node_id(node_id) <= 0) {
+ ul_random_get_bytes(node_id, 6);
+ /*
+ * Set multicast bit, to prevent conflicts
+ * with IEEE 802 addresses obtained from
+ * network cards
+ */
+ node_id[0] |= 0x01;
+ }
+ has_init = 1;
+ }
+ ret = get_clock(&clock_mid, &uu.time_low, &uu.clock_seq, num);
+ uu.clock_seq |= 0x8000;
+ uu.time_mid = (uint16_t) clock_mid;
+ uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000;
+ memcpy(uu.node, node_id, 6);
+ uuid_pack(&uu, out);
+ return ret;
+}
+
+/*
+ * Generate time-based UUID and store it to @out
+ *
+ * Tries to guarantee uniqueness of the generated UUIDs by obtaining them from the uuidd daemon,
+ * or, if uuidd is not usable, by using the global clock state counter (see get_clock()).
+ * If neither of these is possible (e.g. because of insufficient permissions), it generates
+ * the UUID anyway, but returns -1. Otherwise, returns 0.
+ */
+static int uuid_generate_time_generic(uuid_t out) {
+#ifdef HAVE_TLS
+ THREAD_LOCAL int num = 0;
+ THREAD_LOCAL int cache_size = 1;
+ THREAD_LOCAL struct uuid uu;
+ THREAD_LOCAL time_t last_time = 0;
+ time_t now;
+
+ if (num > 0) {
+ now = time(NULL);
+ if (now > last_time+1)
+ num = 0;
+ }
+ if (num <= 0) {
+ /*
+ * num + OP_BULK provides a local cache in each application.
+ * Start with a small cache size to cover short running applications
+ * and increment the cache size over the runntime.
+ */
+ if (cache_size < 1000000)
+ cache_size *= 10;
+ num = cache_size;
+
+ if (get_uuid_via_daemon(UUIDD_OP_BULK_TIME_UUID,
+ out, &num) == 0) {
+ last_time = time(NULL);
+ uuid_unpack(out, &uu);
+ num--;
+ return 0;
+ }
+ num = 0;
+ }
+ if (num > 0) {
+ uu.time_low++;
+ if (uu.time_low == 0) {
+ uu.time_mid++;
+ if (uu.time_mid == 0)
+ uu.time_hi_and_version++;
+ }
+ num--;
+ uuid_pack(&uu, out);
+ return 0;
+ }
+#else
+ if (get_uuid_via_daemon(UUIDD_OP_TIME_UUID, out, 0) == 0)
+ return 0;
+#endif
+
+ return __uuid_generate_time(out, NULL);
+}
+
+/*
+ * Generate time-based UUID and store it to @out.
+ *
+ * Discards return value from uuid_generate_time_generic()
+ */
+void uuid_generate_time(uuid_t out)
+{
+ (void)uuid_generate_time_generic(out);
+}
+
+
+int uuid_generate_time_safe(uuid_t out)
+{
+ return uuid_generate_time_generic(out);
+}
+
+
+int __uuid_generate_random(uuid_t out, int *num)
+{
+ uuid_t buf;
+ struct uuid uu;
+ int i, n, r = 0;
+
+ if (!num || !*num)
+ n = 1;
+ else
+ n = *num;
+
+ for (i = 0; i < n; i++) {
+ if (ul_random_get_bytes(buf, sizeof(buf)))
+ r = -1;
+ uuid_unpack(buf, &uu);
+
+ uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+ uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF)
+ | 0x4000;
+ uuid_pack(&uu, out);
+ out += sizeof(uuid_t);
+ }
+
+ return r;
+}
+
+void uuid_generate_random(uuid_t out)
+{
+ int num = 1;
+ /* No real reason to use the daemon for random uuid's -- yet */
+
+ __uuid_generate_random(out, &num);
+}
+
+/*
+ * This is the generic front-end to __uuid_generate_random and
+ * uuid_generate_time. It uses __uuid_generate_random output
+ * only if high-quality randomness is available.
+ */
+void uuid_generate(uuid_t out)
+{
+ int num = 1;
+
+ if (__uuid_generate_random(out, &num))
+ uuid_generate_time(out);
+}
+
+/*
+ * Generate an MD5 hashed (predictable) UUID based on a well-known UUID
+ * providing the namespace and an arbitrary binary string.
+ */
+void uuid_generate_md5(uuid_t out, const uuid_t ns, const char *name, size_t len)
+{
+ UL_MD5_CTX ctx;
+ char hash[UL_MD5LENGTH];
+ uuid_t buf;
+ struct uuid uu;
+
+ ul_MD5Init(&ctx);
+ ul_MD5Update(&ctx, ns, sizeof(uuid_t));
+ ul_MD5Update(&ctx, (const unsigned char *)name, len);
+ ul_MD5Final((unsigned char *)hash, &ctx);
+
+ assert(sizeof(buf) <= sizeof(hash));
+
+ memcpy(buf, hash, sizeof(buf));
+ uuid_unpack(buf, &uu);
+
+ uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+ uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x3000;
+ uuid_pack(&uu, out);
+}
+
+/*
+ * Generate a SHA1 hashed (predictable) UUID based on a well-known UUID
+ * providing the namespace and an arbitrary binary string.
+ */
+void uuid_generate_sha1(uuid_t out, const uuid_t ns, const char *name, size_t len)
+{
+ UL_SHA1_CTX ctx;
+ char hash[UL_SHA1LENGTH];
+ uuid_t buf;
+ struct uuid uu;
+
+ ul_SHA1Init(&ctx);
+ ul_SHA1Update(&ctx, ns, sizeof(uuid_t));
+ ul_SHA1Update(&ctx, (const unsigned char *)name, len);
+ ul_SHA1Final((unsigned char *)hash, &ctx);
+
+ assert(sizeof(buf) <= sizeof(hash));
+
+ memcpy(buf, hash, sizeof(buf));
+ uuid_unpack(buf, &uu);
+
+ uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+ uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x5000;
+ uuid_pack(&uu, out);
+}
diff --git a/libuuid/src/isnull.c b/libuuid/src/isnull.c
new file mode 100644
index 0000000..ecb44ea
--- /dev/null
+++ b/libuuid/src/isnull.c
@@ -0,0 +1,44 @@
+/*
+ * isnull.c --- Check whether or not the UUID is null
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+#include <string.h>
+
+/* Returns 1 if the uuid is the NULL uuid */
+int uuid_is_null(const uuid_t uu)
+{
+ const uuid_t nil = { 0 };
+
+ return !memcmp(uu, nil, sizeof(nil));
+}
diff --git a/libuuid/src/libuuid.sym b/libuuid/src/libuuid.sym
new file mode 100644
index 0000000..3424533
--- /dev/null
+++ b/libuuid/src/libuuid.sym
@@ -0,0 +1,66 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol 'foo'
+ * can't run with old libbrary.so not providing 'foo' - the global SONAME
+ * version info can't enforce this since we never change the SONAME.
+ *
+ * The original libuuid from e2fsprogs (<=1.41.5) does not to use
+ * symbol versioning -- all the original symbols are in UUID_1.0 now.
+ *
+ * Copyright (C) 2011-2017 Kareil Zak <kzak@redhat.com>
+ */
+UUID_1.0 {
+global:
+ uuid_clear;
+ uuid_compare;
+ uuid_copy;
+ uuid_generate;
+ uuid_generate_random;
+ uuid_generate_time;
+ uuid_is_null;
+ uuid_parse;
+ uuid_unparse;
+ uuid_unparse_lower;
+ uuid_unparse_upper;
+ uuid_time;
+ uuid_type;
+ uuid_variant;
+};
+
+/*
+ * version(s) since util-linux 2.20
+ */
+UUID_2.20 {
+global:
+ uuid_generate_time_safe;
+} UUID_1.0;
+
+/*
+ * version(s) since util-linux.2.31
+ */
+UUID_2.31 {
+global:
+ uuid_generate_md5;
+ uuid_generate_sha1;
+ uuid_get_template;
+} UUID_2.20;
+
+/*
+ * version(s) since util-linux.2.36
+ */
+UUID_2.36 {
+global:
+ uuid_parse_range;
+} UUID_2.31;
+
+
+/*
+ * __uuid_* this is not part of the official API, this is
+ * uuidd (uuid daemon) specific stuff. Hell.
+ */
+UUIDD_PRIVATE {
+global:
+ __uuid_generate_time;
+ __uuid_generate_random;
+local:
+ *;
+};
diff --git a/libuuid/src/pack.c b/libuuid/src/pack.c
new file mode 100644
index 0000000..6e12476
--- /dev/null
+++ b/libuuid/src/pack.c
@@ -0,0 +1,69 @@
+/*
+ * Internal routine for packing UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_pack(const struct uuid *uu, uuid_t ptr)
+{
+ uint32_t tmp;
+ unsigned char *out = ptr;
+
+ tmp = uu->time_low;
+ out[3] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[2] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[1] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[0] = (unsigned char) tmp;
+
+ tmp = uu->time_mid;
+ out[5] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[4] = (unsigned char) tmp;
+
+ tmp = uu->time_hi_and_version;
+ out[7] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[6] = (unsigned char) tmp;
+
+ tmp = uu->clock_seq;
+ out[9] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[8] = (unsigned char) tmp;
+
+ memcpy(out+10, uu->node, 6);
+}
+
diff --git a/libuuid/src/parse.c b/libuuid/src/parse.c
new file mode 100644
index 0000000..c3e2281
--- /dev/null
+++ b/libuuid/src/parse.c
@@ -0,0 +1,98 @@
+/*
+ * parse.c --- UUID parsing
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "c.h"
+#include "uuidP.h"
+
+int uuid_parse(const char *in, uuid_t uu)
+{
+ size_t len = strlen(in);
+ if (len != 36)
+ return -1;
+
+ return uuid_parse_range(in, in + len, uu);
+}
+
+int uuid_parse_range(const char *in_start, const char *in_end, uuid_t uu)
+{
+ struct uuid uuid;
+ int i;
+ const char *cp;
+ char buf[3];
+
+ if ((in_end - in_start) != 36)
+ return -1;
+ for (i=0, cp = in_start; i < 36; i++,cp++) {
+ if ((i == 8) || (i == 13) || (i == 18) ||
+ (i == 23)) {
+ if (*cp == '-')
+ continue;
+ return -1;
+ }
+
+ if (!isxdigit(*cp))
+ return -1;
+ }
+ errno = 0;
+ uuid.time_low = strtoul(in_start, NULL, 16);
+
+ if (!errno)
+ uuid.time_mid = strtoul(in_start+9, NULL, 16);
+ if (!errno)
+ uuid.time_hi_and_version = strtoul(in_start+14, NULL, 16);
+ if (!errno)
+ uuid.clock_seq = strtoul(in_start+19, NULL, 16);
+ if (errno)
+ return -1;
+
+ cp = in_start+24;
+ buf[2] = 0;
+ for (i=0; i < 6; i++) {
+ buf[0] = *cp++;
+ buf[1] = *cp++;
+
+ errno = 0;
+ uuid.node[i] = strtoul(buf, NULL, 16);
+ if (errno)
+ return -1;
+ }
+
+ uuid_pack(&uuid, uu);
+ return 0;
+}
diff --git a/libuuid/src/predefined.c b/libuuid/src/predefined.c
new file mode 100644
index 0000000..fec1773
--- /dev/null
+++ b/libuuid/src/predefined.c
@@ -0,0 +1,83 @@
+/*
+ * predefined.c --- well-known UUIDs from the RFC-4122 namespace
+ *
+ * Copyright (C) 2017 Philip Prindeville
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuid.h"
+
+/*
+ * These are time-based UUIDs that are well-known in that they've
+ * been canonized as part of RFC-4122, Appendex C. They are to
+ * be used as the namespace (ns) argument to the uuid_generate_md5()
+ * and uuid_generate_sha1() functions.
+ *
+ * See Section 4.3 for the particulars of how namespace UUIDs
+ * are combined with seed values to generate new UUIDs.
+ */
+
+UUID_DEFINE(NameSpace_DNS,
+ 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8);
+
+UUID_DEFINE(NameSpace_URL,
+ 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8);
+
+UUID_DEFINE(NameSpace_OID,
+ 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8);
+
+UUID_DEFINE(NameSpace_X500,
+ 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
+ 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8);
+
+const uuid_t *uuid_get_template(const char *alias)
+{
+ if (!alias || !*alias)
+ return NULL;
+
+ if (!strcmp(alias, "dns"))
+ return &NameSpace_DNS;
+
+ if (!strcmp(alias, "url"))
+ return &NameSpace_URL;
+
+ if (!strcmp(alias, "oid"))
+ return &NameSpace_OID;
+
+ if (!strcmp(alias, "x500") || !strcmp(alias, "x.500"))
+ return &NameSpace_X500;
+
+ return NULL;
+}
+
diff --git a/libuuid/src/test_uuid.c b/libuuid/src/test_uuid.c
new file mode 100644
index 0000000..9907911
--- /dev/null
+++ b/libuuid/src/test_uuid.c
@@ -0,0 +1,121 @@
+/*
+ * tst_uuid.c --- test program from the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "c.h"
+#include "uuid.h"
+
+static int test_uuid(const char * uuid, int isValid)
+{
+ static const char * validStr[2] = {"invalid", "valid"};
+ uuid_t uuidBits;
+ int parsedOk;
+
+ parsedOk = uuid_parse(uuid, uuidBits) == 0;
+
+ printf("%s is %s", uuid, validStr[isValid]);
+ if (parsedOk != isValid) {
+ printf(" but uuid_parse says %s\n", validStr[parsedOk]);
+ return 1;
+ }
+ printf(", OK\n");
+ return 0;
+}
+
+static int check_uuids_in_file(const char *file)
+{
+ int fd, ret = 0;
+ size_t sz;
+ char str[UUID_STR_LEN];
+ uuid_t uuidBits;
+
+ if ((fd = open(file, O_RDONLY)) < 0) {
+ warn("%s", file);
+ return 1;
+ }
+ while ((sz = read(fd, str, sizeof(str))) != 0) {
+ str[sizeof(str) - 1] = '\0';
+ if (uuid_parse(str, uuidBits)) {
+ warnx("%s: %s", file, str);
+ ret++;
+ }
+ }
+
+ close(fd);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ int failed = 0;
+
+ if (argc < 2) {
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981b", 1);
+ failed += test_uuid("84949CC5-4701-4A84-895B-354C584A981B", 1);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981bc", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981", 0);
+ failed += test_uuid("84949cc5x4701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc504701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-470104a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a840895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b0354c584a981b", 0);
+ failed += test_uuid("g4949cc5-4701-4a84-895b-354c584a981b", 0);
+ failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981g", 0);
+ failed += test_uuid("00000000-0000-0000-0000-000000000000", 1);
+ failed += test_uuid("01234567-89ab-cdef-0134-567890abcedf", 1);
+ failed += test_uuid("ffffffff-ffff-ffff-ffff-ffffffffffff", 1);
+ } else {
+ int i;
+
+ for (i = 1; i < argc; i++)
+ failed += check_uuids_in_file(argv[i]);
+ }
+ if (failed) {
+ printf("%d failures.\n", failed);
+ exit(1);
+ }
+ return 0;
+}
diff --git a/libuuid/src/unpack.c b/libuuid/src/unpack.c
new file mode 100644
index 0000000..beaaff3
--- /dev/null
+++ b/libuuid/src/unpack.c
@@ -0,0 +1,63 @@
+/*
+ * Internal routine for unpacking UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_unpack(const uuid_t in, struct uuid *uu)
+{
+ const uint8_t *ptr = in;
+ uint32_t tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_low = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_mid = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_hi_and_version = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->clock_seq = tmp;
+
+ memcpy(uu->node, ptr, 6);
+}
+
diff --git a/libuuid/src/unparse.c b/libuuid/src/unparse.c
new file mode 100644
index 0000000..ffeed2e
--- /dev/null
+++ b/libuuid/src/unparse.c
@@ -0,0 +1,76 @@
+/*
+ * unparse.c -- convert a UUID to string
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include "c.h"
+
+#include "uuidP.h"
+
+static char const hexdigits_lower[16] = "0123456789abcdef";
+static char const hexdigits_upper[16] = "0123456789ABCDEF";
+
+static void uuid_fmt(const uuid_t uuid, char *buf, char const *restrict fmt)
+{
+ char *p = buf;
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ if (i == 4 || i == 6 || i == 8 || i == 10) {
+ *p++ = '-';
+ }
+ size_t tmp = uuid[i];
+ *p++ = fmt[tmp >> 4];
+ *p++ = fmt[tmp & 15];
+ }
+ *p = '\0';
+}
+
+void uuid_unparse_lower(const uuid_t uu, char *out)
+{
+ uuid_fmt(uu, out, hexdigits_lower);
+}
+
+void uuid_unparse_upper(const uuid_t uu, char *out)
+{
+ uuid_fmt(uu, out, hexdigits_upper);
+}
+
+void uuid_unparse(const uuid_t uu, char *out)
+{
+#ifdef UUID_UNPARSE_DEFAULT_UPPER
+ uuid_fmt(uu, out, hexdigits_upper);
+#else
+ uuid_fmt(uu, out, hexdigits_lower);
+#endif
+}
diff --git a/libuuid/src/uuid.h b/libuuid/src/uuid.h
new file mode 100644
index 0000000..e791abf
--- /dev/null
+++ b/libuuid/src/uuid.h
@@ -0,0 +1,123 @@
+/*
+ * Public include file for the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifndef _UL_LIBUUID_UUID_H
+#define _UL_LIBUUID_UUID_H
+
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+typedef unsigned char uuid_t[16];
+
+/* UUID Variant definitions */
+#define UUID_VARIANT_NCS 0
+#define UUID_VARIANT_DCE 1
+#define UUID_VARIANT_MICROSOFT 2
+#define UUID_VARIANT_OTHER 3
+
+#define UUID_VARIANT_SHIFT 5
+#define UUID_VARIANT_MASK 0x7
+
+/* UUID Type definitions */
+#define UUID_TYPE_DCE_NIL 0
+#define UUID_TYPE_DCE_TIME 1
+#define UUID_TYPE_DCE_SECURITY 2
+#define UUID_TYPE_DCE_MD5 3
+#define UUID_TYPE_DCE_RANDOM 4
+#define UUID_TYPE_DCE_SHA1 5
+
+#define UUID_TYPE_SHIFT 4
+#define UUID_TYPE_MASK 0xf
+
+#define UUID_STR_LEN 37
+
+/* Allow UUID constants to be defined */
+#ifdef __GNUC__
+#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \
+ static const uuid_t name __attribute__ ((unused)) = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#else
+#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \
+ static const uuid_t name = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* clear.c */
+extern void uuid_clear(uuid_t uu);
+
+/* compare.c */
+extern int uuid_compare(const uuid_t uu1, const uuid_t uu2);
+
+/* copy.c */
+extern void uuid_copy(uuid_t dst, const uuid_t src);
+
+/* gen_uuid.c */
+extern void uuid_generate(uuid_t out);
+extern void uuid_generate_random(uuid_t out);
+extern void uuid_generate_time(uuid_t out);
+extern int uuid_generate_time_safe(uuid_t out);
+
+extern void uuid_generate_md5(uuid_t out, const uuid_t ns, const char *name, size_t len);
+extern void uuid_generate_sha1(uuid_t out, const uuid_t ns, const char *name, size_t len);
+
+/* isnull.c */
+extern int uuid_is_null(const uuid_t uu);
+
+/* parse.c */
+extern int uuid_parse(const char *in, uuid_t uu);
+extern int uuid_parse_range(const char *in_start, const char *in_end, uuid_t uu);
+
+/* unparse.c */
+extern void uuid_unparse(const uuid_t uu, char *out);
+extern void uuid_unparse_lower(const uuid_t uu, char *out);
+extern void uuid_unparse_upper(const uuid_t uu, char *out);
+
+/* uuid_time.c */
+extern time_t uuid_time(const uuid_t uu, struct timeval *ret_tv);
+extern int uuid_type(const uuid_t uu);
+extern int uuid_variant(const uuid_t uu);
+
+/* predefined.c */
+extern const uuid_t *uuid_get_template(const char *alias);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UL_LIBUUID_UUID_H */
diff --git a/libuuid/src/uuidP.h b/libuuid/src/uuidP.h
new file mode 100644
index 0000000..200702c
--- /dev/null
+++ b/libuuid/src/uuidP.h
@@ -0,0 +1,94 @@
+/*
+ * uuid.h -- private header file for uuids
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+#ifndef _UUID_UUID_PRIVATE_H
+#define _UUID_UUID_PRIVATE_H
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "uuid.h"
+
+#define LIBUUID_CLOCK_FILE "/var/lib/libuuid/clock.txt"
+
+/*
+ * Offset between 15-Oct-1582 and 1-Jan-70
+ */
+#define TIME_OFFSET_HIGH 0x01B21DD2
+#define TIME_OFFSET_LOW 0x13814000
+
+/*
+ * Note that RFC4122 defines UUID in more details:
+ *
+ * Field Data Type Octet Note
+ * -------------------------------------------------
+ * time_low unsigned 32 0-3 The low field of the
+ * bit integer timestamp
+ *
+ * time_mid unsigned 16 4-5 The middle field of the
+ * bit integer timestamp
+ *
+ * time_hi_and_version unsigned 16 6-7 The high field of the
+ * bit integer timestamp multiplexed
+ * with the version number
+ *
+ * clock_seq_hi_and_rese unsigned 8 8 The high field of the
+ * rved bit integer clock sequence
+ * multiplexed with the
+ * variant
+ *
+ * clock_seq_low unsigned 8 9 The low field of the
+ * bit integer clock sequence
+ *
+ * node unsigned 48 10-15 The spatially unique
+ * bit integer node identifier
+ *
+ * We have clock_seq_hi_and_reserved (8bit) and clock_seq_low (8bit)
+ * merged into clock_seq (16bit).
+ */
+struct uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint16_t clock_seq;
+ uint8_t node[6];
+};
+
+
+/*
+ * prototypes
+ */
+void uuid_pack(const struct uuid *uu, uuid_t ptr);
+void uuid_unpack(const uuid_t in, struct uuid *uu);
+
+#endif /* _UUID_UUID_PRIVATE_H */
diff --git a/libuuid/src/uuid_time.c b/libuuid/src/uuid_time.c
new file mode 100644
index 0000000..6f07d51
--- /dev/null
+++ b/libuuid/src/uuid_time.c
@@ -0,0 +1,171 @@
+/*
+ * uuid_time.c --- Interpret the time field from a uuid. This program
+ * violates the UUID abstraction barrier by reaching into the guts
+ * of a UUID and interpreting it.
+ *
+ * Copyright (C) 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include "uuidP.h"
+
+time_t uuid_time(const uuid_t uu, struct timeval *ret_tv)
+{
+ struct timeval tv;
+ struct uuid uuid;
+ uint32_t high;
+ uint64_t clock_reg;
+
+ uuid_unpack(uu, &uuid);
+
+ high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16);
+ clock_reg = uuid.time_low | ((uint64_t) high << 32);
+
+ clock_reg -= (((uint64_t) 0x01B21DD2) << 32) + 0x13814000;
+ tv.tv_sec = clock_reg / 10000000;
+ tv.tv_usec = (clock_reg % 10000000) / 10;
+
+ if (ret_tv)
+ *ret_tv = tv;
+
+ return tv.tv_sec;
+}
+
+int uuid_type(const uuid_t uu)
+{
+ struct uuid uuid;
+
+ uuid_unpack(uu, &uuid);
+ return ((uuid.time_hi_and_version >> 12) & 0xF);
+}
+
+int uuid_variant(const uuid_t uu)
+{
+ struct uuid uuid;
+ int var;
+
+ uuid_unpack(uu, &uuid);
+ var = uuid.clock_seq;
+
+ if ((var & 0x8000) == 0)
+ return UUID_VARIANT_NCS;
+ if ((var & 0x4000) == 0)
+ return UUID_VARIANT_DCE;
+ if ((var & 0x2000) == 0)
+ return UUID_VARIANT_MICROSOFT;
+ return UUID_VARIANT_OTHER;
+}
+
+#ifdef DEBUG
+static const char *variant_string(int variant)
+{
+ switch (variant) {
+ case UUID_VARIANT_NCS:
+ return "NCS";
+ case UUID_VARIANT_DCE:
+ return "DCE";
+ case UUID_VARIANT_MICROSOFT:
+ return "Microsoft";
+ default:
+ return "Other";
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ uuid_t buf;
+ time_t time_reg;
+ struct timeval tv;
+ int type, variant;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s uuid\n", argv[0]);
+ exit(1);
+ }
+ if (uuid_parse(argv[1], buf)) {
+ fprintf(stderr, "Invalid UUID: %s\n", argv[1]);
+ exit(1);
+ }
+ variant = uuid_variant(buf);
+ type = uuid_type(buf);
+ time_reg = uuid_time(buf, &tv);
+
+ printf("UUID variant is %d (%s)\n", variant, variant_string(variant));
+ if (variant != UUID_VARIANT_DCE) {
+ printf("Warning: This program only knows how to interpret "
+ "DCE UUIDs.\n\tThe rest of the output is likely "
+ "to be incorrect!!\n");
+ }
+ printf("UUID type is %d", type);
+ switch (type) {
+ case 1:
+ printf(" (time based)\n");
+ break;
+ case 2:
+ printf(" (DCE)\n");
+ break;
+ case 3:
+ printf(" (name-based)\n");
+ break;
+ case 4:
+ printf(" (random)\n");
+ break;
+ default:
+ printf("\n");
+ }
+ if (type != 1) {
+ printf("Warning: not a time-based UUID, so UUID time "
+ "decoding will likely not work!\n");
+ }
+ printf("UUID time is: (%"PRId64", %"PRId64"): %s\n",
+ (int64_t)tv.tv_sec, (int64_t)tv.tv_usec, ctime(&time_reg));
+
+ return 0;
+}
+#endif
diff --git a/libuuid/src/uuidd.h b/libuuid/src/uuidd.h
new file mode 100644
index 0000000..fbe821f
--- /dev/null
+++ b/libuuid/src/uuidd.h
@@ -0,0 +1,54 @@
+/*
+ * Definitions used by the uuidd daemon
+ *
+ * Copyright (C) 2007 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifndef _UUID_UUIDD_H
+#define _UUID_UUIDD_H
+
+#define UUIDD_DIR _PATH_RUNSTATEDIR "/uuidd"
+#define UUIDD_SOCKET_PATH UUIDD_DIR "/request"
+#define UUIDD_PIDFILE_PATH UUIDD_DIR "/uuidd.pid"
+#define UUIDD_PATH "/usr/sbin/uuidd"
+
+#define UUIDD_OP_GETPID 0
+#define UUIDD_OP_GET_MAXOP 1
+#define UUIDD_OP_TIME_UUID 2
+#define UUIDD_OP_RANDOM_UUID 3
+#define UUIDD_OP_BULK_TIME_UUID 4
+#define UUIDD_OP_BULK_RANDOM_UUID 5
+#define UUIDD_MAX_OP UUIDD_OP_BULK_RANDOM_UUID
+
+extern int __uuid_generate_time(uuid_t out, int *num);
+extern int __uuid_generate_random(uuid_t out, int *num);
+
+#endif /* _UUID_UUID_H */
diff --git a/libuuid/uuid.pc.in b/libuuid/uuid.pc.in
new file mode 100644
index 0000000..875de19
--- /dev/null
+++ b/libuuid/uuid.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: uuid
+Description: Universally unique id library
+Version: @LIBUUID_VERSION@
+Requires:
+Cflags: -I${includedir}/uuid
+Libs: -L${libdir} -luuid