Richard W.M. Jones
2019-Sep-23 20:06 UTC
[Libguestfs] [PATCH nbdkit v2] server: public: Add nbdkit_parse_* functions for safely parsing integers.
On Mon, Sep 23, 2019 at 01:23:28PM -0500, Eric Blake wrote:> > Hopefully it will warn us if uid_t stops being int. (Also > > we're assuming pid_t == int). > > 64-bit mingw has had a long-standing bug that pid_t was declared as > 64-bit, even though getpid() returns a 32-bit 'int'; I wish they'd fix > their headers, but it violates the assumption of pid_t == int. > > Also, POSIX is considering standardizing fcntl(F_SETOWN_EX) in order to > allow pid_t > int: http://austingroupbugs.net/view.php?id=1274 > > So while your assumption may hold for now on Linux and BSD, I wouldn't > hold my breath on it lasting forever.I should say our existing code already has this bug, this patch doesn't change it :-) Below is V2 which fixes everything you mentioned. In this version I have got rid of long, unsigned_long, size_t and ssize_t, but added int8_t and uint8_t. Rich.>From 375e286be27f563a9f1a886e29bdcfcebebfa81c Mon Sep 17 00:00:00 2001From: "Richard W.M. Jones" <rjones@redhat.com> Date: Sat, 21 Sep 2019 07:30:40 +0100 Subject: [PATCH] server: public: Add nbdkit_parse_* functions for safely parsing integers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sscanf is sadly not safe (because it doesn't handle integer overflow correctly), and strto*l functions are a pain to use correctly. Therefore add some functions to hide the pain of parsing integers from the command line. The simpler uses of sscanf and strto*l are replaced. There are still a few where we are using advanced features of sscanf. This may change command line parsing in a few corner cases: * For some parameters you might have written (eg) ‘cache-high-threshold=08’ to mean decimal 8, but now it would be a parse error. ‘cache-high-threshold=010’ would previously have parsed as decimal 10, but would now parse as octal 10 (decimal 8). * Some parameters previously accepted a wider range of values and will now give an error (but it should be that the narrower range is more correct). --- docs/nbdkit-plugin.pod | 46 +++++++ filters/cache/cache.c | 16 ++- filters/cache/cache.h | 2 +- filters/partition/partition.c | 8 +- filters/partition/partition.h | 2 +- filters/retry/retry.c | 14 +-- filters/xz/xz.c | 12 +- include/nbdkit-common.h | 20 +++ plugins/curl/curl.c | 8 +- plugins/nbd/nbd-standalone.c | 9 +- plugins/nbd/nbd.c | 9 +- plugins/partitioning/partitioning.c | 6 +- plugins/partitioning/virtual-disk.h | 4 +- plugins/random/random.c | 4 +- plugins/ssh/ssh.c | 15 ++- plugins/vddk/vddk.c | 12 +- server/internal.h | 2 +- server/main.c | 21 +--- server/nbdkit.syms | 10 ++ server/public.c | 186 ++++++++++++++++++++++++++++ server/socket-activation.c | 10 +- server/test-public.c | 180 ++++++++++++++++++++++++++- server/usergroup.c | 4 +- 23 files changed, 504 insertions(+), 96 deletions(-) diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod index 2a84444..e34ffd1 100644 --- a/docs/nbdkit-plugin.pod +++ b/docs/nbdkit-plugin.pod @@ -1013,6 +1013,52 @@ might be killed by C<SIGKILL> or segfault). =head1 PARSING COMMAND LINE PARAMETERS +=head2 Parsing numbers + +There are several functions for parsing numbers. These all deal +correctly with overflow, out of range and parse errors, and you should +use them instead of unsafe functions like L<sscanf(3)>, L<atoi(3)> and +similar. + + int nbdkit_parse_int (const char *what, const char *str, int *r); + int nbdkit_parse_unsigned (const char *what, + const char *str, unsigned *r); + int nbdkit_parse_int8_t (const char *what, + const char *str, int8_t *r); + int nbdkit_parse_uint8_t (const char *what, + const char *str, uint8_t *r); + int nbdkit_parse_int16_t (const char *what, + const char *str, int16_t *r); + int nbdkit_parse_uint16_t (const char *what, + const char *str, uint16_t *r); + int nbdkit_parse_int32_t (const char *what, + const char *str, int32_t *r); + int nbdkit_parse_uint32_t (const char *what, + const char *str, uint32_t *r); + int nbdkit_parse_int64_t (const char *what, + const char *str, int64_t *r); + int nbdkit_parse_uint64_t (const char *what, + const char *str, uint64_t *r); + +Parse string C<str> into an integer of various types. These functions +parse a decimal, hexadecimal (C<"0x...">) or octal (C<"0...">) number. + +On success the functions return C<0> and set C<*r> to the parsed value +(unless C<*r == NULL> in which case the result is discarded). On +error, C<nbdkit_error> is called and the functions return C<-1>. On +error C<*r> is always unchanged. + +The C<what> parameter is printed in error messages to provide context. +It should usually be a short descriptive string of what you are trying +to parse, eg: + + if (nbdkit_parse_int ("random seed", argv[1], &seed) == -1) + return -1; + +might print an error: + + random seed: could not parse number: "lalala" + =head2 Parsing sizes Use the C<nbdkit_parse_size> utility function to parse human-readable diff --git a/filters/cache/cache.c b/filters/cache/cache.c index 14a3c0a..faf6023 100644 --- a/filters/cache/cache.c +++ b/filters/cache/cache.c @@ -70,7 +70,7 @@ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; unsigned blksize; enum cache_mode cache_mode = CACHE_MODE_WRITEBACK; int64_t max_size = -1; -int hi_thresh = 95, lo_thresh = 80; +unsigned hi_thresh = 95, lo_thresh = 80; bool cache_on_read = false; static int cache_flush (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, uint32_t flags, int *err); @@ -129,22 +129,20 @@ cache_config (nbdkit_next_config *next, void *nxdata, return 0; } else if (strcmp (key, "cache-high-threshold") == 0) { - if (sscanf (value, "%d", &hi_thresh) != 1) { - nbdkit_error ("invalid cache-high-threshold parameter: %s", value); + if (nbdkit_parse_unsigned ("cache-high-threshold", + value, &hi_thresh) == -1) return -1; - } - if (hi_thresh <= 0) { + if (hi_thresh == 0) { nbdkit_error ("cache-high-threshold must be greater than zero"); return -1; } return 0; } else if (strcmp (key, "cache-low-threshold") == 0) { - if (sscanf (value, "%d", &lo_thresh) != 1) { - nbdkit_error ("invalid cache-low-threshold parameter: %s", value); + if (nbdkit_parse_unsigned ("cache-low-threshold", + value, &lo_thresh) == -1) return -1; - } - if (lo_thresh <= 0) { + if (lo_thresh == 0) { nbdkit_error ("cache-low-threshold must be greater than zero"); return -1; } diff --git a/filters/cache/cache.h b/filters/cache/cache.h index c67e173..2b72221 100644 --- a/filters/cache/cache.h +++ b/filters/cache/cache.h @@ -47,7 +47,7 @@ extern unsigned blksize; /* Maximum size of the cache and high/low thresholds. */ extern int64_t max_size; -extern int hi_thresh, lo_thresh; +extern unsigned hi_thresh, lo_thresh; /* Cache read requests. */ extern bool cache_on_read; diff --git a/filters/partition/partition.c b/filters/partition/partition.c index 15f785e..17dd33a 100644 --- a/filters/partition/partition.c +++ b/filters/partition/partition.c @@ -45,7 +45,7 @@ #include "partition.h" -int partnum = -1; +unsigned partnum = 0; /* Called for each key=value passed on the command line. */ static int @@ -53,7 +53,9 @@ partition_config (nbdkit_next_config *next, void *nxdata, const char *key, const char *value) { if (strcmp (key, "partition") == 0) { - if (sscanf (value, "%d", &partnum) != 1 || partnum <= 0) { + if (nbdkit_parse_unsigned ("partition", value, &partnum) == -1) + return -1; + if (partnum == 0) { nbdkit_error ("invalid partition number"); return -1; } @@ -67,7 +69,7 @@ partition_config (nbdkit_next_config *next, void *nxdata, static int partition_config_complete (nbdkit_next_config_complete *next, void *nxdata) { - if (partnum == -1) { + if (partnum == 0) { nbdkit_error ("you must supply the partition parameter on the command line"); return -1; } diff --git a/filters/partition/partition.h b/filters/partition/partition.h index e25a695..b20d39f 100644 --- a/filters/partition/partition.h +++ b/filters/partition/partition.h @@ -37,7 +37,7 @@ #define SECTOR_SIZE 512 -extern int partnum; +extern unsigned partnum; extern int find_mbr_partition (struct nbdkit_next_ops *next_ops, void *nxdata, int64_t size, uint8_t *mbr, diff --git a/filters/retry/retry.c b/filters/retry/retry.c index b1864fa..4d73f17 100644 --- a/filters/retry/retry.c +++ b/filters/retry/retry.c @@ -44,8 +44,8 @@ #include "cleanup.h" -static int retries = 5; /* 0 = filter is disabled */ -static int initial_delay = 2; +static unsigned retries = 5; /* 0 = filter is disabled */ +static unsigned initial_delay = 2; static bool exponential_backoff = true; static bool force_readonly = false; @@ -67,15 +67,15 @@ retry_config (nbdkit_next_config *next, void *nxdata, int r; if (strcmp (key, "retries") == 0) { - if (sscanf (value, "%d", &retries) != 1 || retries < 0) { - nbdkit_error ("cannot parse retries: %s", value); + if (nbdkit_parse_unsigned ("retries", value, &retries) == -1) return -1; - } return 0; } else if (strcmp (key, "retry-delay") == 0) { - if (sscanf (value, "%d", &initial_delay) != 1 || initial_delay <= 0) { - nbdkit_error ("cannot parse retry-delay: %s", value); + if (nbdkit_parse_unsigned ("retry-delay", value, &initial_delay) == -1) + return -1; + if (initial_delay == 0) { + nbdkit_error ("retry-delay cannot be 0"); return -1; } return 0; diff --git a/filters/xz/xz.c b/filters/xz/xz.c index a420d38..4445ce0 100644 --- a/filters/xz/xz.c +++ b/filters/xz/xz.c @@ -49,7 +49,7 @@ #include "blkcache.h" static uint64_t maxblock = 512 * 1024 * 1024; -static size_t maxdepth = 8; +static uint32_t maxdepth = 8; static int xz_config (nbdkit_next_config *next, void *nxdata, @@ -63,18 +63,12 @@ xz_config (nbdkit_next_config *next, void *nxdata, return 0; } else if (strcmp (key, "xz-max-depth") == 0) { - size_t r; - - if (sscanf (value, "%zu", &r) != 1) { - nbdkit_error ("could not parse 'xz-max-depth' parameter"); + if (nbdkit_parse_uint32_t ("xz-max-depth", value, &maxdepth) == -1) return -1; - } - if (r == 0) { + if (maxdepth == 0) { nbdkit_error ("'xz-max-depth' parameter must be >= 1"); return -1; } - - maxdepth = r; return 0; } else diff --git a/include/nbdkit-common.h b/include/nbdkit-common.h index aac63fb..e2a8b88 100644 --- a/include/nbdkit-common.h +++ b/include/nbdkit-common.h @@ -84,6 +84,26 @@ extern void nbdkit_vdebug (const char *msg, va_list args); extern char *nbdkit_absolute_path (const char *path); extern int64_t nbdkit_parse_size (const char *str); extern int nbdkit_parse_bool (const char *str); +extern int nbdkit_parse_int (const char *what, const char *str, + int *r); +extern int nbdkit_parse_unsigned (const char *what, const char *str, + unsigned *r); +extern int nbdkit_parse_int8_t (const char *what, const char *str, + int8_t *r); +extern int nbdkit_parse_uint8_t (const char *what, const char *str, + uint8_t *r); +extern int nbdkit_parse_int16_t (const char *what, const char *str, + int16_t *r); +extern int nbdkit_parse_uint16_t (const char *what, const char *str, + uint16_t *r); +extern int nbdkit_parse_int32_t (const char *what, const char *str, + int32_t *r); +extern int nbdkit_parse_uint32_t (const char *what, const char *str, + uint32_t *r); +extern int nbdkit_parse_int64_t (const char *what, const char *str, + int64_t *r); +extern int nbdkit_parse_uint64_t (const char *what, const char *str, + uint64_t *r); extern int nbdkit_read_password (const char *value, char **password); extern char *nbdkit_realpath (const char *path); extern int nbdkit_nanosleep (unsigned sec, unsigned nsec); diff --git a/plugins/curl/curl.c b/plugins/curl/curl.c index 0ed3984..cf01aae 100644 --- a/plugins/curl/curl.c +++ b/plugins/curl/curl.c @@ -62,7 +62,7 @@ static const char *proxy_user = NULL; static char *proxy_password = NULL; static char *cookie = NULL; static bool sslverify = true; -static long timeout = 0; +static uint32_t timeout = 0; static const char *unix_socket_path = NULL; static long protocols = CURLPROTO_ALL; @@ -204,7 +204,9 @@ curl_config (const char *key, const char *value) } else if (strcmp (key, "timeout") == 0) { - if (sscanf (value, "%ld", &timeout) != 1 || timeout < 0) { + if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1) + return -1; + if (timeout < 0) { nbdkit_error ("'timeout' must be 0 or a positive timeout in seconds"); return -1; } @@ -334,7 +336,7 @@ curl_open (int readonly) curl_easy_setopt (h->c, CURLOPT_REDIR_PROTOCOLS, protocols); } if (timeout > 0) - curl_easy_setopt (h->c, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt (h->c, CURLOPT_TIMEOUT, (long) timeout); if (!sslverify) { curl_easy_setopt (h->c, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt (h->c, CURLOPT_SSL_VERIFYHOST, 0L); diff --git a/plugins/nbd/nbd-standalone.c b/plugins/nbd/nbd-standalone.c index 1468098..0fabbfe 100644 --- a/plugins/nbd/nbd-standalone.c +++ b/plugins/nbd/nbd-standalone.c @@ -102,7 +102,7 @@ static char *servname; static const char *export; /* Number of retries */ -static unsigned long retry; +static unsigned retry; /* True to share single server connection among all clients */ static bool shared; @@ -128,7 +128,6 @@ nbd_unload (void) static int nbd_config (const char *key, const char *value) { - char *end; int r; if (strcmp (key, "socket") == 0) { @@ -145,12 +144,8 @@ nbd_config (const char *key, const char *value) else if (strcmp (key, "export") == 0) export = value; else if (strcmp (key, "retry") == 0) { - errno = 0; - retry = strtoul (value, &end, 0); - if (value == end || errno) { - nbdkit_error ("could not parse retry as integer (%s)", value); + if (nbdkit_parse_unsigned ("retry", value, &retry) == -1) return -1; - } } else if (strcmp (key, "shared") == 0) { r = nbdkit_parse_bool (value); diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c index cddcfde..3215636 100644 --- a/plugins/nbd/nbd.c +++ b/plugins/nbd/nbd.c @@ -89,7 +89,7 @@ static const char *uri; static const char *export; /* Number of retries */ -static unsigned long retry; +static unsigned retry; /* True to share single server connection among all clients */ static bool shared; @@ -124,7 +124,6 @@ nbdplug_unload (void) static int nbdplug_config (const char *key, const char *value) { - char *end; int r; if (strcmp (key, "socket") == 0) { @@ -143,12 +142,8 @@ nbdplug_config (const char *key, const char *value) else if (strcmp (key, "export") == 0) export = value; else if (strcmp (key, "retry") == 0) { - errno = 0; - retry = strtoul (value, &end, 0); - if (value == end || errno) { - nbdkit_error ("could not parse retry as integer (%s)", value); + if (nbdkit_parse_unsigned ("retry", value, &retry) == -1) return -1; - } } else if (strcmp (key, "shared") == 0) { r = nbdkit_parse_bool (value); diff --git a/plugins/partitioning/partitioning.c b/plugins/partitioning/partitioning.c index 0b4e66b..6e426b9 100644 --- a/plugins/partitioning/partitioning.c +++ b/plugins/partitioning/partitioning.c @@ -68,7 +68,7 @@ int partitioning_debug_regions; * following partitions. */ unsigned long alignment = DEFAULT_ALIGNMENT; -int mbr_id = DEFAULT_MBR_ID; +uint8_t mbr_id = DEFAULT_MBR_ID; char type_guid[16]; /* initialized by partitioning_load function below */ /* partition-type parameter. */ @@ -211,10 +211,8 @@ partitioning_config (const char *key, const char *value) else if (strcmp (key, "mbr-id") == 0) { if (strcasecmp (value, "default") == 0) mbr_id = DEFAULT_MBR_ID; - else if (sscanf (value, "%i", &mbr_id) != 1) { - nbdkit_error ("could not parse mbr-id: %s", value); + else if (nbdkit_parse_uint8_t ("mbr-id", value, &mbr_id) == -1) return -1; - } } else if (strcmp (key, "type-guid") == 0) { if (strcasecmp (value, "default") == 0) diff --git a/plugins/partitioning/virtual-disk.h b/plugins/partitioning/virtual-disk.h index 512756d..e173978 100644 --- a/plugins/partitioning/virtual-disk.h +++ b/plugins/partitioning/virtual-disk.h @@ -69,7 +69,7 @@ extern int partitioning_debug_regions; extern unsigned long alignment; -extern int mbr_id; +extern uint8_t mbr_id; extern char type_guid[16]; #define PARTTYPE_UNSET 0 @@ -84,7 +84,7 @@ struct file { struct stat statbuf; char guid[16]; /* random GUID used for GPT */ unsigned long alignment; /* alignment of this partition */ - int mbr_id; /* MBR ID of this partition */ + uint8_t mbr_id; /* MBR ID of this partition */ char type_guid[16]; /* partition type GUID of this partition */ }; diff --git a/plugins/random/random.c b/plugins/random/random.c index 6377310..cdebdf9 100644 --- a/plugins/random/random.c +++ b/plugins/random/random.c @@ -67,10 +67,8 @@ random_config (const char *key, const char *value) int64_t r; if (strcmp (key, "seed") == 0) { - if (sscanf (value, "%" SCNu32, &seed) != 1) { - nbdkit_error ("could not parse seed parameter"); + if (nbdkit_parse_uint32_t ("seed", value, &seed) == -1) return -1; - } } else if (strcmp (key, "size") == 0) { r = nbdkit_parse_size (value); diff --git a/plugins/ssh/ssh.c b/plugins/ssh/ssh.c index ee42ee1..d163294 100644 --- a/plugins/ssh/ssh.c +++ b/plugins/ssh/ssh.c @@ -38,6 +38,7 @@ #include <stdarg.h> #include <stdint.h> #include <inttypes.h> +#include <limits.h> #include <string.h> #include <unistd.h> #include <fcntl.h> @@ -59,7 +60,7 @@ static bool verify_remote_host = true; static const char *known_hosts = NULL; static const char **identity = NULL; static size_t nr_identities = 0; -static long timeout = 0; +static uint32_t timeout = 0; /* config can be: * NULL => parse options from default file @@ -151,8 +152,11 @@ ssh_config (const char *key, const char *value) } else if (strcmp (key, "timeout") == 0) { - if (sscanf (value, "%ld", &timeout) != 1) { - nbdkit_error ("cannot parse timeout: %s", value); + if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1) + return -1; + /* Because we have to cast it to long before calling the libssh API. */ + if (timeout > LONG_MAX) { + nbdkit_error ("timeout too large"); return -1; } } @@ -403,9 +407,10 @@ ssh_open (int readonly) } } if (timeout > 0) { - r = ssh_options_set (h->session, SSH_OPTIONS_TIMEOUT, &timeout); + long arg = timeout; + r = ssh_options_set (h->session, SSH_OPTIONS_TIMEOUT, &arg); if (r != SSH_OK) { - nbdkit_error ("failed to set timeout in libssh session: %ld: %s", + nbdkit_error ("failed to set timeout in libssh session: %" PRIu32 ": %s", timeout, ssh_get_error (h->session)); goto err; } diff --git a/plugins/vddk/vddk.c b/plugins/vddk/vddk.c index 8ea05b1..db12a85 100644 --- a/plugins/vddk/vddk.c +++ b/plugins/vddk/vddk.c @@ -91,9 +91,9 @@ static char *config = NULL; /* config */ static const char *cookie = NULL; /* cookie */ static const char *filename = NULL; /* file */ static char *libdir = NULL; /* libdir */ -static int nfc_host_port = 0; /* nfchostport */ +static uint16_t nfc_host_port = 0; /* nfchostport */ static char *password = NULL; /* password */ -static int port = 0; /* port */ +static uint16_t port = 0; /* port */ static const char *server_name = NULL; /* server */ static bool single_link = false; /* single-link */ static const char *snapshot_moref = NULL; /* snapshot */ @@ -271,10 +271,8 @@ vddk_config (const char *key, const char *value) return -1; } else if (strcmp (key, "nfchostport") == 0) { - if (sscanf (value, "%d", &nfc_host_port) != 1) { - nbdkit_error ("cannot parse nfchostport: %s", value); + if (nbdkit_parse_uint16_t ("nfchostport", value, &nfc_host_port) == -1) return -1; - } } else if (strcmp (key, "password") == 0) { free (password); @@ -282,10 +280,8 @@ vddk_config (const char *key, const char *value) return -1; } else if (strcmp (key, "port") == 0) { - if (sscanf (value, "%d", &port) != 1) { - nbdkit_error ("cannot parse port: %s", value); + if (nbdkit_parse_uint16_t ("port", value, &port) == -1) return -1; - } } else if (strcmp (key, "server") == 0) { server_name = value; diff --git a/server/internal.h b/server/internal.h index 043a13d..fbabce6 100644 --- a/server/internal.h +++ b/server/internal.h @@ -98,7 +98,7 @@ extern bool read_only; extern const char *run; extern bool listen_stdin; extern const char *selinux_label; -extern int threads; +extern unsigned threads; extern int tls; extern const char *tls_certificates_dir; extern const char *tls_psk; diff --git a/server/main.c b/server/main.c index d433c1f..975716d 100644 --- a/server/main.c +++ b/server/main.c @@ -76,7 +76,7 @@ bool read_only; /* -r */ const char *run; /* --run */ bool listen_stdin; /* -s */ const char *selinux_label; /* --selinux-label */ -int threads; /* -t */ +unsigned threads; /* -t */ int tls; /* --tls : 0=off 1=on 2=require */ const char *tls_certificates_dir; /* --tls-certificates */ const char *tls_psk; /* --tls-psk */ @@ -149,7 +149,6 @@ main (int argc, char *argv[]) } *filter_filenames = NULL; size_t i; const char *magic_config_key; - char *end; /* Refuse to run if stdin/out/err are closed, whether or not -s is used. */ if (fcntl (STDERR_FILENO, F_GETFL) == -1) { @@ -217,7 +216,8 @@ main (int argc, char *argv[]) if (!flag->name) goto debug_flag_perror; flag->flag = strndup (p, q-p-1); if (!flag->flag) goto debug_flag_perror; - if (sscanf (q, "%d", &flag->value) != 1) goto bad_debug_flag; + if (nbdkit_parse_int ("flag", q, &flag->value) == -1) + goto bad_debug_flag; flag->used = false; /* Add flag to the linked list. */ @@ -351,13 +351,9 @@ main (int argc, char *argv[]) break; case MASK_HANDSHAKE_OPTION: - errno = 0; - mask_handshake = strtoul (optarg, &end, 0); - if (errno || *end) { - fprintf (stderr, "%s: cannot parse '%s' into mask-handshake\n", - program_name, optarg); + if (nbdkit_parse_unsigned ("mask-handshake", + optarg, &mask_handshake) == -1) exit (EXIT_FAILURE); - } break; case 'n': @@ -401,13 +397,8 @@ main (int argc, char *argv[]) break; case 't': - errno = 0; - threads = strtoul (optarg, &end, 0); - if (errno || *end) { - fprintf (stderr, "%s: cannot parse '%s' into threads\n", - program_name, optarg); + if (nbdkit_parse_unsigned ("threads", optarg, &threads) == -1) exit (EXIT_FAILURE); - } /* XXX Worth a maximimum limit on threads? */ break; diff --git a/server/nbdkit.syms b/server/nbdkit.syms index d792a5f..390972e 100644 --- a/server/nbdkit.syms +++ b/server/nbdkit.syms @@ -49,7 +49,17 @@ nbdkit_get_extent; nbdkit_nanosleep; nbdkit_parse_bool; + nbdkit_parse_int8_t; + nbdkit_parse_int16_t; + nbdkit_parse_int32_t; + nbdkit_parse_int64_t; + nbdkit_parse_int; nbdkit_parse_size; + nbdkit_parse_uint8_t; + nbdkit_parse_uint16_t; + nbdkit_parse_uint32_t; + nbdkit_parse_uint64_t; + nbdkit_parse_unsigned; nbdkit_peer_name; nbdkit_read_password; nbdkit_realpath; diff --git a/server/public.c b/server/public.c index d0d9ff4..9a3aa31 100644 --- a/server/public.c +++ b/server/public.c @@ -45,6 +45,7 @@ #include <string.h> #include <unistd.h> #include <limits.h> +#include <ctype.h> #include <termios.h> #include <errno.h> #include <poll.h> @@ -108,6 +109,191 @@ nbdkit_realpath (const char *path) return ret; } +/* Common code for parsing integers. */ +#define PARSE_COMMON_TAIL \ + if (errno != 0) { \ + nbdkit_error ("%s: could not parse number: \"%s\": %m", \ + what, str); \ + return -1; \ + } \ + if (end == str) { \ + nbdkit_error ("%s: empty string where we expected a number", \ + what); \ + return -1; \ + } \ + if (*end) { \ + nbdkit_error ("%s: could not parse number: \"%s\": trailing garbage", \ + what, str); \ + return -1; \ + } \ + \ + if (rp) \ + *rp = r; \ + return 0 + +/* Functions for parsing signed integers. */ +int +nbdkit_parse_int (const char *what, const char *str, int *rp) +{ + long r; + char *end; + + errno = 0; + r = strtol (str, &end, 0); +#if INT_MAX != LONG_MAX + if (r < INT_MIN || r > INT_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_int8_t (const char *what, const char *str, int8_t *rp) +{ + long r; + char *end; + + errno = 0; + r = strtol (str, &end, 0); + if (r < INT8_MIN || r > INT8_MAX) + errno = ERANGE; + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_int16_t (const char *what, const char *str, int16_t *rp) +{ + long r; + char *end; + + errno = 0; + r = strtol (str, &end, 0); + if (r < INT16_MIN || r > INT16_MAX) + errno = ERANGE; + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_int32_t (const char *what, const char *str, int32_t *rp) +{ + long r; + char *end; + + errno = 0; + r = strtol (str, &end, 0); +#if INT32_MAX != LONG_MAX + if (r < INT32_MIN || r > INT32_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_int64_t (const char *what, const char *str, int64_t *rp) +{ + long long r; + char *end; + + errno = 0; + r = strtoll (str, &end, 0); +#if INT64_MAX != LONGLONG_MAX + if (r < INT64_MIN || r > INT64_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + +/* Functions for parsing unsigned integers. */ + +/* strtou* functions have surprising behaviour if the first character + * (after whitespace) is '-', so reject this early. + */ +#define PARSE_ERROR_IF_NEGATIVE \ + do { \ + while (isspace (*str)) \ + str++; \ + if (*str == '-') { \ + nbdkit_error ("%s: negative numbers are not allowed", what); \ + return -1; \ + } \ + } while (0) + +int +nbdkit_parse_unsigned (const char *what, const char *str, unsigned *rp) +{ + unsigned long r; + char *end; + + PARSE_ERROR_IF_NEGATIVE; + errno = 0; + r = strtoul (str, &end, 0); +#if UINT_MAX != ULONG_MAX + if (r > UINT_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_uint8_t (const char *what, const char *str, uint8_t *rp) +{ + unsigned long r; + char *end; + + PARSE_ERROR_IF_NEGATIVE; + errno = 0; + r = strtoul (str, &end, 0); + if (r > UINT8_MAX) + errno = ERANGE; + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_uint16_t (const char *what, const char *str, uint16_t *rp) +{ + unsigned long r; + char *end; + + PARSE_ERROR_IF_NEGATIVE; + errno = 0; + r = strtoul (str, &end, 0); + if (r > UINT16_MAX) + errno = ERANGE; + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_uint32_t (const char *what, const char *str, uint32_t *rp) +{ + unsigned long r; + char *end; + + PARSE_ERROR_IF_NEGATIVE; + errno = 0; + r = strtoul (str, &end, 0); +#if UINT32_MAX != ULONG_MAX + if (r > UINT32_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + +int +nbdkit_parse_uint64_t (const char *what, const char *str, uint64_t *rp) +{ + unsigned long long r; + char *end; + + PARSE_ERROR_IF_NEGATIVE; + errno = 0; + r = strtoull (str, &end, 0); +#if UINT64_MAX != ULONGLONG_MAX + if (r > UINT64_MAX) + errno = ERANGE; +#endif + PARSE_COMMON_TAIL; +} + /* Parse a string as a size with possible scaling suffix, or return -1 * after reporting the error. */ diff --git a/server/socket-activation.c b/server/socket-activation.c index b9db67c..50df4ca 100644 --- a/server/socket-activation.c +++ b/server/socket-activation.c @@ -59,11 +59,8 @@ get_socket_activation (void) s = getenv ("LISTEN_PID"); if (s == NULL) return 0; - if (sscanf (s, "%u", &pid) != 1) { - fprintf (stderr, "%s: malformed %s environment variable (ignored)\n", - program_name, "LISTEN_PID"); + if (nbdkit_parse_unsigned ("LISTEN_PID", s, &pid) == -1) return 0; - } if (pid != getpid ()) { fprintf (stderr, "%s: %s was not for us (ignored)\n", program_name, "LISTEN_PID"); @@ -73,11 +70,8 @@ get_socket_activation (void) s = getenv ("LISTEN_FDS"); if (s == NULL) return 0; - if (sscanf (s, "%u", &nr_fds) != 1) { - fprintf (stderr, "%s: malformed %s environment variable (ignored)\n", - program_name, "LISTEN_FDS"); + if (nbdkit_parse_unsigned ("LISTEN_FDS", s, &nr_fds) == -1) return 0; - } /* So these are not passed to any child processes we might start. */ unsetenv ("LISTEN_FDS"); diff --git a/server/test-public.c b/server/test-public.c index 4a01936..7b64288 100644 --- a/server/test-public.c +++ b/server/test-public.c @@ -33,8 +33,11 @@ #include <config.h> #include <stdio.h> -#include <inttypes.h> +#include <stdlib.h> #include <stdbool.h> +#include <stdint.h> +#include <inttypes.h> +#include <limits.h> #include <string.h> #include <unistd.h> @@ -153,6 +156,180 @@ test_nbdkit_parse_size (void) return pass; } +static bool +test_nbdkit_parse_ints (void) +{ + bool pass = true; + +#define PARSE(...) PARSE_(__VA_ARGS__) +#define PARSE_(TYPE, FORMAT, TEST, RET, EXPECTED) \ + do { \ + error_flagged = false; \ + TYPE i = 0; \ + int r = nbdkit_parse_##TYPE ("test", TEST, &i); \ + if (r != RET || i != EXPECTED) { \ + fprintf (stderr, \ + "%s: %d: wrong parse for %s: r=%d i=" FORMAT "\n", \ + __FILE__, __LINE__, TEST, r, i); \ + pass = false; \ + } \ + if ((r == -1) != error_flagged) { \ + fprintf (stderr, \ + "%s: %d: wrong error message handling for %s\n", \ + __FILE__, __LINE__, TEST); \ + pass = false; \ + } \ + } while (0) +#define OK 0 +#define BAD -1, 0 + + /* Test the basic parsing of decimals, hexadecimal, octal and + * negative numbers. + */ + PARSE (int, "%d", "0", OK, 0); + PARSE (int, "%d", " 0", OK, 0); + PARSE (int, "%d", " 0", OK, 0); + PARSE (int, "%d", " 0", OK, 0); + PARSE (int, "%d", "1", OK, 1); + PARSE (int, "%d", " 1", OK, 1); + PARSE (int, "%d", " 1", OK, 1); + PARSE (int, "%d", " 1", OK, 1); + PARSE (int, "%d", "99", OK, 99); + PARSE (int, "%d", "0x1", OK, 1); + PARSE (int, "%d", "0xf", OK, 15); + PARSE (int, "%d", "0x10", OK, 16); + PARSE (int, "%d", "0xff", OK, 255); + PARSE (int, "%d", "0Xff", OK, 255); + PARSE (int, "%d", "01", OK, 1); + PARSE (int, "%d", "07", OK, 7); + PARSE (int, "%d", "010", OK, 8); + PARSE (int, "%d", "+0", OK, 0); + PARSE (int, "%d", " +0", OK, 0); + PARSE (int, "%d", "+99", OK, 99); + PARSE (int, "%d", "+0xf", OK, 15); + PARSE (int, "%d", "+010", OK, 8); + PARSE (int, "%d", "-0", OK, 0); + PARSE (int, "%d", " -0", OK, 0); + PARSE (int, "%d", " -0", OK, 0); + PARSE (int, "%d", "-99", OK, -99); + PARSE (int, "%d", "-0xf", OK, -15); + PARSE (int, "%d", "-0XF", OK, -15); + PARSE (int, "%d", "-010", OK, -8); + PARSE (int, "%d", "2147483647", OK, 2147483647); /* INT_MAX */ + PARSE (int, "%d", "-2147483648", OK, -2147483648); /* INT_MIN */ + PARSE (int, "%d", "0x7fffffff", OK, 0x7fffffff); + PARSE (int, "%d", "-0x80000000", OK, -0x80000000); + + /* Test basic error handling. */ + PARSE (int, "%d", "", BAD); + PARSE (int, "%d", "-", BAD); + PARSE (int, "%d", "- 0", BAD); + PARSE (int, "%d", "+", BAD); + PARSE (int, "%d", "++", BAD); + PARSE (int, "%d", "++0", BAD); + PARSE (int, "%d", "--0", BAD); + PARSE (int, "%d", "0x", BAD); + PARSE (int, "%d", "0xg", BAD); + PARSE (int, "%d", "08", BAD); + PARSE (int, "%d", "0x1p1", BAD); + PARSE (int, "%d", "42x", BAD); + PARSE (int, "%d", "42e42", BAD); + PARSE (int, "%d", "42-", BAD); + PARSE (int, "%d", "garbage", BAD); + PARSE (int, "%d", "inf", BAD); + PARSE (int, "%d", "nan", BAD); + PARSE (int, "%d", "0.0", BAD); + PARSE (int, "%d", "1,000", BAD); + PARSE (int, "%d", "2147483648", BAD); /* INT_MAX + 1 */ + PARSE (int, "%d", "-2147483649", BAD); /* INT_MIN - 1 */ + PARSE (int, "%d", "999999999999999999999999", BAD); + PARSE (int, "%d", "-999999999999999999999999", BAD); + + /* Test nbdkit_parse_unsigned. */ + PARSE (unsigned, "%u", "0", OK, 0); + PARSE (unsigned, "%u", " 0", OK, 0); + PARSE (unsigned, "%u", "1", OK, 1); + PARSE (unsigned, "%u", "99", OK, 99); + PARSE (unsigned, "%u", "0x1", OK, 1); + PARSE (unsigned, "%u", "0xf", OK, 15); + PARSE (unsigned, "%u", "0x10", OK, 16); + PARSE (unsigned, "%u", "0xff", OK, 255); + PARSE (unsigned, "%u", "01", OK, 1); + PARSE (unsigned, "%u", "07", OK, 7); + PARSE (unsigned, "%u", "010", OK, 8); + PARSE (unsigned, "%u", "+0", OK, 0); + PARSE (unsigned, "%u", "+99", OK, 99); + PARSE (unsigned, "%u", "+0xf", OK, 15); + PARSE (unsigned, "%u", "+010", OK, 8); + PARSE (unsigned, "%u", "-0", BAD); /* this is by choice */ + PARSE (unsigned, "%u", " -0", BAD); + PARSE (unsigned, "%u", "-99", BAD); + PARSE (unsigned, "%u", "-0xf", BAD); + PARSE (unsigned, "%u", "-010", BAD); + PARSE (unsigned, "%u", "2147483647", OK, 2147483647); /* INT_MAX */ + PARSE (unsigned, "%u", "-2147483648", BAD); /* INT_MIN */ + PARSE (unsigned, "%u", "0x7fffffff", OK, 0x7fffffff); + PARSE (unsigned, "%u", "-0x80000000", BAD); + + /* Test nbdkit_parse_int8_t. */ + PARSE (int8_t, "%" PRIi8, "0", OK, 0); + PARSE (int8_t, "%" PRIi8, "0x7f", OK, 0x7f); + PARSE (int8_t, "%" PRIi8, "-0x80", OK, -0x80); + PARSE (int8_t, "%" PRIi8, "0x80", BAD); + PARSE (int8_t, "%" PRIi8, "-0x81", BAD); + + /* Test nbdkit_parse_uint8_t. */ + PARSE (uint8_t, "%" PRIu8, "0", OK, 0); + PARSE (uint8_t, "%" PRIu8, "0xff", OK, 0xff); + PARSE (uint8_t, "%" PRIu8, "0x100", BAD); + PARSE (uint8_t, "%" PRIu8, "-1", BAD); + + /* Test nbdkit_parse_int16_t. */ + PARSE (int16_t, "%" PRIi16, "0", OK, 0); + PARSE (int16_t, "%" PRIi16, "0x7fff", OK, 0x7fff); + PARSE (int16_t, "%" PRIi16, "-0x8000", OK, -0x8000); + PARSE (int16_t, "%" PRIi16, "0x8000", BAD); + PARSE (int16_t, "%" PRIi16, "-0x8001", BAD); + + /* Test nbdkit_parse_uint16_t. */ + PARSE (uint16_t, "%" PRIu16, "0", OK, 0); + PARSE (uint16_t, "%" PRIu16, "0xffff", OK, 0xffff); + PARSE (uint16_t, "%" PRIu16, "0x10000", BAD); + PARSE (uint16_t, "%" PRIu16, "-1", BAD); + + /* Test nbdkit_parse_int32_t. */ + PARSE (int32_t, "%" PRIi32, "0", OK, 0); + PARSE (int32_t, "%" PRIi32, "0x7fffffff", OK, 0x7fffffff); + PARSE (int32_t, "%" PRIi32, "-0x80000000", OK, -0x80000000); + PARSE (int32_t, "%" PRIi32, "0x80000000", BAD); + PARSE (int32_t, "%" PRIi32, "-0x80000001", BAD); + + /* Test nbdkit_parse_uint32_t. */ + PARSE (uint32_t, "%" PRIu32, "0", OK, 0); + PARSE (uint32_t, "%" PRIu32, "0xffffffff", OK, 0xffffffff); + PARSE (uint32_t, "%" PRIu32, "0x100000000", BAD); + PARSE (uint32_t, "%" PRIu32, "-1", BAD); + + /* Test nbdkit_parse_int64_t. */ + PARSE (int64_t, "%" PRIi64, "0", OK, 0); + PARSE (int64_t, "%" PRIi64, "0x7fffffffffffffff", OK, 0x7fffffffffffffff); + PARSE (int64_t, "%" PRIi64, "-0x8000000000000000", OK, -0x8000000000000000); + PARSE (int64_t, "%" PRIi64, "0x8000000000000000", BAD); + PARSE (int64_t, "%" PRIi64, "-0x8000000000000001", BAD); + + /* Test nbdkit_parse_uint64_t. */ + PARSE (uint64_t, "%" PRIu64, "0", OK, 0); + PARSE (uint64_t, "%" PRIu64, "0xffffffffffffffff", OK, 0xffffffffffffffff); + PARSE (uint64_t, "%" PRIu64, "0x10000000000000000", BAD); + PARSE (uint64_t, "%" PRIu64, "-1", BAD); + +#undef PARSE +#undef PARSE_ +#undef OK +#undef BAD + return pass; +} + static bool test_nbdkit_read_password (void) { @@ -228,6 +405,7 @@ main (int argc, char *argv[]) { bool pass = true; pass &= test_nbdkit_parse_size (); + pass &= test_nbdkit_parse_ints (); pass &= test_nbdkit_read_password (); /* nbdkit_absolute_path and nbdkit_nanosleep not unit-tested here, but * get plenty of coverage in the main testsuite. diff --git a/server/usergroup.c b/server/usergroup.c index 719c816..11bafce 100644 --- a/server/usergroup.c +++ b/server/usergroup.c @@ -97,7 +97,7 @@ parseuser (const char *id) saved_errno = errno; - if (sscanf (id, "%d", &val) == 1) + if (nbdkit_parse_int ("parseuser", id, &val) == 0) return val; fprintf (stderr, "%s: -u option: %s is not a valid user name or uid", @@ -125,7 +125,7 @@ parsegroup (const char *id) saved_errno = errno; - if (sscanf (id, "%d", &val) == 1) + if (nbdkit_parse_int ("parsegroup", id, &val) == 0) return val; fprintf (stderr, "%s: -g option: %s is not a valid group name or gid", -- 2.23.0
Eric Blake
2019-Sep-23 21:21 UTC
Re: [Libguestfs] [PATCH nbdkit v2] server: public: Add nbdkit_parse_* functions for safely parsing integers.
On 9/23/19 3:06 PM, Richard W.M. Jones wrote:> On Mon, Sep 23, 2019 at 01:23:28PM -0500, Eric Blake wrote: >>> Hopefully it will warn us if uid_t stops being int. (Also >>> we're assuming pid_t == int). >> >> 64-bit mingw has had a long-standing bug that pid_t was declared as >> 64-bit, even though getpid() returns a 32-bit 'int'; I wish they'd fix >> their headers, but it violates the assumption of pid_t == int. >> >> Also, POSIX is considering standardizing fcntl(F_SETOWN_EX) in order to >> allow pid_t > int: http://austingroupbugs.net/view.php?id=1274 >> >> So while your assumption may hold for now on Linux and BSD, I wouldn't >> hold my breath on it lasting forever. > > I should say our existing code already has this bug, this > patch doesn't change it :-)True.> > Below is V2 which fixes everything you mentioned. In this version I > have got rid of long, unsigned_long, size_t and ssize_t, but added > int8_t and uint8_t. >Looks reasonable.> This may change command line parsing in a few corner cases: > > * For some parameters you might have written (eg) > ‘cache-high-threshold=08’ to mean decimal 8, but now it would be a > parse error. ‘cache-high-threshold=010’ would previously have > parsed as decimal 10, but would now parse as octal 10 (decimal 8). > > * Some parameters previously accepted a wider range of values and will > now give an error (but it should be that the narrower range is more > correct).Thanks for that update. I don't know if it will ever bite someone in practice, but it's always nicer to be able to point a bug report back to a commit message stating that the change was intentional if it does come up.> +++ b/plugins/curl/curl.c > @@ -62,7 +62,7 @@ static const char *proxy_user = NULL; > static char *proxy_password = NULL; > static char *cookie = NULL; > static bool sslverify = true; > -static long timeout = 0; > +static uint32_t timeout = 0;> @@ -334,7 +336,7 @@ curl_open (int readonly) > curl_easy_setopt (h->c, CURLOPT_REDIR_PROTOCOLS, protocols); > } > if (timeout > 0) > - curl_easy_setopt (h->c, CURLOPT_TIMEOUT, timeout); > + curl_easy_setopt (h->c, CURLOPT_TIMEOUT, (long) timeout);I might have left a comment here (the cast is necessary because curl_easy_setopt() is varargs), so no one accidentally deletes the cast thinking it is pointless.> +++ b/plugins/ssh/ssh.c> @@ -151,8 +152,11 @@ ssh_config (const char *key, const char *value) > } > > else if (strcmp (key, "timeout") == 0) { > - if (sscanf (value, "%ld", &timeout) != 1) { > - nbdkit_error ("cannot parse timeout: %s", value); > + if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1) > + return -1; > + /* Because we have to cast it to long before calling the libssh API. */ > + if (timeout > LONG_MAX) { > + nbdkit_error ("timeout too large");C17 5.2.4.2.1 requires 'long' to be at least 32 bits. Ergo, (uint32_t) timeout > LONG_MAX is always false. You could assert() rather than trying to use nbdkit_error().> return -1; > } > } > @@ -403,9 +407,10 @@ ssh_open (int readonly) > } > } > if (timeout > 0) { > - r = ssh_options_set (h->session, SSH_OPTIONS_TIMEOUT, &timeout); > + long arg = timeout; > + r = ssh_options_set (h->session, SSH_OPTIONS_TIMEOUT, &arg);This conversion is correct.> +++ b/server/test-public.c > @@ -33,8 +33,11 @@ > #include <config.h> > > #include <stdio.h> > -#include <inttypes.h> > +#include <stdlib.h> > #include <stdbool.h> > +#include <stdint.h> > +#include <inttypes.h> > +#include <limits.h> > #include <string.h> > #include <unistd.h> > > @@ -153,6 +156,180 @@ test_nbdkit_parse_size (void) > return pass; > } > > +static bool > +test_nbdkit_parse_ints (void) > +{ > + bool pass = true; > + > +#define PARSE(...) PARSE_(__VA_ARGS__) > +#define PARSE_(TYPE, FORMAT, TEST, RET, EXPECTED) \ > + do { \Alignment is off.> + error_flagged = false; \ > + TYPE i = 0; \Ooh, we could also test that *r is unchanged on failure. Here, assign TYPE i = 123; (or something unlikely and unused elsewhere, remembering that we have to fit in [u]int8_t)...> + int r = nbdkit_parse_##TYPE ("test", TEST, &i); \ > + if (r != RET || i != EXPECTED) { \then here, if (r != RET || i != (r ? 123 : EXPECTED)) { We didn't properly parenthesize uses of (RET) or (EXPECTED), but it's also easy to see that all uses are local to this function and don't need extra grouping, plus the fact that this macro is not all expressions, but also includes type names, so it's already atypical from the usual patterns of needing to protect arguments.> + fprintf (stderr, \ > + "%s: %d: wrong parse for %s: r=%d i=" FORMAT "\n", \ > + __FILE__, __LINE__, TEST, r, i); \ > + pass = false; \ > + } \ > + if ((r == -1) != error_flagged) { \ > + fprintf (stderr, \ > + "%s: %d: wrong error message handling for %s\n", \ > + __FILE__, __LINE__, TEST); \ > + pass = false; \ > + } \ > + } while (0) > +#define OK 0 > +#define BAD -1, 0 > +At any rate, I think you're good to go. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
Eric Blake
2019-Sep-23 21:40 UTC
Re: [Libguestfs] [PATCH nbdkit v2] server: public: Add nbdkit_parse_* functions for safely parsing integers.
On 9/23/19 4:21 PM, Eric Blake wrote:>> else if (strcmp (key, "timeout") == 0) { >> - if (sscanf (value, "%ld", &timeout) != 1) { >> - nbdkit_error ("cannot parse timeout: %s", value); >> + if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1) >> + return -1; >> + /* Because we have to cast it to long before calling the libssh API. */ >> + if (timeout > LONG_MAX) { >> + nbdkit_error ("timeout too large"); > > C17 5.2.4.2.1 requires 'long' to be at least 32 bits. Ergo, (uint32_t) > timeout > LONG_MAX is always false. You could assert() rather than > trying to use nbdkit_error().Whoops, I mixed signed v unsigned. (int32_t)x > LONG_MAX is always false, (uint32_t)x > ULONG_MAX is always false, but (uint32_t)x > LONG_MAX can be true. Keep the check as-is. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
Reasonably Related Threads
- [PATCH nbdkit v2] server: public: Add nbdkit_parse_* functions for safely parsing integers.
- Re: [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.
- Re: [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.
- Re: [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.
- [PATCH nbdkit] server: public: Add nbdkit_parse_* functions for safely parsing integers.