ps: fix overflow in USER and VSZ columns
[oweals/busybox.git] / libbb / xfuncs.c
index d22cd279a891b9643415fc3957f6c409262de180..6d50bf9bc71ca6bec082ebbfbd63540470dc089a 100644 (file)
@@ -9,7 +9,7 @@
  * Licensed under GPL version 2, see file LICENSE in this tarball for details.
  */
 
-#include "busybox.h"
+#include "libbb.h"
 
 /* All the functions starting with "x" call bb_error_msg_and_die() if they
  * fail, so callers never need to check for errors.  If it returned, it
  * Since dmalloc's prototypes overwrite the impls here as they are
  * included after these prototypes in libbb.h, all is well.
  */
+// Warn if we can't allocate size bytes of memory.
+void *malloc_or_warn(size_t size)
+{
+       void *ptr = malloc(size);
+       if (ptr == NULL && size != 0)
+               bb_error_msg(bb_msg_memory_exhausted);
+       return ptr;
+}
+
 // Die if we can't allocate size bytes of memory.
 void *xmalloc(size_t size)
 {
@@ -57,7 +66,7 @@ char * xstrdup(const char *s)
        if (s == NULL)
                return NULL;
 
-       t = strdup (s);
+       t = strdup(s);
 
        if (t == NULL)
                bb_error_msg_and_die(bb_msg_memory_exhausted);
@@ -69,100 +78,152 @@ char * xstrdup(const char *s)
 // the (possibly truncated to length n) string into it.
 char * xstrndup(const char *s, int n)
 {
+       int m;
        char *t;
 
        if (ENABLE_DEBUG && s == NULL)
                bb_error_msg_and_die("xstrndup bug");
 
-       t = xmalloc(++n);
+       /* We can just xmalloc(n+1) and strncpy into it, */
+       /* but think about xstrndup("abc", 10000) wastage! */
+       m = n;
+       t = (char*) s;
+       while (m) {
+               if (!*t) break;
+               m--;
+               t++;
+       }
+       n -= m;
+       t = xmalloc(n + 1);
+       t[n] = '\0';
 
-       return safe_strncpy(t,s,n);
+       return memcpy(t, s, n);
 }
 
 // Die if we can't open a file and return a FILE * to it.
 // Notice we haven't got xfread(), This is for use with fscanf() and friends.
 FILE *xfopen(const char *path, const char *mode)
 {
-       FILE *fp;
-       if ((fp = fopen(path, mode)) == NULL)
-               bb_perror_msg_and_die("%s", path);
+       FILE *fp = fopen(path, mode);
+       if (fp == NULL)
+               bb_perror_msg_and_die("can't open '%s'", path);
        return fp;
 }
 
-// Die if we can't open an existing file and return an fd.
-int xopen(const char *pathname, int flags)
+// Die if we can't open a file and return a fd.
+int xopen3(const char *pathname, int flags, int mode)
 {
-       if (ENABLE_DEBUG && (flags & O_CREAT))
-               bb_error_msg_and_die("xopen() with O_CREAT");
+       int ret;
 
-       return xopen3(pathname, flags, 0777);
+       ret = open(pathname, flags, mode);
+       if (ret < 0) {
+               bb_perror_msg_and_die("can't open '%s'", pathname);
+       }
+       return ret;
 }
 
-// Die if we can't open a new file and return an fd.
-int xopen3(const char *pathname, int flags, int mode)
+// Die if we can't open an existing file and return a fd.
+int xopen(const char *pathname, int flags)
+{
+       return xopen3(pathname, flags, 0666);
+}
+
+// Warn if we can't open a file and return a fd.
+int open3_or_warn(const char *pathname, int flags, int mode)
 {
        int ret;
 
        ret = open(pathname, flags, mode);
        if (ret < 0) {
-               bb_perror_msg_and_die("%s", pathname);
+               bb_perror_msg("can't open '%s'", pathname);
        }
        return ret;
 }
 
-// Die with an error message if we can't read the entire buffer.
-void xread(int fd, void *buf, size_t count)
+// Warn if we can't open a file and return a fd.
+int open_or_warn(const char *pathname, int flags)
 {
-       while (count) {
-               ssize_t size = safe_read(fd, buf, count);
-               if (size < 1)
-                       bb_error_msg_and_die("short read");
-               count -= size;
-               buf = ((char *) buf) + size;
-       }
+       return open3_or_warn(pathname, flags, 0666);
 }
 
-// Die with an error message if we can't write the entire buffer.
-void xwrite(int fd, void *buf, size_t count)
+void xpipe(int filedes[2])
 {
-       while (count) {
-               ssize_t size = safe_write(fd, buf, count);
-               if (size < 1)
-                       bb_error_msg_and_die("short write");
-               count -= size;
-               buf = ((char *) buf) + size;
-       }
+       if (pipe(filedes))
+               bb_perror_msg_and_die("can't create pipe");
 }
 
-// Die with an error message if we can't lseek to the right spot.
-void xlseek(int fd, off_t offset, int whence)
+void xunlink(const char *pathname)
+{
+       if (unlink(pathname))
+               bb_perror_msg_and_die("can't remove file '%s'", pathname);
+}
+
+// Turn on nonblocking I/O on a fd
+int ndelay_on(int fd)
+{
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);
+}
+
+int close_on_exec_on(int fd)
 {
-       if (offset != lseek(fd, offset, whence))
-               bb_error_msg_and_die("lseek");
+        return fcntl(fd, F_SETFD, FD_CLOEXEC);
 }
 
