[systemd-commits] 21 commits - Makefile.am src/core src/tmpfiles

Michal Schmidt michich at kemper.freedesktop.org
Fri Apr 20 08:17:58 PDT 2012


 Makefile.am             |    2 
 src/core/dbus-job.c     |  146 +++---
 src/core/dbus-manager.c |    6 
 src/core/dbus.c         |   14 
 src/core/job.c          |  146 ++++--
 src/core/job.h          |   21 
 src/core/manager.c      | 1114 +-----------------------------------------------
 src/core/manager.h      |    6 
 src/core/transaction.c  | 1057 +++++++++++++++++++++++++++++++++++++++++++++
 src/core/transaction.h  |   35 +
 src/core/unit.c         |    7 
 src/tmpfiles/tmpfiles.c |    2 
 12 files changed, 1350 insertions(+), 1206 deletions(-)

New commits:
commit 65304075249449a713b4e4842b8538ef4aa1c725
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 14:44:00 2012 +0200

    transaction: add starting requirements for JOB_RESTART
    
    While having a Requires= dependency between units, the dependency is started
    automatically on "systemctl start", but it's not started on "systemctl
    restart".
    
    JOB_RESTART jobs did not pull the dependencies for starting into the
    transaction.
    
    https://bugzilla.redhat.com/show_bug.cgi?id=802770
    
    Note that the other bug noted in comment #2 has been fixed already by avoiding
    the deletion of anchor jobs.

diff --git a/src/core/transaction.c b/src/core/transaction.c
index c3e1e13..a2efcbc 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -866,7 +866,7 @@ int transaction_add_job_and_dependencies(
                 }
 
                 /* Finally, recursively add in all dependencies. */
