qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 8/8] switch -drive to QemuOpts.


From: Gerd Hoffmann
Subject: [Qemu-devel] [PATCH 8/8] switch -drive to QemuOpts.
Date: Wed, 22 Jul 2009 12:25:07 +0200

Demo QemuOpts in action ;)

Implementing a alternative way to specify the filename should be
just a few lines of code now once we decided how the cmd line syntax
should look like.

Signed-off-by: Gerd Hoffmann <address@hidden>
---
 hw/device-hotplug.c |   12 +-
 sysemu.h            |   15 +--
 vl.c                |  308 ++++++++++++++++++++++++++++-----------------------
 3 files changed, 179 insertions(+), 156 deletions(-)

diff --git a/hw/device-hotplug.c b/hw/device-hotplug.c
index 8f0dec8..fd4cc3f 100644
--- a/hw/device-hotplug.c
+++ b/hw/device-hotplug.c
@@ -28,19 +28,19 @@
 #include "block_int.h"
 #include "sysemu.h"
 
-DriveInfo *add_init_drive(const char *opts)
+DriveInfo *add_init_drive(const char *optstr)
 {
     int fatal_error;
     DriveInfo *dinfo;
-    DriveOpt *dopt;
+    QemuOpts *opts;
 
-    dopt = drive_add(NULL, "%s", opts);
-    if (!dopt)
+    opts = drive_add(NULL, "%s", optstr);
+    if (!opts)
         return NULL;
 
-    dinfo = drive_init(dopt, 0, current_machine, &fatal_error);
+    dinfo = drive_init(opts, current_machine, &fatal_error);
     if (!dinfo) {
-        drive_remove(dopt);
+        qemu_opts_del(opts);
         return NULL;
     }
 
diff --git a/sysemu.h b/sysemu.h
index ec55877..c944b28 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -3,6 +3,7 @@
 /* Misc. things related to the system emulator.  */
 
 #include "qemu-common.h"
+#include "qemu-opts.h"
 #include "sys-queue.h"
 
 #ifdef _WIN32
@@ -159,12 +160,6 @@ typedef enum {
 
 #define BLOCK_SERIAL_STRLEN 20
 
-typedef struct DriveOpt {
-    const char *file;
-    char opt[1024];
-    TAILQ_ENTRY(DriveOpt) next;
-} DriveOpt;
-
 typedef struct DriveInfo {
     BlockDriverState *bdrv;
     char *id;
@@ -172,7 +167,7 @@ typedef struct DriveInfo {
     BlockInterfaceType type;
     int bus;
     int unit;
-    DriveOpt *opt;
+    QemuOpts *opts;
     BlockInterfaceErrorAction onerror;
     char serial[BLOCK_SERIAL_STRLEN + 1];
     TAILQ_ENTRY(DriveInfo) next;
@@ -189,15 +184,13 @@ extern DriveInfo *drive_get(BlockInterfaceType type, int 
bus, int unit);
 extern DriveInfo *drive_get_by_id(char *id);
 extern int drive_get_max_bus(BlockInterfaceType type);
 extern void drive_uninit(BlockDriverState *bdrv);
-extern void drive_remove(DriveOpt *opt);
 extern const char *drive_get_serial(BlockDriverState *bdrv);
 extern BlockInterfaceErrorAction drive_get_onerror(BlockDriverState *bdrv);
 
 BlockDriverState *qdev_init_bdrv(DeviceState *dev, BlockInterfaceType type);
 
-extern DriveOpt *drive_add(const char *file, const char *fmt, ...);
-extern DriveInfo *drive_init(DriveOpt *arg, int snapshot, void *machine,
-                             int *fatal_error);
+extern QemuOpts *drive_add(const char *file, const char *fmt, ...);
+extern DriveInfo *drive_init(QemuOpts *arg, void *machine, int *fatal_error);
 
 /* acpi */
 typedef void (*qemu_system_device_hot_add_t)(int pcibus, int slot, int state);
diff --git a/vl.c b/vl.c
index ae91999..fb0aa0a 100644
--- a/vl.c
+++ b/vl.c
@@ -1869,27 +1869,94 @@ static int bt_parse(const char *opt)
 #define MTD_ALIAS "if=mtd"
 #define SD_ALIAS "index=0,if=sd"
 
-DriveOpt *drive_add(const char *file, const char *fmt, ...)
+static QemuOptsList drive_opt_list = {
+    .name = "drive",
+    .head = TAILQ_HEAD_INITIALIZER(drive_opt_list.head),
+    .desc = {
+        {
+            .name = "bus",
+            .type = QEMU_OPT_NUMBER,
+            .help = "bus number",
+        },{
+            .name = "unit",
+            .type = QEMU_OPT_NUMBER,
+            .help = "unit number (i.e. lun for scsi)",
+        },{
+            .name = "if",
+            .type = QEMU_OPT_STRING,
+            .help = "interface (ide, scsi, sd, mtd, floppy, pflash, virtio)",
+        },{
+            .name = "index",
+            .type = QEMU_OPT_NUMBER,
+        },{
+            .name = "cyls",
+            .type = QEMU_OPT_NUMBER,
+            .help = "number of cylinders (ide disk geometry)",
+        },{
+            .name = "heads",
+            .type = QEMU_OPT_NUMBER,
+            .help = "number of heads (ide disk geometry)",
+        },{
+            .name = "secs",
+            .type = QEMU_OPT_NUMBER,
+            .help = "number of sectors (ide disk geometry)",
+        },{
+            .name = "trans",
+            .type = QEMU_OPT_STRING,
+            .help = "chs translation (auto, lba. none)",
+        },{
+            .name = "media",
+            .type = QEMU_OPT_STRING,
+            .help = "media type (disk, cdrom)",
+        },{
+            .name = "snapshot",
+            .type = QEMU_OPT_BOOL,
+        },{
+            .name = "file",
+            .type = QEMU_OPT_STRING,
+            .help = "disk image",
+        },{
+            .name = "cache",
+            .type = QEMU_OPT_STRING,
+            .help = "host cache usage (none, writeback, writethrough)",
+        },{
+            .name = "format",
+            .type = QEMU_OPT_STRING,
+            .help = "disk format (raw, qcow2, ...)",
+        },{
+            .name = "serial",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "werror",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "addr",
+            .type = QEMU_OPT_STRING,
+            .help = "pci address (virtio only)",
+        },
+        { /* end if list */ }
+    },
+};
+
+QemuOpts *drive_add(const char *file, const char *fmt, ...)
 {
     va_list ap;
-    DriveOpt *dopt;
+    char optstr[1024];
+    QemuOpts *opts;
 
-    dopt = qemu_mallocz(sizeof(*dopt));
-
-    dopt->file = file;
     va_start(ap, fmt);
-    vsnprintf(dopt->opt,
-              sizeof(dopt->opt), fmt, ap);
+    vsnprintf(optstr, sizeof(optstr), fmt, ap);
     va_end(ap);
 
-    TAILQ_INSERT_TAIL(&driveopts, dopt, next);
-    return dopt;
-}
-
-void drive_remove(DriveOpt *dopt)
-{
-    TAILQ_REMOVE(&driveopts, dopt, next);
-    qemu_free(dopt);
+    opts = qemu_opts_parse(&drive_opt_list, optstr, NULL);
+    if (!opts) {
+        fprintf(stderr, "%s: huh? duplicate? (%s)\n",
+                __FUNCTION__, optstr);
+        return NULL;
+    }
+    if (file)
+        qemu_opt_set(opts, "file", file);
+    return opts;
 }
 
 DriveInfo *drive_get(BlockInterfaceType type, int bus, int unit)
@@ -1970,20 +2037,20 @@ void drive_uninit(BlockDriverState *bdrv)
     TAILQ_FOREACH(dinfo, &drives, next) {
         if (dinfo->bdrv != bdrv)
             continue;
-        drive_remove(dinfo->opt);
+        qemu_opts_del(dinfo->opts);
         TAILQ_REMOVE(&drives, dinfo, next);
         qemu_free(dinfo);
         break;
     }
 }
 
-DriveInfo *drive_init(DriveOpt *arg, int snapshot, void *opaque,
+DriveInfo *drive_init(QemuOpts *opts, void *opaque,
                       int *fatal_error)
 {
-    char buf[128];
-    char file[1024];
+    const char *buf;
+    const char *file = NULL;
     char devname[128];
-    char serial[21];
+    const char *serial;
     const char *mediastr = "";
     BlockInterfaceType type;
     enum { MEDIA_DISK, MEDIA_CDROM } media;
@@ -1997,27 +2064,11 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     int bdrv_flags, onerror;
     const char *devaddr;
     DriveInfo *dinfo;
-    char *str = arg->opt;
-    static const char * const params[] = { "bus", "unit", "if", "index",
-                                           "cyls", "heads", "secs", "trans",
-                                           "media", "snapshot", "file",
-                                           "cache", "format", "serial",
-                                           "werror", "addr", "id",
-                                           NULL };
-    *fatal_error = 1;
+    int snapshot = 0;
 
-    if (check_params(buf, sizeof(buf), params, str) < 0) {
-         fprintf(stderr, "qemu: unknown parameter '%s' in '%s'\n",
-                         buf, str);
-         return NULL;
-    }
+    *fatal_error = 1;
 
-    file[0] = 0;
-    cyls = heads = secs = 0;
-    bus_id = 0;
-    unit_id = -1;
     translation = BIOS_ATA_TRANSLATION_AUTO;
-    index = -1;
     cache = 1;
 
     if (machine->use_scsi) {
@@ -2032,24 +2083,20 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     media = MEDIA_DISK;
 
     /* extract parameters */
+    bus_id  = qemu_opt_get_number(opts, "bus", 0);
+    unit_id = qemu_opt_get_number(opts, "unit", -1);
+    index   = qemu_opt_get_number(opts, "index", -1);
 
-    if (get_param_value(buf, sizeof(buf), "bus", str)) {
-        bus_id = strtol(buf, NULL, 0);
-       if (bus_id < 0) {
-           fprintf(stderr, "qemu: '%s' invalid bus id\n", str);
-           return NULL;
-       }
-    }
+    cyls  = qemu_opt_get_number(opts, "cyls", 0);
+    heads = qemu_opt_get_number(opts, "heads", 0);
+    secs  = qemu_opt_get_number(opts, "secs", 0);
 
-    if (get_param_value(buf, sizeof(buf), "unit", str)) {
-        unit_id = strtol(buf, NULL, 0);
-       if (unit_id < 0) {
-           fprintf(stderr, "qemu: '%s' invalid unit id\n", str);
-           return NULL;
-       }
-    }
+    snapshot = qemu_opt_get_bool(opts, "snapshot", 0);
 
-    if (get_param_value(buf, sizeof(buf), "if", str)) {
+    file = qemu_opt_get(opts, "file");
+    serial = qemu_opt_get(opts, "serial");
+
+    if ((buf = qemu_opt_get(opts, "if")) != NULL) {
         pstrcpy(devname, sizeof(devname), buf);
         if (!strcmp(buf, "ide")) {
            type = IF_IDE;
@@ -2076,51 +2123,31 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
            type = IF_XEN;
             max_devs = 0;
        } else {
-            fprintf(stderr, "qemu: '%s' unsupported bus type '%s'\n", str, 
buf);
+            fprintf(stderr, "qemu: unsupported bus type '%s'\n", buf);
             return NULL;
        }
     }
 
-    if (get_param_value(buf, sizeof(buf), "index", str)) {
-        index = strtol(buf, NULL, 0);
-       if (index < 0) {
-           fprintf(stderr, "qemu: '%s' invalid index\n", str);
-           return NULL;
-       }
-    }
-
-    if (get_param_value(buf, sizeof(buf), "cyls", str)) {
-        cyls = strtol(buf, NULL, 0);
-    }
-
-    if (get_param_value(buf, sizeof(buf), "heads", str)) {
-        heads = strtol(buf, NULL, 0);
-    }
-
-    if (get_param_value(buf, sizeof(buf), "secs", str)) {
-        secs = strtol(buf, NULL, 0);
-    }
-
     if (cyls || heads || secs) {
         if (cyls < 1 || cyls > 16383) {
-            fprintf(stderr, "qemu: '%s' invalid physical cyls number\n", str);
+            fprintf(stderr, "qemu: '%s' invalid physical cyls number\n", buf);
            return NULL;
        }
         if (heads < 1 || heads > 16) {
-            fprintf(stderr, "qemu: '%s' invalid physical heads number\n", str);
+            fprintf(stderr, "qemu: '%s' invalid physical heads number\n", buf);
            return NULL;
        }
         if (secs < 1 || secs > 63) {
-            fprintf(stderr, "qemu: '%s' invalid physical secs number\n", str);
+            fprintf(stderr, "qemu: '%s' invalid physical secs number\n", buf);
            return NULL;
        }
     }
 
-    if (get_param_value(buf, sizeof(buf), "trans", str)) {
+    if ((buf = qemu_opt_get(opts, "trans")) != NULL) {
         if (!cyls) {
             fprintf(stderr,
                     "qemu: '%s' trans must be used with cyls,heads and secs\n",
-                    str);
+                    buf);
             return NULL;
         }
         if (!strcmp(buf, "none"))
@@ -2130,39 +2157,28 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
         else if (!strcmp(buf, "auto"))
             translation = BIOS_ATA_TRANSLATION_AUTO;
        else {
-            fprintf(stderr, "qemu: '%s' invalid translation type\n", str);
+            fprintf(stderr, "qemu: '%s' invalid translation type\n", buf);
            return NULL;
        }
     }
 
-    if (get_param_value(buf, sizeof(buf), "media", str)) {
+    if ((buf = qemu_opt_get(opts, "media")) != NULL) {
         if (!strcmp(buf, "disk")) {
            media = MEDIA_DISK;
        } else if (!strcmp(buf, "cdrom")) {
             if (cyls || secs || heads) {
                 fprintf(stderr,
-                        "qemu: '%s' invalid physical CHS format\n", str);
+                        "qemu: '%s' invalid physical CHS format\n", buf);
                return NULL;
             }
            media = MEDIA_CDROM;
        } else {
-           fprintf(stderr, "qemu: '%s' invalid media\n", str);
-           return NULL;
-       }
-    }
-
-    if (get_param_value(buf, sizeof(buf), "snapshot", str)) {
-        if (!strcmp(buf, "on"))
-           snapshot = 1;
-        else if (!strcmp(buf, "off"))
-           snapshot = 0;
-       else {
-           fprintf(stderr, "qemu: '%s' invalid snapshot option\n", str);
+           fprintf(stderr, "qemu: '%s' invalid media\n", buf);
            return NULL;
        }
     }
 
-    if (get_param_value(buf, sizeof(buf), "cache", str)) {
+    if ((buf = qemu_opt_get(opts, "cache")) != NULL) {
         if (!strcmp(buf, "off") || !strcmp(buf, "none"))
             cache = 0;
         else if (!strcmp(buf, "writethrough"))
@@ -2175,7 +2191,7 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
         }
     }
 
-    if (get_param_value(buf, sizeof(buf), "format", str)) {
+    if ((buf = qemu_opt_get(opts, "format")) != NULL) {
        if (strcmp(buf, "?") == 0) {
             fprintf(stderr, "qemu: Supported formats:");
             bdrv_iterate_format(bdrv_format_print, NULL);
@@ -2189,16 +2205,8 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
         }
     }
 
-    if (arg->file == NULL)
-        get_param_value(file, sizeof(file), "file", str);
-    else
-        pstrcpy(file, sizeof(file), arg->file);
-
-    if (!get_param_value(serial, sizeof(serial), "serial", str))
-           memset(serial, 0,  sizeof(serial));
-
     onerror = BLOCK_ERR_STOP_ENOSPC;
-    if (get_param_value(buf, sizeof(serial), "werror", str)) {
+    if ((buf = qemu_opt_get(opts, "werror")) != NULL) {
         if (type != IF_IDE && type != IF_SCSI && type != IF_VIRTIO) {
             fprintf(stderr, "werror is no supported by this format\n");
             return NULL;
@@ -2217,13 +2225,11 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
         }
     }
 
-    devaddr = NULL;
-    if (get_param_value(buf, sizeof(buf), "addr", str)) {
+    if ((devaddr = qemu_opt_get(opts, "addr")) != NULL) {
         if (type != IF_VIRTIO) {
-            fprintf(stderr, "addr is not supported by in '%s'\n", str);
+            fprintf(stderr, "addr is not supported\n");
             return NULL;
         }
-        devaddr = strdup(buf);
     }
 
     /* compute bus and unit according index */
@@ -2231,7 +2237,7 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     if (index != -1) {
         if (bus_id != 0 || unit_id != -1) {
             fprintf(stderr,
-                    "qemu: '%s' index cannot be used with bus and unit\n", 
str);
+                    "qemu: index cannot be used with bus and unit\n");
             return NULL;
         }
         if (max_devs == 0)
@@ -2262,8 +2268,8 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     /* check unit id */
 
     if (max_devs && unit_id >= max_devs) {
-        fprintf(stderr, "qemu: '%s' unit %d too big (max is %d)\n",
-                        str, unit_id, max_devs - 1);
+        fprintf(stderr, "qemu: unit %d too big (max is %d)\n",
+                unit_id, max_devs - 1);
         return NULL;
     }
 
@@ -2279,26 +2285,29 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     /* init */
 
     dinfo = qemu_mallocz(sizeof(*dinfo));
-    if (!get_param_value(buf, sizeof(buf), "id", str)) {
+    if ((buf = qemu_opt_get(opts, "id")) != NULL) {
+        dinfo->id = qemu_strdup(buf);
+    } else {
         /* no id supplied -> create one */
+        dinfo->id = qemu_mallocz(32);
         if (type == IF_IDE || type == IF_SCSI)
             mediastr = (media == MEDIA_CDROM) ? "-cd" : "-hd";
         if (max_devs)
-            snprintf(buf, sizeof(buf), "%s%i%s%i",
+            snprintf(dinfo->id, 32, "%s%i%s%i",
                      devname, bus_id, mediastr, unit_id);
         else
-            snprintf(buf, sizeof(buf), "%s%s%i",
+            snprintf(dinfo->id, 32, "%s%s%i",
                      devname, mediastr, unit_id);
     }
-    dinfo->id = qemu_strdup(buf);
     dinfo->bdrv = bdrv_new(dinfo->id);
     dinfo->devaddr = devaddr;
     dinfo->type = type;
     dinfo->bus = bus_id;
     dinfo->unit = unit_id;
     dinfo->onerror = onerror;
-    dinfo->opt = arg;
-    strncpy(dinfo->serial, serial, sizeof(serial));
+    dinfo->opts = opts;
+    if (serial)
+        strncpy(dinfo->serial, serial, sizeof(serial));
     TAILQ_INSERT_TAIL(&drives, dinfo, next);
 
     switch(type) {
@@ -2330,7 +2339,7 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     case IF_COUNT:
         abort();
     }
-    if (!file[0]) {
+    if (!file) {
         *fatal_error = 0;
         return NULL;
     }
@@ -2354,6 +2363,26 @@ DriveInfo *drive_init(DriveOpt *arg, int snapshot, void 
*opaque,
     return dinfo;
 }
 
+static int drive_init_func(QemuOpts *opts, void *opaque)
+{
+    QEMUMachine *machine = opaque;
+    int fatal_error = 0;
+
+    if (drive_init(opts, machine, &fatal_error) == NULL) {
+        if (fatal_error)
+            return 1;
+    }
+    return 0;
+}
+
+static int drive_enable_snapshot(QemuOpts *opts, void *opaque)
+{
+    if (NULL == qemu_opt_get(opts, "snapshot")) {
+        qemu_opt_set(opts, "snapshot", "on");
+    }
+    return 0;
+}
+
 void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque)
 {
     boot_set_handler = func;
@@ -4833,7 +4862,7 @@ int main(int argc, char **argv, char **envp)
     int nb_net_clients;
     const char *bt_opts[MAX_BT_CMDLINE];
     int nb_bt_opts;
-    DriveOpt *dopt, *hda_opt = NULL;
+    QemuOpts *hda_opts = NULL;
     int optind;
     const char *r, *optarg;
     CharDriverState *monitor_hd = NULL;
@@ -4947,7 +4976,7 @@ int main(int argc, char **argv, char **envp)
             break;
         r = argv[optind];
         if (r[0] != '-') {
-           hda_opt = drive_add(argv[optind++], HD_ALIAS, 0);
+           hda_opts = drive_add(argv[optind++], HD_ALIAS, 0);
         } else {
             const QEMUOption *popt;
 
@@ -5008,9 +5037,9 @@ int main(int argc, char **argv, char **envp)
                 break;
             case QEMU_OPTION_hda:
                 if (cyls == 0)
-                    hda_opt = drive_add(optarg, HD_ALIAS, 0);
+                    hda_opts = drive_add(optarg, HD_ALIAS, 0);
                 else
-                    hda_opt = drive_add(optarg, HD_ALIAS
+                    hda_opts = drive_add(optarg, HD_ALIAS
                             ",cyls=%d,heads=%d,secs=%d%s",
                              0, cyls, heads, secs,
                              translation == BIOS_ATA_TRANSLATION_LBA ?
@@ -5072,15 +5101,19 @@ int main(int argc, char **argv, char **envp)
                         fprintf(stderr, "qemu: invalid physical CHS format\n");
                         exit(1);
                     }
-                   if (hda_opt != NULL)
-                        snprintf(hda_opt->opt,
-                                 sizeof(hda_opt->opt),
-                                 HD_ALIAS ",cyls=%d,heads=%d,secs=%d%s",
-                                 0, cyls, heads, secs,
-                                translation == BIOS_ATA_TRANSLATION_LBA ?
-                                   ",trans=lba" :
-                                translation == BIOS_ATA_TRANSLATION_NONE ?
-                                    ",trans=none" : "");
+                   if (hda_opts != NULL) {
+                        char num[16];
+                        snprintf(num, sizeof(num), "%d", cyls);
+                        qemu_opt_set(hda_opts, "cyls", num);
+                        snprintf(num, sizeof(num), "%d", heads);
+                        qemu_opt_set(hda_opts, "heads", num);
+                        snprintf(num, sizeof(num), "%d", secs);
+                        qemu_opt_set(hda_opts, "secs", num);
+                        if (translation == BIOS_ATA_TRANSLATION_LBA)
+                            qemu_opt_set(hda_opts, "trans", "lba");
+                        if (translation == BIOS_ATA_TRANSLATION_NONE)
+                            qemu_opt_set(hda_opts, "trans", "none");
+                    }
                 }
                 break;
             case QEMU_OPTION_numa:
@@ -5799,13 +5832,10 @@ int main(int argc, char **argv, char **envp)
     drive_add(NULL, SD_ALIAS);
 
     /* open the virtual block devices */
-
-    TAILQ_FOREACH(dopt, &driveopts, next) {
-        int fatal_error;
-        if (drive_init(dopt, snapshot, machine, &fatal_error) == NULL)
-            if (fatal_error)
-                exit(1);
-    }
+    if (snapshot)
+        qemu_opts_foreach(&drive_opt_list, drive_enable_snapshot, NULL, 0);
+    if (qemu_opts_foreach(&drive_opt_list, drive_init_func, machine, 1) != 0)
+        exit(1);
 
     register_savevm("timer", 0, 2, timer_save, timer_load, NULL);
     register_savevm_live("ram", 0, 3, ram_save_live, NULL, ram_load, NULL);
-- 
1.6.2.5





reply via email to

[Prev in Thread] Current Thread [Next in Thread]