-// Die with an error message if we can't read one character.
-unsigned char xread_char(int fd)
+int ndelay_off(int fd)
 {
-       char tmp;
+       return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) & ~O_NONBLOCK);
+}
 
-       xread(fd, &tmp, 1);
+void xdup2(int from, int to)
+{
+       if (dup2(from, to) != to)
+               bb_perror_msg_and_die("can't duplicate file descriptor");
+}
 
-       return tmp;
+// "Renumber" opened fd
+void xmove_fd(int from, int to)
+{
+       if (from == to)
+               return;
+       xdup2(from, to);
+       close(from);
 }
 
-// Die with supplied error message if this FILE * has ferror set.
-void xferror(FILE *fp, const char *fn)
+// Die with an error message if we can't write the entire buffer.
+void xwrite(int fd, const void *buf, size_t count)
+{
+       if (count) {
+               ssize_t size = full_write(fd, buf, count);
+               if (size != count)
+                       bb_error_msg_and_die("short write");
+       }
+}
+
+// Die with an error message if we can't lseek to the right spot.
+off_t xlseek(int fd, off_t offset, int whence)
+{
+       off_t off = lseek(fd, offset, whence);
+       if (off == (off_t)-1) {
+               if (whence == SEEK_SET)
+                       bb_perror_msg_and_die("lseek(%"OFF_FMT"u)", offset);
+               bb_perror_msg_and_die("lseek");
+       }
+       return off;
+}
+
+// Die with supplied filename if this FILE * has ferror set.
+void die_if_ferror(FILE *fp, const char *fn)
 {
        if (ferror(fp)) {
-               bb_error_msg_and_die("%s", fn);
+               /* ferror doesn't set useful errno */
+               bb_error_msg_and_die("%s: I/O error", fn);
        }
 }
 
 // Die with an error message if stdout has ferror set.
-void xferror_stdout(void)
+void die_if_ferror_stdout(void)
 {
-       xferror(stdout, bb_msg_standard_output);
+       die_if_ferror(stdout, bb_msg_standard_output);
 }
 
 // Die with an error message if we have trouble flushing stdout.
@@ -173,85 +234,192 @@ void xfflush_stdout(void)
        }
 }
 
-// This does a fork/exec in one call, using vfork().  Return PID of new child,
-// -1 for failure.  Runs argv[0], searching path if that has no / in it.
-pid_t spawn(char **argv)
+void sig_block(int sig)
 {
-       static int failed;
-       pid_t pid;
-       void *app = ENABLE_FEATURE_SH_STANDALONE_SHELL ? find_applet_by_name(argv[0]) : 0;
-
-       // Be nice to nommu machines.
-       failed = 0;
-       pid = vfork();
-       if (pid < 0) return pid;
-       if (!pid) {
-               execvp(app ? CONFIG_BUSYBOX_EXEC_PATH : *argv, argv);
-
-               // We're sharing a stack with blocked parent, let parent know we failed
-               // and then exit to unblock parent (but don't run atexit() stuff, which
-               // would screw up parent.)
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_BLOCK, &ss, NULL);
+}
 
-               failed = -1;
-               _exit(0);
-       }
-       return failed ? failed : pid;
+void sig_unblock(int sig)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigaddset(&ss, sig);
+       sigprocmask(SIG_UNBLOCK, &ss, NULL);
 }
 
