summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLaMont Jones <lamont@debian.org>2010-05-14 05:07:14 -0600
committerLaMont Jones <lamont@debian.org>2010-05-14 05:07:14 -0600
commit3cbdfd84fde6159d7778e70618fbbc8df219cafe (patch)
treee545f7d9e6b73426f590f3195f1d458f5c52d518 /lib
parent321ab951929f2ed871751ba8979d1136d036485a (diff)
parentab978962b15489b62f41a2ec0de4c44c9e25df6e (diff)
downloadutil-linux-old-3cbdfd84fde6159d7778e70618fbbc8df219cafe.tar.gz
Merge remote branch 'origin/master' into HEAD
Conflicts: lib/fsprobe.c mount/lomount.c
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am5
-rw-r--r--lib/blkdev.c117
-rw-r--r--lib/canonicalize.c1
-rw-r--r--lib/fsprobe.c63
-rw-r--r--lib/mangle.c116
-rw-r--r--lib/mbsalign.c288
-rw-r--r--lib/strtosize.c148
7 files changed, 686 insertions, 52 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5600d18c..0f008b30 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,11 +2,14 @@ include $(top_srcdir)/config/include-Makefile.am
AM_CPPFLAGS += -DTEST_PROGRAM
-noinst_PROGRAMS = test_blkdev test_ismounted test_wholedisk
+noinst_PROGRAMS = test_blkdev test_ismounted test_wholedisk test_mangle \
+ test_strtosize
test_blkdev_SOURCES = blkdev.c
test_ismounted_SOURCES = ismounted.c
test_wholedisk_SOURCES = wholedisk.c
+test_mangle_SOURCES = mangle.c
+test_strtosize_SOURCES = strtosize.c
if LINUX
test_blkdev_SOURCES += linux_version.c
diff --git a/lib/blkdev.c b/lib/blkdev.c
index 1ca4548b..7d00324d 100644
--- a/lib/blkdev.c
+++ b/lib/blkdev.c
@@ -1,7 +1,24 @@
#include <sys/types.h>
+#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
+#include <stdint.h>
+
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+
+#ifdef HAVE_SYS_DISK_H
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h> /* for LIST_HEAD */
+#endif
+#include <sys/disk.h>
+#endif
#include "blkdev.h"
#include "linux_version.h"
@@ -19,14 +36,23 @@ blkdev_valid_offset (int fd, off_t offset) {
off_t
blkdev_find_size (int fd) {
- off_t high, low;
+ uintmax_t high, low = 0;
+
+ for (high = 1024; blkdev_valid_offset (fd, high); ) {
+ if (high == UINTMAX_MAX)
+ return -1;
- low = 0;
- for (high = 1; high > 0 && blkdev_valid_offset (fd, high); high *= 2)
low = high;
+
+ if (high >= UINTMAX_MAX/2)
+ high = UINTMAX_MAX;
+ else
+ high *= 2;
+ }
+
while (low < high - 1)
{
- const off_t mid = (low + high) / 2;
+ uintmax_t mid = (low + high) / 2;
if (blkdev_valid_offset (fd, mid))
low = mid;
@@ -41,17 +67,26 @@ blkdev_find_size (int fd) {
int
blkdev_get_size(int fd, unsigned long long *bytes)
{
- /* TODO: use stat as well */
+#ifdef DKIOCGETBLOCKCOUNT
+ /* Apple Darwin */
+ if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
+ *bytes <<= 9;
+ return 0;
+ }
+#endif
#ifdef BLKGETSIZE64
+ {
#ifdef __linux__
- int ver = get_linux_version();
- /* kernels 2.4.15-2.4.17, had a broken BLKGETSIZE64 */
- if (ver >= KERNEL_VERSION (2,6,0) ||
- (ver >= KERNEL_VERSION (2,4,18) && ver < KERNEL_VERSION (2,5,0)))
+ int ver = get_linux_version();
+
+ /* kernels 2.4.15-2.4.17, had a broken BLKGETSIZE64 */
+ if (ver >= KERNEL_VERSION (2,6,0) ||
+ (ver >= KERNEL_VERSION (2,4,18) && ver < KERNEL_VERSION (2,5,0)))
#endif
- if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
- return 0;
+ if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
+ return 0;
+ }
#endif /* BLKGETSIZE64 */
#ifdef BLKGETSIZE
@@ -64,9 +99,67 @@ blkdev_get_size(int fd, unsigned long long *bytes)
}
}
- return -1;
#endif /* BLKGETSIZE */
+#ifdef DIOCGMEDIASIZE
+ /* FreeBSD */
+ if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
+ return 0
+#endif
+
+#ifdef FDGETPRM
+ {
+ struct floppy_struct this_floppy;
+
+ if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
+ *bytes = this_floppy.size << 9;
+ return 0;
+ }
+ }
+#endif /* FDGETPRM */
+
+#ifdef HAVE_SYS_DISKLABEL_H
+ {
+ /*
+ * This code works for FreeBSD 4.11 i386, except for the full device
+ * (such as /dev/ad0). It doesn't work properly for newer FreeBSD
+ * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE
+ * above however.
+ *
+ * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw,
+ * character) devices, so we need to check for S_ISCHR, too.
+ */
+ int part = -1;
+ struct disklabel lab;
+ struct partition *pp;
+ char ch;
+ struct stat st;
+
+ if ((fstat(fd, &st) >= 0) &&
+ (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
+ part = st.st_rdev & 7;
+
+ if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) {
+ pp = &lab.d_partitions[part];
+ if (pp->p_size) {
+ *bytes = pp->p_size << 9;
+ return 0;
+ }
+ }
+ }
+#endif /* HAVE_SYS_DISKLABEL_H */
+
+ {
+ struct stat st;
+
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+ *bytes = st.st_size;
+ return 0;
+ }
+ if (!S_ISBLK(st.st_mode))
+ return -1;
+ }
+
*bytes = blkdev_find_size(fd);
return 0;
}
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index e54cf0c0..29b3f259 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -24,7 +24,6 @@
#include <string.h>
#include <ctype.h>
#include <unistd.h>
-#include <string.h>
#include <errno.h>
#include <stdlib.h>
diff --git a/lib/fsprobe.c b/lib/fsprobe.c
index bc3c13c6..e92e03fc 100644
--- a/lib/fsprobe.c
+++ b/lib/fsprobe.c
@@ -20,34 +20,6 @@
static blkid_cache blcache;
-#ifdef HAVE_LIBBLKID_INTERNAL
-/* ask kernel developers why we need such ugly open() method... */
-static int
-open_device(const char *devname)
-{
- int retries = 0;
-
- do {
- int fd = open(devname, O_RDONLY);
- if (fd >= 0)
- return fd;
-#ifdef ENOMEDIUM
- /* ENOMEDIUM is Linux-only */
- if (errno != ENOMEDIUM)
- break;
-#else
- break;
-#endif
- if (retries >= CRDOM_NOMEDIUM_RETRIES)
- break;
- ++retries;
- sleep(3);
- } while(1);
-
- return -1;
-}
-#endif
-
/*
* Parses NAME=value, returns -1 on parse error, 0 success. The success is also
* when the 'spec' doesn't contain name=value pair (because the spec could be
@@ -122,14 +94,14 @@ fsprobe_exit(void)
* probing interface
*/
static char *
-fsprobe_get_value(const char *name, const char *devname)
+fsprobe_get_value(const char *name, const char *devname, int *ambi)
{
- int fd;
+ int fd, rc;
const char *data = NULL;
if (!devname || !name)
return NULL;
- fd = open_device(devname);
+ fd = open(devname, O_RDONLY);
if (fd < 0)
return NULL;
if (!blprobe)
@@ -144,10 +116,11 @@ fsprobe_get_value(const char *name, const char *devname)
blkid_probe_set_superblocks_flags(blprobe,
BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | BLKID_SUBLKS_TYPE);
- if (blkid_do_safeprobe(blprobe))
- goto done;
- if (blkid_probe_lookup_value(blprobe, name, &data, NULL))
- goto done;
+ rc = blkid_do_safeprobe(blprobe);
+ if (ambi)
+ *ambi = rc == -2 ? 1 : 0; /* ambivalent probing result */
+ if (!rc)
+ blkid_probe_lookup_value(blprobe, name, &data, NULL);
done:
close(fd);
return data ? strdup((char *) data) : NULL;
@@ -156,19 +129,25 @@ done:
char *
fsprobe_get_label_by_devname(const char *devname)
{
- return fsprobe_get_value("LABEL", devname);
+ return fsprobe_get_value("LABEL", devname, NULL);
}
char *
fsprobe_get_uuid_by_devname(const char *devname)
{
- return fsprobe_get_value("UUID", devname);
+ return fsprobe_get_value("UUID", devname, NULL);
}
char *
fsprobe_get_fstype_by_devname(const char *devname)
{
- return fsprobe_get_value("TYPE", devname);
+ return fsprobe_get_value("TYPE", devname, NULL);
+}
+
+char *
+fsprobe_get_fstype_by_devname_ambi(const char *devname, int *ambi)
+{
+ return fsprobe_get_value("TYPE", devname, ambi);
}
char *
@@ -237,6 +216,14 @@ fsprobe_get_fstype_by_devname(const char *devname)
}
char *
+fsprobe_get_fstype_by_devname_ambi(const char *devname, int *ambi)
+{
+ if (ambi)
+ *ambi = 0;
+ return fsprobe_get_fstype_by_devname(devname);
+}
+
+char *
fsprobe_get_label_by_devname(const char *devname)
{
if (!blcache)
diff --git a/lib/mangle.c b/lib/mangle.c
new file mode 100644
index 00000000..766d479d
--- /dev/null
+++ b/lib/mangle.c
@@ -0,0 +1,116 @@
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ *
+ * Based on code from mount(8).
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mangle.h"
+
+#define isoctal(a) (((a) & ~7) == '0')
+
+static unsigned char need_escaping[] = { ' ', '\t', '\n', '\\' };
+
+char *mangle(const char *s)
+{
+ char *ss, *sp;
+ int n;
+
+ n = strlen(s);
+ ss = sp = malloc(4*n+1);
+ if (!sp)
+ return NULL;
+ while(1) {
+ for (n = 0; n < sizeof(need_escaping); n++) {
+ if (*s == need_escaping[n]) {
+ *sp++ = '\\';
+ *sp++ = '0' + ((*s & 0300) >> 6);
+ *sp++ = '0' + ((*s & 070) >> 3);
+ *sp++ = '0' + (*s & 07);
+ goto next;
+ }
+ }
+ *sp++ = *s;
+ if (*s == 0)
+ break;
+ next:
+ s++;
+ }
+ return ss;
+}
+
+void unmangle_to_buffer(char *s, char *buf, size_t len)
+{
+ size_t sz = 0;
+
+ while(*s && sz < len - 1) {
+ if (*s == '\\' && sz + 4 < len - 1 && isoctal(s[1]) &&
+ isoctal(s[2]) && isoctal(s[3])) {
+
+ *buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+ s += 4;
+ sz += 4;
+ } else {
+ *buf++ = *s++;
+ sz++;
+ }
+ }
+ *buf = '\0';
+}
+
+static inline char *skip_nonspaces(char *s)
+{
+ while (*s && !(*s == ' ' || *s == '\t'))
+ s++;
+ return s;
+}
+
+/*
+ * Returns mallocated buffer or NULL in case of error.
+ */
+char *unmangle(char *s)
+{
+ char *buf, *end;
+ size_t sz;
+
+ end = skip_nonspaces(s);
+ sz = end - s + 1;
+
+ buf = malloc(sz);
+ if (!buf)
+ return NULL;
+
+ unmangle_to_buffer(s, buf, sz);
+ return buf;
+}
+
+#ifdef TEST_PROGRAM
+#include <err.h>
+#include <errno.h>
+int main(int argc, char *argv[])
+{
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s --mangle | --unmangle <string>\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ if (!strcmp(argv[1], "--mangle"))
+ printf("mangled: '%s'\n", mangle(argv[2]));
+
+ else if (!strcmp(argv[1], "--unmangle")) {
+ char *x = unmangle(argv[2]);
+ if (x)
+ printf("unmangled: '%s'\n", x);
+ else
+ err(EXIT_FAILURE, "unmangle failed");
+ }
+
+ return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/lib/mbsalign.c b/lib/mbsalign.c
new file mode 100644
index 00000000..82ffc09c
--- /dev/null
+++ b/lib/mbsalign.c
@@ -0,0 +1,288 @@
+/* Align/Truncate a string in a given screen width
+ Copyright (C) 2009-2010 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Pádraig Brady. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "c.h"
+#include "mbsalign.h"
+#include "widechar.h"
+
+
+#ifdef HAVE_WIDECHAR
+/* Replace non printable chars.
+ Note \t and \n etc. are non printable.
+ Return 1 if replacement made, 0 otherwise. */
+
+static bool
+wc_ensure_printable (wchar_t *wchars)
+{
+ bool replaced = false;
+ wchar_t *wc = wchars;
+ while (*wc)
+ {
+ if (!iswprint ((wint_t) *wc))
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ replaced = true;
+ }
+ wc++;
+ }
+ return replaced;
+}
+
+/* Truncate wchar string to width cells.
+ * Returns number of cells used. */
+
+static size_t
+wc_truncate (wchar_t *wc, size_t width)
+{
+ size_t cells = 0;
+ int next_cells = 0;
+
+ while (*wc)
+ {
+ next_cells = wcwidth (*wc);
+ if (next_cells == -1) /* non printable */
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ next_cells = 1;
+ }
+ if (cells + next_cells > width)
+ break;
+ cells += next_cells;
+ wc++;
+ }
+ *wc = L'\0';
+ return cells;
+}
+
+/* FIXME: move this function to gnulib as it's missing on:
+ OpenBSD 3.8, IRIX 5.3, Solaris 2.5.1, mingw, BeOS */
+
+static int
+rpl_wcswidth (const wchar_t *s, size_t n)
+{
+ int ret = 0;
+
+ while (n-- > 0 && *s != L'\0')
+ {
+ int nwidth = wcwidth (*s++);
+ if (nwidth == -1) /* non printable */
+ return -1;
+ if (ret > (INT_MAX - nwidth)) /* overflow */
+ return -1;
+ ret += nwidth;
+ }
+
+ return ret;
+}
+#endif
+
+/* Truncate multi-byte string to @width and returns number of
+ * bytes of the new string @str, and in @width returns number
+ * of cells.
+ */
+size_t
+mbs_truncate(char *str, size_t *width)
+{
+ size_t bytes = strlen(str);
+#ifdef HAVE_WIDECHAR
+ size_t sz = mbstowcs(NULL, str, 0);
+ wchar_t *wcs = NULL;
+
+ if (sz == (size_t) -1)
+ goto done;
+
+ wcs = malloc((sz + 1) * sizeof(wchar_t));
+ if (!wcs)
+ goto done;
+
+ if (!mbstowcs(wcs, str, sz))
+ goto done;
+ *width = wc_truncate(wcs, *width);
+ bytes = wcstombs(str, wcs, bytes);
+done:
+ free(wcs);
+#else
+ if (*width < bytes)
+ bytes = *width;
+#endif
+ if (bytes >= 0)
+ str[bytes] = '\0';
+ return bytes;
+}
+
+/* Write N_SPACES space characters to DEST while ensuring
+ nothing is written beyond DEST_END. A terminating NUL
+ is always added to DEST.
+ A pointer to the terminating NUL is returned. */
+
+static char*
+mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
+{
+ /* FIXME: Should we pad with "figure space" (\u2007)
+ if non ascii data present? */
+ while (n_spaces-- && (dest < dest_end))
+ *dest++ = ' ';
+ *dest = '\0';
+ return dest;
+}
+
+/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
+ characters; write the result into the DEST_SIZE-byte buffer, DEST.
+ ALIGNMENT specifies whether to left- or right-justify or to center.
+ If SRC requires more than *WIDTH columns, truncate it to fit.
+ When centering, the number of trailing spaces may be one less than the
+ number of leading spaces. The FLAGS parameter is unused at present.
+ Return the length in bytes required for the final result, not counting
+ the trailing NUL. A return value of DEST_SIZE or larger means there
+ wasn't enough space. DEST will be NUL terminated in any case.
+ Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
+ or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
+ Update *WIDTH to indicate how many columns were used before padding. */
+
+size_t
+mbsalign (const char *src, char *dest, size_t dest_size,
+ size_t *width, mbs_align_t align, int flags)
+{
+ size_t ret = -1;
+ size_t src_size = strlen (src) + 1;
+ char *newstr = NULL;
+ wchar_t *str_wc = NULL;
+ const char *str_to_print = src;
+ size_t n_cols = src_size - 1;
+ size_t n_used_bytes = n_cols; /* Not including NUL */
+ size_t n_spaces = 0;
+ bool conversion = false;
+ bool wc_enabled = false;
+
+#ifdef HAVE_WIDECHAR
+ /* In multi-byte locales convert to wide characters
+ to allow easy truncation. Also determine number
+ of screen columns used. */
+ if (MB_CUR_MAX > 1)
+ {
+ size_t src_chars = mbstowcs (NULL, src, 0);
+ if (src_chars == (size_t) -1)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ src_chars += 1; /* make space for NUL */
+ str_wc = malloc (src_chars * sizeof (wchar_t));
+ if (str_wc == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ if (mbstowcs (str_wc, src, src_chars) != 0)
+ {
+ str_wc[src_chars - 1] = L'\0';
+ wc_enabled = true;
+ conversion = wc_ensure_printable (str_wc);
+ n_cols = rpl_wcswidth (str_wc, src_chars);
+ }
+ }
+
+ /* If we transformed or need to truncate the source string
+ then create a modified copy of it. */
+ if (wc_enabled && (conversion || (n_cols > *width)))
+ {
+ if (conversion)
+ {
+ /* May have increased the size by converting
+ \t to \uFFFD for example. */
+ src_size = wcstombs(NULL, str_wc, 0) + 1;
+ }
+ newstr = malloc (src_size);
+ if (newstr == NULL)
+ {
+ if (flags & MBA_UNIBYTE_FALLBACK)
+ goto mbsalign_unibyte;
+ else
+ goto mbsalign_cleanup;
+ }
+ str_to_print = newstr;
+ n_cols = wc_truncate (str_wc, *width);
+ n_used_bytes = wcstombs (newstr, str_wc, src_size);
+ }
+#endif
+
+mbsalign_unibyte:
+
+ if (n_cols > *width) /* Unibyte truncation required. */
+ {
+ n_cols = *width;
+ n_used_bytes = n_cols;
+ }
+
+ if (*width > n_cols) /* Padding required. */
+ n_spaces = *width - n_cols;
+
+ /* indicate to caller how many cells needed (not including padding). */
+ *width = n_cols;
+
+ /* indicate to caller how many bytes needed (not including NUL). */
+ ret = n_used_bytes + (n_spaces * 1);
+
+ /* Write as much NUL terminated output to DEST as possible. */
+ if (dest_size != 0)
+ {
+ char *dest_end = dest + dest_size - 1;
+ size_t start_spaces = n_spaces / 2 + n_spaces % 2;
+ size_t end_spaces = n_spaces / 2;
+
+ switch (align)
+ {
+ case MBS_ALIGN_CENTER:
+ start_spaces = n_spaces / 2 + n_spaces % 2;
+ end_spaces = n_spaces / 2;
+ break;
+ case MBS_ALIGN_LEFT:
+ start_spaces = 0;
+ end_spaces = n_spaces;
+ break;
+ case MBS_ALIGN_RIGHT:
+ start_spaces = n_spaces;
+ end_spaces = 0;
+ break;
+ }
+
+ dest = mbs_align_pad (dest, dest_end, start_spaces);
+ size_t space_left = dest_end - dest;
+ dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left));
+ mbs_align_pad (dest, dest_end, end_spaces);
+ }
+
+mbsalign_cleanup:
+
+ free (str_wc);
+ free (newstr);
+
+ return ret;
+}
diff --git a/lib/strtosize.c b/lib/strtosize.c
new file mode 100644
index 00000000..068c5429
--- /dev/null
+++ b/lib/strtosize.c
@@ -0,0 +1,148 @@
+/*
+ * strtosize() - convert string to size (uintmax_t).
+ *
+ * Supported suffixes:
+ *
+ * XiB or X for 2^N
+ * where X = {K,M,G,T,P,E,Y,Z}
+ * or X = {k,m,g,t,p,e} (undocumented for backward compatibility only)
+ * for example:
+ * 10KiB = 10240
+ * 10K = 10240
+ *
+ * XB for 10^N
+ * where X = {K,M,G,T,P,E,Y,Z}
+ * for example:
+ * 10KB = 10000
+ *
+ * Note that the function does not accept numbers with '-' (negative sign)
+ * prefix.
+ *
+ * Returns 0 on success, -1 in case of error, -2 in case of overflow.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+
+static int do_scale_by_power (uintmax_t *x, int base, int power)
+{
+ while (power--) {
+ if (UINTMAX_MAX / base < *x)
+ return -2;
+ *x *= base;
+ }
+ return 0;
+}
+
+int strtosize(const char *str, uintmax_t *res)
+{
+ char *p;
+ uintmax_t x;
+ int base = 1024, rc = 0;
+
+ *res = 0;
+
+ if (!str || !*str)
+ goto err;
+
+ /* Only positive numbers are acceptable
+ *
+ * Note that this check is not perfect, it would be better to
+ * use lconv->negative_sign. But coreutils use the same solution,
+ * so it's probably good enough...
+ */
+ p = (char *) str;
+ while (isspace((unsigned char) *p))
+ p++;
+ if (*p == '-')
+ goto err;
+ p = NULL;
+
+ errno = 0;
+ x = strtoumax(str, &p, 0);
+
+ if (p == str ||
+ (errno != 0 && (x == UINTMAX_MAX || x == 0)))
+ goto err;
+
+ if (!p || !*p)
+ goto done; /* without suffix */
+
+ /*
+ * Check size suffixes
+ */
+ if (*(p + 1) == 'i' && *(p + 2) == 'B' && !*(p + 3))
+ base = 1024; /* XiB, 2^N */
+ else if (*(p + 1) == 'B' && !*(p + 2))
+ base = 1000; /* XB, 10^N */
+ else if (*(p + 1))
+ goto err; /* unexpected suffix */
+
+ switch(*p) {
+ case 'K':
+ case 'k':
+ rc = do_scale_by_power(&x, base, 1);
+ break;
+ case 'M':
+ case 'm':
+ rc = do_scale_by_power(&x, base, 2);
+ break;
+ case 'G':
+ case 'g':
+ rc = do_scale_by_power(&x, base, 3);
+ break;
+ case 'T':
+ case 't':
+ rc = do_scale_by_power(&x, base, 4);
+ break;
+ case 'P':
+ case 'p':
+ rc = do_scale_by_power(&x, base, 5);
+ break;
+ case 'E':
+ case 'e':
+ rc = do_scale_by_power(&x, base, 6);
+ break;
+ case 'Z':
+ rc = do_scale_by_power(&x, base, 7);
+ break;
+ case 'Y':
+ rc = do_scale_by_power(&x, base, 8);
+ break;
+ default:
+ goto err;
+ }
+
+done:
+ *res = x;
+ return rc;
+err:
+ return -1;
+}
+
+#ifdef TEST_PROGRAM
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+
+int main(int argc, char *argv[])
+{
+ uintmax_t size = 0;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <number>[suffix]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (strtosize(argv[1], &size))
+ errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
+
+ printf("%25s : %20ju\n", argv[1], size);
+ return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM */
+