S: Supported
 F: blockjob.c
 F: include/block/blockjob.h
+F: job.c
+F: include/block/job.h
 F: block/backup.c
 F: block/commit.c
 F: block/stream.c
 
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
 block-obj-y += nbd/
-block-obj-y += block.o blockjob.o
+block-obj-y += block.o blockjob.o job.o
 block-obj-y += block/ scsi/
 block-obj-y += qemu-io-cmds.o
 block-obj-$(CONFIG_REPLICATION) += replication.o
 
 }
 
 static const BlockJobDriver backup_job_driver = {
-    .instance_size          = sizeof(BackupBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(BackupBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_BACKUP,
     .start                  = backup_run,
     .commit                 = backup_commit,
 
 }
 
 static const BlockJobDriver commit_job_driver = {
-    .instance_size = sizeof(CommitBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(CommitBlockJob),
+    },
     .job_type      = BLOCK_JOB_TYPE_COMMIT,
     .start         = commit_run,
 };
 
 
     if (!s->synced) {
         error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->id);
+                   job->job.id);
         return;
     }
 
 }
 
 static const BlockJobDriver mirror_job_driver = {
-    .instance_size          = sizeof(MirrorBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(MirrorBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_MIRROR,
     .start                  = mirror_run,
     .complete               = mirror_complete,
 };
 
 static const BlockJobDriver commit_active_job_driver = {
-    .instance_size          = sizeof(MirrorBlockJob),
+    .job_driver = {
+        .instance_size          = sizeof(MirrorBlockJob),
+    },
     .job_type               = BLOCK_JOB_TYPE_COMMIT,
     .start                  = mirror_run,
     .complete               = mirror_complete,
 
 }
 
 static const BlockJobDriver stream_job_driver = {
-    .instance_size = sizeof(StreamBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(StreamBlockJob),
+    },
     .job_type      = BLOCK_JOB_TYPE_STREAM,
     .start         = stream_run,
 };
 
 #include "qapi/qapi-events-block-core.h"
 #include "qapi/qmp/qerror.h"
 #include "qemu/coroutine.h"
-#include "qemu/id.h"
 #include "qemu/timer.h"
 
 /* Right now, this mutex is only needed to synchronize accesses to job->busy
         return 0;
     }
     error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'",
-               job->id, BlockJobStatus_str(job->status), BlockJobVerb_str(bv));
+               job->job.id, BlockJobStatus_str(job->status),
+               BlockJobVerb_str(bv));
     return -EPERM;
 }
 
     BlockJob *job;
 
     QLIST_FOREACH(job, &block_jobs, job_list) {
-        if (job->id && !strcmp(id, job->id)) {
+        if (job->job.id && !strcmp(id, job->job.id)) {
             return job;
         }
     }
                                         block_job_detach_aio_context, job);
         blk_unref(job->blk);
         error_free(job->blocker);
-        g_free(job->id);
+        g_free(job->job.id);
         assert(!timer_pending(&job->sleep_timer));
         g_free(job);
     }
     BlockJob *job = c->opaque;
     return g_strdup_printf("%s job '%s'",
                            BlockJobType_str(job->driver->job_type),
-                           job->id);
+                           job->job.id);
 }
 
 static void child_job_drained_begin(BdrvChild *c)
 
 bool block_job_is_internal(BlockJob *job)
 {
-    return (job->id == NULL);
+    return (job->job.id == NULL);
 }
 
 static bool block_job_started(BlockJob *job)
 void block_job_complete(BlockJob *job, Error **errp)
 {
     /* Should not be reachable via external interface for internal jobs */
-    assert(job->id);
+    assert(job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_COMPLETE, errp)) {
         return;
     }
     if (job->pause_count || job->cancelled || !job->driver->complete) {
         error_setg(errp, "The active block job '%s' cannot be completed",
-                   job->id);
+                   job->job.id);
         return;
     }
 
 
 void block_job_finalize(BlockJob *job, Error **errp)
 {
-    assert(job && job->id);
+    assert(job && job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) {
         return;
     }
 {
     BlockJob *job = *jobptr;
     /* similarly to _complete, this is QMP-interface only. */
-    assert(job->id);
+    assert(job->job.id);
     if (block_job_apply_verb(job, BLOCK_JOB_VERB_DISMISS, errp)) {
         return;
     }
     }
     info = g_new0(BlockJobInfo, 1);
     info->type      = g_strdup(BlockJobType_str(job->driver->job_type));
-    info->device    = g_strdup(job->id);
+    info->device    = g_strdup(job->job.id);
     info->len       = job->len;
     info->busy      = atomic_read(&job->busy);
     info->paused    = job->pause_count > 0;
     }
 
     qapi_event_send_block_job_cancelled(job->driver->job_type,
-                                        job->id,
+                                        job->job.id,
                                         job->len,
                                         job->offset,
                                         job->speed,
     }
 
     qapi_event_send_block_job_completed(job->driver->job_type,
-                                        job->id,
+                                        job->job.id,
                                         job->len,
                                         job->offset,
                                         job->speed,
     block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING);
     if (!job->auto_finalize && !block_job_is_internal(job)) {
         qapi_event_send_block_job_pending(job->driver->job_type,
-                                          job->id,
+                                          job->job.id,
                                           &error_abort);
     }
     return 0;
             error_setg(errp, "Cannot specify job ID for internal block job");
             return NULL;
         }
