android_external_toybox/lib/lib.c
Rob Landley 4030e4564f Fix "./truncate -s 1e2" to say "not integer" instead of "too large".
It's not scientific notation, it's "exabyte suffix" and then the check
for trailing 'd' meaning decimal instead of binary always incremented past
the non-null character whether or not the test matched, so the error
check at the end didn't catch a non-d second suffix.
2024-01-27 17:54:50 -06:00

1548 lines
36 KiB
C

/* lib.c - various reusable stuff.
*
* Copyright 2006 Rob Landley <rob@landley.net>
*/
#define SYSLOG_NAMES
#include "toys.h"
void verror_msg(char *msg, int err, va_list va)
{
char *s = ": %s";
// Exit silently in a pipeline
if (err != EPIPE) {
fprintf(stderr, "%s: ", toys.which->name);
if (msg) vfprintf(stderr, msg, va);
else s+=2;
if (err>0) fprintf(stderr, s, strerror(err));
if (err<0 && CFG_TOYBOX_HELP)
fprintf(stderr, " (see \"%s --help\")", toys.which->name);
if (msg || err) putc('\n', stderr);
}
if (!toys.exitval) toys.exitval = (toys.which->flags>>24) ? : 1;
}
// These functions don't collapse together because of the va_stuff.
void error_msg(char *msg, ...)
{
va_list va;
va_start(va, msg);
verror_msg(msg, 0, va);
va_end(va);
}
void perror_msg(char *msg, ...)
{
va_list va;
va_start(va, msg);
verror_msg(msg, errno, va);
va_end(va);
}
// Die with an error message.
void error_exit(char *msg, ...)
{
va_list va;
va_start(va, msg);
verror_msg(msg, 0, va);
va_end(va);
xexit();
}
// Die with an error message and strerror(errno)
void perror_exit(char *msg, ...)
{
va_list va;
va_start(va, msg);
verror_msg(msg, errno, va);
va_end(va);
xexit();
}
// Exit with an error message after showing help text.
void help_exit(char *msg, ...)
{
va_list va;
if (!msg) show_help(stdout, 1);
else {
va_start(va, msg);
verror_msg(msg, -1, va);
va_end(va);
}
xexit();
}
// If you want to explicitly disable the printf() behavior (because you're
// printing user-supplied data, or because android's static checker produces
// false positives for 'char *s = x ? "blah1" : "blah2"; printf(s);' and it's
// -Werror there for policy reasons).
void error_msg_raw(char *msg)
{
error_msg("%s", msg);
}
void perror_msg_raw(char *msg)
{
perror_msg("%s", msg);
}
void error_exit_raw(char *msg)
{
error_exit("%s", msg);
}
void perror_exit_raw(char *msg)
{
perror_exit("%s", msg);
}
// Keep reading until full or EOF. Note: assumes sigaction(SA_RESTART),
// otherwise SIGSTOP/SIGCONT can return 0 from read/write without EOF.
ssize_t readall(int fd, void *buf, size_t len)
{
size_t count = 0;
while (count<len) {
int i = read(fd, (char *)buf+count, len-count);
if (!i) break;
if (i<0) return i;
count += i;
}
return count;
}
// Keep writing until done or EOF
ssize_t writeall(int fd, void *buf, size_t len)
{
size_t count = 0;
while (count<len) {
int i = write(fd, count+(char *)buf, len-count);
if (i<1) return i;
count += i;
}
return count;
}
// skip this many bytes of input. Return 0 for success, >0 means this much
// left after input skipped.
off_t lskip(int fd, off_t offset)
{
off_t cur = lseek(fd, 0, SEEK_CUR);
if (cur != -1) {
off_t end = lseek(fd, 0, SEEK_END) - cur;
if (end > 0 && end < offset) return offset - end;
end = offset+cur;
if (end == lseek(fd, end, SEEK_SET)) return 0;
perror_exit("lseek");
}
while (offset>0) {
int try = offset>sizeof(libbuf) ? sizeof(libbuf) : offset, or;
or = readall(fd, libbuf, try);
if (or < 0) perror_exit("lskip to %lld", (long long)offset);
else offset -= or;
if (or < try) break;
}
return offset;
}
// flags:
// MKPATHAT_MKLAST make last dir (with mode lastmode, else skips last part)
// MKPATHAT_MAKE make leading dirs (it's ok if they already exist)
// MKPATHAT_VERBOSE Print what got created to stderr
// returns 0 = path ok, 1 = error
int mkpathat(int atfd, char *dir, mode_t lastmode, int flags)
{
struct stat buf;
char *s;
// mkdir -p one/two/three is not an error if the path already exists,
// but is if "three" is a file. The others we dereference and catch
// not-a-directory along the way, but the last one we must explicitly
// test for. Might as well do it up front.
if (!fstatat(atfd, dir, &buf, 0)) {
// Note that mkdir should return EEXIST for already existed directory/file.
if (!(flags&MKPATHAT_MAKE) || ((flags&MKPATHAT_MKLAST) && !S_ISDIR(buf.st_mode))) {
errno = EEXIST;
return 1;
} else return 0;
}
for (s = dir; ;s++) {
char save = 0;
mode_t mode = (0777&~toys.old_umask)|0300;
// find next '/', but don't try to mkdir "" at start of absolute path
if (*s == '/' && (flags&MKPATHAT_MAKE) && s != dir) {
save = *s;
*s = 0;
} else if (*s) continue;
// Use the mode from the -m option only for the last directory.
if (!save) {
if (flags&MKPATHAT_MKLAST) mode = lastmode;
else break;
}
if (mkdirat(atfd, dir, mode)) {
if (!(flags&MKPATHAT_MAKE) || errno != EEXIST) return 1;
} else if (flags&MKPATHAT_VERBOSE)
fprintf(stderr, "%s: created directory '%s'\n", toys.which->name, dir);
if (!(*s = save)) break;
}
return 0;
}
// The common case
int mkpath(char *dir)
{
return mkpathat(AT_FDCWD, dir, 0, MKPATHAT_MAKE);
}
// Split a path into linked list of components, tracking head and tail of list.
// Assigns head of list to *list, returns address of ->next entry to extend list
// Filters out // entries with no contents.
struct string_list **splitpath(char *path, struct string_list **list)
{
char *new = path;
*list = 0;
do {
int len;
if (*path && *path != '/') continue;
len = path-new;
if (len > 0) {
*list = xmalloc(sizeof(struct string_list) + len + 1);
(*list)->next = 0;
memcpy((*list)->str, new, len);
(*list)->str[len] = 0;
list = &(*list)->next;
}
new = path+1;
} while (*path++);
return list;
}
// Find all file in a colon-separated path with access type "type" (generally
// X_OK or R_OK). Returns a list of absolute paths to each file found, in
// order.
struct string_list *find_in_path(char *path, char *filename)
{
struct string_list *rlist = NULL, **prlist=&rlist;
char *cwd;
if (!path) return 0;
cwd = xgetcwd();
for (;;) {
char *res, *next = strchr(path, ':');
int len = next ? next-path : strlen(path);
struct string_list *rnext;
struct stat st;
rnext = xmalloc(sizeof(void *) + strlen(filename)
+ (len ? len : strlen(cwd)) + 2);
if (!len) sprintf(rnext->str, "%s/%s", cwd, filename);
else {
memcpy(res = rnext->str, path, len);
res += len;
*(res++) = '/';
strcpy(res, filename);
}
// Confirm it's not a directory.
if (!stat(rnext->str, &st) && S_ISREG(st.st_mode)) {
*prlist = rnext;
rnext->next = NULL;
prlist = &(rnext->next);
} else free(rnext);
if (!next) break;
path += len;
path++;
}
free(cwd);
return rlist;
}
long long estrtol(char *str, char **end, int base)
{
errno = 0;
return strtoll(str, end, base);
}
long long xstrtol(char *str, char **end, int base)
{
long long l = estrtol(str, end, base);
if (errno) perror_exit_raw(str);
return l;
}
// atol() with the kilo/mega/giga/tera/peta/exa extensions, plus word and block.
// (zetta and yotta don't fit in 64 bits.)
long long atolx(char *numstr)
{
char *c = numstr, *suffixes="cwbkmgtpe", *end;
long long val;
val = xstrtol(numstr, &c, 0);
if (c != numstr && *c && (end = strchr(suffixes, tolower(*c)))) {
int shift = end-suffixes-2;
++c;
if (shift==-1) val *= 2;
else if (!shift) val *= 512;
else if (shift>0) {
if (*c && tolower(*c)=='d') {
c++;
while (shift--) val *= 1000;
} else val *= 1LL<<(shift*10);
}
}
while (isspace(*c)) c++;
if (c==numstr || *c) error_exit("not integer: %s", numstr);
return val;
}
long long atolx_range(char *numstr, long long low, long long high)
{
long long val = atolx(numstr);
if (val < low) error_exit("%lld < %lld", val, low);
if (val > high) error_exit("%lld > %lld", val, high);
return val;
}
int stridx(char *haystack, char needle)
{
char *off;
if (!needle) return -1;
off = strchr(haystack, needle);
if (!off) return -1;
return off-haystack;
}
// Convert wc to utf8, returning bytes written. Does not null terminate.
int wctoutf8(char *s, unsigned wc)
{
int len = (wc>0x7ff)+(wc>0xffff), i;
if (wc<128) {
*s = wc;
return 1;
} else {
i = len;
do {
s[1+i] = 0x80+(wc&0x3f);
wc >>= 6;
} while (i--);
*s = (((signed char) 0x80) >> (len+1)) | wc;
}
return 2+len;
}
// Convert utf8 sequence to a unicode wide character
// returns bytes consumed, or -1 if err, or -2 if need more data.
int utf8towc(unsigned *wc, char *str, unsigned len)
{
unsigned result, mask, first;
char *s, c;
// fast path ASCII
if (len && *str<128) return !!(*wc = *str);
result = first = *(s = str++);
if (result<0xc2 || result>0xf4) return -1;
for (mask = 6; (first&0xc0)==0xc0; mask += 5, first <<= 1) {
if (!--len) return -2;
if (((c = *(str++))&0xc0) != 0x80) return -1;
result = (result<<6)|(c&0x3f);
}
result &= (1<<mask)-1;
c = str-s;
// Avoid overlong encodings
if (result<(unsigned []){0x80,0x800,0x10000}[c-2]) return -1;
// Limit unicode so it can't encode anything UTF-16 can't.
if (result>0x10ffff || (result>=0xd800 && result<=0xdfff)) return -1;
*wc = result;
return str-s;
}
// Convert string to lower case, utf8 aware.
char *strlower(char *s)
{
char *try, *new;
int len, mlen = (strlen(s)|7)+9;
unsigned c;
try = new = xmalloc(mlen);
while (*s) {
if (1>(len = utf8towc(&c, s, MB_CUR_MAX))) {
*(new++) = *(s++);
continue;
}
s += len;
// squash title case too
c = towlower(c);
// if we had a valid utf8 sequence, convert it to lower case, and can't
// encode back to utf8, something is wrong with your libc. But just
// in case somebody finds an exploit...
len = wcrtomb(new, c, 0);
if (len < 1) error_exit("bad utf8 %x", (int)c);
new += len;
// Case conversion can expand utf8 representation, but with extra mlen
// space above we should basically never need to realloc
if (mlen+4 > (len = new-try)) continue;
try = xrealloc(try, mlen = len+16);
new = try+len;
}
*new = 0;
return try;
}
// strstr but returns pointer after match
char *strafter(char *haystack, char *needle)
{
char *s = strstr(haystack, needle);
return s ? s+strlen(needle) : s;
}
// Remove trailing \n
char *chomp(char *s)
{
char *p;
if (s) for (p = s+strlen(s); p>s && (p[-1]=='\r' || p[-1]=='\n'); *--p = 0);
return s;
}
int unescape(char c)
{
char *from = "\\abefnrtv", *to = "\\\a\b\e\f\n\r\t\v";
int idx = stridx(from, c);
return (idx == -1) ? 0 : to[idx];
}
// parse next character advancing pointer. echo requires leading 0 in octal esc
int unescape2(char **c, int echo)
{
int idx = *((*c)++), i, off;
if (idx != '\\' || !**c) return idx;
if (**c == 'c') return 31&*(++*c);
for (i = 0; i<4; i++) {
if (sscanf(*c, (char *[]){"0%3o%n"+!echo, "x%2x%n", "u%4x%n", "U%6x%n"}[i],
&idx, &off) > 0)
{
*c += off;
return idx;
}
}
if (-1 == (idx = stridx("\\abeEfnrtv'\"?0", **c))) return '\\';
++*c;
return "\\\a\b\e\e\f\n\r\t\v'\"?"[idx];
}
// If string ends with suffix return pointer to start of suffix in string,
// else NULL
char *strend(char *str, char *suffix)
{
long a = strlen(str), b = strlen(suffix);
if (a>b && !strcmp(str += a-b, suffix)) return str;
return 0;
}
// If *a starts with b, advance *a past it and return 1, else return 0;
int strstart(char **a, char *b)
{
char *c = *a;
while (*b && *c == *b) b++, c++;
if (!*b) *a = c;
return !*b;
}
// If *a starts with b, advance *a past it and return 1, else return 0;
int strcasestart(char **a, char *b)
{
int len = strlen(b), i = !strncasecmp(*a, b, len);
if (i) *a += len;
return i;
}
int same_file(struct stat *st1, struct stat *st2)
{
return st1->st_ino==st2->st_ino && st1->st_dev==st2->st_dev;
}
int same_dev_ino(struct stat *st, struct dev_ino *di)
{
return st->st_ino==di->ino && st->st_dev==di->dev;
}
// Return how long the file at fd is, if there's any way to determine it.
off_t fdlength(int fd)
{
struct stat st;
off_t base = 0, range = 1, expand = 1, old;
unsigned long long size;
if (!fstat(fd, &st) && S_ISREG(st.st_mode)) return st.st_size;
// If the ioctl works for this, return it.
if (get_block_device_size(fd, &size)) return size;
// If not, do a binary search for the last location we can read. (Some
// block devices don't do BLKGETSIZE right.) This should probably have
// a CONFIG option...
old = lseek(fd, 0, SEEK_CUR);
do {
char temp;
off_t pos = base + range / 2;
if (lseek(fd, pos, 0)>=0 && read(fd, &temp, 1)==1) {
off_t delta = (pos + 1) - base;
base += delta;
if (expand) range = (expand <<= 1) - base;
else range -= delta;
} else {
expand = 0;
range = pos - base;
}
} while (range > 0);
lseek(fd, old, SEEK_SET);
return base;
}
char *readfd(int fd, char *ibuf, off_t *plen)
{
off_t len, rlen;
char *buf, *rbuf;
// Unsafe to probe for size with a supplied buffer, don't ever do that.
if (CFG_TOYBOX_DEBUG && (ibuf ? !*plen : *plen)) error_exit("bad readfileat");
// If we dunno the length, probe it. If we can't probe, start with 1 page.
if (!*plen) {
if ((len = fdlength(fd))>0) *plen = len;
else len = 4096;
} else len = *plen-1;
if (!ibuf) buf = xmalloc(len+1);
else buf = ibuf;
for (rbuf = buf;;) {
rlen = readall(fd, rbuf, len);
if (*plen || rlen<len) break;
// If reading unknown size, expand buffer by 1.5 each time we fill it up.
rlen += rbuf-buf;
buf = xrealloc(buf, len = (rlen*3)/2);
rbuf = buf+rlen;
len -= rlen;
}
*plen = len = rlen+(rbuf-buf);
if (rlen<0) {
if (ibuf != buf) free(buf);
buf = 0;
} else buf[len] = 0;
return buf;
}
// Read contents of file as a single nul-terminated string.
// measure file size if !len, allocate buffer if !buf
// Existing buffers need len in *plen
// Returns amount of data read in *plen
char *readfileat(int dirfd, char *name, char *ibuf, off_t *plen)
{
if (-1 == (dirfd = openat(dirfd, name, O_RDONLY))) return 0;
ibuf = readfd(dirfd, ibuf, plen);
close(dirfd);
return ibuf;
}
char *readfile(char *name, char *ibuf, off_t len)
{
return readfileat(AT_FDCWD, name, ibuf, &len);
}
// Sleep for this many thousandths of a second
void msleep(long milliseconds)
{
struct timespec ts;
ts.tv_sec = milliseconds/1000;
ts.tv_nsec = (milliseconds%1000)*1000000;
nanosleep(&ts, &ts);
}
// Adjust timespec by nanosecond offset
void nanomove(struct timespec *ts, long long offset)
{
long long nano = ts->tv_nsec + offset, secs = nano/1000000000;
ts->tv_sec += secs;
nano %= 1000000000;
if (nano<0) {
ts->tv_sec--;
nano += 1000000000;
}
ts->tv_nsec = nano;
}
// return difference between two timespecs in nanosecs
long long nanodiff(struct timespec *old, struct timespec *new)
{
return (new->tv_sec - old->tv_sec)*1000000000LL+(new->tv_nsec - old->tv_nsec);
}
// return 1<<x of highest bit set
int highest_bit(unsigned long l)
{
int i;
for (i = 0; l; i++) l >>= 1;
return i-1;
}
// Inefficient, but deals with unaligned access
long long peek_le(void *ptr, unsigned size)
{
long long ret = 0;
char *c = ptr;
int i;
for (i=0; i<size; i++) ret |= ((long long)c[i])<<(i*8);
return ret;
}
long long peek_be(void *ptr, unsigned size)
{
long long ret = 0;
char *c = ptr;
int i;
for (i=0; i<size; i++) ret = (ret<<8)|(c[i]&0xff);
return ret;
}
long long peek(void *ptr, unsigned size)
{
return (IS_BIG_ENDIAN ? peek_be : peek_le)(ptr, size);
}
void poke_le(void *ptr, long long val, unsigned size)
{
char *c = ptr;
while (size--) {
*c++ = val&255;
val >>= 8;
}
}
void poke_be(void *ptr, long long val, unsigned size)
{
char *c = ptr + size;
while (size--) {
*--c = val&255;
val >>=8;
}
}
void poke(void *ptr, long long val, unsigned size)
{
(IS_BIG_ENDIAN ? poke_be : poke_le)(ptr, val, size);
}
// Iterate through an array of files, opening each one and calling a function
// on that filehandle and name. The special filename "-" means stdin if
// flags is O_RDONLY, stdout otherwise. An empty argument list calls
// function() on just stdin/stdout.
//
// Note: pass O_CLOEXEC to automatically close filehandles when function()
// returns, otherwise filehandles must be closed by function().
// pass WARN_ONLY to produce warning messages about files it couldn't
// open/create, and skip them. Otherwise function is called with fd -1.
void loopfiles_rw(char **argv, int flags, int permissions,
void (*function)(int fd, char *name))
{
int fd, failok = !(flags&WARN_ONLY), anyway = flags & LOOPFILES_ANYWAY;
flags &= ~(WARN_ONLY|LOOPFILES_ANYWAY);
// If no arguments, read from stdin.
if (!*argv) function((flags & O_ACCMODE) != O_RDONLY ? 1 : 0, "-");
else do {
// Filename "-" means read from stdin.
// Inability to open a file prints a warning, but doesn't exit.
if (!strcmp(*argv, "-")) fd = 0;
else if (0>(fd = xnotstdio(open(*argv, flags, permissions))) && !failok) {
perror_msg_raw(*argv);
if (!anyway) continue;
}
function(fd, *argv);
if ((flags & O_CLOEXEC) && fd>0) close(fd);
} while (*++argv);
}
// Call loopfiles_rw with O_RDONLY|O_CLOEXEC|WARN_ONLY (common case)
void loopfiles(char **argv, void (*function)(int fd, char *name))
{
loopfiles_rw(argv, O_RDONLY|O_CLOEXEC|WARN_ONLY, 0, function);
}
// glue to call do_lines() from loopfiles
static void (*do_lines_bridge)(char **pline, long len);
static void loopfile_lines_bridge(int fd, char *name)
{
do_lines(fd, '\n', do_lines_bridge);
}
void loopfiles_lines(char **argv, void (*function)(char **pline, long len))
{
do_lines_bridge = function;
// No O_CLOEXEC because we need to call fclose.
loopfiles_rw(argv, O_RDONLY|WARN_ONLY, 0, loopfile_lines_bridge);
}
int wfchmodat(int fd, char *name, mode_t mode)
{
int rc = fchmodat(fd, name, mode, 0);
if (rc) {
perror_msg("chmod '%s' to %04o", name, mode);
toys.exitval=1;
}
return rc;
}
static char *tempfile2zap;
static void tempfile_handler(void)
{
if (1 < (long)tempfile2zap) unlink(tempfile2zap);
}
// Open a temporary file to copy an existing file into.
int copy_tempfile(int fdin, char *name, char **tempname)
{
struct stat statbuf;
int fd = xtempfile(name, tempname), ignored __attribute__((__unused__));
// Record tempfile for exit cleanup if interrupted
if (!tempfile2zap) sigatexit(tempfile_handler);
tempfile2zap = *tempname;
// Set permissions of output file.
if (!fstat(fdin, &statbuf)) fchmod(fd, statbuf.st_mode);
// We chmod before chown, which strips the suid bit. Caller has to explicitly
// switch it back on if they want to keep suid.
// Suppress warn-unused-result. Both gcc and clang clutch their pearls about
// this but it's _supposed_ to fail when we're not root.
ignored = fchown(fd, statbuf.st_uid, statbuf.st_gid);
return fd;
}
// Abort the copy and delete the temporary file.
void delete_tempfile(int fdin, int fdout, char **tempname)
{
close(fdin);
close(fdout);
if (*tempname) unlink(*tempname);
tempfile2zap = (char *)1;
free(*tempname);
*tempname = NULL;
}
// Copy the rest of the data and replace the original with the copy.
void replace_tempfile(int fdin, int fdout, char **tempname)
{
char *temp = xstrdup(*tempname);
temp[strlen(temp)-6]=0;
if (fdin != -1) {
xsendfile(fdin, fdout);
xclose(fdin);
}
xclose(fdout);
xrename(*tempname, temp);
tempfile2zap = (char *)1;
free(*tempname);
free(temp);
*tempname = NULL;
}
// Create a 256 entry CRC32 lookup table.
void crc_init(unsigned *crc_table, int little_endian)
{
unsigned int i;
// Init the CRC32 table (big endian)
for (i=0; i<256; i++) {
unsigned int j, c = little_endian ? i : i<<24;
for (j=8; j; j--)
if (little_endian) c = (c&1) ? (c>>1)^0xEDB88320 : c>>1;
else c=c&0x80000000 ? (c<<1)^0x04c11db7 : (c<<1);
crc_table[i] = c;
}
}
// Init base64 table
void base64_init(char *p)
{
int i;
for (i = 'A'; i != ':'; i++) {
if (i == 'Z'+1) i = 'a';
if (i == 'z'+1) i = '0';
*(p++) = i;
}
*(p++) = '+';
*(p++) = '/';
}
int yesno(int def)
{
return fyesno(stdin, def);
}
int fyesno(FILE *in, int def)
{
char buf;
fprintf(stderr, " (%c/%c):", def ? 'Y' : 'y', def ? 'n' : 'N');
fflush(stderr);
while (fread(&buf, 1, 1, in)) {
int new;
// The letter changes the value, the newline (or space) returns it.
if (isspace(buf)) break;
if (-1 != (new = stridx("ny", tolower(buf)))) def = new;
}
return def;
}
// Handler that sets toys.signal, and writes to toys.signalfd if set
void generic_signal(int sig)
{
if (toys.signalfd) {
char c = sig;
writeall(toys.signalfd, &c, 1);
}
toys.signal = sig;
}
// More or less SIG_DFL that runs our atexit list and can siglongjmp.
void exit_signal(int sig)
{
sigset_t sigset;
if (sig) toys.exitval = sig|128;
sigfillset(&sigset);
sigprocmask(SIG_BLOCK, &sigset, 0);
xexit();
}
// Install an atexit handler. Also install the same handler on every signal
// that defaults to killing the process, calling the handler on the way out.
// Calling multiple times adds the handlers to a list, to be called in LIFO
// order.
void sigatexit(void *handler)
{
struct arg_list *al = 0;
xsignal_all_killers(handler ? exit_signal : SIG_DFL);
if (handler) {
al = xmalloc(sizeof(struct arg_list));
al->next = toys.xexit;
al->arg = handler;
} else llist_traverse(toys.xexit, free);
toys.xexit = al;
}
// Output a nicely formatted table of all the signals.
void list_signals(void)
{
int i = 1, count = 0;
unsigned cols = 80;
char *name;
terminal_size(&cols, 0);
cols /= 16;
for (; i<=NSIG; i++) {
if ((name = num_to_sig(i))) {
printf("%2d) SIG%-9s", i, name);
if (++count % cols == 0) putchar('\n');
}
}
putchar('\n');
}
// premute mode bits based on posix mode strings.
mode_t string_to_mode(char *modestr, mode_t mode)
{
char *whos = "ogua", *hows = "=+-", *whats = "xwrstX", *whys = "ogu",
*s, *str = modestr;
mode_t extrabits = mode & ~(07777);
// Handle octal mode
if (isdigit(*str)) {
mode = estrtol(str, &s, 8);
if (errno || *s || (mode & ~(07777))) goto barf;
return mode | extrabits;
}
// Gaze into the bin of permission...
for (;;) {
int i, j, dowho, dohow, dowhat, amask;
dowho = dohow = dowhat = amask = 0;
// Find the who, how, and what stanzas, in that order
while (*str && (s = strchr(whos, *str))) {
dowho |= 1<<(s-whos);
str++;
}
// If who isn't specified, like "a" but honoring umask.
if (!dowho) {
dowho = 8;
umask(amask = umask(0));
}
// Repeated "hows" are allowed; something like "a=r+w+s" is valid.
for (;;) {
if (-1 == stridx(hows, dohow = *str)) goto barf;
while (*++str && (s = strchr(whats, *str))) dowhat |= 1<<(s-whats);
// Convert X to x for directory or if already executable somewhere
if ((dowhat&32) && (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
// Copy mode from another category?
if (!dowhat && -1 != (i = stridx(whys, *str))) {
dowhat = (mode>>(3*i))&7;
str++;
}
// Loop through what=xwrs and who=ogu to apply bits to the mode.
for (i=0; i<4; i++) {
for (j=0; j<3; j++) {
mode_t bit = 0;
int where = 1<<((3*i)+j);
if (amask & where) continue;
// Figure out new value at this location
if (i == 3) {
// suid and sticky
if (!j) bit = dowhat&16; // o+s = t but a+s doesn't set t, hence t
else if ((dowhat&8) && (dowho&(8|(1<<j)))) bit++;
} else {
if (!(dowho&(8|(1<<i)))) continue;
else if (dowhat&(1<<j)) bit++;
}
// When selection active, modify bit
if (dohow == '=' || (bit && dohow == '-')) mode &= ~where;
if (bit && dohow != '-') mode |= where;
}
}
if (!*str) return mode|extrabits;
if (*str == ',') {
str++;
break;
}
}
}
barf:
error_exit("bad mode '%s'", modestr);
}
// Format access mode into a drwxrwxrwx string
void mode_to_string(mode_t mode, char *buf)
{
char c, d;
int i, bit;
buf[10]=0;
for (i=0; i<9; i++) {
bit = mode & (1<<i);
c = i%3;
if (!c && (mode & (1<<((d=i/3)+9)))) {
c = "tss"[d];
if (!bit) c &= ~0x20;
} else c = bit ? "xwr"[c] : '-';
buf[9-i] = c;
}
if (S_ISDIR(mode)) c = 'd';
else if (S_ISBLK(mode)) c = 'b';
else if (S_ISCHR(mode)) c = 'c';
else if (S_ISLNK(mode)) c = 'l';
else if (S_ISFIFO(mode)) c = 'p';
else if (S_ISSOCK(mode)) c = 's';
else c = '-';
*buf = c;
}
// basename() can modify its argument or return a pointer to a constant string
// This just gives after the last '/' or the whole stirng if no /
char *getbasename(char *name)
{
char *s = strrchr(name, '/');
if (s) return s+1;
return name;
}
// Return pointer to xabspath(file) if file is under dir, else 0
char *fileunderdir(char *file, char *dir)
{
char *s1 = xabspath(dir, ABS_FILE), *s2 = xabspath(file, 0), *ss = s2;
int rc = s1 && s2 && strstart(&ss, s1) && (!s1[1] || s2[strlen(s1)] == '/');
free(s1);
if (!rc) free(s2);
return rc ? s2 : 0;
}
void *mepcpy(void *to, void *from, unsigned long len)
{
memcpy(to, from, len);
return ((char *)to)+len;
}
// return (malloced) relative path to get between two normalized absolute paths
// normalized: no duplicate / or trailing / or .. or . (symlinks optional)
char *relative_path(char *from, char *to, int abs)
{
char *s, *ret = 0;
int i, j, k;
if (abs) {
if (!(from = xabspath(from, 0))) return 0;
if (!(to = xabspath(to, 0))) goto error;
}
for (i = j = 0;; i++) {
if (!from[i] || !to[i]) {
if (from[i]=='/' || to[i]=='/' || from[i]==to[i]) j = i;
break;
}
if (from[i] != to[i]) break;
if (from[i] == '/') j = i;
}
// count remaining destination directories
for (i = j, k = 0; from[i]; i++) if (from[i] == '/') k++;
if (!k) {
if (to[j]=='/') j++;
ret = xstrdup(to[j] ? to+j : ".");
} else {
s = ret = xmprintf("%*c%s", 3*k-!!k, ' ', to+j);
for (i = 0; i<k; i++) s = mepcpy(s, "/.."+!i, 3-!i);
}
error:
if (abs) {
free(from);
free(to);
}
return ret;
}
// Execute a callback for each PID that matches a process name from a list.
void names_to_pid(char **names, int (*callback)(pid_t pid, char *name),
int scripts)
{
DIR *dp;
struct dirent *entry;
if (!(dp = opendir("/proc"))) perror_exit("no /proc");
while ((entry = readdir(dp))) {
unsigned u = atoi(entry->d_name);
char *cmd = 0, *comm = 0, **cur;
off_t len;
if (!u) continue;
// Comm is original name of executable (argv[0] could be #! interpreter)
// but it's limited to 15 characters
if (scripts) {
sprintf(libbuf, "/proc/%u/comm", u);
len = sizeof(libbuf);
if (!(comm = readfileat(AT_FDCWD, libbuf, libbuf, &len)) || !len)
continue;
if (libbuf[len-1] == '\n') libbuf[--len] = 0;
}
for (cur = names; *cur; cur++) {
struct stat st1, st2;
char *bb = getbasename(*cur);
off_t len = strlen(bb);
// Fast path: only matching a filename (no path) that fits in comm.
// `len` must be 14 or less because with a full 15 bytes we don't
// know whether the name fit or was truncated.
if (scripts && len<=14 && bb==*cur && !strcmp(comm, bb)) goto match;
// If we have a path to existing file only match if same inode
if (bb!=*cur && !stat(*cur, &st1)) {
char buf[32];
sprintf(buf, "/proc/%u/exe", u);
if (stat(buf, &st2) || !same_file(&st1, &st2)) continue;
goto match;
}
// Nope, gotta read command line to confirm
if (!cmd) {
sprintf(cmd = libbuf+16, "/proc/%u/cmdline", u);
len = sizeof(libbuf)-17;
if (!(cmd = readfileat(AT_FDCWD, cmd, cmd, &len))) continue;
// readfile only guarantees one null terminator and we need two
// (yes the kernel should do this for us, don't care)
cmd[len] = 0;
}
if (!strcmp(bb, getbasename(cmd))) goto match;
if (scripts && !strcmp(bb, getbasename(cmd+strlen(cmd)+1))) goto match;
continue;
match:
if (callback(u, *cur)) goto done;
}
}
done:
closedir(dp);
}
// display first "dgt" many digits of number plus unit (kilo-exabytes)
int human_readable_long(char *buf, unsigned long long num, int dgt, int unit,
int style)
{
unsigned long long snap = 0;
int len, divisor = (style&HR_1000) ? 1000 : 1024;
// Divide rounding up until we have 3 or fewer digits. Since the part we
// print is decimal, the test is 999 even when we divide by 1024.
// The largest unit we can detect is 1<<64 = 18 Exabytes, but we added
// Zettabyte and Yottabyte in case "unit" starts above zero.
for (;;unit++) {
if ((len = snprintf(0, 0, "%llu", num))<=dgt) break;
num = ((snap = num)+(divisor/2))/divisor;
}
if (CFG_TOYBOX_DEBUG && unit>8) return sprintf(buf, "%.*s", dgt, "TILT");
len = sprintf(buf, "%llu", num);
if (!(style & HR_NODOT) && unit && len == 1) {
// Redo rounding for 1.2M case, this works with and without HR_1000.
num = snap/divisor;
snap -= num*divisor;
snap = ((snap*100)+50)/divisor;
snap /= 10;
len = sprintf(buf, "%llu.%llu", num, snap);
}
if (style & HR_SPACE) buf[len++] = ' ';
if (unit) {
unit = " kMGTPEZY"[unit];
if (!(style&HR_1000)) unit = toupper(unit);
buf[len++] = unit;
} else if (style & HR_B) buf[len++] = 'B';
buf[len] = 0;
return len;
}
// Give 3 digit estimate + units ala 999M or 1.7T
int human_readable(char *buf, unsigned long long num, int style)
{
return human_readable_long(buf, num, 3, 0, style);
}
// The qsort man page says you can use alphasort, the posix committee
// disagreed, and doubled down: http://austingroupbugs.net/view.php?id=142
// So just do our own. (The const is entirely to humor the stupid compiler.)
int qstrcmp(const void *a, const void *b)
{
return strcmp(*(char **)a, *(char **)b);
}
// See https://tools.ietf.org/html/rfc4122, specifically section 4.4
// "Algorithms for Creating a UUID from Truly Random or Pseudo-Random
// Numbers".
void create_uuid(char *uuid)
{
// "Set all the ... bits to randomly (or pseudo-randomly) chosen values".
xgetrandom(uuid, 16);
// "Set the four most significant bits ... of the time_hi_and_version
// field to the 4-bit version number [4]".
uuid[6] = (uuid[6] & 0x0F) | 0x40;
// "Set the two most significant bits (bits 6 and 7) of
// clock_seq_hi_and_reserved to zero and one, respectively".
uuid[8] = (uuid[8] & 0x3F) | 0x80;
}
char *show_uuid(char *uuid)
{
char *out = libbuf;
int i;
for (i=0; i<16; i++) out+=sprintf(out, "-%02x"+!(0x550&(1<<i)), uuid[i]);
*out = 0;
return libbuf;
}
// Returns pointer to letter at end, 0 if none. *start = initial %
char *next_printf(char *s, char **start)
{
for (; *s; s++) {
if (*s != '%') continue;
if (*++s == '%') continue;
if (start) *start = s-1;
while (0 <= stridx("0'#-+ ", *s)) s++;
while (isdigit(*s)) s++;
if (*s == '.') s++;
while (isdigit(*s)) s++;
return s;
}
return 0;
}
// Return cached passwd entries.
struct passwd *bufgetpwnamuid(char *name, uid_t uid)
{
struct pwuidbuf_list {
struct pwuidbuf_list *next;
struct passwd pw;
} *list = 0;
struct passwd *temp;
static struct pwuidbuf_list *pwuidbuf;
unsigned size = 256;
// If we already have this one, return it.
for (list = pwuidbuf; list; list = list->next)
if (name ? !strcmp(name, list->pw.pw_name) : list->pw.pw_uid==uid)
return &(list->pw);
for (;;) {
list = xrealloc(list, size *= 2);
if (name) errno = getpwnam_r(name, &list->pw, sizeof(*list)+(char *)list,
size-sizeof(*list), &temp);
else errno = getpwuid_r(uid, &list->pw, sizeof(*list)+(char *)list,
size-sizeof(*list), &temp);
if (errno != ERANGE) break;
}
if (!temp) {
free(list);
return 0;
}
list->next = pwuidbuf;
pwuidbuf = list;
return &list->pw;
}
struct passwd *bufgetpwuid(uid_t uid)
{
return bufgetpwnamuid(0, uid);
}
// Return cached group entries.
struct group *bufgetgrnamgid(char *name, gid_t gid)
{
struct grgidbuf_list {
struct grgidbuf_list *next;
struct group gr;
} *list = 0;
struct group *temp;
static struct grgidbuf_list *grgidbuf;
unsigned size = 256;
for (list = grgidbuf; list; list = list->next)
if (name ? !strcmp(name, list->gr.gr_name) : list->gr.gr_gid==gid)
return &(list->gr);
for (;;) {
list = xrealloc(list, size *= 2);
if (name) errno = getgrnam_r(name, &list->gr, sizeof(*list)+(char *)list,
size-sizeof(*list), &temp);
else errno = getgrgid_r(gid, &list->gr, sizeof(*list)+(char *)list,
size-sizeof(*list), &temp);
if (errno != ERANGE) break;
}
if (!temp) {
free(list);
return 0;
}
list->next = grgidbuf;
grgidbuf = list;
return &list->gr;
}
struct group *bufgetgrgid(gid_t gid)
{
return bufgetgrnamgid(0, gid);
}
// Always null terminates, returns 0 for failure, len for success
int readlinkat0(int dirfd, char *path, char *buf, int len)
{
if (!len) return 0;
len = readlinkat(dirfd, path, buf, len-1);
if (len<0) len = 0;
buf[len] = 0;
return len;
}
int readlink0(char *path, char *buf, int len)
{
return readlinkat0(AT_FDCWD, path, buf, len);
}
// Do regex matching with len argument to handle embedded NUL bytes in string
int regexec0(regex_t *preg, char *string, long len, int nmatch,
regmatch_t *pmatch, int eflags)
{
regmatch_t backup;
if (!nmatch) pmatch = &backup;
pmatch->rm_so = 0;
pmatch->rm_eo = len;
return regexec(preg, string, nmatch, pmatch, eflags|REG_STARTEND);
}
// Return user name or string representation of number, returned buffer
// lasts until next call.
char *getusername(uid_t uid)
{
struct passwd *pw = bufgetpwuid(uid);
static char unum[12];
sprintf(unum, "%u", (unsigned)uid);
return pw ? pw->pw_name : unum;
}
// Return group name or string representation of number, returned buffer
// lasts until next call.
char *getgroupname(gid_t gid)
{
struct group *gr = bufgetgrgid(gid);
static char gnum[12];
sprintf(gnum, "%u", (unsigned)gid);
return gr ? gr->gr_name : gnum;
}
// Iterate over lines in file, calling function. Function can write 0 to
// the line pointer if they want to keep it, or 1 to terminate processing,
// otherwise line is freed. Passed file descriptor is closed at the end.
// At EOF calls function(0, 0)
void do_lines(int fd, char delim, void (*call)(char **pline, long len))
{
FILE *fp = fd ? xfdopen(fd, "r") : stdin;
for (;;) {
char *line = 0;
ssize_t len;
len = getdelim(&line, (void *)&len, delim, fp);
if (len > 0) {
call(&line, len);
if (line == (void *)1) break;
free(line);
} else break;
}
call(0, 0);
if (fd) fclose(fp);
}
// Return unix time in milliseconds
long long millitime(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec*1000+ts.tv_nsec/1000000;
}
// Formats `ts` in ISO format ("2018-06-28 15:08:58.846386216 -0700").
char *format_iso_time(char *buf, size_t len, struct timespec *ts)
{
char *s = buf;
s += strftime(s, len, "%F %T", localtime(&(ts->tv_sec)));
s += sprintf(s, ".%09ld ", ts->tv_nsec);
s += strftime(s, len-strlen(buf), "%z", localtime(&(ts->tv_sec)));
return buf;
}
// Syslog with the openlog/closelog, autodetecting daemon status via no tty
void loggit(int priority, char *format, ...)
{
int i, facility = LOG_DAEMON;
va_list va;
for (i = 0; i<3; i++) if (isatty(i)) facility = LOG_AUTH;
openlog(toys.which->name, LOG_PID, facility);
va_start(va, format);
vsyslog(priority, format, va);
va_end(va);
closelog();
}
// Calculate tar packet checksum, with cksum field treated as 8 spaces
unsigned tar_cksum(void *data)
{
unsigned i, cksum = 8*' ';
for (i = 0; i<500; i += (i==147) ? 9 : 1) cksum += ((char *)data)[i];
return cksum;
}
// is this a valid tar header?
int is_tar_header(void *pkt)
{
char *p = pkt;
int i = 0;
if (p[257] && smemcmp("ustar", p+257, 5)) return 0;
if (p[148] != '0' && p[148] != ' ') return 0;
sscanf(p+148, "%8o", &i);
return i && tar_cksum(pkt) == i;
}
// Remove octal escapes from string (common in kernel exports)
void octal_deslash(char *s)
{
char *o = s;
while (*s) {
if (*s == '\\') {
int i, oct = 0;
for (i = 1; i < 4; i++) {
if (!isdigit(s[i])) break;
oct = (oct<<3)+s[i]-'0';
}
if (i == 4) {
*o++ = oct;
s += i;
continue;
}
}
*o++ = *s++;
}
*o = 0;
}
// ASAN flips out about memcmp("a", "abc", 4) but the result is well-defined.
// This one's guaranteed to stop at len _or_ the first difference.
int smemcmp(char *one, char *two, unsigned long len)
{
int ii = 0;
// NULL sorts after anything else
if (one == two) return 0;
if (!one) return 1;
if (!two) return -1;
while (len--) if ((ii = *one++ - *two++)) break;
return ii;
}