summaryrefslogtreecommitdiff
path: root/src/stat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stat.c')
-rw-r--r--src/stat.c979
1 files changed, 979 insertions, 0 deletions
diff --git a/src/stat.c b/src/stat.c
new file mode 100644
index 0000000..ca84236
--- /dev/null
+++ b/src/stat.c
@@ -0,0 +1,979 @@
+/* stat.c -- display file or file system status
+ Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation.
+
+ 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.
+
+ Written by Michael Meskes. */
+
+#include <config.h>
+
+/* Keep this conditional in sync with the similar conditional in
+ ../m4/stat-prog.m4. */
+#if (STAT_STATVFS \
+ && (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 <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_NETINET_IN_H && 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 "system.h"
+
+#include "error.h"
+#include "filemode.h"
+#include "file-type.h"
+#include "fs.h"
+#include "getopt.h"
+#include "inttostr.h"
+#include "quote.h"
+#include "quotearg.h"
+#include "stat-time.h"
+#include "strftime.h"
+#include "xreadlink.h"
+
+#define alignof(type) offsetof (struct { char c; type x; }, x)
+
+#if USE_STATVFS
+# define STRUCT_STATVFS struct 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
+# define STATFS statvfs
+# 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)
+# 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. */
+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
+# define STATFS_FRSIZE(S) 0
+# endif
+#endif
+
+#ifdef SB_F_NAMEMAX
+# define OUT_NAMEMAX out_uint
+#else
+/* NetBSD 1.5.2 has neither f_namemax nor f_namelen. */
+# 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
+
+/* 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')
+
+#define PROGRAM_NAME "stat"
+
+#define AUTHORS "Michael Meskes"
+
+enum
+{
+ PRINTF_OPTION = CHAR_MAX + 1
+};
+
+static struct option const long_options[] = {
+ {"dereference", no_argument, NULL, 'L'},
+ {"file-system", no_argument, NULL, 'f'},
+ {"filesystem", no_argument, NULL, 'f'}, /* obsolete and undocumented alias */
+ {"format", required_argument, NULL, 'c'},
+ {"printf", required_argument, NULL, PRINTF_OPTION},
+ {"terse", no_argument, NULL, 't'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+char *program_name;
+
+/* 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 = "";
+
+/* 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 (Linux).
+ But f_type may only exist in statfs (Cygwin). */
+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__
+
+ /* 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_AFFS: /* 0xADFF */
+ return "affs";
+ case S_MAGIC_DEVPTS: /* 0x1CD1 */
+ return "devpts";
+ case S_MAGIC_EXT: /* 0x137D */
+ return "ext";
+ case S_MAGIC_EXT2_OLD: /* 0xEF51 */
+ return "ext2";
+ case S_MAGIC_EXT2: /* 0xEF53 */
+ return "ext2/ext3";
+ case S_MAGIC_JFS: /* 0x3153464a */
+ return "jfs";
+ case S_MAGIC_XFS: /* 0x58465342 */
+ return "xfs";
+ case S_MAGIC_HPFS: /* 0xF995E849 */
+ return "hpfs";
+ case S_MAGIC_ISOFS: /* 0x9660 */
+ return "isofs";
+ case S_MAGIC_ISOFS_WIN: /* 0x4000 */
+ return "isofs";
+ case S_MAGIC_ISOFS_R_WIN: /* 0x4004 */
+ return "isofs";
+ case S_MAGIC_MINIX: /* 0x137F */
+ return "minix";
+ case S_MAGIC_MINIX_30: /* 0x138F */
+ return "minix (30 char.)";
+ case S_MAGIC_MINIX_V2: /* 0x2468 */
+ return "minix v2";
+ case S_MAGIC_MINIX_V2_30: /* 0x2478 */
+ return "minix v2 (30 char.)";
+ case S_MAGIC_MSDOS: /* 0x4d44 */
+ return "msdos";
+ case S_MAGIC_FAT: /* 0x4006 */
+ return "fat";
+ case S_MAGIC_NCP: /* 0x564c */
+ return "novell";
+ case S_MAGIC_NFS: /* 0x6969 */
+ return "nfs";
+ case S_MAGIC_PROC: /* 0x9fa0 */
+ return "proc";
+ case S_MAGIC_SMB: /* 0x517B */
+ return "smb";
+ case S_MAGIC_XENIX: /* 0x012FF7B4 */
+ return "xenix";
+ case S_MAGIC_SYSV4: /* 0x012FF7B5 */
+ return "sysv4";
+ case S_MAGIC_SYSV2: /* 0x012FF7B6 */
+ return "sysv2";
+ case S_MAGIC_COH: /* 0x012FF7B7 */
+ return "coh";
+ case S_MAGIC_UFS: /* 0x00011954 */
+ return "ufs";
+ case S_MAGIC_XIAFS: /* 0x012FD16D */
+ return "xia";
+ case S_MAGIC_NTFS: /* 0x5346544e */
+ return "ntfs";
+ case S_MAGIC_TMPFS: /* 0x1021994 */
+ return "tmpfs";
+ case S_MAGIC_REISERFS: /* 0x52654973 */
+ return "reiserfs";
+ case S_MAGIC_CRAMFS: /* 0x28cd3d45 */
+ return "cramfs";
+ case S_MAGIC_ROMFS: /* 0x7275 */
+ return "romfs";
+ case S_MAGIC_RAMFS: /* 0x858458f6 */
+ return "ramfs";
+ case S_MAGIC_SQUASHFS: /* 0x73717368 */
+ return "squashfs";
+ case S_MAGIC_SYSFS: /* 0x62656572 */
+ return "sysfs";
+# 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
+}
+
+static char *
+human_access (struct stat const *statbuf)
+{
+ static char modebuf[12];
+ filemodestring (statbuf, modebuf);
+ modebuf[10] = 0;
+ return modebuf;
+}
+
+static char *
+human_time (struct timespec t)
+{
+ static char str[MAX (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 +ZZZZ"))];
+ struct tm const *tm = localtime (&t.tv_sec);
+ if (tm == NULL)
+ return (TYPE_SIGNED (time_t)
+ ? imaxtostr (t.tv_sec, str)
+ : umaxtostr (t.tv_sec, str));
+ nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", tm, 0, t.tv_nsec);
+ return str;
+}
+
+static void
+out_string (char *pformat, size_t prefix_len, char const *arg)
+{
+ strcpy (pformat + prefix_len, "s");
+ printf (pformat, arg);
+}
+static void
+out_int (char *pformat, size_t prefix_len, intmax_t arg)
+{
+ strcpy (pformat + prefix_len, PRIdMAX);
+ printf (pformat, arg);
+}
+static void
+out_uint (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ strcpy (pformat + prefix_len, PRIuMAX);
+ printf (pformat, arg);
+}
+static void
+out_uint_o (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ strcpy (pformat + prefix_len, PRIoMAX);
+ printf (pformat, arg);
+}
+static void
+out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg)
+{
+ strcpy (pformat + prefix_len, PRIxMAX);
+ printf (pformat, arg);
+}
+
+/* print statfs info */
+static void
+print_statfs (char *pformat, size_t prefix_len, char m, char const *filename,
+ void const *data)
+{
+ STRUCT_STATVFS const *statfsbuf = data;
+
+ 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;
+ int i;
+ for (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_int (pformat, prefix_len, statfsbuf->f_files);
+ break;
+ case 'd':
+ out_int (pformat, prefix_len, statfsbuf->f_ffree);
+ break;
+
+ default:
+ fputc ('?', stdout);
+ break;
+ }
+}
+
+/* print stat info */
+static void
+print_stat (char *pformat, size_t prefix_len, char m,
+ char const *filename, void const *data)
+{
+ struct stat *statbuf = (struct stat *) data;
+ struct passwd *pw_ent;
+ struct group *gw_ent;
+
+ switch (m)
+ {
+ case 'n':
+ out_string (pformat, prefix_len, filename);
+ break;
+ case 'N':
+ out_string (pformat, prefix_len, quote (filename));
+ if (S_ISLNK (statbuf->st_mode))
+ {
+ char *linkname = xreadlink_with_size (filename, statbuf->st_size);
+ if (linkname == NULL)
+ {
+ error (0, errno, _("cannot read symbolic link %s"),
+ quote (filename));
+ return;
+ }
+ printf (" -> ");
+ out_string (pformat, prefix_len, quote (linkname));
+ }
+ break;
+ case 'd':
+ 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':
+ setpwent ();
+ 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':
+ setgrent ();
+ gw_ent = getgrgid (statbuf->st_gid);
+ out_string (pformat, prefix_len,
+ gw_ent ? gw_ent->gr_name : "UNKNOWN");
+ 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 's':
+ out_uint (pformat, prefix_len, statbuf->st_size);
+ 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, statbuf->st_blksize);
+ break;
+ case 'x':
+ out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
+ break;
+ case 'X':
+ if (TYPE_SIGNED (time_t))
+ out_int (pformat, prefix_len, statbuf->st_atime);
+ else
+ out_uint (pformat, prefix_len, statbuf->st_atime);
+ break;
+ case 'y':
+ out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
+ break;
+ case 'Y':
+ if (TYPE_SIGNED (time_t))
+ out_int (pformat, prefix_len, statbuf->st_mtime);
+ else
+ out_uint (pformat, prefix_len, statbuf->st_mtime);
+ break;
+ case 'z':
+ out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
+ break;
+ case 'Z':
+ if (TYPE_SIGNED (time_t))
+ out_int (pformat, prefix_len, statbuf->st_ctime);
+ else
+ out_uint (pformat, prefix_len, statbuf->st_ctime);
+ break;
+ default:
+ fputc ('?', stdout);
+ break;
+ }
+}
+
+/* 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 '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);
+}
+
+static void
+print_it (char const *format, char const *filename,
+ void (*print_func) (char *, size_t, char, char const *, void const *),
+ void const *data)
+{
+ /* 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 = strspn (b + 1, "#-+.I 0123456789");
+ char const *fmt_char = b + len + 1;
+ memcpy (dest, b, len + 1);
+
+ b = fmt_char;
+ switch (*fmt_char)
+ {
+ case '\0':
+ --b;
+ /* fall through */
+ case '%':
+ if (0 < len)
+ {
+ dest[len + 1] = *fmt_char;
+ dest[len + 2] = '\0';
+ error (EXIT_FAILURE, 0, _("%s: invalid directive"),
+ quotearg_colon (dest));
+ }
+ putchar ('%');
+ break;
+ default:
+ print_func (dest, len + 1, *fmt_char, 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);
+}
+
+/* Stat the file system and print what we find. */
+static bool
+do_statfs (char const *filename, bool terse, char const *format)
+{
+ STRUCT_STATVFS statfsbuf;
+
+ if (STATFS (filename, &statfsbuf) != 0)
+ {
+ error (0, errno, _("cannot read file system information for %s"),
+ quote (filename));
+ return false;
+ }
+
+ if (format == NULL)
+ {
+ format = (terse
+ ? "%n %i %l %t %s %S %b %f %a %c %d\n"
+ : " 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");
+ }
+
+ print_it (format, filename, print_statfs, &statfsbuf);
+ return true;
+}
+
+/* stat the file and print what we find */
+static bool
+do_stat (char const *filename, bool follow_links, bool terse,
+ char const *format)
+{
+ struct stat statbuf;
+
+ if ((follow_links ? stat : lstat) (filename, &statbuf) != 0)
+ {
+ error (0, errno, _("cannot stat %s"), quote (filename));
+ return false;
+ }
+
+ if (format == NULL)
+ {
+ if (terse)
+ {
+ format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n";
+ }
+ else
+ {
+ /* Temporary hack to match original output until conditional
+ implemented. */
+ if (S_ISBLK (statbuf.st_mode) || S_ISCHR (statbuf.st_mode))
+ {
+ format =
+ " File: %N\n"
+ " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
+ "Device: %Dh/%dd\tInode: %-10i Links: %-5h"
+ " Device type: %t,%T\n"
+ "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n"
+ "Access: %x\n" "Modify: %y\n" "Change: %z\n";
+ }
+ else
+ {
+ format =
+ " File: %N\n"
+ " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"
+ "Device: %Dh/%dd\tInode: %-10i Links: %h\n"
+ "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n"
+ "Access: %x\n" "Modify: %y\n" "Change: %z\n";
+ }
+ }
+ }
+ print_it (format, filename, print_stat, &statbuf);
+ return true;
+}
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("Usage: %s [OPTION] FILE...\n"), program_name);
+ fputs (_("\
+Display file or file system status.\n\
+\n\
+ -L, --dereference follow links\n\
+ -f, --file-system display file system status instead of file status\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 valid format sequences for files (without --file-system):\n\
+\n\
+ %a Access rights in octal\n\
+ %A Access rights 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\
+"), stdout);
+ fputs (_("\
+ %d Device number in decimal\n\
+ %D Device number in hex\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\
+ %n File name\n\
+ %N Quoted file name with dereference if symbolic link\n\
+ %o I/O block size\n\
+ %s Total size, in bytes\n\
+ %t Major device type in hex\n\
+ %T Minor device type in hex\n\
+"), stdout);
+ fputs (_("\
+ %u User ID of owner\n\
+ %U User name of owner\n\
+ %x Time of last access\n\
+ %X Time of last access as seconds since Epoch\n\
+ %y Time of last modification\n\
+ %Y Time of last modification as seconds since Epoch\n\
+ %z Time of last change\n\
+ %Z Time of last change as 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 Type in hex\n\
+ %T Type in human readable form\n\
+"), stdout);
+ printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
+ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ }
+ exit (status);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int c;
+ int i;
+ bool follow_links = false;
+ bool fs = false;
+ bool terse = false;
+ char *format = NULL;
+ bool ok = true;
+
+ initialize_main (&argc, &argv);
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ 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_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);
+ }
+
+ for (i = optind; i < argc; i++)
+ ok &= (fs
+ ? do_statfs (argv[i], terse, format)
+ : do_stat (argv[i], follow_links, terse, format));
+
+ exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}