-
-        if (!id_wellformed(job_id)) {
-            error_setg(errp, "Invalid job ID '%s'", job_id);
-            return NULL;
-        }
-
         if (block_job_get(job_id)) {
             error_setg(errp, "Job ID '%s' already in use", job_id);
             return NULL;
         return NULL;
     }
 
-    job = g_malloc0(driver->instance_size);
+    job = job_create(job_id, &driver->job_driver, errp);
+    if (job == NULL) {
+        blk_unref(blk);
+        return NULL;
+    }
+
     job->driver        = driver;
-    job->id            = g_strdup(job_id);
     job->blk           = blk;
     job->cb            = cb;
     job->opaque        = opaque;
     }
 
     qapi_event_send_block_job_ready(job->driver->job_type,
-                                    job->id,
+                                    job->job.id,
                                     job->len,
                                     job->offset,
                                     job->speed, &error_abort);
         abort();
     }
     if (!block_job_is_internal(job)) {
-        qapi_event_send_block_job_error(job->id,
+        qapi_event_send_block_job_error(job->job.id,
                                         is_read ? IO_OPERATION_TYPE_READ :
                                         IO_OPERATION_TYPE_WRITE,
                                         action, &error_abort);
 
 #ifndef BLOCKJOB_H
 #define BLOCKJOB_H
 
+#include "qemu/job.h"
 #include "block/block.h"
 #include "qemu/ratelimit.h"
 
  * Long-running operation on a BlockDriverState.
  */
 typedef struct BlockJob {
+    /** Data belonging to the generic Job infrastructure */
+    Job job;
+
     /** The job type, including the job vtable.  */
     const BlockJobDriver *driver;
 
     /** The block device on which the job is operating.  */
     BlockBackend *blk;
 
-    /**
-     * The ID of the block job. May be NULL for internal jobs.
-     */
-    char *id;
-
     /**
      * The coroutine that executes the job.  If not NULL, it is
      * reentered when busy is false and the job is cancelled.
 
  * A class type for block job driver.
  */
 struct BlockJobDriver {
-    /** Derived BlockJob struct size */
-    size_t instance_size;
+    /** Generic JobDriver callbacks and settings */
+    JobDriver job_driver;
 
     /** String describing the operation, part of query-block-jobs QMP API */
     BlockJobType job_type;
 
--- /dev/null
+/*
+ * Declarations for background jobs
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012, 2018 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifndef JOB_H
+#define JOB_H
+
+typedef struct JobDriver JobDriver;
+
+/**
+ * Long-running operation.
+ */
+typedef struct Job {
+    /** The ID of the job. May be NULL for internal jobs. */
+    char *id;
+
+    /** The type of this job. */
+    const JobDriver *driver;
+} Job;
+
+/**
+ * Callbacks and other information about a Job driver.
+ */
+struct JobDriver {
+    /** Derived Job struct size */
+    size_t instance_size;
+};
+
+
+/**
+ * Create a new long-running job and return it.
+ *
+ * @job_id: The id of the newly-created job, or %NULL for internal jobs
+ * @driver: The class object for the newly-created job.
+ * @errp: Error object.
+ */
+void *job_create(const char *job_id, const JobDriver *driver, Error **errp);
+
+#endif
 
--- /dev/null
+/*
+ * Background jobs (long-running operations)
+ *
+ * Copyright (c) 2011 IBM Corp.
+ * Copyright (c) 2012, 2018 Red Hat, Inc.
+ *
+ * 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/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qemu/job.h"
+#include "qemu/id.h"
+
+void *job_create(const char *job_id, const JobDriver *driver, Error **errp)
+{
+    Job *job;
+
+    if (job_id) {
+        if (!id_wellformed(job_id)) {
+            error_setg(errp, "Invalid job ID '%s'", job_id);
+            return NULL;
+        }
+    }
+
+    job = g_malloc0(driver->instance_size);
+    job->driver        = driver;
+    job->id            = g_strdup(job_id);
+
+    return job;
+}
 
 }
 
 BlockJobDriver test_job_driver = {
-    .instance_size  = sizeof(TestBlockJob),
+    .job_driver = {
+        .instance_size  = sizeof(TestBlockJob),
+    },
     .start          = test_job_start,
     .complete       = test_job_complete,
 };
 
 }
 
 static const BlockJobDriver test_block_job_driver = {
-    .instance_size = sizeof(TestBlockJob),
+    .job_driver = {
+        .instance_size = sizeof(TestBlockJob),
+    },
     .start = test_block_job_run,
 };
 
 
 #include "sysemu/block-backend.h"
 
 static const BlockJobDriver test_block_job_driver = {
-    .instance_size = sizeof(BlockJob),
+    .job_driver = {
+        .instance_size = sizeof(BlockJob),
+    },
 };
 
 static void block_job_cb(void *opaque, int ret)
         g_assert_null(errp);
         g_assert_nonnull(job);
         if (id) {
-            g_assert_cmpstr(job->id, ==, id);
+            g_assert_cmpstr(job->job.id, ==, id);
         } else {
-            g_assert_cmpstr(job->id, ==, blk_name(blk));
+            g_assert_cmpstr(job->job.id, ==, blk_name(blk));
         }
     } else {
         g_assert_nonnull(errp);
 }
 
 static const BlockJobDriver test_cancel_driver = {
-    .instance_size = sizeof(CancelJob),
+    .job_driver = {
+        .instance_size = sizeof(CancelJob),
+    },
     .start         = cancel_job_start,
     .complete      = cancel_job_complete,
 };