-                if (type == JOB_START || type == JOB_RELOAD_OR_START) {
+                if (type == JOB_START || type == JOB_RELOAD_OR_START || type == JOB_RESTART) {
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
                                 r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {

commit 97e6a11996855800f68dc41c13537784198c8b61
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 12:28:31 2012 +0200

    dbus-job: allow multiple bus clients
    
    Merging of jobs can result in more than one client being interested in a job.

diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c
index 1b86e96..ef839ff 100644
--- a/src/core/dbus-job.c
+++ b/src/core/dbus-job.c
@@ -225,61 +225,71 @@ const DBusObjectPathVTable bus_job_vtable = {
         .message_function = bus_job_message_handler
 };
 
-static int job_send_message(Job *j, DBusMessage *m) {
+static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) {
+        DBusMessage *m = NULL;
         int r;
 
         assert(j);
-        assert(m);
+        assert(new_message);
 
         if (bus_has_subscriber(j->manager)) {
-                if ((r = bus_broadcast(j->manager, m)) < 0)
+                m = new_message(j);
+                if (!m)
+                        goto oom;
+                r = bus_broadcast(j->manager, m);
+                dbus_message_unref(m);
+                if (r < 0)
                         return r;
 
-        } else  if (j->bus_client) {
+        } else {
                 /* If nobody is subscribed, we just send the message
-                 * to the client which created the job */
+                 * to the client(s) which created the job */
+                JobBusClient *cl;
+                assert(j->bus_client_list);
+                LIST_FOREACH(client, cl, j->bus_client_list) {
+                        assert(cl->bus);
+
+                        m = new_message(j);
+                        if (!m)
+                                goto oom;
 
-                assert(j->bus);
+                        if (!dbus_message_set_destination(m, cl->name))
+                                goto oom;
 
-                if (!dbus_message_set_destination(m, j->bus_client))
-                        return -ENOMEM;
+                        if (!dbus_connection_send(cl->bus, m, NULL))
+                                goto oom;
 
-                if (!dbus_connection_send(j->bus, m, NULL))
-                        return -ENOMEM;
+                        dbus_message_unref(m);
+                        m = NULL;
+                }
         }
 
         return 0;
+oom:
+        if (m)
+                dbus_message_unref(m);
+        return -ENOMEM;
 }
 
-void bus_job_send_change_signal(Job *j) {
-        char *p = NULL;
+static DBusMessage* new_change_signal_message(Job *j) {
         DBusMessage *m = NULL;
+        char *p = NULL;
 
-        assert(j);
-
-        if (j->in_dbus_queue) {
-                LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
-                j->in_dbus_queue = false;
-        }
-
-        if (!bus_has_subscriber(j->manager) && !j->bus_client) {
-                j->sent_dbus_new_signal = true;
-                return;
-        }
-
-        if (!(p = job_dbus_path(j)))
+        p = job_dbus_path(j);
+        if (!p)
                 goto oom;
 
         if (j->sent_dbus_new_signal) {
                 /* Send a properties changed signal */
-
-                if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES)))
+                m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES);
+                if (!m)
                         goto oom;
 
         } else {
                 /* Send a new signal */
 
-                if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew")))
+                m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew");
+                if (!m)
                         goto oom;
 
                 if (!dbus_message_append_args(m,
@@ -289,42 +299,26 @@ void bus_job_send_change_signal(Job *j) {
                         goto oom;
         }
 
-        if (job_send_message(j, m) < 0)
-                goto oom;
-
-        free(p);
-        dbus_message_unref(m);
-
-        j->sent_dbus_new_signal = true;
-
-        return;
+        return m;
 
 oom:
-        free(p);
-
         if (m)
                 dbus_message_unref(m);
-
-        log_error("Failed to allocate job change signal.");
+        free(p);
+        return NULL;
 }
 
-void bus_job_send_removed_signal(Job *j) {
-        char *p = NULL;
+static DBusMessage* new_removed_signal_message(Job *j) {
         DBusMessage *m = NULL;
+        char *p = NULL;
         const char *r;
 
-        assert(j);
-
-        if (!bus_has_subscriber(j->manager) && !j->bus_client)
-                return;
-
-        if (!j->sent_dbus_new_signal)
-                bus_job_send_change_signal(j);
-
-        if (!(p = job_dbus_path(j)))
+        p = job_dbus_path(j);
+        if (!p)
                 goto oom;
 
-        if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved")))
+        m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
+        if (!m)
                 goto oom;
 
         r = job_result_to_string(j->result);
@@ -336,19 +330,53 @@ void bus_job_send_removed_signal(Job *j) {
                                       DBUS_TYPE_INVALID))
                 goto oom;
 
-        if (job_send_message(j, m) < 0)
-                goto oom;
+        return m;
 
+oom:
+        if (m)
+                dbus_message_unref(m);
         free(p);
-        dbus_message_unref(m);
+        return NULL;
+}
+
+void bus_job_send_change_signal(Job *j) {
+        assert(j);
+
+        if (j->in_dbus_queue) {
+                LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
+                j->in_dbus_queue = false;
+        }
+
+        if (!bus_has_subscriber(j->manager) && !j->bus_client_list) {
+                j->sent_dbus_new_signal = true;
+                return;
+        }
+
+        if (job_send_message(j, new_change_signal_message) < 0)
+                goto oom;
+
+        j->sent_dbus_new_signal = true;
 
         return;
 
 oom:
-        free(p);
+        log_error("Failed to allocate job change signal.");
+}
 
-        if (m)
-                dbus_message_unref(m);
+void bus_job_send_removed_signal(Job *j) {
+        assert(j);
 
+        if (!bus_has_subscriber(j->manager) && !j->bus_client_list)
+                return;
+
+        if (!j->sent_dbus_new_signal)
+                bus_job_send_change_signal(j);
+
+        if (job_send_message(j, new_removed_signal_message) < 0)
+                goto oom;
+
+        return;
+
+oom:
         log_error("Failed to allocate job remove signal.");
 }
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index e81e6af..efc626a 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -1470,6 +1470,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 const char *name, *smode, *old_name = NULL;
                 JobMode mode;
                 Job *j;
+                JobBusClient *cl;
                 Unit *u;
                 bool b;
 
@@ -1527,10 +1528,11 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0)
                         return bus_send_error_reply(connection, message, &error, r);
 
-                if (!(j->bus_client = strdup(message_get_sender_with_fallback(message))))
+                cl = job_bus_client_new(connection, message_get_sender_with_fallback(message));
+                if (!cl)
                         goto oom;
 
-                j->bus = connection;
+                LIST_PREPEND(JobBusClient, client, j->bus_client_list, cl);
 
                 if (!(reply = dbus_message_new_method_return(message)))
                         goto oom;
diff --git a/src/core/dbus.c b/src/core/dbus.c
index fe73b0a..4347964 100644
--- a/src/core/dbus.c
+++ b/src/core/dbus.c
@@ -1167,13 +1167,15 @@ static void shutdown_connection(Manager *m, DBusConnection *c) {
         Job *j;
         Iterator i;
 
-        HASHMAP_FOREACH(j, m->jobs, i)
-                if (j->bus == c) {
-                        free(j->bus_client);
-                        j->bus_client = NULL;
-
-                        j->bus = NULL;
+        HASHMAP_FOREACH(j, m->jobs, i) {
+                JobBusClient *cl, *nextcl;
+                LIST_FOREACH_SAFE(client, cl, nextcl, j->bus_client_list) {
+                        if (cl->bus == c) {
+                                LIST_REMOVE(JobBusClient, client, j->bus_client_list, cl);
+                                free(cl);
+                        }
                 }
+        }
 
         set_remove(m->bus_connections, c);
         set_remove(m->bus_connections_for_dispatch, c);
diff --git a/src/core/job.c b/src/core/job.c
index 436f4a1..b21de44 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -33,6 +33,20 @@
 #include "log.h"
 #include "dbus-job.h"
 
+JobBusClient* job_bus_client_new(DBusConnection *connection, const char *name) {
+        JobBusClient *cl;
+        size_t name_len;
+
+        name_len = strlen(name);
+        cl = malloc0(sizeof(JobBusClient) + name_len + 1);
+        if (!cl)
+                return NULL;
+
+        cl->bus = connection;
+        memcpy(cl->name, name, name_len + 1);
+        return cl;
+}
+
 Job* job_new(Unit *unit, JobType type) {
         Job *j;
 
@@ -55,6 +69,8 @@ Job* job_new(Unit *unit, JobType type) {
 }
 
 void job_free(Job *j) {
+        JobBusClient *cl;
+
         assert(j);
         assert(!j->installed);
         assert(!j->transaction_prev);
@@ -77,7 +93,10 @@ void job_free(Job *j) {
                 close_nointr_nofail(j->timer_watch.fd);
         }
 
-        free(j->bus_client);
+        while ((cl = j->bus_client_list)) {
+                LIST_REMOVE(JobBusClient, client, j->bus_client_list, cl);
+                free(cl);
+        }
         free(j);
 }
 
diff --git a/src/core/job.h b/src/core/job.h
index 3acf7a2..e869856 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -28,6 +28,7 @@
 
 typedef struct Job Job;
 typedef struct JobDependency JobDependency;
+typedef struct JobBusClient JobBusClient;
 typedef enum JobType JobType;
 typedef enum JobState JobState;
 typedef enum JobMode JobMode;
@@ -99,6 +100,13 @@ struct JobDependency {
         bool conflicts;
 };
 
+struct JobBusClient {
+        LIST_FIELDS(JobBusClient, client);
+        /* Note that this bus object is not ref counted here. */
+        DBusConnection *bus;
+        char name[0];
+};
+
 struct Job {
         Manager *manager;
         Unit *unit;
@@ -121,9 +129,8 @@ struct Job {
 
         Watch timer_watch;
 
-        /* Note that this bus object is not ref counted here. */
-        DBusConnection *bus;
-        char *bus_client;
+        /* There can be more than one client, because of job merging. */
+        LIST_HEAD(JobBusClient, bus_client_list);
 
         JobResult result;
 
@@ -136,6 +143,8 @@ struct Job {
         bool ignore_order:1;
 };
 
+JobBusClient* job_bus_client_new(DBusConnection *connection, const char *name);
+
 Job* job_new(Unit *unit, JobType type);
 void job_free(Job *job);
 Job* job_install(Job *j);

commit d6a093d098054b6fe866441251ad9485c9e31584
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 10:02:05 2012 +0200

    transaction: remove checks for installed
    
    Transactions cannot contain installed jobs anymore. Remove the now pointless
    checks.

diff --git a/src/core/job.c b/src/core/job.c
index 3454ffd..436f4a1 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -83,14 +83,13 @@ void job_free(Job *j) {
 
 void job_uninstall(Job *j) {
         assert(j->installed);
+        assert(j->unit->job == j);
         /* Detach from next 'bigger' objects */
 
         bus_job_send_removed_signal(j);
 
-        if (j->unit->job == j) {
-                j->unit->job = NULL;
-                unit_add_to_gc_queue(j->unit);
-        }
+        j->unit->job = NULL;
+        unit_add_to_gc_queue(j->unit);
 
         hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
         j->installed = false;
diff --git a/src/core/transaction.c b/src/core/transaction.c
index aa0cedf..c3e1e13 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -11,8 +11,7 @@ static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependen
 
         transaction_unlink_job(tr, j, delete_dependencies);
 
-        if (!j->installed)
-                job_free(j);
+        job_free(j);
 }
 
 static void transaction_delete_unit(Transaction *tr, Unit *u) {
@@ -279,7 +278,7 @@ static void transaction_drop_redundant(Transaction *tr) {
                         LIST_FOREACH(transaction, k, j) {
 
                                 if (tr->anchor_job != k &&
-                                    (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) &&
+                                    job_type_is_redundant(k->type, unit_active_state(k->unit)) &&
                                     (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type)))
                                         continue;
 
@@ -349,7 +348,6 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi
                         log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type));
 
                         if (!delete &&
-                            !k->installed &&
                             !unit_matters_to_anchor(k->unit, k)) {
                                 /* Ok, we can drop this one, so let's
                                  * do so. */
@@ -478,7 +476,6 @@ static int transaction_is_destructive(Transaction *tr, DBusError *e) {
                 assert(!j->transaction_next);
 
                 if (j->unit->job &&
-                    j->unit->job != j &&
                     !job_type_is_superset(j->type, j->unit->job->type)) {
 
                         dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive.");
@@ -576,9 +573,6 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
                 assert(!j->transaction_prev);
                 assert(!j->transaction_next);
 
-                if (j->installed)
-                        continue;
-
                 r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
                 if (r < 0)
                         goto rollback;
@@ -587,11 +581,6 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
         while ((j = hashmap_steal_first(tr->jobs))) {
                 Job *installed_job;
 
-                if (j->installed) {
-                        /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
-                        continue;
-                }
-
                 /* Clean the job dependencies */
                 transaction_unlink_job(tr, j, false);
 
@@ -614,12 +603,8 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
 
 rollback:
 
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-                if (j->installed)
-                        continue;
-
+        HASHMAP_FOREACH(j, tr->jobs, i)
                 hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
-        }
 
         return r;
 }

commit 656bbffc6c45bdd8d5c28a96ca948ba16c546547
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 09:38:43 2012 +0200

    transaction: rework merging with installed jobs
    
    Previously transactions could reference installed jobs. It made some issues
    difficult to fix.
    
    This sets new rules for jobs:
    A job cannot be both a member of a transaction and installed. When jobs are
    created, they are linked to a transaction. The whole transaction is constructed
    (with merging of jobs within, etc.). When it's complete, all the jobs are
    unlinked from it one by one and let to install themselves. It is during the
    installation when merging with previously installed jobs (from older
    transactions) is contemplated.
    
    Merging with installed jobs has different rules than merging within a
    transaction:
     - An installed conflicting job gets cancelled. It cannot be simply deleted,
       because someone might be waiting for its completion on DBus.
     - An installed, but still waiting, job can be safely merged into.
     - An installed and running job can be tricky. For some job types it is safe to
       just merge. For the other types we merge anyway, but put the job back into
       JOB_WAITING to allow it to run again. This may be suboptimal, but it is not
       currently possible to have more than one installed job for a unit.
    
    Note this also fixes a bug where the anchor job could be deleted during merging
    within the transaction.

diff --git a/src/core/job.c b/src/core/job.c
index 0b50888..3454ffd 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -96,18 +96,70 @@ void job_uninstall(Job *j) {
         j->installed = false;
 }
 
-void job_install(Job *j) {
+static bool job_type_allows_late_merge(JobType t) {
+        /* Tells whether it is OK to merge a job of type 't' with an already
+         * running job.
+         * Reloads cannot be merged this way. Think of the sequence:
+         * 1. Reload of a daemon is in progress; the daemon has already loaded
+         *    its config file, but hasn't completed the reload operation yet.
+         * 2. Edit foo's config file.
+         * 3. Trigger another reload to have the daemon use the new config.
+         * Should the second reload job be merged into the first one, the daemon
+         * would not know about the new config.
+         * JOB_RESTART jobs on the other hand can be merged, because they get
+         * patched into JOB_START after stopping the unit. So if we see a
+         * JOB_RESTART running, it means the unit hasn't stopped yet and at
+         * this time the merge is still allowed. */
+        return !(t == JOB_RELOAD || t == JOB_RELOAD_OR_START);
+}
+
+static void job_merge_into_installed(Job *j, Job *other) {
+        assert(j->installed);
+        assert(j->unit == other->unit);
+
+        j->type = job_type_lookup_merge(j->type, other->type);
+        assert(j->type >= 0);
+
+        j->override = j->override || other->override;
+}
+
+Job* job_install(Job *j) {
         Job *uj = j->unit->job;
 
+        assert(!j->installed);
+
         if (uj) {
-                job_uninstall(uj);
-                job_free(uj);
+                if (job_type_is_conflicting(uj->type, j->type))
+                        job_finish_and_invalidate(uj, JOB_CANCELED);
+                else {
+                        /* not conflicting, i.e. mergeable */
+
+                        if (uj->state == JOB_WAITING ||
+                            (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) {
+                                job_merge_into_installed(uj, j);
+                                log_debug("Merged into installed job %s/%s as %u",
+                                          uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id);
+                                return uj;
+                        } else {
+                                /* already running and not safe to merge into */
+                                /* Patch uj to become a merged job and re-run it. */
+                                /* XXX It should be safer to queue j to run after uj finishes, but it is
+                                 * not currently possible to have more than one installed job per unit. */
+                                job_merge_into_installed(uj, j);
+                                log_debug("Merged into running job, re-running: %s/%s as %u",
+                                          uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id);
+                                uj->state = JOB_WAITING;
+                                return uj;
+                        }
+                }
         }
 
+        /* Install the job */
         j->unit->job = j;
         j->installed = true;
         j->manager->n_installed_jobs ++;
         log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+        return j;
 }
 
 JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
diff --git a/src/core/job.h b/src/core/job.h
index 0110126..3acf7a2 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -138,7 +138,7 @@ struct Job {
 
 Job* job_new(Unit *unit, JobType type);
 void job_free(Job *job);
-void job_install(Job *j);
+Job* job_install(Job *j);
 void job_uninstall(Job *j);
 void job_dump(Job *j, FILE*f, const char *prefix);
 
diff --git a/src/core/transaction.c b/src/core/transaction.c
index d495cbd..aa0cedf 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -243,21 +243,14 @@ static int transaction_merge_jobs(Transaction *tr, DBusError *e) {
                 LIST_FOREACH(transaction, k, j->transaction_next)
                         assert_se(job_type_merge(&t, k->type) == 0);
 
-                /* If an active job is mergeable, merge it too */
-                if (j->unit->job)
-                        job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */
-
                 while ((k = j->transaction_next)) {
-                        if (j->installed) {
+                        if (tr->anchor_job == k) {
                                 transaction_merge_and_delete_job(tr, k, j, t);
                                 j = k;
                         } else
                                 transaction_merge_and_delete_job(tr, j, k, t);
                 }
 
-                if (j->unit->job && !j->installed)
-                        transaction_merge_and_delete_job(tr, j, j->unit->job, t);
-
                 assert(!j->transaction_next);
                 assert(!j->transaction_prev);
         }
@@ -592,6 +585,8 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
         }
 
         while ((j = hashmap_steal_first(tr->jobs))) {
+                Job *installed_job;
+
                 if (j->installed) {
                         /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
                         continue;
@@ -600,7 +595,15 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
                 /* Clean the job dependencies */
                 transaction_unlink_job(tr, j, false);
 
-                job_install(j);
+                installed_job = job_install(j);
+                if (installed_job != j) {
+                        /* j has been merged into a previously installed job */
+                        if (tr->anchor_job == j)
+                                tr->anchor_job = installed_job;
+                        hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
+                        job_free(j);
+                        j = installed_job;
+                }
 
                 job_add_to_run_queue(j);
                 job_add_to_dbus_queue(j);

commit 05d576f1f77699ea5c2e5ed0e04451b14dfc45a0
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 02:04:01 2012 +0200

    job: separate job_install()
    
    Let the jobs install themselves.

diff --git a/src/core/job.c b/src/core/job.c
index aa7cdbf..0b50888 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -54,21 +54,6 @@ Job* job_new(Unit *unit, JobType type) {
         return j;
 }
 
-void job_uninstall(Job *j) {
-        assert(j->installed);
-        /* Detach from next 'bigger' objects */
-
-        bus_job_send_removed_signal(j);
-
-        if (j->unit->job == j) {
-                j->unit->job = NULL;
-                unit_add_to_gc_queue(j->unit);
-        }
-
-        hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
-        j->installed = false;
-}
-
 void job_free(Job *j) {
         assert(j);
         assert(!j->installed);
@@ -96,6 +81,35 @@ void job_free(Job *j) {
         free(j);
 }
 
+void job_uninstall(Job *j) {
+        assert(j->installed);
+        /* Detach from next 'bigger' objects */
+
+        bus_job_send_removed_signal(j);
+
+        if (j->unit->job == j) {
+                j->unit->job = NULL;
+                unit_add_to_gc_queue(j->unit);
+        }
+
+        hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
+        j->installed = false;
+}
+
+void job_install(Job *j) {
+        Job *uj = j->unit->job;
+
+        if (uj) {
+                job_uninstall(uj);
+                job_free(uj);
+        }
+
+        j->unit->job = j;
+        j->installed = true;
+        j->manager->n_installed_jobs ++;
+        log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+}
+
 JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
         JobDependency *l;
 
diff --git a/src/core/job.h b/src/core/job.h
index 4e0c7ca..0110126 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -137,8 +137,9 @@ struct Job {
 };
 
 Job* job_new(Unit *unit, JobType type);
-void job_uninstall(Job *j);
 void job_free(Job *job);
+void job_install(Job *j);
+void job_uninstall(Job *j);
 void job_dump(Job *j, FILE*f, const char *prefix);
 
 JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
diff --git a/src/core/transaction.c b/src/core/transaction.c
index 41f7b82..d495cbd 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -592,33 +592,19 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
         }
 
         while ((j = hashmap_steal_first(tr->jobs))) {
-                Job *uj;
                 if (j->installed) {
                         /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
                         continue;
                 }
 
-                uj = j->unit->job;
-                if (uj) {
-                        job_uninstall(uj);
-                        job_free(uj);
-                }
-
-                j->unit->job = j;
-                j->installed = true;
-                m->n_installed_jobs ++;
-
-                /* We're fully installed. Now let's free data we don't
-                 * need anymore. */
-
                 /* Clean the job dependencies */
                 transaction_unlink_job(tr, j, false);
 
+                job_install(j);
+
                 job_add_to_run_queue(j);
                 job_add_to_dbus_queue(j);
                 job_start_timer(j);
-
-                log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
         }
 
         return 0;

commit f1c2bdca422dba1bc5615f72662dee5ce69c147b
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Thu Apr 19 23:56:26 2012 +0200

    transaction: remove a couple of asserts
    
    We already asserted these facts in the previous loop.

diff --git a/src/core/transaction.c b/src/core/transaction.c
index 39cfe54..41f7b82 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -611,9 +611,6 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
                 /* We're fully installed. Now let's free data we don't
                  * need anymore. */
 
-                assert(!j->transaction_next);
-                assert(!j->transaction_prev);
-
                 /* Clean the job dependencies */
                 transaction_unlink_job(tr, j, false);
 

commit e6eda1f23efab618bb26e7015230d8552b401dc6
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 02:11:14 2012 +0200

    transaction: remove the anchor link
    
    tr->anchor_job is sufficient.

diff --git a/src/core/job.c b/src/core/job.c
index 18ec823..aa7cdbf 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -151,18 +151,6 @@ void job_dump(Job *j, FILE*f, const char *prefix) {
                 prefix, yes_no(j->override));
 }
 
-bool job_is_anchor(Job *j) {
-        JobDependency *l;
-
-        assert(j);
-
-        LIST_FOREACH(object, l, j->object_list)
-                if (!l->subject)
-                        return true;
-
-        return false;
-}
-
 /*
  * Merging is commutative, so imagine the matrix as symmetric. We store only
  * its lower triangle to avoid duplication. We don't store the main diagonal,
diff --git a/src/core/job.h b/src/core/job.h
index c7cf58d..4e0c7ca 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -144,8 +144,6 @@ void job_dump(Job *j, FILE*f, const char *prefix);
 JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
 void job_dependency_free(JobDependency *l);
 
-bool job_is_anchor(Job *j);
-
 int job_merge(Job *j, Job *other);
 
 JobType job_type_lookup_merge(JobType a, JobType b);
diff --git a/src/core/transaction.c b/src/core/transaction.c
index ddb02c0..39cfe54 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -34,8 +34,6 @@ void transaction_abort(Transaction *tr) {
                 transaction_delete_job(tr, j, true);
 
         assert(hashmap_isempty(tr->jobs));
-
-        assert(!tr->anchor);
 }
 
 static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) {
@@ -287,7 +285,7 @@ static void transaction_drop_redundant(Transaction *tr) {
 
                         LIST_FOREACH(transaction, k, j) {
 
-                                if (!job_is_anchor(k) &&
+                                if (tr->anchor_job != k &&
                                     (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) &&
                                     (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type)))
                                         continue;
@@ -626,8 +624,6 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
                 log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
         }
 
-        assert(!tr->anchor);
-
         return 0;
 
 rollback:
@@ -726,7 +722,6 @@ int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e
         }
 
         assert(hashmap_isempty(tr->jobs));
-        assert(!tr->anchor);
 
         return 0;
 }
@@ -778,12 +773,6 @@ static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, b
         return j;
 }
 
-static void transaction_job_dependency_free(Transaction *tr, JobDependency *l) {
-        if (!l->subject)
-                LIST_REMOVE(JobDependency, subject, tr->anchor, l);
-        job_dependency_free(l);
-}
-
 static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
         assert(tr);
         assert(j);
@@ -801,12 +790,12 @@ static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependen
         j->transaction_prev = j->transaction_next = NULL;
 
         while (j->subject_list)
-                transaction_job_dependency_free(tr, j->subject_list);
+                job_dependency_free(j->subject_list);
 
         while (j->object_list) {
                 Job *other = j->object_list->matters ? j->object_list->subject : NULL;
 
-                transaction_job_dependency_free(tr, j->object_list);
+                job_dependency_free(j->object_list);
 
                 if (other && delete_dependencies) {
                         log_debug("Deleting job %s/%s as dependency of job %s/%s",
@@ -829,7 +818,6 @@ int transaction_add_job_and_dependencies(
                 bool ignore_order,
                 DBusError *e) {
         Job *ret;
-        JobDependency *l;
         Iterator i;
         Unit *dep;
         int r;
@@ -879,17 +867,14 @@ int transaction_add_job_and_dependencies(
         ret->ignore_order = ret->ignore_order || ignore_order;
 
         /* Then, add a link to the job. */
-        l = job_dependency_new(by, ret, matters, conflicts);
-        if (!l)
-                return -ENOMEM;
-
-        /* If the link has no subject job, it's the anchor link. */
-        if (!by) {
-                LIST_PREPEND(JobDependency, subject, tr->anchor, l);
+        if (by) {
+                if (!job_dependency_new(by, ret, matters, conflicts))
+                        return -ENOMEM;
+        } else {
+                /* If the job has no parent job, it is the anchor job. */
                 assert(!tr->anchor_job);
                 tr->anchor_job = ret;
         }
-
         if (is_new && !ignore_requirements) {
                 Set *following;
 
diff --git a/src/core/transaction.h b/src/core/transaction.h
index 4818cea..74d7461 100644
--- a/src/core/transaction.h
+++ b/src/core/transaction.h
@@ -11,7 +11,6 @@ typedef struct Transaction Transaction;
 struct Transaction {
         /* Jobs to be added */
         Hashmap *jobs;      /* Unit object => Job object list 1:1 */
-        JobDependency *anchor;
         Job *anchor_job;      /* the job the user asked for */
 };
 

commit 38809d9dfed4c75d9e97c4e5da2ff957723c4cad
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 02:48:24 2012 +0200

    transaction: avoid garbage collecting the anchor job
    
    Make sure the anchor job is never considered garbage, even if it has no links
    leading to it (this will be allowed in the next patch).

diff --git a/src/core/transaction.c b/src/core/transaction.c
index cac58e6..ddb02c0 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -454,7 +454,7 @@ static void transaction_collect_garbage(Transaction *tr) {
                 again = false;
 
                 HASHMAP_FOREACH(j, tr->jobs, i) {
-                        if (j->object_list) {
+                        if (tr->anchor_job == j || j->object_list) {
                                 /* log_debug("Keeping job %s/%s because of %s/%s", */
                                 /*           j->unit->id, job_type_to_string(j->type), */
                                 /*           j->object_list->subject ? j->object_list->subject->unit->id : "root", */

commit 0d9989aa68963037a18fb7ed4f309f6155927d70
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 01:20:14 2012 +0200

    transaction: simplify transaction_find_jobs_that_matter_to_anchor()

diff --git a/src/core/transaction.c b/src/core/transaction.c
index 09abe00..cac58e6 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -38,22 +38,18 @@ void transaction_abort(Transaction *tr) {
         assert(!tr->anchor);
 }
 
-static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, Job *j, unsigned generation) {
+static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) {
         JobDependency *l;
 
-        assert(tr);
-
         /* A recursive sweep through the graph that marks all units
          * that matter to the anchor job, i.e. are directly or
          * indirectly a dependency of the anchor job via paths that
          * are fully marked as mattering. */
 
-        if (j)
-                l = j->subject_list;
-        else
-                l = tr->anchor;
+        j->matters_to_anchor = true;
+        j->generation = generation;
 
-        LIST_FOREACH(subject, l, l) {
+        LIST_FOREACH(subject, l, j->subject_list) {
 
                 /* This link does not matter */
                 if (!l->matters)
@@ -63,10 +59,7 @@ static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, Job *j,
                 if (l->object->generation == generation)
                         continue;
 
-                l->object->matters_to_anchor = true;
-                l->object->generation = generation;
-
-                transaction_find_jobs_that_matter_to_anchor(tr, l->object, generation);
+                transaction_find_jobs_that_matter_to_anchor(l->object, generation);
         }
 }
 
@@ -659,7 +652,7 @@ int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e
          * the actual list of jobs, if possible. */
 
         /* First step: figure out which jobs matter */
-        transaction_find_jobs_that_matter_to_anchor(tr, NULL, generation++);
+        transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++);
 
         /* Second step: Try not to stop any running services if
          * we don't have to. Don't try to reverse running

commit 4483f694983382b4092e3e295ffb59926cff96d9
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 10:33:37 2012 +0200

    transaction: change the linking of isolate jobs to the anchor
    
    When isolating, the JOB_STOP jobs have no parent job, so they are all peers
    of the real anchor job. This is a bit odd.
    
    Link them from the anchor job.

diff --git a/src/core/transaction.c b/src/core/transaction.c
index d7ecfdb..09abe00 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -893,8 +893,8 @@ int transaction_add_job_and_dependencies(
         /* If the link has no subject job, it's the anchor link. */
         if (!by) {
                 LIST_PREPEND(JobDependency, subject, tr->anchor, l);
-                if (!tr->anchor_job)
-                        tr->anchor_job = ret;
+                assert(!tr->anchor_job);
+                tr->anchor_job = ret;
         }
 
         if (is_new && !ignore_requirements) {
@@ -1077,7 +1077,7 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
                 if (hashmap_get(tr->jobs, u))
                         continue;
 
-                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL);
+                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, false, NULL);
                 if (r < 0)
                         log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
         }

commit b94fbd30781d7533492cec65bf7d86fa19a1a074
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 00:33:26 2012 +0200

    transaction: maintain anchor_job
    
    Track which job is the anchor in the transaction.

diff --git a/src/core/manager.c b/src/core/manager.c
index f070295..636aaa3 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -656,7 +656,6 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
 
 int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) {
         int r;
-        Job *ret;
         Transaction *tr;
 
         assert(m);
@@ -682,7 +681,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
 
         r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, override, false,
                                                  mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS,
-                                                 mode == JOB_IGNORE_DEPENDENCIES, e, &ret);
+                                                 mode == JOB_IGNORE_DEPENDENCIES, e);
         if (r < 0)
                 goto tr_abort;
 
@@ -696,10 +695,10 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
         if (r < 0)
                 goto tr_abort;
 
-        log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) ret->id);
+        log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) tr->anchor_job->id);
 
         if (_ret)
-                *_ret = ret;
+                *_ret = tr->anchor_job;
 
         transaction_free(tr);
         return 0;
diff --git a/src/core/transaction.c b/src/core/transaction.c
index c00dd45..d7ecfdb 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -834,8 +834,7 @@ int transaction_add_job_and_dependencies(
                 bool conflicts,
                 bool ignore_requirements,
                 bool ignore_order,
-                DBusError *e,
-                Job **_ret) {
+                DBusError *e) {
         Job *ret;
         JobDependency *l;
         Iterator i;
@@ -892,8 +891,11 @@ int transaction_add_job_and_dependencies(
                 return -ENOMEM;
 
         /* If the link has no subject job, it's the anchor link. */
-        if (!by)
+        if (!by) {
                 LIST_PREPEND(JobDependency, subject, tr->anchor, l);
+                if (!tr->anchor_job)
+                        tr->anchor_job = ret;
+        }
 
         if (is_new && !ignore_requirements) {
                 Set *following;
@@ -902,7 +904,7 @@ int transaction_add_job_and_dependencies(
                  * add all dependencies of everybody following. */
                 if (unit_following_set(ret->unit, &following) > 0) {
                         SET_FOREACH(dep, following, i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -917,7 +919,7 @@ int transaction_add_job_and_dependencies(
                 /* Finally, recursively add in all dependencies. */
                 if (type == JOB_START || type == JOB_RELOAD_OR_START) {
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -928,7 +930,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -939,7 +941,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -949,7 +951,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -959,7 +961,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -970,7 +972,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -980,7 +982,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -991,7 +993,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -1005,7 +1007,7 @@ int transaction_add_job_and_dependencies(
                 if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -1016,7 +1018,7 @@ int transaction_add_job_and_dependencies(
                         }
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
@@ -1030,7 +1032,7 @@ int transaction_add_job_and_dependencies(
                 if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) {
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -1043,9 +1045,6 @@ int transaction_add_job_and_dependencies(
                 /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
         }
 
-        if (_ret)
-                *_ret = ret;
-
         return 0;
 
 fail:
@@ -1078,7 +1077,7 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
                 if (hashmap_get(tr->jobs, u))
                         continue;
 
-                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL);
+                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL);
                 if (r < 0)
                         log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
         }
diff --git a/src/core/transaction.h b/src/core/transaction.h
index d519ffb..4818cea 100644
--- a/src/core/transaction.h
+++ b/src/core/transaction.h
@@ -12,6 +12,7 @@ struct Transaction {
         /* Jobs to be added */
         Hashmap *jobs;      /* Unit object => Job object list 1:1 */
         JobDependency *anchor;
+        Job *anchor_job;      /* the job the user asked for */
 };
 
 Transaction *transaction_new(void);
@@ -27,8 +28,7 @@ int transaction_add_job_and_dependencies(
                 bool conflicts,
                 bool ignore_requirements,
                 bool ignore_order,
-                DBusError *e,
-                Job **_ret);
+                DBusError *e);
 int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e);
 int transaction_add_isolate_jobs(Transaction *tr, Manager *m);
 void transaction_abort(Transaction *tr);

commit 3c956cfee29fe2642fbe257f55adb3583a4af878
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Wed Apr 18 18:15:49 2012 +0200

    transaction: do not add installed jobs to the transaction
    
    Do not attempt to optimize away the job creation by refering to installed jobs.
    We do not want to disturb installed jobs until commiting the transaction.
    
    (A later patch to job merging will make the separation of transaction jobs and
    installed jobs complete.)

diff --git a/src/core/transaction.c b/src/core/transaction.c
index 2a6f1de..c00dd45 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -760,13 +760,9 @@ static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, b
                 }
         }
 
-        if (unit->job && unit->job->type == type)
-                j = unit->job;
-        else {
-                j = job_new(unit, type);
-                if (!j)
-                        return NULL;
-        }
+        j = job_new(unit, type);
+        if (!j)
+                return NULL;
 
         j->generation = 0;
         j->marker = NULL;

commit 1da4264fbd0fa5e6b1bd5e0ac4674a3503774369
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Wed Apr 18 15:21:24 2012 +0200

    job: jobs shouldn't need to know about transaction anchors
    
    Let the transactions maintain their own anchor links.

diff --git a/src/core/job.c b/src/core/job.c
index bfb6b74..18ec823 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -96,7 +96,7 @@ void job_free(Job *j) {
         free(j);
 }
 
-JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts, Transaction *tr) {
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
         JobDependency *l;
 
         assert(object);
@@ -116,21 +116,17 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool
 
         if (subject)
                 LIST_PREPEND(JobDependency, subject, subject->subject_list, l);
-        else
-                LIST_PREPEND(JobDependency, subject, tr->anchor, l);
 
         LIST_PREPEND(JobDependency, object, object->object_list, l);
 
         return l;
 }
 
-void job_dependency_free(JobDependency *l, Transaction *tr) {
+void job_dependency_free(JobDependency *l) {
         assert(l);
 
         if (l->subject)
                 LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l);
-        else
-                LIST_REMOVE(JobDependency, subject, tr->anchor, l);
 
         LIST_REMOVE(JobDependency, object, l->object->object_list, l);
 
diff --git a/src/core/job.h b/src/core/job.h
index 35c213d..c7cf58d 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -34,7 +34,6 @@ typedef enum JobMode JobMode;
 typedef enum JobResult JobResult;
 
 #include "manager.h"
-#include "transaction.h"
 #include "unit.h"
 #include "hashmap.h"
 #include "list.h"
@@ -142,8 +141,8 @@ void job_uninstall(Job *j);
 void job_free(Job *job);
 void job_dump(Job *j, FILE*f, const char *prefix);
 
-JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts, Transaction *tr);
-void job_dependency_free(JobDependency *l, Transaction *tr);
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
+void job_dependency_free(JobDependency *l);
 
 bool job_is_anchor(Job *j);
 
diff --git a/src/core/transaction.c b/src/core/transaction.c
index 1344e2f..2a6f1de 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -789,6 +789,12 @@ static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, b
         return j;
 }
 
+static void transaction_job_dependency_free(Transaction *tr, JobDependency *l) {
+        if (!l->subject)
+                LIST_REMOVE(JobDependency, subject, tr->anchor, l);
+        job_dependency_free(l);
+}
+
 static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
         assert(tr);
         assert(j);
@@ -806,12 +812,12 @@ static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependen
         j->transaction_prev = j->transaction_next = NULL;
 
         while (j->subject_list)
-                job_dependency_free(j->subject_list, tr);
+                transaction_job_dependency_free(tr, j->subject_list);
 
         while (j->object_list) {
                 Job *other = j->object_list->matters ? j->object_list->subject : NULL;
 
-                job_dependency_free(j->object_list, tr);
+                transaction_job_dependency_free(tr, j->object_list);
 
                 if (other && delete_dependencies) {
                         log_debug("Deleting job %s/%s as dependency of job %s/%s",
@@ -835,6 +841,7 @@ int transaction_add_job_and_dependencies(
                 DBusError *e,
                 Job **_ret) {
         Job *ret;
+        JobDependency *l;
         Iterator i;
         Unit *dep;
         int r;
@@ -884,9 +891,14 @@ int transaction_add_job_and_dependencies(
         ret->ignore_order = ret->ignore_order || ignore_order;
 
         /* Then, add a link to the job. */
-        if (!job_dependency_new(by, ret, matters, conflicts, tr))
+        l = job_dependency_new(by, ret, matters, conflicts);
+        if (!l)
                 return -ENOMEM;
 
+        /* If the link has no subject job, it's the anchor link. */
+        if (!by)
+                LIST_PREPEND(JobDependency, subject, tr->anchor, l);
+
         if (is_new && !ignore_requirements) {
                 Set *following;
 

commit 668ad332a404736474749cbcc8404af3e4447170
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Wed Apr 18 01:39:20 2012 +0200

    job: job_new() can find the manager from the unit

diff --git a/src/core/job.c b/src/core/job.c
index 543f37d..bfb6b74 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -33,18 +33,17 @@
 #include "log.h"
 #include "dbus-job.h"
 
-Job* job_new(Manager *m, JobType type, Unit *unit) {
+Job* job_new(Unit *unit, JobType type) {
         Job *j;
 
-        assert(m);
         assert(type < _JOB_TYPE_MAX);
         assert(unit);
 
         if (!(j = new0(Job, 1)))
                 return NULL;
 
-        j->manager = m;
-        j->id = m->current_job_id++;
+        j->manager = unit->manager;
+        j->id = j->manager->current_job_id++;
         j->type = type;
         j->unit = unit;
 
diff --git a/src/core/job.h b/src/core/job.h
index e25fc26..35c213d 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -137,7 +137,7 @@ struct Job {
         bool ignore_order:1;
 };
 
-Job* job_new(Manager *m, JobType type, Unit *unit);
+Job* job_new(Unit *unit, JobType type);
 void job_uninstall(Job *j);
 void job_free(Job *job);
 void job_dump(Job *j, FILE*f, const char *prefix);
diff --git a/src/core/transaction.c b/src/core/transaction.c
index 8fa89a7..1344e2f 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -763,7 +763,7 @@ static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, b
         if (unit->job && unit->job->type == type)
                 j = unit->job;
         else {
-                j = job_new(unit->manager, type, unit);
+                j = job_new(unit, type);
                 if (!j)
                         return NULL;
         }

commit 75778e21dfeee51036d24501e39ea7398fabe502
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Thu Apr 19 23:54:11 2012 +0200

    manager: split transaction.[ch]
    
    manager.c takes care of the main loop, unit management, signal handling, ...
    transaction.c computes transactions.
    
    After split:
    manager.c:     65 KB
    transaction.c: 40 KB

diff --git a/Makefile.am b/Makefile.am
index 4d13eea..cc284a7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -681,6 +681,8 @@ libsystemd_core_la_SOURCES = \
 	src/core/job.h \
 	src/core/manager.c \
 	src/core/manager.h \
+	src/core/transaction.c \
+	src/core/transaction.h \
 	src/core/load-fragment.c \
 	src/core/load-fragment.h \
 	src/core/service.c \
diff --git a/src/core/job.h b/src/core/job.h
index 4c543f2..e25fc26 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -34,6 +34,7 @@ typedef enum JobMode JobMode;
 typedef enum JobResult JobResult;
 
 #include "manager.h"
+#include "transaction.h"
 #include "unit.h"
 #include "hashmap.h"
 #include "list.h"
diff --git a/src/core/manager.c b/src/core/manager.c
index 52702f3..f070295 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -44,6 +44,7 @@
 #include <systemd/sd-daemon.h>
 
 #include "manager.h"
+#include "transaction.h"
 #include "hashmap.h"
 #include "macro.h"
 #include "strv.h"
@@ -653,1105 +654,6 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         return r;
 }
 
-static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies);
-
-static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) {
-        assert(tr);
-        assert(j);
-
-        /* Deletes one job from the transaction */
-
-        transaction_unlink_job(tr, j, delete_dependencies);
-
-        if (!j->installed)
-                job_free(j);
-}
-
-static void transaction_delete_unit(Transaction *tr, Unit *u) {
-        Job *j;
-
-        /* Deletes all jobs associated with a certain unit from the
-         * transaction */
-
-        while ((j = hashmap_get(tr->jobs, u)))
-                transaction_delete_job(tr, j, true);
-}
-
-static void transaction_abort(Transaction *tr) {
-        Job *j;
-
-        assert(tr);
-
-        while ((j = hashmap_first(tr->jobs)))
-                transaction_delete_job(tr, j, true);
-
-        assert(hashmap_isempty(tr->jobs));
-
-        assert(!tr->anchor);
-}
-
-static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, Job *j, unsigned generation) {
-        JobDependency *l;
-
-        assert(tr);
-
-        /* A recursive sweep through the graph that marks all units
-         * that matter to the anchor job, i.e. are directly or
-         * indirectly a dependency of the anchor job via paths that
-         * are fully marked as mattering. */
-
-        if (j)
-                l = j->subject_list;
-        else
-                l = tr->anchor;
-
-        LIST_FOREACH(subject, l, l) {
-
-                /* This link does not matter */
-                if (!l->matters)
-                        continue;
-
-                /* This unit has already been marked */
-                if (l->object->generation == generation)
-                        continue;
-
-                l->object->matters_to_anchor = true;
-                l->object->generation = generation;
-
-                transaction_find_jobs_that_matter_to_anchor(tr, l->object, generation);
-        }
-}
-
-static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) {
-        JobDependency *l, *last;
-
-        assert(j);
-        assert(other);
-        assert(j->unit == other->unit);
-        assert(!j->installed);
-
-        /* Merges 'other' into 'j' and then deletes 'other'. */
-
-        j->type = t;
-        j->state = JOB_WAITING;
-        j->override = j->override || other->override;
-
-        j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor;
-
-        /* Patch us in as new owner of the JobDependency objects */
-        last = NULL;
-        LIST_FOREACH(subject, l, other->subject_list) {
-                assert(l->subject == other);
-                l->subject = j;
-                last = l;
-        }
-
-        /* Merge both lists */
-        if (last) {
-                last->subject_next = j->subject_list;
-                if (j->subject_list)
-                        j->subject_list->subject_prev = last;
-                j->subject_list = other->subject_list;
-        }
-
-        /* Patch us in as new owner of the JobDependency objects */
-        last = NULL;
-        LIST_FOREACH(object, l, other->object_list) {
-                assert(l->object == other);
-                l->object = j;
-                last = l;
-        }
-
-        /* Merge both lists */
-        if (last) {
-                last->object_next = j->object_list;
-                if (j->object_list)
-                        j->object_list->object_prev = last;
-                j->object_list = other->object_list;
-        }
-
-        /* Kill the other job */
-        other->subject_list = NULL;
-        other->object_list = NULL;
-        transaction_delete_job(tr, other, true);
-}
-
-static bool job_is_conflicted_by(Job *j) {
-        JobDependency *l;
-
-        assert(j);
-
-        /* Returns true if this job is pulled in by a least one
-         * ConflictedBy dependency. */
-
-        LIST_FOREACH(object, l, j->object_list)
-                if (l->conflicts)
-                        return true;
-
-        return false;
-}
-
-static int delete_one_unmergeable_job(Transaction *tr, Job *j) {
-        Job *k;
-
-        assert(j);
-
-        /* Tries to delete one item in the linked list
-         * j->transaction_next->transaction_next->... that conflicts
-         * with another one, in an attempt to make an inconsistent
-         * transaction work. */
-
-        /* We rely here on the fact that if a merged with b does not
-         * merge with c, either a or b merge with c neither */
-        LIST_FOREACH(transaction, j, j)
-                LIST_FOREACH(transaction, k, j->transaction_next) {
-                        Job *d;
-
-                        /* Is this one mergeable? Then skip it */
-                        if (job_type_is_mergeable(j->type, k->type))
-                                continue;
-
-                        /* Ok, we found two that conflict, let's see if we can
-                         * drop one of them */
-                        if (!j->matters_to_anchor && !k->matters_to_anchor) {
-
-                                /* Both jobs don't matter, so let's
-                                 * find the one that is smarter to
-                                 * remove. Let's think positive and
-                                 * rather remove stops then starts --
-                                 * except if something is being
-                                 * stopped because it is conflicted by
-                                 * another unit in which case we
-                                 * rather remove the start. */
-
-                                log_debug("Looking at job %s/%s conflicted_by=%s", j->unit->id, job_type_to_string(j->type), yes_no(j->type == JOB_STOP && job_is_conflicted_by(j)));
-                                log_debug("Looking at job %s/%s conflicted_by=%s", k->unit->id, job_type_to_string(k->type), yes_no(k->type == JOB_STOP && job_is_conflicted_by(k)));
-
-                                if (j->type == JOB_STOP) {
-
-                                        if (job_is_conflicted_by(j))
-                                                d = k;
-                                        else
-                                                d = j;
-
-                                } else if (k->type == JOB_STOP) {
-
-                                        if (job_is_conflicted_by(k))
-                                                d = j;
-                                        else
-                                                d = k;
-                                } else
-                                        d = j;
-
-                        } else if (!j->matters_to_anchor)
-                                d = j;
-                        else if (!k->matters_to_anchor)
-                                d = k;
-                        else
-                                return -ENOEXEC;
-
-                        /* Ok, we can drop one, so let's do so. */
-                        log_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type));
-                        transaction_delete_job(tr, d, true);
-                        return 0;
-                }
-
-        return -EINVAL;
-}
-
-static int transaction_merge_jobs(Transaction *tr, DBusError *e) {
-        Job *j;
-        Iterator i;
-        int r;
-
-        assert(tr);
-
-        /* First step, check whether any of the jobs for one specific
-         * task conflict. If so, try to drop one of them. */
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-                JobType t;
-                Job *k;
-
-                t = j->type;
-                LIST_FOREACH(transaction, k, j->transaction_next) {
-                        if (job_type_merge(&t, k->type) >= 0)
-                                continue;
-
-                        /* OK, we could not merge all jobs for this
-                         * action. Let's see if we can get rid of one
-                         * of them */
-
-                        r = delete_one_unmergeable_job(tr, j);
-                        if (r >= 0)
-                                /* Ok, we managed to drop one, now
-                                 * let's ask our callers to call us
-                                 * again after garbage collecting */
-                                return -EAGAIN;
-
-                        /* We couldn't merge anything. Failure */
-                        dbus_set_error(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, "Transaction contains conflicting jobs '%s' and '%s' for %s. Probably contradicting requirement dependencies configured.",
-                                       job_type_to_string(t), job_type_to_string(k->type), k->unit->id);
-                        return r;
-                }
-        }
-
-        /* Second step, merge the jobs. */
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-                JobType t = j->type;
-                Job *k;
-
-                /* Merge all transactions */
-                LIST_FOREACH(transaction, k, j->transaction_next)
-                        assert_se(job_type_merge(&t, k->type) == 0);
-
-                /* If an active job is mergeable, merge it too */
-                if (j->unit->job)
-                        job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */
-
-                while ((k = j->transaction_next)) {
-                        if (j->installed) {
-                                transaction_merge_and_delete_job(tr, k, j, t);
-                                j = k;
-                        } else
-                                transaction_merge_and_delete_job(tr, j, k, t);
-                }
-
-                if (j->unit->job && !j->installed)
-                        transaction_merge_and_delete_job(tr, j, j->unit->job, t);
-
-                assert(!j->transaction_next);
-                assert(!j->transaction_prev);
-        }
-
-        return 0;
-}
-
-static void transaction_drop_redundant(Transaction *tr) {
-        bool again;
-
-        assert(tr);
-
-        /* Goes through the transaction and removes all jobs that are
-         * a noop */
-
-        do {
-                Job *j;
-                Iterator i;
-
-                again = false;
-
-                HASHMAP_FOREACH(j, tr->jobs, i) {
-                        bool changes_something = false;
-                        Job *k;
-
-                        LIST_FOREACH(transaction, k, j) {
-
-                                if (!job_is_anchor(k) &&
-                                    (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) &&
-                                    (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type)))
-                                        continue;
-
-                                changes_something = true;
-                                break;
-                        }
-
-                        if (changes_something)
-                                continue;
-
-                        /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */
-                        transaction_delete_job(tr, j, false);
-                        again = true;
-                        break;
-                }
-
-        } while (again);
-}
-
-static bool unit_matters_to_anchor(Unit *u, Job *j) {
-        assert(u);
-        assert(!j->transaction_prev);
-
-        /* Checks whether at least one of the jobs for this unit
-         * matters to the anchor. */
-
-        LIST_FOREACH(transaction, j, j)
-                if (j->matters_to_anchor)
-                        return true;
-
-        return false;
-}
-
-static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, DBusError *e) {
-        Iterator i;
-        Unit *u;
-        int r;
-
-        assert(tr);
-        assert(j);
-        assert(!j->transaction_prev);
-
-        /* Does a recursive sweep through the ordering graph, looking
-         * for a cycle. If we find cycle we try to break it. */
-
-        /* Have we seen this before? */
-        if (j->generation == generation) {
-                Job *k, *delete;
-
-                /* If the marker is NULL we have been here already and
-                 * decided the job was loop-free from here. Hence
-                 * shortcut things and return right-away. */
-                if (!j->marker)
-                        return 0;
-
-                /* So, the marker is not NULL and we already have been
-                 * here. We have a cycle. Let's try to break it. We go
-                 * backwards in our path and try to find a suitable
-                 * job to remove. We use the marker to find our way
-                 * back, since smart how we are we stored our way back
-                 * in there. */
-                log_warning("Found ordering cycle on %s/%s", j->unit->id, job_type_to_string(j->type));
-
-                delete = NULL;
-                for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) {
-
-                        log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type));
-
-                        if (!delete &&
-                            !k->installed &&
-                            !unit_matters_to_anchor(k->unit, k)) {
-                                /* Ok, we can drop this one, so let's
-                                 * do so. */
-                                delete = k;
-                        }
-
-                        /* Check if this in fact was the beginning of
-                         * the cycle */
-                        if (k == j)
-                                break;
-                }
-
-
-                if (delete) {
-                        log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type));
-                        transaction_delete_unit(tr, delete->unit);
-                        return -EAGAIN;
-                }
-
-                log_error("Unable to break cycle");
-
-                dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details.");
-                return -ENOEXEC;
-        }
-
-        /* Make the marker point to where we come from, so that we can
-         * find our way backwards if we want to break a cycle. We use
-         * a special marker for the beginning: we point to
-         * ourselves. */
-        j->marker = from ? from : j;
-        j->generation = generation;
-
-        /* We assume that the the dependencies are bidirectional, and
-         * hence can ignore UNIT_AFTER */
-        SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) {
-                Job *o;
-
-                /* Is there a job for this unit? */
-                o = hashmap_get(tr->jobs, u);
-                if (!o) {
-                        /* Ok, there is no job for this in the
-                         * transaction, but maybe there is already one
-                         * running? */
-                        o = u->job;
-                        if (!o)
-                                continue;
-                }
-
-                r = transaction_verify_order_one(tr, o, j, generation, e);
-                if (r < 0)
-                        return r;
-        }
-
-        /* Ok, let's backtrack, and remember that this entry is not on
-         * our path anymore. */
-        j->marker = NULL;
-
-        return 0;
-}
-
-static int transaction_verify_order(Transaction *tr, unsigned *generation, DBusError *e) {
-        Job *j;
-        int r;
-        Iterator i;
-        unsigned g;
-
-        assert(tr);
-        assert(generation);
-
-        /* Check if the ordering graph is cyclic. If it is, try to fix
-         * that up by dropping one of the jobs. */
-
-        g = (*generation)++;
-
-        HASHMAP_FOREACH(j, tr->jobs, i)
-                if ((r = transaction_verify_order_one(tr, j, NULL, g, e)) < 0)
-                        return r;
-
-        return 0;
-}
-
-static void transaction_collect_garbage(Transaction *tr) {
-        bool again;
-
-        assert(tr);
-
-        /* Drop jobs that are not required by any other job */
-
-        do {
-                Iterator i;
-                Job *j;
-
-                again = false;
-
-                HASHMAP_FOREACH(j, tr->jobs, i) {
-                        if (j->object_list) {
-                                /* log_debug("Keeping job %s/%s because of %s/%s", */
-                                /*           j->unit->id, job_type_to_string(j->type), */
-                                /*           j->object_list->subject ? j->object_list->subject->unit->id : "root", */
-                                /*           j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */
-                                continue;
-                        }
-
-                        /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */
-                        transaction_delete_job(tr, j, true);
-                        again = true;
-                        break;
-                }
-
-        } while (again);
-}
-
-static int transaction_is_destructive(Transaction *tr, DBusError *e) {
-        Iterator i;
-        Job *j;
-
-        assert(tr);
-
-        /* Checks whether applying this transaction means that
-         * existing jobs would be replaced */
-
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-
-                /* Assume merged */
-                assert(!j->transaction_prev);
-                assert(!j->transaction_next);
-
-                if (j->unit->job &&
-                    j->unit->job != j &&
-                    !job_type_is_superset(j->type, j->unit->job->type)) {
-
-                        dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive.");
-                        return -EEXIST;
-                }
-        }
-
-        return 0;
-}
-
-static void transaction_minimize_impact(Transaction *tr) {
-        bool again;
-        assert(tr);
-
-        /* Drops all unnecessary jobs that reverse already active jobs
-         * or that stop a running service. */
-
-        do {
-                Job *j;
-                Iterator i;
-
-                again = false;
-
-                HASHMAP_FOREACH(j, tr->jobs, i) {
-                        LIST_FOREACH(transaction, j, j) {
-                                bool stops_running_service, changes_existing_job;
-
-                                /* If it matters, we shouldn't drop it */
-                                if (j->matters_to_anchor)
-                                        continue;
-
-                                /* Would this stop a running service?
-                                 * Would this change an existing job?
-                                 * If so, let's drop this entry */
-
-                                stops_running_service =
-                                        j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit));
-
-                                changes_existing_job =
-                                        j->unit->job &&
-                                        job_type_is_conflicting(j->type, j->unit->job->type);
-
-                                if (!stops_running_service && !changes_existing_job)
-                                        continue;
-
-                                if (stops_running_service)
-                                        log_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type));
-
-                                if (changes_existing_job)
-                                        log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type));
-
-                                /* Ok, let's get rid of this */
-                                log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type));
-
-                                transaction_delete_job(tr, j, true);
-                                again = true;
-                                break;
-                        }
-
-                        if (again)
-                                break;
-                }
-
-        } while (again);
-}
-
-static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
-        Iterator i;
-        Job *j;
-        int r;
-
-        /* Moves the transaction jobs to the set of active jobs */
-
-        if (mode == JOB_ISOLATE) {
-
-                /* When isolating first kill all installed jobs which
-                 * aren't part of the new transaction */
-        rescan:
-                HASHMAP_FOREACH(j, m->jobs, i) {
-                        assert(j->installed);
-
-                        if (hashmap_get(tr->jobs, j->unit))
-                                continue;
-
-                        /* 'j' itself is safe to remove, but if other jobs
-                           are invalidated recursively, our iterator may become
-                           invalid and we need to start over. */
-                        if (job_finish_and_invalidate(j, JOB_CANCELED) > 0)
-                                goto rescan;
-                }
-        }
-
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-                /* Assume merged */
-                assert(!j->transaction_prev);
-                assert(!j->transaction_next);
-
-                if (j->installed)
-                        continue;
-
-                r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
-                if (r < 0)
-                        goto rollback;
-        }
-
-        while ((j = hashmap_steal_first(tr->jobs))) {
-                Job *uj;
-                if (j->installed) {
-                        /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
-                        continue;
-                }
-
-                uj = j->unit->job;
-                if (uj) {
-                        job_uninstall(uj);
-                        job_free(uj);
-                }
-
-                j->unit->job = j;
-                j->installed = true;
-                m->n_installed_jobs ++;
-
-                /* We're fully installed. Now let's free data we don't
-                 * need anymore. */
-
-                assert(!j->transaction_next);
-                assert(!j->transaction_prev);
-
-                /* Clean the job dependencies */
-                transaction_unlink_job(tr, j, false);
-
-                job_add_to_run_queue(j);
-                job_add_to_dbus_queue(j);
-                job_start_timer(j);
-
-                log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
-        }
-
-        assert(!tr->anchor);
-
-        return 0;
-
-rollback:
-
-        HASHMAP_FOREACH(j, tr->jobs, i) {
-                if (j->installed)
-                        continue;
-
-                hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
-        }
-
-        return r;
-}
-
-static int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e) {
-        int r;
-        unsigned generation = 1;
-
-        assert(tr);
-
-        /* This applies the changes recorded in tr->jobs to
-         * the actual list of jobs, if possible. */
-
-        /* First step: figure out which jobs matter */
-        transaction_find_jobs_that_matter_to_anchor(tr, NULL, generation++);
-
-        /* Second step: Try not to stop any running services if
-         * we don't have to. Don't try to reverse running
-         * jobs if we don't have to. */
-        if (mode == JOB_FAIL)
-                transaction_minimize_impact(tr);
-
-        /* Third step: Drop redundant jobs */
-        transaction_drop_redundant(tr);
-
-        for (;;) {
-                /* Fourth step: Let's remove unneeded jobs that might
-                 * be lurking. */
-                if (mode != JOB_ISOLATE)
-                        transaction_collect_garbage(tr);
-
-                /* Fifth step: verify order makes sense and correct
-                 * cycles if necessary and possible */
-                r = transaction_verify_order(tr, &generation, e);
-                if (r >= 0)
-                        break;
-
-                if (r != -EAGAIN) {
-                        log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r));
-                        return r;
-                }
-
-                /* Let's see if the resulting transaction ordering
-                 * graph is still cyclic... */
-        }
-
-        for (;;) {
-                /* Sixth step: let's drop unmergeable entries if
-                 * necessary and possible, merge entries we can
-                 * merge */
-                r = transaction_merge_jobs(tr, e);
-                if (r >= 0)
-                        break;
-
-                if (r != -EAGAIN) {
-                        log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r));
-                        return r;
-                }
-
-                /* Seventh step: an entry got dropped, let's garbage
-                 * collect its dependencies. */
-                if (mode != JOB_ISOLATE)
-                        transaction_collect_garbage(tr);
-
-                /* Let's see if the resulting transaction still has
-                 * unmergeable entries ... */
-        }
-
-        /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
-        transaction_drop_redundant(tr);
-
-        /* Ninth step: check whether we can actually apply this */
-        if (mode == JOB_FAIL) {
-                r = transaction_is_destructive(tr, e);
-                if (r < 0) {
-                        log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r));
-                        return r;
-                }
-        }
-
-        /* Tenth step: apply changes */
-        r = transaction_apply(tr, m, mode);
-        if (r < 0) {
-                log_warning("Failed to apply transaction: %s", strerror(-r));
-                return r;
-        }
-
-        assert(hashmap_isempty(tr->jobs));
-        assert(!tr->anchor);
-
-        return 0;
-}
-
-static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool override, bool *is_new) {
-        Job *j, *f;
-
-        assert(tr);
-        assert(unit);
-
-        /* Looks for an existing prospective job and returns that. If
-         * it doesn't exist it is created and added to the prospective
-         * jobs list. */
-
-        f = hashmap_get(tr->jobs, unit);
-
-        LIST_FOREACH(transaction, j, f) {
-                assert(j->unit == unit);
-
-                if (j->type == type) {
-                        if (is_new)
-                                *is_new = false;
-                        return j;
-                }
-        }
-
-        if (unit->job && unit->job->type == type)
-                j = unit->job;
-        else {
-                j = job_new(unit->manager, type, unit);
-                if (!j)
-                        return NULL;
-        }
-
-        j->generation = 0;
-        j->marker = NULL;
-        j->matters_to_anchor = false;
-        j->override = override;
-
-        LIST_PREPEND(Job, transaction, f, j);
-
-        if (hashmap_replace(tr->jobs, unit, f) < 0) {
-                LIST_REMOVE(Job, transaction, f, j);
-                job_free(j);
-                return NULL;
-        }
-
-        if (is_new)
-                *is_new = true;
-
-        /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */
-
-        return j;
-}
-
-static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
-        assert(tr);
-        assert(j);
-
-        if (j->transaction_prev)
-                j->transaction_prev->transaction_next = j->transaction_next;
-        else if (j->transaction_next)
-                hashmap_replace(tr->jobs, j->unit, j->transaction_next);
-        else
-                hashmap_remove_value(tr->jobs, j->unit, j);
-
-        if (j->transaction_next)
-                j->transaction_next->transaction_prev = j->transaction_prev;
-
-        j->transaction_prev = j->transaction_next = NULL;
-
-        while (j->subject_list)
-                job_dependency_free(j->subject_list, tr);
-
-        while (j->object_list) {
-                Job *other = j->object_list->matters ? j->object_list->subject : NULL;
-
-                job_dependency_free(j->object_list, tr);
-
-                if (other && delete_dependencies) {
-                        log_debug("Deleting job %s/%s as dependency of job %s/%s",
-                                  other->unit->id, job_type_to_string(other->type),
-                                  j->unit->id, job_type_to_string(j->type));
-                        transaction_delete_job(tr, other, delete_dependencies);
-                }
-        }
-}
-
-static int transaction_add_job_and_dependencies(
-                Transaction *tr,
-                JobType type,
-                Unit *unit,
-                Job *by,
-                bool matters,
-                bool override,
-                bool conflicts,
-                bool ignore_requirements,
-                bool ignore_order,
-                DBusError *e,
-                Job **_ret) {
-        Job *ret;
-        Iterator i;
-        Unit *dep;
-        int r;
-        bool is_new;
-
-        assert(tr);
-        assert(type < _JOB_TYPE_MAX);
-        assert(unit);
-
-        /* log_debug("Pulling in %s/%s from %s/%s", */
-        /*           unit->id, job_type_to_string(type), */
-        /*           by ? by->unit->id : "NA", */
-        /*           by ? job_type_to_string(by->type) : "NA"); */
-
-        if (unit->load_state != UNIT_LOADED &&
-            unit->load_state != UNIT_ERROR &&
-            unit->load_state != UNIT_MASKED) {
-                dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id);
-                return -EINVAL;
-        }
-
-        if (type != JOB_STOP && unit->load_state == UNIT_ERROR) {
-                dbus_set_error(e, BUS_ERROR_LOAD_FAILED,
-                               "Unit %s failed to load: %s. "
-                               "See system logs and 'systemctl status %s' for details.",
-                               unit->id,
-                               strerror(-unit->load_error),
-                               unit->id);
-                return -EINVAL;
-        }
-
-        if (type != JOB_STOP && unit->load_state == UNIT_MASKED) {
-                dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id);
-                return -EINVAL;
-        }
-
-        if (!unit_job_is_applicable(unit, type)) {
-                dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id);
-                return -EBADR;
-        }
-
-        /* First add the job. */
-        ret = transaction_add_one_job(tr, type, unit, override, &is_new);
-        if (!ret)
-                return -ENOMEM;
-
-        ret->ignore_order = ret->ignore_order || ignore_order;
-
-        /* Then, add a link to the job. */
-        if (!job_dependency_new(by, ret, matters, conflicts, tr))
-                return -ENOMEM;
-
-        if (is_new && !ignore_requirements) {
-                Set *following;
-
-                /* If we are following some other unit, make sure we
-                 * add all dependencies of everybody following. */
-                if (unit_following_set(ret->unit, &following) > 0) {
-                        SET_FOREACH(dep, following, i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        set_free(following);
-                }
-
-                /* Finally, recursively add in all dependencies. */
-                if (type == JOB_START || type == JOB_RELOAD_OR_START) {
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                }
-
-                if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) {
-                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        if (r != -EBADR)
-                                                goto fail;
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-                }
-
-                if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) {
-
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) {
-                                r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL);
-                                if (r < 0) {
-                                        log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
-
-                                        if (e)
-                                                dbus_error_free(e);
-                                }
-                        }
-                }
-
-                /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
-        }
-
-        if (_ret)
-                *_ret = ret;
-
-        return 0;
-
-fail:
-        return r;
-}
-
-static int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
-        Iterator i;
-        Unit *u;
-        char *k;
-        int r;
-
-        assert(tr);
-        assert(m);
-
-        HASHMAP_FOREACH_KEY(u, k, m->units, i) {
-
-                /* ignore aliases */
-                if (u->id != k)
-                        continue;
-
-                if (u->ignore_on_isolate)
-                        continue;
-
-                /* No need to stop inactive jobs */
-                if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job)
-                        continue;
-
-                /* Is there already something listed for this? */
-                if (hashmap_get(tr->jobs, u))
-                        continue;
-
-                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL);
-                if (r < 0)
-                        log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
-        }
-
-        return 0;
-}
-
-static Transaction *transaction_new(void) {
-        Transaction *tr;
-
-        tr = new0(Transaction, 1);
-        if (!tr)
-                return NULL;
-
-        tr->jobs = hashmap_new(trivial_hash_func, trivial_compare_func);
-        if (!tr->jobs) {
-                free(tr);
-                return NULL;
-        }
-
-        return tr;
-}
-
-static void transaction_free(Transaction *tr) {
-        assert(hashmap_isempty(tr->jobs));
-        hashmap_free(tr->jobs);
-        free(tr);
-}
-
 int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) {
         int r;
         Job *ret;
diff --git a/src/core/manager.h b/src/core/manager.h
index 2bf7b7a..20b743a 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -33,7 +33,6 @@
 #define MANAGER_MAX_NAMES 131072 /* 128K */
 
 typedef struct Manager Manager;
-typedef struct Transaction Transaction;
 typedef enum WatchType WatchType;
 typedef struct Watch Watch;
 
@@ -92,12 +91,6 @@ struct Watch {
 #include "dbus.h"
 #include "path-lookup.h"
 
-struct Transaction {
-        /* Jobs to be added */
-        Hashmap *jobs;      /* Unit object => Job object list 1:1 */
-        JobDependency *anchor;
-};
-
 struct Manager {
         /* Note that the set of units we know of is allowed to be
          * inconsistent. However the subset of it that is loaded may
diff --git a/src/core/transaction.c b/src/core/transaction.c
new file mode 100644
index 0000000..8fa89a7
--- /dev/null
+++ b/src/core/transaction.c
@@ -0,0 +1,1101 @@
+#include "transaction.h"
+#include "bus-errors.h"
+
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies);
+
+static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) {
+        assert(tr);
+        assert(j);
+
+        /* Deletes one job from the transaction */
+
+        transaction_unlink_job(tr, j, delete_dependencies);
+
+        if (!j->installed)
+                job_free(j);
+}
+
+static void transaction_delete_unit(Transaction *tr, Unit *u) {
+        Job *j;
+
+        /* Deletes all jobs associated with a certain unit from the
+         * transaction */
+
+        while ((j = hashmap_get(tr->jobs, u)))
+                transaction_delete_job(tr, j, true);
+}
+
+void transaction_abort(Transaction *tr) {
+        Job *j;
+
+        assert(tr);
+
+        while ((j = hashmap_first(tr->jobs)))
+                transaction_delete_job(tr, j, true);
+
+        assert(hashmap_isempty(tr->jobs));
+
+        assert(!tr->anchor);
+}
+
+static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, Job *j, unsigned generation) {
+        JobDependency *l;
+
+        assert(tr);
+
+        /* A recursive sweep through the graph that marks all units
+         * that matter to the anchor job, i.e. are directly or
+         * indirectly a dependency of the anchor job via paths that
+         * are fully marked as mattering. */
+
+        if (j)
+                l = j->subject_list;
+        else
+                l = tr->anchor;
+
+        LIST_FOREACH(subject, l, l) {
+
+                /* This link does not matter */
+                if (!l->matters)
+                        continue;
+
+                /* This unit has already been marked */
+                if (l->object->generation == generation)
+                        continue;
+
+                l->object->matters_to_anchor = true;
+                l->object->generation = generation;
+
+                transaction_find_jobs_that_matter_to_anchor(tr, l->object, generation);
+        }
+}
+
+static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) {
+        JobDependency *l, *last;
+
+        assert(j);
+        assert(other);
+        assert(j->unit == other->unit);
+        assert(!j->installed);
+
+        /* Merges 'other' into 'j' and then deletes 'other'. */
+
+        j->type = t;
+        j->state = JOB_WAITING;
+        j->override = j->override || other->override;
+
+        j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor;
+
+        /* Patch us in as new owner of the JobDependency objects */
+        last = NULL;
+        LIST_FOREACH(subject, l, other->subject_list) {
+                assert(l->subject == other);
+                l->subject = j;
+                last = l;
+        }
+
+        /* Merge both lists */
+        if (last) {
+                last->subject_next = j->subject_list;
+                if (j->subject_list)
+                        j->subject_list->subject_prev = last;
+                j->subject_list = other->subject_list;
+        }
+
+        /* Patch us in as new owner of the JobDependency objects */
+        last = NULL;
+        LIST_FOREACH(object, l, other->object_list) {
+                assert(l->object == other);
+                l->object = j;
+                last = l;
+        }
+
+        /* Merge both lists */
+        if (last) {
+                last->object_next = j->object_list;
+                if (j->object_list)
+                        j->object_list->object_prev = last;
+                j->object_list = other->object_list;
+        }
+
+        /* Kill the other job */
+        other->subject_list = NULL;
+        other->object_list = NULL;
+        transaction_delete_job(tr, other, true);
+}
+
+static bool job_is_conflicted_by(Job *j) {
+        JobDependency *l;
+
+        assert(j);
+
+        /* Returns true if this job is pulled in by a least one
+         * ConflictedBy dependency. */
+
+        LIST_FOREACH(object, l, j->object_list)
+                if (l->conflicts)
+                        return true;
+
+        return false;
+}
+
+static int delete_one_unmergeable_job(Transaction *tr, Job *j) {
+        Job *k;
+
+        assert(j);
+
+        /* Tries to delete one item in the linked list
+         * j->transaction_next->transaction_next->... that conflicts
+         * with another one, in an attempt to make an inconsistent
+         * transaction work. */
+
+        /* We rely here on the fact that if a merged with b does not
+         * merge with c, either a or b merge with c neither */
+        LIST_FOREACH(transaction, j, j)
+                LIST_FOREACH(transaction, k, j->transaction_next) {
+                        Job *d;
+
+                        /* Is this one mergeable? Then skip it */
+                        if (job_type_is_mergeable(j->type, k->type))
+                                continue;
+
+                        /* Ok, we found two that conflict, let's see if we can
+                         * drop one of them */
+                        if (!j->matters_to_anchor && !k->matters_to_anchor) {
+
+                                /* Both jobs don't matter, so let's
+                                 * find the one that is smarter to
+                                 * remove. Let's think positive and
+                                 * rather remove stops then starts --
+                                 * except if something is being
+                                 * stopped because it is conflicted by
+                                 * another unit in which case we
+                                 * rather remove the start. */
+
+                                log_debug("Looking at job %s/%s conflicted_by=%s", j->unit->id, job_type_to_string(j->type), yes_no(j->type == JOB_STOP && job_is_conflicted_by(j)));
+                                log_debug("Looking at job %s/%s conflicted_by=%s", k->unit->id, job_type_to_string(k->type), yes_no(k->type == JOB_STOP && job_is_conflicted_by(k)));
+
+                                if (j->type == JOB_STOP) {
+
+                                        if (job_is_conflicted_by(j))
+                                                d = k;
+                                        else
+                                                d = j;
+
+                                } else if (k->type == JOB_STOP) {
+
+                                        if (job_is_conflicted_by(k))
+                                                d = j;
+                                        else
+                                                d = k;
+                                } else
+                                        d = j;
+
+                        } else if (!j->matters_to_anchor)
+                                d = j;
+                        else if (!k->matters_to_anchor)
+                                d = k;
+                        else
+                                return -ENOEXEC;
+
+                        /* Ok, we can drop one, so let's do so. */
+                        log_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type));
+                        transaction_delete_job(tr, d, true);
+                        return 0;
+                }
+
+        return -EINVAL;
+}
+
+static int transaction_merge_jobs(Transaction *tr, DBusError *e) {
+        Job *j;
+        Iterator i;
+        int r;
+
+        assert(tr);
+
+        /* First step, check whether any of the jobs for one specific
+         * task conflict. If so, try to drop one of them. */
+        HASHMAP_FOREACH(j, tr->jobs, i) {
+                JobType t;
+                Job *k;
+
+                t = j->type;
+                LIST_FOREACH(transaction, k, j->transaction_next) {
+                        if (job_type_merge(&t, k->type) >= 0)
+                                continue;
+
+                        /* OK, we could not merge all jobs for this
+                         * action. Let's see if we can get rid of one
+                         * of them */
+
+                        r = delete_one_unmergeable_job(tr, j);
+                        if (r >= 0)
+                                /* Ok, we managed to drop one, now
+                                 * let's ask our callers to call us
+                                 * again after garbage collecting */
+                                return -EAGAIN;
+
+                        /* We couldn't merge anything. Failure */
+                        dbus_set_error(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, "Transaction contains conflicting jobs '%s' and '%s' for %s. Probably contradicting requirement dependencies configured.",
+                                       job_type_to_string(t), job_type_to_string(k->type), k->unit->id);
+                        return r;
+                }
+        }
+
+        /* Second step, merge the jobs. */
+        HASHMAP_FOREACH(j, tr->jobs, i) {
+                JobType t = j->type;
+                Job *k;
+
+                /* Merge all transactions */
+                LIST_FOREACH(transaction, k, j->transaction_next)
+                        assert_se(job_type_merge(&t, k->type) == 0);
+
+                /* If an active job is mergeable, merge it too */
+                if (j->unit->job)
+                        job_type_merge(&t, j->unit->job->type); /* Might fail. Which is OK */
+
+                while ((k = j->transaction_next)) {
+                        if (j->installed) {
+                                transaction_merge_and_delete_job(tr, k, j, t);
+                                j = k;
+                        } else
+                                transaction_merge_and_delete_job(tr, j, k, t);
+                }
+
+                if (j->unit->job && !j->installed)
+                        transaction_merge_and_delete_job(tr, j, j->unit->job, t);
+
+                assert(!j->transaction_next);
+                assert(!j->transaction_prev);
+        }
+
+        return 0;
+}
+
+static void transaction_drop_redundant(Transaction *tr) {
+        bool again;
+
+        assert(tr);
+
+        /* Goes through the transaction and removes all jobs that are
+         * a noop */
+
+        do {
+                Job *j;
+                Iterator i;
+
+                again = false;
+
+                HASHMAP_FOREACH(j, tr->jobs, i) {
+                        bool changes_something = false;
+                        Job *k;
+
+                        LIST_FOREACH(transaction, k, j) {
+
+                                if (!job_is_anchor(k) &&
+                                    (k->installed || job_type_is_redundant(k->type, unit_active_state(k->unit))) &&
+                                    (!k->unit->job || !job_type_is_conflicting(k->type, k->unit->job->type)))
+                                        continue;
+
+                                changes_something = true;
+                                break;
+                        }
+
+                        if (changes_something)
+                                continue;
+
+                        /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */
+                        transaction_delete_job(tr, j, false);
+                        again = true;
+                        break;
+                }
+
+        } while (again);
+}
+
+static bool unit_matters_to_anchor(Unit *u, Job *j) {
+        assert(u);
+        assert(!j->transaction_prev);
+
+        /* Checks whether at least one of the jobs for this unit
+         * matters to the anchor. */
+
+        LIST_FOREACH(transaction, j, j)
+                if (j->matters_to_anchor)
+                        return true;
+
+        return false;
+}
+
+static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, DBusError *e) {
+        Iterator i;
+        Unit *u;
+        int r;
+
+        assert(tr);
+        assert(j);
+        assert(!j->transaction_prev);
+
+        /* Does a recursive sweep through the ordering graph, looking
+         * for a cycle. If we find cycle we try to break it. */
+
+        /* Have we seen this before? */
+        if (j->generation == generation) {
+                Job *k, *delete;
+
+                /* If the marker is NULL we have been here already and
+                 * decided the job was loop-free from here. Hence
+                 * shortcut things and return right-away. */
+                if (!j->marker)
+                        return 0;
+
+                /* So, the marker is not NULL and we already have been
+                 * here. We have a cycle. Let's try to break it. We go
+                 * backwards in our path and try to find a suitable
+                 * job to remove. We use the marker to find our way
+                 * back, since smart how we are we stored our way back
+                 * in there. */
+                log_warning("Found ordering cycle on %s/%s", j->unit->id, job_type_to_string(j->type));
+
+                delete = NULL;
+                for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) {
+
+                        log_info("Walked on cycle path to %s/%s", k->unit->id, job_type_to_string(k->type));
+
+                        if (!delete &&
+                            !k->installed &&
+                            !unit_matters_to_anchor(k->unit, k)) {
+                                /* Ok, we can drop this one, so let's
+                                 * do so. */
+                                delete = k;
+                        }
+
+                        /* Check if this in fact was the beginning of
+                         * the cycle */
+                        if (k == j)
+                                break;
+                }
+
+
+                if (delete) {
+                        log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type));
+                        transaction_delete_unit(tr, delete->unit);
+                        return -EAGAIN;
+                }
+
+                log_error("Unable to break cycle");
+
+                dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details.");
+                return -ENOEXEC;
+        }
+
+        /* Make the marker point to where we come from, so that we can
+         * find our way backwards if we want to break a cycle. We use
+         * a special marker for the beginning: we point to
+         * ourselves. */
+        j->marker = from ? from : j;
+        j->generation = generation;
+
+        /* We assume that the the dependencies are bidirectional, and
+         * hence can ignore UNIT_AFTER */
+        SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) {
+                Job *o;
+
+                /* Is there a job for this unit? */
+                o = hashmap_get(tr->jobs, u);
+                if (!o) {
+                        /* Ok, there is no job for this in the
+                         * transaction, but maybe there is already one
+                         * running? */
+                        o = u->job;
+                        if (!o)
+                                continue;
+                }
+
+                r = transaction_verify_order_one(tr, o, j, generation, e);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Ok, let's backtrack, and remember that this entry is not on
+         * our path anymore. */
+        j->marker = NULL;
+
+        return 0;
+}
+
+static int transaction_verify_order(Transaction *tr, unsigned *generation, DBusError *e) {
+        Job *j;
+        int r;
+        Iterator i;
+        unsigned g;
+
+        assert(tr);
+        assert(generation);
+
+        /* Check if the ordering graph is cyclic. If it is, try to fix
+         * that up by dropping one of the jobs. */
+
+        g = (*generation)++;
+
+        HASHMAP_FOREACH(j, tr->jobs, i)
+                if ((r = transaction_verify_order_one(tr, j, NULL, g, e)) < 0)
+                        return r;
+
+        return 0;
+}
+
+static void transaction_collect_garbage(Transaction *tr) {
+        bool again;
+
+        assert(tr);
+
+        /* Drop jobs that are not required by any other job */
+
+        do {
+                Iterator i;
+                Job *j;
+
+                again = false;
+
+                HASHMAP_FOREACH(j, tr->jobs, i) {
+                        if (j->object_list) {
+                                /* log_debug("Keeping job %s/%s because of %s/%s", */
+                                /*           j->unit->id, job_type_to_string(j->type), */
+                                /*           j->object_list->subject ? j->object_list->subject->unit->id : "root", */
+                                /*           j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */
+                                continue;
+                        }
+
+                        /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */
+                        transaction_delete_job(tr, j, true);
+                        again = true;
+                        break;
+                }
+
+        } while (again);
+}
+
+static int transaction_is_destructive(Transaction *tr, DBusError *e) {
+        Iterator i;
+        Job *j;
+
+        assert(tr);
+
+        /* Checks whether applying this transaction means that
+         * existing jobs would be replaced */
+
+        HASHMAP_FOREACH(j, tr->jobs, i) {
+
+                /* Assume merged */
+                assert(!j->transaction_prev);
+                assert(!j->transaction_next);
+
+                if (j->unit->job &&
+                    j->unit->job != j &&
+                    !job_type_is_superset(j->type, j->unit->job->type)) {
+
+                        dbus_set_error(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction is destructive.");
+                        return -EEXIST;
+                }
+        }
+
+        return 0;
+}
+
+static void transaction_minimize_impact(Transaction *tr) {
+        bool again;
+        assert(tr);
+
+        /* Drops all unnecessary jobs that reverse already active jobs
+         * or that stop a running service. */
+
+        do {
+                Job *j;
+                Iterator i;
+
+                again = false;
+
+                HASHMAP_FOREACH(j, tr->jobs, i) {
+                        LIST_FOREACH(transaction, j, j) {
+                                bool stops_running_service, changes_existing_job;
+
+                                /* If it matters, we shouldn't drop it */
+                                if (j->matters_to_anchor)
+                                        continue;
+
+                                /* Would this stop a running service?
+                                 * Would this change an existing job?
+                                 * If so, let's drop this entry */
+
+                                stops_running_service =
+                                        j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit));
+
+                                changes_existing_job =
+                                        j->unit->job &&
+                                        job_type_is_conflicting(j->type, j->unit->job->type);
+
+                                if (!stops_running_service && !changes_existing_job)
+                                        continue;
+
+                                if (stops_running_service)
+                                        log_debug("%s/%s would stop a running service.", j->unit->id, job_type_to_string(j->type));
+
+                                if (changes_existing_job)
+                                        log_debug("%s/%s would change existing job.", j->unit->id, job_type_to_string(j->type));
+
+                                /* Ok, let's get rid of this */
+                                log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type));
+
+                                transaction_delete_job(tr, j, true);
+                                again = true;
+                                break;
+                        }
+
+                        if (again)
+                                break;
+                }
+
+        } while (again);
+}
+
+static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
+        Iterator i;
+        Job *j;
+        int r;
+
+        /* Moves the transaction jobs to the set of active jobs */
+
+        if (mode == JOB_ISOLATE) {
+
+                /* When isolating first kill all installed jobs which
+                 * aren't part of the new transaction */
+        rescan:
+                HASHMAP_FOREACH(j, m->jobs, i) {
+                        assert(j->installed);
+
+                        if (hashmap_get(tr->jobs, j->unit))
+                                continue;
+
+                        /* 'j' itself is safe to remove, but if other jobs
+                           are invalidated recursively, our iterator may become
+                           invalid and we need to start over. */
+                        if (job_finish_and_invalidate(j, JOB_CANCELED) > 0)
+                                goto rescan;
+                }
+        }
+
+        HASHMAP_FOREACH(j, tr->jobs, i) {
+                /* Assume merged */
+                assert(!j->transaction_prev);
+                assert(!j->transaction_next);
+
+                if (j->installed)
+                        continue;
+
+                r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
+                if (r < 0)
+                        goto rollback;
+        }
+
+        while ((j = hashmap_steal_first(tr->jobs))) {
+                Job *uj;
+                if (j->installed) {
+                        /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
+                        continue;
+                }
+
+                uj = j->unit->job;
+                if (uj) {
+                        job_uninstall(uj);
+                        job_free(uj);
+                }
+
+                j->unit->job = j;
+                j->installed = true;
+                m->n_installed_jobs ++;
+
+                /* We're fully installed. Now let's free data we don't
+                 * need anymore. */
+
+                assert(!j->transaction_next);
+                assert(!j->transaction_prev);
+
+                /* Clean the job dependencies */
+                transaction_unlink_job(tr, j, false);
+
+                job_add_to_run_queue(j);
+                job_add_to_dbus_queue(j);
+                job_start_timer(j);
+
+                log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+        }
+
+        assert(!tr->anchor);
+
+        return 0;
+
+rollback:
+
+        HASHMAP_FOREACH(j, tr->jobs, i) {
+                if (j->installed)
+                        continue;
+
+                hashmap_remove(m->jobs, UINT32_TO_PTR(j->id));
+        }
+
+        return r;
+}
+
+int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e) {
+        int r;
+        unsigned generation = 1;
+
+        assert(tr);
+
+        /* This applies the changes recorded in tr->jobs to
+         * the actual list of jobs, if possible. */
+
+        /* First step: figure out which jobs matter */
+        transaction_find_jobs_that_matter_to_anchor(tr, NULL, generation++);
+
+        /* Second step: Try not to stop any running services if
+         * we don't have to. Don't try to reverse running
+         * jobs if we don't have to. */
+        if (mode == JOB_FAIL)
+                transaction_minimize_impact(tr);
+
+        /* Third step: Drop redundant jobs */
+        transaction_drop_redundant(tr);
+
+        for (;;) {
+                /* Fourth step: Let's remove unneeded jobs that might
+                 * be lurking. */
+                if (mode != JOB_ISOLATE)
+                        transaction_collect_garbage(tr);
+
+                /* Fifth step: verify order makes sense and correct
+                 * cycles if necessary and possible */
+                r = transaction_verify_order(tr, &generation, e);
+                if (r >= 0)
+                        break;
+
+                if (r != -EAGAIN) {
+                        log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r));
+                        return r;
+                }
+
+                /* Let's see if the resulting transaction ordering
+                 * graph is still cyclic... */
+        }
+
+        for (;;) {
+                /* Sixth step: let's drop unmergeable entries if
+                 * necessary and possible, merge entries we can
+                 * merge */
+                r = transaction_merge_jobs(tr, e);
+                if (r >= 0)
+                        break;
+
+                if (r != -EAGAIN) {
+                        log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r));
+                        return r;
+                }
+
+                /* Seventh step: an entry got dropped, let's garbage
+                 * collect its dependencies. */
+                if (mode != JOB_ISOLATE)
+                        transaction_collect_garbage(tr);
+
+                /* Let's see if the resulting transaction still has
+                 * unmergeable entries ... */
+        }
+
+        /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
+        transaction_drop_redundant(tr);
+
+        /* Ninth step: check whether we can actually apply this */
+        if (mode == JOB_FAIL) {
+                r = transaction_is_destructive(tr, e);
+                if (r < 0) {
+                        log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r));
+                        return r;
+                }
+        }
+
+        /* Tenth step: apply changes */
+        r = transaction_apply(tr, m, mode);
+        if (r < 0) {
+                log_warning("Failed to apply transaction: %s", strerror(-r));
+                return r;
+        }
+
+        assert(hashmap_isempty(tr->jobs));
+        assert(!tr->anchor);
+
+        return 0;
+}
+
+static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool override, bool *is_new) {
+        Job *j, *f;
+
+        assert(tr);
+        assert(unit);
+
+        /* Looks for an existing prospective job and returns that. If
+         * it doesn't exist it is created and added to the prospective
+         * jobs list. */
+
+        f = hashmap_get(tr->jobs, unit);
+
+        LIST_FOREACH(transaction, j, f) {
+                assert(j->unit == unit);
+
+                if (j->type == type) {
+                        if (is_new)
+                                *is_new = false;
+                        return j;
+                }
+        }
+
+        if (unit->job && unit->job->type == type)
+                j = unit->job;
+        else {
+                j = job_new(unit->manager, type, unit);
+                if (!j)
+                        return NULL;
+        }
+
+        j->generation = 0;
+        j->marker = NULL;
+        j->matters_to_anchor = false;
+        j->override = override;
+
+        LIST_PREPEND(Job, transaction, f, j);
+
+        if (hashmap_replace(tr->jobs, unit, f) < 0) {
+                LIST_REMOVE(Job, transaction, f, j);
+                job_free(j);
+                return NULL;
+        }
+
+        if (is_new)
+                *is_new = true;
+
+        /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */
+
+        return j;
+}
+
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
+        assert(tr);
+        assert(j);
+
+        if (j->transaction_prev)
+                j->transaction_prev->transaction_next = j->transaction_next;
+        else if (j->transaction_next)
+                hashmap_replace(tr->jobs, j->unit, j->transaction_next);
+        else
+                hashmap_remove_value(tr->jobs, j->unit, j);
+
+        if (j->transaction_next)
+                j->transaction_next->transaction_prev = j->transaction_prev;
+
+        j->transaction_prev = j->transaction_next = NULL;
+
+        while (j->subject_list)
+                job_dependency_free(j->subject_list, tr);
+
+        while (j->object_list) {
+                Job *other = j->object_list->matters ? j->object_list->subject : NULL;
+
+                job_dependency_free(j->object_list, tr);
+
+                if (other && delete_dependencies) {
+                        log_debug("Deleting job %s/%s as dependency of job %s/%s",
+                                  other->unit->id, job_type_to_string(other->type),
+                                  j->unit->id, job_type_to_string(j->type));
+                        transaction_delete_job(tr, other, delete_dependencies);
+                }
+        }
+}
+
+int transaction_add_job_and_dependencies(
+                Transaction *tr,
+                JobType type,
+                Unit *unit,
+                Job *by,
+                bool matters,
+                bool override,
+                bool conflicts,
+                bool ignore_requirements,
+                bool ignore_order,
+                DBusError *e,
+                Job **_ret) {
+        Job *ret;
+        Iterator i;
+        Unit *dep;
+        int r;
+        bool is_new;
+
+        assert(tr);
+        assert(type < _JOB_TYPE_MAX);
+        assert(unit);
+
+        /* log_debug("Pulling in %s/%s from %s/%s", */
+        /*           unit->id, job_type_to_string(type), */
+        /*           by ? by->unit->id : "NA", */
+        /*           by ? job_type_to_string(by->type) : "NA"); */
+
+        if (unit->load_state != UNIT_LOADED &&
+            unit->load_state != UNIT_ERROR &&
+            unit->load_state != UNIT_MASKED) {
+                dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id);
+                return -EINVAL;
+        }
+
+        if (type != JOB_STOP && unit->load_state == UNIT_ERROR) {
+                dbus_set_error(e, BUS_ERROR_LOAD_FAILED,
+                               "Unit %s failed to load: %s. "
+                               "See system logs and 'systemctl status %s' for details.",
+                               unit->id,
+                               strerror(-unit->load_error),
+                               unit->id);
+                return -EINVAL;
+        }
+
+        if (type != JOB_STOP && unit->load_state == UNIT_MASKED) {
+                dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->id);
+                return -EINVAL;
+        }
+
+        if (!unit_job_is_applicable(unit, type)) {
+                dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->id);
+                return -EBADR;
+        }
+
+        /* First add the job. */
+        ret = transaction_add_one_job(tr, type, unit, override, &is_new);
+        if (!ret)
+                return -ENOMEM;
+
+        ret->ignore_order = ret->ignore_order || ignore_order;
+
+        /* Then, add a link to the job. */
+        if (!job_dependency_new(by, ret, matters, conflicts, tr))
+                return -ENOMEM;
+
+        if (is_new && !ignore_requirements) {
+                Set *following;
+
+                /* If we are following some other unit, make sure we
+                 * add all dependencies of everybody following. */
+                if (unit_following_set(ret->unit, &following) > 0) {
+                        SET_FOREACH(dep, following, i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        set_free(following);
+                }
+
+                /* Finally, recursively add in all dependencies. */
+                if (type == JOB_START || type == JOB_RELOAD_OR_START) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                }
+
+                if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        if (r != -EBADR)
+                                                goto fail;
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+                }
+
+                if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) {
+
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
+                                        log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
+
+                                        if (e)
+                                                dbus_error_free(e);
+                                }
+                        }
+                }
+
+                /* JOB_VERIFY_STARTED, JOB_RELOAD require no dependency handling */
+        }
+
+        if (_ret)
+                *_ret = ret;
+
+        return 0;
+
+fail:
+        return r;
+}
+
+int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
+        Iterator i;
+        Unit *u;
+        char *k;
+        int r;
+
+        assert(tr);
+        assert(m);
+
+        HASHMAP_FOREACH_KEY(u, k, m->units, i) {
+
+                /* ignore aliases */
+                if (u->id != k)
+                        continue;
+
+                if (u->ignore_on_isolate)
+                        continue;
+
+                /* No need to stop inactive jobs */
+                if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job)
+                        continue;
+
+                /* Is there already something listed for this? */
+                if (hashmap_get(tr->jobs, u))
+                        continue;
+
+                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL);
+                if (r < 0)
+                        log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
+        }
+
+        return 0;
+}
+
+Transaction *transaction_new(void) {
+        Transaction *tr;
+
+        tr = new0(Transaction, 1);
+        if (!tr)
+                return NULL;
+
+        tr->jobs = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!tr->jobs) {
+                free(tr);
+                return NULL;
+        }
+
+        return tr;
+}
+
+void transaction_free(Transaction *tr) {
+        assert(hashmap_isempty(tr->jobs));
+        hashmap_free(tr->jobs);
+        free(tr);
+}
diff --git a/src/core/transaction.h b/src/core/transaction.h
new file mode 100644
index 0000000..d519ffb
--- /dev/null
+++ b/src/core/transaction.h
@@ -0,0 +1,36 @@
+#ifndef footransactionhfoo
+#define footransactionhfoo
+
+typedef struct Transaction Transaction;
+
+#include "unit.h"
+#include "manager.h"
+#include "job.h"
+#include "hashmap.h"
+
+struct Transaction {
+        /* Jobs to be added */
+        Hashmap *jobs;      /* Unit object => Job object list 1:1 */
+        JobDependency *anchor;
+};
+
+Transaction *transaction_new(void);
+void transaction_free(Transaction *tr);
+
+int transaction_add_job_and_dependencies(
+                Transaction *tr,
+                JobType type,
+                Unit *unit,
+                Job *by,
+                bool matters,
+                bool override,
+                bool conflicts,
+                bool ignore_requirements,
+                bool ignore_order,
+                DBusError *e,
+                Job **_ret);
+int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e);
+int transaction_add_isolate_jobs(Transaction *tr, Manager *m);
+void transaction_abort(Transaction *tr);
+
+#endif

