summaryrefslogtreecommitdiff
path: root/getfacl.c
diff options
context:
space:
mode:
Diffstat (limited to 'getfacl.c')
-rw-r--r--getfacl.c834
1 files changed, 834 insertions, 0 deletions
diff --git a/getfacl.c b/getfacl.c
new file mode 100644
index 0000000000..8e18e49f77
--- /dev/null
+++ b/getfacl.c
@@ -0,0 +1,834 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1999, 2001, 2002 Robert N M Watson
+ * All rights reserved.
+ *
+ * Copyright (c) 2026
+ * Project Tick. All rights reserved.
+ *
+ * This software was developed by Robert Watson for the TrustedBSD Project.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Linux-native getfacl implementation.
+ *
+ * FreeBSD's original utility depends on sys/acl.h and acl_*_np interfaces.
+ * This port reads Linux POSIX ACLs directly from xattrs instead:
+ * - system.posix_acl_access
+ * - system.posix_acl_default
+ *
+ * The goal is a native Linux implementation that builds cleanly with musl
+ * without a FreeBSD ACL compatibility layer.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef le16toh
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define le16toh(x) (x)
+#define le32toh(x) (x)
+#else
+#define le16toh(x) __builtin_bswap16(x)
+#define le32toh(x) __builtin_bswap32(x)
+#endif
+#endif
+
+#ifndef ENOATTR
+#define ENOATTR ENODATA
+#endif
+
+#define ACL_XATTR_ACCESS "system.posix_acl_access"
+#define ACL_XATTR_DEFAULT "system.posix_acl_default"
+
+#define POSIX_ACL_XATTR_VERSION 0x0002U
+#define ACL_UNDEFINED_ID ((uint32_t)-1)
+
+#define ACL_READ 0x04U
+#define ACL_WRITE 0x02U
+#define ACL_EXECUTE 0x01U
+
+#define ACL_USER_OBJ 0x01U
+#define ACL_USER 0x02U
+#define ACL_GROUP_OBJ 0x04U
+#define ACL_GROUP 0x08U
+#define ACL_MASK 0x10U
+#define ACL_OTHER 0x20U
+
+struct posix_acl_xattr_entry_linux {
+ uint16_t e_tag;
+ uint16_t e_perm;
+ uint32_t e_id;
+};
+
+struct posix_acl_xattr_header_linux {
+ uint32_t a_version;
+};
+
+enum acl_kind {
+ ACL_KIND_ACCESS,
+ ACL_KIND_DEFAULT,
+};
+
+enum xattr_result {
+ XATTR_RESULT_ERROR = -1,
+ XATTR_RESULT_ABSENT = 0,
+ XATTR_RESULT_PRESENT = 1,
+};
+
+struct options {
+ bool default_acl;
+ bool no_follow;
+ bool numeric_ids;
+ bool omit_header;
+ bool skip_base;
+};
+
+struct acl_entry_linux {
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+};
+
+struct acl_blob {
+ struct acl_entry_linux *entries;
+ size_t count;
+};
+
+struct acl_shape {
+ bool has_mask;
+ bool has_named_user;
+ bool has_named_group;
+ uint16_t user_obj_perm;
+ uint16_t group_obj_perm;
+ uint16_t other_perm;
+ uint16_t mask_perm;
+};
+
+struct output_state {
+ size_t sections_emitted;
+};
+
+static const struct option long_options[] = {
+ { "default", no_argument, NULL, 'd' },
+ { "numeric", no_argument, NULL, 'n' },
+ { "omit-header", no_argument, NULL, 'q' },
+ { "skip-base", no_argument, NULL, 's' },
+ { NULL, 0, NULL, 0 },
+};
+
+static void usage(void) __attribute__((noreturn));
+static int process_path(const char *path, const struct options *opts,
+ struct output_state *state);
+static int process_stdin(const struct options *opts, struct output_state *state);
+static int stat_path(const char *path, bool no_follow, struct stat *st);
+static int load_acl_xattr(const char *path, enum acl_kind kind, bool no_follow,
+ void **buf_out, size_t *size_out);
+static int parse_acl_blob(const void *buf, size_t size, struct acl_blob *acl,
+ const char **error_out);
+static void free_acl_blob(struct acl_blob *acl);
+static int compare_acl_entries(const void *lhs, const void *rhs);
+static int validate_acl_blob(const struct acl_blob *acl, struct acl_shape *shape,
+ const char **error_out);
+static bool acl_is_trivial(const struct acl_shape *shape, mode_t mode);
+static int emit_access_acl(const char *path, const struct stat *st,
+ const struct options *opts, const struct acl_blob *acl,
+ const struct acl_shape *shape, bool have_acl, struct output_state *state);
+static int emit_default_acl(const char *path, const struct stat *st,
+ const struct options *opts, const struct acl_blob *acl, bool have_acl,
+ struct output_state *state);
+static void begin_section_if_needed(struct output_state *state);
+static void print_header(const char *path, const struct stat *st,
+ bool numeric_ids);
+static void print_acl_entries(const struct acl_blob *acl, enum acl_kind kind,
+ bool numeric_ids);
+static void print_synthesized_access_acl(mode_t mode);
+static const char *tag_name(uint16_t tag);
+static void perm_string(uint16_t perm, char out[4]);
+static const char *format_uid(uid_t uid, bool numeric, char *buf, size_t buflen);
+static const char *format_gid(gid_t gid, bool numeric, char *buf, size_t buflen);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: getfacl [-dhinqsv] [file ...]\n");
+ exit(1);
+}
+
+static int
+stat_path(const char *path, bool no_follow, struct stat *st)
+{
+
+ if (no_follow)
+ return (lstat(path, st));
+ return (stat(path, st));
+}
+
+static int
+load_acl_xattr(const char *path, enum acl_kind kind, bool no_follow, void **buf_out,
+ size_t *size_out)
+{
+ const char *name;
+ ssize_t size;
+ void *buf;
+
+ *buf_out = NULL;
+ *size_out = 0;
+ name = (kind == ACL_KIND_DEFAULT) ? ACL_XATTR_DEFAULT : ACL_XATTR_ACCESS;
+
+ for (;;) {
+ if (no_follow)
+ size = lgetxattr(path, name, NULL, 0);
+ else
+ size = getxattr(path, name, NULL, 0);
+ if (size >= 0)
+ break;
+ if (errno == ENODATA || errno == ENOATTR || errno == ENOTSUP ||
+ errno == EOPNOTSUPP)
+ return (XATTR_RESULT_ABSENT);
+ return (XATTR_RESULT_ERROR);
+ }
+
+ if (size == 0)
+ return (XATTR_RESULT_ABSENT);
+
+ buf = malloc((size_t)size);
+ if (buf == NULL)
+ err(1, "malloc");
+
+ for (;;) {
+ ssize_t nread;
+
+ if (no_follow)
+ nread = lgetxattr(path, name, buf, (size_t)size);
+ else
+ nread = getxattr(path, name, buf, (size_t)size);
+ if (nread >= 0) {
+ *buf_out = buf;
+ *size_out = (size_t)nread;
+ return (XATTR_RESULT_PRESENT);
+ }
+ if (errno != ERANGE) {
+ free(buf);
+ if (errno == ENODATA || errno == ENOATTR || errno == ENOTSUP ||
+ errno == EOPNOTSUPP)
+ return (XATTR_RESULT_ABSENT);
+ return (XATTR_RESULT_ERROR);
+ }
+ free(buf);
+ if (no_follow)
+ size = lgetxattr(path, name, NULL, 0);
+ else
+ size = getxattr(path, name, NULL, 0);
+ if (size < 0) {
+ if (errno == ENODATA || errno == ENOATTR || errno == ENOTSUP ||
+ errno == EOPNOTSUPP)
+ return (XATTR_RESULT_ABSENT);
+ return (XATTR_RESULT_ERROR);
+ }
+ buf = malloc((size_t)size);
+ if (buf == NULL)
+ err(1, "malloc");
+ }
+}
+
+static int
+compare_acl_entries(const void *lhs, const void *rhs)
+{
+ const struct acl_entry_linux *a, *b;
+ int order_a, order_b;
+
+ a = lhs;
+ b = rhs;
+
+ switch (a->tag) {
+ case ACL_USER_OBJ:
+ order_a = 0;
+ break;
+ case ACL_USER:
+ order_a = 1;
+ break;
+ case ACL_GROUP_OBJ:
+ order_a = 2;
+ break;
+ case ACL_GROUP:
+ order_a = 3;
+ break;
+ case ACL_MASK:
+ order_a = 4;
+ break;
+ case ACL_OTHER:
+ order_a = 5;
+ break;
+ default:
+ order_a = 6;
+ break;
+ }
+
+ switch (b->tag) {
+ case ACL_USER_OBJ:
+ order_b = 0;
+ break;
+ case ACL_USER:
+ order_b = 1;
+ break;
+ case ACL_GROUP_OBJ:
+ order_b = 2;
+ break;
+ case ACL_GROUP:
+ order_b = 3;
+ break;
+ case ACL_MASK:
+ order_b = 4;
+ break;
+ case ACL_OTHER:
+ order_b = 5;
+ break;
+ default:
+ order_b = 6;
+ break;
+ }
+
+ if (order_a != order_b)
+ return (order_a - order_b);
+ if (a->id < b->id)
+ return (-1);
+ if (a->id > b->id)
+ return (1);
+ return (0);
+}
+
+static int
+parse_acl_blob(const void *buf, size_t size, struct acl_blob *acl,
+ const char **error_out)
+{
+ const struct posix_acl_xattr_header_linux *header;
+ const struct posix_acl_xattr_entry_linux *src;
+ size_t payload_size, count, i;
+
+ memset(acl, 0, sizeof(*acl));
+
+ if (size < sizeof(*header)) {
+ *error_out = "truncated header";
+ return (-1);
+ }
+
+ header = buf;
+ if (le32toh(header->a_version) != POSIX_ACL_XATTR_VERSION) {
+ *error_out = "unsupported ACL xattr version";
+ return (-1);
+ }
+
+ payload_size = size - sizeof(*header);
+ if (payload_size % sizeof(*src) != 0) {
+ *error_out = "truncated ACL entry";
+ return (-1);
+ }
+
+ count = payload_size / sizeof(*src);
+ if (count == 0) {
+ *error_out = "empty ACL";
+ return (-1);
+ }
+
+ acl->entries = calloc(count, sizeof(*acl->entries));
+ if (acl->entries == NULL)
+ err(1, "calloc");
+ acl->count = count;
+
+ src = (const struct posix_acl_xattr_entry_linux *)((const char *)buf +
+ sizeof(*header));
+ for (i = 0; i < count; i++) {
+ acl->entries[i].tag = le16toh(src[i].e_tag);
+ acl->entries[i].perm = le16toh(src[i].e_perm);
+ acl->entries[i].id = le32toh(src[i].e_id);
+ }
+
+ qsort(acl->entries, acl->count, sizeof(*acl->entries), compare_acl_entries);
+ return (0);
+}
+
+static int
+validate_acl_blob(const struct acl_blob *acl, struct acl_shape *shape,
+ const char **error_out)
+{
+ size_t i;
+ bool have_user_obj, have_group_obj, have_other;
+
+ memset(shape, 0, sizeof(*shape));
+ have_user_obj = false;
+ have_group_obj = false;
+ have_other = false;
+
+ for (i = 0; i < acl->count; i++) {
+ const struct acl_entry_linux *entry;
+
+ entry = &acl->entries[i];
+ if ((entry->perm & ~(ACL_READ | ACL_WRITE | ACL_EXECUTE)) != 0) {
+ *error_out = "invalid permission bits";
+ return (-1);
+ }
+
+ switch (entry->tag) {
+ case ACL_USER_OBJ:
+ if (have_user_obj) {
+ *error_out = "duplicate user:: entry";
+ return (-1);
+ }
+ have_user_obj = true;
+ shape->user_obj_perm = entry->perm;
+ break;
+ case ACL_USER:
+ if (entry->id == (uint32_t)ACL_UNDEFINED_ID) {
+ *error_out = "named user entry without id";
+ return (-1);
+ }
+ if (i > 0 && acl->entries[i - 1].tag == ACL_USER &&
+ acl->entries[i - 1].id == entry->id) {
+ *error_out = "duplicate named user entry";
+ return (-1);
+ }
+ shape->has_named_user = true;
+ break;
+ case ACL_GROUP_OBJ:
+ if (have_group_obj) {
+ *error_out = "duplicate group:: entry";
+ return (-1);
+ }
+ have_group_obj = true;
+ shape->group_obj_perm = entry->perm;
+ break;
+ case ACL_GROUP:
+ if (entry->id == (uint32_t)ACL_UNDEFINED_ID) {
+ *error_out = "named group entry without id";
+ return (-1);
+ }
+ if (i > 0 && acl->entries[i - 1].tag == ACL_GROUP &&
+ acl->entries[i - 1].id == entry->id) {
+ *error_out = "duplicate named group entry";
+ return (-1);
+ }
+ shape->has_named_group = true;
+ break;
+ case ACL_MASK:
+ if (shape->has_mask) {
+ *error_out = "duplicate mask:: entry";
+ return (-1);
+ }
+ shape->has_mask = true;
+ shape->mask_perm = entry->perm;
+ break;
+ case ACL_OTHER:
+ if (have_other) {
+ *error_out = "duplicate other:: entry";
+ return (-1);
+ }
+ have_other = true;
+ shape->other_perm = entry->perm;
+ break;
+ default:
+ *error_out = "unknown ACL tag";
+ return (-1);
+ }
+ }
+
+ if (!have_user_obj || !have_group_obj || !have_other) {
+ *error_out = "missing required ACL entry";
+ return (-1);
+ }
+ if ((shape->has_named_user || shape->has_named_group) && !shape->has_mask) {
+ *error_out = "named ACL entries require mask::";
+ return (-1);
+ }
+
+ return (0);
+}
+
+static bool
+acl_is_trivial(const struct acl_shape *shape, mode_t mode)
+{
+ uint16_t user_perm, group_perm, other_perm;
+
+ if (shape->has_named_user || shape->has_named_group)
+ return (false);
+
+ user_perm = (uint16_t)((mode >> 6) & 0x7);
+ group_perm = (uint16_t)((mode >> 3) & 0x7);
+ other_perm = (uint16_t)(mode & 0x7);
+
+ if (shape->user_obj_perm != user_perm ||
+ shape->group_obj_perm != group_perm ||
+ shape->other_perm != other_perm)
+ return (false);
+ if (shape->has_mask && shape->mask_perm != group_perm)
+ return (false);
+ return (true);
+}
+
+static void
+free_acl_blob(struct acl_blob *acl)
+{
+
+ free(acl->entries);
+ acl->entries = NULL;
+ acl->count = 0;
+}
+
+static void
+begin_section_if_needed(struct output_state *state)
+{
+
+ if (state->sections_emitted > 0)
+ printf("\n");
+ state->sections_emitted++;
+}
+
+static const char *
+format_uid(uid_t uid, bool numeric, char *buf, size_t buflen)
+{
+ struct passwd *pw;
+
+ if (!numeric) {
+ pw = getpwuid(uid);
+ if (pw != NULL)
+ return (pw->pw_name);
+ }
+ snprintf(buf, buflen, "%lu", (unsigned long)uid);
+ return (buf);
+}
+
+static const char *
+format_gid(gid_t gid, bool numeric, char *buf, size_t buflen)
+{
+ struct group *gr;
+
+ if (!numeric) {
+ gr = getgrgid(gid);
+ if (gr != NULL)
+ return (gr->gr_name);
+ }
+ snprintf(buf, buflen, "%lu", (unsigned long)gid);
+ return (buf);
+}
+
+static void
+print_header(const char *path, const struct stat *st, bool numeric_ids)
+{
+ char owner_buf[32], group_buf[32];
+
+ printf("# file: %s\n", path);
+ printf("# owner: %s\n", format_uid(st->st_uid, numeric_ids, owner_buf,
+ sizeof(owner_buf)));
+ printf("# group: %s\n", format_gid(st->st_gid, numeric_ids, group_buf,
+ sizeof(group_buf)));
+}
+
+static void
+perm_string(uint16_t perm, char out[4])
+{
+
+ out[0] = (perm & ACL_READ) ? 'r' : '-';
+ out[1] = (perm & ACL_WRITE) ? 'w' : '-';
+ out[2] = (perm & ACL_EXECUTE) ? 'x' : '-';
+ out[3] = '\0';
+}
+
+static const char *
+tag_name(uint16_t tag)
+{
+
+ switch (tag) {
+ case ACL_USER_OBJ:
+ case ACL_USER:
+ return ("user");
+ case ACL_GROUP_OBJ:
+ case ACL_GROUP:
+ return ("group");
+ case ACL_MASK:
+ return ("mask");
+ case ACL_OTHER:
+ return ("other");
+ default:
+ return ("unknown");
+ }
+}
+
+static void
+print_acl_entries(const struct acl_blob *acl, enum acl_kind kind, bool numeric_ids)
+{
+ size_t i;
+
+ for (i = 0; i < acl->count; i++) {
+ const struct acl_entry_linux *entry;
+ char qualifier_buf[32], perms[4];
+ const char *prefix, *qualifier;
+
+ entry = &acl->entries[i];
+ prefix = (kind == ACL_KIND_DEFAULT) ? "default:" : "";
+ perm_string(entry->perm, perms);
+
+ switch (entry->tag) {
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ printf("%s%s::%s\n", prefix, tag_name(entry->tag), perms);
+ break;
+ case ACL_USER:
+ qualifier = format_uid((uid_t)entry->id, numeric_ids,
+ qualifier_buf, sizeof(qualifier_buf));
+ printf("%suser:%s:%s\n", prefix, qualifier, perms);
+ break;
+ case ACL_GROUP:
+ qualifier = format_gid((gid_t)entry->id, numeric_ids,
+ qualifier_buf, sizeof(qualifier_buf));
+ printf("%sgroup:%s:%s\n", prefix, qualifier, perms);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+print_synthesized_access_acl(mode_t mode)
+{
+ char perms[4];
+
+ perm_string((uint16_t)((mode >> 6) & 0x7), perms);
+ printf("user::%s\n", perms);
+
+ perm_string((uint16_t)((mode >> 3) & 0x7), perms);
+ printf("group::%s\n", perms);
+
+ perm_string((uint16_t)(mode & 0x7), perms);
+ printf("other::%s\n", perms);
+}
+
+static int
+emit_access_acl(const char *path, const struct stat *st, const struct options *opts,
+ const struct acl_blob *acl, const struct acl_shape *shape, bool have_acl,
+ struct output_state *state)
+{
+ bool trivial;
+
+ trivial = !have_acl || acl_is_trivial(shape, st->st_mode);
+ if (opts->skip_base && trivial)
+ return (0);
+
+ begin_section_if_needed(state);
+ if (!opts->omit_header)
+ print_header(path, st, opts->numeric_ids);
+ if (have_acl)
+ print_acl_entries(acl, ACL_KIND_ACCESS, opts->numeric_ids);
+ else
+ print_synthesized_access_acl(st->st_mode);
+ return (0);
+}
+
+static int
+emit_default_acl(const char *path, const struct stat *st, const struct options *opts,
+ const struct acl_blob *acl, bool have_acl, struct output_state *state)
+{
+ bool have_output;
+
+ if (!have_acl && opts->skip_base)
+ return (0);
+
+ have_output = !opts->omit_header || acl->count > 0;
+ if (!have_output)
+ return (0);
+
+ begin_section_if_needed(state);
+ if (!opts->omit_header)
+ print_header(path, st, opts->numeric_ids);
+ if (acl->count > 0)
+ print_acl_entries(acl, ACL_KIND_DEFAULT, opts->numeric_ids);
+ return (0);
+}
+
+static int
+process_path(const char *path, const struct options *opts, struct output_state *state)
+{
+ struct stat st;
+ struct acl_blob acl;
+ struct acl_shape shape;
+ const char *parse_error;
+ void *raw_acl;
+ size_t raw_size;
+ int xattr_state;
+
+ memset(&acl, 0, sizeof(acl));
+ raw_acl = NULL;
+ raw_size = 0;
+
+ if (stat_path(path, opts->no_follow, &st) == -1) {
+ warn("%s", path);
+ return (1);
+ }
+
+ if (opts->no_follow && S_ISLNK(st.st_mode)) {
+ warnx("%s: symbolic link ACLs are not supported on Linux", path);
+ return (1);
+ }
+
+ xattr_state = load_acl_xattr(path,
+ opts->default_acl ? ACL_KIND_DEFAULT : ACL_KIND_ACCESS,
+ opts->no_follow, &raw_acl, &raw_size);
+ if (xattr_state == XATTR_RESULT_ERROR) {
+ warn("%s", path);
+ return (1);
+ }
+
+ if (xattr_state == XATTR_RESULT_PRESENT) {
+ if (parse_acl_blob(raw_acl, raw_size, &acl, &parse_error) != 0) {
+ warnx("%s: invalid POSIX ACL xattr: %s", path, parse_error);
+ free(raw_acl);
+ return (1);
+ }
+ if (validate_acl_blob(&acl, &shape, &parse_error) != 0) {
+ warnx("%s: invalid POSIX ACL xattr: %s", path, parse_error);
+ free(raw_acl);
+ free_acl_blob(&acl);
+ return (1);
+ }
+ }
+ free(raw_acl);
+
+ if (opts->default_acl)
+ xattr_state = emit_default_acl(path, &st, opts, &acl,
+ xattr_state == XATTR_RESULT_PRESENT, state);
+ else
+ xattr_state = emit_access_acl(path, &st, opts, &acl, &shape,
+ xattr_state == XATTR_RESULT_PRESENT, state);
+
+ free_acl_blob(&acl);
+ return (xattr_state);
+}
+
+static int
+process_stdin(const struct options *opts, struct output_state *state)
+{
+ char *line;
+ size_t cap;
+ ssize_t len;
+ int status;
+
+ line = NULL;
+ cap = 0;
+ status = 0;
+
+ while ((len = getline(&line, &cap, stdin)) != -1) {
+ if (len > 0 && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ if (line[0] == '\0') {
+ warnx("stdin: empty pathname");
+ status = 1;
+ continue;
+ }
+ if (process_path(line, opts, state) != 0)
+ status = 1;
+ }
+
+ if (ferror(stdin)) {
+ warn("stdin");
+ status = 1;
+ }
+
+ free(line);
+ return (status);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct options opts;
+ struct output_state state;
+ int ch, i, status;
+
+ memset(&opts, 0, sizeof(opts));
+ memset(&state, 0, sizeof(state));
+ opterr = 0;
+
+ while ((ch = getopt_long(argc, argv, "+dhinqsv", long_options, NULL)) != -1) {
+ switch (ch) {
+ case 'd':
+ opts.default_acl = true;
+ break;
+ case 'h':
+ opts.no_follow = true;
+ break;
+ case 'i':
+ errx(1, "option -i is not supported on Linux");
+ case 'n':
+ opts.numeric_ids = true;
+ break;
+ case 'q':
+ opts.omit_header = true;
+ break;
+ case 's':
+ opts.skip_base = true;
+ break;
+ case 'v':
+ errx(1, "option -v is not supported on Linux");
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ return (process_stdin(&opts, &state));
+
+ status = 0;
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "-") == 0) {
+ if (process_stdin(&opts, &state) != 0)
+ status = 1;
+ continue;
+ }
+ if (process_path(argv[i], &opts, &state) != 0)
+ status = 1;
+ }
+
+ return (status);
+}