| /* misc utilities for plan9 */ |
| |
| #include <ns.h> |
| #include <string.h> |
| #include <err.h> |
| #include <syscall.h> |
| #include <smp.h> |
| |
| /* Copies n bytes from mem + offset into buf, similar to a read() call. */ |
| int readmem(unsigned long offset, char *buf, unsigned long n, |
| const void *mem, size_t mem_len) |
| { |
| if (offset >= mem_len) |
| return 0; |
| if (offset + n > mem_len) |
| n = mem_len - offset; |
| memmove(buf, mem + offset, n); |
| return n; |
| } |
| |
| /* Read a num/string to user mode, accounting for offset. Not a huge fan of the |
| * 'size' parameter (the old plan9 users just picked NUMSIZE (12), though they |
| * seem to want to limit it). */ |
| static int __readnum(unsigned long off, char *buf, unsigned long n, |
| unsigned long val, size_t size, const char *fmt) |
| { |
| char tmp[64]; |
| |
| size = MIN(sizeof(tmp), size); |
| /* we really need the %* format. */ |
| size = snprintf(tmp, size, fmt, val); |
| /* size is now strlen, so the rest of this is just like readstr. */ |
| /* always include the \0 */ |
| return readmem(off, buf, n, tmp, size + 1); |
| } |
| |
| int readnum(unsigned long off, char *buf, unsigned long n, unsigned long val, |
| size_t size) |
| { |
| return __readnum(off, buf, n, val, size, "%lu"); |
| } |
| |
| int readnum_hex(unsigned long off, char *buf, unsigned long n, |
| unsigned long val, size_t size) |
| { |
| return __readnum(off, buf, n, val, size, "0x%lx"); |
| } |
| |
| int readstr(unsigned long offset, char *buf, unsigned long n, const char *str) |
| { |
| return readmem(offset, buf, n, str, strlen(str)); |
| } |
| |
| /* Helper: extracts a long from a user buffer (in text). */ |
| unsigned long strtoul_from_ubuf(void *ubuf, size_t count, int base) |
| { |
| char num64[NUMSIZE64]; |
| |
| /* want to give strtoul a null-terminated buf (can't handle random |
| * user strings) */ |
| if (count > sizeof(num64)) { |
| set_errno(EINVAL); |
| error(EFAIL, "attempted to write %d chars, max %d", count, |
| sizeof(num64)); |
| } |
| memcpy(num64, ubuf, count); |
| num64[count] = 0; /* enforce trailing 0 */ |
| return strtoul(num64, 0, base); |
| } |
| |
| /* Converts open mode flags, e.g. O_RDWR, to a rwx------ value, e.g. S_IRUSR */ |
| int omode_to_rwx(int open_flags) |
| { |
| static int rwx_opts[] = { [O_RDWR | O_EXEC] = 0700, |
| [O_RDWR] = 0600, |
| [O_READ | O_EXEC] = 0500, |
| [O_READ] = 0400, |
| [O_WRITE | O_EXEC] = 0300, |
| [O_WRITE] = 0200, |
| [O_EXEC] = 0100 }; |
| |
| return rwx_opts[open_flags & O_ACCMODE]; |
| } |
| |
| /* Converts open mode flags related to permissions, e.g. O_RDWR, to 9p. It's a |
| * bit ugly, since 9p (according to http://man.cat-v.org/plan_9/5/open) seems to |
| * require that O_EXEC is mutually exclusive with the others. If someone on |
| * Akaros wants EXEC, we'll just substitute READ. */ |
| int omode_to_9p_accmode(int open_flags) |
| { |
| static int acc_opts[] = { [O_RDWR | O_EXEC] = 2, |
| [O_WRITE | O_EXEC] = 2, |
| [O_READ | O_EXEC] = 0, |
| [O_EXEC] = 0, |
| [O_RDWR] = 2, |
| [O_WRITE] = 1, |
| [O_READ] = 0, |
| [0] = 0 /* we can't express no permissions */ |
| }; |
| |
| return acc_opts[open_flags & O_ACCMODE]; |
| } |
| |
| int access_bits_to_omode(int access_bits) |
| { |
| int omode = 0; |
| |
| if (R_OK) |
| omode |= O_READ; |
| if (W_OK) |
| omode |= O_WRITE; |
| if (X_OK) |
| omode |= O_EXEC; |
| return omode; |
| } |
| |
| /* TODO: This assumes UID isn't concurrently changed */ |
| bool caller_is_username(char *uid) |
| { |
| struct username *current_user = current ? ¤t->user : &eve; |
| |
| return strcmp(current_user->name, uid) == 0; |
| } |
| |
| /* Checks if current->user has permissions for omode access on something with |
| * {owner_fileuid, perm} */ |
| bool caller_has_perms(char *fileuid, uint32_t perm, int omode) |
| { |
| int rwx; |
| |
| perm &= S_PMASK; /* technically unnecessary; good for clarity */ |
| /* select user, group, or other from the traditional rwxrwxrwx, shifting |
| * into the upper-most position */ |
| if (caller_is_username(fileuid)) |
| perm <<= 0; |
| else if (iseve()) |
| perm <<= 3; |
| else |
| perm <<= 6; |
| /* translate omode into things like S_IRUSR (just one set of rwx------). |
| * Plan 9 originally only returned 0400 0200 0600 and 0100 here; it |
| * didn't seem to handle O_EXEC being mixed readable or writable. */ |
| rwx = omode_to_rwx(omode); |
| return (rwx & perm) == rwx; |
| } |
| |
| bool caller_has_dir_perms(struct dir *dir, int omode) |
| { |
| return caller_has_perms(dir->uid, READ_ONCE(dir->mode), omode); |
| } |
| |
| void dir_perm_check(struct dir *dir, int omode) |
| { |
| devpermcheck(dir->uid, READ_ONCE(dir->mode) & S_PMASK, omode); |
| } |