commit 7527cb527598aaabf0ed9b38a352edb28536392a
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 10:22:07 2012 +0200

    manager: Transaction as an object
    
    This makes it obvious that transactions are short-lived. They are created in
    manager_add_job() and destroyed after the application of jobs.
    It also prepares for a split of the transaction code to a new source.

diff --git a/src/core/job.c b/src/core/job.c
index 9199cf6..543f37d 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -97,7 +97,7 @@ void job_free(Job *j) {
         free(j);
 }
 
-JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts, Transaction *tr) {
         JobDependency *l;
 
         assert(object);
@@ -118,20 +118,20 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool
         if (subject)
                 LIST_PREPEND(JobDependency, subject, subject->subject_list, l);
         else
-                LIST_PREPEND(JobDependency, subject, object->manager->transaction_anchor, l);
+                LIST_PREPEND(JobDependency, subject, tr->anchor, l);
 
         LIST_PREPEND(JobDependency, object, object->object_list, l);
 
         return l;
 }
 
-void job_dependency_free(JobDependency *l) {
+void job_dependency_free(JobDependency *l, Transaction *tr) {
         assert(l);
 
         if (l->subject)
                 LIST_REMOVE(JobDependency, subject, l->subject->subject_list, l);
         else
-                LIST_REMOVE(JobDependency, subject, l->object->manager->transaction_anchor, l);
+                LIST_REMOVE(JobDependency, subject, tr->anchor, l);
 
         LIST_REMOVE(JobDependency, object, l->object->object_list, l);
 
diff --git a/src/core/job.h b/src/core/job.h
index 442b13e..4c543f2 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -141,8 +141,8 @@ void job_uninstall(Job *j);
 void job_free(Job *job);
 void job_dump(Job *j, FILE*f, const char *prefix);
 
-JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
-void job_dependency_free(JobDependency *l);
+JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts, Transaction *tr);
+void job_dependency_free(JobDependency *l, Transaction *tr);
 
 bool job_is_anchor(Job *j);
 
diff --git a/src/core/manager.c b/src/core/manager.c
index 445931c..52702f3 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -284,9 +284,6 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) {
         if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func)))
                 goto fail;
 
