summaryrefslogtreecommitdiffstats
path: root/src/stat.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
commit758f820bcc0f68aeebac1717e537ca13a320b909 (patch)
tree48111ece75cf4f98316848b37a7e26356e00669e /src/stat.c
parentInitial commit. (diff)
downloadcoreutils-upstream.tar.xz
coreutils-upstream.zip
Adding upstream version 9.1.upstream/9.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/stat.c')
-rw-r--r--src/stat.c1972
1 files changed, 1972 insertions, 0 deletions
diff --git a/src/stat.c b/src/stat.c
new file mode 100644
index 0000000..3765a8f
--- /dev/null
+++ b/src/stat.c
@@ -0,0 +1,1972 @@
+/* stat.c -- display file or file system status
+ Copyright (C) 2001-2022 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+ Written by Michael Meskes. */
+
+#include <config.h>
+
+/* Keep this conditional in sync with the similar conditional in
+ ../m4/stat-prog.m4. */
+#if ((STAT_STATVFS || STAT_STATVFS64) \
+ && (HAVE_STRUCT_STATVFS_F_BASETYPE || HAVE_STRUCT_STATVFS_F_FSTYPENAME \
+ || (! HAVE_STRUCT_STATFS_F_FSTYPENAME && HAVE_STRUCT_STATVFS_F_TYPE)))
+# define USE_STATVFS 1
+#else
+# define USE_STATVFS 0
+#endif
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdalign.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#if USE_STATVFS
+# include <sys/statvfs.h>
+#elif HAVE_SYS_VFS_H
+# include <sys/vfs.h>
+#elif HAVE_SYS_MOUNT_H && HAVE_SYS_PARAM_H
+/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
+ It does have statvfs.h, but shouldn't use it, since it doesn't
+ HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
+/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
+# include <sys/param.h>
+# include <sys/mount.h>
+# if HAVE_NFS_NFS_CLNT_H && HAVE_NFS_VFS_H
+/* Ultrix 4.4 needs these for the declaration of struct statfs. */
+# include <netinet/in.h>
+# include <nfs/nfs_clnt.h>
+# include <nfs/vfs.h>
+# endif
+#elif HAVE_OS_H /* BeOS */
+# include <fs_info.h>
+#endif
+#include <selinux/selinux.h>
+
+#include "system.h"
+
+#include "areadlink.h"
+#include "argmatch.h"
+#include "die.h"
+#include "error.h"
+#include "file-type.h"
+#include "filemode.h"
+#include "fs.h"
+#include "getopt.h"
+#include "mountlist.h"
+#include "quote.h"
+#include "stat-size.h"
+#include "stat-time.h"
+#include "strftime.h"
+#include "find-mount-point.h"
+#include "xvasprintf.h"
+#include "statx.h"
+
+#if HAVE_STATX && defined STATX_INO
+# define USE_STATX 1
+#else
+# define USE_STATX 0
+#endif
+
+#if USE_STATVFS
+# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATVFS_F_FSID_IS_INTEGER
+# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATVFS_F_TYPE
+# if HAVE_STRUCT_STATVFS_F_NAMEMAX
+# define SB_F_NAMEMAX(S) ((S)->f_namemax)
+# endif
+# if ! STAT_STATVFS && STAT_STATVFS64
+# define STRUCT_STATVFS struct statvfs64
+# define STATFS statvfs64
+# else
+# define STRUCT_STATVFS struct statvfs
+# define STATFS statvfs
+# endif
+# define STATFS_FRSIZE(S) ((S)->f_frsize)
+#else
+# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATFS_F_TYPE
+# if HAVE_STRUCT_STATFS_F_NAMELEN
+# define SB_F_NAMEMAX(S) ((S)->f_namelen)
+# elif HAVE_STRUCT_STATFS_F_NAMEMAX
+# define SB_F_NAMEMAX(S) ((S)->f_namemax)
+# endif
+# define STATFS statfs
+# if HAVE_OS_H /* BeOS */
+/* BeOS has a statvfs function, but it does not return sensible values
+ for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
+ f_fstypename. Use 'struct fs_info' instead. */
+NODISCARD
+static int
+statfs (char const *filename, struct fs_info *buf)
+{
+ dev_t device = dev_for_path (filename);
+ if (device < 0)
+ {
+ errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
+ : device == B_BAD_VALUE ? EINVAL
+ : device == B_NAME_TOO_LONG ? ENAMETOOLONG
+ : device == B_NO_MEMORY ? ENOMEM
+ : device == B_FILE_ERROR ? EIO
+ : 0);
+ return -1;
+ }
+ /* If successful, buf->dev will be == device. */
+ return fs_stat_dev (device, buf);
+}
+# define f_fsid dev
+# define f_blocks total_blocks
+# define f_bfree free_blocks
+# define f_bavail free_blocks
+# define f_bsize io_size
+# define f_files total_nodes
+# define f_ffree free_nodes
+# define STRUCT_STATVFS struct fs_info
+# define STRUCT_STATXFS_F_FSID_IS_INTEGER true
+# define STATFS_FRSIZE(S) ((S)->block_size)
+# else
+# define STRUCT_STATVFS struct statfs
+# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATFS_F_FSID_IS_INTEGER
+# if HAVE_STRUCT_STATFS_F_FRSIZE
+# define STATFS_FRSIZE(S) ((S)->f_frsize)
+# else
+# define STATFS_FRSIZE(S) 0
+# endif
+# endif
+#endif
+
+#ifdef SB_F_NAMEMAX
+# define OUT_NAMEMAX out_uint
+#else
+/* Depending on whether statvfs or statfs is used,
+ neither f_namemax or f_namelen may be available. */
+# define SB_F_NAMEMAX(S) "?"
+# define OUT_NAMEMAX out_string
+#endif
+
+#if HAVE_STRUCT_STATVFS_F_BASETYPE
+# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
+#else
+# if HAVE_STRUCT_STATVFS_F_FSTYPENAME || HAVE_STRUCT_STATFS_F_FSTYPENAME
+# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
+# elif HAVE_OS_H /* BeOS */
+# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
+# endif
+#endif
+
+#if HAVE_GETATTRAT
+# include <attr.h>
+# include <sys/nvpair.h>
+#endif
+
+/* FIXME: these are used by printf.c, too */
+#define isodigit(c) ('0' <= (c) && (c) <= '7')
+#define octtobin(c) ((c) - '0')
+#define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \
+ (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0')
+
+static char const digits[] = "0123456789";
+
+/* Flags that are portable for use in printf, for at least one
+ conversion specifier; make_format removes unportable flags as
+ needed for particular specifiers. The glibc 2.2 extension "I" is
+ listed here; it is removed by make_format because it has undefined
+ behavior elsewhere and because it is incompatible with
+ out_epoch_sec. */
+static char const printf_flags[] = "'-+ #0I";
+
+/* Formats for the --terse option. */
+static char const fmt_terse_fs[] = "%n %i %l %t %s %S %b %f %a %c %d\n";
+static char const fmt_terse_regular[] = "%n %s %b %f %u %g %D %i %h %t %T"
+ " %X %Y %Z %W %o\n";
+static char const fmt_terse_selinux[] = "%n %s %b %f %u %g %D %i %h %t %T"
+ " %X %Y %Z %W %o %C\n";
+
+#define PROGRAM_NAME "stat"
+
+#define AUTHORS proper_name ("Michael Meskes")
+
+enum
+{
+ PRINTF_OPTION = CHAR_MAX + 1
+};
+
+enum cached_mode
+{
+ cached_default,
+ cached_never,
+ cached_always
+};
+
+static char const *const cached_args[] =
+{
+ "default", "never", "always", NULL
+};
+
+static enum cached_mode const cached_modes[] =
+{
+ cached_default, cached_never, cached_always
+};
+
+static struct option const long_options[] =
+{
+ {"dereference", no_argument, NULL, 'L'},
+ {"file-system", no_argument, NULL, 'f'},
+ {"format", required_argument, NULL, 'c'},
+ {"printf", required_argument, NULL, PRINTF_OPTION},
+ {"terse", no_argument, NULL, 't'},
+ {"cached", required_argument, NULL, 0},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+/* Whether to follow symbolic links; True for --dereference (-L). */
+static bool follow_links;
+
+/* Whether to interpret backslash-escape sequences.
+ True for --printf=FMT, not for --format=FMT (-c). */
+static bool interpret_backslash_escapes;
+
+/* The trailing delimiter string:
+ "" for --printf=FMT, "\n" for --format=FMT (-c). */
+static char const *trailing_delim = "";
+
+/* The representation of the decimal point in the current locale. */
+static char const *decimal_point;
+static size_t decimal_point_len;
+
+static bool
+print_stat (char *pformat, size_t prefix_len, char mod, char m,
+ int fd, char const *filename, void const *data);
+
+/* Return the type of the specified file system.
+ Some systems have statfvs.f_basetype[FSTYPSZ] (AIX, HP-UX, and Solaris).
+ Others have statvfs.f_fstypename[_VFS_NAMELEN] (NetBSD 3.0).
+ Others have statfs.f_fstypename[MFSNAMELEN] (NetBSD 1.5.2).
+ Still others have neither and have to get by with f_type (GNU/Linux).
+ But f_type may only exist in statfs (Cygwin). */
+NODISCARD
+static char const *
+human_fstype (STRUCT_STATVFS const *statfsbuf)
+{
+#ifdef STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME
+ return statfsbuf->STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME;
+#else
+ switch (statfsbuf->f_type)
+ {
+# if defined __linux__ || defined __ANDROID__
+
+ /* Compare with what's in libc:
+ f=/a/libc/sysdeps/unix/sysv/linux/linux_fsinfo.h
+ sed -n '/ADFS_SUPER_MAGIC/,/SYSFS_MAGIC/p' $f \
+ | perl -n -e '/#define (.*?)_(?:SUPER_)MAGIC\s+0x(\S+)/' \
+ -e 'and print "case S_MAGIC_$1: /\* 0x" . uc($2) . " *\/\n"' \
+ | sort > sym_libc
+ perl -ne '/^\s+(case S_MAGIC_.*?): \/\* 0x(\S+) \*\//' \
+ -e 'and do { $v=uc$2; print "$1: /\* 0x$v *\/\n"}' stat.c \
+ | sort > sym_stat
+ diff -u sym_stat sym_libc
+ */
+
+ /* Also compare with the list in "man 2 statfs" using the
+ fs-magic-compare make target. */
+
+ /* IMPORTANT NOTE: Each of the following 'case S_MAGIC_...:'
+ statements must be followed by a hexadecimal constant in
+ a comment. The S_MAGIC_... name and constant are automatically
+ combined to produce the #define directives in fs.h. */
+
+ case S_MAGIC_AAFS: /* 0x5A3C69F0 local */
+ return "aafs";
+ case S_MAGIC_ACFS: /* 0x61636673 remote */
+ return "acfs";
+ case S_MAGIC_ADFS: /* 0xADF5 local */
+ return "adfs";
+ case S_MAGIC_AFFS: /* 0xADFF local */
+ return "affs";
+ case S_MAGIC_AFS: /* 0x5346414F remote */
+ return "afs";
+ case S_MAGIC_ANON_INODE_FS: /* 0x09041934 local */
+ return "anon-inode FS";
+ case S_MAGIC_AUFS: /* 0x61756673 remote */
+ /* FIXME: change syntax or add an optional attribute like "inotify:no".
+ The above is labeled as "remote" so that tail always uses polling,
+ but this isn't really a remote file system type. */
+ return "aufs";
+ case S_MAGIC_AUTOFS: /* 0x0187 local */
+ return "autofs";
+ case S_MAGIC_BALLOON_KVM: /* 0x13661366 local */
+ return "balloon-kvm-fs";
+ case S_MAGIC_BEFS: /* 0x42465331 local */
+ return "befs";
+ case S_MAGIC_BDEVFS: /* 0x62646576 local */
+ return "bdevfs";
+ case S_MAGIC_BFS: /* 0x1BADFACE local */
+ return "bfs";
+ case S_MAGIC_BINDERFS: /* 0x6C6F6F70 local */
+ return "binderfs";
+ case S_MAGIC_BPF_FS: /* 0xCAFE4A11 local */
+ return "bpf_fs";
+ case S_MAGIC_BINFMTFS: /* 0x42494E4D local */
+ return "binfmt_misc";
+ case S_MAGIC_BTRFS: /* 0x9123683E local */
+ return "btrfs";
+ case S_MAGIC_BTRFS_TEST: /* 0x73727279 local */
+ return "btrfs_test";
+ case S_MAGIC_CEPH: /* 0x00C36400 remote */
+ return "ceph";
+ case S_MAGIC_CGROUP: /* 0x0027E0EB local */
+ return "cgroupfs";
+ case S_MAGIC_CGROUP2: /* 0x63677270 local */
+ return "cgroup2fs";
+ case S_MAGIC_CIFS: /* 0xFF534D42 remote */
+ return "cifs";
+ case S_MAGIC_CODA: /* 0x73757245 remote */
+ return "coda";
+ case S_MAGIC_COH: /* 0x012FF7B7 local */
+ return "coh";
+ case S_MAGIC_CONFIGFS: /* 0x62656570 local */
+ return "configfs";
+ case S_MAGIC_CRAMFS: /* 0x28CD3D45 local */
+ return "cramfs";
+ case S_MAGIC_CRAMFS_WEND: /* 0x453DCD28 local */
+ return "cramfs-wend";
+ case S_MAGIC_DAXFS: /* 0x64646178 local */
+ return "daxfs";
+ case S_MAGIC_DEBUGFS: /* 0x64626720 local */
+ return "debugfs";
+ case S_MAGIC_DEVFS: /* 0x1373 local */
+ return "devfs";
+ case S_MAGIC_DEVMEM: /* 0x454D444D local */
+ return "devmem";
+ case S_MAGIC_DEVPTS: /* 0x1CD1 local */
+ return "devpts";
+ case S_MAGIC_DMA_BUF: /* 0x444D4142 local */
+ return "dma-buf-fs";
+ case S_MAGIC_ECRYPTFS: /* 0xF15F local */
+ return "ecryptfs";
+ case S_MAGIC_EFIVARFS: /* 0xDE5E81E4 local */
+ return "efivarfs";
+ case S_MAGIC_EFS: /* 0x00414A53 local */
+ return "efs";
+ case S_MAGIC_EROFS_V1: /* 0xE0F5E1E2 local */
+ return "erofs";
+ case S_MAGIC_EXFAT: /* 0x2011BAB0 local */
+ return "exfat";
+ case S_MAGIC_EXFS: /* 0x45584653 local */
+ return "exfs";
+ case S_MAGIC_EXOFS: /* 0x5DF5 local */
+ return "exofs";
+ case S_MAGIC_EXT: /* 0x137D local */
+ return "ext";
+ case S_MAGIC_EXT2: /* 0xEF53 local */
+ return "ext2/ext3";
+ case S_MAGIC_EXT2_OLD: /* 0xEF51 local */
+ return "ext2";
+ case S_MAGIC_F2FS: /* 0xF2F52010 local */
+ return "f2fs";
+ case S_MAGIC_FAT: /* 0x4006 local */
+ return "fat";
+ case S_MAGIC_FHGFS: /* 0x19830326 remote */
+ return "fhgfs";
+ case S_MAGIC_FUSEBLK: /* 0x65735546 remote */
+ return "fuseblk";
+ case S_MAGIC_FUSECTL: /* 0x65735543 remote */
+ return "fusectl";
+ case S_MAGIC_FUTEXFS: /* 0x0BAD1DEA local */
+ return "futexfs";
+ case S_MAGIC_GFS: /* 0x01161970 remote */
+ return "gfs/gfs2";
+ case S_MAGIC_GPFS: /* 0x47504653 remote */
+ return "gpfs";
+ case S_MAGIC_HFS: /* 0x4244 local */
+ return "hfs";
+ case S_MAGIC_HFS_PLUS: /* 0x482B local */
+ return "hfs+";
+ case S_MAGIC_HFS_X: /* 0x4858 local */
+ return "hfsx";
+ case S_MAGIC_HOSTFS: /* 0x00C0FFEE local */
+ return "hostfs";
+ case S_MAGIC_HPFS: /* 0xF995E849 local */
+ return "hpfs";
+ case S_MAGIC_HUGETLBFS: /* 0x958458F6 local */
+ return "hugetlbfs";
+ case S_MAGIC_MTD_INODE_FS: /* 0x11307854 local */
+ return "inodefs";
+ case S_MAGIC_IBRIX: /* 0x013111A8 remote */
+ return "ibrix";
+ case S_MAGIC_INOTIFYFS: /* 0x2BAD1DEA local */
+ return "inotifyfs";
+ case S_MAGIC_ISOFS: /* 0x9660 local */
+ return "isofs";
+ case S_MAGIC_ISOFS_R_WIN: /* 0x4004 local */
+ return "isofs";
+ case S_MAGIC_ISOFS_WIN: /* 0x4000 local */
+ return "isofs";
+ case S_MAGIC_JFFS: /* 0x07C0 local */
+ return "jffs";
+ case S_MAGIC_JFFS2: /* 0x72B6 local */
+ return "jffs2";
+ case S_MAGIC_JFS: /* 0x3153464A local */
+ return "jfs";
+ case S_MAGIC_KAFS: /* 0x6B414653 remote */
+ return "k-afs";
+ case S_MAGIC_LOGFS: /* 0xC97E8168 local */
+ return "logfs";
+ case S_MAGIC_LUSTRE: /* 0x0BD00BD0 remote */
+ return "lustre";
+ case S_MAGIC_M1FS: /* 0x5346314D local */
+ return "m1fs";
+ case S_MAGIC_MINIX: /* 0x137F local */
+ return "minix";
+ case S_MAGIC_MINIX_30: /* 0x138F local */
+ return "minix (30 char.)";
+ case S_MAGIC_MINIX_V2: /* 0x2468 local */
+ return "minix v2";
+ case S_MAGIC_MINIX_V2_30: /* 0x2478 local */
+ return "minix v2 (30 char.)";
+ case S_MAGIC_MINIX_V3: /* 0x4D5A local */
+ return "minix3";
+ case S_MAGIC_MQUEUE: /* 0x19800202 local */
+ return "mqueue";
+ case S_MAGIC_MSDOS: /* 0x4D44 local */
+ return "msdos";
+ case S_MAGIC_NCP: /* 0x564C remote */
+ return "novell";
+ case S_MAGIC_NFS: /* 0x6969 remote */
+ return "nfs";
+ case S_MAGIC_NFSD: /* 0x6E667364 remote */
+ return "nfsd";
+ case S_MAGIC_NILFS: /* 0x3434 local */
+ return "nilfs";
+ case S_MAGIC_NSFS: /* 0x6E736673 local */
+ return "nsfs";
+ case S_MAGIC_NTFS: /* 0x5346544E local */
+ return "ntfs";
+ case S_MAGIC_OPENPROM: /* 0x9FA1 local */
+ return "openprom";
+ case S_MAGIC_OCFS2: /* 0x7461636F remote */
+ return "ocfs2";
+ case S_MAGIC_OVERLAYFS: /* 0x794C7630 remote */
+ /* This may overlay remote file systems.
+ Also there have been issues reported with inotify and overlayfs,
+ so mark as "remote" so that polling is used. */
+ return "overlayfs";
+ case S_MAGIC_PANFS: /* 0xAAD7AAEA remote */
+ return "panfs";
+ case S_MAGIC_PIPEFS: /* 0x50495045 remote */
+ /* FIXME: change syntax or add an optional attribute like "inotify:no".
+ pipefs and prlfs are labeled as "remote" so that tail always polls,
+ but these aren't really remote file system types. */
+ return "pipefs";
+ case S_MAGIC_PPC_CMM: /* 0xC7571590 local */
+ return "ppc-cmm-fs";
+ case S_MAGIC_PRL_FS: /* 0x7C7C6673 remote */
+ return "prl_fs";
+ case S_MAGIC_PROC: /* 0x9FA0 local */
+ return "proc";
+ case S_MAGIC_PSTOREFS: /* 0x6165676C local */
+ return "pstorefs";
+ case S_MAGIC_QNX4: /* 0x002F local */
+ return "qnx4";
+ case S_MAGIC_QNX6: /* 0x68191122 local */
+ return "qnx6";
+ case S_MAGIC_RAMFS: /* 0x858458F6 local */
+ return "ramfs";
+ case S_MAGIC_RDTGROUP: /* 0x07655821 local */
+ return "rdt";
+ case S_MAGIC_REISERFS: /* 0x52654973 local */
+ return "reiserfs";
+ case S_MAGIC_ROMFS: /* 0x7275 local */
+ return "romfs";
+ case S_MAGIC_RPC_PIPEFS: /* 0x67596969 local */
+ return "rpc_pipefs";
+ case S_MAGIC_SDCARDFS: /* 0x5DCA2DF5 local */
+ return "sdcardfs";
+ case S_MAGIC_SECRETMEM: /* 0x5345434D local */
+ return "secretmem";
+ case S_MAGIC_SECURITYFS: /* 0x73636673 local */
+ return "securityfs";
+ case S_MAGIC_SELINUX: /* 0xF97CFF8C local */
+ return "selinux";
+ case S_MAGIC_SMACK: /* 0x43415D53 local */
+ return "smackfs";
+ case S_MAGIC_SMB: /* 0x517B remote */
+ return "smb";
+ case S_MAGIC_SMB2: /* 0xFE534D42 remote */
+ return "smb2";
+ case S_MAGIC_SNFS: /* 0xBEEFDEAD remote */
+ return "snfs";
+ case S_MAGIC_SOCKFS: /* 0x534F434B local */
+ return "sockfs";
+ case S_MAGIC_SQUASHFS: /* 0x73717368 local */
+ return "squashfs";
+ case S_MAGIC_SYSFS: /* 0x62656572 local */
+ return "sysfs";
+ case S_MAGIC_SYSV2: /* 0x012FF7B6 local */
+ return "sysv2";
+ case S_MAGIC_SYSV4: /* 0x012FF7B5 local */
+ return "sysv4";
+ case S_MAGIC_TMPFS: /* 0x01021994 local */
+ return "tmpfs";
+ case S_MAGIC_TRACEFS: /* 0x74726163 local */
+ return "tracefs";
+ case S_MAGIC_UBIFS: /* 0x24051905 local */
+ return "ubifs";
+ case S_MAGIC_UDF: /* 0x15013346 local */
+ return "udf";
+ case S_MAGIC_UFS: /* 0x00011954 local */
+ return "ufs";
+ case S_MAGIC_UFS_BYTESWAPPED: /* 0x54190100 local */
+ return "ufs";
+ case S_MAGIC_USBDEVFS: /* 0x9FA2 local */
+ return "usbdevfs";
+ case S_MAGIC_V9FS: /* 0x01021997 local */
+ return "v9fs";
+ case S_MAGIC_VBOXSF: /* 0x786F4256 remote */
+ return "vboxsf";
+ case S_MAGIC_VMHGFS: /* 0xBACBACBC remote */
+ return "vmhgfs";
+ case S_MAGIC_VXFS: /* 0xA501FCF5 remote */
+ /* Veritas File System can run in single instance or clustered mode,
+ so mark as remote to cater for the latter case. */
+ return "vxfs";
+ case S_MAGIC_VZFS: /* 0x565A4653 local */
+ return "vzfs";
+ case S_MAGIC_WSLFS: /* 0x53464846 local */
+ return "wslfs";
+ case S_MAGIC_XENFS: /* 0xABBA1974 local */
+ return "xenfs";
+ case S_MAGIC_XENIX: /* 0x012FF7B4 local */
+ return "xenix";
+ case S_MAGIC_XFS: /* 0x58465342 local */
+ return "xfs";
+ case S_MAGIC_XIAFS: /* 0x012FD16D local */
+ return "xia";
+ case S_MAGIC_Z3FOLD: /* 0x0033 local */
+ return "z3fold";
+ case S_MAGIC_ZFS: /* 0x2FC12FC1 local */
+ return "zfs";
+ case S_MAGIC_ZONEFS: /* 0x5A4F4653 local */
+ return "zonefs";
+ case S_MAGIC_ZSMALLOC: /* 0x58295829 local */
+ return "zsmallocfs";
+
+
+# elif __GNU__
+ case FSTYPE_UFS:
+ return "ufs";
+ case FSTYPE_NFS:
+ return "nfs";
+ case FSTYPE_GFS:
+ return "gfs";
+ case FSTYPE_LFS:
+ return "lfs";
+ case FSTYPE_SYSV:
+ return "sysv";
+ case FSTYPE_FTP:
+ return "ftp";
+ case FSTYPE_TAR:
+ return "tar";
+ case FSTYPE_AR:
+ return "ar";
+ case FSTYPE_CPIO:
+ return "cpio";
+ case FSTYPE_MSLOSS:
+ return "msloss";
+ case FSTYPE_CPM:
+ return "cpm";
+ case FSTYPE_HFS:
+ return "hfs";
+ case FSTYPE_DTFS:
+ return "dtfs";
+ case FSTYPE_GRFS:
+ return "grfs";
+ case FSTYPE_TERM:
+ return "term";
+ case FSTYPE_DEV:
+ return "dev";
+ case FSTYPE_PROC:
+ return "proc";
+ case FSTYPE_IFSOCK:
+ return "ifsock";
+ case FSTYPE_AFS:
+ return "afs";
+ case FSTYPE_DFS:
+ return "dfs";
+ case FSTYPE_PROC9:
+ return "proc9";
+ case FSTYPE_SOCKET:
+ return "socket";
+ case FSTYPE_MISC:
+ return "misc";
+ case FSTYPE_EXT2FS:
+ return "ext2/ext3";
+ case FSTYPE_HTTP:
+ return "http";
+ case FSTYPE_MEMFS:
+ return "memfs";
+ case FSTYPE_ISO9660:
+ return "iso9660";
+# endif
+ default:
+ {
+ unsigned long int type = statfsbuf->f_type;
+ static char buf[sizeof "UNKNOWN (0x%lx)" - 3
+ + (sizeof type * CHAR_BIT + 3) / 4];
+ sprintf (buf, "UNKNOWN (0x%lx)", type);
+ return buf;
+ }
+ }
+#endif
+}
+
+NODISCARD
+static char *
+human_access (struct stat const *statbuf)
+{
+ static char modebuf[12];
+ filemodestring (statbuf, modebuf);
+ modebuf[10] = 0;
+ return modebuf;
+}
+
+NODISCARD
+static char *
+human_time (struct timespec t)
+{
+ /* STR must be at least INT_BUFSIZE_BOUND (intmax_t) big, either
+ because localtime_rz fails, or because the time zone is truly
+ outlandish so that %z expands to a long string. */
+ static char str[INT_BUFSIZE_BOUND (intmax_t)
+ + INT_STRLEN_BOUND (int) /* YYYY */
+ + 1 /* because YYYY might equal INT_MAX + 1900 */
+ + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +"];
+ static timezone_t tz;
+ if (!tz)
+ tz = tzalloc (getenv ("TZ"));
+ struct tm tm;
+ int ns = t.tv_nsec;
+ if (localtime_rz (tz, &t.tv_sec, &tm))
+ nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", &tm, tz, ns);
+ else
+ {
+ char secbuf[INT_BUFSIZE_BOUND (intmax_t)];
+ sprintf (str, "%s.%09d", timetostr (t.tv_sec, secbuf), ns);
+ }
+ return str;
+}
+
+/* PFORMAT points to a '%' followed by a prefix of a format, all of
+ size PREFIX_LEN. The flags allowed for this format are
+ ALLOWED_FLAGS; remove other printf flags from the prefix, then
+ append SUFFIX. */
+static void
+make_format (char *pformat, size_t prefix_len, char const *allowed_flags,
+ char const *suffix)
+{
+ char *dst = pformat + 1;
+ char const *src;
+ char const *srclim = pformat + prefix_len;
+ for (src = dst; src < srclim && strchr (printf_flags, *src); src++)
+ if (strchr (allowed_flags, *src))
+ *dst++ = *src;
+ while (src < srclim)
+ *dst++ = *src++;
+ strcpy (dst, suffix);
+}
+
+static void
+out_string (char *pformat, size_t prefix_len, char const *arg)
+{
+ make_format (pformat, prefix_len, "-", "s");
+ printf (pformat, arg);
+}
+static int
+out_int (char *pformat, size_t prefix_len, intmax_t arg)
+{
+ make_format (pformat, prefix_len, "'-+ 0", PRIdMAX);
+ return printf (pformat, arg);
+}
+static int
+out_uint (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ make_format (pformat, prefix_len, "'-0", PRIuMAX);
+ return printf (pformat, arg);
+}
+static void
+out_uint_o (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ make_format (pformat, prefix_len, "-#0", PRIoMAX);
+ printf (pformat, arg);
+}
+static void
+out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ make_format (pformat, prefix_len, "-#0", PRIxMAX);
+ printf (pformat, arg);
+}
+static int
+out_minus_zero (char *pformat, size_t prefix_len)
+{
+ make_format (pformat, prefix_len, "'-+ 0", ".0f");
+ return printf (pformat, -0.25);
+}
+
+/* Output the number of seconds since the Epoch, using a format that
+ acts like printf's %f format. */
+static void
+out_epoch_sec (char *pformat, size_t prefix_len,
+ struct timespec arg)
+{
+ char *dot = memchr (pformat, '.', prefix_len);
+ size_t sec_prefix_len = prefix_len;
+ int width = 0;
+ int precision = 0;
+ bool frac_left_adjust = false;
+
+ if (dot)
+ {
+ sec_prefix_len = dot - pformat;
+ pformat[prefix_len] = '\0';
+
+ if (ISDIGIT (dot[1]))
+ {
+ long int lprec = strtol (dot + 1, NULL, 10);
+ precision = (lprec <= INT_MAX ? lprec : INT_MAX);
+ }
+ else
+ {
+ precision = 9;
+ }
+
+ if (precision && ISDIGIT (dot[-1]))
+ {
+ /* If a nontrivial width is given, subtract the width of the
+ decimal point and PRECISION digits that will be output
+ later. */
+ char *p = dot;
+ *dot = '\0';
+
+ do
+ --p;
+ while (ISDIGIT (p[-1]));
+
+ long int lwidth = strtol (p, NULL, 10);
+ width = (lwidth <= INT_MAX ? lwidth : INT_MAX);
+ if (1 < width)
+ {
+ p += (*p == '0');
+ sec_prefix_len = p - pformat;
+ int w_d = (decimal_point_len < width
+ ? width - decimal_point_len
+ : 0);
+ if (1 < w_d)
+ {
+ int w = w_d - precision;
+ if (1 < w)
+ {
+ char *dst = pformat;
+ for (char const *src = dst; src < p; src++)
+ {
+ if (*src == '-')
+ frac_left_adjust = true;
+ else
+ *dst++ = *src;
+ }
+ sec_prefix_len =
+ (dst - pformat
+ + (frac_left_adjust ? 0 : sprintf (dst, "%d", w)));
+ }
+ }
+ }
+ }
+ }
+
+ int divisor = 1;
+ for (int i = precision; i < 9; i++)
+ divisor *= 10;
+ int frac_sec = arg.tv_nsec / divisor;
+ int int_len;
+
+ if (TYPE_SIGNED (time_t))
+ {
+ bool minus_zero = false;
+ if (arg.tv_sec < 0 && arg.tv_nsec != 0)
+ {
+ int frac_sec_modulus = 1000000000 / divisor;
+ frac_sec = (frac_sec_modulus - frac_sec
+ - (arg.tv_nsec % divisor != 0));
+ arg.tv_sec += (frac_sec != 0);
+ minus_zero = (arg.tv_sec == 0);
+ }
+ int_len = (minus_zero
+ ? out_minus_zero (pformat, sec_prefix_len)
+ : out_int (pformat, sec_prefix_len, arg.tv_sec));
+ }
+ else
+ int_len = out_uint (pformat, sec_prefix_len, arg.tv_sec);
+
+ if (precision)
+ {
+ int prec = (precision < 9 ? precision : 9);
+ int trailing_prec = precision - prec;
+ int ilen = (int_len < 0 ? 0 : int_len);
+ int trailing_width = (ilen < width && decimal_point_len < width - ilen
+ ? width - ilen - decimal_point_len - prec
+ : 0);
+ printf ("%s%.*d%-*.*d", decimal_point, prec, frac_sec,
+ trailing_width, trailing_prec, 0);
+ }
+}
+
+/* Print the context information of FILENAME, and return true iff the
+ context could not be obtained. */
+NODISCARD
+static bool
+out_file_context (char *pformat, size_t prefix_len, char const *filename)
+{
+ char *scontext;
+ bool fail = false;
+
+ if ((follow_links
+ ? getfilecon (filename, &scontext)
+ : lgetfilecon (filename, &scontext)) < 0)
+ {
+ error (0, errno, _("failed to get security context of %s"),
+ quoteaf (filename));
+ scontext = NULL;
+ fail = true;
+ }
+ strcpy (pformat + prefix_len, "s");
+ printf (pformat, (scontext ? scontext : "?"));
+ if (scontext)
+ freecon (scontext);
+ return fail;
+}
+
+/* Print statfs info. Return zero upon success, nonzero upon failure. */
+NODISCARD
+static bool
+print_statfs (char *pformat, size_t prefix_len, MAYBE_UNUSED char mod, char m,
+ int fd, char const *filename,
+ void const *data)
+{
+ STRUCT_STATVFS const *statfsbuf = data;
+ bool fail = false;
+
+ switch (m)
+ {
+ case 'n':
+ out_string (pformat, prefix_len, filename);
+ break;
+
+ case 'i':
+ {
+#if STRUCT_STATXFS_F_FSID_IS_INTEGER
+ uintmax_t fsid = statfsbuf->f_fsid;
+#else
+ typedef unsigned int fsid_word;
+ verify (alignof (STRUCT_STATVFS) % alignof (fsid_word) == 0);
+ verify (offsetof (STRUCT_STATVFS, f_fsid) % alignof (fsid_word) == 0);
+ verify (sizeof statfsbuf->f_fsid % alignof (fsid_word) == 0);
+ fsid_word const *p = (fsid_word *) &statfsbuf->f_fsid;
+
+ /* Assume a little-endian word order, as that is compatible
+ with glibc's statvfs implementation. */
+ uintmax_t fsid = 0;
+ int words = sizeof statfsbuf->f_fsid / sizeof *p;
+ for (int i = 0; i < words && i * sizeof *p < sizeof fsid; i++)
+ {
+ uintmax_t u = p[words - 1 - i];
+ fsid |= u << (i * CHAR_BIT * sizeof *p);
+ }
+#endif
+ out_uint_x (pformat, prefix_len, fsid);
+ }
+ break;
+
+ case 'l':
+ OUT_NAMEMAX (pformat, prefix_len, SB_F_NAMEMAX (statfsbuf));
+ break;
+ case 't':
+#if HAVE_STRUCT_STATXFS_F_TYPE
+ out_uint_x (pformat, prefix_len, statfsbuf->f_type);
+#else
+ fputc ('?', stdout);
+#endif
+ break;
+ case 'T':
+ out_string (pformat, prefix_len, human_fstype (statfsbuf));
+ break;
+ case 'b':
+ out_int (pformat, prefix_len, statfsbuf->f_blocks);
+ break;
+ case 'f':
+ out_int (pformat, prefix_len, statfsbuf->f_bfree);
+ break;
+ case 'a':
+ out_int (pformat, prefix_len, statfsbuf->f_bavail);
+ break;
+ case 's':
+ out_uint (pformat, prefix_len, statfsbuf->f_bsize);
+ break;
+ case 'S':
+ {
+ uintmax_t frsize = STATFS_FRSIZE (statfsbuf);
+ if (! frsize)
+ frsize = statfsbuf->f_bsize;
+ out_uint (pformat, prefix_len, frsize);
+ }
+ break;
+ case 'c':
+ out_uint (pformat, prefix_len, statfsbuf->f_files);
+ break;
+ case 'd':
+ out_int (pformat, prefix_len, statfsbuf->f_ffree);
+ break;
+ default:
+ fputc ('?', stdout);
+ break;
+ }
+ return fail;
+}
+
+/* Return any bind mounted source for a path.
+ The caller should not free the returned buffer.
+ Return NULL if no bind mount found. */
+NODISCARD
+static char const *
+find_bind_mount (char const * name)
+{
+ char const * bind_mount = NULL;
+
+ static struct mount_entry *mount_list;
+ static bool tried_mount_list = false;
+ if (!tried_mount_list) /* attempt/warn once per process. */
+ {
+ if (!(mount_list = read_file_system_list (false)))
+ error (0, errno, "%s", _("cannot read table of mounted file systems"));
+ tried_mount_list = true;
+ }
+
+ struct stat name_stats;
+ if (stat (name, &name_stats) != 0)
+ return NULL;
+
+ struct mount_entry *me;
+ for (me = mount_list; me; me = me->me_next)
+ {
+ if (me->me_dummy && me->me_devname[0] == '/'
+ && STREQ (me->me_mountdir, name))
+ {
+ struct stat dev_stats;
+
+ if (stat (me->me_devname, &dev_stats) == 0
+ && SAME_INODE (name_stats, dev_stats))
+ {
+ bind_mount = me->me_devname;
+ break;
+ }
+ }
+ }
+
+ return bind_mount;
+}
+
+/* Print mount point. Return zero upon success, nonzero upon failure. */
+NODISCARD
+static bool
+out_mount_point (char const *filename, char *pformat, size_t prefix_len,
+ const struct stat *statp)
+{
+
+ char const *np = "?", *bp = NULL;
+ char *mp = NULL;
+ bool fail = true;
+
+ /* Look for bind mounts first. Note we output the immediate alias,
+ rather than further resolving to a base device mount point. */
+ if (follow_links || !S_ISLNK (statp->st_mode))
+ {
+ char *resolved = canonicalize_file_name (filename);
+ if (!resolved)
+ {
+ error (0, errno, _("failed to canonicalize %s"), quoteaf (filename));
+ goto print_mount_point;
+ }
+ bp = find_bind_mount (resolved);
+ free (resolved);
+ if (bp)
+ {
+ fail = false;
+ goto print_mount_point;
+ }
+ }
+
+ /* If there is no direct bind mount, then navigate
+ back up the tree looking for a device change.
+ Note we don't detect if any of the directory components
+ are bind mounted to the same device, but that's OK
+ since we've not directly queried them. */
+ if ((mp = find_mount_point (filename, statp)))
+ {
+ /* This dir might be bind mounted to another device,
+ so we resolve the bound source in that case also. */
+ bp = find_bind_mount (mp);
+ fail = false;
+ }
+
+print_mount_point:
+
+ out_string (pformat, prefix_len, bp ? bp : mp ? mp : np);
+ free (mp);
+ return fail;
+}
+
+/* Map a TS with negative TS.tv_nsec to {0,0}. */
+static inline struct timespec
+neg_to_zero (struct timespec ts)
+{
+ if (0 <= ts.tv_nsec)
+ return ts;
+ struct timespec z = {0, 0};
+ return z;
+}
+
+/* Set the quoting style default if the environment variable
+ QUOTING_STYLE is set. */
+
+static void
+getenv_quoting_style (void)
+{
+ char const *q_style = getenv ("QUOTING_STYLE");
+ if (q_style)
+ {
+ int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals);
+ if (0 <= i)
+ set_quoting_style (NULL, quoting_style_vals[i]);
+ else
+ {
+ set_quoting_style (NULL, shell_escape_always_quoting_style);
+ error (0, 0, _("ignoring invalid value of environment "
+ "variable QUOTING_STYLE: %s"), quote (q_style));
+ }
+ }
+ else
+ set_quoting_style (NULL, shell_escape_always_quoting_style);
+}
+
+/* Equivalent to quotearg(), but explicit to avoid syntax checks. */
+#define quoteN(x) quotearg_style (get_quoting_style (NULL), x)
+
+/* Output a single-character \ escape. */
+
+static void
+print_esc_char (char c)
+{
+ switch (c)
+ {
+ case 'a': /* Alert. */
+ c ='\a';
+ break;
+ case 'b': /* Backspace. */
+ c ='\b';
+ break;
+ case 'e': /* Escape. */
+ c ='\x1B';
+ break;
+ case 'f': /* Form feed. */
+ c ='\f';
+ break;
+ case 'n': /* New line. */
+ c ='\n';
+ break;
+ case 'r': /* Carriage return. */
+ c ='\r';
+ break;
+ case 't': /* Horizontal tab. */
+ c ='\t';
+ break;
+ case 'v': /* Vertical tab. */
+ c ='\v';
+ break;
+ case '"':
+ case '\\':
+ break;
+ default:
+ error (0, 0, _("warning: unrecognized escape '\\%c'"), c);
+ break;
+ }
+ putchar (c);
+}
+
+ATTRIBUTE_PURE
+static size_t
+format_code_offset (char const *directive)
+{
+ size_t len = strspn (directive + 1, printf_flags);
+ char const *fmt_char = directive + len + 1;
+ fmt_char += strspn (fmt_char, digits);
+ if (*fmt_char == '.')
+ fmt_char += 1 + strspn (fmt_char + 1, digits);
+ return fmt_char - directive;
+}
+
+/* Print the information specified by the format string, FORMAT,
+ calling PRINT_FUNC for each %-directive encountered.
+ Return zero upon success, nonzero upon failure. */
+NODISCARD
+static bool
+print_it (char const *format, int fd, char const *filename,
+ bool (*print_func) (char *, size_t, char, char,
+ int, char const *, void const *),
+ void const *data)
+{
+ bool fail = false;
+
+ /* Add 2 to accommodate our conversion of the stat '%s' format string
+ to the longer printf '%llu' one. */
+ enum
+ {
+ MAX_ADDITIONAL_BYTES =
+ (MAX (sizeof PRIdMAX,
+ MAX (sizeof PRIoMAX, MAX (sizeof PRIuMAX, sizeof PRIxMAX)))
+ - 1)
+ };
+ size_t n_alloc = strlen (format) + MAX_ADDITIONAL_BYTES + 1;
+ char *dest = xmalloc (n_alloc);
+ char const *b;
+ for (b = format; *b; b++)
+ {
+ switch (*b)
+ {
+ case '%':
+ {
+ size_t len = format_code_offset (b);
+ char fmt_char = *(b + len);
+ char mod_char = 0;
+ memcpy (dest, b, len);
+ b += len;
+
+ switch (fmt_char)
+ {
+ case '\0':
+ --b;
+ FALLTHROUGH;
+ case '%':
+ if (1 < len)
+ {
+ dest[len] = fmt_char;
+ dest[len + 1] = '\0';
+ die (EXIT_FAILURE, 0, _("%s: invalid directive"),
+ quote (dest));
+ }
+ putchar ('%');
+ break;
+ case 'H':
+ case 'L':
+ mod_char = fmt_char;
+ fmt_char = *(b + 1);
+ if (print_func == print_stat
+ && (fmt_char == 'd' || fmt_char == 'r'))
+ {
+ b++;
+ }
+ else
+ {
+ fmt_char = mod_char;
+ mod_char = 0;
+ }
+ FALLTHROUGH;
+ default:
+ fail |= print_func (dest, len, mod_char, fmt_char,
+ fd, filename, data);
+ break;
+ }
+ break;
+ }
+
+ case '\\':
+ if ( ! interpret_backslash_escapes)
+ {
+ putchar ('\\');
+ break;
+ }
+ ++b;
+ if (isodigit (*b))
+ {
+ int esc_value = octtobin (*b);
+ int esc_length = 1; /* number of octal digits */
+ for (++b; esc_length < 3 && isodigit (*b);
+ ++esc_length, ++b)
+ {
+ esc_value = esc_value * 8 + octtobin (*b);
+ }
+ putchar (esc_value);
+ --b;
+ }
+ else if (*b == 'x' && isxdigit (to_uchar (b[1])))
+ {
+ int esc_value = hextobin (b[1]); /* Value of \xhh escape. */
+ /* A hexadecimal \xhh escape sequence must have
+ 1 or 2 hex. digits. */
+ ++b;
+ if (isxdigit (to_uchar (b[1])))
+ {
+ ++b;
+ esc_value = esc_value * 16 + hextobin (*b);
+ }
+ putchar (esc_value);
+ }
+ else if (*b == '\0')
+ {
+ error (0, 0, _("warning: backslash at end of format"));
+ putchar ('\\');
+ /* Arrange to exit the loop. */
+ --b;
+ }
+ else
+ {
+ print_esc_char (*b);
+ }
+ break;
+
+ default:
+ putchar (*b);
+ break;
+ }
+ }
+ free (dest);
+
+ fputs (trailing_delim, stdout);
+
+ return fail;
+}
+
+/* Stat the file system and print what we find. */
+NODISCARD
+static bool
+do_statfs (char const *filename, char const *format)
+{
+ STRUCT_STATVFS statfsbuf;
+
+ if (STREQ (filename, "-"))
+ {
+ error (0, 0, _("using %s to denote standard input does not work"
+ " in file system mode"), quoteaf (filename));
+ return false;
+ }
+
+ if (STATFS (filename, &statfsbuf) != 0)
+ {
+ error (0, errno, _("cannot read file system information for %s"),
+ quoteaf (filename));
+ return false;
+ }
+
+ bool fail = print_it (format, -1, filename, print_statfs, &statfsbuf);
+ return ! fail;
+}
+
+struct print_args {
+ struct stat *st;
+ struct timespec btime;
+};
+
+/* Ask statx to avoid syncing? */
+static bool dont_sync;
+
+/* Ask statx to force sync? */
+static bool force_sync;
+
+#if USE_STATX
+static unsigned int
+fmt_to_mask (char fmt)
+{
+ switch (fmt)
+ {
+ case 'N':
+ return STATX_MODE;
+ case 'd':
+ case 'D':
+ return STATX_MODE;
+ case 'i':
+ return STATX_INO;
+ case 'a':
+ case 'A':
+ return STATX_MODE;
+ case 'f':
+ return STATX_MODE|STATX_TYPE;
+ case 'F':
+ return STATX_TYPE;
+ case 'h':
+ return STATX_NLINK;
+ case 'u':
+ case 'U':
+ return STATX_UID;
+ case 'g':
+ case 'G':
+ return STATX_GID;
+ case 'm':
+ return STATX_MODE|STATX_INO;
+ case 's':
+ return STATX_SIZE;
+ case 't':
+ case 'T':
+ return STATX_MODE;
+ case 'b':
+ return STATX_BLOCKS;
+ case 'w':
+ case 'W':
+ return STATX_BTIME;
+ case 'x':
+ case 'X':
+ return STATX_ATIME;
+ case 'y':
+ case 'Y':
+ return STATX_MTIME;
+ case 'z':
+ case 'Z':
+ return STATX_CTIME;
+ }
+ return 0;
+}
+
+ATTRIBUTE_PURE
+static unsigned int
+format_to_mask (char const *format)
+{
+ unsigned int mask = 0;
+ char const *b;
+
+ for (b = format; *b; b++)
+ {
+ if (*b != '%')
+ continue;
+
+ b += format_code_offset (b);
+ if (*b == '\0')
+ break;
+ mask |= fmt_to_mask (*b);
+ }
+ return mask;
+}
+
+/* statx the file and print what we find */
+NODISCARD
+static bool
+do_stat (char const *filename, char const *format, char const *format2)
+{
+ int fd = STREQ (filename, "-") ? 0 : AT_FDCWD;
+ int flags = 0;
+ struct stat st;
+ struct statx stx = { 0, };
+ char const *pathname = filename;
+ struct print_args pa;
+ pa.st = &st;
+ pa.btime = (struct timespec) {-1, -1};
+
+ if (AT_FDCWD != fd)
+ {
+ pathname = "";
+ flags = AT_EMPTY_PATH;
+ }
+ else if (!follow_links)
+ {
+ flags = AT_SYMLINK_NOFOLLOW;
+ }
+
+ if (dont_sync)
+ flags |= AT_STATX_DONT_SYNC;
+ else if (force_sync)
+ flags |= AT_STATX_FORCE_SYNC;
+
+ if (! force_sync)
+ flags |= AT_NO_AUTOMOUNT;
+
+ fd = statx (fd, pathname, flags, format_to_mask (format), &stx);
+ if (fd < 0)
+ {
+ if (flags & AT_EMPTY_PATH)
+ error (0, errno, _("cannot stat standard input"));
+ else
+ error (0, errno, _("cannot statx %s"), quoteaf (filename));
+ return false;
+ }
+
+ if (S_ISBLK (stx.stx_mode) || S_ISCHR (stx.stx_mode))
+ format = format2;
+
+ statx_to_stat (&stx, &st);
+ if (stx.stx_mask & STATX_BTIME)
+ pa.btime = statx_timestamp_to_timespec (stx.stx_btime);
+
+ bool fail = print_it (format, fd, filename, print_stat, &pa);
+ return ! fail;
+}
+
+#else /* USE_STATX */
+
+static struct timespec
+get_birthtime (int fd, char const *filename, struct stat const *st)
+{
+ struct timespec ts = get_stat_birthtime (st);
+
+# if HAVE_GETATTRAT
+ if (ts.tv_nsec < 0)
+ {
+ nvlist_t *response;
+ if ((fd < 0
+ ? getattrat (AT_FDCWD, XATTR_VIEW_READWRITE, filename, &response)
+ : fgetattr (fd, XATTR_VIEW_READWRITE, &response))
+ == 0)
+ {
+ uint64_t *val;
+ uint_t n;
+ if (nvlist_lookup_uint64_array (response, A_CRTIME, &val, &n) == 0
+ && 2 <= n
+ && val[0] <= TYPE_MAXIMUM (time_t)
+ && val[1] < 1000000000 * 2 /* for leap seconds */)
+ {
+ ts.tv_sec = val[0];
+ ts.tv_nsec = val[1];
+ }
+ nvlist_free (response);
+ }
+ }
+# endif
+
+ return ts;
+}
+
+
+/* stat the file and print what we find */
+NODISCARD
+static bool
+do_stat (char const *filename, char const *format,
+ char const *format2)
+{
+ int fd = STREQ (filename, "-") ? 0 : -1;
+ struct stat statbuf;
+ struct print_args pa;
+ pa.st = &statbuf;
+ pa.btime = (struct timespec) {-1, -1};
+
+ if (0 <= fd)
+ {
+ if (fstat (fd, &statbuf) != 0)
+ {
+ error (0, errno, _("cannot stat standard input"));
+ return false;
+ }
+ }
+ /* We can't use the shorter
+ (follow_links?stat:lstat) (filename, &statbug)
+ since stat might be a function-like macro. */
+ else if ((follow_links
+ ? stat (filename, &statbuf)
+ : lstat (filename, &statbuf)) != 0)
+ {
+ error (0, errno, _("cannot stat %s"), quoteaf (filename));
+ return false;
+ }
+
+ if (S_ISBLK (statbuf.st_mode) || S_ISCHR (statbuf.st_mode))
+ format = format2;
+
+ bool fail = print_it (format, fd, filename, print_stat, &pa);
+ return ! fail;
+}
+#endif /* USE_STATX */
+
+
+/* Print stat info. Return zero upon success, nonzero upon failure. */
+static bool
+print_stat (char *pformat, size_t prefix_len, char mod, char m,
+ int fd, char const *filename, void const *data)
+{
+ struct print_args *parg = (struct print_args *) data;
+ struct stat *statbuf = parg->st;
+ struct timespec btime = parg->btime;
+ struct passwd *pw_ent;
+ struct group *gw_ent;
+ bool fail = false;
+
+ switch (m)
+ {
+ case 'n':
+ out_string (pformat, prefix_len, filename);
+ break;
+ case 'N':
+ out_string (pformat, prefix_len, quoteN (filename));
+ if (S_ISLNK (statbuf->st_mode))
+ {
+ char *linkname = areadlink_with_size (filename, statbuf->st_size);
+ if (linkname == NULL)
+ {
+ error (0, errno, _("cannot read symbolic link %s"),
+ quoteaf (filename));
+ return true;
+ }
+ printf (" -> ");
+ out_string (pformat, prefix_len, quoteN (linkname));
+ free (linkname);
+ }
+ break;
+ case 'd':
+ if (mod == 'H')
+ out_uint (pformat, prefix_len, major (statbuf->st_dev));
+ else if (mod == 'L')
+ out_uint (pformat, prefix_len, minor (statbuf->st_dev));
+ else
+ out_uint (pformat, prefix_len, statbuf->st_dev);
+ break;
+ case 'D':
+ out_uint_x (pformat, prefix_len, statbuf->st_dev);
+ break;
+ case 'i':
+ out_uint (pformat, prefix_len, statbuf->st_ino);
+ break;
+ case 'a':
+ out_uint_o (pformat, prefix_len, statbuf->st_mode & CHMOD_MODE_BITS);
+ break;
+ case 'A':
+ out_string (pformat, prefix_len, human_access (statbuf));
+ break;
+ case 'f':
+ out_uint_x (pformat, prefix_len, statbuf->st_mode);
+ break;
+ case 'F':
+ out_string (pformat, prefix_len, file_type (statbuf));
+ break;
+ case 'h':
+ out_uint (pformat, prefix_len, statbuf->st_nlink);
+ break;
+ case 'u':
+ out_uint (pformat, prefix_len, statbuf->st_uid);
+ break;
+ case 'U':
+ pw_ent = getpwuid (statbuf->st_uid);
+ out_string (pformat, prefix_len,
+ pw_ent ? pw_ent->pw_name : "UNKNOWN");
+ break;
+ case 'g':
+ out_uint (pformat, prefix_len, statbuf->st_gid);
+ break;
+ case 'G':
+ gw_ent = getgrgid (statbuf->st_gid);
+ out_string (pformat, prefix_len,
+ gw_ent ? gw_ent->gr_name : "UNKNOWN");
+ break;
+ case 'm':
+ fail |= out_mount_point (filename, pformat, prefix_len, statbuf);
+ break;
+ case 's':
+ out_int (pformat, prefix_len, statbuf->st_size);
+ break;
+ case 'r':
+ if (mod == 'H')
+ out_uint (pformat, prefix_len, major (statbuf->st_rdev));
+ else if (mod == 'L')
+ out_uint (pformat, prefix_len, minor (statbuf->st_rdev));
+ else
+ out_uint (pformat, prefix_len, statbuf->st_rdev);
+ break;
+ case 'R':
+ out_uint_x (pformat, prefix_len, statbuf->st_rdev);
+ break;
+ case 't':
+ out_uint_x (pformat, prefix_len, major (statbuf->st_rdev));
+ break;
+ case 'T':
+ out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev));
+ break;
+ case 'B':
+ out_uint (pformat, prefix_len, ST_NBLOCKSIZE);
+ break;
+ case 'b':
+ out_uint (pformat, prefix_len, ST_NBLOCKS (*statbuf));
+ break;
+ case 'o':
+ out_uint (pformat, prefix_len, ST_BLKSIZE (*statbuf));
+ break;
+ case 'w':
+ {
+#if ! USE_STATX
+ btime = get_birthtime (fd, filename, statbuf);
+#endif
+ if (btime.tv_nsec < 0)
+ out_string (pformat, prefix_len, "-");
+ else
+ out_string (pformat, prefix_len, human_time (btime));
+ }
+ break;
+ case 'W':
+ {
+#if ! USE_STATX
+ btime = get_birthtime (fd, filename, statbuf);
+#endif
+ out_epoch_sec (pformat, prefix_len, neg_to_zero (btime));
+ }
+ break;
+ case 'x':
+ out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
+ break;
+ case 'X':
+ out_epoch_sec (pformat, prefix_len, get_stat_atime (statbuf));
+ break;
+ case 'y':
+ out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
+ break;
+ case 'Y':
+ out_epoch_sec (pformat, prefix_len, get_stat_mtime (statbuf));
+ break;
+ case 'z':
+ out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
+ break;
+ case 'Z':
+ out_epoch_sec (pformat, prefix_len, get_stat_ctime (statbuf));
+ break;
+ case 'C':
+ fail |= out_file_context (pformat, prefix_len, filename);
+ break;
+ default:
+ fputc ('?', stdout);
+ break;
+ }
+ return fail;
+}
+
+/* Return an allocated format string in static storage that
+ corresponds to whether FS and TERSE options were declared. */
+static char *
+default_format (bool fs, bool terse, bool device)
+{
+ char *format;
+ if (fs)
+ {
+ if (terse)
+ format = xstrdup (fmt_terse_fs);
+ else
+ {
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' with --file-system, and NOT from printf. */
+ format = xstrdup (_(" File: \"%n\"\n"
+ " ID: %-8i Namelen: %-7l Type: %T\n"
+ "Block size: %-10s Fundamental block size: %S\n"
+ "Blocks: Total: %-10b Free: %-10f Available: %a\n"
+ "Inodes: Total: %-10c Free: %d\n"));
+ }
+ }
+ else /* ! fs */
+ {
+ if (terse)
+ {
+ if (0 < is_selinux_enabled ())
+ format = xstrdup (fmt_terse_selinux);
+ else
+ format = xstrdup (fmt_terse_regular);
+ }
+ else
+ {
+ char *temp;
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xstrdup (_("\
+ File: %N\n\
+ Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n\
+"));
+
+ temp = format;
+ if (device)
+ {
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xasprintf ("%s%s", format, _("\
+" "Device: %Hd,%Ld\tInode: %-10i Links: %-5h Device type: %Hr,%Lr\n\
+"));
+ }
+ else
+ {
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xasprintf ("%s%s", format, _("\
+" "Device: %Hd,%Ld\tInode: %-10i Links: %h\n\
+"));
+ }
+ free (temp);
+
+ temp = format;
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xasprintf ("%s%s", format, _("\
+" "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n\
+"));
+ free (temp);
+
+ if (0 < is_selinux_enabled ())
+ {
+ temp = format;
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xasprintf ("%s%s", format, _("Context: %C\n"));
+ free (temp);
+ }
+
+ temp = format;
+ /* TRANSLATORS: This string uses format specifiers from
+ 'stat --help' without --file-system, and NOT from printf. */
+ format = xasprintf ("%s%s", format,
+ _("Access: %x\n"
+ "Modify: %y\n"
+ "Change: %z\n"
+ " Birth: %w\n"));
+ free (temp);
+ }
+ }
+ return format;
+}
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ emit_try_help ();
+ else
+ {
+ printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
+ fputs (_("\
+Display file or file system status.\n\
+"), stdout);
+
+ emit_mandatory_arg_note ();
+
+ fputs (_("\
+ -L, --dereference follow links\n\
+ -f, --file-system display file system status instead of file status\n\
+"), stdout);
+ fputs (_("\
+ --cached=MODE specify how to use cached attributes;\n\
+ useful on remote file systems. See MODE below\n\
+"), stdout);
+ fputs (_("\
+ -c --format=FORMAT use the specified FORMAT instead of the default;\n\
+ output a newline after each use of FORMAT\n\
+ --printf=FORMAT like --format, but interpret backslash escapes,\n\
+ and do not output a mandatory trailing newline;\n\
+ if you want a newline, include \\n in FORMAT\n\
+ -t, --terse print the information in terse form\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+
+ fputs (_("\n\
+The MODE argument of --cached can be: always, never, or default.\n\
+'always' will use cached attributes if available, while\n\
+'never' will try to synchronize with the latest attributes, and\n\
+'default' will leave it up to the underlying file system.\n\
+"), stdout);
+
+ fputs (_("\n\
+The valid format sequences for files (without --file-system):\n\
+\n\
+ %a permission bits in octal (note '#' and '0' printf flags)\n\
+ %A permission bits and file type in human readable form\n\
+ %b number of blocks allocated (see %B)\n\
+ %B the size in bytes of each block reported by %b\n\
+ %C SELinux security context string\n\
+"), stdout);
+ fputs (_("\
+ %d device number in decimal (st_dev)\n\
+ %D device number in hex (st_dev)\n\
+ %Hd major device number in decimal\n\
+ %Ld minor device number in decimal\n\
+ %f raw mode in hex\n\
+ %F file type\n\
+ %g group ID of owner\n\
+ %G group name of owner\n\
+"), stdout);
+ fputs (_("\
+ %h number of hard links\n\
+ %i inode number\n\
+ %m mount point\n\
+ %n file name\n\
+ %N quoted file name with dereference if symbolic link\n\
+ %o optimal I/O transfer size hint\n\
+ %s total size, in bytes\n\
+ %r device type in decimal (st_rdev)\n\
+ %R device type in hex (st_rdev)\n\
+ %Hr major device type in decimal, for character/block device special files\n\
+ %Lr minor device type in decimal, for character/block device special files\n\
+ %t major device type in hex, for character/block device special files\n\
+ %T minor device type in hex, for character/block device special files\n\
+"), stdout);
+ fputs (_("\
+ %u user ID of owner\n\
+ %U user name of owner\n\
+ %w time of file birth, human-readable; - if unknown\n\
+ %W time of file birth, seconds since Epoch; 0 if unknown\n\
+ %x time of last access, human-readable\n\
+ %X time of last access, seconds since Epoch\n\
+ %y time of last data modification, human-readable\n\
+ %Y time of last data modification, seconds since Epoch\n\
+ %z time of last status change, human-readable\n\
+ %Z time of last status change, seconds since Epoch\n\
+\n\
+"), stdout);
+
+ fputs (_("\
+Valid format sequences for file systems:\n\
+\n\
+ %a free blocks available to non-superuser\n\
+ %b total data blocks in file system\n\
+ %c total file nodes in file system\n\
+ %d free file nodes in file system\n\
+ %f free blocks in file system\n\
+"), stdout);
+ fputs (_("\
+ %i file system ID in hex\n\
+ %l maximum length of filenames\n\
+ %n file name\n\
+ %s block size (for faster transfers)\n\
+ %S fundamental block size (for block counts)\n\
+ %t file system type in hex\n\
+ %T file system type in human readable form\n\
+"), stdout);
+
+ printf (_("\n\
+--terse is equivalent to the following FORMAT:\n\
+ %s\
+"),
+#if HAVE_SELINUX_SELINUX_H
+ fmt_terse_selinux
+#else
+ fmt_terse_regular
+#endif
+ );
+
+ printf (_("\
+--terse --file-system is equivalent to the following FORMAT:\n\
+ %s\
+"), fmt_terse_fs);
+
+ printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
+ emit_ancillary_info (PROGRAM_NAME);
+ }
+ exit (status);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int c;
+ bool fs = false;
+ bool terse = false;
+ char *format = NULL;
+ char *format2;
+ bool ok = true;
+
+ initialize_main (&argc, &argv);
+ set_program_name (argv[0]);
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ struct lconv const *locale = localeconv ();
+ decimal_point = (locale->decimal_point[0] ? locale->decimal_point : ".");
+ decimal_point_len = strlen (decimal_point);
+
+ atexit (close_stdout);
+
+ while ((c = getopt_long (argc, argv, "c:fLt", long_options, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case PRINTF_OPTION:
+ format = optarg;
+ interpret_backslash_escapes = true;
+ trailing_delim = "";
+ break;
+
+ case 'c':
+ format = optarg;
+ interpret_backslash_escapes = false;
+ trailing_delim = "\n";
+ break;
+
+ case 'L':
+ follow_links = true;
+ break;
+
+ case 'f':
+ fs = true;
+ break;
+
+ case 't':
+ terse = true;
+ break;
+
+ case 0:
+ switch (XARGMATCH ("--cached", optarg, cached_args, cached_modes))
+ {
+ case cached_never:
+ force_sync = true;
+ dont_sync = false;
+ break;
+ case cached_always:
+ force_sync = false;
+ dont_sync = true;
+ break;
+ case cached_default:
+ force_sync = false;
+ dont_sync = false;
+ }
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (argc == optind)
+ {
+ error (0, 0, _("missing operand"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (format)
+ {
+ if (strstr (format, "%N"))
+ getenv_quoting_style ();
+ format2 = format;
+ }
+ else
+ {
+ format = default_format (fs, terse, /* device= */ false);
+ format2 = default_format (fs, terse, /* device= */ true);
+ }
+
+ for (int i = optind; i < argc; i++)
+ ok &= (fs
+ ? do_statfs (argv[i], format)
+ : do_stat (argv[i], format, format2));
+
+ main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}