diff options
Diffstat (limited to 'ports/darwin/pseudo_wrappers.c')
-rw-r--r-- | ports/darwin/pseudo_wrappers.c | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/ports/darwin/pseudo_wrappers.c b/ports/darwin/pseudo_wrappers.c new file mode 100644 index 0000000..e33533e --- /dev/null +++ b/ports/darwin/pseudo_wrappers.c @@ -0,0 +1,606 @@ +/* + * pseudo_wrappers.c, darwin pseudo wrappers + * + * Copyright (c) 2008-2011 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * 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 Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* we need XATTR_NOFOLLOW in scope */ +#include <sys/xattr.h> + +/* shared functionality for the xattr code */ +/* Each of these functions is expecting to get an optional name, and + * a populated statbuf to use for sending messages to the server. + */ + +/* to avoid namespace pollution and such, we now duplicate the + * basic functionality of a POSIX ACL list, as used by libacl or + * the kernel. Documentation was obtained from the headers of libacl + * and from a page or two of _The Linux Programming Interface_, by + * Michael Kerrisk. + */ + +typedef struct { + uint16_t tag; + uint16_t perm; + uint32_t id; +} acl_entry; + +typedef struct { + uint32_t version; + acl_entry entries[]; +} acl_header; + +enum acl_tags { + ACL_UNDEFINED = 0x0, + ACL_USER_OBJ = 0x1, + ACL_USER = 0x2, + ACL_GROUP_OBJ = 0x4, + ACL_GROUP = 0x8, + ACL_MASK = 0x10, + ACL_OTHER = 0x20, +}; + +static const int endian_test = 1; +static const char *endian_tester = (char *) &endian_test; + +static inline int +le16(int x16) { + if (*endian_tester) { + return x16; + } else { + return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8); + } +} + +static inline int +le32(int x32) { + if (*endian_tester) { + return x32; + } else { + return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) | + ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24); + } +} + +/* set mode to match the contents of header. Return non-zero on error. + * On a zero return, mode is a valid posix mode, and *extra is set to + * 1 if any of the entries are not reflected by that mode. On a non-zero + * return, no promises are made about *extra or *mode. + */ +static int +posix_permissions(const acl_header *header, int entries, int *extra, int *mode) { + int acl_seen = 0; + if (le32(header->version) != 2) { + pseudo_diag("Fatal: ACL support no available for header version %d.\n", + le32(header->version)); + return 1; + } + *mode = 0; + *extra = 0; + for (int i = 0; i < entries; ++i) { + const acl_entry *e = &header->entries[i]; + int tag = le16(e->tag); + int perm = le16(e->perm); + acl_seen |= tag; + switch (tag) { + case ACL_USER_OBJ: + *mode = *mode | (perm << 6); + break; + case ACL_GROUP_OBJ: + *mode = *mode | (perm << 3); + break; + case ACL_OTHER: + *mode = *mode | perm; + break; + case ACL_USER: + case ACL_GROUP: + case ACL_MASK: + *extra = *extra + 1; + break; + default: + pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n", + tag); + return 1; + } + } + return 0; +} + +#define RC_AND_BUF \ + int rc; \ + PSEUDO_STATBUF buf; \ + if (path) { \ + rc = base_lstat(path, &buf); \ + } else { \ + rc = base_fstat(fd, &buf); \ + } \ + if (rc == -1) { \ + return rc; \ + } + +/* Note: no profiling implementation yet. */ +static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size, u_int32_t position, int options) { + RC_AND_BUF + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fgetxattr(fd, name, value, size, position, options); + } else { + return real_getxattr(path, name, value, size, position, options); + } + } + + pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n", + path ? path : "<no path>", fd, name); + pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name); + if (result->result != RESULT_SUCCEED) { + errno = ENOATTR; + return -1; + } + + if (value) { + pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n", + result->path, result->pathlen); + if (size >= result->pathlen) { + memcpy(value, result->path, result->pathlen); + } else { + memcpy(value, result->path, size); + errno = ERANGE; + } + } + return result->pathlen; +} + +static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, u_int32_t position, int options) { + RC_AND_BUF + pseudo_op_t op; + + pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n", + path ? path : "<no path>", fd, name, (int) size, (char *) value); + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fsetxattr(fd, name, value, size, position, options); + } else { + return real_setxattr(path, name, value, size, position, options); + } + } + + /* this may be a plain chmod */ + if (!strcmp(name, "system.posix_acl_access")) { + int extra; + int mode; + int entries = (size - sizeof(acl_header)) / sizeof(acl_entry); + if (!posix_permissions(value, entries, &extra, &mode)) { + pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n", + mode, extra); + buf.st_mode = mode; + /* we want to actually issue a corresponding chmod, + * as well, or else the file ends up 0600 on the + * host. Using the slightly-less-efficient wrap_chmod + * avoids possible misalignment. + */ + if (path) { + wrap_chmod(path, mode); + } else { + wrap_fchmod(fd, mode); + } + /* we are sneaky, and do not actually record this using + * extended attributes. */ + if (!extra) { + return 0; + } + } + } + + if (options & XATTR_CREATE) { + op = OP_CREATE_XATTR; + } else if (options & XATTR_REPLACE) { + op = OP_REPLACE_XATTR; + } else { + op = OP_SET_XATTR; + } + + pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size); + + /* we automatically assume success */ + if (op == OP_SET_XATTR) { + return 0; + } + + /* CREATE/REPLACE operations can report failure */ + if (!result || result->result == RESULT_FAIL) { + return -1; + } + + return 0; +} + +static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size, int options) { + RC_AND_BUF + char extra_name_buf[4096]; + ssize_t real_attr_len; + ssize_t used = 0; + if (fd != -1) { + real_attr_len = real_flistxattr(fd, extra_name_buf, sizeof(extra_name_buf), options); + } else { + real_attr_len = real_listxattr(path, extra_name_buf, sizeof(extra_name_buf), options); + } + pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of FS xattr names, starting '%.*s'\n", + (int) real_attr_len, (int) real_attr_len, extra_name_buf); + + /* we don't care why there aren't any */ + if (real_attr_len < 1) { + real_attr_len = 0; + } + + pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf); + + if (result->result != RESULT_SUCCEED && real_attr_len < 1) { + pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n"); + errno = ENOATTR; + return -1; + } + + if (list) { + pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n", + (int) result->pathlen, (int) result->pathlen, result->path); + if (size >= result->pathlen) { + memcpy(list, result->path, result->pathlen); + used = result->pathlen; + } else { + memcpy(list, result->path, size); + used = size; + errno = ERANGE; + } + if (real_attr_len > 0) { + if ((ssize_t) size >= used + real_attr_len) { + memcpy(list + used, extra_name_buf, real_attr_len); + used += real_attr_len; + } else { + memcpy(list + used, extra_name_buf, size - used); + used = size; + errno = ERANGE; + } + + } + } else { + used = real_attr_len + result->pathlen; + } + return used; +} + +static int shared_removexattr(const char *path, int fd, const char *name, int options) { + RC_AND_BUF + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fremovexattr(fd, name, options); + } else { + return real_removexattr(path, name, options); + } + } + + pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name); + + if (result->result != RESULT_SUCCEED) { + /* docs say ENOATTR, but I don't have one */ + errno = ENOENT; + return -1; + } + return 0; +} + + +/* there's no fgetgrent_r or fgetpwent_r in Darwin */ + +#define PLENTY_LONG 2048 +/* the original uid/gid code for Linux was written in terms of the + * fget*ent_r() functions... which Darwin doesn't have. But wait! They're + * actually pretty easy to implement. + */ +int +pseudo_fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + char linebuf[PLENTY_LONG] = { 0 }; + char *s, *t, *u; + size_t max_members; + char **members; + size_t member = 0; + long started_at = -1; + gid_t gid; + int error = ENOENT; + size_t len; + + /* any early exit should set *gbufp to NULL */ + if (gbufp) + *gbufp = NULL; + + if (!gbuf || !fp || !buf) + goto error_out; + + if (fp == pseudo_host_etc_group_file) { + struct group *g; + pseudo_antimagic(); + g = getgrent(); + pseudo_magic(); + if (g) { + char *s = linebuf; + s += snprintf(linebuf, PLENTY_LONG, + "%s:%s:%ld:", + g->gr_name, + g->gr_passwd, + (long) g->gr_gid); + if (g->gr_mem) { + int i; + for (i = 0; g->gr_mem[i]; ++i) { + s += snprintf(s, + PLENTY_LONG - (s - linebuf), + "%s,", + g->gr_mem[i]); + } + if (s[-1] == ',') + --s; + } + strcpy(s, "\n"); + } else { + goto error_out; + } + } else { + started_at = ftell(fp); + if (started_at == -1) { + goto error_out; + } + s = fgets(linebuf, PLENTY_LONG, fp); + if (!s) { + goto error_out; + } + } + /* fgets will have stored a '\0' if there was no error; if there + * was an error, though, linebuf was initialized to all zeroes so + * the string is null-terminated anyway... + */ + len = strlen(linebuf); + if (len > buflen) { + error = ERANGE; + goto error_out; + } + memcpy(buf, linebuf, len); + /* round up to 8, hope for the best? */ + len = len + 8 + (((unsigned long long) (buf + len)) % 8); + members = (char **) (buf + len); + if (len >= buflen) { + error = ERANGE; + goto error_out; + } + /* this is how many pointers we have room for... */ + max_members = (buflen - len) / sizeof(*members); + + t = buf; + /* yes, I can assume that Darwin has strsep() */ + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gbuf->gr_name = s; + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gbuf->gr_passwd = s; + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gid = (gid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + gbuf->gr_gid = gid; + + /* now, s points to a comma-separated list of members, which we + * want to stash pointers to in 'members'. + */ + s = strsep(&t, ":"); + t = s; + while ((s = strsep(&t, ",")) != NULL) { + if (*s) { + if (member + 1 > max_members) { + errno = ERANGE; + goto error_out; + } + members[member++] = s; + } + } + if (member + 1 > max_members) { + errno = ERANGE; + goto error_out; + } + members[member++] = NULL; + *gbufp = gbuf; + return 0; + +error_out: + if (started_at != -1) + fseek(fp, started_at, SEEK_SET); + return error; + return -1; +} + +int +pseudo_fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp) { + char linebuf[PLENTY_LONG] = { 0 }; + char *s, *t, *u; + long started_at = -1; + __darwin_time_t timestamp; + uid_t uid; + gid_t gid; + int error = ENOENT; + size_t len; + + /* any early exit should set *gbufp to NULL */ + if (pbufp) + *pbufp = NULL; + + if (!pbuf || !fp || !buf) + goto error_out; + + if (fp == pseudo_host_etc_passwd_file) { + struct passwd *p; + + pseudo_antimagic(); + p = getpwent(); + pseudo_magic(); + if (p) { + snprintf(linebuf, PLENTY_LONG, + "%s:%s:%ld:%ld:%s:%ld:%ld:%s:%s:%s\n", + p->pw_name, + p->pw_passwd, + (long) p->pw_uid, + (long) p->pw_gid, + p->pw_class, + (long) p->pw_change, + (long) p->pw_expire, + p->pw_gecos, + p->pw_dir, + p->pw_shell); + } else { + goto error_out; + } + } else { + started_at = ftell(fp); + if (started_at == -1) { + goto error_out; + } + s = fgets(linebuf, PLENTY_LONG, fp); + if (!s) { + goto error_out; + } + } + /* fgets will have stored a '\0' if there was no error; if there + * was an error, though, linebuf was initialized to all zeroes so + * the string is null-terminated anyway... + */ + len = strlen(linebuf); + if (len > buflen) { + error = ERANGE; + goto error_out; + } + if (linebuf[len - 1] == '\n') { + linebuf[len - 1] = '\0'; + --len; + } + memcpy(buf, linebuf, len); + + t = buf; + /* yes, I can assume that Darwin has strsep() */ + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + pbuf->pw_name = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_passwd = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + uid = (uid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_uid = uid; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + gid = (gid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_gid = gid; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_class = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + timestamp = (__darwin_time_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_change = timestamp; + + timestamp = (__darwin_time_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_expire = timestamp; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_gecos = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_dir = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_shell = s; + + *pbufp = pbuf; + return 0; + +error_out: + if (started_at != -1) + fseek(fp, started_at, SEEK_SET); + return error; + return -1; +} + +int +pseudo_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { + /* note that we don't wrap fgetpwent_r, since there's no path + * references in it. + */ + if (!pseudo_pwd) { + errno = ENOENT; + return -1; + } + return pseudo_fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp); +} + +int +pseudo_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + /* note that we don't wrap fgetgrent_r, since there's no path + * references in it. + */ + if (!pseudo_grp) { + errno = ENOENT; + return -1; + } + return pseudo_fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp); +} + |