diff --git a/include/block/blockjob.h b/include/block/blockjob.h
index aef06295f6..3f405d1fa7 100644
--- a/include/block/blockjob.h
+++ b/include/block/blockjob.h
@@ -76,9 +76,6 @@ typedef struct BlockJob {
     /** Rate limiting data structure for implementing @speed. */
     RateLimit limit;
 
-    /** The completion function that will be called when the job completes.  */
-    BlockCompletionFunc *cb;
-
     /** Block other operations when block job is running */
     Error *blocker;
 
@@ -94,12 +91,6 @@ typedef struct BlockJob {
     /** BlockDriverStates that are involved in this block job */
     GSList *nodes;
 
-    /** The opaque value that is passed to the completion function.  */
-    void *opaque;
-
-    /** ret code passed to block_job_completed. */
-    int ret;
-
     BlockJobTxn *txn;
     QLIST_ENTRY(BlockJob) txn_list;
 } BlockJob;
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
index 88639f7efc..bf2b762808 100644
--- a/include/block/blockjob_int.h
+++ b/include/block/blockjob_int.h
@@ -54,34 +54,6 @@ struct BlockJobDriver {
      */
     int (*prepare)(BlockJob *job);
 
-    /**
-     * If the callback is not NULL, it will be invoked when all the jobs
-     * belonging to the same transaction complete; or upon this job's
-     * completion if it is not in a transaction. Skipped if NULL.
-     *
-     * All jobs will complete with a call to either .commit() or .abort() but
-     * never both.
-     */
-    void (*commit)(BlockJob *job);
-
-    /**
-     * If the callback is not NULL, it will be invoked when any job in the
-     * same transaction fails; or upon this job's failure (due to error or
-     * cancellation) if it is not in a transaction. Skipped if NULL.
-     *
-     * All jobs will complete with a call to either .commit() or .abort() but
-     * never both.
-     */
-    void (*abort)(BlockJob *job);
-
-    /**
-     * If the callback is not NULL, it will be invoked after a call to either
-     * .commit() or .abort(). Regardless of which callback is invoked after
-     * completion, .clean() will always be called, even if the job does not
-     * belong to a transaction group.
-     */
-    void (*clean)(BlockJob *job);
-
     /*
      * If the callback is not NULL, it will be invoked before the job is
      * resumed in a new AioContext.  This is the place to move any resources
@@ -156,14 +128,6 @@ void block_job_yield(BlockJob *job);
 int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
 
 /**
- * block_job_early_fail:
- * @bs: The block device.
- *
- * The block job could not be started, free it.
- */
-void block_job_early_fail(BlockJob *job);
-
-/**
  * block_job_completed:
  * @job: The job being completed.
  * @ret: The status code.
diff --git a/include/qemu/job.h b/include/qemu/job.h
index 14d93778f3..3e817beee9 100644
--- a/include/qemu/job.h
+++ b/include/qemu/job.h
@@ -29,6 +29,7 @@
 #include "qapi/qapi-types-block-core.h"
 #include "qemu/queue.h"
 #include "qemu/coroutine.h"
+#include "block/aio.h"
 
 typedef struct JobDriver JobDriver;
 
@@ -105,6 +106,15 @@ typedef struct Job {
     /** True if this job should automatically dismiss itself */
     bool auto_dismiss;
 
+    /** ret code passed to block_job_completed. */
+    int ret;
+
+    /** The completion function that will be called when the job completes.  */
+    BlockCompletionFunc *cb;
+
+    /** The opaque value that is passed to the completion function.  */
+    void *opaque;
+
     /** Notifiers called when a cancelled job is finalised */
     NotifierList on_finalize_cancelled;
 
@@ -151,6 +161,35 @@ struct JobDriver {
      */
     void (*user_resume)(Job *job);
 
+    /**
+     * If the callback is not NULL, it will be invoked when all the jobs
+     * belonging to the same transaction complete; or upon this job's
+     * completion if it is not in a transaction. Skipped if NULL.
+     *
+     * All jobs will complete with a call to either .commit() or .abort() but
+     * never both.
+     */
+    void (*commit)(Job *job);
+
+    /**
+     * If the callback is not NULL, it will be invoked when any job in the
+     * same transaction fails; or upon this job's failure (due to error or
+     * cancellation) if it is not in a transaction. Skipped if NULL.
+     *
+     * All jobs will complete with a call to either .commit() or .abort() but
+     * never both.
+     */
+    void (*abort)(Job *job);
+
+    /**
+     * If the callback is not NULL, it will be invoked after a call to either
+     * .commit() or .abort(). Regardless of which callback is invoked after
+     * completion, .clean() will always be called, even if the job does not
+     * belong to a transaction group.
+     */
+    void (*clean)(Job *job);
+
+
     /** Called when the job is freed */
     void (*free)(Job *job);
 };
@@ -174,10 +213,12 @@ typedef enum JobCreateFlags {
  * @driver: The class object for the newly-created job.
  * @ctx: The AioContext to run the job coroutine in.
  * @flags: Creation flags for the job. See @JobCreateFlags.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
  * @errp: Error object.
  */
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, Error **errp);
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp);
 
 /**
  * Add a reference to Job refcnt, it will be decreased with job_unref, and then
@@ -300,6 +341,10 @@ Job *job_get(const char *id);
  */
 int job_apply_verb(Job *job, JobVerb verb, Error **errp);
 
+/** The @job could not be started, free it. */
+void job_early_fail(Job *job);
+
+
 typedef void JobDeferToMainLoopFn(Job *job, void *opaque);
 
 /**
@@ -322,5 +367,11 @@ void job_state_transition(Job *job, JobStatus s1);
 void coroutine_fn job_do_yield(Job *job, uint64_t ns);
 bool job_should_pause(Job *job);
 bool job_started(Job *job);
+void job_do_dismiss(Job *job);
+int job_finalize_single(Job *job);
+void job_update_rc(Job *job);
+
+typedef struct BlockJob BlockJob;
+void block_job_txn_del_job(BlockJob *job);
 
 #endif
diff --git a/block/backup.c b/block/backup.c
index 4d011d5a5c..bd31282924 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -207,25 +207,25 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
     }
 }
 
-static void backup_commit(BlockJob *job)
+static void backup_commit(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     if (s->sync_bitmap) {
         backup_cleanup_sync_bitmap(s, 0);
     }
 }
 
-static void backup_abort(BlockJob *job)
+static void backup_abort(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     if (s->sync_bitmap) {
         backup_cleanup_sync_bitmap(s, -1);
     }
 }
 
-static void backup_clean(BlockJob *job)
+static void backup_clean(Job *job)
 {
-    BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+    BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
     assert(s->target);
     blk_unref(s->target);
     s->target = NULL;
@@ -530,10 +530,10 @@ static const BlockJobDriver backup_job_driver = {
         .free                   = block_job_free,
         .user_resume            = block_job_user_resume,
         .start                  = backup_run,
+        .commit                 = backup_commit,
+        .abort                  = backup_abort,
+        .clean                  = backup_clean,
     },
-    .commit                 = backup_commit,
-    .abort                  = backup_abort,
-    .clean                  = backup_clean,
     .attached_aio_context   = backup_attached_aio_context,
     .drain                  = backup_drain,
 };
@@ -678,8 +678,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
         bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
     }
     if (job) {
-        backup_clean(&job->common);
-        block_job_early_fail(&job->common);
+        backup_clean(&job->common.job);
+        job_early_fail(&job->common.job);
     }
 
     return NULL;
diff --git a/block/commit.c b/block/commit.c
index 7a6ae59d42..e53b2d7d55 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -385,7 +385,7 @@ fail:
     if (commit_top_bs) {
         bdrv_replace_node(commit_top_bs, top, &error_abort);
     }
-    block_job_early_fail(&s->common);
+    job_early_fail(&s->common.job);
 }
 
 
diff --git a/block/mirror.c b/block/mirror.c
index 5091e72554..e9a90ea730 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1257,7 +1257,7 @@ fail:
 
         g_free(s->replaces);
         blk_unref(s->target);
-        block_job_early_fail(&s->common);
+        job_early_fail(&s->common.job);
     }
 
     bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
diff --git a/blockjob.c b/blockjob.c
index 05d7921b3f..34c57da304 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -127,7 +127,7 @@ void block_job_txn_add_job(BlockJobTxn *txn, BlockJob *job)
     block_job_txn_ref(txn);
 }
 
-static void block_job_txn_del_job(BlockJob *job)
+void block_job_txn_del_job(BlockJob *job)
 {
     if (job->txn) {
         QLIST_REMOVE(job, txn_list);
@@ -262,101 +262,12 @@ const BlockJobDriver *block_job_driver(BlockJob *job)
     return job->driver;
 }
 
-static void block_job_decommission(BlockJob *job)
-{
-    assert(job);
-    job->job.busy = false;
-    job->job.paused = false;
-    job->job.deferred_to_main_loop = true;
-    block_job_txn_del_job(job);
-    job_state_transition(&job->job, JOB_STATUS_NULL);
-    job_unref(&job->job);
-}
-
-static void block_job_do_dismiss(BlockJob *job)
-{
-    block_job_decommission(job);
-}
-
-static void block_job_conclude(BlockJob *job)
-{
-    job_state_transition(&job->job, JOB_STATUS_CONCLUDED);
-    if (job->job.auto_dismiss || !job_started(&job->job)) {
-        block_job_do_dismiss(job);
-    }
-}
-
-static void block_job_update_rc(BlockJob *job)
-{
-    if (!job->ret && job_is_cancelled(&job->job)) {
-        job->ret = -ECANCELED;
-    }
-    if (job->ret) {
-        job_state_transition(&job->job, JOB_STATUS_ABORTING);
-    }
-}
-
 static int block_job_prepare(BlockJob *job)
 {
-    if (job->ret == 0 && job->driver->prepare) {
-        job->ret = job->driver->prepare(job);
-    }
-    return job->ret;
-}
-
-static void block_job_commit(BlockJob *job)
-{
-    assert(!job->ret);
-    if (job->driver->commit) {
-        job->driver->commit(job);
-    }
-}
-
-static void block_job_abort(BlockJob *job)
-{
-    assert(job->ret);
-    if (job->driver->abort) {
-        job->driver->abort(job);
-    }
-}
-
-static void block_job_clean(BlockJob *job)
-{
-    if (job->driver->clean) {
-        job->driver->clean(job);
+    if (job->job.ret == 0 && job->driver->prepare) {
+        job->job.ret = job->driver->prepare(job);
     }
-}
-
-static int block_job_finalize_single(BlockJob *job)
-{
-    assert(job_is_completed(&job->job));
-
-    /* Ensure abort is called for late-transactional failures */
-    block_job_update_rc(job);
-
-    if (!job->ret) {
-        block_job_commit(job);
-    } else {
-        block_job_abort(job);
-    }
-    block_job_clean(job);
-
-    if (job->cb) {
-        job->cb(job->opaque, job->ret);
-    }
-
-    /* Emit events only if we actually started */
-    if (job_started(&job->job)) {
-        if (job_is_cancelled(&job->job)) {
-            job_event_cancelled(&job->job);
-        } else {
-            job_event_completed(&job->job);
-        }
-    }
-
-    block_job_txn_del_job(job);
-    block_job_conclude(job);
-    return 0;
+    return job->job.ret;
 }
 
 static void block_job_cancel_async(BlockJob *job, bool force)