-// Die with an error message if we can't spawn a child process.
-pid_t xspawn(char **argv)
+#if 0
+void sig_blocknone(void)
 {
-       pid_t pid = spawn(argv);
-       if (pid < 0) bb_perror_msg_and_die("%s", *argv);
-       return pid;
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigprocmask(SIG_SETMASK, &ss, NULL);
 }
+#endif
 
-// Wait for the specified child PID to exit, returning child's error return.
-int wait4pid(int pid)
+void sig_catch(int sig, void (*f)(int))
 {
-       int status;
+       struct sigaction sa;
+       sa.sa_handler = f;
+       sa.sa_flags = 0;
+       sigemptyset(&sa.sa_mask);
+       sigaction(sig, &sa, NULL);
+}
 
-       if (pid == -1 || waitpid(pid, &status, 0) == -1) return -1;
-       if (WIFEXITED(status)) return WEXITSTATUS(status);
-       if (WIFSIGNALED(status)) return WTERMSIG(status);
-       return 0;
+void sig_pause(void)
+{
+       sigset_t ss;
+       sigemptyset(&ss);
+       sigsuspend(&ss);
 }
 
+
 void xsetenv(const char *key, const char *value)
 {
        if (setenv(key, value, 1))
                bb_error_msg_and_die(bb_msg_memory_exhausted);
 }
 
