summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJerry Jelinek <jerry.jelinek@joyent.com>2017-09-05 11:33:35 +0000
committerJerry Jelinek <jerry.jelinek@joyent.com>2017-09-05 11:33:35 +0000
commit9105a020d63cb56f2be32de07d869c429cf3d4a8 (patch)
tree15099569f7776b11f1f5dcb909fd2722b68dd68a
parentae3f6fefe9c3bb258f65cb0b40e132eeef10fb5a (diff)
parent27b4c18acb5aee865d218de91c475ae0a7cc4e8a (diff)
downloadillumos-joyent-9105a020d63cb56f2be32de07d869c429cf3d4a8.tar.gz
[illumos-gate merge]
commit 27b4c18acb5aee865d218de91c475ae0a7cc4e8a 8598 loader: Fix BSD label partition end sector calculation. commit 0a0551200ecbcd4f1b17560acaeeb7b6c8b0090e 640 number_to_scaled_string is duplicated in several commands commit 30c75cb09b4d5e86a94a25a9a7ab7481510b57b0 8093 loader.efi: cleanup loader main and implement comconsole commit ce1d04bfb4326d1e894811fc63d56219b33f2f7a 8447 uts: acpica_check_bios_date() instead of bios-free, use efi-systab commit a62d310a2be18bb1054fea448f82a292724aa661 8445 uts: pci_bios_get_irq_routing() should check for BIOS commit 1271e4b10dfaaed576c08a812f466f6e81370e5e 8585 improve batching done in zil_commit() commit e6ab4525d156c82445c116ecf6b2b874d5e9009d 8590 memory leak in dsl_destroy_snapshots_nvl()
-rw-r--r--usr/src/boot/Makefile.version2
-rw-r--r--usr/src/boot/sys/boot/common/bootstrap.h2
-rw-r--r--usr/src/boot/sys/boot/common/part.c2
-rw-r--r--usr/src/boot/sys/boot/efi/loader/comconsole.c551
-rw-r--r--usr/src/boot/sys/boot/efi/loader/main.c63
-rw-r--r--usr/src/boot/sys/boot/i386/libfirewire/dconsole.c17
-rw-r--r--usr/src/boot/sys/boot/i386/libi386/comconsole.c199
-rw-r--r--usr/src/boot/sys/boot/i386/libi386/nullconsole.c18
-rw-r--r--usr/src/boot/sys/boot/i386/libi386/spinconsole.c18
-rw-r--r--usr/src/boot/sys/boot/ofw/libofw/ofw_console.c17
-rw-r--r--usr/src/boot/sys/boot/userboot/userboot/userboot_cons.c17
-rw-r--r--usr/src/cmd/beadm/Makefile2
-rw-r--r--usr/src/cmd/beadm/beadm.c30
-rw-r--r--usr/src/cmd/du/du.c82
-rw-r--r--usr/src/cmd/fs.d/Makefile1
-rw-r--r--usr/src/cmd/fs.d/df.c101
-rw-r--r--usr/src/cmd/ls/ls.c106
-rw-r--r--usr/src/cmd/stat/fsstat/Makefile2
-rw-r--r--usr/src/cmd/stat/fsstat/fsstat.c157
-rw-r--r--usr/src/cmd/swap/Makefile.com2
-rw-r--r--usr/src/cmd/swap/swap.c177
-rw-r--r--usr/src/cmd/zdb/Makefile.com3
-rw-r--r--usr/src/cmd/zdb/zdb.c138
-rwxr-xr-xusr/src/cmd/ztest/zloop.bash1
-rw-r--r--usr/src/cmd/ztest/ztest.c32
-rw-r--r--usr/src/lib/libcmdutils/Makefile.com2
-rw-r--r--usr/src/lib/libcmdutils/common/mapfile-vers2
-rw-r--r--usr/src/lib/libcmdutils/common/nicenum.c130
-rw-r--r--usr/src/lib/libcmdutils/libcmdutils.h8
-rw-r--r--usr/src/lib/libzfs/Makefile.com2
-rw-r--r--usr/src/lib/libzfs/common/libzfs_util.c38
-rw-r--r--usr/src/lib/libzpool/common/llib-lzpool1
-rw-r--r--usr/src/lib/libzpool/common/sys/zfs_context.h2
-rw-r--r--usr/src/lib/libzpool/common/util.c47
-rw-r--r--usr/src/uts/common/fs/zfs/dmu.c7
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_destroy.c1
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dmu.h2
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zil.h8
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zil_impl.h72
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zio.h1
-rw-r--r--usr/src/uts/common/fs/zfs/txg.c12
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_vnops.c12
-rw-r--r--usr/src/uts/common/fs/zfs/zil.c1483
-rw-r--r--usr/src/uts/common/fs/zfs/zio.c33
-rw-r--r--usr/src/uts/common/fs/zfs/zvol.c15
-rw-r--r--usr/src/uts/common/sys/debug.h5
-rw-r--r--usr/src/uts/common/sys/time.h3
-rw-r--r--usr/src/uts/i86pc/os/pci_bios.c4
-rw-r--r--usr/src/uts/intel/io/acpica/acpica.c2
49 files changed, 2511 insertions, 1121 deletions
diff --git a/usr/src/boot/Makefile.version b/usr/src/boot/Makefile.version
index 4a0d16a0d4..a90435f0cd 100644
--- a/usr/src/boot/Makefile.version
+++ b/usr/src/boot/Makefile.version
@@ -33,4 +33,4 @@ LOADER_VERSION = 1.1
# Use date like formatting here, YYYY.MM.DD.XX, without leading zeroes.
# The version is processed from left to right, the version number can only
# be increased.
-BOOT_VERSION = $(LOADER_VERSION)-2017.6.29.1
+BOOT_VERSION = $(LOADER_VERSION)-2017.8.31.1
diff --git a/usr/src/boot/sys/boot/common/bootstrap.h b/usr/src/boot/sys/boot/common/bootstrap.h
index 83495af92c..f963ad5391 100644
--- a/usr/src/boot/sys/boot/common/bootstrap.h
+++ b/usr/src/boot/sys/boot/common/bootstrap.h
@@ -108,7 +108,7 @@ struct console
void (*c_out)(struct console *, int); /* emit c */
int (*c_in)(struct console *); /* wait for and return input */
int (*c_ready)(struct console *); /* return nonzer if input waiting */
- void *private; /* private data */
+ void *c_private; /* private data */
};
extern struct console *consoles[];
void cons_probe(void);
diff --git a/usr/src/boot/sys/boot/common/part.c b/usr/src/boot/sys/boot/common/part.c
index 8366c88234..0a91aa768e 100644
--- a/usr/src/boot/sys/boot/common/part.c
+++ b/usr/src/boot/sys/boot/common/part.c
@@ -530,7 +530,7 @@ ptable_bsdread(struct ptable *table, void *dev, diskread_t dread)
break;
entry->part.start = le32toh(part->p_offset) - raw_offset;
entry->part.end = entry->part.start +
- le32toh(part->p_size) + 1;
+ le32toh(part->p_size) - 1;
entry->part.type = bsd_parttype(part->p_fstype);
entry->part.index = i; /* starts from zero */
entry->type.bsd = part->p_fstype;
diff --git a/usr/src/boot/sys/boot/efi/loader/comconsole.c b/usr/src/boot/sys/boot/efi/loader/comconsole.c
new file mode 100644
index 0000000000..539637b833
--- /dev/null
+++ b/usr/src/boot/sys/boot/efi/loader/comconsole.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <stand.h>
+#include <sys/errno.h>
+#include <bootstrap.h>
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "loader_efi.h"
+
+static EFI_GUID serial = SERIAL_IO_PROTOCOL;
+
+#define COMC_TXWAIT 0x40000 /* transmit timeout */
+
+#ifndef COMSPEED
+#define COMSPEED 9600
+#endif
+
+struct serial {
+ uint64_t baudrate;
+ uint8_t databits;
+ EFI_PARITY_TYPE parity;
+ EFI_STOP_BITS_TYPE stopbits;
+ uint8_t ignore_cd; /* boolean */
+ uint8_t rtsdtr_off; /* boolean */
+ int ioaddr; /* index in handles array */
+ SERIAL_IO_INTERFACE *sio;
+};
+
+static void comc_probe(struct console *);
+static int comc_init(struct console *, int);
+static void comc_putchar(struct console *, int);
+static int comc_getchar(struct console *);
+static int comc_ischar(struct console *);
+static void comc_setup(struct console *);
+static char *comc_asprint_mode(struct serial *);
+static int comc_parse_mode(struct serial *, const char *);
+static int comc_mode_set(struct env_var *, int, const void *);
+static int comc_cd_set(struct env_var *, int, const void *);
+static int comc_rtsdtr_set(struct env_var *, int, const void *);
+
+struct console ttya = {
+ .c_name = "ttya",
+ .c_desc = "serial port a",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
+};
+
+struct console ttyb = {
+ .c_name = "ttyb",
+ .c_desc = "serial port b",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
+};
+
+struct console ttyc = {
+ .c_name = "ttyc",
+ .c_desc = "serial port c",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
+};
+
+struct console ttyd = {
+ .c_name = "ttyd",
+ .c_desc = "serial port d",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
+};
+
+EFI_STATUS
+efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
+{
+ UINTN bufsz = 0;
+ EFI_STATUS status;
+ EFI_HANDLE *handles;
+
+ /*
+ * get buffer size
+ */
+ *nhandles = 0;
+ status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return (status);
+
+ if ((handles = malloc(bufsz)) == NULL)
+ return (ENOMEM);
+
+ *nhandles = (int)(bufsz/sizeof (EFI_HANDLE));
+ /*
+ * get handle array
+ */
+ status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
+ if (EFI_ERROR(status)) {
+ free(handles);
+ *nhandles = 0;
+ } else
+ *handlep = handles;
+ return (status);
+}
+
+static void
+comc_probe(struct console *cp)
+{
+ EFI_STATUS status;
+ struct serial *port;
+ char name[20];
+ char value[20];
+ char *cons, *env;
+ EFI_HANDLE *handles = NULL; /* array of handles */
+ int nhandles = 0; /* number of handles in array */
+
+ /* are we already set up? */
+ if (cp->c_private != NULL)
+ return;
+
+ /* make sure the handles are available */
+ status = efi_serial_init(&handles, &nhandles);
+
+ cp->c_private = malloc(sizeof (struct serial));
+ port = cp->c_private;
+ port->baudrate = COMSPEED;
+
+ if (strcmp(cp->c_name, "ttya") == 0)
+ port->ioaddr = 0;
+ else if (strcmp(cp->c_name, "ttyb") == 0)
+ port->ioaddr = 1;
+ else if (strcmp(cp->c_name, "ttyc") == 0)
+ port->ioaddr = 2;
+ else if (strcmp(cp->c_name, "ttyd") == 0)
+ port->ioaddr = 3;
+
+ if (port->ioaddr >= nhandles)
+ port->ioaddr = -1; /* invalid port */
+
+ port->databits = 8; /* 8,n,1 */
+ port->parity = NoParity; /* 8,n,1 */
+ port->stopbits = OneStopBit; /* 8,n,1 */
+ port->ignore_cd = 1; /* ignore cd */
+ port->rtsdtr_off = 0; /* rts-dtr is on */
+ port->sio = NULL;
+
+ if (port->ioaddr != -1) {
+ status = BS->OpenProtocol(handles[port->ioaddr],
+ &serial, (void**)&port->sio, IH, NULL,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+
+ if (EFI_ERROR(status))
+ port->ioaddr = -1; /* invalid port */
+ }
+ if (handles != NULL)
+ free(handles);
+
+ snprintf(name, sizeof (name), "%s-mode", cp->c_name);
+ env = getenv(name);
+
+ if (env != NULL)
+ (void) comc_parse_mode(port, env);
+
+ env = comc_asprint_mode(port);
+
+ if (env != NULL) {
+ unsetenv(name);
+ env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
+ free(env);
+ }
+
+ snprintf(name, sizeof (name), "%s-ignore-cd", cp->c_name);
+ env = getenv(name);
+ if (env != NULL) {
+ if (strcmp(env, "true") == 0)
+ port->ignore_cd = 1;
+ else if (strcmp(env, "false") == 0)
+ port->ignore_cd = 0;
+ }
+
+ snprintf(value, sizeof (value), "%s",
+ port->ignore_cd? "true" : "false");
+ unsetenv(name);
+ env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
+
+ snprintf(name, sizeof (name), "%s-rts-dtr-off", cp->c_name);
+ env = getenv(name);
+ if (env != NULL) {
+ if (strcmp(env, "true") == 0)
+ port->rtsdtr_off = 1;
+ else if (strcmp(env, "false") == 0)
+ port->rtsdtr_off = 0;
+ }
+
+ snprintf(value, sizeof (value), "%s",
+ port->rtsdtr_off? "true" : "false");
+ unsetenv(name);
+ env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
+ comc_setup(cp);
+}
+
+static int
+comc_init(struct console *cp, int arg __attribute((unused)))
+{
+
+ comc_setup(cp);
+
+ if ((cp->c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
+ (C_PRESENTIN | C_PRESENTOUT))
+ return (CMD_OK);
+ return (CMD_ERROR);
+}
+
+static void
+comc_putchar(struct console *cp, int c)
+{
+ int wait;
+ EFI_STATUS status;
+ UINTN bufsz = 1;
+ char cb = c;
+ struct serial *sp = cp->c_private;
+
+ if (sp->sio == NULL)
+ return;
+
+ for (wait = COMC_TXWAIT; wait > 0; wait--) {
+ status = sp->sio->Write(sp->sio, &bufsz, &cb);
+ if (status != EFI_TIMEOUT)
+ break;
+ }
+}
+
+static int
+comc_getchar(struct console *cp)
+{
+ EFI_STATUS status;
+ UINTN bufsz = 1;
+ char c;
+ struct serial *sp = cp->c_private;
+
+ if (sp->sio == NULL || !comc_ischar(cp))
+ return (-1);
+
+ status = sp->sio->Read(sp->sio, &bufsz, &c);
+ if (EFI_ERROR(status) || bufsz == 0)
+ return (-1);
+
+ return (c);
+}
+
+static int
+comc_ischar(struct console *cp)
+{
+ EFI_STATUS status;
+ uint32_t control;
+ struct serial *sp = cp->c_private;
+
+ if (sp->sio == NULL)
+ return (0);
+
+ status = sp->sio->GetControl(sp->sio, &control);
+ if (EFI_ERROR(status))
+ return (0);
+
+ return (!(status & EFI_SERIAL_INPUT_BUFFER_EMPTY));
+}
+
+static char *
+comc_asprint_mode(struct serial *sp)
+{
+ char par = 'n', *buf;
+ int stop = 1;
+
+ if (sp == NULL)
+ return (NULL);
+
+ switch (sp->parity) {
+ case NoParity: par = 'n';
+ break;
+ case EvenParity: par = 'e';
+ break;
+ case OddParity: par = 'o';
+ break;
+ }
+ switch (sp->stopbits) {
+ case OneStopBit: stop = 1;
+ break;
+ case TwoStopBits: stop = 2;
+ break;
+ }
+
+ asprintf(&buf, "%ju,%d,%c,%d,-", sp->baudrate, sp->databits, par, stop);
+ return (buf);
+}
+
+static int
+comc_parse_mode(struct serial *sp, const char *value)
+{
+ unsigned long n;
+ uint64_t baudrate;
+ uint8_t databits = 8;
+ int parity = NoParity;
+ int stopbits = OneStopBit;
+ char *ep;
+
+ if (value == NULL || *value == '\0')
+ return (CMD_ERROR);
+
+ errno = 0;
+ n = strtoul(value, &ep, 10);
+ if (errno != 0 || *ep != ',')
+ return (CMD_ERROR);
+ baudrate = n;
+
+ ep++;
+ n = strtoul(ep, &ep, 10);
+ if (errno != 0 || *ep != ',')
+ return (CMD_ERROR);
+
+ switch (n) {
+ case 7: databits = 7;
+ break;
+ case 8: databits = 8;
+ break;
+ default:
+ return (CMD_ERROR);
+ }
+
+ ep++;
+ switch (*ep++) {
+ case 'n': parity = NoParity;
+ break;
+ case 'e': parity = EvenParity;
+ break;
+ case 'o': parity = OddParity;
+ break;
+ default:
+ return (CMD_ERROR);
+ }
+
+ if (*ep == ',')
+ ep++;
+ else
+ return (CMD_ERROR);
+
+ switch (*ep++) {
+ case '1': stopbits = OneStopBit;
+ break;
+ case '2': stopbits = TwoStopBits;
+ break;
+ default:
+ return (CMD_ERROR);
+ }
+
+ /* handshake is ignored, but we check syntax anyhow */
+ if (*ep == ',')
+ ep++;
+ else
+ return (CMD_ERROR);
+
+ switch (*ep++) {
+ case '-':
+ case 'h':
+ case 's':
+ break;
+ default:
+ return (CMD_ERROR);
+ }
+
+ if (*ep != '\0')
+ return (CMD_ERROR);
+
+ sp->baudrate = baudrate;
+ sp->databits = databits;
+ sp->parity = parity;
+ sp->stopbits = stopbits;
+ return (CMD_OK);
+}
+
+static struct console *
+get_console(char *name)
+{
+ struct console *cp = NULL;
+
+ switch (name[3]) {
+ case 'a': cp = &ttya;
+ break;
+ case 'b': cp = &ttyb;
+ break;
+ case 'c': cp = &ttyc;
+ break;
+ case 'd': cp = &ttyd;
+ break;
+ }
+ return (cp);
+}
+
+static int
+comc_mode_set(struct env_var *ev, int flags, const void *value)
+{
+ struct console *cp;
+
+ if (value == NULL)
+ return (CMD_ERROR);
+
+ if ((cp = get_console(ev->ev_name)) == NULL)
+ return (CMD_ERROR);
+
+ if (comc_parse_mode(cp->c_private, value) == CMD_ERROR)
+ return (CMD_ERROR);
+
+ comc_setup(cp);
+ env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
+
+ return (CMD_OK);
+}
+
+static int
+comc_cd_set(struct env_var *ev, int flags, const void *value)
+{
+ struct console *cp;
+ struct serial *sp;
+
+ if (value == NULL)
+ return (CMD_ERROR);
+
+ if ((cp = get_console(ev->ev_name)) == NULL)
+ return (CMD_ERROR);
+
+ sp = cp->c_private;
+ if (strcmp(value, "true") == 0)
+ sp->ignore_cd = 1;
+ else if (strcmp(value, "false") == 0)
+ sp->ignore_cd = 0;
+ else
+ return (CMD_ERROR);
+
+ comc_setup(cp);
+ env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
+
+ return (CMD_OK);
+}
+
+static int
+comc_rtsdtr_set(struct env_var *ev, int flags, const void *value)
+{
+ struct console *cp;
+ struct serial *sp;
+
+ if (value == NULL)
+ return (CMD_ERROR);
+
+ if ((cp = get_console(ev->ev_name)) == NULL)
+ return (CMD_ERROR);
+
+ sp = cp->c_private;
+ if (strcmp(value, "true") == 0)
+ sp->rtsdtr_off = 1;
+ else if (strcmp(value, "false") == 0)
+ sp->rtsdtr_off = 0;
+ else
+ return (CMD_ERROR);
+
+ comc_setup(cp);
+ env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
+
+ return (CMD_OK);
+}
+
+static void
+comc_setup(struct console *cp)
+{
+ EFI_STATUS status;
+ UINT32 control;
+ struct serial *sp = cp->c_private;
+
+ if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
+ return;
+
+ /* port is not usable */
+ if (sp->sio == NULL) {
+ cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ return;
+ }
+
+ cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
+ status = sp->sio->Reset(sp->sio);
+ if (EFI_ERROR(status)) {
+ cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ }
+
+ status = sp->sio->SetAttributes(sp->sio, sp->baudrate, 0, 0, sp->parity,
+ sp->databits, sp->stopbits);
+ if (EFI_ERROR(status)) {
+ cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ }
+
+ if (sp->rtsdtr_off) {
+ status = sp->sio->GetControl(sp->sio, &control);
+ if (EFI_ERROR(status)) {
+ cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ }
+ control &= ~(EFI_SERIAL_REQUEST_TO_SEND |
+ EFI_SERIAL_DATA_TERMINAL_READY);
+ status = sp->sio->SetControl(sp->sio, control);
+ if (EFI_ERROR(status)) {
+ cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ }
+ }
+}
diff --git a/usr/src/boot/sys/boot/efi/loader/main.c b/usr/src/boot/sys/boot/efi/loader/main.c
index 472ce86509..3ef635fd91 100644
--- a/usr/src/boot/sys/boot/efi/loader/main.c
+++ b/usr/src/boot/sys/boot/efi/loader/main.c
@@ -41,7 +41,6 @@
#include <efigpt.h>
#include <uuid.h>
-#include <stdbool.h>
#include <bootstrap.h>
#include <smbios.h>
@@ -61,10 +60,8 @@ EFI_GUID imgid = LOADED_IMAGE_PROTOCOL;
EFI_GUID smbios = SMBIOS_TABLE_GUID;
EFI_GUID smbios3 = SMBIOS3_TABLE_GUID;
EFI_GUID inputid = SIMPLE_TEXT_INPUT_PROTOCOL;
-EFI_GUID serial_io = SERIAL_IO_PROTOCOL;
extern void acpi_detect(void);
-void efi_serial_init(void);
extern void efi_getsmap(void);
#ifdef EFI_ZFS_BOOT
static void efi_zfs_probe(void);
@@ -415,7 +412,6 @@ main(int argc, CHAR16 *argv[])
ptr = efi_get_table(&smbios);
smbios_detect(ptr);
- efi_serial_init(); /* detect and set up serial ports */
interact(NULL); /* doesn't return */
return (EFI_SUCCESS); /* keep compiler happy */
@@ -424,8 +420,7 @@ main(int argc, CHAR16 *argv[])
COMMAND_SET(reboot, "reboot", "reboot the system", command_reboot);
static int
-command_reboot(int argc __attribute((unused)),
- char *argv[] __attribute((unused)))
+command_reboot(int argc __unused, char *argv[] __unused)
{
int i;
@@ -442,8 +437,7 @@ command_reboot(int argc __attribute((unused)),
COMMAND_SET(memmap, "memmap", "print memory map", command_memmap);
static int
-command_memmap(int argc __attribute((unused)),
- char *argv[] __attribute((unused)))
+command_memmap(int argc __unused, char *argv[] __unused)
{
UINTN sz;
EFI_MEMORY_DESCRIPTOR *map, *p;
@@ -523,8 +517,7 @@ COMMAND_SET(configuration, "configuration", "print configuration tables",
command_configuration);
static int
-command_configuration(int argc __attribute((unused)),
- char *argv[] __attribute((unused)))
+command_configuration(int argc __unused, char *argv[] __unused)
{
UINTN i;
char *name;
@@ -608,8 +601,7 @@ command_mode(int argc, char *argv[])
COMMAND_SET(lsefi, "lsefi", "list EFI handles", command_lsefi);
static int
-command_lsefi(int argc __attribute((unused)),
- char *argv[] __attribute((unused)))
+command_lsefi(int argc __unused, char *argv[] __unused)
{
char *name;
EFI_HANDLE *buffer = NULL;
@@ -730,53 +722,6 @@ command_reloadbe(int argc, char *argv[])
}
#endif /* __FreeBSD__ */
-void
-efi_serial_init(void)
-{
- EFI_HANDLE *buffer = NULL;
- UINTN bufsz = 0, i;
- EFI_STATUS status;
- int serial = 0;
-
- /*
- * get buffer size
- */
- status = BS->LocateHandle(ByProtocol, &serial_io, NULL, &bufsz, buffer);
- if (status != EFI_BUFFER_TOO_SMALL) {
- snprintf(command_errbuf, sizeof (command_errbuf),
- "unexpected error: %lld", (long long)status);
- return;
- }
- if ((buffer = malloc(bufsz)) == NULL) {
- sprintf(command_errbuf, "out of memory");
- return;
- }
-
- /*
- * get handle array
- */
- status = BS->LocateHandle(ByProtocol, &serial_io, NULL, &bufsz, buffer);
- if (EFI_ERROR(status)) {
- free(buffer);
- snprintf(command_errbuf, sizeof (command_errbuf),
- "LocateHandle() error: %lld", (long long)status);
- return;
- }
-
- for (i = 0; i < (bufsz / sizeof (EFI_HANDLE)); i++) {
- SERIAL_IO_INTERFACE *sio;
- status = BS->OpenProtocol(buffer[i], &serial_io, (void**)&sio,
- IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
- if (EFI_ERROR(status)) {
- snprintf(command_errbuf, sizeof (command_errbuf),
- "OpenProtocol() error: %lld", (long long)status);
- }
- printf("serial# %d\n", serial++);
- }
-
- free(buffer);
-}
-
#ifdef LOADER_FDT_SUPPORT
extern int command_fdt_internal(int argc, char *argv[]);
diff --git a/usr/src/boot/sys/boot/i386/libfirewire/dconsole.c b/usr/src/boot/sys/boot/i386/libfirewire/dconsole.c
index eccdc77799..a77e4ba098 100644
--- a/usr/src/boot/sys/boot/i386/libfirewire/dconsole.c
+++ b/usr/src/boot/sys/boot/i386/libfirewire/dconsole.c
@@ -60,14 +60,15 @@ uint32_t dcons_paddr;
static char dcons_buffer[DCONS_BUF_SIZE + PAGE_SIZE];
struct console dconsole = {
- "dcons",
- "dumb console port",
- 0,
- dconsole_probe,
- dconsole_init,
- dconsole_putchar,
- dconsole_getchar,
- dconsole_ischar
+ .c_name = "dcons",
+ .c_desc = "dumb console port",
+ .c_flags = 0,
+ .c_probe = dconsole_probe,
+ .c_init = dconsole_init,
+ .c_out = dconsole_putchar,
+ .c_in = dconsole_getchar,
+ .c_ready = dconsole_ischar,
+ .c_private = NULL
};
#define DCONSOLE_AS_MULTI_CONSOLE 1
diff --git a/usr/src/boot/sys/boot/i386/libi386/comconsole.c b/usr/src/boot/sys/boot/i386/libi386/comconsole.c
index a8ce7906a0..6f24526ac8 100644
--- a/usr/src/boot/sys/boot/i386/libi386/comconsole.c
+++ b/usr/src/boot/sys/boot/i386/libi386/comconsole.c
@@ -67,69 +67,68 @@ struct serial {
uint32_t locator;
};
-static void comc_probe(struct console *cp);
-static int comc_init(struct console *cp, int arg);
-static void comc_putchar(struct console *cp, int c);
-static int comc_getchar(struct console *cp);
-static int comc_getspeed(struct serial *sp);
-static int comc_ischar(struct console *cp);
-static uint32_t comc_parse_pcidev(const char *string);
-static int comc_pcidev_set(struct env_var *ev, int flags,
- const void *value);
-static int comc_pcidev_handle(struct console *cp, uint32_t locator);
-static void comc_setup(struct console *cp);
-static char *comc_print_mode(struct serial *sp, char *buf);
-static int comc_parse_mode(struct serial *sp, const char *value);
+static void comc_probe(struct console *);
+static int comc_init(struct console *, int);
+static void comc_putchar(struct console *, int);
+static int comc_getchar(struct console *);
+static int comc_getspeed(struct serial *);
+static int comc_ischar(struct console *);
+static uint32_t comc_parse_pcidev(const char *);
+static int comc_pcidev_set(struct env_var *, int, const void *);
+static int comc_pcidev_handle(struct console *, uint32_t);
+static void comc_setup(struct console *);
+static char *comc_asprint_mode(struct serial *);
+static int comc_parse_mode(struct serial *, const char *);
static int comc_mode_set(struct env_var *, int, const void *);
static int comc_cd_set(struct env_var *, int, const void *);
static int comc_rtsdtr_set(struct env_var *, int, const void *);
struct console ttya = {
- "ttya",
- "serial port a",
- 0,
- comc_probe,
- comc_init,
- comc_putchar,
- comc_getchar,
- comc_ischar,
- NULL
+ .c_name = "ttya",
+ .c_desc = "serial port a",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
};
struct console ttyb = {
- "ttyb",
- "serial port b",
- 0,
- comc_probe,
- comc_init,
- comc_putchar,
- comc_getchar,
- comc_ischar,
- NULL
+ .c_name = "ttyb",
+ .c_desc = "serial port b",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
};
struct console ttyc = {
- "ttyc",
- "serial port c",
- 0,
- comc_probe,
- comc_init,
- comc_putchar,
- comc_getchar,
- comc_ischar,
- NULL
+ .c_name = "ttyc",
+ .c_desc = "serial port c",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
};
struct console ttyd = {
- "ttyd",
- "serial port d",
- 0,
- comc_probe,
- comc_init,
- comc_putchar,
- comc_getchar,
- comc_ischar,
- NULL
+ .c_name = "ttyd",
+ .c_desc = "serial port d",
+ .c_flags = 0,
+ .c_probe = comc_probe,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+ .c_private = NULL
};
static void
@@ -140,9 +139,9 @@ comc_probe(struct console *cp)
char value[20];
char *cons, *env;
- if (cp->private == NULL) {
- cp->private = malloc(sizeof(struct serial));
- port = cp->private;
+ if (cp->c_private == NULL) {
+ cp->c_private = malloc(sizeof(struct serial));
+ port = cp->c_private;
port->speed = COMSPEED;
if (strcmp(cp->c_name, "ttya") == 0)
@@ -168,18 +167,21 @@ comc_probe(struct console *cp)
port->speed = comc_getspeed(port);
}
- snprintf(name, 20, "%s-mode", cp->c_name);
+ snprintf(name, sizeof (name), "%s-mode", cp->c_name);
env = getenv(name);
if (env != NULL) {
(void) comc_parse_mode(port, env);
}
- env = comc_print_mode(port, value);
+ env = comc_asprint_mode(port);
- unsetenv(name);
- env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
+ if (env != NULL) {
+ unsetenv(name);
+ env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
+ free(env);
+ }
- snprintf(name, 20, "%s-ignore-cd", cp->c_name);
+ snprintf(name, sizeof (name), "%s-ignore-cd", cp->c_name);
env = getenv(name);
if (env != NULL) {
if (strcmp(env, "true") == 0)
@@ -188,11 +190,12 @@ comc_probe(struct console *cp)
port->ignore_cd = 0;
}
- sprintf(value, "%s", port->ignore_cd? "true":"false");
+ snprintf(value, sizeof (value), "%s",
+ port->ignore_cd? "true" : "false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
- snprintf(name, 20, "%s-rts-dtr-off", cp->c_name);
+ snprintf(name, sizeof (name), "%s-rts-dtr-off", cp->c_name);
env = getenv(name);
if (env != NULL) {
if (strcmp(env, "true") == 0)
@@ -201,11 +204,12 @@ comc_probe(struct console *cp)
port->rtsdtr_off = 0;
}
- sprintf(value, "%s", port->rtsdtr_off? "true":"false");
+ snprintf(value, sizeof (value), "%s",
+ port->rtsdtr_off? "true" : "false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
- snprintf(name, 20, "%s-pcidev", cp->c_name);
+ snprintf(name, sizeof (name), "%s-pcidev", cp->c_name);
env = getenv(name);
if (env != NULL) {
port->locator = comc_parse_pcidev(env);
@@ -235,7 +239,7 @@ static void
comc_putchar(struct console *cp, int c)
{
int wait;
- struct serial *sp = cp->private;
+ struct serial *sp = cp->c_private;
for (wait = COMC_TXWAIT; wait > 0; wait--)
if (inb(sp->ioaddr + com_lsr) & LSR_TXRDY) {
@@ -247,21 +251,24 @@ comc_putchar(struct console *cp, int c)
static int
comc_getchar(struct console *cp)
{
- struct serial *sp = cp->private;
+ struct serial *sp = cp->c_private;
return (comc_ischar(cp) ? inb(sp->ioaddr + com_data) : -1);
}
static int
comc_ischar(struct console *cp)
{
- struct serial *sp = cp->private;
+ struct serial *sp = cp->c_private;
return (inb(sp->ioaddr + com_lsr) & LSR_RXRDY);
}
static char *
-comc_print_mode(struct serial *sp, char *buf)
+comc_asprint_mode(struct serial *sp)
{
- char par;
+ char par, *buf;
+
+ if (sp == NULL)
+ return (NULL);
if ((sp->lcr & (PAREN|PAREVN)) == (PAREN|PAREVN))
par = 'e';
@@ -270,32 +277,35 @@ comc_print_mode(struct serial *sp, char *buf)
else
par = 'n';
- sprintf(buf, "%d,%d,%c,%d,-", sp->speed,
+ asprintf(&buf, "%d,%d,%c,%d,-", sp->speed,
(sp->lcr & BITS8) == BITS8? 8:7,
- par, (par & STOP2) == STOP2? 2:1);
+ par, (sp->lcr & STOP2) == STOP2? 2:1);
return (buf);
}
static int
comc_parse_mode(struct serial *sp, const char *value)
{
- int n;
+ unsigned long n;
int speed;
int lcr;
char *ep;
- n = strtol(value, &ep, 0);
- if (n > 0)
- speed = n;
- else
+ if (value == NULL || *value == '\0')
return (CMD_ERROR);
- if (*ep == ',')
- ep++;
- else
+ errno = 0;
+ n = strtoul(value, &ep, 10);
+ if (errno != 0 || *ep != ',')
+ return (CMD_ERROR);
+ speed = n;
+
+ ep++;
+ errno = 0;
+ n = strtoul(ep, &ep, 10);
+ if (errno != 0 || *ep != ',')
return (CMD_ERROR);
- n = strtol(ep, &ep, 0);
switch (n) {
case 7: lcr = BITS7;
break;
@@ -305,11 +315,7 @@ comc_parse_mode(struct serial *sp, const char *value)
return (CMD_ERROR);
}
- if (*ep == ',')
- ep++;
- else
- return (CMD_ERROR);
-
+ ep++;
switch (*ep++) {
case 'n':
break;
@@ -387,7 +393,7 @@ comc_mode_set(struct env_var *ev, int flags, const void *value)
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
- if (comc_parse_mode(cp->private, value) == CMD_ERROR)
+ if (comc_parse_mode(cp->c_private, value) == CMD_ERROR)
return (CMD_ERROR);
comc_setup(cp);
@@ -409,7 +415,7 @@ comc_cd_set(struct env_var *ev, int flags, const void *value)
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
- sp = cp->private;
+ sp = cp->c_private;
if (strcmp(value, "true") == 0)
sp->ignore_cd = 1;
else if (strcmp(value, "false") == 0)
@@ -436,7 +442,7 @@ comc_rtsdtr_set(struct env_var *ev, int flags, const void *value)
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
- sp = cp->private;
+ sp = cp->c_private;
if (strcmp(value, "true") == 0)
sp->rtsdtr_off = 1;
else if (strcmp(value, "false") == 0)
@@ -467,27 +473,28 @@ comc_parse_pcidev(const char *string)
uint32_t locator;
int pres;
- pres = strtol(string, &p, 0);
- if (p == string || *p != ':' || pres < 0 )
+ errno = 0;
+ pres = strtoul(string, &p, 10);
+ if (errno != 0 || p == string || *p != ':' || pres < 0 )
return (0);
bus = pres;
p1 = ++p;
- pres = strtol(p1, &p, 0);
- if (p == string || *p != ':' || pres < 0 )
+ pres = strtoul(p1, &p, 10);
+ if (errno != 0 || p == string || *p != ':' || pres < 0 )
return (0);
dev = pres;
p1 = ++p;
- pres = strtol(p1, &p, 0);
- if (p == string || (*p != ':' && *p != '\0') || pres < 0 )
+ pres = strtoul(p1, &p, 10);
+ if (errno != 0 || p == string || (*p != ':' && *p != '\0') || pres < 0 )
return (0);
func = pres;
if (*p == ':') {
p1 = ++p;
- pres = strtol(p1, &p, 0);
- if (p == string || *p != '\0' || pres <= 0 )
+ pres = strtoul(p1, &p, 10);
+ if (errno != 0 || p == string || *p != '\0' || pres <= 0 )
return (0);
bar = pres;
} else
@@ -506,7 +513,7 @@ comc_pcidev_handle(struct console *cp, uint32_t locator)
(void)locator;
return (CMD_ERROR);
#else
- struct serial *sp = cp->private;
+ struct serial *sp = cp->c_private;
char intbuf[64];
uint32_t port;
@@ -538,7 +545,7 @@ comc_pcidev_set(struct env_var *ev, int flags, const void *value)
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
- sp = cp->private;
+ sp = cp->c_private;
if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) {
printf("Invalid pcidev\n");
@@ -557,7 +564,7 @@ comc_pcidev_set(struct env_var *ev, int flags, const void *value)
static void
comc_setup(struct console *cp)
{
- struct serial *sp = cp->private;
+ struct serial *sp = cp->c_private;
static int TRY_COUNT = 1000000;
int tries;
diff --git a/usr/src/boot/sys/boot/i386/libi386/nullconsole.c b/usr/src/boot/sys/boot/i386/libi386/nullconsole.c
index cd9f3a514a..bef9841ffd 100644
--- a/usr/src/boot/sys/boot/i386/libi386/nullconsole.c
+++ b/usr/src/boot/sys/boot/i386/libi386/nullconsole.c
@@ -47,15 +47,15 @@ static int nullc_getchar(struct console *);
static int nullc_ischar(struct console *);
struct console nullconsole = {
- "null",
- "null port",
- 0,
- nullc_probe,
- nullc_init,
- nullc_putchar,
- nullc_getchar,
- nullc_ischar,
- NULL
+ .c_name = "null",
+ .c_desc = "null port",
+ .c_flags = 0,
+ .c_probe = nullc_probe,
+ .c_init = nullc_init,
+ .c_out = nullc_putchar,
+ .c_in = nullc_getchar,
+ .c_ready = nullc_ischar,
+ .c_private = NULL
};
static void
diff --git a/usr/src/boot/sys/boot/i386/libi386/spinconsole.c b/usr/src/boot/sys/boot/i386/libi386/spinconsole.c
index 762b88abd1..47a85da493 100644
--- a/usr/src/boot/sys/boot/i386/libi386/spinconsole.c
+++ b/usr/src/boot/sys/boot/i386/libi386/spinconsole.c
@@ -55,15 +55,15 @@ static int spinc_getchar(struct console *cp);
static int spinc_ischar(struct console *cp);
struct console spinconsole = {
- "spin",
- "spin port",
- 0,
- spinc_probe,
- spinc_init,
- spinc_putchar,
- spinc_getchar,
- spinc_ischar,
- NULL
+ .c_name = "spin",
+ .c_desc = "spin port",
+ .c_flags = 0,
+ .c_probe = spinc_probe,
+ .c_init = spinc_init,
+ .c_out = spinc_putchar,
+ .c_in = spinc_getchar,
+ .c_ready = spinc_ischar,
+ .c_private = NULL
};
static void
diff --git a/usr/src/boot/sys/boot/ofw/libofw/ofw_console.c b/usr/src/boot/sys/boot/ofw/libofw/ofw_console.c
index 59ce9a5067..a46c4a66ae 100644
--- a/usr/src/boot/sys/boot/ofw/libofw/ofw_console.c
+++ b/usr/src/boot/sys/boot/ofw/libofw/ofw_console.c
@@ -44,14 +44,15 @@ static ihandle_t stdin;
static ihandle_t stdout;
struct console ofwconsole = {
- "ofw",
- "Open Firmware console",
- 0,
- ofw_cons_probe,
- ofw_cons_init,
- ofw_cons_putchar,
- ofw_cons_getchar,
- ofw_cons_poll,
+ .c_name = "ofw",
+ .c_desc = "Open Firmware console",
+ .c_flags = 0,
+ .c_probe = ofw_cons_probe,
+ .c_init = ofw_cons_init,
+ .c_out = ofw_cons_putchar,
+ .c_in = ofw_cons_getchar,
+ .c_ready = ofw_cons_poll,
+ .c_private = NULL
};
static void
diff --git a/usr/src/boot/sys/boot/userboot/userboot/userboot_cons.c b/usr/src/boot/sys/boot/userboot/userboot/userboot_cons.c
index 6f73ad5633..9b388e5f08 100644
--- a/usr/src/boot/sys/boot/userboot/userboot/userboot_cons.c
+++ b/usr/src/boot/sys/boot/userboot/userboot/userboot_cons.c
@@ -44,14 +44,15 @@ static int userboot_cons_getchar(void);
static int userboot_cons_poll(void);
struct console userboot_console = {
- "userboot",
- "userboot",
- 0,
- userboot_cons_probe,
- userboot_cons_init,
- userboot_cons_putchar,
- userboot_cons_getchar,
- userboot_cons_poll,
+ .c_name = "userboot",
+ .c_desc = "userboot",
+ .c_flags = 0,
+ .c_probe = userboot_cons_probe,
+ .c_init = userboot_cons_init,
+ .c_out = userboot_cons_putchar,
+ .c_in = userboot_cons_getchar,
+ .c_ready = userboot_cons_poll,
+ .c_private = NULL
};
/*
diff --git a/usr/src/cmd/beadm/Makefile b/usr/src/cmd/beadm/Makefile
index 8ea7ac8cae..e19adc52bc 100644
--- a/usr/src/cmd/beadm/Makefile
+++ b/usr/src/cmd/beadm/Makefile
@@ -30,7 +30,7 @@ POFILE= beadm.po
include ../Makefile.cmd
-LDLIBS += -lnvpair -lbe
+LDLIBS += -lnvpair -lbe -lcmdutils
CERRWARN += -_gcc=-Wno-parentheses
$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
diff --git a/usr/src/cmd/beadm/beadm.c b/usr/src/cmd/beadm/beadm.c
index 00073ae717..3667c762a4 100644
--- a/usr/src/cmd/beadm/beadm.c
+++ b/usr/src/cmd/beadm/beadm.c
@@ -25,6 +25,7 @@
* Copyright 2015 Toomas Soome <tsoome@me.com>
* Copyright 2015 Gary Mills
* Copyright (c) 2015 by Delphix. All rights reserved.
+ * Copyright 2017 Jason King
*/
/*
@@ -40,6 +41,8 @@
#include <stdlib.h>
#include <wchar.h>
#include <sys/types.h>
+#include <sys/debug.h>
+#include <libcmdutils.h>
#include "libbe.h"
@@ -55,6 +58,7 @@
#define DT_BUF_LEN (128)
#define NUM_COLS (6)
+CTASSERT(DT_BUF_LEN >= NN_NUMBUF_SZ);
static int be_do_activate(int argc, char **argv);
static int be_do_create(int argc, char **argv);
@@ -249,32 +253,6 @@ init_hdr_cols(enum be_fmt be_fmt, struct hdr_info *hdr)
}
static void
-nicenum(uint64_t num, char *buf, size_t buflen)
-{
- uint64_t n = num;
- int index = 0;
- char u;
-
- while (n >= 1024) {
- n /= 1024;
- index++;
- }
-
- u = " KMGTPE"[index];
-
- if (index == 0) {
- (void) snprintf(buf, buflen, "%llu", n);
- } else {
- int i;
- for (i = 2; i >= 0; i--) {
- if (snprintf(buf, buflen, "%.*f%c", i,
- (double)num / (1ULL << 10 * index), u) <= 5)
- break;
- }
- }
-}
-
-static void
count_widths(enum be_fmt be_fmt, struct hdr_info *hdr, be_node_list_t *be_nodes)
{
size_t len[NUM_COLS];
diff --git a/usr/src/cmd/du/du.c b/usr/src/cmd/du/du.c
index 3911135b50..24ca7aca50 100644
--- a/usr/src/cmd/du/du.c
+++ b/usr/src/cmd/du/du.c
@@ -22,6 +22,7 @@
* Copyright 2017 OmniTI Computer Consulting, Inc. All rights reserved.
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ * Copyright 2017 Jason King
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
@@ -66,9 +67,6 @@ static char *name;
static size_t base_len = PATH_MAX + 1; /* # of chars for base */
static size_t name_len = PATH_MAX + 1; /* # of chars for name */
-#define NUMBER_WIDTH 64
-typedef char numbuf_t[NUMBER_WIDTH];
-
/*
* Output formats. illumos uses a tab as separator, XPG4 a space.
*/
@@ -528,75 +526,6 @@ descend(char *curname, int curfd, int *retcode, dev_t device)
return (blocks);
}
-/*
- * Convert an unsigned long long to a string representation and place the
- * result in the caller-supplied buffer.
- * The given number is in units of "unit_from" size,
- * this will first be converted to a number in 1024 or 1000 byte size,
- * depending on the scaling factor.
- * Then the number is scaled down until it is small enough to be in a good
- * human readable format i.e. in the range 0 thru scale-1.
- * If it's smaller than 10 there's room enough to provide one decimal place.
- * The value "(unsigned long long)-1" is a special case and is always
- * converted to "-1".
- * Returns a pointer to the caller-supplied buffer.
- */
-static char *
-number_to_scaled_string(
- numbuf_t buf, /* put the result here */
- unsigned long long number, /* convert this number */
- unsigned long long unit_from, /* number of bytes per input unit */
- unsigned long long scale) /* 1024 (-h) or 1000 (-H) */
-{
- unsigned long long save = 0;
- char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
- char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */
-
- if ((long long)number == (long long)-1) {
- (void) strcpy(buf, "-1");
- return (buf);
- }
-
- /*
- * Convert number from unit_from to given scale (1024 or 1000)
- * This means multiply number with unit_from and divide by scale.
- * if number is large enough, we first divide and then multiply
- * to avoid an overflow
- * (large enough here means 100 (rather arbitrary value)
- * times scale in order to reduce rounding errors)
- * otherwise, we first multiply and then divide
- * to avoid an underflow
- */
- if (number >= 100L * scale) {
- number = number / scale;
- number = number * unit_from;
- } else {
- number = number * unit_from;
- number = number / scale;
- }
-
- /*
- * Now we have number as a count of scale units.
- * Stop scaling when we reached exa bytes, then something is
- * probably wrong with our number.
- */
- while ((number >= scale) && (*uom != 'E')) {
- uom++; /* next unit of measurement */
- save = number;
- number = (number + (scale / 2)) / scale;
- }
-
- /* check if we should output a decimal place after the point */
- if (save && ((save / scale) < 10)) {
- /* sprintf() will round for us */
- float fnum = (float)save / scale;
- (void) sprintf(buf, "%4.1f%c", fnum, *uom);
- } else {
- (void) sprintf(buf, "%4llu%c", number, *uom);
- }
- return (buf);
-}
-
static void
printsize(blkcnt_t blocks, char *path)
{
@@ -605,11 +534,10 @@ printsize(blkcnt_t blocks, char *path)
bsize = Aflg ? 1 : DEV_BSIZE;
if (hflg) {
- numbuf_t numbuf;
- unsigned long long scale = 1024L;
- (void) printf(FORMAT1,
- number_to_scaled_string(numbuf, blocks, bsize, scale),
- path);
+ char buf[NN_NUMBUF_SZ] = { 0 };
+
+ nicenum_scale(blocks, bsize, buf, sizeof (buf), 0);
+ (void) printf(FORMAT1, buf, path);
} else if (kflg) {
(void) printf(FORMAT2, (long long)kb(blocks), path);
} else if (mflg) {
diff --git a/usr/src/cmd/fs.d/Makefile b/usr/src/cmd/fs.d/Makefile
index 54460a6084..8f8d752a94 100644
--- a/usr/src/cmd/fs.d/Makefile
+++ b/usr/src/cmd/fs.d/Makefile
@@ -88,6 +88,7 @@ CERRWARN += -_gcc=-Wno-unused-variable
CERRWARN += -_gcc=-Wno-uninitialized
CERRWARN += -_gcc=-Wno-unused-function
+$(DFPROG) := LDLIBS += -lcmdutils
$(SPPROG) := LDLIBS += -lkstat
$(ROOTETCMNTTAB) := FILEMODE = 444
diff --git a/usr/src/cmd/fs.d/df.c b/usr/src/cmd/fs.d/df.c
index 3ba4aeb832..ee7bf6e7d5 100644
--- a/usr/src/cmd/fs.d/df.c
+++ b/usr/src/cmd/fs.d/df.c
@@ -26,6 +26,9 @@
* Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
*/
+/*
+ * Copyright 2016 Jason King
+ */
#include <dlfcn.h>
#include <stdio.h>
@@ -48,7 +51,9 @@
#include <sys/mkdev.h>
#include <sys/int_limits.h>
#include <sys/zone.h>
+#include <sys/debug.h>
#include <libzfs.h>
+#include <libcmdutils.h>
#include "fslib.h"
@@ -134,6 +139,7 @@ extern char *default_fstype(char *);
/* message */
#define NUMBER_WIDTH 40
+CTASSERT(NUMBER_WIDTH >= NN_NUMBUF_SZ);
/*
* A numbuf_t is used when converting a number to a string representation
@@ -219,7 +225,6 @@ static bool_int o_option;
static bool_int tty_output;
static bool_int use_scaling;
-static int scale;
static void usage(void);
static void do_devnm(int, char **);
@@ -591,7 +596,6 @@ parse_options(int argc, char *argv[])
SET_OPTION(g);
} else if (arg == 'h') {
use_scaling = TRUE;
- scale = 1024;
} else if (arg == 'k' && ! k_option) {
SET_OPTION(k);
} else if (arg == 'l' && ! l_option) {
@@ -967,8 +971,7 @@ run_fs_specific_df(struct df_request request_list[], int entries)
*/
static int
prune_list(struct df_request request_list[],
- size_t n_requests,
- size_t *valid_requests)
+ size_t n_requests, size_t *valid_requests)
{
size_t i;
size_t n_valid = 0;
@@ -1167,73 +1170,6 @@ number_to_string(
}
/*
- * Convert an unsigned long long to a string representation and place the
- * result in the caller-supplied buffer.
- * The given number is in units of "unit_from" size,
- * this will first be converted to a number in 1024 or 1000 byte size,
- * depending on the scaling factor.
- * Then the number is scaled down until it is small enough to be in a good
- * human readable format i.e. in the range 0 thru scale-1.
- * If it's smaller than 10 there's room enough to provide one decimal place.
- * The value "(unsigned long long)-1" is a special case and is always
- * converted to "-1".
- * Returns a pointer to the caller-supplied buffer.
- */
-static char *
-number_to_scaled_string(
- numbuf_t buf, /* put the result here */
- unsigned long long number, /* convert this number */
- int unit_from,
- int scale)
-{
- unsigned long long save = 0;
- char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
- char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */
-
- if ((long long)number == (long long)-1) {
- (void) strcpy(buf, "-1");
- return (buf);
- }
-
- /*
- * Convert number from unit_from to given scale (1024 or 1000).
- * This means multiply number by unit_from and divide by scale.
- *
- * Would like to multiply by unit_from and then divide by scale,
- * but if the first multiplication would overflow, then need to
- * divide by scale and then multiply by unit_from.
- */
- if (number > (UINT64_MAX / (unsigned long long)unit_from)) {
- number = (number / (unsigned long long)scale) *
- (unsigned long long)unit_from;
- } else {
- number = (number * (unsigned long long)unit_from) /
- (unsigned long long)scale;
- }
-
- /*
- * Now we have number as a count of scale units.
- * Stop scaling when we reached exa bytes, then something is
- * probably wrong with our number.
- */
-
- while ((number >= scale) && (*uom != 'E')) {
- uom++; /* next unit of measurement */
- save = number;
- number = (number + (scale / 2)) / scale;
- }
- /* check if we should output a decimal place after the point */
- if (save && ((save / scale) < 10)) {
- /* sprintf() will round for us */
- float fnum = (float)save / scale;
- (void) sprintf(buf, "%2.1f%c", fnum, *uom);
- } else {
- (void) sprintf(buf, "%4llu%c", number, *uom);
- }
- return (buf);
-}
-
-/*
* The statvfs() implementation allows us to return only two values, the total
* number of blocks and the number of blocks free. The equation 'used = total -
* free' will not work for ZFS filesystems, due to the nature of pooled storage.
@@ -1488,16 +1424,19 @@ k_output(struct df_request *dfrp, struct statvfs64 *fsp)
adjust_total_blocks(dfrp, &total_blocks, fsp->f_frsize);
if (use_scaling) { /* comes from the -h option */
- (void) printf("%-*s %*s %*s %*s %-*s %-s\n",
- FILESYSTEM_WIDTH, file_system,
- SCALED_WIDTH, number_to_scaled_string(total_blocks_buf,
- total_blocks, fsp->f_frsize, scale),
- SCALED_WIDTH, number_to_scaled_string(used_blocks_buf,
- used_blocks, fsp->f_frsize, scale),
- AVAILABLE_WIDTH, number_to_scaled_string(available_blocks_buf,
- available_blocks, fsp->f_frsize, scale),
- CAPACITY_WIDTH, capacity_buf,
- DFR_MOUNT_POINT(dfrp));
+ nicenum_scale(total_blocks, fsp->f_frsize,
+ total_blocks_buf, sizeof (total_blocks_buf), 0);
+ nicenum_scale(used_blocks, fsp->f_frsize,
+ used_blocks_buf, sizeof (used_blocks_buf), 0);
+ nicenum_scale(available_blocks, fsp->f_frsize,
+ available_blocks_buf, sizeof (available_blocks_buf), 0);
+
+ (void) printf("%-*s %*s %*s %*s %-*s %-s\n",
+ FILESYSTEM_WIDTH, file_system,
+ SCALED_WIDTH, total_blocks_buf,
+ SCALED_WIDTH, used_blocks_buf,
+ AVAILABLE_WIDTH, available_blocks_buf,
+ CAPACITY_WIDTH, capacity_buf, DFR_MOUNT_POINT(dfrp));
return;
}
diff --git a/usr/src/cmd/ls/ls.c b/usr/src/cmd/ls/ls.c
index 1adab128a4..876ea20117 100644
--- a/usr/src/cmd/ls/ls.c
+++ b/usr/src/cmd/ls/ls.c
@@ -26,7 +26,7 @@
*/
/*
- * Copyright 2009 Jason King. All rights reserved.
+ * Copyright 2017 Jason King. All rights reserved.
* Use is subject to license terms.
*/
@@ -125,7 +125,6 @@
#undef BUFSIZ
#define BUFSIZ 4096
-#define NUMBER_WIDTH 40
#define FMTSIZE 50
struct ditem {
@@ -216,11 +215,6 @@ struct dchain {
struct dchain *dc_next; /* next directory in the chain */
};
-/*
- * A numbuf_t is used when converting a number to a string representation
- */
-typedef char numbuf_t[NUMBER_WIDTH];
-
static struct dchain *dfirst; /* start of the dir chain */
static struct dchain *cdfirst; /* start of the current dir chain */
static struct dchain *dtemp; /* temporary - used for linking */
@@ -252,9 +246,6 @@ static struct cachenode *findincache(struct cachenode **, long);
static void csi_pprintf(unsigned char *);
static void pprintf(char *, char *);
static int compar(struct lbuf **pp1, struct lbuf **pp2);
-static char *number_to_scaled_string(numbuf_t buf,
- unsigned long long number,
- long scale);
static void record_ancestry(char *, struct stat *, struct lbuf *,
int, struct ditem *);
static void ls_color_init(void);
@@ -306,7 +297,7 @@ static int atm;
static int mtm;
static int crtm;
static int alltm;
-static long hscale;
+static uint_t nicenum_flags;
static mode_t flags;
static int err = 0; /* Contains return code */
static int colorflg;
@@ -481,7 +472,7 @@ main(int argc, char *argv[])
if (strcmp(long_options[option_index].name,
"si") == 0) {
hflg++;
- hscale = 1000;
+ nicenum_flags |= NN_DIVISOR_1000;
continue;
}
@@ -751,7 +742,6 @@ main(int argc, char *argv[])
continue;
case 'h':
hflg++;
- hscale = 1024;
continue;
case 'H':
Hflg++;
@@ -1255,7 +1245,6 @@ static void
pentry(struct lbuf *ap)
{
struct lbuf *p;
- numbuf_t hbuf;
char *dmark = ""; /* Used if -p or -F option active */
char *cp;
char *str;
@@ -1308,9 +1297,13 @@ pentry(struct lbuf *ap)
curcol += printf("%3u, %2u",
(uint_t)major((dev_t)p->lsize),
(uint_t)minor((dev_t)p->lsize));
- } else if (hflg && (p->lsize >= hscale)) {
- curcol += printf("%7s",
- number_to_scaled_string(hbuf, p->lsize, hscale));
+ } else if (hflg) {
+ char numbuf[NN_NUMBUF_SZ];
+
+ nicenum_scale(p->lsize, 1, numbuf, sizeof (numbuf),
+ nicenum_flags);
+
+ curcol += printf("%7s", numbuf);
} else {
uint64_t bsize = p->lsize / block_size;
@@ -2444,85 +2437,6 @@ strcol(unsigned char *s1)
return (w);
}
-/*
- * Convert an unsigned long long to a string representation and place the
- * result in the caller-supplied buffer.
- *
- * The number provided is a size in bytes. The number is first
- * converted to an integral multiple of 'scale' bytes. This new
- * number is then scaled down until it is small enough to be in a good
- * human readable format, i.e. in the range 0 thru scale-1. If the
- * number used to derive the final number is not a multiple of scale, and
- * the final number has only a single significant digit, we compute
- * tenths of units to provide a second significant digit.
- *
- * The value "(unsigned long long)-1" is a special case and is always
- * converted to "-1".
- *
- * A pointer to the caller-supplied buffer is returned.
- */
-static char *
-number_to_scaled_string(
- numbuf_t buf, /* put the result here */
- unsigned long long number, /* convert this number */
- long scale)
-{
- unsigned long long save;
- /* Measurement: kilo, mega, giga, tera, peta, exa */
- char *uom = "KMGTPE";
-
- if ((long long)number == (long long)-1) {
- (void) strlcpy(buf, "-1", sizeof (numbuf_t));
- return (buf);
- }
-
- save = number;
- number = number / scale;
-
- /*
- * Now we have number as a count of scale units.
- * If no further scaling is necessary, we round up as appropriate.
- *
- * The largest value number could have had entering the routine is
- * 16 Exabytes, so running off the end of the uom array should
- * never happen. We check for that, though, as a guard against
- * a breakdown elsewhere in the algorithm.
- */
- if (number < (unsigned long long)scale) {
- if ((save % scale) >= (unsigned long long)(scale / 2)) {
- if (++number == (unsigned long long)scale) {
- uom++;
- number = 1;
- }
- }
- } else {
- while ((number >= (unsigned long long)scale) && (*uom != 'E')) {
- uom++; /* next unit of measurement */
- save = number;
- /*
- * If we're over half way to the next unit of
- * 'scale' bytes (which means we should round
- * up), then adding half of 'scale' prior to
- * the division will push us into that next
- * unit of scale when we perform the division
- */
- number = (number + (scale / 2)) / scale;
- }
- }
-
- /* check if we should output a decimal place after the point */
- if ((save / scale) < 10) {
- /* snprintf() will round for us */
- float fnum = (float)save / scale;
- (void) snprintf(buf, sizeof (numbuf_t), "%2.1f%c",
- fnum, *uom);
- } else {
- (void) snprintf(buf, sizeof (numbuf_t), "%4llu%c",
- number, *uom);
- }
- return (buf);
-}
-
/* Get extended system attributes and set the display */
int
diff --git a/usr/src/cmd/stat/fsstat/Makefile b/usr/src/cmd/stat/fsstat/Makefile
index 26bfdb023b..b86e2cc8f7 100644
--- a/usr/src/cmd/stat/fsstat/Makefile
+++ b/usr/src/cmd/stat/fsstat/Makefile
@@ -33,7 +33,7 @@ include $(SRC)/cmd/stat/Makefile.stat
COMMON_OBJS = common.o timestamp.o
COMMON_SRCS = $(COMMON_OBJS:%.o=$(STATCOMMONDIR)/%.c)
-LDLIBS += -lkstat
+LDLIBS += -lkstat -lcmdutils
CFLAGS += $(CCVERBOSE) -I${STATCOMMONDIR}
CERRWARN += -_gcc=-Wno-parentheses
FILEMODE= 0555
diff --git a/usr/src/cmd/stat/fsstat/fsstat.c b/usr/src/cmd/stat/fsstat/fsstat.c
index 5563f410ca..6bf07e333e 100644
--- a/usr/src/cmd/stat/fsstat/fsstat.c
+++ b/usr/src/cmd/stat/fsstat/fsstat.c
@@ -22,6 +22,7 @@
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright (c) 2016 by Delphix. All rights reserved.
+ * Copyright 2017 Jason King
*/
#include <stdio.h>
@@ -41,12 +42,14 @@
#include <sys/fstyp.h>
#include <sys/fsid.h>
#include <sys/mnttab.h>
+#include <sys/debug.h>
#include <values.h>
#include <poll.h>
#include <ctype.h>
#include <libintl.h>
#include <locale.h>
#include <signal.h>
+#include <libcmdutils.h>
#include "statcommon.h"
@@ -73,12 +76,7 @@
#define HEADERLINES 12 /* Number of lines between display headers */
#define LBUFSZ 64 /* Generic size for local buffer */
-
-/*
- * The following are used for the nicenum() function
- */
-#define KILO_VAL 1024
-#define ONE_INDEX 3
+CTASSERT(LBUFSZ >= NN_NUMBUF_SZ);
#define NENTITY_INIT 1 /* Initial number of entities to allocate */
@@ -112,9 +110,6 @@ typedef struct entity {
#define ENTYPE_FSTYPE 1
#define ENTYPE_MNTPT 2
-/* If more sub-one units are added, make sure to adjust ONE_INDEX above */
-static char units[] = "num KMGTPE";
-
char *cmdname; /* name of this command */
int caught_cont = 0; /* have caught a SIGCONT */
@@ -131,70 +126,14 @@ usage()
exit(2);
}
-/*
- * Given a 64-bit number and a starting unit (e.g., n - nanoseconds),
- * convert the number to a 5-character representation including any
- * decimal point and single-character unit. Put that representation
- * into the array "buf" (which had better be big enough).
- */
-char *
-nicenum(uint64_t num, char unit, char *buf)
-{
- uint64_t n = num;
- int unit_index;
- int index;
- char u;
-
- /* If the user passed in a NUL/zero unit, use the blank value for 1 */
- if (unit == '\0')
- unit = ' ';
-
- unit_index = 0;
- while (units[unit_index] != unit) {
- unit_index++;
- if (unit_index > sizeof (units) - 1) {
- (void) sprintf(buf, "??");
- return (buf);
- }
- }
-
- index = 0;
- while (n >= KILO_VAL) {
- n = (n + (KILO_VAL / 2)) / KILO_VAL; /* Round up or down */
- index++;
- unit_index++;
- }
-
- if (unit_index >= sizeof (units) - 1) {
- (void) sprintf(buf, "??");
- return (buf);
- }
-
- u = units[unit_index];
-
- if (unit_index == ONE_INDEX) {
- (void) sprintf(buf, "%llu", (u_longlong_t)n);
- } else if (n < 10 && (num & (num - 1)) != 0) {
- (void) sprintf(buf, "%.2f%c",
- (double)num / (1ULL << 10 * index), u);
- } else if (n < 100 && (num & (num - 1)) != 0) {
- (void) sprintf(buf, "%.1f%c",
- (double)num / (1ULL << 10 * index), u);
- } else {
- (void) sprintf(buf, "%llu%c", (u_longlong_t)n, u);
- }
-
- return (buf);
-}
-
-
#define RAWVAL(ptr, member) ((ptr)->member.value.ui64)
#define DELTA(member) \
(newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0))
-#define PRINTSTAT(isnice, nicestring, rawstring, rawval, unit, buf) \
+#define PRINTSTAT(isnice, nicestring, rawstring, rawval, buf) \
(isnice) ? \
- (void) printf((nicestring), nicenum(rawval, unit, buf)) \
+ nicenum(rawval, buf, sizeof (buf)), \
+ (void) printf((nicestring), (buf)) \
: \
(void) printf((rawstring), (rawval))
@@ -256,17 +195,17 @@ dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
" file remov chng get set ops ops ops bytes ops bytes\n");
}
- PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, buf);
(void) printf("%s\n", name);
}
@@ -282,17 +221,17 @@ io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
" ops bytes ops bytes ops bytes ops ops\n");
}
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nrwlock), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), ' ', buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nrwlock), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), buf);
(void) printf("%s\n", name);
}
@@ -307,12 +246,12 @@ vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
(void) printf(" map addmap delmap getpag putpag pagio\n");
}
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), buf);
(void) printf("%s\n", name);
}
@@ -326,10 +265,10 @@ attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
(void) printf("getattr setattr getsec setsec\n");
}
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetattr), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetsecattr), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetsecattr), ' ', buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetattr), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetsecattr), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetsecattr), buf);
(void) printf("%s\n", name);
}
@@ -345,16 +284,16 @@ naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
"lookup creat remov link renam mkdir rmdir rddir symlnk rdlnk\n");
}
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlookup), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
- PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), ' ', buf);
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlookup), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), buf);
+ PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), buf);
(void) printf("%s\n", name);
}
@@ -362,7 +301,7 @@ naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
#define PRINT_VOPSTAT_CMN(niceflag, vop) \
if (niceflag) \
(void) printf("%10s ", #vop); \
- PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), ' ', buf);
+ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), buf);
#define PRINT_VOPSTAT(niceflag, vop) \
PRINT_VOPSTAT_CMN(niceflag, vop); \
@@ -372,7 +311,7 @@ naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
#define PRINT_VOPSTAT_IO(niceflag, vop) \
PRINT_VOPSTAT_CMN(niceflag, vop); \
PRINTSTAT(niceflag, " %5s\n", "%lld:", \
- DELTA(vop##_bytes), ' ', buf);
+ DELTA(vop##_bytes), buf);
static void
vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
diff --git a/usr/src/cmd/swap/Makefile.com b/usr/src/cmd/swap/Makefile.com
index 3734d8ac58..0514c15e62 100644
--- a/usr/src/cmd/swap/Makefile.com
+++ b/usr/src/cmd/swap/Makefile.com
@@ -43,7 +43,7 @@ CLEANFILES += $(OBJS)
all: $(PROG)
-LDLIBS += -ldiskmgt
+LDLIBS += -ldiskmgt -lcmdutils
$(PROG): $(OBJS)
$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
$(POST_PROCESS)
diff --git a/usr/src/cmd/swap/swap.c b/usr/src/cmd/swap/swap.c
index 91cecd5cb4..6b4522745d 100644
--- a/usr/src/cmd/swap/swap.c
+++ b/usr/src/cmd/swap/swap.c
@@ -36,7 +36,9 @@
* contributors.
*/
-#pragma ident "%Z%%M% %I% %E% SMI"
+/*
+ * Copyright 2017 Jason King.
+ */
/*
* Swap administrative interface
@@ -64,6 +66,8 @@
#include <libintl.h>
#include <libdiskmgt.h>
#include <sys/fs/zfs.h>
+#include <libcmdutils.h>
+#include <sys/debug.h>
#define LFLAG 0x01 /* swap -l (list swap devices) */
#define DFLAG 0x02 /* swap -d (delete swap device) */
@@ -75,6 +79,8 @@
#define KFLAG 0x80 /* swap -k (size in kilobytes) */
#define NUMBER_WIDTH 64
+/* make sure nicenum_scale() has enough space */
+CTASSERT(NUMBER_WIDTH >= NN_NUMBUF_SZ);
typedef char numbuf_t[NUMBER_WIDTH];
static char *prognamep;
@@ -85,9 +91,6 @@ static void usage(void);
static int doswap(int flag);
static int valid(char *, off_t, off_t);
static int list(int flag);
-static char *number_to_scaled_string(numbuf_t buf, unsigned long long number,
- unsigned long long unit_from, unsigned long long scale);
-
int
main(int argc, char **argv)
@@ -253,7 +256,7 @@ main(int argc, char **argv)
}
if ((ret = valid(pathname,
s_offset * 512, length * 512)) == 0) {
- ret = add(pathname, s_offset, length, flag);
+ ret = add(pathname, s_offset, length, flag);
}
}
if (!(flag & ~HFLAG & ~KFLAG)) {
@@ -271,16 +274,16 @@ usage(void)
(void) fprintf(stderr, gettext("Usage:\t%s -l\n"), prognamep);
(void) fprintf(stderr, gettext("\tsub option :\n"));
(void) fprintf(stderr, gettext("\t\t-h : displays size in human "
- "readable format\n"));
+ "readable format\n"));
(void) fprintf(stderr, gettext("\t\t-k : displays size in KB\n"));
(void) fprintf(stderr, "\t%s -s\n", prognamep);
(void) fprintf(stderr, gettext("\tsub option :\n"));
(void) fprintf(stderr, gettext("\t\t-h : displays size in human "
- "readable format rather than KB\n"));
+ "readable format rather than KB\n"));
(void) fprintf(stderr, gettext("\t%s -d <file name> [low block]\n"),
- prognamep);
+ prognamep);
(void) fprintf(stderr, gettext("\t%s -a <file name> [low block]"
- " [nbr of blocks]\n"), prognamep);
+ " [nbr of blocks]\n"), prognamep);
}
/*
@@ -305,7 +308,6 @@ doswap(int flag)
struct anoninfo ai;
pgcnt_t allocated, reserved, available;
numbuf_t numbuf;
- unsigned long long scale = 1024L;
/*
* max = total amount of swap space including physical memory
@@ -339,24 +341,25 @@ doswap(int flag)
if (flag & HFLAG) {
int factor = (int)(sysconf(_SC_PAGESIZE));
- (void) printf(gettext("total: %s allocated + "),
- number_to_scaled_string(numbuf, allocated,
- factor, scale));
- (void) printf(gettext("%s reserved = "),
- number_to_scaled_string(numbuf, reserved,
- factor, scale));
- (void) printf(gettext("%s used, "),
- number_to_scaled_string(numbuf,
- allocated + reserved, factor, scale));
- (void) printf(gettext("%s available\n"),
- number_to_scaled_string(numbuf, available,
- factor, scale));
+
+ nicenum_scale(allocated, factor, numbuf, sizeof (numbuf), 0);
+ (void) printf(gettext("total: %s allocated + "), numbuf);
+
+ nicenum_scale(reserved, factor, numbuf, sizeof (numbuf), 0);
+ (void) printf(gettext("%s reserved = "), numbuf);
+
+ nicenum_scale(allocated + reserved, factor, numbuf,
+ sizeof (numbuf), 0);
+ (void) printf(gettext("%s used, "), numbuf);
+
+ nicenum_scale(available, factor, numbuf, sizeof (numbuf), 0);
+ (void) printf(gettext("%s available\n"), numbuf);
} else {
(void) printf(gettext("total: %luk bytes allocated + %luk"
- " reserved = %luk used, %luk available\n"),
- ctok(allocated), ctok(reserved),
- ctok(reserved) + ctok(allocated),
- ctok(available));
+ " reserved = %luk used, %luk available\n"),
+ ctok(allocated), ctok(reserved),
+ ctok(reserved) + ctok(allocated),
+ ctok(available));
}
return (0);
@@ -373,7 +376,6 @@ list(int flag)
char fullpath[MAXPATHLEN+1];
int num;
numbuf_t numbuf;
- unsigned long long scale = 1024L;
if ((num = swapctl(SC_GETNSWP, NULL)) == -1) {
perror(prognamep);
@@ -387,13 +389,13 @@ list(int flag)
if ((st = malloc(num * sizeof (swapent_t) + sizeof (int)))
== NULL) {
(void) fprintf(stderr,
- gettext("Malloc failed. Please try later.\n"));
+ gettext("Malloc failed. Please try later.\n"));
perror(prognamep);
return (2);
}
if ((path = malloc(num * MAXPATHLEN)) == NULL) {
(void) fprintf(stderr,
- gettext("Malloc failed. Please try later.\n"));
+ gettext("Malloc failed. Please try later.\n"));
perror(prognamep);
return (2);
}
@@ -431,17 +433,17 @@ list(int flag)
for (i = 0; i < num; i++, swapent++) {
if (*swapent->ste_path != '/')
(void) snprintf(fullpath, sizeof (fullpath),
- "/dev/%s", swapent->ste_path);
+ "/dev/%s", swapent->ste_path);
else
(void) snprintf(fullpath, sizeof (fullpath),
- "%s", swapent->ste_path);
+ "%s", swapent->ste_path);
if (stat64(fullpath, &statbuf) < 0)
if (*swapent->ste_path != '/')
(void) printf(gettext("%-20s - "),
- swapent->ste_path);
+ swapent->ste_path);
else
(void) printf(gettext("%-20s ?,? "),
- fullpath);
+ fullpath);
else {
if (S_ISBLK(statbuf.st_mode) ||
S_ISCHR(statbuf.st_mode)) {
@@ -455,34 +457,31 @@ list(int flag)
}
{
int diskblks_per_page =
- (int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT);
+ (int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT);
if (flag & HFLAG) {
- (void) printf(gettext(" %8s"),
- number_to_scaled_string(numbuf,
- swapent->ste_start, DEV_BSIZE,
- scale));
- (void) printf(gettext(" %8s"),
- number_to_scaled_string(numbuf,
- swapent->ste_pages *
- diskblks_per_page,
- DEV_BSIZE, scale));
- (void) printf(gettext(" %8s"),
- number_to_scaled_string(numbuf,
- swapent->ste_free *
- diskblks_per_page,
- DEV_BSIZE, scale));
+ nicenum_scale(swapent->ste_start, DEV_BSIZE, numbuf,
+ sizeof (numbuf), 0);
+ (void) printf(gettext(" %8s"), numbuf);
+
+ nicenum_scale(swapent->ste_pages, DEV_BSIZE *
+ diskblks_per_page, numbuf, sizeof (numbuf), 0);
+ (void) printf(gettext(" %8s"), numbuf);
+
+ nicenum_scale(swapent->ste_free, DEV_BSIZE *
+ diskblks_per_page, numbuf, sizeof (numbuf), 0);
+ (void) printf(gettext(" %8s"), numbuf);
} else if (flag & KFLAG) {
(void) printf(gettext(" %7luK %7luK %7luK"),
- swapent->ste_start * DEV_BSIZE / 1024,
- swapent->ste_pages * diskblks_per_page *
- DEV_BSIZE / 1024,
- swapent->ste_free * diskblks_per_page *
- DEV_BSIZE / 1024);
+ swapent->ste_start * DEV_BSIZE / 1024,
+ swapent->ste_pages * diskblks_per_page *
+ DEV_BSIZE / 1024,
+ swapent->ste_free * diskblks_per_page *
+ DEV_BSIZE / 1024);
} else {
(void) printf(gettext(" %8lu %8lu %8lu"),
- swapent->ste_start,
- swapent->ste_pages * diskblks_per_page,
- swapent->ste_free * diskblks_per_page);
+ swapent->ste_start,
+ swapent->ste_pages * diskblks_per_page,
+ swapent->ste_free * diskblks_per_page);
}
}
if (swapent->ste_flags & ST_INDEL)
@@ -493,64 +492,6 @@ list(int flag)
return (0);
}
-/* Copied from du.c */
-static char *
-number_to_scaled_string(
- numbuf_t buf, /* put the result here */
- unsigned long long number, /* convert this number */
- unsigned long long unit_from, /* number of byes per input unit */
- unsigned long long scale) /* 1024 (-h) or 1000 (-H) */
-{
- unsigned long long save = 0;
- char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
- char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */
-
- if ((long long)number == (long long) -1) {
- (void) strcpy(buf, "-1");
- return (buf);
- }
-
- /*
- * Convert number from unit_from to given scale (1024 or 1000)
- * This means multiply number with unit_from and divide by scale.
- * if number is large enough, we first divide and then multiply
- * to avoid an overflow (large enough here means 100 (rather arbitrary
- * value) times scale in order to reduce rounding errors)
- * otherwise, we first multiply and then divide to avoid an underflow.
- */
- if (number >= 100L * scale) {
- number = number / scale;
- number = number * unit_from;
- } else {
- number = number * unit_from;
- number = number / scale;
- }
-
- /*
- * Now we have number as a count of scale units.
- * Stop scaling when we reached exa bytes, then something is
- * probably wrong with our number.
- */
- while ((number >= scale) && (*uom != 'E')) {
- uom++; /* Next unit of measurement */
- save = number;
- number = (number + (scale / 2)) / scale;
- }
-
- /* Check if we should output a decimal place after the point */
- if (save && ((save / scale) < 10)) {
- /* sprintf() will round for us */
- float fnum = (float)save / scale;
- (void) sprintf(buf, "%.1f%c", fnum, *uom);
- } else {
- (void) sprintf(buf, "%llu%c", number, *uom);
- }
- return (buf);
-}
-
-
-
-
static void
dumpadm_err(const char *warning)
{
@@ -701,7 +642,7 @@ add(char *path, off_t offset, off_t cnt, int flags)
statvfs("/etc", &fsb) == 0 && !(fsb.f_flag & ST_RDONLY)) {
(void) printf(
- gettext("operating system crash dump was previously "
+ gettext("operating system crash dump was previously "
"disabled --\ninvoking dumpadm(1M) -d swap to select "
"new dump device\n"));
@@ -726,7 +667,7 @@ valid(char *pathname, off_t offset, off_t length)
}
if (!((S_ISREG(f.st_mode) && (f.st_mode & S_ISVTX) == S_ISVTX) ||
- S_ISBLK(f.st_mode))) {
+ S_ISBLK(f.st_mode))) {
(void) fprintf(stderr,
gettext("\"%s\" is not valid for swapping.\n"
"It must be a block device or a regular file with the\n"
@@ -753,8 +694,8 @@ valid(char *pathname, off_t offset, off_t length)
if (offset < 0) {
(void) fprintf(stderr,
- gettext("%s: low block is invalid\n"),
- pathname);
+ gettext("%s: low block is invalid\n"),
+ pathname);
return (EINVAL);
}
diff --git a/usr/src/cmd/zdb/Makefile.com b/usr/src/cmd/zdb/Makefile.com
index 63b8366c80..022c984dbe 100644
--- a/usr/src/cmd/zdb/Makefile.com
+++ b/usr/src/cmd/zdb/Makefile.com
@@ -23,6 +23,7 @@
# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
# Copyright (c) 2012 by Delphix. All rights reserved.
+# Copyright 2017 Joyent, Inc.
#
PROG:sh= cd ..; basename `pwd`
@@ -36,7 +37,7 @@ INCS += -I../../../lib/libzpool/common
INCS += -I../../../uts/common/fs/zfs
INCS += -I../../../common/zfs
-LDLIBS += -lzpool -lumem -lnvpair -lzfs -lavl
+LDLIBS += -lzpool -lumem -lnvpair -lzfs -lavl -lcmdutils
C99MODE= -xc99=%all
C99LMODE= -Xc99=%all
diff --git a/usr/src/cmd/zdb/zdb.c b/usr/src/cmd/zdb/zdb.c
index a2e0a67e11..5dcb12f5bf 100644
--- a/usr/src/cmd/zdb/zdb.c
+++ b/usr/src/cmd/zdb/zdb.c
@@ -63,6 +63,7 @@
#include <sys/abd.h>
#include <sys/blkptr.h>
#include <zfs_comutil.h>
+#include <libcmdutils.h>
#undef verify
#include <libzfs.h>
@@ -276,12 +277,12 @@ dump_history_offsets(objset_t *os, uint64_t object, void *data, size_t size)
}
static void
-zdb_nicenum(uint64_t num, char *buf)
+zdb_nicenum(uint64_t num, char *buf, size_t buflen)
{
if (dump_opt['P'])
- (void) sprintf(buf, "%llu", (longlong_t)num);
+ (void) snprintf(buf, buflen, "%llu", (longlong_t)num);
else
- nicenum(num, buf);
+ nicenum(num, buf, sizeof (buf));
}
const char histo_stars[] = "****************************************";
@@ -458,12 +459,17 @@ dump_bpobj(objset_t *os, uint64_t object, void *data, size_t size)
bpobj_phys_t *bpop = data;
char bytes[32], comp[32], uncomp[32];
+ /* make sure the output won't get truncated */
+ CTASSERT(sizeof (bytes) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (comp) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (uncomp) >= NN_NUMBUF_SZ);
+
if (bpop == NULL)
return;
- zdb_nicenum(bpop->bpo_bytes, bytes);
- zdb_nicenum(bpop->bpo_comp, comp);
- zdb_nicenum(bpop->bpo_uncomp, uncomp);
+ zdb_nicenum(bpop->bpo_bytes, bytes, sizeof (bytes));
+ zdb_nicenum(bpop->bpo_comp, comp, sizeof (comp));
+ zdb_nicenum(bpop->bpo_uncomp, uncomp, sizeof (uncomp));
(void) printf("\t\tnum_blkptrs = %llu\n",
(u_longlong_t)bpop->bpo_num_blkptrs);
@@ -756,7 +762,10 @@ dump_metaslab_stats(metaslab_t *msp)
avl_tree_t *t = &msp->ms_size_tree;
int free_pct = range_tree_space(rt) * 100 / msp->ms_size;
- zdb_nicenum(metaslab_block_maxsize(msp), maxbuf);
+ /* max sure nicenum has enough space */
+ CTASSERT(sizeof (maxbuf) >= NN_NUMBUF_SZ);
+
+ zdb_nicenum(metaslab_block_maxsize(msp), maxbuf, sizeof (maxbuf));
(void) printf("\t %25s %10lu %7s %6s %4s %4d%%\n",
"segments", avl_numnodes(t), "maxsize", maxbuf,
@@ -773,7 +782,8 @@ dump_metaslab(metaslab_t *msp)
space_map_t *sm = msp->ms_sm;
char freebuf[32];
- zdb_nicenum(msp->ms_size - space_map_allocated(sm), freebuf);
+ zdb_nicenum(msp->ms_size - space_map_allocated(sm), freebuf,
+ sizeof (freebuf));
(void) printf(
"\tmetaslab %6llu offset %12llx spacemap %6llu free %5s\n",
@@ -1331,6 +1341,9 @@ dump_dsl_dir(objset_t *os, uint64_t object, void *data, size_t size)
time_t crtime;
char nice[32];
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (nice) >= NN_NUMBUF_SZ);
+
if (dd == NULL)
return;
@@ -1346,15 +1359,15 @@ dump_dsl_dir(objset_t *os, uint64_t object, void *data, size_t size)
(u_longlong_t)dd->dd_origin_obj);
(void) printf("\t\tchild_dir_zapobj = %llu\n",
(u_longlong_t)dd->dd_child_dir_zapobj);
- zdb_nicenum(dd->dd_used_bytes, nice);
+ zdb_nicenum(dd->dd_used_bytes, nice, sizeof (nice));
(void) printf("\t\tused_bytes = %s\n", nice);
- zdb_nicenum(dd->dd_compressed_bytes, nice);
+ zdb_nicenum(dd->dd_compressed_bytes, nice, sizeof (nice));
(void) printf("\t\tcompressed_bytes = %s\n", nice);
- zdb_nicenum(dd->dd_uncompressed_bytes, nice);
+ zdb_nicenum(dd->dd_uncompressed_bytes, nice, sizeof (nice));
(void) printf("\t\tuncompressed_bytes = %s\n", nice);
- zdb_nicenum(dd->dd_quota, nice);
+ zdb_nicenum(dd->dd_quota, nice, sizeof (nice));
(void) printf("\t\tquota = %s\n", nice);
- zdb_nicenum(dd->dd_reserved, nice);
+ zdb_nicenum(dd->dd_reserved, nice, sizeof (nice));
(void) printf("\t\treserved = %s\n", nice);
(void) printf("\t\tprops_zapobj = %llu\n",
(u_longlong_t)dd->dd_props_zapobj);
@@ -1364,7 +1377,8 @@ dump_dsl_dir(objset_t *os, uint64_t object, void *data, size_t size)
(u_longlong_t)dd->dd_flags);
#define DO(which) \
- zdb_nicenum(dd->dd_used_breakdown[DD_USED_ ## which], nice); \
+ zdb_nicenum(dd->dd_used_breakdown[DD_USED_ ## which], nice, \
+ sizeof (nice)); \
(void) printf("\t\tused_breakdown[" #which "] = %s\n", nice)
DO(HEAD);
DO(SNAP);
@@ -1383,15 +1397,22 @@ dump_dsl_dataset(objset_t *os, uint64_t object, void *data, size_t size)
char used[32], compressed[32], uncompressed[32], unique[32];
char blkbuf[BP_SPRINTF_LEN];
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (used) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (compressed) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (uncompressed) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (unique) >= NN_NUMBUF_SZ);
+
if (ds == NULL)
return;
ASSERT(size == sizeof (*ds));
crtime = ds->ds_creation_time;
- zdb_nicenum(ds->ds_referenced_bytes, used);
- zdb_nicenum(ds->ds_compressed_bytes, compressed);
- zdb_nicenum(ds->ds_uncompressed_bytes, uncompressed);
- zdb_nicenum(ds->ds_unique_bytes, unique);
+ zdb_nicenum(ds->ds_referenced_bytes, used, sizeof (used));
+ zdb_nicenum(ds->ds_compressed_bytes, compressed, sizeof (compressed));
+ zdb_nicenum(ds->ds_uncompressed_bytes, uncompressed,
+ sizeof (uncompressed));
+ zdb_nicenum(ds->ds_unique_bytes, unique, sizeof (unique));
snprintf_blkptr(blkbuf, sizeof (blkbuf), &ds->ds_bp);
(void) printf("\t\tdir_obj = %llu\n",
@@ -1450,12 +1471,15 @@ dump_bptree(objset_t *os, uint64_t obj, char *name)
bptree_phys_t *bt;
dmu_buf_t *db;
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (bytes) >= NN_NUMBUF_SZ);
+
if (dump_opt['d'] < 3)
return;
VERIFY3U(0, ==, dmu_bonus_hold(os, obj, FTAG, &db));
bt = db->db_data;
- zdb_nicenum(bt->bt_bytes, bytes);
+ zdb_nicenum(bt->bt_bytes, bytes, sizeof (bytes));
(void) printf("\n %s: %llu datasets, %s\n",
name, (unsigned long long)(bt->bt_end - bt->bt_begin), bytes);
dmu_buf_rele(db, FTAG);
@@ -1487,13 +1511,18 @@ dump_full_bpobj(bpobj_t *bpo, char *name, int indent)
char comp[32];
char uncomp[32];
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (bytes) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (comp) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (uncomp) >= NN_NUMBUF_SZ);
+
if (dump_opt['d'] < 3)
return;
- zdb_nicenum(bpo->bpo_phys->bpo_bytes, bytes);
+ zdb_nicenum(bpo->bpo_phys->bpo_bytes, bytes, sizeof (bytes));
if (bpo->bpo_havesubobj && bpo->bpo_phys->bpo_subobjs != 0) {
- zdb_nicenum(bpo->bpo_phys->bpo_comp, comp);
- zdb_nicenum(bpo->bpo_phys->bpo_uncomp, uncomp);
+ zdb_nicenum(bpo->bpo_phys->bpo_comp, comp, sizeof (comp));
+ zdb_nicenum(bpo->bpo_phys->bpo_uncomp, uncomp, sizeof (uncomp));
(void) printf(" %*s: object %llu, %llu local blkptrs, "
"%llu subobjs in object %llu, %s (%s/%s comp)\n",
indent * 8, name,
@@ -1547,6 +1576,11 @@ dump_deadlist(dsl_deadlist_t *dl)
char comp[32];
char uncomp[32];
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (bytes) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (comp) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (uncomp) >= NN_NUMBUF_SZ);
+
if (dump_opt['d'] < 3)
return;
@@ -1555,9 +1589,9 @@ dump_deadlist(dsl_deadlist_t *dl)
return;
}
- zdb_nicenum(dl->dl_phys->dl_used, bytes);
- zdb_nicenum(dl->dl_phys->dl_comp, comp);
- zdb_nicenum(dl->dl_phys->dl_uncomp, uncomp);
+ zdb_nicenum(dl->dl_phys->dl_used, bytes, sizeof (bytes));
+ zdb_nicenum(dl->dl_phys->dl_comp, comp, sizeof (comp));
+ zdb_nicenum(dl->dl_phys->dl_uncomp, uncomp, sizeof (uncomp));
(void) printf("\n Deadlist: %s (%s/%s comp)\n",
bytes, comp, uncomp);
@@ -1860,6 +1894,13 @@ dump_object(objset_t *os, uint64_t object, int verbosity, int *print_header)
char aux[50];
int error;
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (iblk) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (dblk) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (lsize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (asize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (bonus_size) >= NN_NUMBUF_SZ);
+
if (*print_header) {
(void) printf("\n%10s %3s %5s %5s %5s %5s %6s %s\n",
"Object", "lvl", "iblk", "dblk", "dsize", "lsize",
@@ -1880,11 +1921,11 @@ dump_object(objset_t *os, uint64_t object, int verbosity, int *print_header)
}
dmu_object_info_from_dnode(dn, &doi);
- zdb_nicenum(doi.doi_metadata_block_size, iblk);
- zdb_nicenum(doi.doi_data_block_size, dblk);
- zdb_nicenum(doi.doi_max_offset, lsize);
- zdb_nicenum(doi.doi_physical_blocks_512 << 9, asize);
- zdb_nicenum(doi.doi_bonus_size, bonus_size);
+ zdb_nicenum(doi.doi_metadata_block_size, iblk, sizeof (iblk));
+ zdb_nicenum(doi.doi_data_block_size, dblk, sizeof (dblk));
+ zdb_nicenum(doi.doi_max_offset, lsize, sizeof (lsize));
+ zdb_nicenum(doi.doi_physical_blocks_512 << 9, asize, sizeof (asize));
+ zdb_nicenum(doi.doi_bonus_size, bonus_size, sizeof (bonus_size));
(void) sprintf(fill, "%6.2f", 100.0 * doi.doi_fill_count *
doi.doi_data_block_size / (object == 0 ? DNODES_PER_BLOCK : 1) /
doi.doi_max_offset);
@@ -1947,6 +1988,8 @@ dump_object(objset_t *os, uint64_t object, int verbosity, int *print_header)
for (;;) {
char segsize[32];
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (segsize) >= NN_NUMBUF_SZ);
error = dnode_next_offset(dn,
0, &start, minlvl, blkfill, 0);
if (error)
@@ -1954,7 +1997,7 @@ dump_object(objset_t *os, uint64_t object, int verbosity, int *print_header)
end = start;
error = dnode_next_offset(dn,
DNODE_FIND_HOLE, &end, minlvl, blkfill, 0);
- zdb_nicenum(end - start, segsize);
+ zdb_nicenum(end - start, segsize, sizeof (segsize));
(void) printf("\t\tsegment [%016llx, %016llx)"
" size %5s\n", (u_longlong_t)start,
(u_longlong_t)end, segsize);
@@ -1985,6 +2028,9 @@ dump_dir(objset_t *os)
int print_header = 1;
int i, error;
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (numbuf) >= NN_NUMBUF_SZ);
+
dsl_pool_config_enter(dmu_objset_pool(os), FTAG);
dmu_objset_fast_stat(os, &dds);
dsl_pool_config_exit(dmu_objset_pool(os), FTAG);
@@ -2003,7 +2049,7 @@ dump_dir(objset_t *os)
ASSERT3U(usedobjs, ==, BP_GET_FILL(os->os_rootbp));
- zdb_nicenum(refdbytes, numbuf);
+ zdb_nicenum(refdbytes, numbuf, sizeof (numbuf));
if (verbosity >= 4) {
(void) snprintf(blkbuf, sizeof (blkbuf), ", rootbp ");
@@ -2648,6 +2694,9 @@ zdb_blkptr_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
int sec_remaining =
(zcb->zcb_totalasize - bytes) / 1024 / kb_per_sec;
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (buf) >= NN_NUMBUF_SZ);
+
zfs_nicenum(bytes, buf, sizeof (buf));
(void) fprintf(stderr,
"\r%5s completed (%4dMB/s) "
@@ -2997,6 +3046,14 @@ dump_block_stats(spa_t *spa)
char avg[32], gang[32];
char *typename;
+ /* make sure nicenum has enough space */
+ CTASSERT(sizeof (csize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (lsize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (psize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (asize) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (avg) >= NN_NUMBUF_SZ);
+ CTASSERT(sizeof (gang) >= NN_NUMBUF_SZ);
+
if (t < DMU_OT_NUMTYPES)
typename = dmu_ot[t].ot_name;
else
@@ -3030,12 +3087,17 @@ dump_block_stats(spa_t *spa)
zcb.zcb_type[ZB_TOTAL][t].zb_asize)
continue;
- zdb_nicenum(zb->zb_count, csize);
- zdb_nicenum(zb->zb_lsize, lsize);
- zdb_nicenum(zb->zb_psize, psize);
- zdb_nicenum(zb->zb_asize, asize);
- zdb_nicenum(zb->zb_asize / zb->zb_count, avg);
- zdb_nicenum(zb->zb_gangs, gang);
+ zdb_nicenum(zb->zb_count, csize,
+ sizeof (csize));
+ zdb_nicenum(zb->zb_lsize, lsize,
+ sizeof (lsize));
+ zdb_nicenum(zb->zb_psize, psize,
+ sizeof (psize));
+ zdb_nicenum(zb->zb_asize, asize,
+ sizeof (asize));
+ zdb_nicenum(zb->zb_asize / zb->zb_count, avg,
+ sizeof (avg));
+ zdb_nicenum(zb->zb_gangs, gang, sizeof (gang));
(void) printf("%6s\t%5s\t%5s\t%5s\t%5s"
"\t%5.2f\t%6.2f\t",
diff --git a/usr/src/cmd/ztest/zloop.bash b/usr/src/cmd/ztest/zloop.bash
index 7203356d3d..2410eaa985 100755
--- a/usr/src/cmd/ztest/zloop.bash
+++ b/usr/src/cmd/ztest/zloop.bash
@@ -189,6 +189,7 @@ while [[ $timeout -eq 0 ]] || [[ $curtime -le $(($starttime + $timeout)) ]]; do
$BIN/$cmd >>ztest.out 2>&1
ztrc=$?
/bin/egrep '===|WARNING' ztest.out >>ztest.history
+ $SBIN/zdb -U $workdir/zpool.cache -DD ztest >>ztest.ddt 2>&1
store_core
diff --git a/usr/src/cmd/ztest/ztest.c b/usr/src/cmd/ztest/ztest.c
index b8264b3105..32ecef6b18 100644
--- a/usr/src/cmd/ztest/ztest.c
+++ b/usr/src/cmd/ztest/ztest.c
@@ -24,6 +24,7 @@
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2013 Steven Hartland. All rights reserved.
* Copyright (c) 2014 Integros [integros.com]
+ * Copyright 2017 Joyent, Inc.
*/
/*
@@ -123,6 +124,7 @@
#include <math.h>
#include <sys/fs/zfs.h>
#include <libnvpair.h>
+#include <libcmdutils.h>
static int ztest_fd_data = -1;
static int ztest_fd_rand = -1;
@@ -554,12 +556,13 @@ usage(boolean_t requested)
{
const ztest_shared_opts_t *zo = &ztest_opts_defaults;
- char nice_vdev_size[10];
- char nice_gang_bang[10];
+ char nice_vdev_size[NN_NUMBUF_SZ];
+ char nice_gang_bang[NN_NUMBUF_SZ];
FILE *fp = requested ? stdout : stderr;
- nicenum(zo->zo_vdev_size, nice_vdev_size);
- nicenum(zo->zo_metaslab_gang_bang, nice_gang_bang);
+ nicenum(zo->zo_vdev_size, nice_vdev_size, sizeof (nice_vdev_size));
+ nicenum(zo->zo_metaslab_gang_bang, nice_gang_bang,
+ sizeof (nice_gang_bang));
(void) fprintf(fp, "Usage: %s\n"
"\t[-v vdevs (default: %llu)]\n"
@@ -1829,13 +1832,14 @@ ztest_get_done(zgd_t *zgd, int error)
ztest_object_unlock(zd, object);
if (error == 0 && zgd->zgd_bp)
- zil_add_block(zgd->zgd_zilog, zgd->zgd_bp);
+ zil_lwb_add_block(zgd->zgd_lwb, zgd->zgd_bp);
umem_free(zgd, sizeof (*zgd));
}
static int
-ztest_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
+ztest_get_data(void *arg, lr_write_t *lr, char *buf, struct lwb *lwb,
+ zio_t *zio)
{
ztest_ds_t *zd = arg;
objset_t *os = zd->zd_os;
@@ -1849,6 +1853,10 @@ ztest_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
zgd_t *zgd;
int error;
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3P(zio, !=, NULL);
+ ASSERT3U(size, !=, 0);
+
ztest_object_lock(zd, object, RL_READER);
error = dmu_bonus_hold(os, object, FTAG, &db);
if (error) {
@@ -1869,7 +1877,7 @@ ztest_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
db = NULL;
zgd = umem_zalloc(sizeof (*zgd), UMEM_NOFAIL);
- zgd->zgd_zilog = zd->zd_zilog;
+ zgd->zgd_lwb = lwb;
zgd->zgd_private = zd;
if (buf != NULL) { /* immediate write */
@@ -3151,10 +3159,10 @@ ztest_vdev_LUN_growth(ztest_ds_t *zd, uint64_t id)
old_class_space, new_class_space);
if (ztest_opts.zo_verbose >= 5) {
- char oldnumbuf[6], newnumbuf[6];
+ char oldnumbuf[NN_NUMBUF_SZ], newnumbuf[NN_NUMBUF_SZ];
- nicenum(old_class_space, oldnumbuf);
- nicenum(new_class_space, newnumbuf);
+ nicenum(old_class_space, oldnumbuf, sizeof (oldnumbuf));
+ nicenum(new_class_space, newnumbuf, sizeof (newnumbuf));
(void) printf("%s grew from %s to %s\n",
spa->spa_name, oldnumbuf, newnumbuf);
}
@@ -6196,7 +6204,7 @@ main(int argc, char **argv)
ztest_info_t *zi;
ztest_shared_callstate_t *zc;
char timebuf[100];
- char numbuf[6];
+ char numbuf[NN_NUMBUF_SZ];
spa_t *spa;
char *cmd;
boolean_t hasalt;
@@ -6333,7 +6341,7 @@ main(int argc, char **argv)
now = MIN(now, zs->zs_proc_stop);
print_time(zs->zs_proc_stop - now, timebuf);
- nicenum(zs->zs_space, numbuf);
+ nicenum(zs->zs_space, numbuf, sizeof (numbuf));
(void) printf("Pass %3d, %8s, %3llu ENOSPC, "
"%4.1f%% of %5s used, %3.0f%% done, %8s to go\n",
diff --git a/usr/src/lib/libcmdutils/Makefile.com b/usr/src/lib/libcmdutils/Makefile.com
index ffe3c9cba5..9cb35f8795 100644
--- a/usr/src/lib/libcmdutils/Makefile.com
+++ b/usr/src/lib/libcmdutils/Makefile.com
@@ -26,7 +26,7 @@
LIBRARY= libcmdutils.a
VERS= .1
CMD_OBJS= avltree.o sysattrs.o writefile.o process_xattrs.o uid.o gid.o \
- custr.o
+ custr.o nicenum.o
COM_OBJS= list.o
OBJECTS= $(CMD_OBJS) $(COM_OBJS)
diff --git a/usr/src/lib/libcmdutils/common/mapfile-vers b/usr/src/lib/libcmdutils/common/mapfile-vers
index 1f63d4eee0..0ac77eb010 100644
--- a/usr/src/lib/libcmdutils/common/mapfile-vers
+++ b/usr/src/lib/libcmdutils/common/mapfile-vers
@@ -72,6 +72,8 @@ SYMBOL_VERSION SUNWprivate_1.1 {
list_remove_head;
list_remove_tail;
list_tail;
+ nicenum;
+ nicenum_scale;
tnode_compare;
sysattr_type;
sysattr_support;
diff --git a/usr/src/lib/libcmdutils/common/nicenum.c b/usr/src/lib/libcmdutils/common/nicenum.c
new file mode 100644
index 0000000000..8e3202f792
--- /dev/null
+++ b/usr/src/lib/libcmdutils/common/nicenum.c
@@ -0,0 +1,130 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2017 Jason king
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/debug.h>
+#include "libcmdutils.h"
+
+/* The largest suffix that can fit, aka an exabyte (2^60 / 10^18) */
+#define INDEX_MAX (6)
+
+/* Verify INDEX_MAX fits */
+CTASSERT(INDEX_MAX * 10 < sizeof (uint64_t) * 8);
+
+void
+nicenum_scale(uint64_t n, size_t units, char *buf, size_t buflen,
+ uint32_t flags)
+{
+ uint64_t divamt = 1024;
+ uint64_t divisor = 1;
+ int index = 0;
+ int rc = 0;
+ char u;
+
+ if (units == 0)
+ units = 1;
+
+ if (n > 0) {
+ n *= units;
+ if (n < units)
+ goto overflow;
+ }
+
+ if (flags & NN_DIVISOR_1000)
+ divamt = 1000;
+
+ /*
+ * This tries to find the suffix S(n) such that
+ * S(n) <= n < S(n+1), where S(n) = 2^(n*10) | 10^(3*n)
+ * (i.e. 1024/1000, 1,048,576/1,000,000, etc). Stop once S(n)
+ * is the largest prefix supported (i.e. don't bother computing
+ * and checking S(n+1). Since INDEX_MAX should be the largest
+ * suffix that fits (currently an exabyte), S(INDEX_MAX + 1) is
+ * never checked as it would overflow.
+ */
+ while (index < INDEX_MAX) {
+ uint64_t newdiv = divisor * divamt;
+
+ /* CTASSERT() guarantee these never trip */
+ VERIFY3U(newdiv, >=, divamt);
+ VERIFY3U(newdiv, >=, divisor);
+
+ if (n < newdiv)
+ break;
+
+ divisor = newdiv;
+ index++;
+ }
+
+ u = " KMGTPE"[index];
+
+ if (index == 0) {
+ rc = snprintf(buf, buflen, "%llu", n);
+ } else if (n % divisor == 0) {
+ /*
+ * If this is an even multiple of the base, always display
+ * without any decimal precision.
+ */
+ rc = snprintf(buf, buflen, "%llu%c", n / divisor, u);
+ } else {
+ /*
+ * We want to choose a precision that reflects the best choice
+ * for fitting in 5 characters. This can get rather tricky
+ * when we have numbers that are very close to an order of
+ * magnitude. For example, when displaying 10239 (which is
+ * really 9.999K), we want only a single place of precision
+ * for 10.0K. We could develop some complex heuristics for
+ * this, but it's much easier just to try each combination
+ * in turn.
+ */
+ int i;
+ for (i = 2; i >= 0; i--) {
+ if ((rc = snprintf(buf, buflen, "%.*f%c", i,
+ (double)n / divisor, u)) <= 5)
+ break;
+ }
+ }
+
+ if (rc + 1 > buflen || rc < 0)
+ goto overflow;
+
+ return;
+
+overflow:
+ /* prefer a more verbose message if possible */
+ if (buflen > 10)
+ (void) strlcpy(buf, "<overflow>", buflen);
+ else
+ (void) strlcpy(buf, "??", buflen);
+}
+
+void
+nicenum(uint64_t num, char *buf, size_t buflen)
+{
+ nicenum_scale(num, 1, buf, buflen, 0);
+}
diff --git a/usr/src/lib/libcmdutils/libcmdutils.h b/usr/src/lib/libcmdutils/libcmdutils.h
index 933ceb0fde..5f9957b861 100644
--- a/usr/src/lib/libcmdutils/libcmdutils.h
+++ b/usr/src/lib/libcmdutils/libcmdutils.h
@@ -216,6 +216,14 @@ extern void custr_reset(custr_t *);
*/
extern const char *custr_cstr(custr_t *str);
+#define NN_DIVISOR_1000 (1U << 0)
+
+/* Minimum size for the output of nicenum, including NULL */
+#define NN_NUMBUF_SZ (6)
+
+void nicenum(uint64_t, char *, size_t);
+void nicenum_scale(uint64_t, size_t, char *, size_t, uint32_t);
+
#ifdef __cplusplus
}
#endif
diff --git a/usr/src/lib/libzfs/Makefile.com b/usr/src/lib/libzfs/Makefile.com
index 31619b01c1..6eaf4332f2 100644
--- a/usr/src/lib/libzfs/Makefile.com
+++ b/usr/src/lib/libzfs/Makefile.com
@@ -70,7 +70,7 @@ INCS += -I../../libc/inc
C99MODE= -xc99=%all
C99LMODE= -Xc99=%all
LDLIBS += -lc -lm -ldevid -lgen -lnvpair -luutil -lavl -lefi \
- -ladm -lidmap -ltsol -lmd -lumem -lzfs_core
+ -ladm -lidmap -ltsol -lmd -lumem -lzfs_core -lcmdutils
CPPFLAGS += $(INCS) -D_LARGEFILE64_SOURCE=1 -D_REENTRANT
$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c
index 95b74604db..7442df57aa 100644
--- a/usr/src/lib/libzfs/common/libzfs_util.c
+++ b/usr/src/lib/libzfs/common/libzfs_util.c
@@ -45,6 +45,7 @@
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/types.h>
+#include <libcmdutils.h>
#include <libzfs.h>
#include <libzfs_core.h>
@@ -575,42 +576,7 @@ zfs_strdup(libzfs_handle_t *hdl, const char *str)
void
zfs_nicenum(uint64_t num, char *buf, size_t buflen)
{
- uint64_t n = num;
- int index = 0;
- char u;
-
- while (n >= 1024) {
- n /= 1024;
- index++;
- }
-
- u = " KMGTPE"[index];
-
- if (index == 0) {
- (void) snprintf(buf, buflen, "%llu", n);
- } else if ((num & ((1ULL << 10 * index) - 1)) == 0) {
- /*
- * If this is an even multiple of the base, always display
- * without any decimal precision.
- */
- (void) snprintf(buf, buflen, "%llu%c", n, u);
- } else {
- /*
- * We want to choose a precision that reflects the best choice
- * for fitting in 5 characters. This can get rather tricky when
- * we have numbers that are very close to an order of magnitude.
- * For example, when displaying 10239 (which is really 9.999K),
- * we want only a single place of precision for 10.0K. We could
- * develop some complex heuristics for this, but it's much
- * easier just to try each combination in turn.
- */
- int i;
- for (i = 2; i >= 0; i--) {
- if (snprintf(buf, buflen, "%.*f%c", i,
- (double)num / (1ULL << 10 * index), u) <= 5)
- break;
- }
- }
+ nicenum(num, buf, buflen);
}
void
diff --git a/usr/src/lib/libzpool/common/llib-lzpool b/usr/src/lib/libzpool/common/llib-lzpool
index 3636b4e76e..27d01fd54d 100644
--- a/usr/src/lib/libzpool/common/llib-lzpool
+++ b/usr/src/lib/libzpool/common/llib-lzpool
@@ -62,6 +62,7 @@
#include <sys/dsl_userhold.h>
#include <sys/blkptr.h>
#include <sys/abd.h>
+#include <libcmdutils.h>
extern uint64_t metaslab_gang_bang;
extern uint64_t metaslab_df_alloc_threshold;
diff --git a/usr/src/lib/libzpool/common/sys/zfs_context.h b/usr/src/lib/libzpool/common/sys/zfs_context.h
index bc43d455e0..301ffc0df8 100644
--- a/usr/src/lib/libzpool/common/sys/zfs_context.h
+++ b/usr/src/lib/libzpool/common/sys/zfs_context.h
@@ -543,7 +543,7 @@ extern void kernel_init(int);
extern void kernel_fini(void);
struct spa;
-extern void nicenum(uint64_t num, char *buf);
+extern void nicenum(uint64_t num, char *buf, size_t);
extern void show_pool_stats(struct spa *);
extern int set_global_var(char *arg);
diff --git a/usr/src/lib/libzpool/common/util.c b/usr/src/lib/libzpool/common/util.c
index e929aa5645..363940677c 100644
--- a/usr/src/lib/libzpool/common/util.c
+++ b/usr/src/lib/libzpool/common/util.c
@@ -38,33 +38,6 @@
* Routines needed by more than one client of libzpool.
*/
-void
-nicenum(uint64_t num, char *buf)
-{
- uint64_t n = num;
- int index = 0;
- char u;
-
- while (n >= 1024) {
- n = (n + (1024 / 2)) / 1024; /* Round up or down */
- index++;
- }
-
- u = " KMGTPE"[index];
-
- if (index == 0) {
- (void) sprintf(buf, "%llu", (u_longlong_t)n);
- } else if (n < 10 && (num & (num - 1)) != 0) {
- (void) sprintf(buf, "%.2f%c",
- (double)num / (1ULL << 10 * index), u);
- } else if (n < 100 && (num & (num - 1)) != 0) {
- (void) sprintf(buf, "%.1f%c",
- (double)num / (1ULL << 10 * index), u);
- } else {
- (void) sprintf(buf, "%llu%c", (u_longlong_t)n, u);
- }
-}
-
static void
show_vdev_stats(const char *desc, const char *ctype, nvlist_t *nv, int indent)
{
@@ -97,15 +70,17 @@ show_vdev_stats(const char *desc, const char *ctype, nvlist_t *nv, int indent)
sec = MAX(1, vs->vs_timestamp / NANOSEC);
- nicenum(vs->vs_alloc, used);
- nicenum(vs->vs_space - vs->vs_alloc, avail);
- nicenum(vs->vs_ops[ZIO_TYPE_READ] / sec, rops);
- nicenum(vs->vs_ops[ZIO_TYPE_WRITE] / sec, wops);
- nicenum(vs->vs_bytes[ZIO_TYPE_READ] / sec, rbytes);
- nicenum(vs->vs_bytes[ZIO_TYPE_WRITE] / sec, wbytes);
- nicenum(vs->vs_read_errors, rerr);
- nicenum(vs->vs_write_errors, werr);
- nicenum(vs->vs_checksum_errors, cerr);
+ nicenum(vs->vs_alloc, used, sizeof (used));
+ nicenum(vs->vs_space - vs->vs_alloc, avail, sizeof (avail));
+ nicenum(vs->vs_ops[ZIO_TYPE_READ] / sec, rops, sizeof (rops));
+ nicenum(vs->vs_ops[ZIO_TYPE_WRITE] / sec, wops, sizeof (wops));
+ nicenum(vs->vs_bytes[ZIO_TYPE_READ] / sec, rbytes,
+ sizeof (rbytes));
+ nicenum(vs->vs_bytes[ZIO_TYPE_WRITE] / sec, wbytes,
+ sizeof (wbytes));
+ nicenum(vs->vs_read_errors, rerr, sizeof (rerr));
+ nicenum(vs->vs_write_errors, werr, sizeof (werr));
+ nicenum(vs->vs_checksum_errors, cerr, sizeof (cerr));
(void) printf("%*s%s%*s%*s%*s %5s %5s %5s %5s %5s %5s %5s\n",
indent, "",
diff --git a/usr/src/uts/common/fs/zfs/dmu.c b/usr/src/uts/common/fs/zfs/dmu.c
index 28a5f106dd..c2c7162f53 100644
--- a/usr/src/uts/common/fs/zfs/dmu.c
+++ b/usr/src/uts/common/fs/zfs/dmu.c
@@ -1645,6 +1645,13 @@ dmu_sync_late_arrival(zio_t *pio, objset_t *os, dmu_sync_cb_t *done, zgd_t *zgd,
return (SET_ERROR(EIO));
}
+ /*
+ * In order to prevent the zgd's lwb from being free'd prior to
+ * dmu_sync_late_arrival_done() being called, we have to ensure
+ * the lwb's "max txg" takes this tx's txg into account.
+ */
+ zil_lwb_add_txg(zgd->zgd_lwb, dmu_tx_get_txg(tx));
+
dsa = kmem_alloc(sizeof (dmu_sync_arg_t), KM_SLEEP);
dsa->dsa_dr = NULL;
dsa->dsa_done = done;
diff --git a/usr/src/uts/common/fs/zfs/dsl_destroy.c b/usr/src/uts/common/fs/zfs/dsl_destroy.c
index b632070e39..133b47108c 100644
--- a/usr/src/uts/common/fs/zfs/dsl_destroy.c
+++ b/usr/src/uts/common/fs/zfs/dsl_destroy.c
@@ -501,6 +501,7 @@ dsl_destroy_snapshots_nvl(nvlist_t *snaps, boolean_t defer,
nvpair_name(pair), B_TRUE);
}
fnvlist_add_nvlist(arg, "snaps", snaps_normalized);
+ fnvlist_free(snaps_normalized);
fnvlist_add_boolean_value(arg, "defer", defer);
nvlist_t *wrapper = fnvlist_alloc();
diff --git a/usr/src/uts/common/fs/zfs/sys/dmu.h b/usr/src/uts/common/fs/zfs/sys/dmu.h
index 1dba052ae0..7cb65596e4 100644
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h
@@ -911,7 +911,7 @@ uint64_t dmu_tx_get_txg(dmu_tx_t *tx);
* {zfs,zvol,ztest}_get_done() args
*/
typedef struct zgd {
- struct zilog *zgd_zilog;
+ struct lwb *zgd_lwb;
struct blkptr *zgd_bp;
dmu_buf_t *zgd_db;
struct rl *zgd_rl;
diff --git a/usr/src/uts/common/fs/zfs/sys/zil.h b/usr/src/uts/common/fs/zfs/sys/zil.h
index 23ef83dfe4..2b0484ad5e 100644
--- a/usr/src/uts/common/fs/zfs/sys/zil.h
+++ b/usr/src/uts/common/fs/zfs/sys/zil.h
@@ -40,6 +40,7 @@ extern "C" {
struct dsl_pool;
struct dsl_dataset;
+struct lwb;
/*
* Intent log format:
@@ -140,6 +141,7 @@ typedef enum zil_create {
/*
* Intent log transaction types and record structures
*/
+#define TX_COMMIT 0 /* Commit marker (no on-disk state) */
#define TX_CREATE 1 /* Create file */
#define TX_MKDIR 2 /* Make directory */
#define TX_MKXATTR 3 /* Make XATTR directory */
@@ -388,7 +390,8 @@ typedef int zil_parse_blk_func_t(zilog_t *zilog, blkptr_t *bp, void *arg,
typedef int zil_parse_lr_func_t(zilog_t *zilog, lr_t *lr, void *arg,
uint64_t txg);
typedef int zil_replay_func_t();
-typedef int zil_get_data_t(void *arg, lr_write_t *lr, char *dbuf, zio_t *zio);
+typedef int zil_get_data_t(void *arg, lr_write_t *lr, char *dbuf,
+ struct lwb *lwb, zio_t *zio);
extern int zil_parse(zilog_t *zilog, zil_parse_blk_func_t *parse_blk_func,
zil_parse_lr_func_t *parse_lr_func, void *arg, uint64_t txg);
@@ -426,7 +429,8 @@ extern void zil_clean(zilog_t *zilog, uint64_t synced_txg);
extern int zil_suspend(const char *osname, void **cookiep);
extern void zil_resume(void *cookie);
-extern void zil_add_block(zilog_t *zilog, const blkptr_t *bp);
+extern void zil_lwb_add_block(struct lwb *lwb, const blkptr_t *bp);
+extern void zil_lwb_add_txg(struct lwb *lwb, uint64_t txg);
extern int zil_bp_tree_add(zilog_t *zilog, const blkptr_t *bp);
extern void zil_set_sync(zilog_t *zilog, uint64_t syncval);
diff --git a/usr/src/uts/common/fs/zfs/sys/zil_impl.h b/usr/src/uts/common/fs/zfs/sys/zil_impl.h
index 0618133124..308f4a5da3 100644
--- a/usr/src/uts/common/fs/zfs/sys/zil_impl.h
+++ b/usr/src/uts/common/fs/zfs/sys/zil_impl.h
@@ -20,7 +20,7 @@
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
* Copyright (c) 2014 Integros [integros.com]
*/
@@ -37,7 +37,30 @@ extern "C" {
#endif
/*
- * Log write buffer.
+ * Possbile states for a given lwb structure. An lwb will start out in
+ * the "closed" state, and then transition to the "opened" state via a
+ * call to zil_lwb_write_open(). After the lwb is "open", it can
+ * transition into the "issued" state via zil_lwb_write_issue(). After
+ * the lwb's zio completes, and the vdev's are flushed, the lwb will
+ * transition into the "done" state via zil_lwb_write_done(), and the
+ * structure eventually freed.
+ */
+typedef enum {
+ LWB_STATE_CLOSED,
+ LWB_STATE_OPENED,
+ LWB_STATE_ISSUED,
+ LWB_STATE_DONE,
+ LWB_NUM_STATES
+} lwb_state_t;
+
+/*
+ * Log write block (lwb)
+ *
+ * Prior to an lwb being issued to disk via zil_lwb_write_issue(), it
+ * will be protected by the zilog's "zl_writer_lock". Basically, prior
+ * to it being issued, it will only be accessed by the thread that's
+ * holding the "zl_writer_lock". After the lwb is issued, the zilog's
+ * "zl_lock" is used to protect the lwb against concurrent access.
*/
typedef struct lwb {
zilog_t *lwb_zilog; /* back pointer to log struct */
@@ -45,14 +68,45 @@ typedef struct lwb {
boolean_t lwb_slog; /* lwb_blk is on SLOG device */
int lwb_nused; /* # used bytes in buffer */
int lwb_sz; /* size of block and buffer */
+ lwb_state_t lwb_state; /* the state of this lwb */
char *lwb_buf; /* log write buffer */
- zio_t *lwb_zio; /* zio for this buffer */
+ zio_t *lwb_write_zio; /* zio for the lwb buffer */
+ zio_t *lwb_root_zio; /* root zio for lwb write and flushes */
dmu_tx_t *lwb_tx; /* tx for log block allocation */
uint64_t lwb_max_txg; /* highest txg in this lwb */
list_node_t lwb_node; /* zilog->zl_lwb_list linkage */
+ list_t lwb_waiters; /* list of zil_commit_waiter's */
+ avl_tree_t lwb_vdev_tree; /* vdevs to flush after lwb write */
+ kmutex_t lwb_vdev_lock; /* protects lwb_vdev_tree */
+ hrtime_t lwb_issued_timestamp; /* when was the lwb issued? */
} lwb_t;
/*
+ * ZIL commit waiter.
+ *
+ * This structure is allocated each time zil_commit() is called, and is
+ * used by zil_commit() to communicate with other parts of the ZIL, such
+ * that zil_commit() can know when it safe for it return. For more
+ * details, see the comment above zil_commit().
+ *
+ * The "zcw_lock" field is used to protect the commit waiter against
+ * concurrent access. This lock is often acquired while already holding
+ * the zilog's "zl_writer_lock" or "zl_lock"; see the functions
+ * zil_process_commit_list() and zil_lwb_flush_vdevs_done() as examples
+ * of this. Thus, one must be careful not to acquire the
+ * "zl_writer_lock" or "zl_lock" when already holding the "zcw_lock";
+ * e.g. see the zil_commit_waiter_timeout() function.
+ */
+typedef struct zil_commit_waiter {
+ kcondvar_t zcw_cv; /* signalled when "done" */
+ kmutex_t zcw_lock; /* protects fields of this struct */
+ list_node_t zcw_node; /* linkage in lwb_t:lwb_waiter list */
+ lwb_t *zcw_lwb; /* back pointer to lwb when linked */
+ boolean_t zcw_done; /* B_TRUE when "done", else B_FALSE */
+ int zcw_zio_error; /* contains the zio io_error value */
+} zil_commit_waiter_t;
+
+/*
* Intent log transaction lists
*/
typedef struct itxs {
@@ -94,20 +148,20 @@ struct zilog {
const zil_header_t *zl_header; /* log header buffer */
objset_t *zl_os; /* object set we're logging */
zil_get_data_t *zl_get_data; /* callback to get object content */
- zio_t *zl_root_zio; /* log writer root zio */
+ lwb_t *zl_last_lwb_opened; /* most recent lwb opened */
+ hrtime_t zl_last_lwb_latency; /* zio latency of last lwb done */
uint64_t zl_lr_seq; /* on-disk log record sequence number */
uint64_t zl_commit_lr_seq; /* last committed on-disk lr seq */
uint64_t zl_destroy_txg; /* txg of last zil_destroy() */
uint64_t zl_replayed_seq[TXG_SIZE]; /* last replayed rec seq */
uint64_t zl_replaying_seq; /* current replay seq number */
uint32_t zl_suspend; /* log suspend count */
- kcondvar_t zl_cv_writer; /* log writer thread completion */
kcondvar_t zl_cv_suspend; /* log suspend completion */
uint8_t zl_suspending; /* log is currently suspending */
uint8_t zl_keep_first; /* keep first log block in destroy */
uint8_t zl_replay; /* replaying records while set */
uint8_t zl_stop_sync; /* for debugging */
- uint8_t zl_writer; /* boolean: write setup in progress */
+ kmutex_t zl_writer_lock; /* single writer, per ZIL, at a time */
uint8_t zl_logbias; /* latency or throughput */
uint8_t zl_sync; /* synchronous or asynchronous */
int zl_parse_error; /* last zil_parse() error */
@@ -115,15 +169,10 @@ struct zilog {
uint64_t zl_parse_lr_seq; /* highest lr seq on last parse */
uint64_t zl_parse_blk_count; /* number of blocks parsed */
uint64_t zl_parse_lr_count; /* number of log records parsed */
- uint64_t zl_next_batch; /* next batch number */
- uint64_t zl_com_batch; /* committed batch number */
- kcondvar_t zl_cv_batch[2]; /* batch condition variables */
itxg_t zl_itxg[TXG_SIZE]; /* intent log txg chains */
list_t zl_itx_commit_list; /* itx list to be committed */
uint64_t zl_cur_used; /* current commit log size used */
list_t zl_lwb_list; /* in-flight log write list */
- kmutex_t zl_vdev_lock; /* protects zl_vdev_tree */
- avl_tree_t zl_vdev_tree; /* vdevs to flush in zil_commit() */
avl_tree_t zl_bp_tree; /* track bps during log parse */
clock_t zl_replay_time; /* lbolt of when replay started */
uint64_t zl_replay_blks; /* number of log blocks replayed */
@@ -131,6 +180,7 @@ struct zilog {
uint_t zl_prev_blks[ZIL_PREV_BLKS]; /* size - sector rounded */
uint_t zl_prev_rotor; /* rotor for zl_prev[] */
txg_node_t zl_dirty_link; /* protected by dp_dirty_zilogs list */
+ uint64_t zl_dirty_max_txg; /* highest txg used to dirty zilog */
};
typedef struct zil_bp_node {
diff --git a/usr/src/uts/common/fs/zfs/sys/zio.h b/usr/src/uts/common/fs/zfs/sys/zio.h
index 9c2beafaa6..281b2a678d 100644
--- a/usr/src/uts/common/fs/zfs/sys/zio.h
+++ b/usr/src/uts/common/fs/zfs/sys/zio.h
@@ -558,6 +558,7 @@ extern enum zio_checksum zio_checksum_dedup_select(spa_t *spa,
extern enum zio_compress zio_compress_select(spa_t *spa,
enum zio_compress child, enum zio_compress parent);
+extern void zio_cancel(zio_t *zio);
extern void zio_suspend(spa_t *spa, zio_t *zio);
extern int zio_resume(spa_t *spa);
extern void zio_resume_wait(spa_t *spa);
diff --git a/usr/src/uts/common/fs/zfs/txg.c b/usr/src/uts/common/fs/zfs/txg.c
index a3ad0fb1aa..52740996b1 100644
--- a/usr/src/uts/common/fs/zfs/txg.c
+++ b/usr/src/uts/common/fs/zfs/txg.c
@@ -159,7 +159,7 @@ txg_fini(dsl_pool_t *dp)
tx_state_t *tx = &dp->dp_tx;
int c;
- ASSERT(tx->tx_threads == 0);
+ ASSERT0(tx->tx_threads);
mutex_destroy(&tx->tx_sync_lock);
@@ -200,7 +200,7 @@ txg_sync_start(dsl_pool_t *dp)
dprintf("pool %p\n", dp);
- ASSERT(tx->tx_threads == 0);
+ ASSERT0(tx->tx_threads);
tx->tx_threads = 2;
@@ -262,7 +262,7 @@ txg_sync_stop(dsl_pool_t *dp)
/*
* Finish off any work in progress.
*/
- ASSERT(tx->tx_threads == 2);
+ ASSERT3U(tx->tx_threads, ==, 2);
/*
* We need to ensure that we've vacated the deferred space_maps.
@@ -274,7 +274,7 @@ txg_sync_stop(dsl_pool_t *dp)
*/
mutex_enter(&tx->tx_sync_lock);
- ASSERT(tx->tx_threads == 2);
+ ASSERT3U(tx->tx_threads, ==, 2);
tx->tx_exiting = 1;
@@ -611,7 +611,7 @@ txg_wait_synced(dsl_pool_t *dp, uint64_t txg)
ASSERT(!dsl_pool_config_held(dp));
mutex_enter(&tx->tx_sync_lock);
- ASSERT(tx->tx_threads == 2);
+ ASSERT3U(tx->tx_threads, ==, 2);
if (txg == 0)
txg = tx->tx_open_txg + TXG_DEFER_SIZE;
if (tx->tx_sync_txg_waiting < txg)
@@ -636,7 +636,7 @@ txg_wait_open(dsl_pool_t *dp, uint64_t txg)
ASSERT(!dsl_pool_config_held(dp));
mutex_enter(&tx->tx_sync_lock);
- ASSERT(tx->tx_threads == 2);
+ ASSERT3U(tx->tx_threads, ==, 2);
if (txg == 0)
txg = tx->tx_open_txg + 1;
if (tx->tx_quiesce_txg_waiting < txg)
diff --git a/usr/src/uts/common/fs/zfs/zfs_vnops.c b/usr/src/uts/common/fs/zfs/zfs_vnops.c
index 7cb53007ed..b7bf82b43a 100644
--- a/usr/src/uts/common/fs/zfs/zfs_vnops.c
+++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c
@@ -83,6 +83,7 @@
#include <sys/kidmap.h>
#include <sys/cred.h>
#include <sys/attr.h>
+#include <sys/zil.h>
/*
* Programming rules.
@@ -1034,7 +1035,7 @@ zfs_get_done(zgd_t *zgd, int error)
VN_RELE_ASYNC(ZTOV(zp), dsl_pool_vnrele_taskq(dmu_objset_pool(os)));
if (error == 0 && zgd->zgd_bp)
- zil_add_block(zgd->zgd_zilog, zgd->zgd_bp);
+ zil_lwb_add_block(zgd->zgd_lwb, zgd->zgd_bp);
kmem_free(zgd, sizeof (zgd_t));
}
@@ -1047,7 +1048,7 @@ static int zil_fault_io = 0;
* Get data to generate a TX_WRITE intent log record.
*/
int
-zfs_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
+zfs_get_data(void *arg, lr_write_t *lr, char *buf, struct lwb *lwb, zio_t *zio)
{
zfsvfs_t *zfsvfs = arg;
objset_t *os = zfsvfs->z_os;
@@ -1059,8 +1060,9 @@ zfs_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
zgd_t *zgd;
int error = 0;
- ASSERT(zio != NULL);
- ASSERT(size != 0);
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3P(zio, !=, NULL);
+ ASSERT3U(size, !=, 0);
/*
* Nothing to do if the file has been removed
@@ -1078,7 +1080,7 @@ zfs_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
}
zgd = (zgd_t *)kmem_zalloc(sizeof (zgd_t), KM_SLEEP);
- zgd->zgd_zilog = zfsvfs->z_log;
+ zgd->zgd_lwb = lwb;
zgd->zgd_private = zp;
/*
diff --git a/usr/src/uts/common/fs/zfs/zil.c b/usr/src/uts/common/fs/zfs/zil.c
index 1e1f1cdd5d..8ad62309eb 100644
--- a/usr/src/uts/common/fs/zfs/zil.c
+++ b/usr/src/uts/common/fs/zfs/zil.c
@@ -43,30 +43,51 @@
#include <sys/abd.h>
/*
- * The zfs intent log (ZIL) saves transaction records of system calls
- * that change the file system in memory with enough information
- * to be able to replay them. These are stored in memory until
- * either the DMU transaction group (txg) commits them to the stable pool
- * and they can be discarded, or they are flushed to the stable log
- * (also in the pool) due to a fsync, O_DSYNC or other synchronous
- * requirement. In the event of a panic or power fail then those log
- * records (transactions) are replayed.
+ * The ZFS Intent Log (ZIL) saves "transaction records" (itxs) of system
+ * calls that change the file system. Each itx has enough information to
+ * be able to replay them after a system crash, power loss, or
+ * equivalent failure mode. These are stored in memory until either:
*
- * There is one ZIL per file system. Its on-disk (pool) format consists
- * of 3 parts:
+ * 1. they are committed to the pool by the DMU transaction group
+ * (txg), at which point they can be discarded; or
+ * 2. they are committed to the on-disk ZIL for the dataset being
+ * modified (e.g. due to an fsync, O_DSYNC, or other synchronous
+ * requirement).
*
- * - ZIL header
- * - ZIL blocks
- * - ZIL records
+ * In the event of a crash or power loss, the itxs contained by each
+ * dataset's on-disk ZIL will be replayed when that dataset is first
+ * instantianted (e.g. if the dataset is a normal fileystem, when it is
+ * first mounted).
*
- * A log record holds a system call transaction. Log blocks can
- * hold many log records and the blocks are chained together.
- * Each ZIL block contains a block pointer (blkptr_t) to the next
- * ZIL block in the chain. The ZIL header points to the first
- * block in the chain. Note there is not a fixed place in the pool
- * to hold blocks. They are dynamically allocated and freed as
- * needed from the blocks available. Figure X shows the ZIL structure:
+ * As hinted at above, there is one ZIL per dataset (both the in-memory
+ * representation, and the on-disk representation). The on-disk format
+ * consists of 3 parts:
+ *
+ * - a single, per-dataset, ZIL header; which points to a chain of
+ * - zero or more ZIL blocks; each of which contains
+ * - zero or more ZIL records
+ *
+ * A ZIL record holds the information necessary to replay a single
+ * system call transaction. A ZIL block can hold many ZIL records, and
+ * the blocks are chained together, similarly to a singly linked list.
+ *
+ * Each ZIL block contains a block pointer (blkptr_t) to the next ZIL
+ * block in the chain, and the ZIL header points to the first block in
+ * the chain.
+ *
+ * Note, there is not a fixed place in the pool to hold these ZIL
+ * blocks; they are dynamically allocated and freed as needed from the
+ * blocks available on the pool, though they can be preferentially
+ * allocated from a dedicated "log" vdev.
+ */
+
+/*
+ * This controls the amount of time that a ZIL block (lwb) will remain
+ * "open" when it isn't "full", and it has a thread waiting for it to be
+ * committed to stable storage. Please refer to the zil_commit_waiter()
+ * function (and the comments within it) for more details.
*/
+int zfs_commit_timeout_pct = 5;
/*
* Disable intent logging replay. This global ZIL switch affects all pools.
@@ -88,6 +109,7 @@ boolean_t zfs_nocacheflush = B_FALSE;
uint64_t zil_slog_bulk = 768 * 1024;
static kmem_cache_t *zil_lwb_cache;
+static kmem_cache_t *zil_zcw_cache;
static void zil_async_to_sync(zilog_t *zilog, uint64_t foid);
@@ -437,6 +459,20 @@ zil_free_log_record(zilog_t *zilog, lr_t *lrc, void *tx, uint64_t claim_txg)
return (0);
}
+static int
+zil_lwb_vdev_compare(const void *x1, const void *x2)
+{
+ const uint64_t v1 = ((zil_vdev_node_t *)x1)->zv_vdev;
+ const uint64_t v2 = ((zil_vdev_node_t *)x2)->zv_vdev;
+
+ if (v1 < v2)
+ return (-1);
+ if (v1 > v2)
+ return (1);
+
+ return (0);
+}
+
static lwb_t *
zil_alloc_lwb(zilog_t *zilog, blkptr_t *bp, boolean_t slog, uint64_t txg)
{
@@ -446,10 +482,13 @@ zil_alloc_lwb(zilog_t *zilog, blkptr_t *bp, boolean_t slog, uint64_t txg)
lwb->lwb_zilog = zilog;
lwb->lwb_blk = *bp;
lwb->lwb_slog = slog;
+ lwb->lwb_state = LWB_STATE_CLOSED;
lwb->lwb_buf = zio_buf_alloc(BP_GET_LSIZE(bp));
lwb->lwb_max_txg = txg;
- lwb->lwb_zio = NULL;
+ lwb->lwb_write_zio = NULL;
+ lwb->lwb_root_zio = NULL;
lwb->lwb_tx = NULL;
+ lwb->lwb_issued_timestamp = 0;
if (BP_GET_CHECKSUM(bp) == ZIO_CHECKSUM_ZILOG2) {
lwb->lwb_nused = sizeof (zil_chain_t);
lwb->lwb_sz = BP_GET_LSIZE(bp);
@@ -462,9 +501,54 @@ zil_alloc_lwb(zilog_t *zilog, blkptr_t *bp, boolean_t slog, uint64_t txg)
list_insert_tail(&zilog->zl_lwb_list, lwb);
mutex_exit(&zilog->zl_lock);
+ ASSERT(!MUTEX_HELD(&lwb->lwb_vdev_lock));
+ ASSERT(avl_is_empty(&lwb->lwb_vdev_tree));
+ ASSERT(list_is_empty(&lwb->lwb_waiters));
+
return (lwb);
}
+static void
+zil_free_lwb(zilog_t *zilog, lwb_t *lwb)
+{
+ ASSERT(MUTEX_HELD(&zilog->zl_lock));
+ ASSERT(!MUTEX_HELD(&lwb->lwb_vdev_lock));
+ ASSERT(list_is_empty(&lwb->lwb_waiters));
+
+ if (lwb->lwb_state == LWB_STATE_OPENED) {
+ avl_tree_t *t = &lwb->lwb_vdev_tree;
+ void *cookie = NULL;
+ zil_vdev_node_t *zv;
+
+ while ((zv = avl_destroy_nodes(t, &cookie)) != NULL)
+ kmem_free(zv, sizeof (*zv));
+
+ ASSERT3P(lwb->lwb_root_zio, !=, NULL);
+ ASSERT3P(lwb->lwb_write_zio, !=, NULL);
+
+ zio_cancel(lwb->lwb_root_zio);
+ zio_cancel(lwb->lwb_write_zio);
+
+ lwb->lwb_root_zio = NULL;
+ lwb->lwb_write_zio = NULL;
+ } else {
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_ISSUED);
+ }
+
+ ASSERT(avl_is_empty(&lwb->lwb_vdev_tree));
+ ASSERT3P(lwb->lwb_write_zio, ==, NULL);
+ ASSERT3P(lwb->lwb_root_zio, ==, NULL);
+
+ /*
+ * Clear the zilog's field to indicate this lwb is no longer
+ * valid, and prevent use-after-free errors.
+ */
+ if (zilog->zl_last_lwb_opened == lwb)
+ zilog->zl_last_lwb_opened = NULL;
+
+ kmem_cache_free(zil_lwb_cache, lwb);
+}
+
/*
* Called when we create in-memory log transactions so that we know
* to cleanup the itxs at the end of spa_sync().
@@ -475,12 +559,16 @@ zilog_dirty(zilog_t *zilog, uint64_t txg)
dsl_pool_t *dp = zilog->zl_dmu_pool;
dsl_dataset_t *ds = dmu_objset_ds(zilog->zl_os);
+ ASSERT(spa_writeable(zilog->zl_spa));
+
if (ds->ds_is_snapshot)
panic("dirtying snapshot!");
if (txg_list_add(&dp->dp_dirty_zilogs, zilog, txg)) {
/* up the hold count until we can be written out */
dmu_buf_add_ref(ds->ds_dbuf, zilog);
+
+ zilog->zl_dirty_max_txg = MAX(txg, zilog->zl_dirty_max_txg);
}
}
@@ -548,7 +636,7 @@ zil_create(zilog_t *zilog)
*/
if (BP_IS_HOLE(&blk) || BP_SHOULD_BYTESWAP(&blk)) {
tx = dmu_tx_create(zilog->zl_os);
- VERIFY(dmu_tx_assign(tx, TXG_WAIT) == 0);
+ VERIFY0(dmu_tx_assign(tx, TXG_WAIT));
dsl_dataset_dirty(dmu_objset_ds(zilog->zl_os), tx);
txg = dmu_tx_get_txg(tx);
@@ -565,7 +653,7 @@ zil_create(zilog_t *zilog)
}
/*
- * Allocate a log write buffer (lwb) for the first log block.
+ * Allocate a log write block (lwb) for the first log block.
*/
if (error == 0)
lwb = zil_alloc_lwb(zilog, &blk, slog, txg);
@@ -586,13 +674,13 @@ zil_create(zilog_t *zilog)
}
/*
- * In one tx, free all log blocks and clear the log header.
- * If keep_first is set, then we're replaying a log with no content.
- * We want to keep the first block, however, so that the first
- * synchronous transaction doesn't require a txg_wait_synced()
- * in zil_create(). We don't need to txg_wait_synced() here either
- * when keep_first is set, because both zil_create() and zil_destroy()
- * will wait for any in-progress destroys to complete.
+ * In one tx, free all log blocks and clear the log header. If keep_first
+ * is set, then we're replaying a log with no content. We want to keep the
+ * first block, however, so that the first synchronous transaction doesn't
+ * require a txg_wait_synced() in zil_create(). We don't need to
+ * txg_wait_synced() here either when keep_first is set, because both
+ * zil_create() and zil_destroy() will wait for any in-progress destroys
+ * to complete.
*/
void
zil_destroy(zilog_t *zilog, boolean_t keep_first)
@@ -613,7 +701,7 @@ zil_destroy(zilog_t *zilog, boolean_t keep_first)
return;
tx = dmu_tx_create(zilog->zl_os);
- VERIFY(dmu_tx_assign(tx, TXG_WAIT) == 0);
+ VERIFY0(dmu_tx_assign(tx, TXG_WAIT));
dsl_dataset_dirty(dmu_objset_ds(zilog->zl_os), tx);
txg = dmu_tx_get_txg(tx);
@@ -630,8 +718,8 @@ zil_destroy(zilog_t *zilog, boolean_t keep_first)
list_remove(&zilog->zl_lwb_list, lwb);
if (lwb->lwb_buf != NULL)
zio_buf_free(lwb->lwb_buf, lwb->lwb_sz);
- zio_free_zil(zilog->zl_spa, txg, &lwb->lwb_blk);
- kmem_cache_free(zil_lwb_cache, lwb);
+ zio_free(zilog->zl_spa, txg, &lwb->lwb_blk);
+ zil_free_lwb(zilog, lwb);
}
} else if (!keep_first) {
zil_destroy_sync(zilog, tx);
@@ -769,24 +857,64 @@ zil_check_log_chain(dsl_pool_t *dp, dsl_dataset_t *ds, void *tx)
return ((error == ECKSUM || error == ENOENT) ? 0 : error);
}
-static int
-zil_vdev_compare(const void *x1, const void *x2)
+/*
+ * When an itx is "skipped", this function is used to properly mark the
+ * waiter as "done, and signal any thread(s) waiting on it. An itx can
+ * be skipped (and not committed to an lwb) for a variety of reasons,
+ * one of them being that the itx was committed via spa_sync(), prior to
+ * it being committed to an lwb; this can happen if a thread calling
+ * zil_commit() is racing with spa_sync().
+ */
+static void
+zil_commit_waiter_skip(zil_commit_waiter_t *zcw)
{
- const uint64_t v1 = ((zil_vdev_node_t *)x1)->zv_vdev;
- const uint64_t v2 = ((zil_vdev_node_t *)x2)->zv_vdev;
+ mutex_enter(&zcw->zcw_lock);
+ ASSERT3B(zcw->zcw_done, ==, B_FALSE);
+ zcw->zcw_done = B_TRUE;
+ cv_broadcast(&zcw->zcw_cv);
+ mutex_exit(&zcw->zcw_lock);
+}
- if (v1 < v2)
- return (-1);
- if (v1 > v2)
- return (1);
+/*
+ * This function is used when the given waiter is to be linked into an
+ * lwb's "lwb_waiter" list; i.e. when the itx is committed to the lwb.
+ * At this point, the waiter will no longer be referenced by the itx,
+ * and instead, will be referenced by the lwb.
+ */
+static void
+zil_commit_waiter_link_lwb(zil_commit_waiter_t *zcw, lwb_t *lwb)
+{
+ mutex_enter(&zcw->zcw_lock);
+ ASSERT(!list_link_active(&zcw->zcw_node));
+ ASSERT3P(zcw->zcw_lwb, ==, NULL);
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT(lwb->lwb_state == LWB_STATE_OPENED ||
+ lwb->lwb_state == LWB_STATE_ISSUED);
+
+ list_insert_tail(&lwb->lwb_waiters, zcw);
+ zcw->zcw_lwb = lwb;
+ mutex_exit(&zcw->zcw_lock);
+}
- return (0);
+/*
+ * This function is used when zio_alloc_zil() fails to allocate a ZIL
+ * block, and the given waiter must be linked to the "nolwb waiters"
+ * list inside of zil_process_commit_list().
+ */
+static void
+zil_commit_waiter_link_nolwb(zil_commit_waiter_t *zcw, list_t *nolwb)
+{
+ mutex_enter(&zcw->zcw_lock);
+ ASSERT(!list_link_active(&zcw->zcw_node));
+ ASSERT3P(zcw->zcw_lwb, ==, NULL);
+ list_insert_tail(nolwb, zcw);
+ mutex_exit(&zcw->zcw_lock);
}
void
-zil_add_block(zilog_t *zilog, const blkptr_t *bp)
+zil_lwb_add_block(lwb_t *lwb, const blkptr_t *bp)
{
- avl_tree_t *t = &zilog->zl_vdev_tree;
+ avl_tree_t *t = &lwb->lwb_vdev_tree;
avl_index_t where;
zil_vdev_node_t *zv, zvsearch;
int ndvas = BP_GET_NDVAS(bp);
@@ -795,14 +923,7 @@ zil_add_block(zilog_t *zilog, const blkptr_t *bp)
if (zfs_nocacheflush)
return;
- ASSERT(zilog->zl_writer);
-
- /*
- * Even though we're zl_writer, we still need a lock because the
- * zl_get_data() callbacks may have dmu_sync() done callbacks
- * that will run concurrently.
- */
- mutex_enter(&zilog->zl_vdev_lock);
+ mutex_enter(&lwb->lwb_vdev_lock);
for (i = 0; i < ndvas; i++) {
zvsearch.zv_vdev = DVA_GET_VDEV(&bp->blk_dva[i]);
if (avl_find(t, &zvsearch, &where) == NULL) {
@@ -811,56 +932,116 @@ zil_add_block(zilog_t *zilog, const blkptr_t *bp)
avl_insert(t, zv, where);
}
}
- mutex_exit(&zilog->zl_vdev_lock);
+ mutex_exit(&lwb->lwb_vdev_lock);
+}
+
+void
+zil_lwb_add_txg(lwb_t *lwb, uint64_t txg)
+{
+ lwb->lwb_max_txg = MAX(lwb->lwb_max_txg, txg);
}
+/*
+ * This function is a called after all VDEVs associated with a given lwb
+ * write have completed their DKIOCFLUSHWRITECACHE command; or as soon
+ * as the lwb write completes, if "zfs_nocacheflush" is set.
+ *
+ * The intention is for this function to be called as soon as the
+ * contents of an lwb are considered "stable" on disk, and will survive
+ * any sudden loss of power. At this point, any threads waiting for the
+ * lwb to reach this state are signalled, and the "waiter" structures
+ * are marked "done".
+ */
static void
-zil_flush_vdevs(zilog_t *zilog)
+zil_lwb_flush_vdevs_done(zio_t *zio)
{
- spa_t *spa = zilog->zl_spa;
- avl_tree_t *t = &zilog->zl_vdev_tree;
- void *cookie = NULL;
- zil_vdev_node_t *zv;
- zio_t *zio;
+ lwb_t *lwb = zio->io_private;
+ zilog_t *zilog = lwb->lwb_zilog;
+ dmu_tx_t *tx = lwb->lwb_tx;
+ zil_commit_waiter_t *zcw;
+
+ spa_config_exit(zilog->zl_spa, SCL_STATE, lwb);
- ASSERT(zilog->zl_writer);
+ zio_buf_free(lwb->lwb_buf, lwb->lwb_sz);
+
+ mutex_enter(&zilog->zl_lock);
/*
- * We don't need zl_vdev_lock here because we're the zl_writer,
- * and all zl_get_data() callbacks are done.
+ * Ensure the lwb buffer pointer is cleared before releasing the
+ * txg. If we have had an allocation failure and the txg is
+ * waiting to sync then we want zil_sync() to remove the lwb so
+ * that it's not picked up as the next new one in
+ * zil_process_commit_list(). zil_sync() will only remove the
+ * lwb if lwb_buf is null.
*/
- if (avl_numnodes(t) == 0)
- return;
+ lwb->lwb_buf = NULL;
+ lwb->lwb_tx = NULL;
- spa_config_enter(spa, SCL_STATE, FTAG, RW_READER);
+ ASSERT3U(lwb->lwb_issued_timestamp, >, 0);
+ zilog->zl_last_lwb_latency = gethrtime() - lwb->lwb_issued_timestamp;
- zio = zio_root(spa, NULL, NULL, ZIO_FLAG_CANFAIL);
+ lwb->lwb_root_zio = NULL;
+ lwb->lwb_state = LWB_STATE_DONE;
- while ((zv = avl_destroy_nodes(t, &cookie)) != NULL) {
- vdev_t *vd = vdev_lookup_top(spa, zv->zv_vdev);
- if (vd != NULL)
- zio_flush(zio, vd);
- kmem_free(zv, sizeof (*zv));
+ if (zilog->zl_last_lwb_opened == lwb) {
+ /*
+ * Remember the highest committed log sequence number
+ * for ztest. We only update this value when all the log
+ * writes succeeded, because ztest wants to ASSERT that
+ * it got the whole log chain.
+ */
+ zilog->zl_commit_lr_seq = zilog->zl_lr_seq;
}
+ while ((zcw = list_head(&lwb->lwb_waiters)) != NULL) {
+ mutex_enter(&zcw->zcw_lock);
+
+ ASSERT(list_link_active(&zcw->zcw_node));
+ list_remove(&lwb->lwb_waiters, zcw);
+
+ ASSERT3P(zcw->zcw_lwb, ==, lwb);
+ zcw->zcw_lwb = NULL;
+
+ zcw->zcw_zio_error = zio->io_error;
+
+ ASSERT3B(zcw->zcw_done, ==, B_FALSE);
+ zcw->zcw_done = B_TRUE;
+ cv_broadcast(&zcw->zcw_cv);
+
+ mutex_exit(&zcw->zcw_lock);
+ }
+
+ mutex_exit(&zilog->zl_lock);
+
/*
- * Wait for all the flushes to complete. Not all devices actually
- * support the DKIOCFLUSHWRITECACHE ioctl, so it's OK if it fails.
+ * Now that we've written this log block, we have a stable pointer
+ * to the next block in the chain, so it's OK to let the txg in
+ * which we allocated the next block sync.
*/
- (void) zio_wait(zio);
-
- spa_config_exit(spa, SCL_STATE, FTAG);
+ dmu_tx_commit(tx);
}
/*
- * Function called when a log block write completes
+ * This is called when an lwb write completes. This means, this specific
+ * lwb was written to disk, and all dependent lwb have also been
+ * written to disk.
+ *
+ * At this point, a DKIOCFLUSHWRITECACHE command hasn't been issued to
+ * the VDEVs involved in writing out this specific lwb. The lwb will be
+ * "done" once zil_lwb_flush_vdevs_done() is called, which occurs in the
+ * zio completion callback for the lwb's root zio.
*/
static void
zil_lwb_write_done(zio_t *zio)
{
lwb_t *lwb = zio->io_private;
+ spa_t *spa = zio->io_spa;
zilog_t *zilog = lwb->lwb_zilog;
- dmu_tx_t *tx = lwb->lwb_tx;
+ avl_tree_t *t = &lwb->lwb_vdev_tree;
+ void *cookie = NULL;
+ zil_vdev_node_t *zv;
+
+ ASSERT3S(spa_config_held(spa, SCL_STATE, RW_READER), !=, 0);
ASSERT(BP_GET_COMPRESS(zio->io_bp) == ZIO_COMPRESS_OFF);
ASSERT(BP_GET_TYPE(zio->io_bp) == DMU_OT_INTENT_LOG);
@@ -870,58 +1051,115 @@ zil_lwb_write_done(zio_t *zio)
ASSERT(!BP_IS_HOLE(zio->io_bp));
ASSERT(BP_GET_FILL(zio->io_bp) == 0);
- /*
- * Ensure the lwb buffer pointer is cleared before releasing
- * the txg. If we have had an allocation failure and
- * the txg is waiting to sync then we want want zil_sync()
- * to remove the lwb so that it's not picked up as the next new
- * one in zil_commit_writer(). zil_sync() will only remove
- * the lwb if lwb_buf is null.
- */
abd_put(zio->io_abd);
- zio_buf_free(lwb->lwb_buf, lwb->lwb_sz);
+
+ ASSERT3S(lwb->lwb_state, ==, LWB_STATE_ISSUED);
+
mutex_enter(&zilog->zl_lock);
- lwb->lwb_buf = NULL;
- lwb->lwb_tx = NULL;
+ lwb->lwb_write_zio = NULL;
mutex_exit(&zilog->zl_lock);
+ if (avl_numnodes(t) == 0)
+ return;
+
/*
- * Now that we've written this log block, we have a stable pointer
- * to the next block in the chain, so it's OK to let the txg in
- * which we allocated the next block sync.
+ * If there was an IO error, we're not going to call zio_flush()
+ * on these vdevs, so we simply empty the tree and free the
+ * nodes. We avoid calling zio_flush() since there isn't any
+ * good reason for doing so, after the lwb block failed to be
+ * written out.
*/
- dmu_tx_commit(tx);
+ if (zio->io_error != 0) {
+ while ((zv = avl_destroy_nodes(t, &cookie)) != NULL)
+ kmem_free(zv, sizeof (*zv));
+ return;
+ }
+
+ while ((zv = avl_destroy_nodes(t, &cookie)) != NULL) {
+ vdev_t *vd = vdev_lookup_top(spa, zv->zv_vdev);
+ if (vd != NULL)
+ zio_flush(lwb->lwb_root_zio, vd);
+ kmem_free(zv, sizeof (*zv));
+ }
}
/*
- * Initialize the io for a log block.
+ * This function's purpose is to "open" an lwb such that it is ready to
+ * accept new itxs being committed to it. To do this, the lwb's zio
+ * structures are created, and linked to the lwb. This function is
+ * idempotent; if the passed in lwb has already been opened, this
+ * function is essentially a no-op.
*/
static void
-zil_lwb_write_init(zilog_t *zilog, lwb_t *lwb)
+zil_lwb_write_open(zilog_t *zilog, lwb_t *lwb)
{
zbookmark_phys_t zb;
zio_priority_t prio;
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
+ ASSERT3P(lwb, !=, NULL);
+ EQUIV(lwb->lwb_root_zio == NULL, lwb->lwb_state == LWB_STATE_CLOSED);
+ EQUIV(lwb->lwb_root_zio != NULL, lwb->lwb_state == LWB_STATE_OPENED);
+
SET_BOOKMARK(&zb, lwb->lwb_blk.blk_cksum.zc_word[ZIL_ZC_OBJSET],
ZB_ZIL_OBJECT, ZB_ZIL_LEVEL,
lwb->lwb_blk.blk_cksum.zc_word[ZIL_ZC_SEQ]);
- if (zilog->zl_root_zio == NULL) {
- zilog->zl_root_zio = zio_root(zilog->zl_spa, NULL, NULL,
- ZIO_FLAG_CANFAIL);
- }
- if (lwb->lwb_zio == NULL) {
+ if (lwb->lwb_root_zio == NULL) {
abd_t *lwb_abd = abd_get_from_buf(lwb->lwb_buf,
BP_GET_LSIZE(&lwb->lwb_blk));
+
if (!lwb->lwb_slog || zilog->zl_cur_used <= zil_slog_bulk)
prio = ZIO_PRIORITY_SYNC_WRITE;
else
prio = ZIO_PRIORITY_ASYNC_WRITE;
- lwb->lwb_zio = zio_rewrite(zilog->zl_root_zio, zilog->zl_spa,
- 0, &lwb->lwb_blk, lwb_abd, BP_GET_LSIZE(&lwb->lwb_blk),
- zil_lwb_write_done, lwb, prio,
- ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE, &zb);
+
+ lwb->lwb_root_zio = zio_root(zilog->zl_spa,
+ zil_lwb_flush_vdevs_done, lwb, ZIO_FLAG_CANFAIL);
+ ASSERT3P(lwb->lwb_root_zio, !=, NULL);
+
+ lwb->lwb_write_zio = zio_rewrite(lwb->lwb_root_zio,
+ zilog->zl_spa, 0, &lwb->lwb_blk, lwb_abd,
+ BP_GET_LSIZE(&lwb->lwb_blk), zil_lwb_write_done, lwb,
+ prio, ZIO_FLAG_CANFAIL | ZIO_FLAG_DONT_PROPAGATE, &zb);
+ ASSERT3P(lwb->lwb_write_zio, !=, NULL);
+
+ lwb->lwb_state = LWB_STATE_OPENED;
+
+ mutex_enter(&zilog->zl_lock);
+
+ /*
+ * The zilog's "zl_last_lwb_opened" field is used to
+ * build the lwb/zio dependency chain, which is used to
+ * preserve the ordering of lwb completions that is
+ * required by the semantics of the ZIL. Each new lwb
+ * zio becomes a parent of the "previous" lwb zio, such
+ * that the new lwb's zio cannot complete until the
+ * "previous" lwb's zio completes.
+ *
+ * This is required by the semantics of zil_commit();
+ * the commit waiters attached to the lwbs will be woken
+ * in the lwb zio's completion callback, so this zio
+ * dependency graph ensures the waiters are woken in the
+ * correct order (the same order the lwbs were created).
+ */
+ lwb_t *last_lwb_opened = zilog->zl_last_lwb_opened;
+ if (last_lwb_opened != NULL &&
+ last_lwb_opened->lwb_state != LWB_STATE_DONE) {
+ ASSERT(last_lwb_opened->lwb_state == LWB_STATE_OPENED ||
+ last_lwb_opened->lwb_state == LWB_STATE_ISSUED);
+ ASSERT3P(last_lwb_opened->lwb_root_zio, !=, NULL);
+ zio_add_child(lwb->lwb_root_zio,
+ last_lwb_opened->lwb_root_zio);
+ }
+ zilog->zl_last_lwb_opened = lwb;
+
+ mutex_exit(&zilog->zl_lock);
}
+
+ ASSERT3P(lwb->lwb_root_zio, !=, NULL);
+ ASSERT3P(lwb->lwb_write_zio, !=, NULL);
+ ASSERT3S(lwb->lwb_state, ==, LWB_STATE_OPENED);
}
/*
@@ -943,7 +1181,7 @@ uint64_t zil_block_buckets[] = {
* Calls are serialized.
*/
static lwb_t *
-zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
+zil_lwb_write_issue(zilog_t *zilog, lwb_t *lwb)
{
lwb_t *nlwb = NULL;
zil_chain_t *zilc;
@@ -955,6 +1193,11 @@ zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
int i, error;
boolean_t slog;
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
+ ASSERT3P(lwb->lwb_root_zio, !=, NULL);
+ ASSERT3P(lwb->lwb_write_zio, !=, NULL);
+ ASSERT3S(lwb->lwb_state, ==, LWB_STATE_OPENED);
+
if (BP_GET_CHECKSUM(&lwb->lwb_blk) == ZIO_CHECKSUM_ZILOG2) {
zilc = (zil_chain_t *)lwb->lwb_buf;
bp = &zilc->zc_next_blk;
@@ -974,6 +1217,7 @@ zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
* We dirty the dataset to ensure that zil_sync() will be called
* to clean up in the event of allocation failure or I/O failure.
*/
+
tx = dmu_tx_create(zilog->zl_os);
/*
@@ -1026,6 +1270,7 @@ zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
zilog->zl_prev_rotor = (zilog->zl_prev_rotor + 1) & (ZIL_PREV_BLKS - 1);
BP_ZERO(bp);
+
/* pass the old blkptr in order to spread log blocks across devs */
error = zio_alloc_zil(spa, txg, bp, &lwb->lwb_blk, zil_blksz, &slog);
if (error == 0) {
@@ -1034,19 +1279,16 @@ zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
bp->blk_cksum.zc_word[ZIL_ZC_SEQ]++;
/*
- * Allocate a new log write buffer (lwb).
+ * Allocate a new log write block (lwb).
*/
nlwb = zil_alloc_lwb(zilog, bp, slog, txg);
-
- /* Record the block for later vdev flushing */
- zil_add_block(zilog, &lwb->lwb_blk);
}
if (BP_GET_CHECKSUM(&lwb->lwb_blk) == ZIO_CHECKSUM_ZILOG2) {
/* For Slim ZIL only write what is used. */
wsz = P2ROUNDUP_TYPED(lwb->lwb_nused, ZIL_MIN_BLKSZ, uint64_t);
ASSERT3U(wsz, <=, lwb->lwb_sz);
- zio_shrink(lwb->lwb_zio, wsz);
+ zio_shrink(lwb->lwb_write_zio, wsz);
} else {
wsz = lwb->lwb_sz;
@@ -1061,7 +1303,14 @@ zil_lwb_write_start(zilog_t *zilog, lwb_t *lwb)
*/
bzero(lwb->lwb_buf + lwb->lwb_nused, wsz - lwb->lwb_nused);
- zio_nowait(lwb->lwb_zio); /* Kick off the write for the old log block */
+ spa_config_enter(zilog->zl_spa, SCL_STATE, lwb, RW_READER);
+
+ zil_lwb_add_block(lwb, &lwb->lwb_blk);
+ lwb->lwb_issued_timestamp = gethrtime();
+ lwb->lwb_state = LWB_STATE_ISSUED;
+
+ zio_nowait(lwb->lwb_root_zio);
+ zio_nowait(lwb->lwb_write_zio);
/*
* If there was an allocation failure then nlwb will be null which
@@ -1078,13 +1327,33 @@ zil_lwb_commit(zilog_t *zilog, itx_t *itx, lwb_t *lwb)
char *lr_buf;
uint64_t dlen, dnow, lwb_sp, reclen, txg;
- if (lwb == NULL)
- return (NULL);
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3P(lwb->lwb_buf, !=, NULL);
+
+ zil_lwb_write_open(zilog, lwb);
+
+ lrc = &itx->itx_lr;
+ lrw = (lr_write_t *)lrc;
- ASSERT(lwb->lwb_buf != NULL);
+ /*
+ * A commit itx doesn't represent any on-disk state; instead
+ * it's simply used as a place holder on the commit list, and
+ * provides a mechanism for attaching a "commit waiter" onto the
+ * correct lwb (such that the waiter can be signalled upon
+ * completion of that lwb). Thus, we don't process this itx's
+ * log record if it's a commit itx (these itx's don't have log
+ * records), and instead link the itx's waiter onto the lwb's
+ * list of waiters.
+ *
+ * For more details, see the comment above zil_commit().
+ */
+ if (lrc->lrc_txtype == TX_COMMIT) {
+ zil_commit_waiter_link_lwb(itx->itx_private, lwb);
+ itx->itx_private = NULL;
+ return (lwb);
+ }
- lrc = &itx->itx_lr; /* Common log record inside itx. */
- lrw = (lr_write_t *)lrc; /* Write log record inside itx. */
if (lrc->lrc_txtype == TX_WRITE && itx->itx_wr_state == WR_NEED_COPY) {
dlen = P2ROUNDUP_TYPED(
lrw->lr_length, sizeof (uint64_t), uint64_t);
@@ -1095,7 +1364,7 @@ zil_lwb_commit(zilog_t *zilog, itx_t *itx, lwb_t *lwb)
zilog->zl_cur_used += (reclen + dlen);
txg = lrc->lrc_txg;
- zil_lwb_write_init(zilog, lwb);
+ ASSERT3U(zilog->zl_cur_used, <, UINT64_MAX - (reclen + dlen));
cont:
/*
@@ -1106,10 +1375,10 @@ cont:
if (reclen > lwb_sp || (reclen + dlen > lwb_sp &&
lwb_sp < ZIL_MAX_WASTE_SPACE && (dlen % ZIL_MAX_LOG_DATA == 0 ||
lwb_sp < reclen + dlen % ZIL_MAX_LOG_DATA))) {
- lwb = zil_lwb_write_start(zilog, lwb);
+ lwb = zil_lwb_write_issue(zilog, lwb);
if (lwb == NULL)
return (NULL);
- zil_lwb_write_init(zilog, lwb);
+ zil_lwb_write_open(zilog, lwb);
ASSERT(LWB_EMPTY(lwb));
lwb_sp = lwb->lwb_sz - lwb->lwb_nused;
ASSERT3U(reclen + MIN(dlen, sizeof (uint64_t)), <=, lwb_sp);
@@ -1142,8 +1411,25 @@ cont:
ASSERT(itx->itx_wr_state == WR_INDIRECT);
dbuf = NULL;
}
- error = zilog->zl_get_data(
- itx->itx_private, lrwb, dbuf, lwb->lwb_zio);
+
+ /*
+ * We pass in the "lwb_write_zio" rather than
+ * "lwb_root_zio" so that the "lwb_write_zio"
+ * becomes the parent of any zio's created by
+ * the "zl_get_data" callback. The vdevs are
+ * flushed after the "lwb_write_zio" completes,
+ * so we want to make sure that completion
+ * callback waits for these additional zio's,
+ * such that the vdevs used by those zio's will
+ * be included in the lwb's vdev tree, and those
+ * vdevs will be properly flushed. If we passed
+ * in "lwb_root_zio" here, then these additional
+ * vdevs may not be flushed; e.g. if these zio's
+ * completed after "lwb_write_zio" completed.
+ */
+ error = zilog->zl_get_data(itx->itx_private,
+ lrwb, dbuf, lwb, lwb->lwb_write_zio);
+
if (error == EIO) {
txg_wait_synced(zilog->zl_dmu_pool, txg);
return (lwb);
@@ -1162,9 +1448,11 @@ cont:
* equal to the itx sequence number because not all transactions
* are synchronous, and sometimes spa_sync() gets there first.
*/
- lrcb->lrc_seq = ++zilog->zl_lr_seq; /* we are single threaded */
+ lrcb->lrc_seq = ++zilog->zl_lr_seq;
lwb->lwb_nused += reclen + dnow;
- lwb->lwb_max_txg = MAX(lwb->lwb_max_txg, txg);
+
+ zil_lwb_add_txg(lwb, txg);
+
ASSERT3U(lwb->lwb_nused, <=, lwb->lwb_sz);
ASSERT0(P2PHASE(lwb->lwb_nused, sizeof (uint64_t)));
@@ -1214,9 +1502,30 @@ zil_itxg_clean(itxs_t *itxs)
list = &itxs->i_sync_list;
while ((itx = list_head(list)) != NULL) {
+ /*
+ * In the general case, commit itxs will not be found
+ * here, as they'll be committed to an lwb via
+ * zil_lwb_commit(), and free'd in that function. Having
+ * said that, it is still possible for commit itxs to be
+ * found here, due to the following race:
+ *
+ * - a thread calls zil_commit() which assigns the
+ * commit itx to a per-txg i_sync_list
+ * - zil_itxg_clean() is called (e.g. via spa_sync())
+ * while the waiter is still on the i_sync_list
+ *
+ * There's nothing to prevent syncing the txg while the
+ * waiter is on the i_sync_list. This normally doesn't
+ * happen because spa_sync() is slower than zil_commit(),
+ * but if zil_commit() calls txg_wait_synced() (e.g.
+ * because zil_create() or zil_commit_writer_stall() is
+ * called) we will hit this case.
+ */
+ if (itx->itx_lr.lrc_txtype == TX_COMMIT)
+ zil_commit_waiter_skip(itx->itx_private);
+
list_remove(list, itx);
- kmem_free(itx, offsetof(itx_t, itx_lr) +
- itx->itx_lr.lrc_reclen);
+ zil_itx_destroy(itx);
}
cookie = NULL;
@@ -1225,8 +1534,9 @@ zil_itxg_clean(itxs_t *itxs)
list = &ian->ia_list;
while ((itx = list_head(list)) != NULL) {
list_remove(list, itx);
- kmem_free(itx, offsetof(itx_t, itx_lr) +
- itx->itx_lr.lrc_reclen);
+ /* commit itxs should never be on the async lists. */
+ ASSERT3U(itx->itx_lr.lrc_txtype, !=, TX_COMMIT);
+ zil_itx_destroy(itx);
}
list_destroy(list);
kmem_free(ian, sizeof (itx_async_node_t));
@@ -1291,8 +1601,9 @@ zil_remove_async(zilog_t *zilog, uint64_t oid)
}
while ((itx = list_head(&clean_list)) != NULL) {
list_remove(&clean_list, itx);
- kmem_free(itx, offsetof(itx_t, itx_lr) +
- itx->itx_lr.lrc_reclen);
+ /* commit itxs should never be on the async lists. */
+ ASSERT3U(itx->itx_lr.lrc_txtype, !=, TX_COMMIT);
+ zil_itx_destroy(itx);
}
list_destroy(&clean_list);
}
@@ -1368,7 +1679,14 @@ zil_itx_assign(zilog_t *zilog, itx_t *itx, dmu_tx_t *tx)
}
itx->itx_lr.lrc_txg = dmu_tx_get_txg(tx);
- zilog_dirty(zilog, txg);
+
+ /*
+ * We don't want to dirty the ZIL using ZILTEST_TXG, because
+ * zil_clean() will never be called using ZILTEST_TXG. Thus, we
+ * need to be careful to always dirty the ZIL using the "real"
+ * TXG (not itxg_txg) even when the SPA is frozen.
+ */
+ zilog_dirty(zilog, dmu_tx_get_txg(tx));
mutex_exit(&itxg->itxg_lock);
/* Release the old itxs now we've dropped the lock */
@@ -1389,6 +1707,8 @@ zil_clean(zilog_t *zilog, uint64_t synced_txg)
itxg_t *itxg = &zilog->zl_itxg[synced_txg & TXG_MASK];
itxs_t *clean_me;
+ ASSERT3U(synced_txg, <, ZILTEST_TXG);
+
mutex_enter(&itxg->itxg_lock);
if (itxg->itxg_itxs == NULL || itxg->itxg_txg == ZILTEST_TXG) {
mutex_exit(&itxg->itxg_lock);
@@ -1414,7 +1734,8 @@ zil_clean(zilog_t *zilog, uint64_t synced_txg)
}
/*
- * Get the list of itxs to commit into zl_itx_commit_list.
+ * This function will traverse the queue of itxs that need to be
+ * committed, and move them onto the ZIL's zl_itx_commit_list.
*/
static void
zil_get_commit_list(zilog_t *zilog)
@@ -1422,6 +1743,8 @@ zil_get_commit_list(zilog_t *zilog)
uint64_t otxg, txg;
list_t *commit_list = &zilog->zl_itx_commit_list;
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
+
if (spa_freeze_txg(zilog->zl_spa) != UINT64_MAX) /* ziltest support */
otxg = ZILTEST_TXG;
else
@@ -1513,142 +1836,787 @@ zil_async_to_sync(zilog_t *zilog, uint64_t foid)
}
}
+/*
+ * This function will prune commit itxs that are at the head of the
+ * commit list (it won't prune past the first non-commit itx), and
+ * either: a) attach them to the last lwb that's still pending
+ * completion, or b) skip them altogether.
+ *
+ * This is used as a performance optimization to prevent commit itxs
+ * from generating new lwbs when it's unnecessary to do so.
+ */
static void
-zil_commit_writer(zilog_t *zilog)
+zil_prune_commit_list(zilog_t *zilog)
{
- uint64_t txg;
itx_t *itx;
- lwb_t *lwb;
- spa_t *spa = zilog->zl_spa;
- int error = 0;
- ASSERT(zilog->zl_root_zio == NULL);
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
- mutex_exit(&zilog->zl_lock);
+ while (itx = list_head(&zilog->zl_itx_commit_list)) {
+ lr_t *lrc = &itx->itx_lr;
+ if (lrc->lrc_txtype != TX_COMMIT)
+ break;
- zil_get_commit_list(zilog);
+ mutex_enter(&zilog->zl_lock);
+
+ lwb_t *last_lwb = zilog->zl_last_lwb_opened;
+ if (last_lwb == NULL || last_lwb->lwb_state == LWB_STATE_DONE) {
+ /*
+ * All of the itxs this waiter was waiting on
+ * must have already completed (or there were
+ * never any itx's for it to wait on), so it's
+ * safe to skip this waiter and mark it done.
+ */
+ zil_commit_waiter_skip(itx->itx_private);
+ } else {
+ zil_commit_waiter_link_lwb(itx->itx_private, last_lwb);
+ itx->itx_private = NULL;
+ }
+
+ mutex_exit(&zilog->zl_lock);
+
+ list_remove(&zilog->zl_itx_commit_list, itx);
+ zil_itx_destroy(itx);
+ }
+
+ IMPLY(itx != NULL, itx->itx_lr.lrc_txtype != TX_COMMIT);
+}
+
+static void
+zil_commit_writer_stall(zilog_t *zilog)
+{
+ /*
+ * When zio_alloc_zil() fails to allocate the next lwb block on
+ * disk, we must call txg_wait_synced() to ensure all of the
+ * lwbs in the zilog's zl_lwb_list are synced and then freed (in
+ * zil_sync()), such that any subsequent ZIL writer (i.e. a call
+ * to zil_process_commit_list()) will have to call zil_create(),
+ * and start a new ZIL chain.
+ *
+ * Since zil_alloc_zil() failed, the lwb that was previously
+ * issued does not have a pointer to the "next" lwb on disk.
+ * Thus, if another ZIL writer thread was to allocate the "next"
+ * on-disk lwb, that block could be leaked in the event of a
+ * crash (because the previous lwb on-disk would not point to
+ * it).
+ *
+ * We must hold the zilog's zl_writer_lock while we do this, to
+ * ensure no new threads enter zil_process_commit_list() until
+ * all lwb's in the zl_lwb_list have been synced and freed
+ * (which is achieved via the txg_wait_synced() call).
+ */
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
+ txg_wait_synced(zilog->zl_dmu_pool, 0);
+ ASSERT3P(list_tail(&zilog->zl_lwb_list), ==, NULL);
+}
+
+/*
+ * This function will traverse the commit list, creating new lwbs as
+ * needed, and committing the itxs from the commit list to these newly
+ * created lwbs. Additionally, as a new lwb is created, the previous
+ * lwb will be issued to the zio layer to be written to disk.
+ */
+static void
+zil_process_commit_list(zilog_t *zilog)
+{
+ spa_t *spa = zilog->zl_spa;
+ list_t nolwb_waiters;
+ lwb_t *lwb;
+ itx_t *itx;
+
+ ASSERT(MUTEX_HELD(&zilog->zl_writer_lock));
/*
* Return if there's nothing to commit before we dirty the fs by
* calling zil_create().
*/
- if (list_head(&zilog->zl_itx_commit_list) == NULL) {
- mutex_enter(&zilog->zl_lock);
+ if (list_head(&zilog->zl_itx_commit_list) == NULL)
return;
- }
- if (zilog->zl_suspend) {
- lwb = NULL;
+ list_create(&nolwb_waiters, sizeof (zil_commit_waiter_t),
+ offsetof(zil_commit_waiter_t, zcw_node));
+
+ lwb = list_tail(&zilog->zl_lwb_list);
+ if (lwb == NULL) {
+ lwb = zil_create(zilog);
} else {
- lwb = list_tail(&zilog->zl_lwb_list);
- if (lwb == NULL)
- lwb = zil_create(zilog);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_ISSUED);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_DONE);
}
- DTRACE_PROBE1(zil__cw1, zilog_t *, zilog);
while (itx = list_head(&zilog->zl_itx_commit_list)) {
- txg = itx->itx_lr.lrc_txg;
+ lr_t *lrc = &itx->itx_lr;
+ uint64_t txg = lrc->lrc_txg;
+
ASSERT3U(txg, !=, 0);
+ if (lrc->lrc_txtype == TX_COMMIT) {
+ DTRACE_PROBE2(zil__process__commit__itx,
+ zilog_t *, zilog, itx_t *, itx);
+ } else {
+ DTRACE_PROBE2(zil__process__normal__itx,
+ zilog_t *, zilog, itx_t *, itx);
+ }
+
/*
* This is inherently racy and may result in us writing
- * out a log block for a txg that was just synced. This is
- * ok since we'll end cleaning up that log block the next
- * time we call zil_sync().
+ * out a log block for a txg that was just synced. This
+ * is ok since we'll end cleaning up that log block the
+ * next time we call zil_sync().
*/
- if (txg > spa_last_synced_txg(spa) || txg > spa_freeze_txg(spa))
- lwb = zil_lwb_commit(zilog, itx, lwb);
+ boolean_t synced = txg <= spa_last_synced_txg(spa);
+ boolean_t frozen = txg > spa_freeze_txg(spa);
+
+ if (!synced || frozen) {
+ if (lwb != NULL) {
+ lwb = zil_lwb_commit(zilog, itx, lwb);
+ } else if (lrc->lrc_txtype == TX_COMMIT) {
+ ASSERT3P(lwb, ==, NULL);
+ zil_commit_waiter_link_nolwb(
+ itx->itx_private, &nolwb_waiters);
+ }
+ } else if (lrc->lrc_txtype == TX_COMMIT) {
+ ASSERT3B(synced, ==, B_TRUE);
+ ASSERT3B(frozen, ==, B_FALSE);
+
+ /*
+ * If this is a commit itx, then there will be a
+ * thread that is either: already waiting for
+ * it, or soon will be waiting.
+ *
+ * This itx has already been committed to disk
+ * via spa_sync() so we don't bother committing
+ * it to an lwb. As a result, we cannot use the
+ * lwb zio callback to signal the waiter and
+ * mark it as done, so we must do that here.
+ */
+ zil_commit_waiter_skip(itx->itx_private);
+ }
+
list_remove(&zilog->zl_itx_commit_list, itx);
- kmem_free(itx, offsetof(itx_t, itx_lr)
- + itx->itx_lr.lrc_reclen);
+ zil_itx_destroy(itx);
}
DTRACE_PROBE1(zil__cw2, zilog_t *, zilog);
- /* write the last block out */
- if (lwb != NULL && lwb->lwb_zio != NULL)
- lwb = zil_lwb_write_start(zilog, lwb);
+ if (lwb == NULL) {
+ /*
+ * This indicates zio_alloc_zil() failed to allocate the
+ * "next" lwb on-disk. When this happens, we must stall
+ * the ZIL write pipeline; see the comment within
+ * zil_commit_writer_stall() for more details.
+ */
+ zil_commit_writer_stall(zilog);
- zilog->zl_cur_used = 0;
+ /*
+ * Additionally, we have to signal and mark the "nolwb"
+ * waiters as "done" here, since without an lwb, we
+ * can't do this via zil_lwb_flush_vdevs_done() like
+ * normal.
+ */
+ zil_commit_waiter_t *zcw;
+ while (zcw = list_head(&nolwb_waiters)) {
+ zil_commit_waiter_skip(zcw);
+ list_remove(&nolwb_waiters, zcw);
+ }
+ } else {
+ ASSERT(list_is_empty(&nolwb_waiters));
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_ISSUED);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_DONE);
+
+ /*
+ * At this point, the ZIL block pointed at by the "lwb"
+ * variable is in one of the following states: "closed"
+ * or "open".
+ *
+ * If its "closed", then no itxs have been committed to
+ * it, so there's no point in issuing its zio (i.e.
+ * it's "empty").
+ *
+ * If its "open" state, then it contains one or more
+ * itxs that eventually need to be committed to stable
+ * storage. In this case we intentionally do not issue
+ * the lwb's zio to disk yet, and instead rely on one of
+ * the following two mechanisms for issuing the zio:
+ *
+ * 1. Ideally, there will be more ZIL activity occuring
+ * on the system, such that this function will be
+ * immediately called again (not necessarily by the same
+ * thread) and this lwb's zio will be issued via
+ * zil_lwb_commit(). This way, the lwb is guaranteed to
+ * be "full" when it is issued to disk, and we'll make
+ * use of the lwb's size the best we can.
+ *
+ * 2. If there isn't sufficient ZIL activity occuring on
+ * the system, such that this lwb's zio isn't issued via
+ * zil_lwb_commit(), zil_commit_waiter() will issue the
+ * lwb's zio. If this occurs, the lwb is not guaranteed
+ * to be "full" by the time its zio is issued, and means
+ * the size of the lwb was "too large" given the amount
+ * of ZIL activity occuring on the system at that time.
+ *
+ * We do this for a couple of reasons:
+ *
+ * 1. To try and reduce the number of IOPs needed to
+ * write the same number of itxs. If an lwb has space
+ * available in it's buffer for more itxs, and more itxs
+ * will be committed relatively soon (relative to the
+ * latency of performing a write), then it's beneficial
+ * to wait for these "next" itxs. This way, more itxs
+ * can be committed to stable storage with fewer writes.
+ *
+ * 2. To try and use the largest lwb block size that the
+ * incoming rate of itxs can support. Again, this is to
+ * try and pack as many itxs into as few lwbs as
+ * possible, without significantly impacting the latency
+ * of each individual itx.
+ */
+ }
+}
+
+/*
+ * This function is responsible for ensuring the passed in commit waiter
+ * (and associated commit itx) is committed to an lwb. If the waiter is
+ * not already committed to an lwb, all itxs in the zilog's queue of
+ * itxs will be processed. The assumption is the passed in waiter's
+ * commit itx will found in the queue just like the other non-commit
+ * itxs, such that when the entire queue is processed, the waiter will
+ * have been commited to an lwb.
+ *
+ * The lwb associated with the passed in waiter is not guaranteed to
+ * have been issued by the time this function completes. If the lwb is
+ * not issued, we rely on future calls to zil_commit_writer() to issue
+ * the lwb, or the timeout mechanism found in zil_commit_waiter().
+ */
+static void
+zil_commit_writer(zilog_t *zilog, zil_commit_waiter_t *zcw)
+{
+ ASSERT(!MUTEX_HELD(&zilog->zl_lock));
+ ASSERT(spa_writeable(zilog->zl_spa));
+ ASSERT0(zilog->zl_suspend);
+
+ mutex_enter(&zilog->zl_writer_lock);
+
+ if (zcw->zcw_lwb != NULL || zcw->zcw_done) {
+ /*
+ * It's possible that, while we were waiting to acquire
+ * the "zl_writer_lock", another thread committed this
+ * waiter to an lwb. If that occurs, we bail out early,
+ * without processing any of the zilog's queue of itxs.
+ *
+ * On certain workloads and system configurations, the
+ * "zl_writer_lock" can become highly contended. In an
+ * attempt to reduce this contention, we immediately drop
+ * the lock if the waiter has already been processed.
+ *
+ * We've measured this optimization to reduce CPU spent
+ * contending on this lock by up to 5%, using a system
+ * with 32 CPUs, low latency storage (~50 usec writes),
+ * and 1024 threads performing sync writes.
+ */
+ goto out;
+ }
+
+ zil_get_commit_list(zilog);
+ zil_prune_commit_list(zilog);
+ zil_process_commit_list(zilog);
+
+out:
+ mutex_exit(&zilog->zl_writer_lock);
+}
+
+static void
+zil_commit_waiter_timeout(zilog_t *zilog, zil_commit_waiter_t *zcw)
+{
+ ASSERT(!MUTEX_HELD(&zilog->zl_writer_lock));
+ ASSERT(MUTEX_HELD(&zcw->zcw_lock));
+ ASSERT3B(zcw->zcw_done, ==, B_FALSE);
+
+ lwb_t *lwb = zcw->zcw_lwb;
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_CLOSED);
+
+ /*
+ * If the lwb has already been issued by another thread, we can
+ * immediately return since there's no work to be done (the
+ * point of this function is to issue the lwb). Additionally, we
+ * do this prior to acquiring the zl_writer_lock, to avoid
+ * acquiring it when it's not necessary to do so.
+ */
+ if (lwb->lwb_state == LWB_STATE_ISSUED ||
+ lwb->lwb_state == LWB_STATE_DONE)
+ return;
+
+ /*
+ * In order to call zil_lwb_write_issue() we must hold the
+ * zilog's "zl_writer_lock". We can't simply acquire that lock,
+ * since we're already holding the commit waiter's "zcw_lock",
+ * and those two locks are aquired in the opposite order
+ * elsewhere.
+ */
+ mutex_exit(&zcw->zcw_lock);
+ mutex_enter(&zilog->zl_writer_lock);
+ mutex_enter(&zcw->zcw_lock);
+
+ /*
+ * Since we just dropped and re-acquired the commit waiter's
+ * lock, we have to re-check to see if the waiter was marked
+ * "done" during that process. If the waiter was marked "done",
+ * the "lwb" pointer is no longer valid (it can be free'd after
+ * the waiter is marked "done"), so without this check we could
+ * wind up with a use-after-free error below.
+ */
+ if (zcw->zcw_done)
+ goto out;
+
+ ASSERT3P(lwb, ==, zcw->zcw_lwb);
+
+ /*
+ * We've already checked this above, but since we hadn't
+ * acquired the zilog's zl_writer_lock, we have to perform this
+ * check a second time while holding the lock. We can't call
+ * zil_lwb_write_issue() if the lwb had already been issued.
+ */
+ if (lwb->lwb_state == LWB_STATE_ISSUED ||
+ lwb->lwb_state == LWB_STATE_DONE)
+ goto out;
+
+ ASSERT3S(lwb->lwb_state, ==, LWB_STATE_OPENED);
+
+ /*
+ * As described in the comments above zil_commit_waiter() and
+ * zil_process_commit_list(), we need to issue this lwb's zio
+ * since we've reached the commit waiter's timeout and it still
+ * hasn't been issued.
+ */
+ lwb_t *nlwb = zil_lwb_write_issue(zilog, lwb);
+
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_OPENED);
/*
- * Wait if necessary for the log blocks to be on stable storage.
+ * Since the lwb's zio hadn't been issued by the time this thread
+ * reached its timeout, we reset the zilog's "zl_cur_used" field
+ * to influence the zil block size selection algorithm.
+ *
+ * By having to issue the lwb's zio here, it means the size of the
+ * lwb was too large, given the incoming throughput of itxs. By
+ * setting "zl_cur_used" to zero, we communicate this fact to the
+ * block size selection algorithm, so it can take this informaiton
+ * into account, and potentially select a smaller size for the
+ * next lwb block that is allocated.
*/
- if (zilog->zl_root_zio) {
- error = zio_wait(zilog->zl_root_zio);
- zilog->zl_root_zio = NULL;
- zil_flush_vdevs(zilog);
+ zilog->zl_cur_used = 0;
+
+ if (nlwb == NULL) {
+ /*
+ * When zil_lwb_write_issue() returns NULL, this
+ * indicates zio_alloc_zil() failed to allocate the
+ * "next" lwb on-disk. When this occurs, the ZIL write
+ * pipeline must be stalled; see the comment within the
+ * zil_commit_writer_stall() function for more details.
+ *
+ * We must drop the commit waiter's lock prior to
+ * calling zil_commit_writer_stall() or else we can wind
+ * up with the following deadlock:
+ *
+ * - This thread is waiting for the txg to sync while
+ * holding the waiter's lock; txg_wait_synced() is
+ * used within txg_commit_writer_stall().
+ *
+ * - The txg can't sync because it is waiting for this
+ * lwb's zio callback to call dmu_tx_commit().
+ *
+ * - The lwb's zio callback can't call dmu_tx_commit()
+ * because it's blocked trying to acquire the waiter's
+ * lock, which occurs prior to calling dmu_tx_commit()
+ */
+ mutex_exit(&zcw->zcw_lock);
+ zil_commit_writer_stall(zilog);
+ mutex_enter(&zcw->zcw_lock);
}
- if (error || lwb == NULL)
- txg_wait_synced(zilog->zl_dmu_pool, 0);
+out:
+ mutex_exit(&zilog->zl_writer_lock);
+ ASSERT(MUTEX_HELD(&zcw->zcw_lock));
+}
- mutex_enter(&zilog->zl_lock);
+/*
+ * This function is responsible for performing the following two tasks:
+ *
+ * 1. its primary responsibility is to block until the given "commit
+ * waiter" is considered "done".
+ *
+ * 2. its secondary responsibility is to issue the zio for the lwb that
+ * the given "commit waiter" is waiting on, if this function has
+ * waited "long enough" and the lwb is still in the "open" state.
+ *
+ * Given a sufficient amount of itxs being generated and written using
+ * the ZIL, the lwb's zio will be issued via the zil_lwb_commit()
+ * function. If this does not occur, this secondary responsibility will
+ * ensure the lwb is issued even if there is not other synchronous
+ * activity on the system.
+ *
+ * For more details, see zil_process_commit_list(); more specifically,
+ * the comment at the bottom of that function.
+ */
+static void
+zil_commit_waiter(zilog_t *zilog, zil_commit_waiter_t *zcw)
+{
+ ASSERT(!MUTEX_HELD(&zilog->zl_lock));
+ ASSERT(!MUTEX_HELD(&zilog->zl_writer_lock));
+ ASSERT(spa_writeable(zilog->zl_spa));
+ ASSERT0(zilog->zl_suspend);
+
+ mutex_enter(&zcw->zcw_lock);
/*
- * Remember the highest committed log sequence number for ztest.
- * We only update this value when all the log writes succeeded,
- * because ztest wants to ASSERT that it got the whole log chain.
+ * The timeout is scaled based on the lwb latency to avoid
+ * significantly impacting the latency of each individual itx.
+ * For more details, see the comment at the bottom of the
+ * zil_process_commit_list() function.
*/
- if (error == 0 && lwb != NULL)
- zilog->zl_commit_lr_seq = zilog->zl_lr_seq;
+ int pct = MAX(zfs_commit_timeout_pct, 1);
+ hrtime_t sleep = (zilog->zl_last_lwb_latency * pct) / 100;
+ hrtime_t wakeup = gethrtime() + sleep;
+ boolean_t timedout = B_FALSE;
+
+ while (!zcw->zcw_done) {
+ ASSERT(MUTEX_HELD(&zcw->zcw_lock));
+
+ lwb_t *lwb = zcw->zcw_lwb;
+
+ /*
+ * Usually, the waiter will have a non-NULL lwb field here,
+ * but it's possible for it to be NULL as a result of
+ * zil_commit() racing with spa_sync().
+ *
+ * When zil_clean() is called, it's possible for the itxg
+ * list (which may be cleaned via a taskq) to contain
+ * commit itxs. When this occurs, the commit waiters linked
+ * off of these commit itxs will not be committed to an
+ * lwb. Additionally, these commit waiters will not be
+ * marked done until zil_commit_waiter_skip() is called via
+ * zil_itxg_clean().
+ *
+ * Thus, it's possible for this commit waiter (i.e. the
+ * "zcw" variable) to be found in this "in between" state;
+ * where it's "zcw_lwb" field is NULL, and it hasn't yet
+ * been skipped, so it's "zcw_done" field is still B_FALSE.
+ */
+ IMPLY(lwb != NULL, lwb->lwb_state != LWB_STATE_CLOSED);
+
+ if (lwb != NULL && lwb->lwb_state == LWB_STATE_OPENED) {
+ ASSERT3B(timedout, ==, B_FALSE);
+
+ /*
+ * If the lwb hasn't been issued yet, then we
+ * need to wait with a timeout, in case this
+ * function needs to issue the lwb after the
+ * timeout is reached; responsibility (2) from
+ * the comment above this function.
+ */
+ clock_t timeleft = cv_timedwait_hires(&zcw->zcw_cv,
+ &zcw->zcw_lock, wakeup, USEC2NSEC(1),
+ CALLOUT_FLAG_ABSOLUTE);
+
+ if (timeleft >= 0 || zcw->zcw_done)
+ continue;
+
+ timedout = B_TRUE;
+ zil_commit_waiter_timeout(zilog, zcw);
+
+ if (!zcw->zcw_done) {
+ /*
+ * If the commit waiter has already been
+ * marked "done", it's possible for the
+ * waiter's lwb structure to have already
+ * been freed. Thus, we can only reliably
+ * make these assertions if the waiter
+ * isn't done.
+ */
+ ASSERT3P(lwb, ==, zcw->zcw_lwb);
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_OPENED);
+ }
+ } else {
+ /*
+ * If the lwb isn't open, then it must have already
+ * been issued. In that case, there's no need to
+ * use a timeout when waiting for the lwb to
+ * complete.
+ *
+ * Additionally, if the lwb is NULL, the waiter
+ * will soon be signalled and marked done via
+ * zil_clean() and zil_itxg_clean(), so no timeout
+ * is required.
+ */
+
+ IMPLY(lwb != NULL,
+ lwb->lwb_state == LWB_STATE_ISSUED ||
+ lwb->lwb_state == LWB_STATE_DONE);
+ cv_wait(&zcw->zcw_cv, &zcw->zcw_lock);
+ }
+ }
+
+ mutex_exit(&zcw->zcw_lock);
+}
+
+static zil_commit_waiter_t *
+zil_alloc_commit_waiter()
+{
+ zil_commit_waiter_t *zcw = kmem_cache_alloc(zil_zcw_cache, KM_SLEEP);
+
+ cv_init(&zcw->zcw_cv, NULL, CV_DEFAULT, NULL);
+ mutex_init(&zcw->zcw_lock, NULL, MUTEX_DEFAULT, NULL);
+ list_link_init(&zcw->zcw_node);
+ zcw->zcw_lwb = NULL;
+ zcw->zcw_done = B_FALSE;
+ zcw->zcw_zio_error = 0;
+
+ return (zcw);
+}
+
+static void
+zil_free_commit_waiter(zil_commit_waiter_t *zcw)
+{
+ ASSERT(!list_link_active(&zcw->zcw_node));
+ ASSERT3P(zcw->zcw_lwb, ==, NULL);
+ ASSERT3B(zcw->zcw_done, ==, B_TRUE);
+ mutex_destroy(&zcw->zcw_lock);
+ cv_destroy(&zcw->zcw_cv);
+ kmem_cache_free(zil_zcw_cache, zcw);
+}
+
+/*
+ * This function is used to create a TX_COMMIT itx and assign it. This
+ * way, it will be linked into the ZIL's list of synchronous itxs, and
+ * then later committed to an lwb (or skipped) when
+ * zil_process_commit_list() is called.
+ */
+static void
+zil_commit_itx_assign(zilog_t *zilog, zil_commit_waiter_t *zcw)
+{
+ dmu_tx_t *tx = dmu_tx_create(zilog->zl_os);
+ VERIFY0(dmu_tx_assign(tx, TXG_WAIT));
+
+ itx_t *itx = zil_itx_create(TX_COMMIT, sizeof (lr_t));
+ itx->itx_sync = B_TRUE;
+ itx->itx_private = zcw;
+
+ zil_itx_assign(zilog, itx, tx);
+
+ dmu_tx_commit(tx);
}
/*
- * Commit zfs transactions to stable storage.
- * If foid is 0 push out all transactions, otherwise push only those
- * for that object or might reference that object.
+ * Commit ZFS Intent Log transactions (itxs) to stable storage.
+ *
+ * When writing ZIL transactions to the on-disk representation of the
+ * ZIL, the itxs are committed to a Log Write Block (lwb). Multiple
+ * itxs can be committed to a single lwb. Once a lwb is written and
+ * committed to stable storage (i.e. the lwb is written, and vdevs have
+ * been flushed), each itx that was committed to that lwb is also
+ * considered to be committed to stable storage.
+ *
+ * When an itx is committed to an lwb, the log record (lr_t) contained
+ * by the itx is copied into the lwb's zio buffer, and once this buffer
+ * is written to disk, it becomes an on-disk ZIL block.
+ *
+ * As itxs are generated, they're inserted into the ZIL's queue of
+ * uncommitted itxs. The semantics of zil_commit() are such that it will
+ * block until all itxs that were in the queue when it was called, are
+ * committed to stable storage.
+ *
+ * If "foid" is zero, this means all "synchronous" and "asynchronous"
+ * itxs, for all objects in the dataset, will be committed to stable
+ * storage prior to zil_commit() returning. If "foid" is non-zero, all
+ * "synchronous" itxs for all objects, but only "asynchronous" itxs
+ * that correspond to the foid passed in, will be committed to stable
+ * storage prior to zil_commit() returning.
+ *
+ * Generally speaking, when zil_commit() is called, the consumer doesn't
+ * actually care about _all_ of the uncommitted itxs. Instead, they're
+ * simply trying to waiting for a specific itx to be committed to disk,
+ * but the interface(s) for interacting with the ZIL don't allow such
+ * fine-grained communication. A better interface would allow a consumer
+ * to create and assign an itx, and then pass a reference to this itx to
+ * zil_commit(); such that zil_commit() would return as soon as that
+ * specific itx was committed to disk (instead of waiting for _all_
+ * itxs to be committed).
+ *
+ * When a thread calls zil_commit() a special "commit itx" will be
+ * generated, along with a corresponding "waiter" for this commit itx.
+ * zil_commit() will wait on this waiter's CV, such that when the waiter
+ * is marked done, and signalled, zil_commit() will return.
+ *
+ * This commit itx is inserted into the queue of uncommitted itxs. This
+ * provides an easy mechanism for determining which itxs were in the
+ * queue prior to zil_commit() having been called, and which itxs were
+ * added after zil_commit() was called.
*
- * itxs are committed in batches. In a heavily stressed zil there will be
- * a commit writer thread who is writing out a bunch of itxs to the log
- * for a set of committing threads (cthreads) in the same batch as the writer.
- * Those cthreads are all waiting on the same cv for that batch.
+ * The commit it is special; it doesn't have any on-disk representation.
+ * When a commit itx is "committed" to an lwb, the waiter associated
+ * with it is linked onto the lwb's list of waiters. Then, when that lwb
+ * completes, each waiter on the lwb's list is marked done and signalled
+ * -- allowing the thread waiting on the waiter to return from zil_commit().
*
- * There will also be a different and growing batch of threads that are
- * waiting to commit (qthreads). When the committing batch completes
- * a transition occurs such that the cthreads exit and the qthreads become
- * cthreads. One of the new cthreads becomes the writer thread for the
- * batch. Any new threads arriving become new qthreads.
+ * It's important to point out a few critical factors that allow us
+ * to make use of the commit itxs, commit waiters, per-lwb lists of
+ * commit waiters, and zio completion callbacks like we're doing:
*
- * Only 2 condition variables are needed and there's no transition
- * between the two cvs needed. They just flip-flop between qthreads
- * and cthreads.
+ * 1. The list of waiters for each lwb is traversed, and each commit
+ * waiter is marked "done" and signalled, in the zio completion
+ * callback of the lwb's zio[*].
*
- * Using this scheme we can efficiently wakeup up only those threads
- * that have been committed.
+ * * Actually, the waiters are signalled in the zio completion
+ * callback of the root zio for the DKIOCFLUSHWRITECACHE commands
+ * that are sent to the vdevs upon completion of the lwb zio.
+ *
+ * 2. When the itxs are inserted into the ZIL's queue of uncommitted
+ * itxs, the order in which they are inserted is preserved[*]; as
+ * itxs are added to the queue, they are added to the tail of
+ * in-memory linked lists.
+ *
+ * When committing the itxs to lwbs (to be written to disk), they
+ * are committed in the same order in which the itxs were added to
+ * the uncommitted queue's linked list(s); i.e. the linked list of
+ * itxs to commit is traversed from head to tail, and each itx is
+ * committed to an lwb in that order.
+ *
+ * * To clarify:
+ *
+ * - the order of "sync" itxs is preserved w.r.t. other
+ * "sync" itxs, regardless of the corresponding objects.
+ * - the order of "async" itxs is preserved w.r.t. other
+ * "async" itxs corresponding to the same object.
+ * - the order of "async" itxs is *not* preserved w.r.t. other
+ * "async" itxs corresponding to different objects.
+ * - the order of "sync" itxs w.r.t. "async" itxs (or vice
+ * versa) is *not* preserved, even for itxs that correspond
+ * to the same object.
+ *
+ * For more details, see: zil_itx_assign(), zil_async_to_sync(),
+ * zil_get_commit_list(), and zil_process_commit_list().
+ *
+ * 3. The lwbs represent a linked list of blocks on disk. Thus, any
+ * lwb cannot be considered committed to stable storage, until its
+ * "previous" lwb is also committed to stable storage. This fact,
+ * coupled with the fact described above, means that itxs are
+ * committed in (roughly) the order in which they were generated.
+ * This is essential because itxs are dependent on prior itxs.
+ * Thus, we *must not* deem an itx as being committed to stable
+ * storage, until *all* prior itxs have also been committed to
+ * stable storage.
+ *
+ * To enforce this ordering of lwb zio's, while still leveraging as
+ * much of the underlying storage performance as possible, we rely
+ * on two fundamental concepts:
+ *
+ * 1. The creation and issuance of lwb zio's is protected by
+ * the zilog's "zl_writer_lock", which ensures only a single
+ * thread is creating and/or issuing lwb's at a time
+ * 2. The "previous" lwb is a child of the "current" lwb
+ * (leveraging the zio parent-child depenency graph)
+ *
+ * By relying on this parent-child zio relationship, we can have
+ * many lwb zio's concurrently issued to the underlying storage,
+ * but the order in which they complete will be the same order in
+ * which they were created.
*/
void
zil_commit(zilog_t *zilog, uint64_t foid)
{
- uint64_t mybatch;
+ /*
+ * We should never attempt to call zil_commit on a snapshot for
+ * a couple of reasons:
+ *
+ * 1. A snapshot may never be modified, thus it cannot have any
+ * in-flight itxs that would have modified the dataset.
+ *
+ * 2. By design, when zil_commit() is called, a commit itx will
+ * be assigned to this zilog; as a result, the zilog will be
+ * dirtied. We must not dirty the zilog of a snapshot; there's
+ * checks in the code that enforce this invariant, and will
+ * cause a panic if it's not upheld.
+ */
+ ASSERT3B(dmu_objset_is_snapshot(zilog->zl_os), ==, B_FALSE);
if (zilog->zl_sync == ZFS_SYNC_DISABLED)
return;
- /* move the async itxs for the foid to the sync queues */
- zil_async_to_sync(zilog, foid);
+ if (!spa_writeable(zilog->zl_spa)) {
+ /*
+ * If the SPA is not writable, there should never be any
+ * pending itxs waiting to be committed to disk. If that
+ * weren't true, we'd skip writing those itxs out, and
+ * would break the sematics of zil_commit(); thus, we're
+ * verifying that truth before we return to the caller.
+ */
+ ASSERT(list_is_empty(&zilog->zl_lwb_list));
+ ASSERT3P(zilog->zl_last_lwb_opened, ==, NULL);
+ for (int i = 0; i < TXG_SIZE; i++)
+ ASSERT3P(zilog->zl_itxg[i].itxg_itxs, ==, NULL);
+ return;
+ }
- mutex_enter(&zilog->zl_lock);
- mybatch = zilog->zl_next_batch;
- while (zilog->zl_writer) {
- cv_wait(&zilog->zl_cv_batch[mybatch & 1], &zilog->zl_lock);
- if (mybatch <= zilog->zl_com_batch) {
- mutex_exit(&zilog->zl_lock);
- return;
- }
+ /*
+ * If the ZIL is suspended, we don't want to dirty it by calling
+ * zil_commit_itx_assign() below, nor can we write out
+ * lwbs like would be done in zil_commit_write(). Thus, we
+ * simply rely on txg_wait_synced() to maintain the necessary
+ * semantics, and avoid calling those functions altogether.
+ */
+ if (zilog->zl_suspend > 0) {
+ txg_wait_synced(zilog->zl_dmu_pool, 0);
+ return;
}
- zilog->zl_next_batch++;
- zilog->zl_writer = B_TRUE;
- zil_commit_writer(zilog);
- zilog->zl_com_batch = mybatch;
- zilog->zl_writer = B_FALSE;
- mutex_exit(&zilog->zl_lock);
+ /*
+ * Move the "async" itxs for the specified foid to the "sync"
+ * queues, such that they will be later committed (or skipped)
+ * to an lwb when zil_process_commit_list() is called.
+ *
+ * Since these "async" itxs must be committed prior to this
+ * call to zil_commit returning, we must perform this operation
+ * before we call zil_commit_itx_assign().
+ */
+ zil_async_to_sync(zilog, foid);
- /* wake up one thread to become the next writer */
- cv_signal(&zilog->zl_cv_batch[(mybatch+1) & 1]);
+ /*
+ * We allocate a new "waiter" structure which will initially be
+ * linked to the commit itx using the itx's "itx_private" field.
+ * Since the commit itx doesn't represent any on-disk state,
+ * when it's committed to an lwb, rather than copying the its
+ * lr_t into the lwb's buffer, the commit itx's "waiter" will be
+ * added to the lwb's list of waiters. Then, when the lwb is
+ * committed to stable storage, each waiter in the lwb's list of
+ * waiters will be marked "done", and signalled.
+ *
+ * We must create the waiter and assign the commit itx prior to
+ * calling zil_commit_writer(), or else our specific commit itx
+ * is not guaranteed to be committed to an lwb prior to calling
+ * zil_commit_waiter().
+ */
+ zil_commit_waiter_t *zcw = zil_alloc_commit_waiter();
+ zil_commit_itx_assign(zilog, zcw);
+
+ zil_commit_writer(zilog, zcw);
+ zil_commit_waiter(zilog, zcw);
- /* wake up all threads waiting for this batch to be committed */
- cv_broadcast(&zilog->zl_cv_batch[mybatch & 1]);
+ if (zcw->zcw_zio_error != 0) {
+ /*
+ * If there was an error writing out the ZIL blocks that
+ * this thread is waiting on, then we fallback to
+ * relying on spa_sync() to write out the data this
+ * thread is waiting on. Obviously this has performance
+ * implications, but the expectation is for this to be
+ * an exceptional case, and shouldn't occur often.
+ */
+ DTRACE_PROBE2(zil__commit__io__error,
+ zilog_t *, zilog, zil_commit_waiter_t *, zcw);
+ txg_wait_synced(zilog->zl_dmu_pool, 0);
+ }
+
+ zil_free_commit_waiter(zcw);
}
/*
@@ -1707,8 +2675,8 @@ zil_sync(zilog_t *zilog, dmu_tx_t *tx)
if (lwb->lwb_buf != NULL || lwb->lwb_max_txg > txg)
break;
list_remove(&zilog->zl_lwb_list, lwb);
- zio_free_zil(spa, txg, &lwb->lwb_blk);
- kmem_cache_free(zil_lwb_cache, lwb);
+ zio_free(spa, txg, &lwb->lwb_blk);
+ zil_free_lwb(zilog, lwb);
/*
* If we don't have anything left in the lwb list then
@@ -1722,16 +2690,43 @@ zil_sync(zilog_t *zilog, dmu_tx_t *tx)
mutex_exit(&zilog->zl_lock);
}
+/* ARGSUSED */
+static int
+zil_lwb_cons(void *vbuf, void *unused, int kmflag)
+{
+ lwb_t *lwb = vbuf;
+ list_create(&lwb->lwb_waiters, sizeof (zil_commit_waiter_t),
+ offsetof(zil_commit_waiter_t, zcw_node));
+ avl_create(&lwb->lwb_vdev_tree, zil_lwb_vdev_compare,
+ sizeof (zil_vdev_node_t), offsetof(zil_vdev_node_t, zv_node));
+ mutex_init(&lwb->lwb_vdev_lock, NULL, MUTEX_DEFAULT, NULL);
+ return (0);
+}
+
+/* ARGSUSED */
+static void
+zil_lwb_dest(void *vbuf, void *unused)
+{
+ lwb_t *lwb = vbuf;
+ mutex_destroy(&lwb->lwb_vdev_lock);
+ avl_destroy(&lwb->lwb_vdev_tree);
+ list_destroy(&lwb->lwb_waiters);
+}
+
void
zil_init(void)
{
zil_lwb_cache = kmem_cache_create("zil_lwb_cache",
- sizeof (struct lwb), 0, NULL, NULL, NULL, NULL, NULL, 0);
+ sizeof (lwb_t), 0, zil_lwb_cons, zil_lwb_dest, NULL, NULL, NULL, 0);
+
+ zil_zcw_cache = kmem_cache_create("zil_zcw_cache",
+ sizeof (zil_commit_waiter_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
}
void
zil_fini(void)
{
+ kmem_cache_destroy(zil_zcw_cache);
kmem_cache_destroy(zil_lwb_cache);
}
@@ -1761,9 +2756,12 @@ zil_alloc(objset_t *os, zil_header_t *zh_phys)
zilog->zl_destroy_txg = TXG_INITIAL - 1;
zilog->zl_logbias = dmu_objset_logbias(os);
zilog->zl_sync = dmu_objset_syncprop(os);
- zilog->zl_next_batch = 1;
+ zilog->zl_dirty_max_txg = 0;
+ zilog->zl_last_lwb_opened = NULL;
+ zilog->zl_last_lwb_latency = 0;
mutex_init(&zilog->zl_lock, NULL, MUTEX_DEFAULT, NULL);
+ mutex_init(&zilog->zl_writer_lock, NULL, MUTEX_DEFAULT, NULL);
for (int i = 0; i < TXG_SIZE; i++) {
mutex_init(&zilog->zl_itxg[i].itxg_lock, NULL,
@@ -1776,15 +2774,7 @@ zil_alloc(objset_t *os, zil_header_t *zh_phys)
list_create(&zilog->zl_itx_commit_list, sizeof (itx_t),
offsetof(itx_t, itx_node));
- mutex_init(&zilog->zl_vdev_lock, NULL, MUTEX_DEFAULT, NULL);
-
- avl_create(&zilog->zl_vdev_tree, zil_vdev_compare,
- sizeof (zil_vdev_node_t), offsetof(zil_vdev_node_t, zv_node));
-
- cv_init(&zilog->zl_cv_writer, NULL, CV_DEFAULT, NULL);
cv_init(&zilog->zl_cv_suspend, NULL, CV_DEFAULT, NULL);
- cv_init(&zilog->zl_cv_batch[0], NULL, CV_DEFAULT, NULL);
- cv_init(&zilog->zl_cv_batch[1], NULL, CV_DEFAULT, NULL);
return (zilog);
}
@@ -1800,9 +2790,6 @@ zil_free(zilog_t *zilog)
ASSERT(list_is_empty(&zilog->zl_lwb_list));
list_destroy(&zilog->zl_lwb_list);
- avl_destroy(&zilog->zl_vdev_tree);
- mutex_destroy(&zilog->zl_vdev_lock);
-
ASSERT(list_is_empty(&zilog->zl_itx_commit_list));
list_destroy(&zilog->zl_itx_commit_list);
@@ -1819,12 +2806,10 @@ zil_free(zilog_t *zilog)
mutex_destroy(&zilog->zl_itxg[i].itxg_lock);
}
+ mutex_destroy(&zilog->zl_writer_lock);
mutex_destroy(&zilog->zl_lock);
- cv_destroy(&zilog->zl_cv_writer);
cv_destroy(&zilog->zl_cv_suspend);
- cv_destroy(&zilog->zl_cv_batch[0]);
- cv_destroy(&zilog->zl_cv_batch[1]);
kmem_free(zilog, sizeof (zilog_t));
}
@@ -1837,7 +2822,8 @@ zil_open(objset_t *os, zil_get_data_t *get_data)
{
zilog_t *zilog = dmu_objset_zil(os);
- ASSERT(zilog->zl_get_data == NULL);
+ ASSERT3P(zilog->zl_get_data, ==, NULL);
+ ASSERT3P(zilog->zl_last_lwb_opened, ==, NULL);
ASSERT(list_is_empty(&zilog->zl_lwb_list));
zilog->zl_get_data = get_data;
@@ -1852,22 +2838,30 @@ void
zil_close(zilog_t *zilog)
{
lwb_t *lwb;
- uint64_t txg = 0;
+ uint64_t txg;
- zil_commit(zilog, 0); /* commit all itx */
+ if (!dmu_objset_is_snapshot(zilog->zl_os)) {
+ zil_commit(zilog, 0);
+ } else {
+ ASSERT3P(list_tail(&zilog->zl_lwb_list), ==, NULL);
+ ASSERT0(zilog->zl_dirty_max_txg);
+ ASSERT3B(zilog_is_dirty(zilog), ==, B_FALSE);
+ }
- /*
- * The lwb_max_txg for the stubby lwb will reflect the last activity
- * for the zil. After a txg_wait_synced() on the txg we know all the
- * callbacks have occurred that may clean the zil. Only then can we
- * destroy the zl_clean_taskq.
- */
mutex_enter(&zilog->zl_lock);
lwb = list_tail(&zilog->zl_lwb_list);
- if (lwb != NULL)
- txg = lwb->lwb_max_txg;
+ if (lwb == NULL)
+ txg = zilog->zl_dirty_max_txg;
+ else
+ txg = MAX(zilog->zl_dirty_max_txg, lwb->lwb_max_txg);
mutex_exit(&zilog->zl_lock);
- if (txg)
+
+ /*
+ * We need to use txg_wait_synced() to wait long enough for the
+ * ZIL to be clean, and to wait for all pending lwbs to be
+ * written out.
+ */
+ if (txg != 0)
txg_wait_synced(zilog->zl_dmu_pool, txg);
if (zilog_is_dirty(zilog))
@@ -1877,15 +2871,16 @@ zil_close(zilog_t *zilog)
zilog->zl_get_data = NULL;
/*
- * We should have only one LWB left on the list; remove it now.
+ * We should have only one lwb left on the list; remove it now.
*/
mutex_enter(&zilog->zl_lock);
lwb = list_head(&zilog->zl_lwb_list);
if (lwb != NULL) {
- ASSERT(lwb == list_tail(&zilog->zl_lwb_list));
+ ASSERT3P(lwb, ==, list_tail(&zilog->zl_lwb_list));
+ ASSERT3S(lwb->lwb_state, !=, LWB_STATE_ISSUED);
list_remove(&zilog->zl_lwb_list, lwb);
zio_buf_free(lwb->lwb_buf, lwb->lwb_sz);
- kmem_cache_free(zil_lwb_cache, lwb);
+ zil_free_lwb(zilog, lwb);
}
mutex_exit(&zilog->zl_lock);
}
diff --git a/usr/src/uts/common/fs/zfs/zio.c b/usr/src/uts/common/fs/zfs/zio.c
index f26e109267..6dbba83ee1 100644
--- a/usr/src/uts/common/fs/zfs/zio.c
+++ b/usr/src/uts/common/fs/zfs/zio.c
@@ -399,7 +399,7 @@ zio_add_child(zio_t *pio, zio_t *cio)
* Vdev I/Os can only have vdev children.
* The following ASSERT captures all of these constraints.
*/
- ASSERT(cio->io_child_type <= pio->io_child_type);
+ ASSERT3S(cio->io_child_type, <=, pio->io_child_type);
zl->zl_parent = pio;
zl->zl_child = cio;
@@ -1106,9 +1106,9 @@ zio_flush(zio_t *zio, vdev_t *vd)
void
zio_shrink(zio_t *zio, uint64_t size)
{
- ASSERT(zio->io_executor == NULL);
- ASSERT(zio->io_orig_size == zio->io_size);
- ASSERT(size <= zio->io_size);
+ ASSERT3P(zio->io_executor, ==, NULL);
+ ASSERT3P(zio->io_orig_size, ==, zio->io_size);
+ ASSERT3U(size, <=, zio->io_size);
/*
* We don't shrink for raidz because of problems with the
@@ -1619,8 +1619,8 @@ zio_wait(zio_t *zio)
{
int error;
- ASSERT(zio->io_stage == ZIO_STAGE_OPEN);
- ASSERT(zio->io_executor == NULL);
+ ASSERT3P(zio->io_stage, ==, ZIO_STAGE_OPEN);
+ ASSERT3P(zio->io_executor, ==, NULL);
zio->io_waiter = curthread;
ASSERT0(zio->io_queued_timestamp);
@@ -1642,7 +1642,7 @@ zio_wait(zio_t *zio)
void
zio_nowait(zio_t *zio)
{
- ASSERT(zio->io_executor == NULL);
+ ASSERT3P(zio->io_executor, ==, NULL);
if (zio->io_child_type == ZIO_CHILD_LOGICAL &&
zio_unique_parent(zio) == NULL) {
@@ -1663,7 +1663,7 @@ zio_nowait(zio_t *zio)
/*
* ==========================================================================
- * Reexecute or suspend/resume failed I/O
+ * Reexecute, cancel, or suspend/resume failed I/O
* ==========================================================================
*/
@@ -1721,6 +1721,20 @@ zio_reexecute(zio_t *pio)
}
void
+zio_cancel(zio_t *zio)
+{
+ /*
+ * Disallow cancellation of a zio that's already been issued.
+ */
+ VERIFY3P(zio->io_executor, ==, NULL);
+
+ zio->io_pipeline = ZIO_INTERLOCK_PIPELINE;
+ zio->io_done = NULL;
+
+ zio_nowait(zio);
+}
+
+void
zio_suspend(spa_t *spa, zio_t *zio)
{
if (spa_get_failmode(spa) == ZIO_FAILURE_MODE_PANIC)
@@ -2952,6 +2966,9 @@ zio_alloc_zil(spa_t *spa, uint64_t txg, blkptr_t *new_bp, blkptr_t *old_bp,
BP_SET_LEVEL(new_bp, 0);
BP_SET_DEDUP(new_bp, 0);
BP_SET_BYTEORDER(new_bp, ZFS_HOST_BYTEORDER);
+ } else {
+ zfs_dbgmsg("%s: zil block allocation failure: "
+ "size %llu, error %d", spa_name(spa), size, error);
}
return (error);
diff --git a/usr/src/uts/common/fs/zfs/zvol.c b/usr/src/uts/common/fs/zfs/zvol.c
index b724bbe5ca..2ae0d7b35b 100644
--- a/usr/src/uts/common/fs/zfs/zvol.c
+++ b/usr/src/uts/common/fs/zfs/zvol.c
@@ -89,6 +89,7 @@
#include <sys/dmu_tx.h>
#include <sys/zfeature.h>
#include <sys/zio_checksum.h>
+#include <sys/zil_impl.h>
#include "zfs_namecheck.h"
@@ -163,7 +164,8 @@ boolean_t zvol_unmap_sync_enabled = B_FALSE;
extern int zfs_set_prop_nvlist(const char *, zprop_source_t,
nvlist_t *, nvlist_t *);
static int zvol_remove_zv(zvol_state_t *);
-static int zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio);
+static int zvol_get_data(void *arg, lr_write_t *lr, char *buf,
+ struct lwb *lwb, zio_t *zio);
static int zvol_dumpify(zvol_state_t *zv);
static int zvol_dump_fini(zvol_state_t *zv);
static int zvol_dump_init(zvol_state_t *zv, boolean_t resize);
@@ -981,7 +983,7 @@ zvol_get_done(zgd_t *zgd, int error)
zfs_range_unlock(zgd->zgd_rl);
if (error == 0 && zgd->zgd_bp)
- zil_add_block(zgd->zgd_zilog, zgd->zgd_bp);
+ zil_lwb_add_block(zgd->zgd_lwb, zgd->zgd_bp);
kmem_free(zgd, sizeof (zgd_t));
}
@@ -990,7 +992,7 @@ zvol_get_done(zgd_t *zgd, int error)
* Get data to generate a TX_WRITE intent log record.
*/
static int
-zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
+zvol_get_data(void *arg, lr_write_t *lr, char *buf, struct lwb *lwb, zio_t *zio)
{
zvol_state_t *zv = arg;
objset_t *os = zv->zv_objset;
@@ -1001,11 +1003,12 @@ zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio)
zgd_t *zgd;
int error;
- ASSERT(zio != NULL);
- ASSERT(size != 0);
+ ASSERT3P(lwb, !=, NULL);
+ ASSERT3P(zio, !=, NULL);
+ ASSERT3U(size, !=, 0);
zgd = kmem_zalloc(sizeof (zgd_t), KM_SLEEP);
- zgd->zgd_zilog = zv->zv_zilog;
+ zgd->zgd_lwb = lwb;
zgd->zgd_rl = zfs_range_lock(&zv->zv_znode, offset, size, RL_READER);
/*
diff --git a/usr/src/uts/common/sys/debug.h b/usr/src/uts/common/sys/debug.h
index 8efc8956b6..05e62aa1a1 100644
--- a/usr/src/uts/common/sys/debug.h
+++ b/usr/src/uts/common/sys/debug.h
@@ -26,7 +26,7 @@
*/
/*
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
* Copyright 2013 Saso Kiselkov. All rights reserved.
*/
@@ -108,17 +108,20 @@ extern void assfail3(const char *, uintmax_t, const char *, uintmax_t,
__FILE__, __LINE__); \
_NOTE(CONSTCOND) } while (0)
+#define VERIFY3B(x, y, z) VERIFY3_IMPL(x, y, z, boolean_t)
#define VERIFY3S(x, y, z) VERIFY3_IMPL(x, y, z, int64_t)
#define VERIFY3U(x, y, z) VERIFY3_IMPL(x, y, z, uint64_t)
#define VERIFY3P(x, y, z) VERIFY3_IMPL(x, y, z, uintptr_t)
#define VERIFY0(x) VERIFY3_IMPL(x, ==, 0, uintmax_t)
#if DEBUG
+#define ASSERT3B(x, y, z) VERIFY3_IMPL(x, y, z, boolean_t)
#define ASSERT3S(x, y, z) VERIFY3_IMPL(x, y, z, int64_t)
#define ASSERT3U(x, y, z) VERIFY3_IMPL(x, y, z, uint64_t)
#define ASSERT3P(x, y, z) VERIFY3_IMPL(x, y, z, uintptr_t)
#define ASSERT0(x) VERIFY3_IMPL(x, ==, 0, uintmax_t)
#else
+#define ASSERT3B(x, y, z) ((void)0)
#define ASSERT3S(x, y, z) ((void)0)
#define ASSERT3U(x, y, z) ((void)0)
#define ASSERT3P(x, y, z) ((void)0)
diff --git a/usr/src/uts/common/sys/time.h b/usr/src/uts/common/sys/time.h
index 338338c48d..318d3898f5 100644
--- a/usr/src/uts/common/sys/time.h
+++ b/usr/src/uts/common/sys/time.h
@@ -248,6 +248,9 @@ struct itimerval32 {
#define MSEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MILLISEC))
#define NSEC2MSEC(n) ((n) / (NANOSEC / MILLISEC))
+#define USEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MICROSEC))
+#define NSEC2USEC(n) ((n) / (NANOSEC / MICROSEC))
+
#define NSEC2SEC(n) ((n) / (NANOSEC / SEC))
#define SEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / SEC))
diff --git a/usr/src/uts/i86pc/os/pci_bios.c b/usr/src/uts/i86pc/os/pci_bios.c
index 9ee865c7d2..4c824c695a 100644
--- a/usr/src/uts/i86pc/os/pci_bios.c
+++ b/usr/src/uts/i86pc/os/pci_bios.c
@@ -76,6 +76,10 @@ pci_bios_get_irq_routing(pci_irq_route_t *routes, int nroutes, int *nneededp)
if (nneededp)
*nneededp = 0;
+ /* in UEFI system, there is no BIOS data */
+ if (BOP_GETPROPLEN(bootops, "efi-systab") > 0)
+ return (0);
+
/*
* Set up irq routing header with the size and address
* of some useable low-memory data addresses. Initalize
diff --git a/usr/src/uts/intel/io/acpica/acpica.c b/usr/src/uts/intel/io/acpica/acpica.c
index 0fba611701..1c994e443b 100644
--- a/usr/src/uts/intel/io/acpica/acpica.c
+++ b/usr/src/uts/intel/io/acpica/acpica.c
@@ -253,7 +253,7 @@ acpica_check_bios_date(int yy, int mm, int dd)
/* If firmware has no bios, skip the check */
if (ddi_prop_exists(DDI_DEV_T_ANY, ddi_root_node(), DDI_PROP_DONTPASS,
- "bios-free"))
+ "efi-systab"))
return (TRUE);
/*