-        if (!(m->transaction_jobs = hashmap_new(trivial_hash_func, trivial_compare_func)))
-                goto fail;
-
         if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func)))
                 goto fail;
 
@@ -454,14 +451,10 @@ static unsigned manager_dispatch_gc_queue(Manager *m) {
 }
 
 static void manager_clear_jobs_and_units(Manager *m) {
-        Job *j;
         Unit *u;
 
         assert(m);
 
-        while ((j = hashmap_first(m->transaction_jobs)))
-                job_free(j);
-
         while ((u = hashmap_first(m->units)))
                 unit_free(u);
 
@@ -474,7 +467,6 @@ static void manager_clear_jobs_and_units(Manager *m) {
         assert(!m->cleanup_queue);
         assert(!m->gc_queue);
 
-        assert(hashmap_isempty(m->transaction_jobs));
         assert(hashmap_isempty(m->jobs));
         assert(hashmap_isempty(m->units));
 }
@@ -500,7 +492,6 @@ void manager_free(Manager *m) {
 
         hashmap_free(m->units);
         hashmap_free(m->jobs);
-        hashmap_free(m->transaction_jobs);
         hashmap_free(m->watch_pids);
         hashmap_free(m->watch_bus);
 
@@ -662,65 +653,47 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         return r;
 }
 
-static void transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies);
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies);
 