-// Convert unsigned integer to ascii, writing into supplied buffer.  A
-// truncated result is always null terminated (unless buflen is 0), and
-// contains the first few digits of the result ala strncpy.
-void utoa_to_buf(unsigned n, char *buf, unsigned buflen)
+/* Converts unsigned long long value into compact 4-char
+ * representation. Examples: "1234", "1.2k", " 27M", "123T"
+ * String is not terminated (buf[4] is untouched) */
+void smart_ulltoa4(unsigned long long ul, char buf[5], const char *scale)
 {
-       int i, out = 0;
-       if (buflen) {
-               for (i=1000000000; i; i/=10) {
-                       int res = n/i;
+       const char *fmt;
+       char c;
+       unsigned v, u, idx = 0;
+
+       if (ul > 9999) { // do not scale if 9999 or less
+               ul *= 10;
+               do {
+                       ul /= 1024;
+                       idx++;
+               } while (ul >= 10000);
+       }
+       v = ul; // ullong divisions are expensive, avoid them
+
+       fmt = " 123456789";
+       u = v / 10;
+       v = v % 10;
+       if (!idx) {
+               // 9999 or less: use "1234" format
+               // u is value/10, v is last digit
+               c = buf[0] = " 123456789"[u/100];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[1] = fmt[u/10%10];
+               if (c != ' ') fmt = "0123456789";
+               buf[2] = fmt[u%10];
+               buf[3] = "0123456789"[v];
+       } else {
+               // u is value, v is 1/10ths (allows for 9.2M format)
+               if (u >= 10) {
+                       // value is >= 10: use "123M', " 12M" formats
+                       c = buf[0] = " 123456789"[u/100];
+                       if (c != ' ') fmt = "0123456789";
+                       v = u % 10;
+                       u = u / 10;
+                       buf[1] = fmt[u%10];
+               } else {
+                       // value is < 10: use "9.2M" format
+                       buf[0] = "0123456789"[u];
+                       buf[1] = '.';
+               }
+               buf[2] = "0123456789"[v];
+               buf[3] = scale[idx]; /* typically scale = " kmgt..." */
+       }
+}
+
+/* Converts unsigned long long value into compact 5-char representation.
+ * String is not terminated (buf[5] is untouched) */
+void smart_ulltoa5(unsigned long long ul, char buf[6], const char *scale)
+{
+       const char *fmt;
+       char c;
+       unsigned v, u, idx = 0;
+
+       if (ul > 99999) { // do not scale if 99999 or less
+               ul *= 10;
+               do {
+                       ul /= 1024;
+                       idx++;
+               } while (ul >= 100000);
+       }
+       v = ul; // ullong divisions are expensive, avoid them
+
+       fmt = " 123456789";
+       u = v / 10;
+       v = v % 10;
+       if (!idx) {
+               // 99999 or less: use "12345" format
+               // u is value/10, v is last digit
+               c = buf[0] = " 123456789"[u/1000];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[1] = fmt[u/100%10];
+               if (c != ' ') fmt = "0123456789";
+               c = buf[2] = fmt[u/10%10];
+               if (c != ' ') fmt = "0123456789";
+               buf[3] = fmt[u%10];
+               buf[4] = "0123456789"[v];
+       } else {
+               // value has been scaled into 0..9999.9 range
+               // u is value, v is 1/10ths (allows for 92.1M format)
+               if (u >= 100) {
+                       // value is >= 100: use "1234M', " 123M" formats
+                       c = buf[0] = " 123456789"[u/1000];
+                       if (c != ' ') fmt = "0123456789";
+                       c = buf[1] = fmt[u/100%10];
+                       if (c != ' ') fmt = "0123456789";
+                       v = u % 10;
+                       u = u / 10;
+                       buf[2] = fmt[u%10];
+               } else {
+                       // value is < 100: use "92.1M" format
+                       c = buf[0] = " 123456789"[u/10];
+                       if (c != ' ') fmt = "0123456789";
+                       buf[1] = fmt[u%10];
+                       buf[2] = '.';
+               }
+               buf[3] = "0123456789"[v];
+               buf[4] = scale[idx]; /* typically scale = " kmgt..." */
+       }
+}
 
-                       if ((res || out || i == 1) && --buflen>0) {
+
+// Convert unsigned integer to ascii, writing into supplied buffer.
+// A truncated result contains the first few digits of the result ala strncpy.
+// Returns a pointer past last generated digit, does _not_ store NUL.
+void BUG_sizeof_unsigned_not_4(void);
+char *utoa_to_buf(unsigned n, char *buf, unsigned buflen)
+{
+       unsigned i, out, res;
+       if (sizeof(unsigned) != 4)
+               BUG_sizeof_unsigned_not_4();
+       if (buflen) {
+               out = 0;
+               for (i = 1000000000; i; i /= 10) {
+                       res = n / i;
+                       if (res || out || i == 1) {
+                               if (!--buflen) break;
                                out++;
                                n -= res*i;
                                *buf++ = '0' + res;
                        }
                }
-               *buf = 0;
        }
+       return buf;
 }
 
 // Convert signed integer to ascii, like utoa_to_buf()
-void itoa_to_buf(int n, char *buf, unsigned buflen)
+char *itoa_to_buf(int n, char *buf, unsigned buflen)
 {
        if (buflen && n<0) {
                n = -n;
                *buf++ = '-';
                buflen--;
        }
-       utoa_to_buf((unsigned)n, buf, buflen);
+       return utoa_to_buf((unsigned)n, buf, buflen);
 }
 
 // The following two functions use a static buffer, so calling either one a
@@ -266,7 +434,7 @@ static char local_buf[12];
 // Convert unsigned integer to ascii using a static buffer (returned).
 char *utoa(unsigned n)
 {
-       utoa_to_buf(n, local_buf, sizeof(local_buf));
+       *(utoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
 
        return local_buf;
 }
@@ -274,23 +442,36 @@ char *utoa(unsigned n)
 // Convert signed integer to ascii using a static buffer (returned).
 char *itoa(int n)
 {
-       itoa_to_buf(n, local_buf, sizeof(local_buf));
+       *(itoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0';
 
        return local_buf;
 }
 
+// Emit a string of hex representation of bytes
+char *bin2hex(char *p, const char *cp, int count)
+{
+       while (count) {
+               unsigned char c = *cp++;
+               /* put lowercase hex digits */
+               *p++ = 0x20 | bb_hexdigits_upcase[c >> 4];
+               *p++ = 0x20 | bb_hexdigits_upcase[c & 0xf];
+               count--;
+       }
+       return p;
+}
+
 // Die with an error message if we can't set gid.  (Because resource limits may
 // limit this user to a given number of processes, and if that fills up the
 // setgid() will fail and we'll _still_be_root_, which is bad.)
 void xsetgid(gid_t gid)
 {
-       if (setgid(gid)) bb_error_msg_and_die("setgid");
+       if (setgid(gid)) bb_perror_msg_and_die("setgid");
 }
 
-// Die with an error message if we cant' set uid.  (See xsetgid() for why.)
+// Die with an error message if we can't set uid.  (See xsetgid() for why.)
 void xsetuid(uid_t uid)
 {
-       if (setuid(uid)) bb_error_msg_and_die("setuid");
+       if (setuid(uid)) bb_perror_msg_and_die("setuid");
 }
 
 // Return how long the file at fd is, if there's any way to determine it.
@@ -303,6 +484,8 @@ off_t fdlength(int fd)
 
        if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512;
 
+       // FIXME: explain why lseek(SEEK_END) is not used here!
+
        // If not, do a binary search for the last location we can read.  (Some
        // block devices don't do BLKGETSIZE right.)
 
@@ -313,7 +496,7 @@ off_t fdlength(int fd)
 
                // If we can read from the current location, it's bigger.
 
-               if (lseek(fd, pos, 0)>=0 && safe_read(fd, &temp, 1)==1) {
+               if (lseek(fd, pos, SEEK_SET)>=0 && safe_read(fd, &temp, 1)==1) {
                        if (bottom == top) bottom = top = (top+1) * 2;
                        else bottom = pos;
 
@@ -331,6 +514,14 @@ off_t fdlength(int fd)
        return pos + 1;
 }
 
+int bb_putchar(int ch)
+{
+       /* time.c needs putc(ch, stdout), not putchar(ch).
+        * it does "stdout = stderr;", but then glibc's putchar()
+        * doesn't work as expected. bad glibc, bad */
+       return putc(ch, stdout);
+}
+
 // Die with an error message if we can't malloc() enough space and do an
 // sprintf() into that space.
 char *xasprintf(const char *format, ...)
@@ -355,17 +546,52 @@ char *xasprintf(const char *format, ...)
        va_end(p);
 #endif
 
-       if (r < 0) bb_error_msg_and_die(bb_msg_memory_exhausted);
+       if (r < 0)
+               bb_error_msg_and_die(bb_msg_memory_exhausted);
        return string_ptr;
 }
 
+#if 0 /* If we will ever meet a libc which hasn't [f]dprintf... */
+int fdprintf(int fd, const char *format, ...)
+{
+       va_list p;
+       int r;
+       char *string_ptr;
+
+#if 1
+       // GNU extension
+       va_start(p, format);
+       r = vasprintf(&string_ptr, format, p);
+       va_end(p);
+#else
+       // Bloat for systems that haven't got the GNU extension.
+       va_start(p, format);
+       r = vsnprintf(NULL, 0, format, p) + 1;
+       va_end(p);
+       string_ptr = malloc(r);
+       if (string_ptr) {
+               va_start(p, format);
+               r = vsnprintf(string_ptr, r, format, p);
+               va_end(p);
+       }
+#endif
+
+       if (r >= 0) {
+               full_write(fd, string_ptr, r);
+               free(string_ptr);
+       }
+       return r;
+}
+#endif
+
 // Die with an error message if we can't copy an entire FILE * to stdout, then
 // close that file.
 void xprint_and_close_file(FILE *file)
 {
+       fflush(stdout);
        // copyfd outputs error messages for us.
        if (bb_copyfd_eof(fileno(file), 1) == -1)
-               exit(xfunc_error_retval);
+               xfunc_die();
 
        fclose(file);
 }
@@ -382,10 +608,9 @@ DIR *warn_opendir(const char *path)
 {
        DIR *dp;
 
-       if ((dp = opendir(path)) == NULL) {
-               bb_perror_msg("unable to open `%s'", path);
-               return NULL;
-       }
+       dp = opendir(path);
+       if (!dp)
+               bb_perror_msg("can't open '%s'", path);
        return dp;
 }
 
@@ -394,25 +619,29 @@ DIR *xopendir(const char *path)
 {
        DIR *dp;
 
-       if ((dp = opendir(path)) == NULL)
-               bb_perror_msg_and_die("unable to open `%s'", path);
+       dp = opendir(path);
+       if (!dp)
+               bb_perror_msg_and_die("can't open '%s'", path);
        return dp;
 }
 
-#ifndef BB_NOMMU
-// Die with an error message if we can't daemonize.
-void xdaemon(int nochdir, int noclose)
-{
-       if (daemon(nochdir, noclose)) bb_perror_msg_and_die("daemon");
-}
-#endif
-
 // Die with an error message if we can't open a new socket.
 int xsocket(int domain, int type, int protocol)
 {
        int r = socket(domain, type, protocol);
 
-       if (r < 0) bb_perror_msg_and_die("socket");
+       if (r < 0) {
+               /* Hijack vaguely related config option */
+#if ENABLE_VERBOSE_RESOLUTION_ERRORS
+               const char *s = "INET";
+               if (domain == AF_PACKET) s = "PACKET";
+               if (domain == AF_NETLINK) s = "NETLINK";
+USE_FEATURE_IPV6(if (domain == AF_INET6) s = "INET6";)
+               bb_perror_msg_and_die("socket(AF_%s)", s);
+#else
+               bb_perror_msg_and_die("socket");
+#endif
+       }
 
        return r;
 }
@@ -429,31 +658,126 @@ void xlisten(int s, int backlog)
        if (listen(s, backlog)) bb_perror_msg_and_die("listen");
 }
 
+/* Die with an error message if sendto failed.
+ * Return bytes sent otherwise  */
+ssize_t xsendto(int s, const  void *buf, size_t len, const struct sockaddr *to,
+                               socklen_t tolen)
+{
+       ssize_t ret = sendto(s, buf, len, 0, to, tolen);
+       if (ret < 0) {
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       close(s);
+               bb_perror_msg_and_die("sendto");
+       }
+       return ret;
+}
+
 // xstat() - a stat() which dies on failure with meaningful error message
-void xstat(char *name, struct stat *stat_buf)
+void xstat(const char *name, struct stat *stat_buf)
 {
        if (stat(name, stat_buf))
                bb_perror_msg_and_die("can't stat '%s'", name);
 }
 
+// selinux_or_die() - die if SELinux is disabled.
+void selinux_or_die(void)
+{
+#if ENABLE_SELINUX
+       int rc = is_selinux_enabled();
+       if (rc == 0) {
+               bb_error_msg_and_die("SELinux is disabled");
+       } else if (rc < 0) {
+               bb_error_msg_and_die("is_selinux_enabled() failed");
+       }
+#else
+       bb_error_msg_and_die("SELinux support is disabled");
+#endif
+}
+
 /* It is perfectly ok to pass in a NULL for either width or for
- *  * height, in which case that value will not be set.  */
+ * height, in which case that value will not be set.  */
 int get_terminal_width_height(int fd, int *width, int *height)
 {
        struct winsize win = { 0, 0, 0, 0 };
        int ret = ioctl(fd, TIOCGWINSZ, &win);
-       if (!win.ws_row) {
-               char *s = getenv("LINES");
-               if (s) win.ws_row = atoi(s);
+
+       if (height) {
+               if (!win.ws_row) {
+                       char *s = getenv("LINES");
+                       if (s) win.ws_row = atoi(s);
+               }
+               if (win.ws_row <= 1 || win.ws_row >= 30000)
+                       win.ws_row = 24;
+               *height = (int) win.ws_row;
+       }
+
+       if (width) {
+               if (!win.ws_col) {
+                       char *s = getenv("COLUMNS");
+                       if (s) win.ws_col = atoi(s);
+               }
+               if (win.ws_col <= 1 || win.ws_col >= 30000)
+                       win.ws_col = 80;
+               *width = (int) win.ws_col;
        }
-       if (win.ws_row <= 1) win.ws_row = 24;
-       if (!win.ws_col) {
-               char *s = getenv("COLUMNS");
-               if (s) win.ws_col = atoi(s);
+
+       return ret;
+}
+
+void ioctl_or_perror_and_die(int fd, int request, void *argp, const char *fmt,...)
+{
+       va_list p;
+
+       if (ioctl(fd, request, argp) < 0) {
+               va_start(p, fmt);
+               bb_verror_msg(fmt, p, strerror(errno));
+               /* xfunc_die can actually longjmp, so be nice */
+               va_end(p);
+               xfunc_die();
+       }
+}
+
+int ioctl_or_perror(int fd, int request, void *argp, const char *fmt,...)
+{
+       va_list p;
+       int ret = ioctl(fd, request, argp);
+
+       if (ret < 0) {
+               va_start(p, fmt);
+               bb_verror_msg(fmt, p, strerror(errno));
+               va_end(p);
        }
-       if (win.ws_col <= 1) win.ws_col = 80;
-       if (height) *height = (int) win.ws_row;
-       if (width) *width = (int) win.ws_col;
+       return ret;
+}
+
+#if ENABLE_IOCTL_HEX2STR_ERROR
+int bb_ioctl_or_warn(int fd, int request, void *argp, const char *ioctl_name)
+{
+       int ret;
+
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_simple_perror_msg(ioctl_name);
+       return ret;
+}
+void bb_xioctl(int fd, int request, void *argp, const char *ioctl_name)
+{
+       if (ioctl(fd, request, argp) < 0)
+               bb_simple_perror_msg_and_die(ioctl_name);
+}
+#else
+int bb_ioctl_or_warn(int fd, int request, void *argp)
+{
+       int ret;
 
+       ret = ioctl(fd, request, argp);
+       if (ret < 0)
+               bb_perror_msg("ioctl %#x failed", request);
        return ret;
 }
+void bb_xioctl(int fd, int request, void *argp)
+{
+       if (ioctl(fd, request, argp) < 0)
+               bb_perror_msg_and_die("ioctl %#x failed", request);
+}
+#endif