dst->b_info.video_memkb = src->b_info.video_memkb;
}
+void libxl__ev_devlock_init(libxl__ev_devlock *lock)
+{
+ libxl__ev_child_init(&lock->child);
+ lock->path = NULL;
+ lock->fd = -1;
+ lock->held = false;
+}
+
+static void ev_lock_prepare_fork(libxl__egc *egc, libxl__ev_devlock *lock);
+static void ev_lock_child_callback(libxl__egc *egc, libxl__ev_child *child,
+ pid_t pid, int status);
+
+void libxl__ev_devlock_lock(libxl__egc *egc, libxl__ev_devlock *lock)
+{
+ STATE_AO_GC(lock->ao);
+ const char *lockfile;
+
+ lockfile = libxl__userdata_path(gc, lock->domid,
+ "libxl-device-changes-lock", "l");
+ if (!lockfile) goto out;
+ lock->path = libxl__strdup(NOGC, lockfile);
+
+ ev_lock_prepare_fork(egc, lock);
+ return;
+out:
+ lock->callback(egc, lock, ERROR_LOCK_FAIL);
+}
+
+static void ev_lock_prepare_fork(libxl__egc *egc, libxl__ev_devlock *lock)
+{
+ STATE_AO_GC(lock->ao);
+ pid_t pid;
+ int fd;
+
+ /* Convenience aliases */
+ libxl_domid domid = lock->domid;
+ const char *lockfile = lock->path;
+
+ lock->fd = open(lockfile, O_RDWR|O_CREAT, 0666);
+ if (lock->fd < 0) {
+ LOGED(ERROR, domid, "cannot open lockfile %s", lockfile);
+ goto out;
+ }
+ fd = lock->fd;
+
+ pid = libxl__ev_child_fork(gc, &lock->child, ev_lock_child_callback);
+ if (pid < 0)
+ goto out;
+ if (!pid) {
+ /* child */
+ int exit_val = 0;
+
+ /* Lock the file in exclusive mode, wait indefinitely to
+ * acquire the lock */
+ while (flock(fd, LOCK_EX)) {
+ switch (errno) {
+ case EINTR:
+ /* Signal received, retry */
+ continue;
+ default:
+ /* All other errno: EBADF, EINVAL, ENOLCK, EWOULDBLOCK */
+ LOGED(ERROR, domid,
+ "unexpected error while trying to lock %s, fd=%d",
+ lockfile, fd);
+ exit_val = 1;
+ break;
+ }
+ }
+ _exit(exit_val);
+ }
+
+ /* Now that the child has the fd, set cloexec in the parent to prevent
+ * more leakage than necessary */
+ libxl_fd_set_cloexec(CTX, fd, 1);
+ return;
+out:
+ libxl__ev_devlock_unlock(gc, lock);
+ lock->callback(egc, lock, ERROR_LOCK_FAIL);
+}
+
+static void ev_lock_child_callback(libxl__egc *egc, libxl__ev_child *child,
+ pid_t pid, int status)
+{
+ EGC_GC;
+ libxl__ev_devlock *lock = CONTAINER_OF(child, *lock, child);
+ struct stat stab, fstab;
+ int rc = ERROR_LOCK_FAIL;
+
+ /* Convenience aliases */
+ int fd = lock->fd;
+ const char *lockfile = lock->path;
+ libxl_domid domid = lock->domid;
+
+ if (status) {
+ libxl_report_child_exitstatus(CTX, XTL_ERROR, "flock child",
+ pid, status);
+ goto out;
+ }
+
+ if (fstat(fd, &fstab)) {
+ LOGED(ERROR, domid, "cannot fstat %s, fd=%d", lockfile, fd);
+ goto out;
+ }
+ if (stat(lockfile, &stab)) {
+ if (errno != ENOENT) {
+ LOGED(ERROR, domid, "cannot stat %s", lockfile);
+ goto out;
+ }
+ } else {
+ if (stab.st_dev == fstab.st_dev && stab.st_ino == fstab.st_ino) {
+ /* We held the lock */
+ lock->held = true;
+ rc = 0;
+ goto out;
+ }
+ }
+
+ /* We didn't grab the lock, let's try again */
+ flock(lock->fd, LOCK_UN);
+ close(lock->fd);
+ lock->fd = -1;
+ ev_lock_prepare_fork(egc, lock);
+ return;
+
+out:
+ if (lock->held) {
+ /* Check the domain is still there, if not we should release the
+ * lock and clean up. */
+ if (libxl_domain_info(CTX, NULL, domid))
+ rc = ERROR_LOCK_FAIL;
+ }
+ if (rc) {
+ LOGD(ERROR, domid, "Failed to grab qmp-lock");
+ libxl__ev_devlock_unlock(gc, lock);
+ }
+ lock->callback(egc, lock, rc);
+}
+
+void libxl__ev_devlock_unlock(libxl__gc *gc, libxl__ev_devlock *lock)
+{
+ int r;
+
+ assert(!libxl__ev_child_inuse(&lock->child));
+
+ /* See the rationale in libxl__unlock_domain_userdata()
+ * about why we do unlink() before unlock(). */
+
+ if (lock->path && lock->held)
+ unlink(lock->path);
+
+ if (lock->fd >= 0) {
+ /* We need to call unlock as the fd may have leaked into other
+ * processes */
+ r = flock(lock->fd, LOCK_UN);
+ if (r)
+ LOGED(ERROR, lock->domid, "failed to unlock fd=%d, path=%s",
+ lock->fd, lock->path);
+ close(lock->fd);
+ }
+ free(lock->path);
+ libxl__ev_devlock_init(lock);
+}
+
/*
* Local variables:
* mode: C
typedef struct libxl__osevent_hook_nexi libxl__osevent_hook_nexi;
typedef struct libxl__json_object libxl__json_object;
typedef struct libxl__carefd libxl__carefd;
+typedef struct libxl__ev_devlock libxl__ev_devlock;
typedef struct libxl__domain_create_state libxl__domain_create_state;
typedef void libxl__domain_create_cb(struct libxl__egc *egc,
* device information, in JSON files, so that we can use this JSON
* file as a template to reconstruct domain configuration.
*
- * In essense there are now two views of device state, one is xenstore,
- * the other is JSON file. We use xenstore as primary reference.
+ * In essense there are now two views of device state, one is the
+ * primary config (xenstore or QEMU), the other is JSON file.
*
- * Here we maintain one invariant: every device in xenstore must have
- * an entry in JSON file.
+ * Here we maintain one invariant: every device in the primary config
+ * must have an entry in JSON file.
*
* All device hotplug routines should comply to following pattern:
* lock json config (json_lock)
* end for loop
* unlock json config
*
+ * Or in case QEMU is the primary config, this pattern can be use:
+ * qmp_lock (libxl__ev_devlock)
+ * lock json config (json_lock)
+ * read json config
+ * update in-memory json config with new entry, replacing
+ * any stale entry
+ * unlock json config
+ * apply new config to primary config
+ * lock json config (json_lock)
+ * read json config
+ * update in-memory json config with new entry, replacing
+ * any stale entry
+ * write in-memory json config to disk
+ * unlock json config
+ * unlock qmp_lock
+ * (CTX_LOCK can be acquired and released several time while holding the
+ * qmp_lock)
+ *
* Device removal routines are not touched.
*
* Here is the proof that we always maintain that invariant and we
{
return GCSPRINTF("%s/qmp-libxl-%d", libxl__run_dir_path(), domid);
}
+
+/*
+ * Lock for device hotplug, qmp_lock.
+ *
+ * libxl__ev_devlock implement a lock that is outside of CTX_LOCK in the
+ * lock hierarchy. It can be used when one want to make QMP calls to QEMU,
+ * which may take a significant amount time.
+ * It is to be acquired by an ao event callback.
+ *
+ * It is to be acquired when adding/removing devices or making changes
+ * to them when this is a slow operation and json_lock isn't appropriate.
+ *
+ * Possible states of libxl__ev_devlock:
+ * Undefined
+ * Might contain anything.
+ * Idle
+ * Struct contents are defined enough to pass to any
+ * libxl__ev_devlock_* function.
+ * The struct does not contain references to any allocated private
+ * resources so can be thrown away.
+ * Active
+ * Waiting to get a lock.
+ * Needs to wait until the callback is called.
+ * LockAcquired
+ * libxl__ev_devlock_unlock will need to be called to release the lock
+ * and the resources of libxl__ev_devlock.
+ *
+ * libxl__ev_devlock_init: Undefined/Idle -> Idle
+ * libxl__ev_devlock_lock: Idle -> Active
+ * May call callback synchronously.
+ * libxl__ev_devlock_unlock: LockAcquired/Idle -> Idle
+ * callback: When called: Active -> LockAcquired (on error: Idle)
+ * The callback is only called once.
+ */
+struct libxl__ev_devlock {
+ /* filled by user */
+ libxl__ao *ao;
+ libxl_domid domid;
+ void (*callback)(libxl__egc *, libxl__ev_devlock *, int rc);
+ /* private to libxl__ev_devlock* */
+ libxl__ev_child child;
+ char *path; /* path of the lock file itself */
+ int fd;
+ bool held;
+};
+_hidden void libxl__ev_devlock_init(libxl__ev_devlock *);
+_hidden void libxl__ev_devlock_lock(libxl__egc *, libxl__ev_devlock *);
+_hidden void libxl__ev_devlock_unlock(libxl__gc *, libxl__ev_devlock *);
+
#endif
/*