@@ -424,8 +335,8 @@ static int block_job_finish_sync(BlockJob *job,
     while (!job_is_completed(&job->job)) {
         aio_poll(qemu_get_aio_context(), true);
     }
-    ret = (job_is_cancelled(&job->job) && job->ret == 0)
-          ? -ECANCELED : job->ret;
+    ret = (job_is_cancelled(&job->job) && job->job.ret == 0)
+          ? -ECANCELED : job->job.ret;
     job_unref(&job->job);
     return ret;
 }
@@ -466,7 +377,7 @@ static void block_job_completed_txn_abort(BlockJob *job)
             assert(job_is_cancelled(&other_job->job));
             block_job_finish_sync(other_job, NULL, NULL);
         }
-        block_job_finalize_single(other_job);
+        job_finalize_single(&other_job->job);
         aio_context_release(ctx);
     }
 
@@ -478,6 +389,11 @@ static int block_job_needs_finalize(BlockJob *job)
     return !job->job.auto_finalize;
 }
 
+static int block_job_finalize_single(BlockJob *job)
+{
+    return job_finalize_single(&job->job);
+}
+
 static void block_job_do_finalize(BlockJob *job)
 {
     int rc;
@@ -516,7 +432,7 @@ static void block_job_completed_txn_success(BlockJob *job)
         if (!job_is_completed(&other_job->job)) {
             return;
         }
-        assert(other_job->ret == 0);
+        assert(other_job->job.ret == 0);
     }
 
     block_job_txn_apply(txn, block_job_transition_to_pending, false);
