Changeset View
Standalone View
bin/ls/print.c
Show All 37 Lines | |||||
#endif /* not lint */ | #endif /* not lint */ | ||||
#endif | #endif | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <sys/acl.h> | #include <sys/acl.h> | ||||
#include <sys/extattr.h> | |||||
#include <err.h> | #include <err.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <fts.h> | #include <fts.h> | ||||
#include <langinfo.h> | #include <langinfo.h> | ||||
#include <libutil.h> | #include <libutil.h> | ||||
#include <limits.h> | #include <limits.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
Show All 19 Lines | |||||
static int printtype(u_int); | static int printtype(u_int); | ||||
static void printsize(size_t, off_t); | static void printsize(size_t, off_t); | ||||
#ifdef COLORLS | #ifdef COLORLS | ||||
static void endcolor_termcap(int); | static void endcolor_termcap(int); | ||||
static void endcolor_ansi(void); | static void endcolor_ansi(void); | ||||
static void endcolor(int); | static void endcolor(int); | ||||
static int colortype(mode_t); | static int colortype(mode_t); | ||||
#endif | #endif | ||||
static void aclmode(char *, const FTSENT *); | static int check_acl(const FTSENT *); | ||||
static int check_extended_attr(const FTSENT *); | |||||
static void print_extattrs(const FTSENT *, int); | |||||
#define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT) | #define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT) | ||||
#ifdef COLORLS | #ifdef COLORLS | ||||
/* Most of these are taken from <sys/stat.h> */ | /* Most of these are taken from <sys/stat.h> */ | ||||
typedef enum Colors { | typedef enum Colors { | ||||
C_DIR, /* directory */ | C_DIR, /* directory */ | ||||
C_LNK, /* symbolic link */ | C_LNK, /* symbolic link */ | ||||
▲ Show 20 Lines • Show All 116 Lines • ▼ Show 20 Lines | |||||
void | void | ||||
printlong(const DISPLAY *dp) | printlong(const DISPLAY *dp) | ||||
{ | { | ||||
struct stat *sp; | struct stat *sp; | ||||
FTSENT *p; | FTSENT *p; | ||||
NAMES *np; | NAMES *np; | ||||
char buf[20]; | char buf[20]; | ||||
int acl, extattr; | |||||
#ifdef COLORLS | #ifdef COLORLS | ||||
int color_printed = 0; | int color_printed = 0; | ||||
#endif | #endif | ||||
if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) && | if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) && | ||||
(f_longform || f_size)) { | (f_longform || f_size)) { | ||||
(void)printf("total %lu\n", howmany(dp->btotal, blocksize)); | (void)printf("total %lu\n", howmany(dp->btotal, blocksize)); | ||||
} | } | ||||
for (p = dp->list; p; p = p->fts_link) { | for (p = dp->list; p; p = p->fts_link) { | ||||
if (IS_NOPRINT(p)) | if (IS_NOPRINT(p)) | ||||
continue; | continue; | ||||
sp = p->fts_statp; | sp = p->fts_statp; | ||||
if (f_inode) | if (f_inode) | ||||
(void)printf("%*ju ", | (void)printf("%*ju ", | ||||
dp->s_inode, (uintmax_t)sp->st_ino); | dp->s_inode, (uintmax_t)sp->st_ino); | ||||
if (f_size) | if (f_size) | ||||
(void)printf("%*jd ", | (void)printf("%*jd ", | ||||
dp->s_block, howmany(sp->st_blocks, blocksize)); | dp->s_block, howmany(sp->st_blocks, blocksize)); | ||||
strmode(sp->st_mode, buf); | strmode(sp->st_mode, buf); | ||||
aclmode(buf, p); | extattr = check_extended_attr(p); | ||||
acl = check_acl(p); | |||||
if (extattr) | |||||
buf[10] = '@'; | |||||
else if (acl) | |||||
buf[10] = '+'; | |||||
jilles: POSIX.1-2016 XCU 4 Utilities ls calls this eleventh character the "optional alternate access… | |||||
sefAuthorUnsubmitted Done Inline ActionsI'm open to other suggestions. I know, from macOS, that I *like* being able to tell which entries have an ACL or extattr. I could just drop that part, and require -@, but that defeats some of the purpose in just doing a quick look. sef: I'm open to other suggestions. I know, from macOS, that I *like* being able to tell which… | |||||
sefAuthorUnsubmitted Done Inline ActionsSo... POSIX just says that if there is an alternate access control method, use a character other than " ". Extended attributes use an alternate access method -- different system calls, and "user" vs "system" namespaces. Since POSIX does not require a "+" there, only a character other than " ", I'm not sure how wrong it is. I would like to *ask* someone, but my POSIX contacts are all long moved on to other pastures; is there a way to do so? sef: So... POSIX just says that if there is an alternate access control method, use a character… | |||||
jillesUnsubmitted Not Done Inline ActionsXBD 4.5 File Access Permissions discusses access control, and is quite clear that it is about operations on a file being permitted or denied. That extended attributes use different functions for access is not relevant. Unfortunately, the ls page uses the terms alternate and additional access control method, while the rest of the standard uses the terms alternate and additional access control mechanism. You can ask questions on the Austin group mailing list: https://www.opengroup.org/austin/lists.html jilles: XBD 4.5 File Access Permissions discusses access control, and is quite clear that it is about… | |||||
jillesUnsubmitted Not Done Inline ActionsThere is no problem for any formal POSIX compliance (one simply ensures that the filesystem for the compliance test does not have any extended attributes) but I don't really like the idea of using the eleventh character for just any moderately unusual property of a file. jilles: There is no problem for any formal POSIX compliance (one simply ensures that the filesystem for… | |||||
sefAuthorUnsubmitted Done Inline ActionsWell, ironically, I was thinking about adding in a check for the st_flags field, and if any of the access-protecting ones were set, to use yet another character, inspired by your earlier comment. I was thinking "*". Or '%', and use '*' to indicate more than one. In terms of modifying the output of "ls -l", using that field does seem like the safest one, since the value isn't defined by anything, and there's a large base (macOS) that already uses it, we can't add a (default) extra field, and it doesn't really look like it would go with any of the existing fields. sef: Well, ironically, I was thinking about adding in a check for the st_flags field, and if any of… | |||||
sefAuthorUnsubmitted Done Inline ActionsOk, so if the issue isn't POSIX, then what is the issue? As I said, I'm open to other suggestions, and I don't know of any place that would be particularly *better*. Can't add a field, and placing a marker on any of the other fields is likely to break more things. Maybe having both (e.g., "-rw-r--r--+@") works, but I come back to the fact that there is an existing system which uses the scheme I proposed here. And, to the fact that I find this information useful when listing files. Maybe nobody else does? sef: Ok, so if the issue isn't POSIX, then what is the issue?
As I said, I'm open to other… | |||||
np = p->fts_pointer; | np = p->fts_pointer; | ||||
(void)printf("%s %*ju %-*s %-*s ", buf, dp->s_nlink, | (void)printf("%s %*ju %-*s %-*s ", buf, dp->s_nlink, | ||||
(uintmax_t)sp->st_nlink, dp->s_user, np->user, dp->s_group, | (uintmax_t)sp->st_nlink, dp->s_user, np->user, dp->s_group, | ||||
np->group); | np->group); | ||||
if (f_flags) | if (f_flags) | ||||
(void)printf("%-*s ", dp->s_flags, np->flags); | (void)printf("%-*s ", dp->s_flags, np->flags); | ||||
if (f_label) | if (f_label) | ||||
(void)printf("%-*s ", dp->s_label, np->label); | (void)printf("%-*s ", dp->s_label, np->label); | ||||
Show All 18 Lines | #ifdef COLORLS | ||||
if (f_color && color_printed) | if (f_color && color_printed) | ||||
endcolor(0); | endcolor(0); | ||||
#endif | #endif | ||||
if (f_type) | if (f_type) | ||||
(void)printtype(sp->st_mode); | (void)printtype(sp->st_mode); | ||||
if (S_ISLNK(sp->st_mode)) | if (S_ISLNK(sp->st_mode)) | ||||
printlink(p); | printlink(p); | ||||
(void)putchar('\n'); | (void)putchar('\n'); | ||||
if (f_printea && extattr) { | |||||
print_extattrs(p, EXTATTR_NAMESPACE_USER); | |||||
print_extattrs(p, EXTATTR_NAMESPACE_SYSTEM); | |||||
} | } | ||||
} | } | ||||
} | |||||
void | void | ||||
printstream(const DISPLAY *dp) | printstream(const DISPLAY *dp) | ||||
{ | { | ||||
FTSENT *p; | FTSENT *p; | ||||
int chcnt; | int chcnt; | ||||
for (p = dp->list, chcnt = 0; p; p = p->fts_link) { | for (p = dp->list, chcnt = 0; p; p = p->fts_link) { | ||||
▲ Show 20 Lines • Show All 472 Lines • ▼ Show 20 Lines | if (f_humanval) { | ||||
/* This format assignment needed to work round gcc bug. */ | /* This format assignment needed to work round gcc bug. */ | ||||
const char *format = "%*j'd "; | const char *format = "%*j'd "; | ||||
(void)printf(format, (u_int)width, bytes); | (void)printf(format, (u_int)width, bytes); | ||||
} else | } else | ||||
(void)printf("%*jd ", (u_int)width, bytes); | (void)printf("%*jd ", (u_int)width, bytes); | ||||
} | } | ||||
/* | /* | ||||
* Add a + after the standard rwxrwxrwx mode if the file has an | * Add a + after the standard rwxrwxrwx mode if the file has an | ||||
* ACL. strmode() reserves space at the end of the string. | * ACL. strmode() reserves space at the end of the string. | ||||
jillesUnsubmitted Not Done Inline ActionsIf you want to keep the change that makes the extra character not POSIX-compliant, then this comment is out of date. jilles: If you want to keep the change that makes the extra character not POSIX-compliant, then this… | |||||
*/ | */ | ||||
static void | static int | ||||
aclmode(char *buf, const FTSENT *p) | check_acl(const FTSENT *p) | ||||
{ | { | ||||
char name[MAXPATHLEN + 1]; | char name[MAXPATHLEN + 1]; | ||||
int ret, trivial; | int ret, trivial; | ||||
static dev_t previous_dev = NODEV; | static dev_t previous_dev = NODEV; | ||||
static int supports_acls = -1; | static int supports_acls = -1; | ||||
static int type = ACL_TYPE_ACCESS; | static int type = ACL_TYPE_ACCESS; | ||||
acl_t facl; | acl_t facl; | ||||
/* | /* | ||||
* XXX: ACLs are not supported on whiteouts and device files | * XXX: ACLs are not supported on whiteouts and device files | ||||
* residing on UFS. | * residing on UFS. | ||||
*/ | */ | ||||
if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) || | if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) || | ||||
S_ISWHT(p->fts_statp->st_mode)) | S_ISWHT(p->fts_statp->st_mode)) | ||||
return; | return (0); | ||||
if (previous_dev == p->fts_statp->st_dev && supports_acls == 0) | if (previous_dev == p->fts_statp->st_dev && supports_acls == 0) | ||||
return; | return (0); | ||||
if (p->fts_level == FTS_ROOTLEVEL) | if (p->fts_level == FTS_ROOTLEVEL) | ||||
snprintf(name, sizeof(name), "%s", p->fts_name); | snprintf(name, sizeof(name), "%s", p->fts_name); | ||||
else | else | ||||
snprintf(name, sizeof(name), "%s/%s", | snprintf(name, sizeof(name), "%s/%s", | ||||
p->fts_parent->fts_accpath, p->fts_name); | p->fts_parent->fts_accpath, p->fts_name); | ||||
if (previous_dev != p->fts_statp->st_dev) { | if (previous_dev != p->fts_statp->st_dev) { | ||||
previous_dev = p->fts_statp->st_dev; | previous_dev = p->fts_statp->st_dev; | ||||
supports_acls = 0; | supports_acls = 0; | ||||
ret = lpathconf(name, _PC_ACL_NFS4); | ret = lpathconf(name, _PC_ACL_NFS4); | ||||
if (ret > 0) { | if (ret > 0) { | ||||
type = ACL_TYPE_NFS4; | type = ACL_TYPE_NFS4; | ||||
supports_acls = 1; | supports_acls = 1; | ||||
} else if (ret < 0 && errno != EINVAL) { | } else if (ret < 0 && errno != EINVAL) { | ||||
warn("%s", name); | warn("%s", name); | ||||
return; | return (0); | ||||
} | } | ||||
if (supports_acls == 0) { | if (supports_acls == 0) { | ||||
ret = lpathconf(name, _PC_ACL_EXTENDED); | ret = lpathconf(name, _PC_ACL_EXTENDED); | ||||
if (ret > 0) { | if (ret > 0) { | ||||
type = ACL_TYPE_ACCESS; | type = ACL_TYPE_ACCESS; | ||||
supports_acls = 1; | supports_acls = 1; | ||||
} else if (ret < 0 && errno != EINVAL) { | } else if (ret < 0 && errno != EINVAL) { | ||||
warn("%s", name); | warn("%s", name); | ||||
return; | return (0); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (supports_acls == 0) | if (supports_acls == 0) | ||||
return; | return (0); | ||||
facl = acl_get_link_np(name, type); | facl = acl_get_link_np(name, type); | ||||
if (facl == NULL) { | if (facl == NULL) { | ||||
warn("%s", name); | warn("%s", name); | ||||
return; | return (0); | ||||
} | } | ||||
if (acl_is_trivial_np(facl, &trivial)) { | if (acl_is_trivial_np(facl, &trivial)) { | ||||
acl_free(facl); | acl_free(facl); | ||||
warn("%s", name); | warn("%s", name); | ||||
return; | return (0); | ||||
} | } | ||||
if (!trivial) | |||||
buf[10] = '+'; | |||||
acl_free(facl); | acl_free(facl); | ||||
if (!trivial) | |||||
return (1); | |||||
return (0); | |||||
} | |||||
/* | |||||
* Print out extended atttributes and their values. It prints | |||||
* them in the format "\t<namespace> <eaname>\t<easize>\n". | |||||
* It prints nothing on error. | |||||
*/ | |||||
static void | |||||
print_extattrs(const FTSENT *p, int namespace) | |||||
{ | |||||
char name[MAXPATHLEN + 1]; | |||||
char *ns_name; | |||||
struct { unsigned char namelen; char name[]; } *ea_entry; | |||||
ssize_t ea_total; | |||||
void *ea_list = NULL; | |||||
void *ea_list_end; | |||||
ssize_t (*lsfunc)(const char *, int, void *, size_t); | |||||
ssize_t (*gfunc)(const char *, int, const char *, void *, size_t); | |||||
lsfunc = f_nofollow ? extattr_list_link : extattr_list_file; | |||||
gfunc = f_nofollow ? extattr_get_link : extattr_get_file; | |||||
if (extattr_namespace_to_string(namespace, &ns_name) == -1) | |||||
return; | |||||
if (p->fts_level == FTS_ROOTLEVEL) | |||||
snprintf(name, sizeof(name), "%s", p->fts_name); | |||||
else | |||||
snprintf(name, sizeof(name), "%s/%s", | |||||
p->fts_parent->fts_accpath, p->fts_name); | |||||
ea_total = (*lsfunc)(name, namespace, NULL, 0); | |||||
if (ea_total <= 0) | |||||
return; | |||||
ea_list = malloc(ea_total); | |||||
if (ea_list == NULL) | |||||
return; | |||||
ea_total = (*lsfunc)(name, namespace, ea_list, ea_total); | |||||
jillesUnsubmitted Not Done Inline ActionsIs it possible that we get a truncated list here if an extended attribute was added concurrently? jilles: Is it possible that we get a truncated list here if an extended attribute was added… | |||||
sefAuthorUnsubmitted Done Inline ActionsYes. There is no atomic operation to get them all, and no iteration function, unfortunately. sef: Yes. There is no atomic operation to get them all, and no iteration function, unfortunately. | |||||
jillesUnsubmitted Not Done Inline ActionsSuch a situation may cause the below loop to read beyond the end of the buffer. This might then cause a crash, or worse. jilles: Such a situation may cause the below loop to read beyond the end of the buffer. This might then… | |||||
if (ea_total <= 0) { | |||||
free(ea_list); | |||||
return; | |||||
} | |||||
ea_list_end = (uint8_t*)ea_list + ea_total; | |||||
for (ea_entry = ea_list; | |||||
(void*)ea_entry < ea_list_end; | |||||
ea_entry = (void*)((uint8_t*)ea_entry + ea_entry->namelen + 1)) { | |||||
char ea_name[ea_entry->namelen+1]; | |||||
jillesUnsubmitted Not Done Inline ActionsPerhaps it is better to allocate a fixed char ea_name[256] instead of a variable length array. Alternatively, allocate one byte more for ea_list and temporarily overwrite the byte after the name with '\0'. jilles: Perhaps it is better to allocate a fixed `char ea_name[256]` instead of a variable length array. | |||||
sefAuthorUnsubmitted Done Inline ActionsChanging it to a constant 256 byte buffer is ok, but I figure, we have dynamic allocation, so why not use it? sef: Changing it to a constant 256 byte buffer is ok, but I figure, we have dynamic allocation, so… | |||||
ssize_t ea_length; | |||||
memcpy(ea_name, ea_entry->name, ea_entry->namelen); | |||||
ea_name[ea_entry->namelen] = 0; | |||||
ea_length = (*gfunc)(name, namespace, ea_name, NULL, 0); | |||||
if (ea_length == -1) | |||||
continue; | |||||
printf("\t%s %s\t%zu\n", ns_name, ea_name, ea_length); | |||||
} | |||||
free(ea_list); | |||||
return; | |||||
} | |||||
/* | |||||
* Determine if the file has extended attributes. This only looks at | |||||
* EXTATTR_NAMESPACE_USER and EXTATTR_NAMESPACE_SYSTEM; if permission | |||||
* is not allowed for either, then it behaves as if there are none. | |||||
*/ | |||||
static int | |||||
check_extended_attr(const FTSENT *p) | |||||
{ | |||||
char name[MAXPATHLEN + 1]; | |||||
ssize_t ea_sizes; | |||||
ssize_t (*func)(const char *, int, void *, size_t); | |||||
func = f_nofollow ? extattr_list_link : extattr_list_file; | |||||
if (p->fts_level == FTS_ROOTLEVEL) | |||||
snprintf(name, sizeof(name), "%s", p->fts_name); | |||||
else | |||||
snprintf(name, sizeof(name), "%s/%s", | |||||
p->fts_parent->fts_accpath, p->fts_name); | |||||
ea_sizes = (*func)(name, EXTATTR_NAMESPACE_USER, NULL, 0); | |||||
if (ea_sizes > 0) | |||||
return (1); | |||||
ea_sizes = (*func)(name, EXTATTR_NAMESPACE_SYSTEM, NULL, 0); | |||||
return (ea_sizes > 0); | |||||
} | } |
POSIX.1-2016 XCU 4 Utilities ls calls this eleventh character the "optional alternate access method flag" and says that shall it be the empty string if there is no alternate or
additional access control method associated with the file; otherwise, it shall be a string
containing a single printable character that is not a <blank>. So unless the extended attribute affects access control it should not be mentioned here.
(On another note, some of the file flags (from st_flags) are additional access control mechanisms, so they should be reflected here; however, I don't think that should be done in this review.)