+
+// 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;
+}
+
+// Posix inexplicably hasn't got this, so find str in line.
+char *strnstr(char *line, char *str)
+{
+ long len = strlen(str);
+ char *s;
+
+ for (s = line; *s; s++) if (!strncasecmp(s, str, len)) break;
+
+ return *s ? s : 0;
+}
+
+int dev_minor(int dev)
+{
+ return ((dev&0xfff00000)>>12)|(dev&0xff);
+}
+
+int dev_major(int dev)
+{
+ return (dev&0xfff00)>>8;
+}
+
+int dev_makedev(int major, int minor)
+{
+ return (minor&0xff)|((major&0xfff)<<8)|((minor&0xfff00)<<12);
+}
+
+// Return cached passwd entries.
+struct passwd *bufgetpwuid(uid_t uid)
+{
+ struct pwuidbuf_list {
+ struct pwuidbuf_list *next;
+ struct passwd pw;
+ } *list;
+ struct passwd *temp;
+ static struct pwuidbuf_list *pwuidbuf;
+
+ for (list = pwuidbuf; list; list = list->next)
+ if (list->pw.pw_uid == uid) return &(list->pw);
+
+ list = xmalloc(512);
+ list->next = pwuidbuf;
+
+ errno = getpwuid_r(uid, &list->pw, sizeof(*list)+(char *)list,
+ 512-sizeof(*list), &temp);
+ if (!temp) {
+ free(list);
+
+ return 0;
+ }
+ pwuidbuf = list;
+
+ return &list->pw;
+}
+
+// Return cached passwd entries.
+struct group *bufgetgrgid(gid_t gid)
+{
+ struct grgidbuf_list {
+ struct grgidbuf_list *next;
+ struct group gr;
+ } *list;
+ struct group *temp;
+ static struct grgidbuf_list *grgidbuf;
+
+ for (list = grgidbuf; list; list = list->next)
+ if (list->gr.gr_gid == gid) return &(list->gr);
+
+ list = xmalloc(512);
+ list->next = grgidbuf;
+
+ errno = getgrgid_r(gid, &list->gr, sizeof(*list)+(char *)list,
+ 512-sizeof(*list), &temp);
+ if (!temp) {
+ free(list);
+
+ return 0;
+ }
+ grgidbuf = list;
+
+ return &list->gr;
+}
+
+// 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<1) return 0;
+ buf[len] = 0;
+
+ return len;
+}
+
+int readlink0(char *path, char *buf, int len)
+{
+ return readlinkat0(AT_FDCWD, path, buf, len);
+}
+
+// Do regex matching handling embedded NUL bytes in string (hence extra len
+// argument). Note that neither the pattern nor the match can currently include
+// NUL bytes (even with wildcards) and string must be null terminated at
+// string[len]. But this can find a match after the first NUL.
+int regexec0(regex_t *preg, char *string, long len, int nmatch,
+ regmatch_t pmatch[], int eflags)
+{
+ char *s = string;
+
+ for (;;) {
+ long ll = 0;
+ int rc;
+
+ while (len && !*s) {
+ s++;
+ len--;
+ }
+ while (s[ll] && ll<len) ll++;
+
+ rc = regexec(preg, s, nmatch, pmatch, eflags);
+ if (!rc) {
+ for (rc = 0; rc<nmatch && pmatch[rc].rm_so!=-1; rc++) {
+ pmatch[rc].rm_so += s-string;
+ pmatch[rc].rm_eo += s-string;
+ }
+
+ return 0;
+ }
+ if (ll==len) return rc;
+
+ s += ll;
+ len -= ll;
+ }
+}
+
+// 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.
+void do_lines(int fd, void (*call)(char **pline, long len))
+{
+ FILE *fp = fd ? xfdopen(fd, "r") : stdin;
+
+ for (;;) {
+ char *line = 0;
+ ssize_t len;
+
+ len = getline(&line, (void *)&len, fp);
+ if (len > 0) {
+ call(&line, len);
+ if (line == (void *)1) break;
+ free(line);
+ } else break;
+ }
+
+ if (fd) fclose(fp);
+}