@@ -601,14 +517,14 @@ void block_job_dismiss(BlockJob **jobptr, Error **errp)
         return;
     }
 
-    block_job_do_dismiss(job);
+    job_do_dismiss(&job->job);
     *jobptr = NULL;
 }
 
 void block_job_cancel(BlockJob *job, bool force)
 {
     if (job->job.status == JOB_STATUS_CONCLUDED) {
-        block_job_do_dismiss(job);
+        job_do_dismiss(&job->job);
         return;
     }
     block_job_cancel_async(job, force);
@@ -691,8 +607,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
     info->status    = job->job.status;
     info->auto_finalize = job->job.auto_finalize;
     info->auto_dismiss  = job->job.auto_dismiss;
-    info->has_error = job->ret != 0;
-    info->error     = job->ret ? g_strdup(strerror(-job->ret)) : NULL;
+    info->has_error = job->job.ret != 0;
+    info->error     = job->job.ret ? g_strdup(strerror(-job->job.ret)) : NULL;
     return info;
 }
 
@@ -729,8 +645,8 @@ static void block_job_event_completed(Notifier *n, void *opaque)
         return;
     }
 
-    if (job->ret < 0) {
-        msg = strerror(-job->ret);
+    if (job->job.ret < 0) {
+        msg = strerror(-job->job.ret);
     }
 
     qapi_event_send_block_job_completed(job_type(&job->job),
@@ -787,7 +703,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     }
 
     job = job_create(job_id, &driver->job_driver, blk_get_aio_context(blk),
-                     flags, errp);
+                     flags, cb, opaque, errp);
     if (job == NULL) {
         blk_unref(blk);
         return NULL;
@@ -799,8 +715,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
     job->driver        = driver;
     job->blk           = blk;
-    job->cb            = cb;
-    job->opaque        = opaque;
 
     job->finalize_cancelled_notifier.notify = block_job_event_cancelled;
     job->finalize_completed_notifier.notify = block_job_event_completed;
@@ -828,7 +742,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
 
         block_job_set_speed(job, speed, &local_err);
         if (local_err) {
-            block_job_early_fail(job);
+            job_early_fail(&job->job);
             error_propagate(errp, local_err);
             return NULL;
         }
@@ -847,20 +761,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
     return job;
 }
 