-static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) {
-        assert(m);
+static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) {
+        assert(tr);
         assert(j);
 
         /* Deletes one job from the transaction */
 
-        transaction_unlink_job(m, j, delete_dependencies);
+        transaction_unlink_job(tr, j, delete_dependencies);
 
         if (!j->installed)
                 job_free(j);
 }
 
-static void transaction_delete_unit(Manager *m, Unit *u) {
+static void transaction_delete_unit(Transaction *tr, Unit *u) {
         Job *j;
 
         /* Deletes all jobs associated with a certain unit from the
          * transaction */
 
-        while ((j = hashmap_get(m->transaction_jobs, u)))
-                transaction_delete_job(m, j, true);
+        while ((j = hashmap_get(tr->jobs, u)))
+                transaction_delete_job(tr, j, true);
 }
 
-static void transaction_clean_dependencies(Manager *m) {
-        Iterator i;
+static void transaction_abort(Transaction *tr) {
         Job *j;
 
-        assert(m);
+        assert(tr);
 
-        /* Drops all dependencies of all installed jobs */
+        while ((j = hashmap_first(tr->jobs)))
+                transaction_delete_job(tr, j, true);
 
-        HASHMAP_FOREACH(j, m->jobs, i) {
-                while (j->subject_list)
-                        job_dependency_free(j->subject_list);
-                while (j->object_list)
-                        job_dependency_free(j->object_list);
-        }
+        assert(hashmap_isempty(tr->jobs));
 
-        assert(!m->transaction_anchor);
+        assert(!tr->anchor);
 }
 
-static void transaction_abort(Manager *m) {
-        Job *j;
-
-        assert(m);
-
-        while ((j = hashmap_first(m->transaction_jobs)))
-                transaction_delete_job(m, j, true);
-
-        assert(hashmap_isempty(m->transaction_jobs));
-
-        transaction_clean_dependencies(m);
-}
-
-static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsigned generation) {
+static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, Job *j, unsigned generation) {
         JobDependency *l;
 
-        assert(m);
+        assert(tr);
 
         /* A recursive sweep through the graph that marks all units
          * that matter to the anchor job, i.e. are directly or
@@ -730,7 +703,7 @@ static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsi
         if (j)
                 l = j->subject_list;
         else
-                l = m->transaction_anchor;
+                l = tr->anchor;
 
         LIST_FOREACH(subject, l, l) {
 
@@ -745,11 +718,11 @@ static void transaction_find_jobs_that_matter_to_anchor(Manager *m, Job *j, unsi
                 l->object->matters_to_anchor = true;
                 l->object->generation = generation;
 
-                transaction_find_jobs_that_matter_to_anchor(m, l->object, generation);
+                transaction_find_jobs_that_matter_to_anchor(tr, l->object, generation);
         }
 }
 
-static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, JobType t) {
+static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) {
         JobDependency *l, *last;
 
         assert(j);
@@ -800,7 +773,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job
         /* Kill the other job */
         other->subject_list = NULL;
         other->object_list = NULL;
-        transaction_delete_job(m, other, true);
+        transaction_delete_job(tr, other, true);
 }
 
 static bool job_is_conflicted_by(Job *j) {
@@ -818,7 +791,7 @@ static bool job_is_conflicted_by(Job *j) {
         return false;
 }
 
-static int delete_one_unmergeable_job(Manager *m, Job *j) {
+static int delete_one_unmergeable_job(Transaction *tr, Job *j) {
         Job *k;
 
         assert(j);
@@ -879,23 +852,23 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
 
                         /* Ok, we can drop one, so let's do so. */
                         log_debug("Fixing conflicting jobs by deleting job %s/%s", d->unit->id, job_type_to_string(d->type));
-                        transaction_delete_job(m, d, true);
+                        transaction_delete_job(tr, d, true);
                         return 0;
                 }
 
         return -EINVAL;
 }
 
-static int transaction_merge_jobs(Manager *m, DBusError *e) {
+static int transaction_merge_jobs(Transaction *tr, DBusError *e) {
         Job *j;
         Iterator i;
         int r;
 
-        assert(m);
+        assert(tr);
 
         /* First step, check whether any of the jobs for one specific
          * task conflict. If so, try to drop one of them. */
-        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+        HASHMAP_FOREACH(j, tr->jobs, i) {
                 JobType t;
                 Job *k;
 
@@ -908,7 +881,8 @@ static int transaction_merge_jobs(Manager *m, DBusError *e) {
                          * action. Let's see if we can get rid of one
                          * of them */
 
-                        if ((r = delete_one_unmergeable_job(m, j)) >= 0)
+                        r = delete_one_unmergeable_job(tr, j);
+                        if (r >= 0)
                                 /* Ok, we managed to drop one, now
                                  * let's ask our callers to call us
                                  * again after garbage collecting */
@@ -922,7 +896,7 @@ static int transaction_merge_jobs(Manager *m, DBusError *e) {
         }
 
         /* Second step, merge the jobs. */
-        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+        HASHMAP_FOREACH(j, tr->jobs, i) {
                 JobType t = j->type;
                 Job *k;
 
@@ -936,14 +910,14 @@ static int transaction_merge_jobs(Manager *m, DBusError *e) {
 
                 while ((k = j->transaction_next)) {
                         if (j->installed) {
-                                transaction_merge_and_delete_job(m, k, j, t);
+                                transaction_merge_and_delete_job(tr, k, j, t);
                                 j = k;
                         } else
-                                transaction_merge_and_delete_job(m, j, k, t);
+                                transaction_merge_and_delete_job(tr, j, k, t);
                 }
 
                 if (j->unit->job && !j->installed)
-                        transaction_merge_and_delete_job(m, j, j->unit->job, t);
+                        transaction_merge_and_delete_job(tr, j, j->unit->job, t);
 
                 assert(!j->transaction_next);
                 assert(!j->transaction_prev);
@@ -952,10 +926,10 @@ static int transaction_merge_jobs(Manager *m, DBusError *e) {
         return 0;
 }
 
-static void transaction_drop_redundant(Manager *m) {
+static void transaction_drop_redundant(Transaction *tr) {
         bool again;
 
-        assert(m);
+        assert(tr);
 
         /* Goes through the transaction and removes all jobs that are
          * a noop */
@@ -966,7 +940,7 @@ static void transaction_drop_redundant(Manager *m) {
 
                 again = false;
 
-                HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+                HASHMAP_FOREACH(j, tr->jobs, i) {
                         bool changes_something = false;
                         Job *k;
 
@@ -985,7 +959,7 @@ static void transaction_drop_redundant(Manager *m) {
                                 continue;
 
                         /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */
-                        transaction_delete_job(m, j, false);
+                        transaction_delete_job(tr, j, false);
                         again = true;
                         break;
                 }
@@ -1007,12 +981,12 @@ static bool unit_matters_to_anchor(Unit *u, Job *j) {
         return false;
 }
 
-static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation, DBusError *e) {
+static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, DBusError *e) {
         Iterator i;
         Unit *u;
         int r;
 
-        assert(m);
+        assert(tr);
         assert(j);
         assert(!j->transaction_prev);
 
@@ -1059,7 +1033,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
 
                 if (delete) {
                         log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->id, job_type_to_string(delete->type));
-                        transaction_delete_unit(m, delete->unit);
+                        transaction_delete_unit(tr, delete->unit);
                         return -EAGAIN;
                 }
 
@@ -1082,15 +1056,18 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
                 Job *o;
 
                 /* Is there a job for this unit? */
-                if (!(o = hashmap_get(m->transaction_jobs, u)))
-
+                o = hashmap_get(tr->jobs, u);
+                if (!o) {
                         /* Ok, there is no job for this in the
                          * transaction, but maybe there is already one
                          * running? */
-                        if (!(o = u->job))
+                        o = u->job;
+                        if (!o)
                                 continue;
+                }
 
-                if ((r = transaction_verify_order_one(m, o, j, generation, e)) < 0)
+                r = transaction_verify_order_one(tr, o, j, generation, e);
+                if (r < 0)
                         return r;
         }
 
@@ -1101,13 +1078,13 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
         return 0;
 }
 
-static int transaction_verify_order(Manager *m, unsigned *generation, DBusError *e) {
+static int transaction_verify_order(Transaction *tr, unsigned *generation, DBusError *e) {
         Job *j;
         int r;
         Iterator i;
         unsigned g;
 
-        assert(m);
+        assert(tr);
         assert(generation);
 
         /* Check if the ordering graph is cyclic. If it is, try to fix
@@ -1115,17 +1092,17 @@ static int transaction_verify_order(Manager *m, unsigned *generation, DBusError
 
         g = (*generation)++;
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, i)
-                if ((r = transaction_verify_order_one(m, j, NULL, g, e)) < 0)
+        HASHMAP_FOREACH(j, tr->jobs, i)
+                if ((r = transaction_verify_order_one(tr, j, NULL, g, e)) < 0)
                         return r;
 
         return 0;
 }
 
-static void transaction_collect_garbage(Manager *m) {
+static void transaction_collect_garbage(Transaction *tr) {
         bool again;
 
-        assert(m);
+        assert(tr);
 
         /* Drop jobs that are not required by any other job */
 
@@ -1135,7 +1112,7 @@ static void transaction_collect_garbage(Manager *m) {
 
                 again = false;
 
-                HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+                HASHMAP_FOREACH(j, tr->jobs, i) {
                         if (j->object_list) {
                                 /* log_debug("Keeping job %s/%s because of %s/%s", */
                                 /*           j->unit->id, job_type_to_string(j->type), */
@@ -1145,7 +1122,7 @@ static void transaction_collect_garbage(Manager *m) {
                         }
 
                         /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */
-                        transaction_delete_job(m, j, true);
+                        transaction_delete_job(tr, j, true);
                         again = true;
                         break;
                 }
@@ -1153,16 +1130,16 @@ static void transaction_collect_garbage(Manager *m) {
         } while (again);
 }
 
-static int transaction_is_destructive(Manager *m, DBusError *e) {
+static int transaction_is_destructive(Transaction *tr, DBusError *e) {
         Iterator i;
         Job *j;
 
-        assert(m);
+        assert(tr);
 
         /* Checks whether applying this transaction means that
          * existing jobs would be replaced */
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+        HASHMAP_FOREACH(j, tr->jobs, i) {
 
                 /* Assume merged */
                 assert(!j->transaction_prev);
@@ -1180,9 +1157,9 @@ static int transaction_is_destructive(Manager *m, DBusError *e) {
         return 0;
 }
 
-static void transaction_minimize_impact(Manager *m) {
+static void transaction_minimize_impact(Transaction *tr) {
         bool again;
-        assert(m);
+        assert(tr);
 
         /* Drops all unnecessary jobs that reverse already active jobs
          * or that stop a running service. */
@@ -1193,7 +1170,7 @@ static void transaction_minimize_impact(Manager *m) {
 
                 again = false;
 
-                HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+                HASHMAP_FOREACH(j, tr->jobs, i) {
                         LIST_FOREACH(transaction, j, j) {
                                 bool stops_running_service, changes_existing_job;
 
@@ -1224,7 +1201,7 @@ static void transaction_minimize_impact(Manager *m) {
                                 /* Ok, let's get rid of this */
                                 log_debug("Deleting %s/%s to minimize impact.", j->unit->id, job_type_to_string(j->type));
 
-                                transaction_delete_job(m, j, true);
+                                transaction_delete_job(tr, j, true);
                                 again = true;
                                 break;
                         }
@@ -1236,7 +1213,7 @@ static void transaction_minimize_impact(Manager *m) {
         } while (again);
 }
 
-static int transaction_apply(Manager *m, JobMode mode) {
+static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
         Iterator i;
         Job *j;
         int r;
@@ -1251,7 +1228,7 @@ static int transaction_apply(Manager *m, JobMode mode) {
                 HASHMAP_FOREACH(j, m->jobs, i) {
                         assert(j->installed);
 
-                        if (hashmap_get(m->transaction_jobs, j->unit))
+                        if (hashmap_get(tr->jobs, j->unit))
                                 continue;
 
                         /* 'j' itself is safe to remove, but if other jobs
@@ -1262,7 +1239,7 @@ static int transaction_apply(Manager *m, JobMode mode) {
                 }
         }
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+        HASHMAP_FOREACH(j, tr->jobs, i) {
                 /* Assume merged */
                 assert(!j->transaction_prev);
                 assert(!j->transaction_next);
@@ -1270,11 +1247,12 @@ static int transaction_apply(Manager *m, JobMode mode) {
                 if (j->installed)
                         continue;
 
-                if ((r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j)) < 0)
+                r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
+                if (r < 0)
                         goto rollback;
         }
 
-        while ((j = hashmap_steal_first(m->transaction_jobs))) {
+        while ((j = hashmap_steal_first(tr->jobs))) {
                 Job *uj;
                 if (j->installed) {
                         /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
@@ -1297,6 +1275,9 @@ static int transaction_apply(Manager *m, JobMode mode) {
                 assert(!j->transaction_next);
                 assert(!j->transaction_prev);
 
+                /* Clean the job dependencies */
+                transaction_unlink_job(tr, j, false);
+
                 job_add_to_run_queue(j);
                 job_add_to_dbus_queue(j);
                 job_start_timer(j);
@@ -1304,14 +1285,13 @@ static int transaction_apply(Manager *m, JobMode mode) {
                 log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
         }
 
-        /* As last step, kill all remaining job dependencies. */
-        transaction_clean_dependencies(m);
+        assert(!tr->anchor);
 
         return 0;
 
 rollback:
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+        HASHMAP_FOREACH(j, tr->jobs, i) {
                 if (j->installed)
                         continue;
 
@@ -1321,41 +1301,42 @@ rollback:
         return r;
 }
 
-static int transaction_activate(Manager *m, JobMode mode, DBusError *e) {
+static int transaction_activate(Transaction *tr, Manager *m, JobMode mode, DBusError *e) {
         int r;
         unsigned generation = 1;
 
-        assert(m);
+        assert(tr);
 
-        /* This applies the changes recorded in transaction_jobs to
+        /* This applies the changes recorded in tr->jobs to
          * the actual list of jobs, if possible. */
 
         /* First step: figure out which jobs matter */
-        transaction_find_jobs_that_matter_to_anchor(m, NULL, generation++);
+        transaction_find_jobs_that_matter_to_anchor(tr, NULL, generation++);
 
         /* Second step: Try not to stop any running services if
          * we don't have to. Don't try to reverse running
          * jobs if we don't have to. */
         if (mode == JOB_FAIL)
-                transaction_minimize_impact(m);
+                transaction_minimize_impact(tr);
 
         /* Third step: Drop redundant jobs */
-        transaction_drop_redundant(m);
+        transaction_drop_redundant(tr);
 
         for (;;) {
                 /* Fourth step: Let's remove unneeded jobs that might
                  * be lurking. */
                 if (mode != JOB_ISOLATE)
-                        transaction_collect_garbage(m);
+                        transaction_collect_garbage(tr);
 
                 /* Fifth step: verify order makes sense and correct
                  * cycles if necessary and possible */
-                if ((r = transaction_verify_order(m, &generation, e)) >= 0)
+                r = transaction_verify_order(tr, &generation, e);
+                if (r >= 0)
                         break;
 
                 if (r != -EAGAIN) {
                         log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error(e, r));
-                        goto rollback;
+                        return r;
                 }
 
                 /* Let's see if the resulting transaction ordering
@@ -1366,60 +1347,60 @@ static int transaction_activate(Manager *m, JobMode mode, DBusError *e) {
                 /* Sixth step: let's drop unmergeable entries if
                  * necessary and possible, merge entries we can
                  * merge */
-                if ((r = transaction_merge_jobs(m, e)) >= 0)
+                r = transaction_merge_jobs(tr, e);
+                if (r >= 0)
                         break;
 
                 if (r != -EAGAIN) {
                         log_warning("Requested transaction contains unmergeable jobs: %s", bus_error(e, r));
-                        goto rollback;
+                        return r;
                 }
 
                 /* Seventh step: an entry got dropped, let's garbage
                  * collect its dependencies. */
                 if (mode != JOB_ISOLATE)
-                        transaction_collect_garbage(m);
+                        transaction_collect_garbage(tr);
 
                 /* Let's see if the resulting transaction still has
                  * unmergeable entries ... */
         }
 
         /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
-        transaction_drop_redundant(m);
+        transaction_drop_redundant(tr);
 
         /* Ninth step: check whether we can actually apply this */
-        if (mode == JOB_FAIL)
-                if ((r = transaction_is_destructive(m, e)) < 0) {
+        if (mode == JOB_FAIL) {
+                r = transaction_is_destructive(tr, e);
+                if (r < 0) {
                         log_notice("Requested transaction contradicts existing jobs: %s", bus_error(e, r));
-                        goto rollback;
+                        return r;
                 }
+        }
 
         /* Tenth step: apply changes */
-        if ((r = transaction_apply(m, mode)) < 0) {
+        r = transaction_apply(tr, m, mode);
+        if (r < 0) {
                 log_warning("Failed to apply transaction: %s", strerror(-r));
-                goto rollback;
+                return r;
         }
 
-        assert(hashmap_isempty(m->transaction_jobs));
-        assert(!m->transaction_anchor);
+        assert(hashmap_isempty(tr->jobs));
+        assert(!tr->anchor);
 
         return 0;
-
-rollback:
-        transaction_abort(m);
-        return r;
 }
 
-static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool override, bool *is_new) {
+static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool override, bool *is_new) {
         Job *j, *f;
 
-        assert(m);
+        assert(tr);
         assert(unit);
 
         /* Looks for an existing prospective job and returns that. If
          * it doesn't exist it is created and added to the prospective
          * jobs list. */
 
-        f = hashmap_get(m->transaction_jobs, unit);
+        f = hashmap_get(tr->jobs, unit);
 
         LIST_FOREACH(transaction, j, f) {
                 assert(j->unit == unit);
@@ -1433,8 +1414,11 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool o
 
         if (unit->job && unit->job->type == type)
                 j = unit->job;
-        else if (!(j = job_new(m, type, unit)))
-                return NULL;
+        else {
+                j = job_new(unit->manager, type, unit);
+                if (!j)
+                        return NULL;
+        }
 
         j->generation = 0;
         j->marker = NULL;
@@ -1443,7 +1427,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool o
 
         LIST_PREPEND(Job, transaction, f, j);
 
-        if (hashmap_replace(m->transaction_jobs, unit, f) < 0) {
+        if (hashmap_replace(tr->jobs, unit, f) < 0) {
                 LIST_REMOVE(Job, transaction, f, j);
                 job_free(j);
                 return NULL;
@@ -1457,16 +1441,16 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool o
         return j;
 }
 
-static void transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
-        assert(m);
+static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) {
+        assert(tr);
         assert(j);
 
         if (j->transaction_prev)
                 j->transaction_prev->transaction_next = j->transaction_next;
         else if (j->transaction_next)
-                hashmap_replace(m->transaction_jobs, j->unit, j->transaction_next);
+                hashmap_replace(tr->jobs, j->unit, j->transaction_next);
         else
-                hashmap_remove_value(m->transaction_jobs, j->unit, j);
+                hashmap_remove_value(tr->jobs, j->unit, j);
 
         if (j->transaction_next)
                 j->transaction_next->transaction_prev = j->transaction_prev;
@@ -1474,24 +1458,24 @@ static void transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies)
         j->transaction_prev = j->transaction_next = NULL;
 
         while (j->subject_list)
-                job_dependency_free(j->subject_list);
+                job_dependency_free(j->subject_list, tr);
 
         while (j->object_list) {
                 Job *other = j->object_list->matters ? j->object_list->subject : NULL;
 
-                job_dependency_free(j->object_list);
+                job_dependency_free(j->object_list, tr);
 
                 if (other && delete_dependencies) {
                         log_debug("Deleting job %s/%s as dependency of job %s/%s",
                                   other->unit->id, job_type_to_string(other->type),
                                   j->unit->id, job_type_to_string(j->type));
-                        transaction_delete_job(m, other, delete_dependencies);
+                        transaction_delete_job(tr, other, delete_dependencies);
                 }
         }
 }
 
 static int transaction_add_job_and_dependencies(
-                Manager *m,
+                Transaction *tr,
                 JobType type,
                 Unit *unit,
                 Job *by,
@@ -1508,7 +1492,7 @@ static int transaction_add_job_and_dependencies(
         int r;
         bool is_new;
 
-        assert(m);
+        assert(tr);
         assert(type < _JOB_TYPE_MAX);
         assert(unit);
 
@@ -1545,13 +1529,14 @@ static int transaction_add_job_and_dependencies(
         }
 
         /* First add the job. */
-        if (!(ret = transaction_add_one_job(m, type, unit, override, &is_new)))
+        ret = transaction_add_one_job(tr, type, unit, override, &is_new);
+        if (!ret)
                 return -ENOMEM;
 
         ret->ignore_order = ret->ignore_order || ignore_order;
 
         /* Then, add a link to the job. */
-        if (!job_dependency_new(by, ret, matters, conflicts))
+        if (!job_dependency_new(by, ret, matters, conflicts, tr))
                 return -ENOMEM;
 
         if (is_new && !ignore_requirements) {
@@ -1560,120 +1545,136 @@ static int transaction_add_job_and_dependencies(
                 /* If we are following some other unit, make sure we
                  * add all dependencies of everybody following. */
                 if (unit_following_set(ret->unit, &following) > 0) {
-                        SET_FOREACH(dep, following, i)
-                                if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, following, i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
                         set_free(following);
                 }
 
                 /* Finally, recursively add in all dependencies. */
                 if (type == JOB_START || type == JOB_RELOAD_OR_START) {
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) {
-
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BIND_TO], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) {
-
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL)) < 0) {
-
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, override, true, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i)
-                                if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL)) < 0) {
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
                 }
 
                 if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i)
-                                if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) {
-
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRED_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
 
-                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i)
-                                if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, false, ignore_order, e, NULL)) < 0) {
-
+                        SET_FOREACH(dep, ret->unit->dependencies[UNIT_BOUND_BY], i) {
+                                r = transaction_add_job_and_dependencies(tr, type, dep, ret, true, override, false, false, ignore_order, e, NULL);
+                                if (r < 0) {
                                         if (r != -EBADR)
                                                 goto fail;
 
                                         if (e)
                                                 dbus_error_free(e);
                                 }
+                        }
                 }
 
                 if (type == JOB_RELOAD || type == JOB_RELOAD_OR_START) {
 
                         SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATE_RELOAD_TO], i) {
-                                r = transaction_add_job_and_dependencies(m, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL);
-
+                                r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, override, false, false, ignore_order, e, NULL);
                                 if (r < 0) {
                                         log_warning("Cannot add dependency reload job for unit %s, ignoring: %s", dep->id, bus_error(e, r));
 
@@ -1695,12 +1696,13 @@ fail:
         return r;
 }
 
-static int transaction_add_isolate_jobs(Manager *m) {
+static int transaction_add_isolate_jobs(Transaction *tr, Manager *m) {
         Iterator i;
         Unit *u;
         char *k;
         int r;
 
+        assert(tr);
         assert(m);
 
         HASHMAP_FOREACH_KEY(u, k, m->units, i) {
@@ -1717,19 +1719,43 @@ static int transaction_add_isolate_jobs(Manager *m) {
                         continue;
 
                 /* Is there already something listed for this? */
-                if (hashmap_get(m->transaction_jobs, u))
+                if (hashmap_get(tr->jobs, u))
                         continue;
 
-                if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL)) < 0)
+                r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, NULL, true, false, false, false, false, NULL, NULL);
+                if (r < 0)
                         log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->id, strerror(-r));
         }
 
         return 0;
 }
 
+static Transaction *transaction_new(void) {
+        Transaction *tr;
+
+        tr = new0(Transaction, 1);
+        if (!tr)
+                return NULL;
+
+        tr->jobs = hashmap_new(trivial_hash_func, trivial_compare_func);
+        if (!tr->jobs) {
+                free(tr);
+                return NULL;
+        }
+
+        return tr;
+}
+
+static void transaction_free(Transaction *tr) {
+        assert(hashmap_isempty(tr->jobs));
+        hashmap_free(tr->jobs);
+        free(tr);
+}
+
 int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool override, DBusError *e, Job **_ret) {
         int r;
         Job *ret;
+        Transaction *tr;
 
         assert(m);
         assert(type < _JOB_TYPE_MAX);
@@ -1748,28 +1774,38 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
 
         log_debug("Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode));
 
-        if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false,
-                                                      mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS,
-                                                      mode == JOB_IGNORE_DEPENDENCIES, e, &ret)) < 0) {
-                transaction_abort(m);
-                return r;
-        }
+        tr = transaction_new();
+        if (!tr)
+                return -ENOMEM;
 
-        if (mode == JOB_ISOLATE)
-                if ((r = transaction_add_isolate_jobs(m)) < 0) {
-                        transaction_abort(m);
-                        return r;
-                }
+        r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, override, false,
+                                                 mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS,
+                                                 mode == JOB_IGNORE_DEPENDENCIES, e, &ret);
+        if (r < 0)
+                goto tr_abort;
 
-        if ((r = transaction_activate(m, mode, e)) < 0)
-                return r;
+        if (mode == JOB_ISOLATE) {
+                r = transaction_add_isolate_jobs(tr, m);
+                if (r < 0)
+                        goto tr_abort;
+        }
+
+        r = transaction_activate(tr, m, mode, e);
+        if (r < 0)
+                goto tr_abort;
 
         log_debug("Enqueued job %s/%s as %u", unit->id, job_type_to_string(type), (unsigned) ret->id);
 
         if (_ret)
                 *_ret = ret;
 
+        transaction_free(tr);
         return 0;
+
+tr_abort:
+        transaction_abort(tr);
+        transaction_free(tr);
+        return r;
 }
 
 int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, bool override, DBusError *e, Job **_ret) {
@@ -1933,8 +1969,6 @@ void manager_clear_jobs(Manager *m) {
 
         assert(m);
 
-        transaction_abort(m);
-
         while ((j = hashmap_first(m->jobs)))
                 job_finish_and_invalidate(j, JOB_CANCELED);
 }
diff --git a/src/core/manager.h b/src/core/manager.h
index 0e9c0d7..2bf7b7a 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -33,6 +33,7 @@
 #define MANAGER_MAX_NAMES 131072 /* 128K */
 
 typedef struct Manager Manager;
+typedef struct Transaction Transaction;
 typedef enum WatchType WatchType;
 typedef struct Watch Watch;
 
@@ -91,6 +92,12 @@ struct Watch {
 #include "dbus.h"
 #include "path-lookup.h"
 
+struct Transaction {
+        /* Jobs to be added */
+        Hashmap *jobs;      /* Unit object => Job object list 1:1 */
+        JobDependency *anchor;
+};
+
 struct Manager {
         /* Note that the set of units we know of is allowed to be
          * inconsistent. However the subset of it that is loaded may
@@ -123,10 +130,6 @@ struct Manager {
         /* Units to check when doing GC */
         LIST_HEAD(Unit, gc_queue);
 
-        /* Jobs to be added */
-        Hashmap *transaction_jobs;      /* Unit object => Job object list 1:1 */
-        JobDependency *transaction_anchor;
-
         Hashmap *watch_pids;  /* pid => Unit object n:1 */
 
         char *notify_socket;

commit 97e7d748d1bf26790fc3b2607885f4ac8c4ecf3a
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Fri Apr 20 10:21:37 2012 +0200

    job: job_uninstall()
    
    Split the uninstallation of the job from job_free() into a separate function.
    Adjust the callers.
    
    job_free() now only works on unlinked and uninstalled jobs. This enforces clear
    thinking about job lifetimes.

diff --git a/src/core/job.c b/src/core/job.c
index 5ea717e..9199cf6 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -55,22 +55,24 @@ Job* job_new(Manager *m, JobType type, Unit *unit) {
         return j;
 }
 
-void job_free(Job *j) {
-        assert(j);
-
+void job_uninstall(Job *j) {
+        assert(j->installed);
         /* Detach from next 'bigger' objects */
-        if (j->installed) {
-                bus_job_send_removed_signal(j);
 
-                if (j->unit->job == j) {
-                        j->unit->job = NULL;
-                        unit_add_to_gc_queue(j->unit);
-                }
+        bus_job_send_removed_signal(j);
 
-                hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
-                j->installed = false;
+        if (j->unit->job == j) {
+                j->unit->job = NULL;
+                unit_add_to_gc_queue(j->unit);
         }
 
+        hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
+        j->installed = false;
+}
+
+void job_free(Job *j) {
+        assert(j);
+        assert(!j->installed);
         assert(!j->transaction_prev);
         assert(!j->transaction_next);
         assert(!j->subject_list);
@@ -492,6 +494,7 @@ int job_finish_and_invalidate(Job *j, JobResult result) {
 
         u = j->unit;
         t = j->type;
+        job_uninstall(j);
         job_free(j);
 
         job_print_status_message(u, t, result);
diff --git a/src/core/job.h b/src/core/job.h
index 3ce2d65..442b13e 100644
--- a/src/core/job.h
+++ b/src/core/job.h
@@ -137,6 +137,7 @@ struct Job {
 };
 
 Job* job_new(Manager *m, JobType type, Unit *unit);
+void job_uninstall(Job *j);
 void job_free(Job *job);
 void job_dump(Job *j, FILE*f, const char *prefix);
 
diff --git a/src/core/manager.c b/src/core/manager.c
index 546b323..445931c 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1275,13 +1275,17 @@ static int transaction_apply(Manager *m, JobMode mode) {
         }
 
         while ((j = hashmap_steal_first(m->transaction_jobs))) {
+                Job *uj;
                 if (j->installed) {
                         /* log_debug("Skipping already installed job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id); */
                         continue;
                 }
 
-                if (j->unit->job)
-                        job_free(j->unit->job);
+                uj = j->unit->job;
+                if (uj) {
+                        job_uninstall(uj);
+                        job_free(uj);
+                }
 
                 j->unit->job = j;
                 j->installed = true;
diff --git a/src/core/unit.c b/src/core/unit.c
index df79fe3..ed3f176 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -352,8 +352,11 @@ void unit_free(Unit *u) {
         SET_FOREACH(t, u->names, i)
                 hashmap_remove_value(u->manager->units, t, u);
 
-        if (u->job)
-                job_free(u->job);
+        if (u->job) {
+                Job *j = u->job;
+                job_uninstall(j);
+                job_free(j);
+        }
 
         for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
                 bidi_set_free(u, u->dependencies[d]);

commit 121b3b318042b7fd67ac96601971c1c2f9b77be5
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Thu Apr 19 23:23:04 2012 +0200

    manager: simplify transaction_abort()
    
    This is equivalent.

diff --git a/src/core/manager.c b/src/core/manager.c
index aa918f1..546b323 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -710,12 +710,7 @@ static void transaction_abort(Manager *m) {
         assert(m);
 
         while ((j = hashmap_first(m->transaction_jobs)))
-                if (j->installed)
-                        transaction_delete_job(m, j, true);
-                else {
-                        transaction_unlink_job(m, j, true);
-                        job_free(j);
-                }
+                transaction_delete_job(m, j, true);
 
         assert(hashmap_isempty(m->transaction_jobs));
 

commit 02a3bcc6b4372ca50c0a62b193f9a75b988ffa69
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Thu Apr 19 23:20:34 2012 +0200

    job: allow job_free() only on already unlinked jobs
    
    job_free() is IMO too helpful when it unlinks the job from the transaction.
    The callers should ensure the job is already unlinked before freeing.
    The added assertions check if anyone gets it wrong.

diff --git a/src/core/job.c b/src/core/job.c
index f3c76d6..5ea717e 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -71,8 +71,10 @@ void job_free(Job *j) {
                 j->installed = false;
         }
 
-        /* Detach from next 'smaller' objects */
-        manager_transaction_unlink_job(j->manager, j, true);
+        assert(!j->transaction_prev);
+        assert(!j->transaction_next);
+        assert(!j->subject_list);
+        assert(!j->object_list);
 
         if (j->in_run_queue)
                 LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
diff --git a/src/core/manager.c b/src/core/manager.c
index 9505629..aa918f1 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -662,13 +662,15 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
         return r;
 }
 
+static void transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies);
+
 static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) {
         assert(m);
         assert(j);
 
         /* Deletes one job from the transaction */
 
-        manager_transaction_unlink_job(m, j, delete_dependencies);
+        transaction_unlink_job(m, j, delete_dependencies);
 
         if (!j->installed)
                 job_free(j);
@@ -710,8 +712,10 @@ static void transaction_abort(Manager *m) {
         while ((j = hashmap_first(m->transaction_jobs)))
                 if (j->installed)
                         transaction_delete_job(m, j, true);
-                else
+                else {
+                        transaction_unlink_job(m, j, true);
                         job_free(j);
+                }
 
         assert(hashmap_isempty(m->transaction_jobs));
 
@@ -1441,6 +1445,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool o
         LIST_PREPEND(Job, transaction, f, j);
 
         if (hashmap_replace(m->transaction_jobs, unit, f) < 0) {
+                LIST_REMOVE(Job, transaction, f, j);
                 job_free(j);
                 return NULL;
         }
@@ -1453,7 +1458,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool o
         return j;
 }
 
-void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
+static void transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
         assert(m);
         assert(j);
 
diff --git a/src/core/manager.h b/src/core/manager.h
index 39e16ae..0e9c0d7 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -257,8 +257,6 @@ int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode
 void manager_dump_units(Manager *s, FILE *f, const char *prefix);
 void manager_dump_jobs(Manager *s, FILE *f, const char *prefix);
 
-void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies);
-
 void manager_clear_jobs(Manager *m);
 
 unsigned manager_dispatch_load_queue(Manager *m);

commit 153bda8f03c670caf137f745350c0215b9be2147
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Tue Apr 17 22:54:23 2012 +0200

    manager: fix comment

diff --git a/src/core/manager.c b/src/core/manager.c
index 1446c01..9505629 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -758,7 +758,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job
         assert(j->unit == other->unit);
         assert(!j->installed);
 
-        /* Merges 'other' into 'j' and then deletes j. */
+        /* Merges 'other' into 'j' and then deletes 'other'. */
 
         j->type = t;
         j->state = JOB_WAITING;
@@ -803,6 +803,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job
         other->object_list = NULL;
         transaction_delete_job(m, other, true);
 }
+
 static bool job_is_conflicted_by(Job *j) {
         JobDependency *l;
 

commit a48f3d156652cc241eb67fdf34041d1b7cdb71fb
Author: Michal Schmidt <mschmidt at redhat.com>
Date:   Tue Apr 17 22:53:35 2012 +0200

    tmpfiles: fix error message

diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 5db827e..d8fcb40 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1291,8 +1291,8 @@ int main(int argc, char *argv[]) {
                                     "/usr/lib/tmpfiles.d",
                                     NULL);
                 if (r < 0) {
-                        r = EXIT_FAILURE;
                         log_error("Failed to enumerate tmpfiles.d files: %s", strerror(-r));
+                        r = EXIT_FAILURE;
                         goto finish;
                 }
 



More information about the systemd-commits mailing list