From c60bf3391bf4cb79b7adc6650094e21671ddaabd Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 14 Nov 2013 11:54:14 +0100 Subject: readline: decouple readline from the monitor Make the readline.c functionality reusable. Instead of calling monitor_printf() and monitor_flush() directly, invoke function pointers provided by the user. This way readline.c does not know about Monitor and other users will be able to make use of readline.c. Note that there is already an "opaque" argument to the ReadLineFunc callback. Consistently call it "readline_opaque" from now on to distinguish from the ReadLinePrintfFunc/ReadLineFlushFunc "opaque" argument. I also dropped the printf macro trickery since it's now highly unlikely that anyone modifying readline.c would call printf(3) directly. We no longer need this protection. Signed-off-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- include/monitor/readline.h | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/monitor/readline.h b/include/monitor/readline.h index 0faf6e1db7..a89fe4a9a9 100644 --- a/include/monitor/readline.h +++ b/include/monitor/readline.h @@ -1,14 +1,15 @@ #ifndef READLINE_H #define READLINE_H -#include "qemu-common.h" - #define READLINE_CMD_BUF_SIZE 4095 #define READLINE_MAX_CMDS 64 #define READLINE_MAX_COMPLETIONS 256 -typedef void ReadLineFunc(Monitor *mon, const char *str, void *opaque); -typedef void ReadLineCompletionFunc(Monitor *mon, +typedef void ReadLinePrintfFunc(void *opaque, const char *fmt, ...); +typedef void ReadLineFlushFunc(void *opaque); +typedef void ReadLineFunc(void *opaque, const char *str, + void *readline_opaque); +typedef void ReadLineCompletionFunc(void *opaque, const char *cmdline); typedef struct ReadLineState { @@ -35,7 +36,10 @@ typedef struct ReadLineState { void *readline_opaque; int read_password; char prompt[256]; - Monitor *mon; + + ReadLinePrintfFunc *printf_func; + ReadLineFlushFunc *flush_func; + void *opaque; } ReadLineState; void readline_add_completion(ReadLineState *rs, const char *str); @@ -46,11 +50,13 @@ const char *readline_get_history(ReadLineState *rs, unsigned int index); void readline_handle_byte(ReadLineState *rs, int ch); void readline_start(ReadLineState *rs, const char *prompt, int read_password, - ReadLineFunc *readline_func, void *opaque); + ReadLineFunc *readline_func, void *readline_opaque); void readline_restart(ReadLineState *rs); void readline_show_prompt(ReadLineState *rs); -ReadLineState *readline_init(Monitor *mon, +ReadLineState *readline_init(ReadLinePrintfFunc *printf_func, + ReadLineFlushFunc *flush_func, + void *opaque, ReadLineCompletionFunc *completion_finder); #endif /* !READLINE_H */ -- cgit 1.4.1 From 0150cd81cf608b93778a067189829f354fe27e4b Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 14 Nov 2013 11:54:15 +0100 Subject: readline: move readline to a generic location Now that the monitor and readline are decoupled, readline.h no longer belongs in include/monitor/. Put the header into include/qemu/. Move the source file into util/ so it can be linked as part of libqemuutil.a. Signed-off-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- Makefile.objs | 1 - include/monitor/monitor.h | 2 +- include/monitor/readline.h | 62 ------ include/qemu/readline.h | 62 ++++++ monitor.c | 2 +- readline.c | 495 --------------------------------------------- util/Makefile.objs | 1 + util/readline.c | 495 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 560 insertions(+), 560 deletions(-) delete mode 100644 include/monitor/readline.h create mode 100644 include/qemu/readline.h delete mode 100644 readline.c create mode 100644 util/readline.c (limited to 'include') diff --git a/Makefile.objs b/Makefile.objs index 857bb53ae4..ac1d0e1c28 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -43,7 +43,6 @@ libcacard-y += libcacard/vcardt.o ifeq ($(CONFIG_SOFTMMU),y) common-obj-y = $(block-obj-y) blockdev.o blockdev-nbd.o block/ common-obj-y += net/ -common-obj-y += readline.o common-obj-y += qdev-monitor.o device-hotplug.o common-obj-$(CONFIG_WIN32) += os-win32.o common-obj-$(CONFIG_POSIX) += os-posix.o diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h index 22d8b8f3e0..7e5f752b7a 100644 --- a/include/monitor/monitor.h +++ b/include/monitor/monitor.h @@ -5,7 +5,7 @@ #include "qapi/qmp/qerror.h" #include "qapi/qmp/qdict.h" #include "block/block.h" -#include "monitor/readline.h" +#include "qemu/readline.h" extern Monitor *cur_mon; extern Monitor *default_mon; diff --git a/include/monitor/readline.h b/include/monitor/readline.h deleted file mode 100644 index a89fe4a9a9..0000000000 --- a/include/monitor/readline.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef READLINE_H -#define READLINE_H - -#define READLINE_CMD_BUF_SIZE 4095 -#define READLINE_MAX_CMDS 64 -#define READLINE_MAX_COMPLETIONS 256 - -typedef void ReadLinePrintfFunc(void *opaque, const char *fmt, ...); -typedef void ReadLineFlushFunc(void *opaque); -typedef void ReadLineFunc(void *opaque, const char *str, - void *readline_opaque); -typedef void ReadLineCompletionFunc(void *opaque, - const char *cmdline); - -typedef struct ReadLineState { - char cmd_buf[READLINE_CMD_BUF_SIZE + 1]; - int cmd_buf_index; - int cmd_buf_size; - - char last_cmd_buf[READLINE_CMD_BUF_SIZE + 1]; - int last_cmd_buf_index; - int last_cmd_buf_size; - - int esc_state; - int esc_param; - - char *history[READLINE_MAX_CMDS]; - int hist_entry; - - ReadLineCompletionFunc *completion_finder; - char *completions[READLINE_MAX_COMPLETIONS]; - int nb_completions; - int completion_index; - - ReadLineFunc *readline_func; - void *readline_opaque; - int read_password; - char prompt[256]; - - ReadLinePrintfFunc *printf_func; - ReadLineFlushFunc *flush_func; - void *opaque; -} ReadLineState; - -void readline_add_completion(ReadLineState *rs, const char *str); -void readline_set_completion_index(ReadLineState *rs, int completion_index); - -const char *readline_get_history(ReadLineState *rs, unsigned int index); - -void readline_handle_byte(ReadLineState *rs, int ch); - -void readline_start(ReadLineState *rs, const char *prompt, int read_password, - ReadLineFunc *readline_func, void *readline_opaque); -void readline_restart(ReadLineState *rs); -void readline_show_prompt(ReadLineState *rs); - -ReadLineState *readline_init(ReadLinePrintfFunc *printf_func, - ReadLineFlushFunc *flush_func, - void *opaque, - ReadLineCompletionFunc *completion_finder); - -#endif /* !READLINE_H */ diff --git a/include/qemu/readline.h b/include/qemu/readline.h new file mode 100644 index 0000000000..a89fe4a9a9 --- /dev/null +++ b/include/qemu/readline.h @@ -0,0 +1,62 @@ +#ifndef READLINE_H +#define READLINE_H + +#define READLINE_CMD_BUF_SIZE 4095 +#define READLINE_MAX_CMDS 64 +#define READLINE_MAX_COMPLETIONS 256 + +typedef void ReadLinePrintfFunc(void *opaque, const char *fmt, ...); +typedef void ReadLineFlushFunc(void *opaque); +typedef void ReadLineFunc(void *opaque, const char *str, + void *readline_opaque); +typedef void ReadLineCompletionFunc(void *opaque, + const char *cmdline); + +typedef struct ReadLineState { + char cmd_buf[READLINE_CMD_BUF_SIZE + 1]; + int cmd_buf_index; + int cmd_buf_size; + + char last_cmd_buf[READLINE_CMD_BUF_SIZE + 1]; + int last_cmd_buf_index; + int last_cmd_buf_size; + + int esc_state; + int esc_param; + + char *history[READLINE_MAX_CMDS]; + int hist_entry; + + ReadLineCompletionFunc *completion_finder; + char *completions[READLINE_MAX_COMPLETIONS]; + int nb_completions; + int completion_index; + + ReadLineFunc *readline_func; + void *readline_opaque; + int read_password; + char prompt[256]; + + ReadLinePrintfFunc *printf_func; + ReadLineFlushFunc *flush_func; + void *opaque; +} ReadLineState; + +void readline_add_completion(ReadLineState *rs, const char *str); +void readline_set_completion_index(ReadLineState *rs, int completion_index); + +const char *readline_get_history(ReadLineState *rs, unsigned int index); + +void readline_handle_byte(ReadLineState *rs, int ch); + +void readline_start(ReadLineState *rs, const char *prompt, int read_password, + ReadLineFunc *readline_func, void *readline_opaque); +void readline_restart(ReadLineState *rs); +void readline_show_prompt(ReadLineState *rs); + +ReadLineState *readline_init(ReadLinePrintfFunc *printf_func, + ReadLineFlushFunc *flush_func, + void *opaque, + ReadLineCompletionFunc *completion_finder); + +#endif /* !READLINE_H */ diff --git a/monitor.c b/monitor.c index 32d02640a0..80456fbe5b 100644 --- a/monitor.c +++ b/monitor.c @@ -37,7 +37,7 @@ #include "ui/qemu-spice.h" #include "sysemu/sysemu.h" #include "monitor/monitor.h" -#include "monitor/readline.h" +#include "qemu/readline.h" #include "ui/console.h" #include "sysemu/blockdev.h" #include "audio/audio.h" diff --git a/readline.c b/readline.c deleted file mode 100644 index ca894d1854..0000000000 --- a/readline.c +++ /dev/null @@ -1,495 +0,0 @@ -/* - * QEMU readline utility - * - * Copyright (c) 2003-2004 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu-common.h" -#include "monitor/readline.h" - -#define IS_NORM 0 -#define IS_ESC 1 -#define IS_CSI 2 -#define IS_SS3 3 - -void readline_show_prompt(ReadLineState *rs) -{ - rs->printf_func(rs->opaque, "%s", rs->prompt); - rs->flush_func(rs->opaque); - rs->last_cmd_buf_index = 0; - rs->last_cmd_buf_size = 0; - rs->esc_state = IS_NORM; -} - -/* update the displayed command line */ -static void readline_update(ReadLineState *rs) -{ - int i, delta, len; - - if (rs->cmd_buf_size != rs->last_cmd_buf_size || - memcmp(rs->cmd_buf, rs->last_cmd_buf, rs->cmd_buf_size) != 0) { - for(i = 0; i < rs->last_cmd_buf_index; i++) { - rs->printf_func(rs->opaque, "\033[D"); - } - rs->cmd_buf[rs->cmd_buf_size] = '\0'; - if (rs->read_password) { - len = strlen(rs->cmd_buf); - for(i = 0; i < len; i++) - rs->printf_func(rs->opaque, "*"); - } else { - rs->printf_func(rs->opaque, "%s", rs->cmd_buf); - } - rs->printf_func(rs->opaque, "\033[K"); - memcpy(rs->last_cmd_buf, rs->cmd_buf, rs->cmd_buf_size); - rs->last_cmd_buf_size = rs->cmd_buf_size; - rs->last_cmd_buf_index = rs->cmd_buf_size; - } - if (rs->cmd_buf_index != rs->last_cmd_buf_index) { - delta = rs->cmd_buf_index - rs->last_cmd_buf_index; - if (delta > 0) { - for(i = 0;i < delta; i++) { - rs->printf_func(rs->opaque, "\033[C"); - } - } else { - delta = -delta; - for(i = 0;i < delta; i++) { - rs->printf_func(rs->opaque, "\033[D"); - } - } - rs->last_cmd_buf_index = rs->cmd_buf_index; - } - rs->flush_func(rs->opaque); -} - -static void readline_insert_char(ReadLineState *rs, int ch) -{ - if (rs->cmd_buf_index < READLINE_CMD_BUF_SIZE) { - memmove(rs->cmd_buf + rs->cmd_buf_index + 1, - rs->cmd_buf + rs->cmd_buf_index, - rs->cmd_buf_size - rs->cmd_buf_index); - rs->cmd_buf[rs->cmd_buf_index] = ch; - rs->cmd_buf_size++; - rs->cmd_buf_index++; - } -} - -static void readline_backward_char(ReadLineState *rs) -{ - if (rs->cmd_buf_index > 0) { - rs->cmd_buf_index--; - } -} - -static void readline_forward_char(ReadLineState *rs) -{ - if (rs->cmd_buf_index < rs->cmd_buf_size) { - rs->cmd_buf_index++; - } -} - -static void readline_delete_char(ReadLineState *rs) -{ - if (rs->cmd_buf_index < rs->cmd_buf_size) { - memmove(rs->cmd_buf + rs->cmd_buf_index, - rs->cmd_buf + rs->cmd_buf_index + 1, - rs->cmd_buf_size - rs->cmd_buf_index - 1); - rs->cmd_buf_size--; - } -} - -static void readline_backspace(ReadLineState *rs) -{ - if (rs->cmd_buf_index > 0) { - readline_backward_char(rs); - readline_delete_char(rs); - } -} - -static void readline_backword(ReadLineState *rs) -{ - int start; - - if (rs->cmd_buf_index == 0 || rs->cmd_buf_index > rs->cmd_buf_size) { - return; - } - - start = rs->cmd_buf_index - 1; - - /* find first word (backwards) */ - while (start > 0) { - if (!qemu_isspace(rs->cmd_buf[start])) { - break; - } - - --start; - } - - /* find first space (backwards) */ - while (start > 0) { - if (qemu_isspace(rs->cmd_buf[start])) { - ++start; - break; - } - - --start; - } - - /* remove word */ - if (start < rs->cmd_buf_index) { - memmove(rs->cmd_buf + start, - rs->cmd_buf + rs->cmd_buf_index, - rs->cmd_buf_size - rs->cmd_buf_index); - rs->cmd_buf_size -= rs->cmd_buf_index - start; - rs->cmd_buf_index = start; - } -} - -static void readline_bol(ReadLineState *rs) -{ - rs->cmd_buf_index = 0; -} - -static void readline_eol(ReadLineState *rs) -{ - rs->cmd_buf_index = rs->cmd_buf_size; -} - -static void readline_up_char(ReadLineState *rs) -{ - int idx; - - if (rs->hist_entry == 0) - return; - if (rs->hist_entry == -1) { - /* Find latest entry */ - for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { - if (rs->history[idx] == NULL) - break; - } - rs->hist_entry = idx; - } - rs->hist_entry--; - if (rs->hist_entry >= 0) { - pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), - rs->history[rs->hist_entry]); - rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); - } -} - -static void readline_down_char(ReadLineState *rs) -{ - if (rs->hist_entry == -1) - return; - if (rs->hist_entry < READLINE_MAX_CMDS - 1 && - rs->history[++rs->hist_entry] != NULL) { - pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), - rs->history[rs->hist_entry]); - } else { - rs->cmd_buf[0] = 0; - rs->hist_entry = -1; - } - rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); -} - -static void readline_hist_add(ReadLineState *rs, const char *cmdline) -{ - char *hist_entry, *new_entry; - int idx; - - if (cmdline[0] == '\0') - return; - new_entry = NULL; - if (rs->hist_entry != -1) { - /* We were editing an existing history entry: replace it */ - hist_entry = rs->history[rs->hist_entry]; - idx = rs->hist_entry; - if (strcmp(hist_entry, cmdline) == 0) { - goto same_entry; - } - } - /* Search cmdline in history buffers */ - for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { - hist_entry = rs->history[idx]; - if (hist_entry == NULL) - break; - if (strcmp(hist_entry, cmdline) == 0) { - same_entry: - new_entry = hist_entry; - /* Put this entry at the end of history */ - memmove(&rs->history[idx], &rs->history[idx + 1], - (READLINE_MAX_CMDS - (idx + 1)) * sizeof(char *)); - rs->history[READLINE_MAX_CMDS - 1] = NULL; - for (; idx < READLINE_MAX_CMDS; idx++) { - if (rs->history[idx] == NULL) - break; - } - break; - } - } - if (idx == READLINE_MAX_CMDS) { - /* Need to get one free slot */ - g_free(rs->history[0]); - memmove(rs->history, &rs->history[1], - (READLINE_MAX_CMDS - 1) * sizeof(char *)); - rs->history[READLINE_MAX_CMDS - 1] = NULL; - idx = READLINE_MAX_CMDS - 1; - } - if (new_entry == NULL) - new_entry = g_strdup(cmdline); - rs->history[idx] = new_entry; - rs->hist_entry = -1; -} - -/* completion support */ - -void readline_add_completion(ReadLineState *rs, const char *str) -{ - if (rs->nb_completions < READLINE_MAX_COMPLETIONS) { - rs->completions[rs->nb_completions++] = g_strdup(str); - } -} - -void readline_set_completion_index(ReadLineState *rs, int index) -{ - rs->completion_index = index; -} - -static void readline_completion(ReadLineState *rs) -{ - int len, i, j, max_width, nb_cols, max_prefix; - char *cmdline; - - rs->nb_completions = 0; - - cmdline = g_malloc(rs->cmd_buf_index + 1); - memcpy(cmdline, rs->cmd_buf, rs->cmd_buf_index); - cmdline[rs->cmd_buf_index] = '\0'; - rs->completion_finder(rs->opaque, cmdline); - g_free(cmdline); - - /* no completion found */ - if (rs->nb_completions <= 0) - return; - if (rs->nb_completions == 1) { - len = strlen(rs->completions[0]); - for(i = rs->completion_index; i < len; i++) { - readline_insert_char(rs, rs->completions[0][i]); - } - /* extra space for next argument. XXX: make it more generic */ - if (len > 0 && rs->completions[0][len - 1] != '/') - readline_insert_char(rs, ' '); - } else { - rs->printf_func(rs->opaque, "\n"); - max_width = 0; - max_prefix = 0; - for(i = 0; i < rs->nb_completions; i++) { - len = strlen(rs->completions[i]); - if (i==0) { - max_prefix = len; - } else { - if (len < max_prefix) - max_prefix = len; - for(j=0; jcompletions[i][j] != rs->completions[0][j]) - max_prefix = j; - } - } - if (len > max_width) - max_width = len; - } - if (max_prefix > 0) - for(i = rs->completion_index; i < max_prefix; i++) { - readline_insert_char(rs, rs->completions[0][i]); - } - max_width += 2; - if (max_width < 10) - max_width = 10; - else if (max_width > 80) - max_width = 80; - nb_cols = 80 / max_width; - j = 0; - for(i = 0; i < rs->nb_completions; i++) { - rs->printf_func(rs->opaque, "%-*s", max_width, rs->completions[i]); - if (++j == nb_cols || i == (rs->nb_completions - 1)) { - rs->printf_func(rs->opaque, "\n"); - j = 0; - } - } - readline_show_prompt(rs); - } - for (i = 0; i < rs->nb_completions; i++) { - g_free(rs->completions[i]); - } -} - -/* return true if command handled */ -void readline_handle_byte(ReadLineState *rs, int ch) -{ - switch(rs->esc_state) { - case IS_NORM: - switch(ch) { - case 1: - readline_bol(rs); - break; - case 4: - readline_delete_char(rs); - break; - case 5: - readline_eol(rs); - break; - case 9: - readline_completion(rs); - break; - case 10: - case 13: - rs->cmd_buf[rs->cmd_buf_size] = '\0'; - if (!rs->read_password) - readline_hist_add(rs, rs->cmd_buf); - rs->printf_func(rs->opaque, "\n"); - rs->cmd_buf_index = 0; - rs->cmd_buf_size = 0; - rs->last_cmd_buf_index = 0; - rs->last_cmd_buf_size = 0; - rs->readline_func(rs->opaque, rs->cmd_buf, rs->readline_opaque); - break; - case 23: - /* ^W */ - readline_backword(rs); - break; - case 27: - rs->esc_state = IS_ESC; - break; - case 127: - case 8: - readline_backspace(rs); - break; - case 155: - rs->esc_state = IS_CSI; - break; - default: - if (ch >= 32) { - readline_insert_char(rs, ch); - } - break; - } - break; - case IS_ESC: - if (ch == '[') { - rs->esc_state = IS_CSI; - rs->esc_param = 0; - } else if (ch == 'O') { - rs->esc_state = IS_SS3; - rs->esc_param = 0; - } else { - rs->esc_state = IS_NORM; - } - break; - case IS_CSI: - switch(ch) { - case 'A': - case 'F': - readline_up_char(rs); - break; - case 'B': - case 'E': - readline_down_char(rs); - break; - case 'D': - readline_backward_char(rs); - break; - case 'C': - readline_forward_char(rs); - break; - case '0' ... '9': - rs->esc_param = rs->esc_param * 10 + (ch - '0'); - goto the_end; - case '~': - switch(rs->esc_param) { - case 1: - readline_bol(rs); - break; - case 3: - readline_delete_char(rs); - break; - case 4: - readline_eol(rs); - break; - } - break; - default: - break; - } - rs->esc_state = IS_NORM; - the_end: - break; - case IS_SS3: - switch(ch) { - case 'F': - readline_eol(rs); - break; - case 'H': - readline_bol(rs); - break; - } - rs->esc_state = IS_NORM; - break; - } - readline_update(rs); -} - -void readline_start(ReadLineState *rs, const char *prompt, int read_password, - ReadLineFunc *readline_func, void *opaque) -{ - pstrcpy(rs->prompt, sizeof(rs->prompt), prompt); - rs->readline_func = readline_func; - rs->readline_opaque = opaque; - rs->read_password = read_password; - readline_restart(rs); -} - -void readline_restart(ReadLineState *rs) -{ - rs->cmd_buf_index = 0; - rs->cmd_buf_size = 0; -} - -const char *readline_get_history(ReadLineState *rs, unsigned int index) -{ - if (index >= READLINE_MAX_CMDS) - return NULL; - return rs->history[index]; -} - -ReadLineState *readline_init(ReadLinePrintfFunc *printf_func, - ReadLineFlushFunc *flush_func, - void *opaque, - ReadLineCompletionFunc *completion_finder) -{ - ReadLineState *rs = g_malloc0(sizeof(*rs)); - - rs->hist_entry = -1; - rs->opaque = opaque; - rs->printf_func = printf_func; - rs->flush_func = flush_func; - rs->completion_finder = completion_finder; - - return rs; -} diff --git a/util/Makefile.objs b/util/Makefile.objs index af3e5cb157..937376b082 100644 --- a/util/Makefile.objs +++ b/util/Makefile.objs @@ -13,3 +13,4 @@ util-obj-y += hexdump.o util-obj-y += crc32c.o util-obj-y += throttle.o util-obj-y += getauxval.o +util-obj-y += readline.o diff --git a/util/readline.c b/util/readline.c new file mode 100644 index 0000000000..8441be484c --- /dev/null +++ b/util/readline.c @@ -0,0 +1,495 @@ +/* + * QEMU readline utility + * + * Copyright (c) 2003-2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "qemu/readline.h" + +#define IS_NORM 0 +#define IS_ESC 1 +#define IS_CSI 2 +#define IS_SS3 3 + +void readline_show_prompt(ReadLineState *rs) +{ + rs->printf_func(rs->opaque, "%s", rs->prompt); + rs->flush_func(rs->opaque); + rs->last_cmd_buf_index = 0; + rs->last_cmd_buf_size = 0; + rs->esc_state = IS_NORM; +} + +/* update the displayed command line */ +static void readline_update(ReadLineState *rs) +{ + int i, delta, len; + + if (rs->cmd_buf_size != rs->last_cmd_buf_size || + memcmp(rs->cmd_buf, rs->last_cmd_buf, rs->cmd_buf_size) != 0) { + for(i = 0; i < rs->last_cmd_buf_index; i++) { + rs->printf_func(rs->opaque, "\033[D"); + } + rs->cmd_buf[rs->cmd_buf_size] = '\0'; + if (rs->read_password) { + len = strlen(rs->cmd_buf); + for(i = 0; i < len; i++) + rs->printf_func(rs->opaque, "*"); + } else { + rs->printf_func(rs->opaque, "%s", rs->cmd_buf); + } + rs->printf_func(rs->opaque, "\033[K"); + memcpy(rs->last_cmd_buf, rs->cmd_buf, rs->cmd_buf_size); + rs->last_cmd_buf_size = rs->cmd_buf_size; + rs->last_cmd_buf_index = rs->cmd_buf_size; + } + if (rs->cmd_buf_index != rs->last_cmd_buf_index) { + delta = rs->cmd_buf_index - rs->last_cmd_buf_index; + if (delta > 0) { + for(i = 0;i < delta; i++) { + rs->printf_func(rs->opaque, "\033[C"); + } + } else { + delta = -delta; + for(i = 0;i < delta; i++) { + rs->printf_func(rs->opaque, "\033[D"); + } + } + rs->last_cmd_buf_index = rs->cmd_buf_index; + } + rs->flush_func(rs->opaque); +} + +static void readline_insert_char(ReadLineState *rs, int ch) +{ + if (rs->cmd_buf_index < READLINE_CMD_BUF_SIZE) { + memmove(rs->cmd_buf + rs->cmd_buf_index + 1, + rs->cmd_buf + rs->cmd_buf_index, + rs->cmd_buf_size - rs->cmd_buf_index); + rs->cmd_buf[rs->cmd_buf_index] = ch; + rs->cmd_buf_size++; + rs->cmd_buf_index++; + } +} + +static void readline_backward_char(ReadLineState *rs) +{ + if (rs->cmd_buf_index > 0) { + rs->cmd_buf_index--; + } +} + +static void readline_forward_char(ReadLineState *rs) +{ + if (rs->cmd_buf_index < rs->cmd_buf_size) { + rs->cmd_buf_index++; + } +} + +static void readline_delete_char(ReadLineState *rs) +{ + if (rs->cmd_buf_index < rs->cmd_buf_size) { + memmove(rs->cmd_buf + rs->cmd_buf_index, + rs->cmd_buf + rs->cmd_buf_index + 1, + rs->cmd_buf_size - rs->cmd_buf_index - 1); + rs->cmd_buf_size--; + } +} + +static void readline_backspace(ReadLineState *rs) +{ + if (rs->cmd_buf_index > 0) { + readline_backward_char(rs); + readline_delete_char(rs); + } +} + +static void readline_backword(ReadLineState *rs) +{ + int start; + + if (rs->cmd_buf_index == 0 || rs->cmd_buf_index > rs->cmd_buf_size) { + return; + } + + start = rs->cmd_buf_index - 1; + + /* find first word (backwards) */ + while (start > 0) { + if (!qemu_isspace(rs->cmd_buf[start])) { + break; + } + + --start; + } + + /* find first space (backwards) */ + while (start > 0) { + if (qemu_isspace(rs->cmd_buf[start])) { + ++start; + break; + } + + --start; + } + + /* remove word */ + if (start < rs->cmd_buf_index) { + memmove(rs->cmd_buf + start, + rs->cmd_buf + rs->cmd_buf_index, + rs->cmd_buf_size - rs->cmd_buf_index); + rs->cmd_buf_size -= rs->cmd_buf_index - start; + rs->cmd_buf_index = start; + } +} + +static void readline_bol(ReadLineState *rs) +{ + rs->cmd_buf_index = 0; +} + +static void readline_eol(ReadLineState *rs) +{ + rs->cmd_buf_index = rs->cmd_buf_size; +} + +static void readline_up_char(ReadLineState *rs) +{ + int idx; + + if (rs->hist_entry == 0) + return; + if (rs->hist_entry == -1) { + /* Find latest entry */ + for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { + if (rs->history[idx] == NULL) + break; + } + rs->hist_entry = idx; + } + rs->hist_entry--; + if (rs->hist_entry >= 0) { + pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), + rs->history[rs->hist_entry]); + rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); + } +} + +static void readline_down_char(ReadLineState *rs) +{ + if (rs->hist_entry == -1) + return; + if (rs->hist_entry < READLINE_MAX_CMDS - 1 && + rs->history[++rs->hist_entry] != NULL) { + pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), + rs->history[rs->hist_entry]); + } else { + rs->cmd_buf[0] = 0; + rs->hist_entry = -1; + } + rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); +} + +static void readline_hist_add(ReadLineState *rs, const char *cmdline) +{ + char *hist_entry, *new_entry; + int idx; + + if (cmdline[0] == '\0') + return; + new_entry = NULL; + if (rs->hist_entry != -1) { + /* We were editing an existing history entry: replace it */ + hist_entry = rs->history[rs->hist_entry]; + idx = rs->hist_entry; + if (strcmp(hist_entry, cmdline) == 0) { + goto same_entry; + } + } + /* Search cmdline in history buffers */ + for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { + hist_entry = rs->history[idx]; + if (hist_entry == NULL) + break; + if (strcmp(hist_entry, cmdline) == 0) { + same_entry: + new_entry = hist_entry; + /* Put this entry at the end of history */ + memmove(&rs->history[idx], &rs->history[idx + 1], + (READLINE_MAX_CMDS - (idx + 1)) * sizeof(char *)); + rs->history[READLINE_MAX_CMDS - 1] = NULL; + for (; idx < READLINE_MAX_CMDS; idx++) { + if (rs->history[idx] == NULL) + break; + } + break; + } + } + if (idx == READLINE_MAX_CMDS) { + /* Need to get one free slot */ + g_free(rs->history[0]); + memmove(rs->history, &rs->history[1], + (READLINE_MAX_CMDS - 1) * sizeof(char *)); + rs->history[READLINE_MAX_CMDS - 1] = NULL; + idx = READLINE_MAX_CMDS - 1; + } + if (new_entry == NULL) + new_entry = g_strdup(cmdline); + rs->history[idx] = new_entry; + rs->hist_entry = -1; +} + +/* completion support */ + +void readline_add_completion(ReadLineState *rs, const char *str) +{ + if (rs->nb_completions < READLINE_MAX_COMPLETIONS) { + rs->completions[rs->nb_completions++] = g_strdup(str); + } +} + +void readline_set_completion_index(ReadLineState *rs, int index) +{ + rs->completion_index = index; +} + +static void readline_completion(ReadLineState *rs) +{ + int len, i, j, max_width, nb_cols, max_prefix; + char *cmdline; + + rs->nb_completions = 0; + + cmdline = g_malloc(rs->cmd_buf_index + 1); + memcpy(cmdline, rs->cmd_buf, rs->cmd_buf_index); + cmdline[rs->cmd_buf_index] = '\0'; + rs->completion_finder(rs->opaque, cmdline); + g_free(cmdline); + + /* no completion found */ + if (rs->nb_completions <= 0) + return; + if (rs->nb_completions == 1) { + len = strlen(rs->completions[0]); + for(i = rs->completion_index; i < len; i++) { + readline_insert_char(rs, rs->completions[0][i]); + } + /* extra space for next argument. XXX: make it more generic */ + if (len > 0 && rs->completions[0][len - 1] != '/') + readline_insert_char(rs, ' '); + } else { + rs->printf_func(rs->opaque, "\n"); + max_width = 0; + max_prefix = 0; + for(i = 0; i < rs->nb_completions; i++) { + len = strlen(rs->completions[i]); + if (i==0) { + max_prefix = len; + } else { + if (len < max_prefix) + max_prefix = len; + for(j=0; jcompletions[i][j] != rs->completions[0][j]) + max_prefix = j; + } + } + if (len > max_width) + max_width = len; + } + if (max_prefix > 0) + for(i = rs->completion_index; i < max_prefix; i++) { + readline_insert_char(rs, rs->completions[0][i]); + } + max_width += 2; + if (max_width < 10) + max_width = 10; + else if (max_width > 80) + max_width = 80; + nb_cols = 80 / max_width; + j = 0; + for(i = 0; i < rs->nb_completions; i++) { + rs->printf_func(rs->opaque, "%-*s", max_width, rs->completions[i]); + if (++j == nb_cols || i == (rs->nb_completions - 1)) { + rs->printf_func(rs->opaque, "\n"); + j = 0; + } + } + readline_show_prompt(rs); + } + for (i = 0; i < rs->nb_completions; i++) { + g_free(rs->completions[i]); + } +} + +/* return true if command handled */ +void readline_handle_byte(ReadLineState *rs, int ch) +{ + switch(rs->esc_state) { + case IS_NORM: + switch(ch) { + case 1: + readline_bol(rs); + break; + case 4: + readline_delete_char(rs); + break; + case 5: + readline_eol(rs); + break; + case 9: + readline_completion(rs); + break; + case 10: + case 13: + rs->cmd_buf[rs->cmd_buf_size] = '\0'; + if (!rs->read_password) + readline_hist_add(rs, rs->cmd_buf); + rs->printf_func(rs->opaque, "\n"); + rs->cmd_buf_index = 0; + rs->cmd_buf_size = 0; + rs->last_cmd_buf_index = 0; + rs->last_cmd_buf_size = 0; + rs->readline_func(rs->opaque, rs->cmd_buf, rs->readline_opaque); + break; + case 23: + /* ^W */ + readline_backword(rs); + break; + case 27: + rs->esc_state = IS_ESC; + break; + case 127: + case 8: + readline_backspace(rs); + break; + case 155: + rs->esc_state = IS_CSI; + break; + default: + if (ch >= 32) { + readline_insert_char(rs, ch); + } + break; + } + break; + case IS_ESC: + if (ch == '[') { + rs->esc_state = IS_CSI; + rs->esc_param = 0; + } else if (ch == 'O') { + rs->esc_state = IS_SS3; + rs->esc_param = 0; + } else { + rs->esc_state = IS_NORM; + } + break; + case IS_CSI: + switch(ch) { + case 'A': + case 'F': + readline_up_char(rs); + break; + case 'B': + case 'E': + readline_down_char(rs); + break; + case 'D': + readline_backward_char(rs); + break; + case 'C': + readline_forward_char(rs); + break; + case '0' ... '9': + rs->esc_param = rs->esc_param * 10 + (ch - '0'); + goto the_end; + case '~': + switch(rs->esc_param) { + case 1: + readline_bol(rs); + break; + case 3: + readline_delete_char(rs); + break; + case 4: + readline_eol(rs); + break; + } + break; + default: + break; + } + rs->esc_state = IS_NORM; + the_end: + break; + case IS_SS3: + switch(ch) { + case 'F': + readline_eol(rs); + break; + case 'H': + readline_bol(rs); + break; + } + rs->esc_state = IS_NORM; + break; + } + readline_update(rs); +} + +void readline_start(ReadLineState *rs, const char *prompt, int read_password, + ReadLineFunc *readline_func, void *opaque) +{ + pstrcpy(rs->prompt, sizeof(rs->prompt), prompt); + rs->readline_func = readline_func; + rs->readline_opaque = opaque; + rs->read_password = read_password; + readline_restart(rs); +} + +void readline_restart(ReadLineState *rs) +{ + rs->cmd_buf_index = 0; + rs->cmd_buf_size = 0; +} + +const char *readline_get_history(ReadLineState *rs, unsigned int index) +{ + if (index >= READLINE_MAX_CMDS) + return NULL; + return rs->history[index]; +} + +ReadLineState *readline_init(ReadLinePrintfFunc *printf_func, + ReadLineFlushFunc *flush_func, + void *opaque, + ReadLineCompletionFunc *completion_finder) +{ + ReadLineState *rs = g_malloc0(sizeof(*rs)); + + rs->hist_entry = -1; + rs->opaque = opaque; + rs->printf_func = printf_func; + rs->flush_func = flush_func; + rs->completion_finder = completion_finder; + + return rs; +} -- cgit 1.4.1 From 13401ba0b982024b62a99388032bbb889dc98b43 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 14 Nov 2013 11:54:16 +0100 Subject: osdep: add qemu_set_tty_echo() Using stdin with readline.c requires disabling echo and line buffering. Add a portable wrapper to set the terminal attributes under Linux and Windows. Signed-off-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- include/qemu/osdep.h | 2 ++ util/oslib-posix.c | 18 ++++++++++++++++++ util/oslib-win32.c | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+) (limited to 'include') diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h index b3e2b6d8ea..eac7172bcb 100644 --- a/include/qemu/osdep.h +++ b/include/qemu/osdep.h @@ -240,4 +240,6 @@ static inline void qemu_init_auxval(char **envp) { } void qemu_init_auxval(char **envp); #endif +void qemu_set_tty_echo(int fd, bool echo); + #endif diff --git a/util/oslib-posix.c b/util/oslib-posix.c index e00a44c86f..f5c401646f 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -47,6 +47,9 @@ extern int daemon(int, int); # define QEMU_VMALLOC_ALIGN getpagesize() #endif +#include +#include + #include #include "config-host.h" @@ -251,3 +254,18 @@ qemu_get_local_state_pathname(const char *relative_pathname) return g_strdup_printf("%s/%s", CONFIG_QEMU_LOCALSTATEDIR, relative_pathname); } + +void qemu_set_tty_echo(int fd, bool echo) +{ + struct termios tty; + + tcgetattr(fd, &tty); + + if (echo) { + tty.c_lflag |= ECHO | ECHONL | ICANON | IEXTEN; + } else { + tty.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); + } + + tcsetattr(fd, TCSANOW, &tty); +} diff --git a/util/oslib-win32.c b/util/oslib-win32.c index 776ccfaaf0..50be0440f2 100644 --- a/util/oslib-win32.c +++ b/util/oslib-win32.c @@ -189,3 +189,22 @@ qemu_get_local_state_pathname(const char *relative_pathname) return g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", base_path, relative_pathname); } + +void qemu_set_tty_echo(int fd, bool echo) +{ + HANDLE handle = (HANDLE)_get_osfhandle(fd); + DWORD dwMode = 0; + + if (handle == INVALID_HANDLE_VALUE) { + return; + } + + GetConsoleMode(handle, &dwMode); + + if (echo) { + SetConsoleMode(handle, dwMode | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + } else { + SetConsoleMode(handle, + dwMode & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT)); + } +} -- cgit 1.4.1 From 4694020d3c0d21f02408d5cc6f44b8fb55b4ee15 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 14 Nov 2013 11:54:18 +0100 Subject: qemu-io: add command completion Autocomplete qemu-io commands at the interactive prompt. Note this only completes command names and not their options. Signed-off-by: Stefan Hajnoczi Signed-off-by: Kevin Wolf --- include/qemu-io.h | 3 +++ qemu-io-cmds.c | 15 +++++++++++++++ qemu-io.c | 8 +++++++- 3 files changed, 25 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/qemu-io.h b/include/qemu-io.h index a418b46a40..7e7c07c09b 100644 --- a/include/qemu-io.h +++ b/include/qemu-io.h @@ -42,5 +42,8 @@ bool qemuio_command(BlockDriverState *bs, const char *cmd); void qemuio_add_command(const cmdinfo_t *ci); int qemuio_command_usage(const cmdinfo_t *ci); +void qemuio_complete_command(const char *input, + void (*fn)(const char *cmd, void *opaque), + void *opaque); #endif /* QEMU_IO_H */ diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c index 85e4982bd8..6dfb4a51ae 100644 --- a/qemu-io-cmds.c +++ b/qemu-io-cmds.c @@ -94,6 +94,21 @@ static const cmdinfo_t *find_command(const char *cmd) return NULL; } +/* Invoke fn() for commands with a matching prefix */ +void qemuio_complete_command(const char *input, + void (*fn)(const char *cmd, void *opaque), + void *opaque) +{ + cmdinfo_t *ct; + size_t input_len = strlen(input); + + for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) { + if (strncmp(input, ct->name, input_len) == 0) { + fn(ct->name, opaque); + } + } +} + static char **breakline(char *input, int *count) { int c = 0; diff --git a/qemu-io.c b/qemu-io.c index d7c26d3ed0..fdc46a97e8 100644 --- a/qemu-io.c +++ b/qemu-io.c @@ -236,9 +236,15 @@ static void readline_func(void *opaque, const char *str, void *readline_opaque) *line = g_strdup(str); } +static void completion_match(const char *cmd, void *opaque) +{ + readline_add_completion(readline_state, cmd); +} + static void readline_completion_func(void *opaque, const char *str) { - /* No command or argument completion implemented yet */ + readline_set_completion_index(readline_state, strlen(str)); + qemuio_complete_command(str, completion_match, NULL); } static char *fetchline_readline(void) -- cgit 1.4.1 From 05a8c2227157eda2540404999c4615d3bf343c18 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Fri, 20 Dec 2013 19:28:03 +0100 Subject: qdict: Add qdict_array_split() This function splits a QDict consisting of entries prefixed by incrementally enumerated indices into a QList of QDicts. Signed-off-by: Max Reitz Reviewed-by: Kevin Wolf Reviewed-by: Eric Blake Signed-off-by: Kevin Wolf --- include/qapi/qmp/qdict.h | 1 + qobject/qdict.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) (limited to 'include') diff --git a/include/qapi/qmp/qdict.h b/include/qapi/qmp/qdict.h index 5cefd8022a..1ddf97b1c3 100644 --- a/include/qapi/qmp/qdict.h +++ b/include/qapi/qmp/qdict.h @@ -68,5 +68,6 @@ QDict *qdict_clone_shallow(const QDict *src); void qdict_flatten(QDict *qdict); void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start); +void qdict_array_split(QDict *src, QList **dst); #endif /* QDICT_H */ diff --git a/qobject/qdict.c b/qobject/qdict.c index 17e14f08b1..2d5848d7d7 100644 --- a/qobject/qdict.c +++ b/qobject/qdict.c @@ -554,3 +554,40 @@ void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start) entry = next; } } + +/** + * qdict_array_split(): This function moves array-like elements of a QDict into + * a new QList of QDicts. Every entry in the original QDict with a key prefixed + * "%u.", where %u designates an unsigned integer starting at 0 and + * incrementally counting up, will be moved to a new QDict at index %u in the + * output QList with the key prefix removed. The function terminates when there + * is no entry in the QDict with a prefix directly (incrementally) following the + * last one. + * Example: {"0.a": 42, "0.b": 23, "1.x": 0, "3.y": 1, "o.o": 7} + * (or {"1.x": 0, "3.y": 1, "0.a": 42, "o.o": 7, "0.b": 23}) + * => [{"a": 42, "b": 23}, {"x": 0}] + * and {"3.y": 1, "o.o": 7} (remainder of the old QDict) + */ +void qdict_array_split(QDict *src, QList **dst) +{ + unsigned i; + + *dst = qlist_new(); + + for (i = 0; i < UINT_MAX; i++) { + QDict *subqdict; + char prefix[32]; + size_t snprintf_ret; + + snprintf_ret = snprintf(prefix, 32, "%u.", i); + assert(snprintf_ret < 32); + + qdict_extract_subqdict(src, &subqdict, prefix); + if (!qdict_size(subqdict)) { + QDECREF(subqdict); + break; + } + + qlist_append_obj(*dst, QOBJECT(subqdict)); + } +} -- cgit 1.4.1 From adf5c449e5beb163999e4ba7366d5f9aebb504a1 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Fri, 20 Dec 2013 19:28:05 +0100 Subject: qemu-option: Add qemu_config_parse_qdict() This function basically parses command-line options given as a QDict replacing a config file. For instance, the QDict {"section.opt1": 42, "section.opt2": 23} corresponds to the config file: [section] opt1 = 42 opt2 = 23 It is possible to specify multiple sections and also multiple sections of the same type. On the command line, this looks like the following: inject-error.0.event=reftable_load,\ inject-error.1.event=l2_load,\ set-state.event=l1_update This would correspond to the following config file: [inject-error "inject-error.0"] event = reftable_load [inject-error "inject-error.1"] event = l2_load [set-state] event = l1_update Signed-off-by: Max Reitz Signed-off-by: Kevin Wolf --- include/qemu/config-file.h | 6 +++ util/qemu-config.c | 100 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) (limited to 'include') diff --git a/include/qemu/config-file.h b/include/qemu/config-file.h index 508428ff32..dbd97c4bdb 100644 --- a/include/qemu/config-file.h +++ b/include/qemu/config-file.h @@ -4,6 +4,7 @@ #include #include "qemu/option.h" #include "qapi/error.h" +#include "qapi/qmp/qdict.h" QemuOptsList *qemu_find_opts(const char *group); QemuOptsList *qemu_find_opts_err(const char *group, Error **errp); @@ -18,6 +19,11 @@ int qemu_config_parse(FILE *fp, QemuOptsList **lists, const char *fname); int qemu_read_config_file(const char *filename); +/* Parse QDict options as a replacement for a config file (allowing multiple + enumerated (0..(n-1)) configuration "sections") */ +void qemu_config_parse_qdict(QDict *options, QemuOptsList **lists, + Error **errp); + /* Read default QEMU config files */ int qemu_read_default_config_files(bool userconfig); diff --git a/util/qemu-config.c b/util/qemu-config.c index 7973659518..9298f55ecf 100644 --- a/util/qemu-config.c +++ b/util/qemu-config.c @@ -356,3 +356,103 @@ int qemu_read_config_file(const char *filename) return -EINVAL; } } + +static void config_parse_qdict_section(QDict *options, QemuOptsList *opts, + Error **errp) +{ + QemuOpts *subopts; + QDict *subqdict; + QList *list = NULL; + Error *local_err = NULL; + size_t orig_size, enum_size; + char *prefix; + + prefix = g_strdup_printf("%s.", opts->name); + qdict_extract_subqdict(options, &subqdict, prefix); + g_free(prefix); + orig_size = qdict_size(subqdict); + if (!orig_size) { + goto out; + } + + subopts = qemu_opts_create(opts, NULL, 0, &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + goto out; + } + + qemu_opts_absorb_qdict(subopts, subqdict, &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + goto out; + } + + enum_size = qdict_size(subqdict); + if (enum_size < orig_size && enum_size) { + error_setg(errp, "Unknown option '%s' for [%s]", + qdict_first(subqdict)->key, opts->name); + goto out; + } + + if (enum_size) { + /* Multiple, enumerated sections */ + QListEntry *list_entry; + unsigned i = 0; + + /* Not required anymore */ + qemu_opts_del(subopts); + + qdict_array_split(subqdict, &list); + if (qdict_size(subqdict)) { + error_setg(errp, "Unused option '%s' for [%s]", + qdict_first(subqdict)->key, opts->name); + goto out; + } + + QLIST_FOREACH_ENTRY(list, list_entry) { + QDict *section = qobject_to_qdict(qlist_entry_obj(list_entry)); + char *opt_name; + + opt_name = g_strdup_printf("%s.%u", opts->name, i++); + subopts = qemu_opts_create(opts, opt_name, 1, &local_err); + g_free(opt_name); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + goto out; + } + + qemu_opts_absorb_qdict(subopts, section, &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + qemu_opts_del(subopts); + goto out; + } + + if (qdict_size(section)) { + error_setg(errp, "[%s] section doesn't support the option '%s'", + opts->name, qdict_first(section)->key); + qemu_opts_del(subopts); + goto out; + } + } + } + +out: + QDECREF(subqdict); + QDECREF(list); +} + +void qemu_config_parse_qdict(QDict *options, QemuOptsList **lists, + Error **errp) +{ + int i; + Error *local_err = NULL; + + for (i = 0; lists[i]; i++) { + config_parse_qdict_section(options, lists[i], &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + return; + } + } +} -- cgit 1.4.1 From 72daa72eeecb6b2ee06ab7d836ac3aa01ad7e6df Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Fri, 20 Dec 2013 19:28:08 +0100 Subject: block: Allow reference for bdrv_file_open() Allow specifying a reference to an existing block device (by name) for bdrv_file_open() instead of a filename and/or options. Signed-off-by: Max Reitz Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block.c | 25 ++++++++++++++++++++++--- block/blkdebug.c | 2 +- block/blkverify.c | 2 +- block/cow.c | 3 ++- block/qcow.c | 3 ++- block/qcow2.c | 2 +- block/qed.c | 4 ++-- block/sheepdog.c | 4 ++-- block/vhdx.c | 2 +- block/vmdk.c | 8 ++++---- include/block/block.h | 3 ++- qemu-io.c | 2 +- 12 files changed, 41 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 64e7d220c6..1e53b3dffd 100644 --- a/block.c +++ b/block.c @@ -858,9 +858,10 @@ free_and_fail: * dictionary, it needs to use QINCREF() before calling bdrv_file_open. */ int bdrv_file_open(BlockDriverState **pbs, const char *filename, - QDict *options, int flags, Error **errp) + const char *reference, QDict *options, int flags, + Error **errp) { - BlockDriverState *bs; + BlockDriverState *bs = NULL; BlockDriver *drv; const char *drvname; bool allow_protocol_prefix = false; @@ -872,6 +873,24 @@ int bdrv_file_open(BlockDriverState **pbs, const char *filename, options = qdict_new(); } + if (reference) { + if (filename || qdict_size(options)) { + error_setg(errp, "Cannot reference an existing block device with " + "additional options or a new filename"); + return -EINVAL; + } + QDECREF(options); + + bs = bdrv_find(reference); + if (!bs) { + error_setg(errp, "Cannot find block device '%s'", reference); + return -ENODEV; + } + bdrv_ref(bs); + *pbs = bs; + return 0; + } + bs = bdrv_new(""); bs->options = options; options = qdict_clone_shallow(options); @@ -1124,7 +1143,7 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options, qdict_extract_subqdict(options, &file_options, "file."); - ret = bdrv_file_open(&file, filename, file_options, + ret = bdrv_file_open(&file, filename, NULL, file_options, bdrv_open_flags(bs, flags | BDRV_O_UNMAP), &local_err); if (ret < 0) { goto fail; diff --git a/block/blkdebug.c b/block/blkdebug.c index 0bf3bb518a..21a4931594 100644 --- a/block/blkdebug.c +++ b/block/blkdebug.c @@ -403,7 +403,7 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags, goto fail; } - ret = bdrv_file_open(&bs->file, filename, NULL, flags, &local_err); + ret = bdrv_file_open(&bs->file, filename, NULL, NULL, flags, &local_err); if (ret < 0) { error_propagate(errp, local_err); goto fail; diff --git a/block/blkverify.c b/block/blkverify.c index 1c1637f55e..e15ac4ca69 100644 --- a/block/blkverify.c +++ b/block/blkverify.c @@ -141,7 +141,7 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags, goto fail; } - ret = bdrv_file_open(&bs->file, raw, NULL, flags, &local_err); + ret = bdrv_file_open(&bs->file, raw, NULL, NULL, flags, &local_err); if (ret < 0) { error_propagate(errp, local_err); goto fail; diff --git a/block/cow.c b/block/cow.c index dc15e46b6c..7fc0b12163 100644 --- a/block/cow.c +++ b/block/cow.c @@ -351,7 +351,8 @@ static int cow_create(const char *filename, QEMUOptionParameter *options, return ret; } - ret = bdrv_file_open(&cow_bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&cow_bs, filename, NULL, NULL, BDRV_O_RDWR, + &local_err); if (ret < 0) { qerror_report_err(local_err); error_free(local_err); diff --git a/block/qcow.c b/block/qcow.c index c470e05f60..948b0c5601 100644 --- a/block/qcow.c +++ b/block/qcow.c @@ -691,7 +691,8 @@ static int qcow_create(const char *filename, QEMUOptionParameter *options, return ret; } - ret = bdrv_file_open(&qcow_bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&qcow_bs, filename, NULL, NULL, BDRV_O_RDWR, + &local_err); if (ret < 0) { qerror_report_err(local_err); error_free(local_err); diff --git a/block/qcow2.c b/block/qcow2.c index 8ec9db10f8..e15a4dd057 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1483,7 +1483,7 @@ static int qcow2_create2(const char *filename, int64_t total_size, return ret; } - ret = bdrv_file_open(&bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&bs, filename, NULL, NULL, BDRV_O_RDWR, &local_err); if (ret < 0) { error_propagate(errp, local_err); return ret; diff --git a/block/qed.c b/block/qed.c index 450a1fa2e9..0dd5c5859e 100644 --- a/block/qed.c +++ b/block/qed.c @@ -563,8 +563,8 @@ static int qed_create(const char *filename, uint32_t cluster_size, return ret; } - ret = bdrv_file_open(&bs, filename, NULL, BDRV_O_RDWR | BDRV_O_CACHE_WB, - &local_err); + ret = bdrv_file_open(&bs, filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_CACHE_WB, &local_err); if (ret < 0) { qerror_report_err(local_err); error_free(local_err); diff --git a/block/sheepdog.c b/block/sheepdog.c index 6088fa5571..2ce3d9b9fb 100644 --- a/block/sheepdog.c +++ b/block/sheepdog.c @@ -1534,7 +1534,7 @@ static int sd_prealloc(const char *filename) Error *local_err = NULL; int ret; - ret = bdrv_file_open(&bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&bs, filename, NULL, NULL, BDRV_O_RDWR, &local_err); if (ret < 0) { qerror_report_err(local_err); error_free(local_err); @@ -1695,7 +1695,7 @@ static int sd_create(const char *filename, QEMUOptionParameter *options, goto out; } - ret = bdrv_file_open(&bs, backing_file, NULL, 0, &local_err); + ret = bdrv_file_open(&bs, backing_file, NULL, NULL, 0, &local_err); if (ret < 0) { qerror_report_err(local_err); error_free(local_err); diff --git a/block/vhdx.c b/block/vhdx.c index 1995778945..9ee0a612ff 100644 --- a/block/vhdx.c +++ b/block/vhdx.c @@ -1797,7 +1797,7 @@ static int vhdx_create(const char *filename, QEMUOptionParameter *options, goto exit; } - ret = bdrv_file_open(&bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&bs, filename, NULL, NULL, BDRV_O_RDWR, &local_err); if (ret < 0) { error_propagate(errp, local_err); goto exit; diff --git a/block/vmdk.c b/block/vmdk.c index 22b99b04ac..599a928545 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -769,8 +769,8 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, path_combine(extent_path, sizeof(extent_path), desc_file_path, fname); - ret = bdrv_file_open(&extent_file, extent_path, NULL, bs->open_flags, - errp); + ret = bdrv_file_open(&extent_file, extent_path, NULL, NULL, + bs->open_flags, errp); if (ret) { return ret; } @@ -1469,7 +1469,7 @@ static int vmdk_create_extent(const char *filename, int64_t filesize, goto exit; } - ret = bdrv_file_open(&bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&bs, filename, NULL, NULL, BDRV_O_RDWR, &local_err); if (ret < 0) { error_propagate(errp, local_err); goto exit; @@ -1807,7 +1807,7 @@ static int vmdk_create(const char *filename, QEMUOptionParameter *options, goto exit; } } - ret = bdrv_file_open(&new_bs, filename, NULL, BDRV_O_RDWR, &local_err); + ret = bdrv_file_open(&new_bs, filename, NULL, NULL, BDRV_O_RDWR, &local_err); if (ret < 0) { error_setg_errno(errp, -ret, "Could not write description"); goto exit; diff --git a/include/block/block.h b/include/block/block.h index 36efaeac2d..e2b2a15f9a 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -184,7 +184,8 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top); int bdrv_parse_cache_flags(const char *mode, int *flags); int bdrv_parse_discard_flags(const char *mode, int *flags); int bdrv_file_open(BlockDriverState **pbs, const char *filename, - QDict *options, int flags, Error **errp); + const char *reference, QDict *options, int flags, + Error **errp); int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp); int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options, int flags, BlockDriver *drv, Error **errp); diff --git a/qemu-io.c b/qemu-io.c index fdc46a97e8..bfb773e70f 100644 --- a/qemu-io.c +++ b/qemu-io.c @@ -59,7 +59,7 @@ static int openfile(char *name, int flags, int growable, QDict *opts) } if (growable) { - if (bdrv_file_open(&qemuio_bs, name, opts, flags, &local_err)) { + if (bdrv_file_open(&qemuio_bs, name, NULL, opts, flags, &local_err)) { fprintf(stderr, "%s: can't open device %s: %s\n", progname, name, error_get_pretty(local_err)); error_free(local_err); -- cgit 1.4.1 From da557aac181fa71fde6a2a7c7a1eb2aea20caf64 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Fri, 20 Dec 2013 19:28:11 +0100 Subject: block: Add bdrv_open_image() Add a common function for opening images to be used for block drivers specified through BlockdevRefs in an option QDict. The difference from bdrv_file_open() is that this function may invoke bdrv_open() instead, allowing auto-detection of the driver to be used; and second, it automatically extracts the BlockdevRef from the option QDict. Signed-off-by: Max Reitz Signed-off-by: Kevin Wolf --- block.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ include/block/block.h | 3 +++ 2 files changed, 76 insertions(+) (limited to 'include') diff --git a/block.c b/block.c index 7464fb237e..76b6c25d90 100644 --- a/block.c +++ b/block.c @@ -1040,6 +1040,79 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp) return 0; } +/* + * Opens a disk image whose options are given as BlockdevRef in another block + * device's options. + * + * If force_raw is true, bdrv_file_open() will be used, thereby preventing any + * image format auto-detection. If it is false and a filename is given, + * bdrv_open() will be used for auto-detection. + * + * If allow_none is true, no image will be opened if filename is false and no + * BlockdevRef is given. *pbs will remain unchanged and 0 will be returned. + * + * bdrev_key specifies the key for the image's BlockdevRef in the options QDict. + * That QDict has to be flattened; therefore, if the BlockdevRef is a QDict + * itself, all options starting with "${bdref_key}." are considered part of the + * BlockdevRef. + * + * The BlockdevRef will be removed from the options QDict. + */ +int bdrv_open_image(BlockDriverState **pbs, const char *filename, + QDict *options, const char *bdref_key, int flags, + bool force_raw, bool allow_none, Error **errp) +{ + QDict *image_options; + int ret; + char *bdref_key_dot; + const char *reference; + + bdref_key_dot = g_strdup_printf("%s.", bdref_key); + qdict_extract_subqdict(options, &image_options, bdref_key_dot); + g_free(bdref_key_dot); + + reference = qdict_get_try_str(options, bdref_key); + if (!filename && !reference && !qdict_size(image_options)) { + if (allow_none) { + ret = 0; + } else { + error_setg(errp, "A block device must be specified for \"%s\"", + bdref_key); + ret = -EINVAL; + } + goto done; + } + + if (filename && !force_raw) { + /* If a filename is given and the block driver should be detected + automatically (instead of using none), use bdrv_open() in order to do + that auto-detection. */ + BlockDriverState *bs; + + if (reference) { + error_setg(errp, "Cannot reference an existing block device while " + "giving a filename"); + ret = -EINVAL; + goto done; + } + + bs = bdrv_new(""); + ret = bdrv_open(bs, filename, image_options, flags, NULL, errp); + if (ret < 0) { + bdrv_unref(bs); + } else { + *pbs = bs; + } + } else { + ret = bdrv_file_open(pbs, filename, reference, image_options, flags, + errp); + } + +done: + qdict_del(options, bdref_key); + return ret; +} + /* * Opens a disk image (raw, qcow2, vmdk, ...) * diff --git a/include/block/block.h b/include/block/block.h index e2b2a15f9a..a47f3d4988 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -186,6 +186,9 @@ int bdrv_parse_discard_flags(const char *mode, int *flags); int bdrv_file_open(BlockDriverState **pbs, const char *filename, const char *reference, QDict *options, int flags, Error **errp); +int bdrv_open_image(BlockDriverState **pbs, const char *filename, + QDict *options, const char *bdref_key, int flags, + bool force_raw, bool allow_none, Error **errp); int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp); int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options, int flags, BlockDriver *drv, Error **errp); -- cgit 1.4.1 From dc364f4cdca0c49e37376b16c3ee0bf3b4a96f4c Mon Sep 17 00:00:00 2001 From: Benoît Canet Date: Thu, 23 Jan 2014 21:31:32 +0100 Subject: block: Add bs->node_name to hold the name of a bs node of the bs graph. Add the minimum of code to prepare for the following patches. Signed-off-by: Benoit Canet Reviewed-by: Fam Zheng Signed-off-by: Kevin Wolf --- block.c | 57 +++++++++++++++++++++++++++++++++++------------ include/block/block.h | 1 + include/block/block_int.h | 9 +++++++- 3 files changed, 52 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 53cc9e09c2..8562685c17 100644 --- a/block.c +++ b/block.c @@ -90,6 +90,9 @@ static int coroutine_fn bdrv_co_do_write_zeroes(BlockDriverState *bs, static QTAILQ_HEAD(, BlockDriverState) bdrv_states = QTAILQ_HEAD_INITIALIZER(bdrv_states); +static QTAILQ_HEAD(, BlockDriverState) graph_bdrv_states = + QTAILQ_HEAD_INITIALIZER(graph_bdrv_states); + static QLIST_HEAD(, BlockDriver) bdrv_drivers = QLIST_HEAD_INITIALIZER(bdrv_drivers); @@ -327,7 +330,7 @@ BlockDriverState *bdrv_new(const char *device_name) QLIST_INIT(&bs->dirty_bitmaps); pstrcpy(bs->device_name, sizeof(bs->device_name), device_name); if (device_name[0] != '\0') { - QTAILQ_INSERT_TAIL(&bdrv_states, bs, list); + QTAILQ_INSERT_TAIL(&bdrv_states, bs, device_list); } bdrv_iostatus_disable(bs); notifier_list_init(&bs->close_notifiers); @@ -1606,7 +1609,7 @@ void bdrv_close_all(void) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { bdrv_close(bs); } } @@ -1635,7 +1638,7 @@ static bool bdrv_requests_pending(BlockDriverState *bs) static bool bdrv_requests_pending_all(void) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { if (bdrv_requests_pending(bs)) { return true; } @@ -1662,7 +1665,7 @@ void bdrv_drain_all(void) BlockDriverState *bs; while (busy) { - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { bdrv_start_throttled_reqs(bs); } @@ -1671,14 +1674,19 @@ void bdrv_drain_all(void) } } -/* make a BlockDriverState anonymous by removing from bdrv_state list. +/* make a BlockDriverState anonymous by removing from bdrv_state and + * graph_bdrv_state list. Also, NULL terminate the device_name to prevent double remove */ void bdrv_make_anon(BlockDriverState *bs) { if (bs->device_name[0] != '\0') { - QTAILQ_REMOVE(&bdrv_states, bs, list); + QTAILQ_REMOVE(&bdrv_states, bs, device_list); } bs->device_name[0] = '\0'; + if (bs->node_name[0] != '\0') { + QTAILQ_REMOVE(&graph_bdrv_states, bs, node_list); + } + bs->node_name[0] = '\0'; } static void bdrv_rebind(BlockDriverState *bs) @@ -1732,7 +1740,12 @@ static void bdrv_move_feature_fields(BlockDriverState *bs_dest, /* keep the same entry in bdrv_states */ pstrcpy(bs_dest->device_name, sizeof(bs_dest->device_name), bs_src->device_name); - bs_dest->list = bs_src->list; + bs_dest->device_list = bs_src->device_list; + + /* keep the same entry in graph_bdrv_states + * We do want to swap name but don't want to swap linked list entries + */ + bs_dest->node_list = bs_src->node_list; } /* @@ -2057,7 +2070,7 @@ int bdrv_commit_all(void) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { if (bs->drv && bs->backing_hd) { int ret = bdrv_commit(bs); if (ret < 0) { @@ -3215,11 +3228,12 @@ void bdrv_iterate_format(void (*it)(void *opaque, const char *name), } } +/* This function is to find block backend bs */ BlockDriverState *bdrv_find(const char *name) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { if (!strcmp(name, bs->device_name)) { return bs; } @@ -3227,19 +3241,34 @@ BlockDriverState *bdrv_find(const char *name) return NULL; } +/* This function is to find a node in the bs graph */ +BlockDriverState *bdrv_find_node(const char *node_name) +{ + BlockDriverState *bs; + + assert(node_name); + + QTAILQ_FOREACH(bs, &graph_bdrv_states, node_list) { + if (!strcmp(node_name, bs->node_name)) { + return bs; + } + } + return NULL; +} + BlockDriverState *bdrv_next(BlockDriverState *bs) { if (!bs) { return QTAILQ_FIRST(&bdrv_states); } - return QTAILQ_NEXT(bs, list); + return QTAILQ_NEXT(bs, device_list); } void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), void *opaque) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { it(opaque, bs); } } @@ -3259,7 +3288,7 @@ int bdrv_flush_all(void) BlockDriverState *bs; int result = 0; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { int ret = bdrv_flush(bs); if (ret < 0 && !result) { result = ret; @@ -4383,7 +4412,7 @@ void bdrv_invalidate_cache_all(void) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { bdrv_invalidate_cache(bs); } } @@ -4392,7 +4421,7 @@ void bdrv_clear_incoming_migration_all(void) { BlockDriverState *bs; - QTAILQ_FOREACH(bs, &bdrv_states, list) { + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { bs->open_flags = bs->open_flags & ~(BDRV_O_INCOMING); } } diff --git a/include/block/block.h b/include/block/block.h index a47f3d4988..501b555885 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -378,6 +378,7 @@ void bdrv_lock_medium(BlockDriverState *bs, bool locked); void bdrv_eject(BlockDriverState *bs, bool eject_flag); const char *bdrv_get_format_name(BlockDriverState *bs); BlockDriverState *bdrv_find(const char *name); +BlockDriverState *bdrv_find_node(const char *node_name); BlockDriverState *bdrv_next(BlockDriverState *bs); void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), void *opaque); diff --git a/include/block/block_int.h b/include/block/block_int.h index 2772f2f1bd..f3f518c4d2 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -325,11 +325,18 @@ struct BlockDriverState { BlockdevOnError on_read_error, on_write_error; bool iostatus_enabled; BlockDeviceIoStatus iostatus; + + /* the following member gives a name to every node on the bs graph. */ + char node_name[32]; + /* element of the list of named nodes building the graph */ + QTAILQ_ENTRY(BlockDriverState) node_list; + /* Device name is the name associated with the "drive" the guest sees */ char device_name[32]; + /* element of the list of "drives" the guest sees */ + QTAILQ_ENTRY(BlockDriverState) device_list; QLIST_HEAD(, BdrvDirtyBitmap) dirty_bitmaps; int refcnt; int in_use; /* users other than guest access, eg. block migration */ - QTAILQ_ENTRY(BlockDriverState) list; QLIST_HEAD(, BdrvTrackedRequest) tracked_requests; -- cgit 1.4.1 From c13163fba151f0be5176eaf55907bc1dbff3a1d4 Mon Sep 17 00:00:00 2001 From: Benoît Canet Date: Thu, 23 Jan 2014 21:31:34 +0100 Subject: qmp: Add QMP query-named-block-nodes to list the named BlockDriverState nodes. Signed-off-by: Benoit Canet Reviewed-by: Fam Zheng Signed-off-by: Kevin Wolf --- block.c | 18 +++++++++ block/qapi.c | 109 +++++++++++++++++++++++++------------------------- blockdev.c | 5 +++ include/block/block.h | 1 + include/block/qapi.h | 1 + qapi-schema.json | 16 +++++++- qmp-commands.hx | 61 ++++++++++++++++++++++++++++ 7 files changed, 155 insertions(+), 56 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index f0436697ff..5f6308df67 100644 --- a/block.c +++ b/block.c @@ -32,6 +32,7 @@ #include "sysemu/sysemu.h" #include "qemu/notify.h" #include "block/coroutine.h" +#include "block/qapi.h" #include "qmp-commands.h" #include "qemu/timer.h" @@ -3291,6 +3292,23 @@ BlockDriverState *bdrv_find_node(const char *node_name) return NULL; } +/* Put this QMP function here so it can access the static graph_bdrv_states. */ +BlockDeviceInfoList *bdrv_named_nodes_list(void) +{ + BlockDeviceInfoList *list, *entry; + BlockDriverState *bs; + + list = NULL; + QTAILQ_FOREACH(bs, &graph_bdrv_states, node_list) { + entry = g_malloc0(sizeof(*entry)); + entry->value = bdrv_block_device_info(bs); + entry->next = list; + list = entry; + } + + return list; +} + BlockDriverState *bdrv_next(BlockDriverState *bs) { if (!bs) { diff --git a/block/qapi.c b/block/qapi.c index 98b1b83bd6..8f4134b40a 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -29,6 +29,60 @@ #include "qapi/qmp-output-visitor.h" #include "qapi/qmp/types.h" +BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs) +{ + BlockDeviceInfo *info = g_malloc0(sizeof(*info)); + + info->file = g_strdup(bs->filename); + info->ro = bs->read_only; + info->drv = g_strdup(bs->drv->format_name); + info->encrypted = bs->encrypted; + info->encryption_key_missing = bdrv_key_required(bs); + + if (bs->node_name[0]) { + info->has_node_name = true; + info->node_name = g_strdup(bs->node_name); + } + + if (bs->backing_file[0]) { + info->has_backing_file = true; + info->backing_file = g_strdup(bs->backing_file); + } + + info->backing_file_depth = bdrv_get_backing_file_depth(bs); + + if (bs->io_limits_enabled) { + ThrottleConfig cfg; + throttle_get_config(&bs->throttle_state, &cfg); + info->bps = cfg.buckets[THROTTLE_BPS_TOTAL].avg; + info->bps_rd = cfg.buckets[THROTTLE_BPS_READ].avg; + info->bps_wr = cfg.buckets[THROTTLE_BPS_WRITE].avg; + + info->iops = cfg.buckets[THROTTLE_OPS_TOTAL].avg; + info->iops_rd = cfg.buckets[THROTTLE_OPS_READ].avg; + info->iops_wr = cfg.buckets[THROTTLE_OPS_WRITE].avg; + + info->has_bps_max = cfg.buckets[THROTTLE_BPS_TOTAL].max; + info->bps_max = cfg.buckets[THROTTLE_BPS_TOTAL].max; + info->has_bps_rd_max = cfg.buckets[THROTTLE_BPS_READ].max; + info->bps_rd_max = cfg.buckets[THROTTLE_BPS_READ].max; + info->has_bps_wr_max = cfg.buckets[THROTTLE_BPS_WRITE].max; + info->bps_wr_max = cfg.buckets[THROTTLE_BPS_WRITE].max; + + info->has_iops_max = cfg.buckets[THROTTLE_OPS_TOTAL].max; + info->iops_max = cfg.buckets[THROTTLE_OPS_TOTAL].max; + info->has_iops_rd_max = cfg.buckets[THROTTLE_OPS_READ].max; + info->iops_rd_max = cfg.buckets[THROTTLE_OPS_READ].max; + info->has_iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max; + info->iops_wr_max = cfg.buckets[THROTTLE_OPS_WRITE].max; + + info->has_iops_size = cfg.op_size; + info->iops_size = cfg.op_size; + } + + return info; +} + /* * Returns 0 on success, with *p_list either set to describe snapshot * information, or NULL because there are no snapshots. Returns -errno on @@ -211,60 +265,7 @@ void bdrv_query_info(BlockDriverState *bs, if (bs->drv) { info->has_inserted = true; - info->inserted = g_malloc0(sizeof(*info->inserted)); - info->inserted->file = g_strdup(bs->filename); - info->inserted->ro = bs->read_only; - info->inserted->drv = g_strdup(bs->drv->format_name); - info->inserted->encrypted = bs->encrypted; - info->inserted->encryption_key_missing = bdrv_key_required(bs); - - if (bs->backing_file[0]) { - info->inserted->has_backing_file = true; - info->inserted->backing_file = g_strdup(bs->backing_file); - } - - info->inserted->backing_file_depth = bdrv_get_backing_file_depth(bs); - - if (bs->io_limits_enabled) { - ThrottleConfig cfg; - throttle_get_config(&bs->throttle_state, &cfg); - info->inserted->bps = cfg.buckets[THROTTLE_BPS_TOTAL].avg; - info->inserted->bps_rd = cfg.buckets[THROTTLE_BPS_READ].avg; - info->inserted->bps_wr = cfg.buckets[THROTTLE_BPS_WRITE].avg; - - info->inserted->iops = cfg.buckets[THROTTLE_OPS_TOTAL].avg; - info->inserted->iops_rd = cfg.buckets[THROTTLE_OPS_READ].avg; - info->inserted->iops_wr = cfg.buckets[THROTTLE_OPS_WRITE].avg; - - info->inserted->has_bps_max = - cfg.buckets[THROTTLE_BPS_TOTAL].max; - info->inserted->bps_max = - cfg.buckets[THROTTLE_BPS_TOTAL].max; - info->inserted->has_bps_rd_max = - cfg.buckets[THROTTLE_BPS_READ].max; - info->inserted->bps_rd_max = - cfg.buckets[THROTTLE_BPS_READ].max; - info->inserted->has_bps_wr_max = - cfg.buckets[THROTTLE_BPS_WRITE].max; - info->inserted->bps_wr_max = - cfg.buckets[THROTTLE_BPS_WRITE].max; - - info->inserted->has_iops_max = - cfg.buckets[THROTTLE_OPS_TOTAL].max; - info->inserted->iops_max = - cfg.buckets[THROTTLE_OPS_TOTAL].max; - info->inserted->has_iops_rd_max = - cfg.buckets[THROTTLE_OPS_READ].max; - info->inserted->iops_rd_max = - cfg.buckets[THROTTLE_OPS_READ].max; - info->inserted->has_iops_wr_max = - cfg.buckets[THROTTLE_OPS_WRITE].max; - info->inserted->iops_wr_max = - cfg.buckets[THROTTLE_OPS_WRITE].max; - - info->inserted->has_iops_size = cfg.op_size; - info->inserted->iops_size = cfg.op_size; - } + info->inserted = bdrv_block_device_info(bs); bs0 = bs; p_image_info = &info->inserted->image; diff --git a/blockdev.c b/blockdev.c index 386109a8d4..0bfe38027b 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1952,6 +1952,11 @@ void qmp_drive_backup(const char *device, const char *target, } } +BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) +{ + return bdrv_named_nodes_list(); +} + #define DEFAULT_MIRROR_BUF_SIZE (10 << 20) void qmp_drive_mirror(const char *device, const char *target, diff --git a/include/block/block.h b/include/block/block.h index 501b555885..13654ede8b 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -379,6 +379,7 @@ void bdrv_eject(BlockDriverState *bs, bool eject_flag); const char *bdrv_get_format_name(BlockDriverState *bs); BlockDriverState *bdrv_find(const char *name); BlockDriverState *bdrv_find_node(const char *node_name); +BlockDeviceInfoList *bdrv_named_nodes_list(void); BlockDriverState *bdrv_next(BlockDriverState *bs); void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), void *opaque); diff --git a/include/block/qapi.h b/include/block/qapi.h index 9518ee4001..e92c00daf6 100644 --- a/include/block/qapi.h +++ b/include/block/qapi.h @@ -29,6 +29,7 @@ #include "block/block.h" #include "block/snapshot.h" +BlockDeviceInfo *bdrv_block_device_info(BlockDriverState *bs); int bdrv_query_snapshot_info_list(BlockDriverState *bs, SnapshotInfoList **p_list, Error **errp); diff --git a/qapi-schema.json b/qapi-schema.json index 26e370b0a0..b619f019ee 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -810,6 +810,8 @@ # # @file: the filename of the backing device # +# @node-name: #optional the name of the block driver node (Since 2.0) +# # @ro: true if the backing device was open read-only # # @drv: the name of the block format used to open the backing device. As of @@ -857,10 +859,9 @@ # # Since: 0.14.0 # -# Notes: This interface is only found in @BlockInfo. ## { 'type': 'BlockDeviceInfo', - 'data': { 'file': 'str', 'ro': 'bool', 'drv': 'str', + 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', '*backing_file': 'str', 'backing_file_depth': 'int', 'encrypted': 'bool', 'encryption_key_missing': 'bool', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', @@ -2010,6 +2011,17 @@ ## { 'command': 'drive-backup', 'data': 'DriveBackup' } +## +# @query-named-block-nodes +# +# Get the named block driver list +# +# Returns: the list of BlockDeviceInfo +# +# Since 2.0 +## +{ 'command': 'query-named-block-nodes', 'returns': [ 'BlockDeviceInfo' ] } + ## # @drive-mirror # diff --git a/qmp-commands.hx b/qmp-commands.hx index 02cc815bc5..11b44c51b7 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -3345,4 +3345,65 @@ Example (2): <- { "return": {} } +EQMP + + { + .name = "query-named-block-nodes", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_named_block_nodes, + }, + +SQMP +@query-named-block-nodes +------------------------ + +Return a list of BlockDeviceInfo for all the named block driver nodes + +Example: + +-> { "execute": "query-named-block-nodes" } +<- { "return": [ { "ro":false, + "drv":"qcow2", + "encrypted":false, + "file":"disks/test.qcow2", + "node-name": "my-node", + "backing_file_depth":1, + "bps":1000000, + "bps_rd":0, + "bps_wr":0, + "iops":1000000, + "iops_rd":0, + "iops_wr":0, + "bps_max": 8000000, + "bps_rd_max": 0, + "bps_wr_max": 0, + "iops_max": 0, + "iops_rd_max": 0, + "iops_wr_max": 0, + "iops_size": 0, + "image":{ + "filename":"disks/test.qcow2", + "format":"qcow2", + "virtual-size":2048000, + "backing_file":"base.qcow2", + "full-backing-filename":"disks/base.qcow2", + "backing-filename-format:"qcow2", + "snapshots":[ + { + "id": "1", + "name": "snapshot1", + "vm-state-size": 0, + "date-sec": 10000200, + "date-nsec": 12, + "vm-clock-sec": 206, + "vm-clock-nsec": 30 + } + ], + "backing-image":{ + "filename":"disks/base.qcow2", + "format":"qcow2", + "virtual-size":2048000 + } + } } ] } + EQMP -- cgit 1.4.1 From 12d3ba821da9f8a039240a8a1bc01e27a12f9c22 Mon Sep 17 00:00:00 2001 From: Benoît Canet Date: Thu, 23 Jan 2014 21:31:35 +0100 Subject: qmp: Allow to change password on named block driver states. Signed-off-by: Benoit Canet Reviewed-by: Fam Zheng There was two candidate ways to implement named node manipulation: 1) { 'command': 'block_passwd', 'data': {'*device': 'str', '*node-name': 'str', 'password': 'str'} } 2) { 'command': 'block_passwd', 'data': {'device': 'str', '*device-is-node': 'bool', 'password': 'str'} } Luiz proposed 1 and says 2 was an abuse of the QMP interface and proposed to rewrite the QMP block interface for 2.0. Luiz does not like in 1 the fact that 2 fields are optional but one of them must be specified leading to an abuse of the QMP semantic. Kevin argumented that 2 what a clear abuse of the device field and would not be practical when reading fast some log file because the user would read "device" and think that a device is manipulated when it's in fact a node name. Documentation of 1 make it pretty clear what to do for the user. Kevin argued that all bs are node including devices ones so 2 does not make sense. Kevin also argued that rewriting the QMP block interface would not make disapear the current one. Kevin pushed the argument that making the QAPI generator compatible with the semantic of the operation would need a rewrite that no one has done yet. A vote has been done on the list to elect the version to use and 1 won. For reference the complete thread is: "[Qemu-devel] [PATCH V4 4/7] qmp: Allow to change password on names block driver states." Signed-off-by: Benoit Canet Signed-off-by: Kevin Wolf --- block.c | 32 ++++++++++++++++++++++++++++++++ blockdev.c | 13 +++++++++---- hmp.c | 2 +- include/block/block.h | 3 +++ qapi-schema.json | 9 +++++++-- qmp-commands.hx | 3 ++- 6 files changed, 54 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 5f6308df67..064df90917 100644 --- a/block.c +++ b/block.c @@ -3309,6 +3309,38 @@ BlockDeviceInfoList *bdrv_named_nodes_list(void) return list; } +BlockDriverState *bdrv_lookup_bs(const char *device, + const char *node_name, + Error **errp) +{ + BlockDriverState *bs = NULL; + + if ((!device && !node_name) || (device && node_name)) { + error_setg(errp, "Use either device or node-name but not both"); + return NULL; + } + + if (device) { + bs = bdrv_find(device); + + if (!bs) { + error_set(errp, QERR_DEVICE_NOT_FOUND, device); + return NULL; + } + + return bs; + } + + bs = bdrv_find_node(node_name); + + if (!bs) { + error_set(errp, QERR_DEVICE_NOT_FOUND, node_name); + return NULL; + } + + return bs; +} + BlockDriverState *bdrv_next(BlockDriverState *bs) { if (!bs) { diff --git a/blockdev.c b/blockdev.c index 0bfe38027b..2f4065e2cd 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1481,14 +1481,19 @@ void qmp_eject(const char *device, bool has_force, bool force, Error **errp) eject_device(bs, force, errp); } -void qmp_block_passwd(const char *device, const char *password, Error **errp) +void qmp_block_passwd(bool has_device, const char *device, + bool has_node_name, const char *node_name, + const char *password, Error **errp) { + Error *local_err = NULL; BlockDriverState *bs; int err; - bs = bdrv_find(device); - if (!bs) { - error_set(errp, QERR_DEVICE_NOT_FOUND, device); + bs = bdrv_lookup_bs(has_device ? device : NULL, + has_node_name ? node_name : NULL, + &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); return; } diff --git a/hmp.c b/hmp.c index 468f97d176..5804a5a2ad 100644 --- a/hmp.c +++ b/hmp.c @@ -871,7 +871,7 @@ void hmp_block_passwd(Monitor *mon, const QDict *qdict) const char *password = qdict_get_str(qdict, "password"); Error *errp = NULL; - qmp_block_passwd(device, password, &errp); + qmp_block_passwd(true, device, false, NULL, password, &errp); hmp_handle_error(mon, &errp); } diff --git a/include/block/block.h b/include/block/block.h index 13654ede8b..b4a77e6cff 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -380,6 +380,9 @@ const char *bdrv_get_format_name(BlockDriverState *bs); BlockDriverState *bdrv_find(const char *name); BlockDriverState *bdrv_find_node(const char *node_name); BlockDeviceInfoList *bdrv_named_nodes_list(void); +BlockDriverState *bdrv_lookup_bs(const char *device, + const char *node_name, + Error **errp); BlockDriverState *bdrv_next(BlockDriverState *bs); void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), void *opaque); diff --git a/qapi-schema.json b/qapi-schema.json index b619f019ee..3f48ae9be4 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1678,7 +1678,11 @@ # determine which ones are encrypted, set the passwords with this command, and # then start the guest with the @cont command. # -# @device: the name of the device to set the password on +# Either @device or @node-name must be set but not both. +# +# @device: #optional the name of the block backend device to set the password on +# +# @node-name: #optional graph node name to set the password on (Since 2.0) # # @password: the password to use for the device # @@ -1692,7 +1696,8 @@ # # Since: 0.14.0 ## -{ 'command': 'block_passwd', 'data': {'device': 'str', 'password': 'str'} } +{ 'command': 'block_passwd', 'data': {'*device': 'str', + '*node-name': 'str', 'password': 'str'} } ## # @balloon: diff --git a/qmp-commands.hx b/qmp-commands.hx index 11b44c51b7..5492eb061b 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -1503,7 +1503,7 @@ EQMP { .name = "block_passwd", - .args_type = "device:B,password:s", + .args_type = "device:s?,node-name:s?,password:s", .mhandler.cmd_new = qmp_marshal_input_block_passwd, }, @@ -1516,6 +1516,7 @@ Set the password of encrypted block devices. Arguments: - "device": device name (json-string) +- "node-name": name in the block driver state graph (json-string) - "password": password (json-string) Example: -- cgit 1.4.1 From 212a5a8f095de9a1624de6b4a589d60688b02747 Mon Sep 17 00:00:00 2001 From: Benoît Canet Date: Thu, 23 Jan 2014 21:31:36 +0100 Subject: block: Create authorizations mechanism for external snapshot and resize. Signed-off-by: Benoit Canet Signed-off-by: Kevin Wolf --- block.c | 65 ++++++++++++++++++++++++++++++++++++++++------- block/blkverify.c | 2 +- blockdev.c | 2 +- include/block/block.h | 20 +++++++-------- include/block/block_int.h | 12 ++++++--- 5 files changed, 77 insertions(+), 24 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 064df90917..18c0a8dd94 100644 --- a/block.c +++ b/block.c @@ -5094,21 +5094,68 @@ int bdrv_amend_options(BlockDriverState *bs, QEMUOptionParameter *options) return bs->drv->bdrv_amend_options(bs, options); } -ExtSnapshotPerm bdrv_check_ext_snapshot(BlockDriverState *bs) +/* Used to recurse on single child block filters. + * Single child block filter will store their child in bs->file. + */ +bool bdrv_generic_is_first_non_filter(BlockDriverState *bs, + BlockDriverState *candidate) { - if (bs->drv->bdrv_check_ext_snapshot) { - return bs->drv->bdrv_check_ext_snapshot(bs); + if (!bs->drv) { + return false; + } + + if (!bs->drv->authorizations[BS_IS_A_FILTER]) { + if (bs == candidate) { + return true; + } else { + return false; + } + } + + if (!bs->drv->authorizations[BS_FILTER_PASS_DOWN]) { + return false; } - if (bs->file && bs->file->drv && bs->file->drv->bdrv_check_ext_snapshot) { - return bs->file->drv->bdrv_check_ext_snapshot(bs); + if (!bs->file) { + return false; + } + + return bdrv_recurse_is_first_non_filter(bs->file, candidate); +} + +bool bdrv_recurse_is_first_non_filter(BlockDriverState *bs, + BlockDriverState *candidate) +{ + if (bs->drv && bs->drv->bdrv_recurse_is_first_non_filter) { + return bs->drv->bdrv_recurse_is_first_non_filter(bs, candidate); } - /* external snapshots are allowed by default */ - return EXT_SNAPSHOT_ALLOWED; + return bdrv_generic_is_first_non_filter(bs, candidate); } -ExtSnapshotPerm bdrv_check_ext_snapshot_forbidden(BlockDriverState *bs) +/* This function checks if the candidate is the first non filter bs down it's + * bs chain. Since we don't have pointers to parents it explore all bs chains + * from the top. Some filters can choose not to pass down the recursion. + */ +bool bdrv_is_first_non_filter(BlockDriverState *candidate) { - return EXT_SNAPSHOT_FORBIDDEN; + BlockDriverState *bs; + + /* walk down the bs forest recursively */ + QTAILQ_FOREACH(bs, &bdrv_states, device_list) { + bool perm; + + if (!bs->file) { + continue; + } + + perm = bdrv_recurse_is_first_non_filter(bs->file, candidate); + + /* candidate is the first non filter */ + if (perm) { + return true; + } + } + + return false; } diff --git a/block/blkverify.c b/block/blkverify.c index a2e8f5f138..cfcbcf41c3 100644 --- a/block/blkverify.c +++ b/block/blkverify.c @@ -404,7 +404,7 @@ static BlockDriver bdrv_blkverify = { .bdrv_aio_writev = blkverify_aio_writev, .bdrv_aio_flush = blkverify_aio_flush, - .bdrv_check_ext_snapshot = bdrv_check_ext_snapshot_forbidden, + .authorizations = { true, false }, }; static void bdrv_blkverify_init(void) diff --git a/blockdev.c b/blockdev.c index 2f4065e2cd..32a356eadc 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1243,7 +1243,7 @@ static void external_snapshot_prepare(BlkTransactionState *common, } } - if (bdrv_check_ext_snapshot(state->old_bs) != EXT_SNAPSHOT_ALLOWED) { + if (!bdrv_is_first_non_filter(state->old_bs)) { error_set(errp, QERR_FEATURE_DISABLED, "snapshot"); return; } diff --git a/include/block/block.h b/include/block/block.h index b4a77e6cff..59d9f12ce4 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -287,16 +287,16 @@ int bdrv_amend_options(BlockDriverState *bs_new, QEMUOptionParameter *options); /* external snapshots */ typedef enum { - EXT_SNAPSHOT_ALLOWED, - EXT_SNAPSHOT_FORBIDDEN, -} ExtSnapshotPerm; - -/* return EXT_SNAPSHOT_ALLOWED if external snapshot is allowed - * return EXT_SNAPSHOT_FORBIDDEN if external snapshot is forbidden - */ -ExtSnapshotPerm bdrv_check_ext_snapshot(BlockDriverState *bs); -/* helper used to forbid external snapshots like in blkverify */ -ExtSnapshotPerm bdrv_check_ext_snapshot_forbidden(BlockDriverState *bs); + BS_IS_A_FILTER, + BS_FILTER_PASS_DOWN, + BS_AUTHORIZATION_COUNT, +} BsAuthorization; + +bool bdrv_generic_is_first_non_filter(BlockDriverState *bs, + BlockDriverState *candidate); +bool bdrv_recurse_is_first_non_filter(BlockDriverState *bs, + BlockDriverState *candidate); +bool bdrv_is_first_non_filter(BlockDriverState *candidate); /* async block I/O */ typedef void BlockDriverDirtyHandler(BlockDriverState *bs, int64_t sector, diff --git a/include/block/block_int.h b/include/block/block_int.h index f3f518c4d2..611a955712 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -69,10 +69,16 @@ struct BlockDriver { const char *format_name; int instance_size; - /* if not defined external snapshots are allowed - * future block filters will query their children to build the response + /* this table of boolean contains authorizations for the block operations */ + bool authorizations[BS_AUTHORIZATION_COUNT]; + /* for snapshots complex block filter like Quorum can implement the + * following recursive callback instead of BS_IS_A_FILTER. + * It's purpose is to recurse on the filter children while calling + * bdrv_recurse_is_first_non_filter on them. + * For a sample implementation look in the future Quorum block filter. */ - ExtSnapshotPerm (*bdrv_check_ext_snapshot)(BlockDriverState *bs); + bool (*bdrv_recurse_is_first_non_filter)(BlockDriverState *bs, + BlockDriverState *candidate); int (*bdrv_probe)(const uint8_t *buf, int buf_size, const char *filename); int (*bdrv_probe_device)(const char *filename); -- cgit 1.4.1 From d34682cd4a06efe9ee3fc8cb7e8a0ea445299989 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Wed, 11 Dec 2013 19:26:16 +0100 Subject: block: Move initialisation of BlockLimits to bdrv_refresh_limits() This function separates filling the BlockLimits from bdrv_open(), which allows it to call it from other operations which may change the limits (e.g. modifications to the backing file chain or bdrv_reopen) Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 18 ++++++++++++++++++ block/iscsi.c | 46 +++++++++++++++++++++++++++++----------------- block/qcow2.c | 11 ++++++++++- block/qed.c | 11 ++++++++++- block/vmdk.c | 22 ++++++++++++++++++---- include/block/block_int.h | 2 ++ 6 files changed, 87 insertions(+), 23 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 2106ae9cee..0a3d12c328 100644 --- a/block.c +++ b/block.c @@ -483,6 +483,19 @@ int bdrv_create_file(const char* filename, QEMUOptionParameter *options, return ret; } +static int bdrv_refresh_limits(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + + memset(&bs->bl, 0, sizeof(bs->bl)); + + if (drv && drv->bdrv_refresh_limits) { + return drv->bdrv_refresh_limits(bs); + } + + return 0; +} + /* * Create a uniquely-named empty temporary file. * Return 0 upon success, otherwise a negative errno value. @@ -872,6 +885,8 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file, goto free_and_fail; } + bdrv_refresh_limits(bs); + #ifndef _WIN32 if (bs->is_temporary) { assert(bs->filename[0] != '\0'); @@ -1085,6 +1100,9 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp) bs->backing_hd->file->filename); } + /* Recalculate the BlockLimits with the backing file */ + bdrv_refresh_limits(bs); + return 0; } diff --git a/block/iscsi.c b/block/iscsi.c index 76b3c96d38..c8bf8dc049 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -1265,23 +1265,6 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, sizeof(struct scsi_inquiry_block_limits)); scsi_free_scsi_task(task); task = NULL; - - if (iscsilun->bl.max_unmap < 0xffffffff) { - bs->bl.max_discard = sector_lun2qemu(iscsilun->bl.max_unmap, - iscsilun); - } - bs->bl.discard_alignment = sector_lun2qemu(iscsilun->bl.opt_unmap_gran, - iscsilun); - - if (iscsilun->bl.max_ws_len < 0xffffffff) { - bs->bl.max_write_zeroes = sector_lun2qemu(iscsilun->bl.max_ws_len, - iscsilun); - } - bs->bl.write_zeroes_alignment = sector_lun2qemu(iscsilun->bl.opt_unmap_gran, - iscsilun); - - bs->bl.opt_transfer_length = sector_lun2qemu(iscsilun->bl.opt_xfer_len, - iscsilun); } #if defined(LIBISCSI_FEATURE_NOP_COUNTER) @@ -1326,6 +1309,34 @@ static void iscsi_close(BlockDriverState *bs) memset(iscsilun, 0, sizeof(IscsiLun)); } +static int iscsi_refresh_limits(BlockDriverState *bs) +{ + IscsiLun *iscsilun = bs->opaque; + + /* We don't actually refresh here, but just return data queried in + * iscsi_open(): iscsi targets don't change their limits. */ + if (iscsilun->lbp.lbpu || iscsilun->lbp.lbpws) { + if (iscsilun->bl.max_unmap < 0xffffffff) { + bs->bl.max_discard = sector_lun2qemu(iscsilun->bl.max_unmap, + iscsilun); + } + bs->bl.discard_alignment = sector_lun2qemu(iscsilun->bl.opt_unmap_gran, + iscsilun); + + if (iscsilun->bl.max_ws_len < 0xffffffff) { + bs->bl.max_write_zeroes = sector_lun2qemu(iscsilun->bl.max_ws_len, + iscsilun); + } + bs->bl.write_zeroes_alignment = sector_lun2qemu(iscsilun->bl.opt_unmap_gran, + iscsilun); + + bs->bl.opt_transfer_length = sector_lun2qemu(iscsilun->bl.opt_xfer_len, + iscsilun); + } + + return 0; +} + static int iscsi_truncate(BlockDriverState *bs, int64_t offset) { IscsiLun *iscsilun = bs->opaque; @@ -1438,6 +1449,7 @@ static BlockDriver bdrv_iscsi = { .bdrv_getlength = iscsi_getlength, .bdrv_get_info = iscsi_get_info, .bdrv_truncate = iscsi_truncate, + .bdrv_refresh_limits = iscsi_refresh_limits, #if defined(LIBISCSI_FEATURE_IOVECTOR) .bdrv_co_get_block_status = iscsi_co_get_block_status, diff --git a/block/qcow2.c b/block/qcow2.c index e15a4dd057..2da62b8a90 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -718,7 +718,6 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, } qemu_opts_del(opts); - bs->bl.write_zeroes_alignment = s->cluster_sectors; if (s->use_lazy_refcounts && s->qcow_version < 3) { error_setg(errp, "Lazy refcounts require a qcow2 image with at least " @@ -751,6 +750,15 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, return ret; } +static int qcow2_refresh_limits(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + + bs->bl.write_zeroes_alignment = s->cluster_sectors; + + return 0; +} + static int qcow2_set_key(BlockDriverState *bs, const char *key) { BDRVQcowState *s = bs->opaque; @@ -2268,6 +2276,7 @@ static BlockDriver bdrv_qcow2 = { .bdrv_change_backing_file = qcow2_change_backing_file, + .bdrv_refresh_limits = qcow2_refresh_limits, .bdrv_invalidate_cache = qcow2_invalidate_cache, .create_options = qcow2_create_options, diff --git a/block/qed.c b/block/qed.c index 0dd5c5859e..694e6e2ee0 100644 --- a/block/qed.c +++ b/block/qed.c @@ -495,7 +495,6 @@ static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags, } } - bs->bl.write_zeroes_alignment = s->header.cluster_size >> BDRV_SECTOR_BITS; s->need_check_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, qed_need_check_timer_cb, s); @@ -507,6 +506,15 @@ out: return ret; } +static int bdrv_qed_refresh_limits(BlockDriverState *bs) +{ + BDRVQEDState *s = bs->opaque; + + bs->bl.write_zeroes_alignment = s->header.cluster_size >> BDRV_SECTOR_BITS; + + return 0; +} + /* We have nothing to do for QED reopen, stubs just return * success */ static int bdrv_qed_reopen_prepare(BDRVReopenState *state, @@ -1616,6 +1624,7 @@ static BlockDriver bdrv_qed = { .bdrv_truncate = bdrv_qed_truncate, .bdrv_getlength = bdrv_qed_getlength, .bdrv_get_info = bdrv_qed_get_info, + .bdrv_refresh_limits = bdrv_qed_refresh_limits, .bdrv_change_backing_file = bdrv_qed_change_backing_file, .bdrv_invalidate_cache = bdrv_qed_invalidate_cache, .bdrv_check = bdrv_qed_check, diff --git a/block/vmdk.c b/block/vmdk.c index 67b5f96a19..99ca60fdb9 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -428,10 +428,6 @@ static int vmdk_add_extent(BlockDriverState *bs, extent->l2_size = l2_size; extent->cluster_sectors = flat ? sectors : cluster_sectors; - if (!flat) { - bs->bl.write_zeroes_alignment = - MAX(bs->bl.write_zeroes_alignment, cluster_sectors); - } if (s->num_extents > 1) { extent->end_sector = (*(extent - 1)).end_sector + extent->sectors; } else { @@ -902,6 +898,23 @@ fail: return ret; } + +static int vmdk_refresh_limits(BlockDriverState *bs) +{ + BDRVVmdkState *s = bs->opaque; + int i; + + for (i = 0; i < s->num_extents; i++) { + if (!s->extents[i].flat) { + bs->bl.write_zeroes_alignment = + MAX(bs->bl.write_zeroes_alignment, + s->extents[i].cluster_sectors); + } + } + + return 0; +} + static int get_whole_cluster(BlockDriverState *bs, VmdkExtent *extent, uint64_t cluster_offset, @@ -2013,6 +2026,7 @@ static BlockDriver bdrv_vmdk = { .bdrv_get_allocated_file_size = vmdk_get_allocated_file_size, .bdrv_has_zero_init = vmdk_has_zero_init, .bdrv_get_specific_info = vmdk_get_specific_info, + .bdrv_refresh_limits = vmdk_refresh_limits, .create_options = vmdk_create_options, }; diff --git a/include/block/block_int.h b/include/block/block_int.h index 611a955712..f6fa1f6f36 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -232,6 +232,8 @@ struct BlockDriver { int (*bdrv_debug_resume)(BlockDriverState *bs, const char *tag); bool (*bdrv_debug_is_suspended)(BlockDriverState *bs, const char *tag); + int (*bdrv_refresh_limits)(BlockDriverState *bs); + /* * Returns 1 if newly created images are guaranteed to contain only * zeros, 0 otherwise. -- cgit 1.4.1 From 355ef4ac95a7a47d5c7201ccd910056a100d2fdf Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Wed, 11 Dec 2013 20:14:09 +0100 Subject: block: Update BlockLimits when they might have changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When reopening with different flags, or when backing files disappear from the chain, the limits may change. Make sure they get updated in these cases. Signed-off-by: Kevin Wolf Reviewed-by: Wenchao Xia Reviewed-by: Max Reitz Reviewed-by: Benoît Canet --- block.c | 5 ++++- block/stream.c | 2 ++ include/block/block.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/block.c b/block.c index 8a6692719a..9d0cfc46da 100644 --- a/block.c +++ b/block.c @@ -483,7 +483,7 @@ int bdrv_create_file(const char* filename, QEMUOptionParameter *options, return ret; } -static int bdrv_refresh_limits(BlockDriverState *bs) +int bdrv_refresh_limits(BlockDriverState *bs) { BlockDriver *drv = bs->drv; @@ -1607,6 +1607,8 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state) reopen_state->bs->enable_write_cache = !!(reopen_state->flags & BDRV_O_CACHE_WB); reopen_state->bs->read_only = !(reopen_state->flags & BDRV_O_RDWR); + + bdrv_refresh_limits(reopen_state->bs); } /* @@ -2441,6 +2443,7 @@ int bdrv_drop_intermediate(BlockDriverState *active, BlockDriverState *top, } new_top_bs->backing_hd = base_bs; + bdrv_refresh_limits(new_top_bs); QSIMPLEQ_FOREACH_SAFE(intermediate_state, &states_to_delete, entry, next) { /* so that bdrv_close() does not recursively close the chain */ diff --git a/block/stream.c b/block/stream.c index 46bec7d379..dd0b4ac3d2 100644 --- a/block/stream.c +++ b/block/stream.c @@ -75,6 +75,8 @@ static void close_unused_images(BlockDriverState *top, BlockDriverState *base, unused->backing_hd = NULL; bdrv_unref(unused); } + + bdrv_refresh_limits(top); } static void coroutine_fn stream_run(void *opaque) diff --git a/include/block/block.h b/include/block/block.h index 59d9f12ce4..00a8790660 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -253,6 +253,7 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset); int64_t bdrv_getlength(BlockDriverState *bs); int64_t bdrv_get_allocated_file_size(BlockDriverState *bs); void bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr); +int bdrv_refresh_limits(BlockDriverState *bs); int bdrv_commit(BlockDriverState *bs); int bdrv_commit_all(void); int bdrv_change_backing_file(BlockDriverState *bs, -- cgit 1.4.1 From 339064d5063924e5176842abbf6c8089f3479c5b Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Thu, 28 Nov 2013 10:23:32 +0100 Subject: block: Don't use guest sector size for qemu_blockalign() bs->buffer_alignment is set by the device emulation and contains the logical block size of the guest device. This isn't something that the block layer should know, and even less something to use for determining the right alignment of buffers to be used for the host. The new BlockLimits field opt_mem_alignment tells the qemu block layer the optimal alignment to be used so that no bounce buffer must be used in the driver. This patch may change the buffer alignment from 4k to 512 for all callers that used qemu_blockalign() with the top-level image format BlockDriverState. The value was never propagated to other levels in the tree, so in particular raw-posix never required anything else than 512. While on disks with 4k sectors direct I/O requires a 4k alignment, memory may still be okay when aligned to 512 byte boundaries. This is what must have happened in practice, because otherwise this would already have failed earlier. Therefore I don't expect regressions even with this intermediate state. Later, raw-posix can implement the hook and expose a different memory alignment requirement. Signed-off-by: Kevin Wolf Reviewed-by: Wenchao Xia Reviewed-by: Max Reitz --- block.c | 23 ++++++++++++++++++++--- include/block/block.h | 3 +++ include/block/block_int.h | 3 +++ 3 files changed, 26 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 275d387354..e5f214d135 100644 --- a/block.c +++ b/block.c @@ -218,6 +218,16 @@ static void bdrv_io_limits_intercept(BlockDriverState *bs, qemu_co_queue_next(&bs->throttled_reqs[is_write]); } +size_t bdrv_opt_mem_align(BlockDriverState *bs) +{ + if (!bs || !bs->drv) { + /* 4k should be on the safe side */ + return 4096; + } + + return bs->bl.opt_mem_alignment; +} + /* check if the path starts with ":" */ static int path_has_protocol(const char *path) { @@ -497,6 +507,9 @@ int bdrv_refresh_limits(BlockDriverState *bs) if (bs->file) { bdrv_refresh_limits(bs->file); bs->bl.opt_transfer_length = bs->file->bl.opt_transfer_length; + bs->bl.opt_mem_alignment = bs->file->bl.opt_mem_alignment; + } else { + bs->bl.opt_mem_alignment = 512; } if (bs->backing_hd) { @@ -504,6 +517,9 @@ int bdrv_refresh_limits(BlockDriverState *bs) bs->bl.opt_transfer_length = MAX(bs->bl.opt_transfer_length, bs->backing_hd->bl.opt_transfer_length); + bs->bl.opt_mem_alignment = + MAX(bs->bl.opt_mem_alignment, + bs->backing_hd->bl.opt_mem_alignment); } /* Then let the driver override it */ @@ -4797,7 +4813,7 @@ void bdrv_set_buffer_alignment(BlockDriverState *bs, int align) void *qemu_blockalign(BlockDriverState *bs, size_t size) { - return qemu_memalign((bs && bs->buffer_alignment) ? bs->buffer_alignment : 512, size); + return qemu_memalign(bdrv_opt_mem_align(bs), size); } /* @@ -4806,12 +4822,13 @@ void *qemu_blockalign(BlockDriverState *bs, size_t size) bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov) { int i; + size_t alignment = bdrv_opt_mem_align(bs); for (i = 0; i < qiov->niov; i++) { - if ((uintptr_t) qiov->iov[i].iov_base % bs->buffer_alignment) { + if ((uintptr_t) qiov->iov[i].iov_base % alignment) { return false; } - if (qiov->iov[i].iov_len % bs->buffer_alignment) { + if (qiov->iov[i].iov_len % alignment) { return false; } } diff --git a/include/block/block.h b/include/block/block.h index 00a8790660..332ebb94fb 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -428,6 +428,9 @@ void bdrv_img_create(const char *filename, const char *fmt, char *options, uint64_t img_size, int flags, Error **errp, bool quiet); +/* Returns the alignment in bytes that is required so that no bounce buffer + * is required throughout the stack */ +size_t bdrv_opt_mem_align(BlockDriverState *bs); void bdrv_set_buffer_alignment(BlockDriverState *bs, int align); void *qemu_blockalign(BlockDriverState *bs, size_t size); bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov); diff --git a/include/block/block_int.h b/include/block/block_int.h index f6fa1f6f36..74a78a6831 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -258,6 +258,9 @@ typedef struct BlockLimits { /* optimal transfer length in sectors */ int opt_transfer_length; + + /* memory alignment so that no bounce buffer is needed */ + size_t opt_mem_alignment; } BlockLimits; /* -- cgit 1.4.1 From 1b7fd729559c6d3b273303aa48bc653ceef08747 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 29 Nov 2011 11:35:47 +0100 Subject: block: rename buffer_alignment to guest_block_size The alignment field is now set to the value that is promised to the guest, rather than required by the host. The next patches will make QEMU aware of the host-provided values, so make this clear. The alignment is also not about memory buffers, but about the sectors on the disk, change the documentation of the field. At this point, the field is set by the device emulation, but completely ignored by the block layer. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf Reviewed-by: Wenchao Xia Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 10 +++++----- hw/block/virtio-blk.c | 2 +- hw/ide/core.c | 2 +- hw/scsi/scsi-disk.c | 2 +- hw/scsi/scsi-generic.c | 2 +- include/block/block.h | 2 +- include/block/block_int.h | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index e5f214d135..195beff706 100644 --- a/block.c +++ b/block.c @@ -851,7 +851,7 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file, } bs->open_flags = flags; - bs->buffer_alignment = 512; + bs->guest_block_size = 512; bs->zero_beyond_eof = true; open_flags = bdrv_open_flags(bs, flags); bs->read_only = !(open_flags & BDRV_O_RDWR); @@ -1796,7 +1796,7 @@ static void bdrv_move_feature_fields(BlockDriverState *bs_dest, bs_dest->dev_ops = bs_src->dev_ops; bs_dest->dev_opaque = bs_src->dev_opaque; bs_dest->dev = bs_src->dev; - bs_dest->buffer_alignment = bs_src->buffer_alignment; + bs_dest->guest_block_size = bs_src->guest_block_size; bs_dest->copy_on_read = bs_src->copy_on_read; bs_dest->enable_write_cache = bs_src->enable_write_cache; @@ -1953,7 +1953,7 @@ void bdrv_detach_dev(BlockDriverState *bs, void *dev) bs->dev = NULL; bs->dev_ops = NULL; bs->dev_opaque = NULL; - bs->buffer_alignment = 512; + bs->guest_block_size = 512; } /* TODO change to return DeviceState * when all users are qdevified */ @@ -4806,9 +4806,9 @@ BlockDriverAIOCB *bdrv_aio_ioctl(BlockDriverState *bs, return NULL; } -void bdrv_set_buffer_alignment(BlockDriverState *bs, int align) +void bdrv_set_guest_block_size(BlockDriverState *bs, int align) { - bs->buffer_alignment = align; + bs->guest_block_size = align; } void *qemu_blockalign(BlockDriverState *bs, size_t size) diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 19d0961a47..8a568e5edb 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -731,7 +731,7 @@ static void virtio_blk_device_realize(DeviceState *dev, Error **errp) register_savevm(dev, "virtio-blk", virtio_blk_id++, 2, virtio_blk_save, virtio_blk_load, s); bdrv_set_dev_ops(s->bs, &virtio_block_ops, s); - bdrv_set_buffer_alignment(s->bs, s->conf->logical_block_size); + bdrv_set_guest_block_size(s->bs, s->conf->logical_block_size); bdrv_iostatus_enable(s->bs); diff --git a/hw/ide/core.c b/hw/ide/core.c index e1f4c33fb8..036cd4a6d1 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -2103,7 +2103,7 @@ int ide_init_drive(IDEState *s, BlockDriverState *bs, IDEDriveKind kind, s->smart_selftest_count = 0; if (kind == IDE_CD) { bdrv_set_dev_ops(bs, &ide_cd_block_ops, s); - bdrv_set_buffer_alignment(bs, 2048); + bdrv_set_guest_block_size(bs, 2048); } else { if (!bdrv_is_inserted(s->bs)) { error_report("Device needs media, but drive is empty"); diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index bce617cb93..649109150b 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -2254,7 +2254,7 @@ static int scsi_initfn(SCSIDevice *dev) } else { bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_disk_block_ops, s); } - bdrv_set_buffer_alignment(s->qdev.conf.bs, s->qdev.blocksize); + bdrv_set_guest_block_size(s->qdev.conf.bs, s->qdev.blocksize); bdrv_iostatus_enable(s->qdev.conf.bs); add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, NULL); diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c index 8f195bec00..f08b64e177 100644 --- a/hw/scsi/scsi-generic.c +++ b/hw/scsi/scsi-generic.c @@ -210,7 +210,7 @@ static void scsi_read_complete(void * opaque, int ret) s->blocksize = ldl_be_p(&r->buf[8]); s->max_lba = ldq_be_p(&r->buf[0]); } - bdrv_set_buffer_alignment(s->conf.bs, s->blocksize); + bdrv_set_guest_block_size(s->conf.bs, s->blocksize); scsi_req_data(&r->req, len); if (!r->req.io_canceled) { diff --git a/include/block/block.h b/include/block/block.h index 332ebb94fb..a2f5657321 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -431,7 +431,7 @@ void bdrv_img_create(const char *filename, const char *fmt, /* Returns the alignment in bytes that is required so that no bounce buffer * is required throughout the stack */ size_t bdrv_opt_mem_align(BlockDriverState *bs); -void bdrv_set_buffer_alignment(BlockDriverState *bs, int align); +void bdrv_set_guest_block_size(BlockDriverState *bs, int align); void *qemu_blockalign(BlockDriverState *bs, size_t size); bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov); diff --git a/include/block/block_int.h b/include/block/block_int.h index 74a78a6831..ae609bd4d6 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -325,8 +325,8 @@ struct BlockDriverState { /* Whether produces zeros when read beyond eof */ bool zero_beyond_eof; - /* the memory alignment required for the buffers handled by this driver */ - int buffer_alignment; + /* the block size for which the guest device expects atomicity */ + int guest_block_size; /* do we need to tell the quest if we have a volatile write cache? */ int enable_write_cache; -- cgit 1.4.1 From c25f53b06eba1575d5d0e92a0132455c97825b83 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 29 Nov 2011 12:42:20 +0100 Subject: raw: Probe required direct I/O alignment Add a bs->request_alignment field that contains the required offset/length alignment for I/O requests and fill it in the raw block drivers. Use ioctls if possible, else see what alignment it takes for O_DIRECT to succeed. While at it, also expose the memory alignment requirements, which may be (and in practice are) different from the disk alignment requirements. Signed-off-by: Paolo Bonzini Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz --- block.c | 3 ++ block/raw-posix.c | 102 ++++++++++++++++++++++++++++++++++++++-------- block/raw-win32.c | 41 +++++++++++++++++++ include/block/block_int.h | 3 ++ 4 files changed, 132 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 195beff706..d4dd7fe172 100644 --- a/block.c +++ b/block.c @@ -852,6 +852,7 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file, bs->open_flags = flags; bs->guest_block_size = 512; + bs->request_alignment = 512; bs->zero_beyond_eof = true; open_flags = bdrv_open_flags(bs, flags); bs->read_only = !(open_flags & BDRV_O_RDWR); @@ -920,6 +921,8 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file, } bdrv_refresh_limits(bs); + assert(bdrv_opt_mem_align(bs) != 0); + assert(bs->request_alignment != 0); #ifndef _WIN32 if (bs->is_temporary) { diff --git a/block/raw-posix.c b/block/raw-posix.c index 0676037e13..126a634e45 100644 --- a/block/raw-posix.c +++ b/block/raw-posix.c @@ -127,6 +127,8 @@ typedef struct BDRVRawState { int fd; int type; int open_flags; + size_t buf_align; + #if defined(__linux__) /* linux floppy specific */ int64_t fd_open_time; @@ -213,6 +215,76 @@ static int raw_normalize_devicepath(const char **filename) } #endif +static void raw_probe_alignment(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + char *buf; + unsigned int sector_size; + + /* For /dev/sg devices the alignment is not really used. + With buffered I/O, we don't have any restrictions. */ + if (bs->sg || !(s->open_flags & O_DIRECT)) { + bs->request_alignment = 1; + s->buf_align = 1; + return; + } + + /* Try a few ioctls to get the right size */ + bs->request_alignment = 0; + s->buf_align = 0; + +#ifdef BLKSSZGET + if (ioctl(s->fd, BLKSSZGET, §or_size) >= 0) { + bs->request_alignment = sector_size; + } +#endif +#ifdef DKIOCGETBLOCKSIZE + if (ioctl(s->fd, DKIOCGETBLOCKSIZE, §or_size) >= 0) { + bs->request_alignment = sector_size; + } +#endif +#ifdef DIOCGSECTORSIZE + if (ioctl(s->fd, DIOCGSECTORSIZE, §or_size) >= 0) { + bs->request_alignment = sector_size; + } +#endif +#ifdef CONFIG_XFS + if (s->is_xfs) { + struct dioattr da; + if (xfsctl(NULL, s->fd, XFS_IOC_DIOINFO, &da) >= 0) { + bs->request_alignment = da.d_miniosz; + /* The kernel returns wrong information for d_mem */ + /* s->buf_align = da.d_mem; */ + } + } +#endif + + /* If we could not get the sizes so far, we can only guess them */ + if (!s->buf_align) { + size_t align; + buf = qemu_memalign(MAX_BLOCKSIZE, 2 * MAX_BLOCKSIZE); + for (align = 512; align <= MAX_BLOCKSIZE; align <<= 1) { + if (pread(s->fd, buf + align, MAX_BLOCKSIZE, 0) >= 0) { + s->buf_align = align; + break; + } + } + qemu_vfree(buf); + } + + if (!bs->request_alignment) { + size_t align; + buf = qemu_memalign(s->buf_align, MAX_BLOCKSIZE); + for (align = 512; align <= MAX_BLOCKSIZE; align <<= 1) { + if (pread(s->fd, buf, align, 0) >= 0) { + bs->request_alignment = align; + break; + } + } + qemu_vfree(buf); + } +} + static void raw_parse_flags(int bdrv_flags, int *open_flags) { assert(open_flags != NULL); @@ -463,7 +535,6 @@ static int raw_reopen_prepare(BDRVReopenState *state, return ret; } - static void raw_reopen_commit(BDRVReopenState *state) { BDRVRawReopenState *raw_s = state->opaque; @@ -499,23 +570,15 @@ static void raw_reopen_abort(BDRVReopenState *state) state->opaque = NULL; } +static int raw_refresh_limits(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; -/* XXX: use host sector size if necessary with: -#ifdef DIOCGSECTORSIZE - { - unsigned int sectorsize = 512; - if (!ioctl(fd, DIOCGSECTORSIZE, §orsize) && - sectorsize > bufsize) - bufsize = sectorsize; - } -#endif -#ifdef CONFIG_COCOA - uint32_t blockSize = 512; - if ( !ioctl( fd, DKIOCGETBLOCKSIZE, &blockSize ) && blockSize > bufsize) { - bufsize = blockSize; - } -#endif -*/ + raw_probe_alignment(bs); + bs->bl.opt_mem_alignment = s->buf_align; + + return 0; +} static ssize_t handle_aiocb_ioctl(RawPosixAIOData *aiocb) { @@ -1363,6 +1426,7 @@ static BlockDriver bdrv_file = { .bdrv_aio_writev = raw_aio_writev, .bdrv_aio_flush = raw_aio_flush, .bdrv_aio_discard = raw_aio_discard, + .bdrv_refresh_limits = raw_refresh_limits, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, @@ -1740,6 +1804,7 @@ static BlockDriver bdrv_host_device = { .bdrv_aio_writev = raw_aio_writev, .bdrv_aio_flush = raw_aio_flush, .bdrv_aio_discard = hdev_aio_discard, + .bdrv_refresh_limits = raw_refresh_limits, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, @@ -1871,6 +1936,7 @@ static BlockDriver bdrv_host_floppy = { .bdrv_aio_readv = raw_aio_readv, .bdrv_aio_writev = raw_aio_writev, .bdrv_aio_flush = raw_aio_flush, + .bdrv_refresh_limits = raw_refresh_limits, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, @@ -1981,6 +2047,7 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_aio_readv = raw_aio_readv, .bdrv_aio_writev = raw_aio_writev, .bdrv_aio_flush = raw_aio_flush, + .bdrv_refresh_limits = raw_refresh_limits, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, @@ -2110,6 +2177,7 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_aio_readv = raw_aio_readv, .bdrv_aio_writev = raw_aio_writev, .bdrv_aio_flush = raw_aio_flush, + .bdrv_refresh_limits = raw_refresh_limits, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, diff --git a/block/raw-win32.c b/block/raw-win32.c index ce314fd54f..beb7f2395e 100644 --- a/block/raw-win32.c +++ b/block/raw-win32.c @@ -202,6 +202,35 @@ static int set_sparse(int fd) NULL, 0, NULL, 0, &returned, NULL); } +static void raw_probe_alignment(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + DWORD sectorsPerCluster, freeClusters, totalClusters, count; + DISK_GEOMETRY_EX dg; + BOOL status; + + if (s->type == FTYPE_CD) { + bs->request_alignment = 2048; + return; + } + if (s->type == FTYPE_HARDDISK) { + status = DeviceIoControl(s->hfile, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, + NULL, 0, &dg, sizeof(dg), &count, NULL); + if (status != 0) { + bs->request_alignment = dg.Geometry.BytesPerSector; + return; + } + /* try GetDiskFreeSpace too */ + } + + if (s->drive_path[0]) { + GetDiskFreeSpace(s->drive_path, §orsPerCluster, + &dg.Geometry.BytesPerSector, + &freeClusters, &totalClusters); + bs->request_alignment = dg.Geometry.BytesPerSector; + } +} + static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped) { assert(access_flags != NULL); @@ -269,6 +298,17 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags, } } + if (filename[0] && filename[1] == ':') { + snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", filename[0]); + } else if (filename[0] == '\\' && filename[1] == '\\') { + s->drive_path[0] = 0; + } else { + /* Relative path. */ + char buf[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, buf); + snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", buf[0]); + } + s->hfile = CreateFile(filename, access_flags, FILE_SHARE_READ, NULL, OPEN_EXISTING, overlapped, NULL); @@ -293,6 +333,7 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags, s->aio = aio; } + raw_probe_alignment(bs); ret = 0; fail: qemu_opts_del(opts); diff --git a/include/block/block_int.h b/include/block/block_int.h index ae609bd4d6..5f9cecd65a 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -325,6 +325,9 @@ struct BlockDriverState { /* Whether produces zeros when read beyond eof */ bool zero_beyond_eof; + /* Alignment requirement for offset/length of I/O requests */ + unsigned int request_alignment; + /* the block size for which the guest device expects atomicity */ int guest_block_size; -- cgit 1.4.1 From 793ed47a7a2b09b67cb2a8863dff531436532b5c Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 3 Dec 2013 15:31:25 +0100 Subject: block: Switch BdrvTrackedRequest to byte granularity Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 52 +++++++++++++++++++++++++++++++---------------- block/backup.c | 7 ++++++- include/block/block_int.h | 4 ++-- 3 files changed, 42 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 576859ea3e..a32c81d644 100644 --- a/block.c +++ b/block.c @@ -2217,13 +2217,13 @@ static void tracked_request_end(BdrvTrackedRequest *req) */ static void tracked_request_begin(BdrvTrackedRequest *req, BlockDriverState *bs, - int64_t sector_num, - int nb_sectors, bool is_write) + int64_t offset, + unsigned int bytes, bool is_write) { *req = (BdrvTrackedRequest){ .bs = bs, - .sector_num = sector_num, - .nb_sectors = nb_sectors, + .offset = offset, + .bytes = bytes, .is_write = is_write, .co = qemu_coroutine_self(), }; @@ -2254,25 +2254,43 @@ void bdrv_round_to_clusters(BlockDriverState *bs, } } +static void round_bytes_to_clusters(BlockDriverState *bs, + int64_t offset, unsigned int bytes, + int64_t *cluster_offset, + unsigned int *cluster_bytes) +{ + BlockDriverInfo bdi; + + if (bdrv_get_info(bs, &bdi) < 0 || bdi.cluster_size == 0) { + *cluster_offset = offset; + *cluster_bytes = bytes; + } else { + *cluster_offset = QEMU_ALIGN_DOWN(offset, bdi.cluster_size); + *cluster_bytes = QEMU_ALIGN_UP(offset - *cluster_offset + bytes, + bdi.cluster_size); + } +} + static bool tracked_request_overlaps(BdrvTrackedRequest *req, - int64_t sector_num, int nb_sectors) { + int64_t offset, unsigned int bytes) +{ /* aaaa bbbb */ - if (sector_num >= req->sector_num + req->nb_sectors) { + if (offset >= req->offset + req->bytes) { return false; } /* bbbb aaaa */ - if (req->sector_num >= sector_num + nb_sectors) { + if (req->offset >= offset + bytes) { return false; } return true; } static void coroutine_fn wait_for_overlapping_requests(BlockDriverState *bs, - int64_t sector_num, int nb_sectors) + int64_t offset, unsigned int bytes) { BdrvTrackedRequest *req; - int64_t cluster_sector_num; - int cluster_nb_sectors; + int64_t cluster_offset; + unsigned int cluster_bytes; bool retry; /* If we touch the same cluster it counts as an overlap. This guarantees @@ -2281,14 +2299,12 @@ static void coroutine_fn wait_for_overlapping_requests(BlockDriverState *bs, * CoR read and write operations are atomic and guest writes cannot * interleave between them. */ - bdrv_round_to_clusters(bs, sector_num, nb_sectors, - &cluster_sector_num, &cluster_nb_sectors); + round_bytes_to_clusters(bs, offset, bytes, &cluster_offset, &cluster_bytes); do { retry = false; QLIST_FOREACH(req, &bs->tracked_requests, list) { - if (tracked_request_overlaps(req, cluster_sector_num, - cluster_nb_sectors)) { + if (tracked_request_overlaps(req, cluster_offset, cluster_bytes)) { /* Hitting this means there was a reentrant request, for * example, a block driver issuing nested requests. This must * never happen since it means deadlock. @@ -2908,10 +2924,10 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs, } if (bs->copy_on_read_in_flight) { - wait_for_overlapping_requests(bs, sector_num, nb_sectors); + wait_for_overlapping_requests(bs, offset, bytes); } - tracked_request_begin(&req, bs, sector_num, nb_sectors, false); + tracked_request_begin(&req, bs, offset, bytes, false); if (flags & BDRV_REQ_COPY_ON_READ) { int pnum; @@ -3160,10 +3176,10 @@ static int coroutine_fn bdrv_aligned_pwritev(BlockDriverState *bs, assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0); if (bs->copy_on_read_in_flight) { - wait_for_overlapping_requests(bs, sector_num, nb_sectors); + wait_for_overlapping_requests(bs, offset, bytes); } - tracked_request_begin(&req, bs, sector_num, nb_sectors, true); + tracked_request_begin(&req, bs, offset, bytes, true); ret = notifier_with_return_list_notify(&bs->before_write_notifiers, &req); diff --git a/block/backup.c b/block/backup.c index 0198514043..15a2e55e8e 100644 --- a/block/backup.c +++ b/block/backup.c @@ -181,8 +181,13 @@ static int coroutine_fn backup_before_write_notify( void *opaque) { BdrvTrackedRequest *req = opaque; + int64_t sector_num = req->offset >> BDRV_SECTOR_BITS; + int nb_sectors = req->bytes >> BDRV_SECTOR_BITS; - return backup_do_cow(req->bs, req->sector_num, req->nb_sectors, NULL); + assert((req->offset & (BDRV_SECTOR_SIZE - 1)) == 0); + assert((req->bytes & (BDRV_SECTOR_SIZE - 1)) == 0); + + return backup_do_cow(req->bs, sector_num, nb_sectors, NULL); } static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp) diff --git a/include/block/block_int.h b/include/block/block_int.h index 5f9cecd65a..bcdd98c503 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -57,8 +57,8 @@ typedef struct BdrvTrackedRequest { BlockDriverState *bs; - int64_t sector_num; - int nb_sectors; + int64_t offset; + unsigned int bytes; bool is_write; QLIST_ENTRY(BdrvTrackedRequest) list; Coroutine *co; /* owner, used for deadlock detection */ -- cgit 1.4.1 From 2dbafdc012d3ea81a97fec6226ca82d644539c9a Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Wed, 4 Dec 2013 16:43:44 +0100 Subject: block: Generalise and optimise COR serialisation Change the API so that specific requests can be marked serialising. Only these requests are checked for overlaps then. This means that during a Copy on Read operation, not all requests overlapping other requests are serialised any more, but only those that actually overlap with the specific COR request. Also remove COR from function and variable names because this functionality can be useful in other contexts. Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 48 ++++++++++++++++++++++++++++------------------- include/block/block_int.h | 5 +++-- 2 files changed, 32 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 0a391bd80f..493c75f33e 100644 --- a/block.c +++ b/block.c @@ -2208,6 +2208,10 @@ int bdrv_commit_all(void) */ static void tracked_request_end(BdrvTrackedRequest *req) { + if (req->serialising) { + req->bs->serialising_in_flight--; + } + QLIST_REMOVE(req, list); qemu_co_queue_restart_all(&req->wait_queue); } @@ -2222,10 +2226,11 @@ static void tracked_request_begin(BdrvTrackedRequest *req, { *req = (BdrvTrackedRequest){ .bs = bs, - .offset = offset, - .bytes = bytes, - .is_write = is_write, - .co = qemu_coroutine_self(), + .offset = offset, + .bytes = bytes, + .is_write = is_write, + .co = qemu_coroutine_self(), + .serialising = false, }; qemu_co_queue_init(&req->wait_queue); @@ -2233,6 +2238,14 @@ static void tracked_request_begin(BdrvTrackedRequest *req, QLIST_INSERT_HEAD(&bs->tracked_requests, req, list); } +static void mark_request_serialising(BdrvTrackedRequest *req) +{ + if (!req->serialising) { + req->bs->serialising_in_flight++; + req->serialising = true; + } +} + /** * Round a region to cluster boundaries */ @@ -2285,26 +2298,31 @@ static bool tracked_request_overlaps(BdrvTrackedRequest *req, return true; } -static void coroutine_fn wait_for_overlapping_requests(BlockDriverState *bs, - BdrvTrackedRequest *self, int64_t offset, unsigned int bytes) +static void coroutine_fn wait_serialising_requests(BdrvTrackedRequest *self) { + BlockDriverState *bs = self->bs; BdrvTrackedRequest *req; int64_t cluster_offset; unsigned int cluster_bytes; bool retry; + if (!bs->serialising_in_flight) { + return; + } + /* If we touch the same cluster it counts as an overlap. This guarantees * that allocating writes will be serialized and not race with each other * for the same cluster. For example, in copy-on-read it ensures that the * CoR read and write operations are atomic and guest writes cannot * interleave between them. */ - round_bytes_to_clusters(bs, offset, bytes, &cluster_offset, &cluster_bytes); + round_bytes_to_clusters(bs, self->offset, self->bytes, + &cluster_offset, &cluster_bytes); do { retry = false; QLIST_FOREACH(req, &bs->tracked_requests, list) { - if (req == self) { + if (req == self || (!req->serialising && !self->serialising)) { continue; } if (tracked_request_overlaps(req, cluster_offset, cluster_bytes)) { @@ -2923,12 +2941,10 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs, /* Handle Copy on Read and associated serialisation */ if (flags & BDRV_REQ_COPY_ON_READ) { - bs->copy_on_read_in_flight++; + mark_request_serialising(req); } - if (bs->copy_on_read_in_flight) { - wait_for_overlapping_requests(bs, req, offset, bytes); - } + wait_serialising_requests(req); if (flags & BDRV_REQ_COPY_ON_READ) { int pnum; @@ -2977,10 +2993,6 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs, } out: - if (flags & BDRV_REQ_COPY_ON_READ) { - bs->copy_on_read_in_flight--; - } - return ret; } @@ -3179,9 +3191,7 @@ static int coroutine_fn bdrv_aligned_pwritev(BlockDriverState *bs, assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0); assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0); - if (bs->copy_on_read_in_flight) { - wait_for_overlapping_requests(bs, req, offset, bytes); - } + wait_serialising_requests(req); ret = notifier_with_return_list_notify(&bs->before_write_notifiers, req); diff --git a/include/block/block_int.h b/include/block/block_int.h index bcdd98c503..c1153cb3ab 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -60,6 +60,7 @@ typedef struct BdrvTrackedRequest { int64_t offset; unsigned int bytes; bool is_write; + bool serialising; QLIST_ENTRY(BdrvTrackedRequest) list; Coroutine *co; /* owner, used for deadlock detection */ CoQueue wait_queue; /* coroutines blocked on this request */ @@ -302,8 +303,8 @@ struct BlockDriverState { /* Callback before write request is processed */ NotifierWithReturnList before_write_notifiers; - /* number of in-flight copy-on-read requests */ - unsigned int copy_on_read_in_flight; + /* number of in-flight serialising requests */ + unsigned int serialising_in_flight; /* I/O throttling */ ThrottleState throttle_state; -- cgit 1.4.1 From 7327145f63a224c9ba9c16d0c29781feffef8dc6 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Wed, 4 Dec 2013 17:08:50 +0100 Subject: block: Make overlap range for serialisation dynamic Copy on Read wants to serialise with all requests touching the same cluster, so wait_serialising_requests() rounded to cluster boundaries. Other users like alignment RMW will have different requirements, though (requests touching the same sector), so make it dynamic. Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 53 ++++++++++++++++++++++++----------------------- include/block/block_int.h | 4 ++++ 2 files changed, 31 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 493c75f33e..784ff079ed 100644 --- a/block.c +++ b/block.c @@ -2231,6 +2231,8 @@ static void tracked_request_begin(BdrvTrackedRequest *req, .is_write = is_write, .co = qemu_coroutine_self(), .serialising = false, + .overlap_offset = offset, + .overlap_bytes = bytes, }; qemu_co_queue_init(&req->wait_queue); @@ -2238,12 +2240,19 @@ static void tracked_request_begin(BdrvTrackedRequest *req, QLIST_INSERT_HEAD(&bs->tracked_requests, req, list); } -static void mark_request_serialising(BdrvTrackedRequest *req) +static void mark_request_serialising(BdrvTrackedRequest *req, size_t align) { + int64_t overlap_offset = req->offset & ~(align - 1); + int overlap_bytes = ROUND_UP(req->offset + req->bytes, align) + - overlap_offset; + if (!req->serialising) { req->bs->serialising_in_flight++; req->serialising = true; } + + req->overlap_offset = MIN(req->overlap_offset, overlap_offset); + req->overlap_bytes = MAX(req->overlap_bytes, overlap_bytes); } /** @@ -2267,20 +2276,16 @@ void bdrv_round_to_clusters(BlockDriverState *bs, } } -static void round_bytes_to_clusters(BlockDriverState *bs, - int64_t offset, unsigned int bytes, - int64_t *cluster_offset, - unsigned int *cluster_bytes) +static int bdrv_get_cluster_size(BlockDriverState *bs) { BlockDriverInfo bdi; + int ret; - if (bdrv_get_info(bs, &bdi) < 0 || bdi.cluster_size == 0) { - *cluster_offset = offset; - *cluster_bytes = bytes; + ret = bdrv_get_info(bs, &bdi); + if (ret < 0 || bdi.cluster_size == 0) { + return bs->request_alignment; } else { - *cluster_offset = QEMU_ALIGN_DOWN(offset, bdi.cluster_size); - *cluster_bytes = QEMU_ALIGN_UP(offset - *cluster_offset + bytes, - bdi.cluster_size); + return bdi.cluster_size; } } @@ -2288,11 +2293,11 @@ static bool tracked_request_overlaps(BdrvTrackedRequest *req, int64_t offset, unsigned int bytes) { /* aaaa bbbb */ - if (offset >= req->offset + req->bytes) { + if (offset >= req->overlap_offset + req->overlap_bytes) { return false; } /* bbbb aaaa */ - if (req->offset >= offset + bytes) { + if (req->overlap_offset >= offset + bytes) { return false; } return true; @@ -2302,30 +2307,21 @@ static void coroutine_fn wait_serialising_requests(BdrvTrackedRequest *self) { BlockDriverState *bs = self->bs; BdrvTrackedRequest *req; - int64_t cluster_offset; - unsigned int cluster_bytes; bool retry; if (!bs->serialising_in_flight) { return; } - /* If we touch the same cluster it counts as an overlap. This guarantees - * that allocating writes will be serialized and not race with each other - * for the same cluster. For example, in copy-on-read it ensures that the - * CoR read and write operations are atomic and guest writes cannot - * interleave between them. - */ - round_bytes_to_clusters(bs, self->offset, self->bytes, - &cluster_offset, &cluster_bytes); - do { retry = false; QLIST_FOREACH(req, &bs->tracked_requests, list) { if (req == self || (!req->serialising && !self->serialising)) { continue; } - if (tracked_request_overlaps(req, cluster_offset, cluster_bytes)) { + if (tracked_request_overlaps(req, self->overlap_offset, + self->overlap_bytes)) + { /* Hitting this means there was a reentrant request, for * example, a block driver issuing nested requests. This must * never happen since it means deadlock. @@ -2941,7 +2937,12 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs, /* Handle Copy on Read and associated serialisation */ if (flags & BDRV_REQ_COPY_ON_READ) { - mark_request_serialising(req); + /* If we touch the same cluster it counts as an overlap. This + * guarantees that allocating writes will be serialized and not race + * with each other for the same cluster. For example, in copy-on-read + * it ensures that the CoR read and write operations are atomic and + * guest writes cannot interleave between them. */ + mark_request_serialising(req, bdrv_get_cluster_size(bs)); } wait_serialising_requests(req); diff --git a/include/block/block_int.h b/include/block/block_int.h index c1153cb3ab..0ee955cb76 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -60,7 +60,11 @@ typedef struct BdrvTrackedRequest { int64_t offset; unsigned int bytes; bool is_write; + bool serialising; + int64_t overlap_offset; + unsigned int overlap_bytes; + QLIST_ENTRY(BdrvTrackedRequest) list; Coroutine *co; /* owner, used for deadlock detection */ CoQueue wait_queue; /* coroutines blocked on this request */ -- cgit 1.4.1 From 6460440f34c709461b84375cfd8a86b27d433225 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 13 Dec 2013 13:04:35 +0100 Subject: block: Allow wait_serialising_requests() at any point We can only have a single wait_serialising_requests() call per request because otherwise we can run into deadlocks where requests are waiting for each other. The same is true when wait_serialising_requests() is not at the very beginning of a request, so that other requests can be issued between the start of the tracking and wait_serialising_requests(). Fix this by changing wait_serialising_requests() to ignore requests that are already (directly or indirectly) waiting for the calling request. Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz Reviewed-by: Benoit Canet --- block.c | 13 ++++++++++--- include/block/block_int.h | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 784ff079ed..96905856a1 100644 --- a/block.c +++ b/block.c @@ -2328,9 +2328,16 @@ static void coroutine_fn wait_serialising_requests(BdrvTrackedRequest *self) */ assert(qemu_coroutine_self() != req->co); - qemu_co_queue_wait(&req->wait_queue); - retry = true; - break; + /* If the request is already (indirectly) waiting for us, or + * will wait for us as soon as it wakes up, then just go on + * (instead of producing a deadlock in the former case). */ + if (!req->waiting_for) { + self->waiting_for = req; + qemu_co_queue_wait(&req->wait_queue); + self->waiting_for = NULL; + retry = true; + break; + } } } } while (retry); diff --git a/include/block/block_int.h b/include/block/block_int.h index 0ee955cb76..0bcf1c9b8c 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -68,6 +68,8 @@ typedef struct BdrvTrackedRequest { QLIST_ENTRY(BdrvTrackedRequest) list; Coroutine *co; /* owner, used for deadlock detection */ CoQueue wait_queue; /* coroutines blocked on this request */ + + struct BdrvTrackedRequest *waiting_for; } BdrvTrackedRequest; struct BlockDriver { -- cgit 1.4.1 From 8407d5d7e265911b05949ee2ffd9e45c97bf0505 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Thu, 5 Dec 2013 12:34:02 +0100 Subject: block: Make bdrv_pwrite() a bdrv_prwv_co() wrapper Instead of implementing the alignment adjustment here, use the now existing functionality of bdrv_co_do_pwritev(). Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz --- block.c | 64 ++++++++------------------------------------------- include/block/block.h | 1 - 2 files changed, 9 insertions(+), 56 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 07114f3169..179d27ac75 100644 --- a/block.c +++ b/block.c @@ -2667,11 +2667,6 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num, return bdrv_rw_co(bs, sector_num, (uint8_t *)buf, nb_sectors, true, 0); } -int bdrv_writev(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov) -{ - return bdrv_prwv_co(bs, sector_num << BDRV_SECTOR_BITS, qiov, true, 0); -} - int bdrv_write_zeroes(BlockDriverState *bs, int64_t sector_num, int nb_sectors, BdrvRequestFlags flags) { @@ -2745,70 +2740,29 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset, void *buf, int bytes) int bdrv_pwritev(BlockDriverState *bs, int64_t offset, QEMUIOVector *qiov) { - uint8_t tmp_buf[BDRV_SECTOR_SIZE]; - int len, nb_sectors, count; - int64_t sector_num; int ret; - count = qiov->size; - - /* first write to align to sector start */ - len = (BDRV_SECTOR_SIZE - offset) & (BDRV_SECTOR_SIZE - 1); - if (len > count) - len = count; - sector_num = offset >> BDRV_SECTOR_BITS; - if (len > 0) { - if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) - return ret; - qemu_iovec_to_buf(qiov, 0, tmp_buf + (offset & (BDRV_SECTOR_SIZE - 1)), - len); - if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) - return ret; - count -= len; - if (count == 0) - return qiov->size; - sector_num++; - } - - /* write the sectors "in place" */ - nb_sectors = count >> BDRV_SECTOR_BITS; - if (nb_sectors > 0) { - QEMUIOVector qiov_inplace; - - qemu_iovec_init(&qiov_inplace, qiov->niov); - qemu_iovec_concat(&qiov_inplace, qiov, len, - nb_sectors << BDRV_SECTOR_BITS); - ret = bdrv_writev(bs, sector_num, &qiov_inplace); - qemu_iovec_destroy(&qiov_inplace); - if (ret < 0) { - return ret; - } - - sector_num += nb_sectors; - len = nb_sectors << BDRV_SECTOR_BITS; - count -= len; + ret = bdrv_prwv_co(bs, offset, qiov, true, 0); + if (ret < 0) { + return ret; } - /* add data from the last sector */ - if (count > 0) { - if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) - return ret; - qemu_iovec_to_buf(qiov, qiov->size - count, tmp_buf, count); - if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) - return ret; - } return qiov->size; } int bdrv_pwrite(BlockDriverState *bs, int64_t offset, - const void *buf, int count1) + const void *buf, int bytes) { QEMUIOVector qiov; struct iovec iov = { .iov_base = (void *) buf, - .iov_len = count1, + .iov_len = bytes, }; + if (bytes < 0) { + return -EINVAL; + } + qemu_iovec_init_external(&qiov, &iov, 1); return bdrv_pwritev(bs, offset, &qiov); } diff --git a/include/block/block.h b/include/block/block.h index a2f5657321..1085992a7a 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -224,7 +224,6 @@ BlockDriverAIOCB *bdrv_aio_write_zeroes(BlockDriverState *bs, int64_t sector_num int nb_sectors, BdrvRequestFlags flags, BlockDriverCompletionFunc *cb, void *opaque); int bdrv_make_zero(BlockDriverState *bs, BdrvRequestFlags flags); -int bdrv_writev(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov); int bdrv_pread(BlockDriverState *bs, int64_t offset, void *buf, int count); int bdrv_pwrite(BlockDriverState *bs, int64_t offset, -- cgit 1.4.1 From 9e1cb96d9a5e434f389a4d7b7ff4dcdd71e8ec0f Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Tue, 14 Jan 2014 15:37:03 +0100 Subject: qemu-iotests: Test pwritev RMW logic Signed-off-by: Kevin Wolf Reviewed-by: Max Reitz --- block.c | 7 ++ block/blkdebug.c | 8 ++ include/block/block.h | 8 ++ tests/qemu-iotests/077 | 278 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/077.out | 202 ++++++++++++++++++++++++++++++++ tests/qemu-iotests/group | 1 + 6 files changed, 504 insertions(+) create mode 100755 tests/qemu-iotests/077 create mode 100644 tests/qemu-iotests/077.out (limited to 'include') diff --git a/block.c b/block.c index 179d27ac75..932ce58265 100644 --- a/block.c +++ b/block.c @@ -3141,10 +3141,13 @@ static int coroutine_fn bdrv_aligned_pwritev(BlockDriverState *bs, if (ret < 0) { /* Do nothing, write notifier decided to fail this request */ } else if (flags & BDRV_REQ_ZERO_WRITE) { + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_ZERO); ret = bdrv_co_do_write_zeroes(bs, sector_num, nb_sectors, flags); } else { + BLKDBG_EVENT(bs, BLKDBG_PWRITEV); ret = drv->bdrv_co_writev(bs, sector_num, nb_sectors, qiov); } + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_DONE); if (ret == 0 && !bs->enable_write_cache) { ret = bdrv_co_flush(bs); @@ -3215,11 +3218,13 @@ static int coroutine_fn bdrv_co_do_pwritev(BlockDriverState *bs, }; qemu_iovec_init_external(&head_qiov, &head_iov, 1); + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_RMW_HEAD); ret = bdrv_aligned_preadv(bs, &req, offset & ~(align - 1), align, align, &head_qiov, 0); if (ret < 0) { goto fail; } + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_RMW_AFTER_HEAD); qemu_iovec_init(&local_qiov, qiov->niov + 2); qemu_iovec_add(&local_qiov, head_buf, offset & (align - 1)); @@ -3247,11 +3252,13 @@ static int coroutine_fn bdrv_co_do_pwritev(BlockDriverState *bs, }; qemu_iovec_init_external(&tail_qiov, &tail_iov, 1); + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_RMW_TAIL); ret = bdrv_aligned_preadv(bs, &req, (offset + bytes) & ~(align - 1), align, align, &tail_qiov, 0); if (ret < 0) { goto fail; } + BLKDBG_EVENT(bs, BLKDBG_PWRITEV_RMW_AFTER_TAIL); if (!use_local_qiov) { qemu_iovec_init(&local_qiov, qiov->niov + 1); diff --git a/block/blkdebug.c b/block/blkdebug.c index 2c03698f93..56c4cd084f 100644 --- a/block/blkdebug.c +++ b/block/blkdebug.c @@ -186,6 +186,14 @@ static const char *event_names[BLKDBG_EVENT_MAX] = { [BLKDBG_FLUSH_TO_OS] = "flush_to_os", [BLKDBG_FLUSH_TO_DISK] = "flush_to_disk", + + [BLKDBG_PWRITEV_RMW_HEAD] = "pwritev_rmw.head", + [BLKDBG_PWRITEV_RMW_AFTER_HEAD] = "pwritev_rmw.after_head", + [BLKDBG_PWRITEV_RMW_TAIL] = "pwritev_rmw.tail", + [BLKDBG_PWRITEV_RMW_AFTER_TAIL] = "pwritev_rmw.after_tail", + [BLKDBG_PWRITEV] = "pwritev", + [BLKDBG_PWRITEV_ZERO] = "pwritev_zero", + [BLKDBG_PWRITEV_DONE] = "pwritev_done", }; static int get_event_by_name(const char *name, BlkDebugEvent *event) diff --git a/include/block/block.h b/include/block/block.h index 1085992a7a..963a61fa4c 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -527,6 +527,14 @@ typedef enum { BLKDBG_FLUSH_TO_OS, BLKDBG_FLUSH_TO_DISK, + BLKDBG_PWRITEV_RMW_HEAD, + BLKDBG_PWRITEV_RMW_AFTER_HEAD, + BLKDBG_PWRITEV_RMW_TAIL, + BLKDBG_PWRITEV_RMW_AFTER_TAIL, + BLKDBG_PWRITEV, + BLKDBG_PWRITEV_ZERO, + BLKDBG_PWRITEV_DONE, + BLKDBG_EVENT_MAX, } BlkDebugEvent; diff --git a/tests/qemu-iotests/077 b/tests/qemu-iotests/077 new file mode 100755 index 0000000000..bbf7b5145a --- /dev/null +++ b/tests/qemu-iotests/077 @@ -0,0 +1,278 @@ +#!/bin/bash +# +# Test concurrent pread/pwrite +# +# Copyright (C) 2014 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# creator +owner=kwolf@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt generic +_supported_proto generic +_supported_os Linux + +CLUSTER_SIZE=4k +size=128M + +_make_test_img $size + +echo +echo "== Some concurrent requests involving RMW ==" + +function test_io() +{ +echo "open -o file.align=4k blkdebug::$TEST_IMG" +# A simple RMW request +cat <