-void block_job_early_fail(BlockJob *job)
-{
-    assert(job->job.status == JOB_STATUS_CREATED);
-    block_job_decommission(job);
-}
-
 void block_job_completed(BlockJob *job, int ret)
 {
     assert(job && job->txn && !job_is_completed(&job->job));
     assert(blk_bs(job->blk)->job == job);
-    job->ret = ret;
-    block_job_update_rc(job);
-    trace_block_job_completed(job, ret, job->ret);
-    if (job->ret) {
+    job->job.ret = ret;
+    job_update_rc(&job->job);
+    trace_block_job_completed(job, ret, job->job.ret);
+    if (job->job.ret) {
         block_job_completed_txn_abort(job);
     } else {
         block_job_completed_txn_success(job);
diff --git a/job.c b/job.c
index 748354b479..f9bd8baff2 100644
--- a/job.c
+++ b/job.c
@@ -85,7 +85,7 @@ void job_state_transition(Job *job, JobStatus s1)
 {
     JobStatus s0 = job->status;
     assert(s1 >= 0 && s1 <= JOB_STATUS__MAX);
-    trace_job_state_transition(job, /* TODO re-enable: job->ret */ 0,
+    trace_job_state_transition(job, job->ret,
                                JobSTT[s0][s1] ? "allowed" : "disallowed",
                                JobStatus_str(s0), JobStatus_str(s1));
     assert(JobSTT[s0][s1]);
@@ -182,7 +182,7 @@ static void job_sleep_timer_cb(void *opaque)
 }
 
 void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
-                 int flags, Error **errp)
+                 int flags, BlockCompletionFunc *cb, void *opaque, Error **errp)
 {
     Job *job;
 
@@ -214,6 +214,8 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,
     job->pause_count   = 1;
     job->auto_finalize = !(flags & JOB_MANUAL_FINALIZE);
     job->auto_dismiss  = !(flags & JOB_MANUAL_DISMISS);
+    job->cb            = cb;
+    job->opaque        = opaque;
 
     notifier_list_init(&job->on_finalize_cancelled);
     notifier_list_init(&job->on_finalize_completed);
@@ -449,6 +451,100 @@ void job_user_resume(Job *job, Error **errp)
     job_resume(job);
 }
 
+void job_do_dismiss(Job *job)
+{
+    assert(job);
+    job->busy = false;
+    job->paused = false;
+    job->deferred_to_main_loop = true;
+
+    /* TODO Don't assume it's a BlockJob */
+    block_job_txn_del_job((BlockJob*) job);
+
+    job_state_transition(job, JOB_STATUS_NULL);
+    job_unref(job);
+}
+
+void job_early_fail(Job *job)
+{
+    assert(job->status == JOB_STATUS_CREATED);
+    job_do_dismiss(job);
+}
+
+static void job_conclude(Job *job)
+{
+    job_state_transition(job, JOB_STATUS_CONCLUDED);
+    if (job->auto_dismiss || !job_started(job)) {
+        job_do_dismiss(job);
+    }
+}
+
+void job_update_rc(Job *job)
+{
+    if (!job->ret && job_is_cancelled(job)) {
+        job->ret = -ECANCELED;
+    }
+    if (job->ret) {
+        job_state_transition(job, JOB_STATUS_ABORTING);
+    }
+}
+
+static void job_commit(Job *job)
+{
+    assert(!job->ret);
+    if (job->driver->commit) {
+        job->driver->commit(job);
+    }
+}
+
+static void job_abort(Job *job)
+{
+    assert(job->ret);
+    if (job->driver->abort) {
+        job->driver->abort(job);
+    }
+}
+
+static void job_clean(Job *job)
+{
+    if (job->driver->clean) {
+        job->driver->clean(job);
+    }
+}
+
+int job_finalize_single(Job *job)
+{
+    assert(job_is_completed(job));
+
+    /* Ensure abort is called for late-transactional failures */
+    job_update_rc(job);
+
+    if (!job->ret) {
+        job_commit(job);
+    } else {
+        job_abort(job);
+    }
+    job_clean(job);
+
+    if (job->cb) {
+        job->cb(job->opaque, job->ret);
+    }
+
+    /* Emit events only if we actually started */
+    if (job_started(job)) {
+        if (job_is_cancelled(job)) {
+            job_event_cancelled(job);
+        } else {
+            job_event_completed(job);
+        }
+    }
+
+    /* TODO Don't assume it's a BlockJob */
+    block_job_txn_del_job((BlockJob*) job);
+    job_conclude(job);
+    return 0;
+}
+
 
 typedef struct {
     Job *job;
diff --git a/qemu-img.c b/qemu-img.c
index 12b5818641..81f338273f 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -872,7 +872,7 @@ static void run_block_job(BlockJob *job, Error **errp)
     if (!job_is_completed(&job->job)) {
         ret = block_job_complete_sync(job, errp);
     } else {
-        ret = job->ret;
+        ret = job->job.ret;
     }
     job_unref(&job->job);
     aio_context_release(aio_context);
diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c
index 8bb0aa8f85..1fe6803fe0 100644
--- a/tests/test-blockjob.c
+++ b/tests/test-blockjob.c
@@ -128,11 +128,11 @@ static void test_job_ids(void)
     job[1] = do_test_id(blk[1], "id0", false);
 
     /* But once job[0] finishes we can reuse its ID */
-    block_job_early_fail(job[0]);
+    job_early_fail(&job[0]->job);
     job[1] = do_test_id(blk[1], "id0", true);
 
     /* No job ID specified, defaults to the backend name ('drive1') */
-    block_job_early_fail(job[1]);
+    job_early_fail(&job[1]->job);
     job[1] = do_test_id(blk[1], NULL, true);
 
     /* Duplicate job ID */
@@ -145,9 +145,9 @@ static void test_job_ids(void)
     /* This one is valid */
     job[2] = do_test_id(blk[2], "id_2", true);
 
-    block_job_early_fail(job[0]);
-    block_job_early_fail(job[1]);
-    block_job_early_fail(job[2]);
+    job_early_fail(&job[0]->job);
+    job_early_fail(&job[1]->job);
+    job_early_fail(&job[2]->job);
 
     destroy_blk(blk[0]);
     destroy_blk(blk[1]);
