[gnome-ostree] qa: Extract guestmount into guestfish helper class



commit 0fbfdaa30ad316984acbae9ced9200888d416e30
Author: Colin Walters <walters verbum org>
Date:   Thu Jan 10 08:33:19 2013 -0500

    qa: Extract guestmount into guestfish helper class
    
    This is significantly cleaner, since we want other bits that also do
    the mounting.

 src/ostbuild/js/builtins/qa_make_disk.js   |    6 +-
 src/ostbuild/js/builtins/qa_pull_deploy.js |  220 ++++++++++------------------
 src/ostbuild/js/builtins/qa_smoketest.js   |   82 ++++++++++
 src/ostbuild/js/guestfish.js               |  152 +++++++++++++++++---
 src/ostbuild/js/libqa.js                   |   38 +++++
 5 files changed, 336 insertions(+), 162 deletions(-)
---
diff --git a/src/ostbuild/js/builtins/qa_make_disk.js b/src/ostbuild/js/builtins/qa_make_disk.js
index b125505..0396170 100644
--- a/src/ostbuild/js/builtins/qa_make_disk.js
+++ b/src/ostbuild/js/builtins/qa_make_disk.js
@@ -56,8 +56,8 @@ const QaMakeDisk = new Lang.Class({
 part-init /dev/vda mbr\n\
 blockdev-getsize64 /dev/vda\n\
 blockdev-getss /dev/vda\n';
-        let gf = new GuestFish.GuestFish(tmppath, true);
-        let lines = gf.run(makeDiskCmd, cancellable, {partitionOpts: [], readWrite: true});
+        let gf = new GuestFish.GuestFish(tmppath, {partitionOpts: [], readWrite: true});
+        let lines = gf.run(makeDiskCmd, cancellable);
         if (lines.length != 2)
             throw new Error("guestfish returned unexpected output lines (" + lines.length + ", expected 2");
         let diskBytesize = parseInt(lines[0]);
@@ -86,7 +86,7 @@ mkdir /boot\n\
     swapOffset, rootOffset - 1,
     rootOffset, endOffset - 1]);
         print("partition config: ", partconfig);
-        lines = gf.run(partconfig, cancellable, {partitionOpts: [], readWrite: true});
+        lines = gf.run(partconfig, cancellable);
         GSystem.file_rename(tmppath, path, cancellable);
         print("Created: " + path.get_path());
     }
diff --git a/src/ostbuild/js/builtins/qa_pull_deploy.js b/src/ostbuild/js/builtins/qa_pull_deploy.js
index 353ae7d..e2b88ca 100644
--- a/src/ostbuild/js/builtins/qa_pull_deploy.js
+++ b/src/ostbuild/js/builtins/qa_pull_deploy.js
@@ -66,6 +66,75 @@ const QaPullDeploy = new Lang.Class({
         return path;
     },
 
+    _doPullDeloy: function(args, cancellable) {
+        let mntdir = this._mntdir;
+        let bootdir = mntdir.get_child('boot');
+        let ostreedir = mntdir.get_child('ostree');
+        let ostree_osdir = ostreedir.resolve_relative_path('deploy/' + args.osname);
+
+        let adminCmd = ['ostree', 'admin', '--ostree-dir=' + ostreedir.get_path(),
+                        '--boot-dir=' + mntdir.get_child('boot').get_path()];
+        let adminEnv = GLib.get_environ();
+        adminEnv.push('LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1');
+        let procdir = mntdir.get_child('proc');
+        if (!procdir.query_exists(cancellable)) {
+            ProcUtil.runSync(adminCmd.concat(['init-fs', mntdir.get_path()]), cancellable,
+                             {logInitiation: true, env: adminEnv});
+        }
+
+        // *** NOTE ***
+        // Here we blow away any current deployment.  This is pretty lame, but it
+        // avoids us triggering a variety of guestfs/FUSE bugs =(
+        // See: https://bugzilla.redhat.com/show_bug.cgi?id=892834
+        //
+        // But regardless, it's probably useful if every
+        // deployment starts clean, and callers can use libguestfs
+        // to crack the FS open afterwards and modify config files
+        // or the like.
+        GSystem.shutil_rm_rf(ostree_osdir, cancellable);
+
+        ProcUtil.runSync(adminCmd.concat(['os-init', args.osname]), cancellable,
+                         {logInitiation: true, env: adminEnv});
+        ProcUtil.runSync(['ostree', '--repo=' + ostreedir.get_child('repo').get_path(),
+                          'pull-local', args.srcrepo, args.target], cancellable,
+                         {logInitiation: true, env: adminEnv});
+
+        ProcUtil.runSync(adminCmd.concat(['deploy', '--no-kernel', args.osname, args.target]), cancellable,
+                         {logInitiation: true, env: adminEnv});
+        ProcUtil.runSync(adminCmd.concat(['update-kernel', '--no-bootloader', args.osname]), cancellable,
+                         {logInitiation: true, env: adminEnv});
+        ProcUtil.runSync(adminCmd.concat(['prune', args.osname]), cancellable,
+                         {logInitiation: true, env: adminEnv});
+
+        let deployKernelPath = this._findCurrentKernel(mntdir, args.osname, cancellable);
+        let bootKernelPath = bootdir.resolve_relative_path('ostree/' + deployKernelPath.get_basename());
+        if (!bootKernelPath.query_exists(cancellable))
+            throw new Error("" + bootKernelPath.get_path() + " doesn't exist");
+        let kernelRelease = this._parseKernelRelease(deployKernelPath);
+        let initramfsPath = this._getInitramfsPath(mntdir, kernelRelease);
+
+        let defaultFstab = 'LABEL=gnostree-root / ext4 defaults 1 1\n\
+LABEL=gnostree-boot /boot ext4 defaults 1 2\n\
+LABEL=gnostree-swap swap swap defaults 0 0\n';
+        let fstabPath = ostreedir.resolve_relative_path('deploy/gnome-ostree/current-etc/fstab'); 
+        fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+        
+        let grubDir = mntdir.resolve_relative_path('boot/grub');
+        GSystem.file_ensure_directory(grubDir, false, cancellable);
+        let bootRelativeKernelPath = bootdir.get_relative_path(bootKernelPath);
+        if (bootRelativeKernelPath == null)
+            throw new Error("" + bootKernelPath.get_path() + " is not relative to " + bootdir.get_path());
+        let bootRelativeInitramfsPath = bootdir.get_relative_path(initramfsPath);
+        let grubConfPath = grubDir.get_child('grub.conf');
+        let grubConf = Format.vprintf('default=0\n\
+timeout=5\n\
+title %s\n\
+root (hd0,0)\n\
+kernel /%s root=LABEL=gnostree-root ostree=%s/current\n\
+initrd /%s\n', [args.osname, bootRelativeKernelPath, args.osname, bootRelativeInitramfsPath]);
+        grubConfPath.replace_contents(grubConf, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+    },
+
     execute: function(argv) {
         let cancellable = null;
         let parser = new ArgParse.ArgumentParser("Generate a disk image");
@@ -78,150 +147,23 @@ const QaPullDeploy = new Lang.Class({
 
         let diskpath = Gio.File.new_for_path(args.diskpath);
 
-        let workdir = Gio.File.new_for_path('.');
-        let mntdir = workdir.get_child('mnt');
-        GSystem.file_ensure_directory(mntdir, true, cancellable);
-        let bootdir = mntdir.get_child('boot');
-        let ostreedir = mntdir.get_child('ostree');
-        let ostree_osdir = ostreedir.resolve_relative_path('deploy/' + args.osname);
-        let guestmountPidFile = workdir.get_child('guestmount.pid');
-
-        if (guestmountPidFile.query_exists(null)) {
-            throw new Error("guestmount pid file exists (unclean shutdown?): " + guestmountPidFile.get_path());
-        }
+        this._workdir = Gio.File.new_for_path('.');
+        this._mntdir = this._workdir.get_child('mnt');
+        GSystem.file_ensure_directory(this._mntdir, true, cancellable);
 
+        let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: ['-m', '/dev/sda3',
+                                                                        '-m', '/dev/sda1:/boot'],
+                                                        readWrite: true});
+        gfmnt.mount(this._mntdir, cancellable);
         try {
-            let procContext = new GSystem.SubprocessContext({ argv: ['guestmount', '--rw', '-o', 'allow_root',
-                                                                     '--pid-file', guestmountPidFile.get_path(),
-                                                                     '-a', diskpath.get_path(),
-                                                                     '-m', '/dev/sda3',
-                                                                     '-m', '/dev/sda1:/boot',
-                                                                     mntdir.get_path()] });
-            let guestfishProc = new GSystem.Subprocess({context: procContext});
-            print("starting guestfish");
-            guestfishProc.init(cancellable);
-            guestfishProc.wait_sync_check(cancellable);
-            // guestfish should have daemonized now (unfortunately, if
-            // there was a way to avoid that we would).
-
-            let adminCmd = ['ostree', 'admin', '--ostree-dir=' + ostreedir.get_path(),
-                            '--boot-dir=' + mntdir.get_child('boot').get_path()];
-            let adminEnv = GLib.get_environ();
-            adminEnv.push('LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND=1');
-            let procdir = mntdir.get_child('proc');
-            if (!procdir.query_exists(cancellable)) {
-                ProcUtil.runSync(adminCmd.concat(['init-fs', mntdir.get_path()]), cancellable,
-                                 {logInitiation: true, env: adminEnv});
-            }
-
-            // *** NOTE ***
-            // Here we blow away any current deployment.  This is pretty lame, but it
-            // avoids us triggering a variety of guestfs/FUSE bugs =(
-            // See: https://bugzilla.redhat.com/show_bug.cgi?id=892834
-            //
-            // But regardless, it's probably useful if every
-            // deployment starts clean, and callers can use libguestfs
-            // to crack the FS open afterwards and modify config files
-            // or the like.
-            GSystem.shutil_rm_rf(ostree_osdir, cancellable);
-
-            ProcUtil.runSync(adminCmd.concat(['os-init', args.osname]), cancellable,
-                             {logInitiation: true, env: adminEnv});
-            ProcUtil.runSync(['ostree', '--repo=' + ostreedir.get_child('repo').get_path(),
-                              'pull-local', args.srcrepo, args.target], cancellable,
-                             {logInitiation: true, env: adminEnv});
-
-            ProcUtil.runSync(adminCmd.concat(['deploy', '--no-kernel', args.osname, args.target]), cancellable,
-                             {logInitiation: true, env: adminEnv});
-            ProcUtil.runSync(adminCmd.concat(['update-kernel', '--no-bootloader', args.osname]), cancellable,
-                             {logInitiation: true, env: adminEnv});
-            ProcUtil.runSync(adminCmd.concat(['prune', args.osname]), cancellable,
-                             {logInitiation: true, env: adminEnv});
-
-            let deployKernelPath = this._findCurrentKernel(mntdir, args.osname, cancellable);
-            let bootKernelPath = bootdir.resolve_relative_path('ostree/' + deployKernelPath.get_basename());
-            if (!bootKernelPath.query_exists(cancellable))
-                throw new Error("" + bootKernelPath.get_path() + " doesn't exist");
-            let kernelRelease = this._parseKernelRelease(deployKernelPath);
-            let initramfsPath = this._getInitramfsPath(mntdir, kernelRelease);
-
-            let defaultFstab = 'LABEL=gnostree-root / ext4 defaults 1 1\n\
-LABEL=gnostree-boot /boot ext4 defaults 1 2\n\
-LABEL=gnostree-swap swap swap defaults 0 0\n';
-            let fstabPath = ostreedir.resolve_relative_path('deploy/gnome-ostree/current-etc/fstab'); 
-            fstabPath.replace_contents(defaultFstab, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
-            
-            let grubDir = mntdir.resolve_relative_path('boot/grub');
-            GSystem.file_ensure_directory(grubDir, false, cancellable);
-            let bootRelativeKernelPath = bootdir.get_relative_path(bootKernelPath);
-            if (bootRelativeKernelPath == null)
-                throw new Error("" + bootKernelPath.get_path() + " is not relative to " + bootdir.get_path());
-            let bootRelativeInitramfsPath = bootdir.get_relative_path(initramfsPath);
-            let grubConfPath = grubDir.get_child('grub.conf');
-            let grubConf = Format.vprintf('default=0\n\
-timeout=5\n\
-title %s\n\
-        root (hd0,0)\n\
-        kernel /%s root=LABEL=gnostree-root ostree=%s/current\n\
-        initrd /%s\n', [args.osname, bootRelativeKernelPath, args.osname, bootRelativeInitramfsPath]);
-            grubConfPath.replace_contents(grubConf, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
+            this._doPullDeloy(args, cancellable);
         } finally {
-            if (guestmountPidFile.query_exists(null)) {
-                let pidStr = GSystem.file_load_contents_utf8(guestmountPidFile, cancellable);
-                if (pidStr.length > 0) {
-                    for (let i = 0; i < 30; i++) {
-                        // See "man guestmount" for why retry loops here might be needed if this
-                        // script is running on a client machine with programs that watch for new mounts
-                        try {
-                            ProcUtil.runSync(['fusermount', '-u', mntdir.get_path()], cancellable,
-                                             {logInitiation: true});
-                            break;
-                        } catch (e) {
-                            if (!(e.origError && e.origError.domain == GLib.spawn_exit_error_quark()))
-                                throw e;
-                            else {
-                                let proc = GSystem.Subprocess.new_simple_argv(['fuser', '-m', mntdir.get_path()],
-                                                                              GSystem.SubprocessStreamDisposition.INHERIT,
-                                                                              GSystem.SubprocessStreamDisposition.INHERIT,
-                                                                              cancellable);
-                                proc.init(cancellable);
-                                proc.wait_sync(cancellable);
-                                let creds = new Gio.Credentials();
-                                proc = GSystem.Subprocess.new_simple_argv(['ls', '-al', '/proc/' + creds.get_unix_pid() + '/fd'],
-                                                                          GSystem.SubprocessStreamDisposition.INHERIT,
-                                                                          GSystem.SubprocessStreamDisposition.INHERIT,
-                                                                          cancellable);
-                                proc.init(cancellable);
-                                proc.wait_sync(cancellable);
-                                                                              
-                                GLib.usleep(GLib.USEC_PER_SEC);
-                            }
-                        }
-                    }
-                    let pid = parseInt(pidStr);
-                    for (let i = 0; i < 30; i++) {
-                        let killContext = new GSystem.SubprocessContext({argv: ['kill', '-0', ''+pid]});
-                        killContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.NULL);
-                        let killProc = new GSystem.Subprocess({context: killContext});
-                        killProc.init(null);
-                        let [waitSuccess, ecode] = killProc.wait_sync(null);
-                        let [killSuccess, statusStr] = ProcUtil.getExitStatusAndString(ecode);
-                        if (killSuccess) {
-                            print("Awaiting termination of guestfish, pid=" + pid + " timeout=" + (30 - i) + "s");
-                            GLib.usleep(GLib.USEC_PER_SEC);
-                        } else {
-                            break;
-                            print("guestmount exited");
-                        }
-                    }
-                }
-            }
+            gfmnt.umount(cancellable);
         }
 
-        let gf = new GuestFish.GuestFish(diskpath, true);
-        gf.run('grub-install / /dev/vda\n', cancellable,
-               {partitionOpts: ['-m', '/dev/sda3', '-m', '/dev/sda1:/boot'],
-                readWrite: true});
+        let gf = new GuestFish.GuestFish(diskpath, {partitionOpts: ['-m', '/dev/sda3', '-m', '/dev/sda1:/boot'],
+                                                    readWrite: true});
+        gf.run('grub-install / /dev/vda\n', cancellable);
         print("Complete!");
     }
 });
diff --git a/src/ostbuild/js/builtins/qa_smoketest.js b/src/ostbuild/js/builtins/qa_smoketest.js
new file mode 100644
index 0000000..f47d4b1
--- /dev/null
+++ b/src/ostbuild/js/builtins/qa_smoketest.js
@@ -0,0 +1,82 @@
+// -*- indent-tabs-mode: nil; tab-width: 2; -*-
+// Copyright (C) 2013 Colin Walters <walters verbum org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Format = imports.format;
+
+const GSystem = imports.gi.GSystem;
+
+const ArgParse = imports.argparse;
+const ProcUtil = imports.procutil;
+
+const loop = GLib.MainLoop.new(null, true);
+
+const QaSmokeTest = new Lang.Class({
+    Name: 'QaSmokeTest',
+
+    execute: function(argv) {
+        let cancellable = null;
+        let parser = new ArgParse.ArgumentParser("Basic smoke testing via parsing serial console");
+        parser.addArgument('diskpath');
+        
+        let args = parser.parse(argv);
+
+        let diskpath = Gio.File.new_for_path(args.diskpath);
+
+        let workdir = Gio.File.new_for_path('.');
+
+        let fallbackPaths = ['/usr/libexec/qemu-kvm']
+        let qemuPathString = GLib.find_program_in_path('qemu-kvm');
+        if (qemuPathString == null) {
+            for (let i = 0; i < fallbackPaths.length; i++) {
+                let path = Gio.File.new_for_path(fallbackPaths[i]);
+                if (!path.query_exists(null))
+                    continue;
+                qemuPathString = path.get_path();
+            }
+        }
+        if (qemuPathString == null) {
+            throw new Error("Unable to find qemu-kvm");
+        }
+
+        let qemuArgs = [qemuPathString, '-vga', 'std', 'm', '768M',
+                        '-usb', '-usbdevice', 'tablet',
+                        '-drive', 'file=' + diskpath + ',if=virtio',
+                       ];
+
+        let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
+        let qemu = new GSystem.Subprocess({context: qemuContext});
+        print("starting qemu");
+        qemu.init(cancellable);
+
+        qemu.wait_sync_check(cancellable);
+        
+        print("Complete!");
+    }
+});
+
+function main(argv) {
+    let ecode = 1;
+    var app = new QaSmokeTest();
+    GLib.idle_add(GLib.PRIORITY_DEFAULT,
+                  function() { try { app.execute(argv); ecode = 0; } finally { loop.quit(); }; return false; });
+    loop.run();
+    return ecode;
+}
diff --git a/src/ostbuild/js/guestfish.js b/src/ostbuild/js/guestfish.js
index bd3a29e..9f057c8 100644
--- a/src/ostbuild/js/guestfish.js
+++ b/src/ostbuild/js/guestfish.js
@@ -23,40 +23,152 @@ const GSystem = imports.gi.GSystem;
 const Params = imports.params;
 const ProcUtil = imports.procutil;
 
-const GuestFish = new Lang.Class({
-    Name: 'GuestFish',
+const LibGuestfs = new Lang.Class({
+    Name: 'LibGuestfs',
 
-    _init: function(diskpath, useLockFile) {
+    _init: function(diskpath, params) {
+	this._params = Params.parse(params, {useLockFile: true,
+					     partitionOpts: ['-i'],
+					     readWrite: false});
 	this._diskpath = diskpath;
-	if (useLockFile) {
+	this._readWrite = params.readWrite
+	this._partitionOpts = params.partitionOpts;
+	if (params.useLockFile) {
 	    let lockfilePath = diskpath.get_parent().get_child(diskpath.get_basename() + '.guestfish-lock');
 	    this._lockfilePath = lockfilePath;
 	} else {
 	    this._lockfilePath = null;
 	}
     },
-    
-    run: function(input, cancellable, params) {
-	params = Params.parse(params, {partitionOpts: ['-i'],
-				       readWrite: false});
-	
-	try {
-	    let guestfishArgv = ['guestfish', '-a', this._diskpath.get_path()];
-	    if (params.readWrite)
-		guestfishArgv.push('--rw');
-	    else
-		guestfishArgv.push('--ro');
-	    guestfishArgv.push.apply(guestfishArgv, params.partitionOpts);
 
+    _lock: function() {
+	if (this._lockfilePath) {
 	    let stream = this._lockfilePath.create(Gio.FileCreateFlags.NONE, cancellable);
 	    stream.close(cancellable);
-	    
+	}
+    },
+
+    _unlock: function() {
+	if (this._lockfilePath != null) {
+	    GSystem.file_unlink(this._lockfilePath, cancellable);
+	}
+    },
+
+    _appendOpts: function(argv) {
+	argv.push.apply(argv, ['-a', this._diskpath.get_path()]);
+	if (this._readWrite)
+	    argv.push('--rw');
+	else
+	    argv.push('--ro');
+	argv.push.apply(argv, this._partitionOpts);
+    }
+});
+
+const GuestFish = new Lang.Class({
+    Name: 'GuestFish',
+    Extends: LibGuestfs,
+
+    run: function(input, cancellable) {
+	this._lock();
+	try {
+	    let guestfishArgv = ['guestfish'];
+	    this._appendOpts(guestfishArgv);
 	    return ProcUtil.runProcWithInputSyncGetLines(guestfishArgv, cancellable, input);
 	} finally {
-	    if (this._lockfilePath != null) {
-		GSystem.file_unlink(this._lockfilePath, cancellable);
-	    }
+	    this._unlock();
 	}
     }
 });
 
+const GuestMount = new Lang.Class({
+    Name: 'GuestMount',
+    Extends: LibGuestfs,
+
+    mount: function(mntdir, cancellable) {
+	this._lock();
+	try {
+	    this._mntdir = mntdir;
+	    this._mountPidFile = mntdir.get_parent().get_child(mntdir.get_basename() + '.guestmount-pid');
+	    let guestmountArgv = ['guestmount', '-o', 'allow_root',
+				  '--pid-file', this._mountPidFile.get_path()];
+	    this._appendOpts(guestmountArgv);
+	    guestmountArgv.push(mntdir.get_path());
+            let context = new GSystem.SubprocessContext({ argv: guestmountArgv });
+            let proc = new GSystem.Subprocess({ context: context });
+	    proc.init(cancellable);
+
+            // guestfish daemonizes, so this process will exit, and
+	    // when it has, we know the mount is ready.  If there was
+	    // a way to get notified instead of this indirect fashion,
+	    // we'd do that.
+	    proc.wait_sync_check(cancellable);
+
+	    this._mounted = true;
+	} catch (e) {
+	    this._unlock();
+	}
+    },
+
+    umount: function(cancellable) {
+	if (!this._mounted)
+	    return;
+
+        let pidStr = GSystem.file_load_contents_utf8(this._mountPidFile, cancellable);
+        if (pidStr.length == 0) {
+	    this._mounted = false;
+	    return;
+	}
+
+        for (let i = 0; i < 30; i++) {
+            // See "man guestmount" for why retry loops here might be needed if this
+            // script is running on a client machine with programs that watch for new mounts
+            try {
+                ProcUtil.runSync(['fusermount', '-u', this._mntdir.get_path()], cancellable,
+                                 {logInitiation: true});
+                break;
+            } catch (e) {
+                if (!(e.origError && e.origError.domain == GLib.spawn_exit_error_quark()))
+                    throw e;
+                else {
+                    let proc = GSystem.Subprocess.new_simple_argv(['fuser', '-m', mntdir.get_path()],
+                                                                  GSystem.SubprocessStreamDisposition.INHERIT,
+                                                                  GSystem.SubprocessStreamDisposition.INHERIT,
+                                                                  cancellable);
+                    proc.init(cancellable);
+                    proc.wait_sync(cancellable);
+                    let creds = new Gio.Credentials();
+                    proc = GSystem.Subprocess.new_simple_argv(['ls', '-al', '/proc/' + creds.get_unix_pid() + '/fd'],
+                                                              GSystem.SubprocessStreamDisposition.INHERIT,
+                                                              GSystem.SubprocessStreamDisposition.INHERIT,
+                                                              cancellable);
+                    proc.init(cancellable);
+                    proc.wait_sync(cancellable);
+                    
+                    GLib.usleep(GLib.USEC_PER_SEC);
+                }
+            }
+        }
+
+        let pid = parseInt(pidStr);
+	let guestfishExited = false;
+        for (let i = 0; i < 30; i++) {
+            let killContext = new GSystem.SubprocessContext({argv: ['kill', '-0', ''+pid]});
+            killContext.set_stderr_disposition(GSystem.SubprocessStreamDisposition.NULL);
+            let killProc = new GSystem.Subprocess({context: killContext});
+            killProc.init(null);
+            let [waitSuccess, ecode] = killProc.wait_sync(null);
+            let [killSuccess, statusStr] = ProcUtil.getExitStatusAndString(ecode);
+            if (killSuccess) {
+                print("Awaiting termination of guestfish, pid=" + pid + " timeout=" + (30 - i) + "s");
+                GLib.usleep(GLib.USEC_PER_SEC);
+            } else {
+		guestfishExited = true;
+                break;
+            }
+        }
+	if (!guestfishExited)
+	    throw new Error("guestfish failed to exit");
+	this._mounted = false;
+	this._unlock();
+    },
+});
diff --git a/src/ostbuild/js/libqa.js b/src/ostbuild/js/libqa.js
new file mode 100644
index 0000000..fd523c9
--- /dev/null
+++ b/src/ostbuild/js/libqa.js
@@ -0,0 +1,38 @@
+// Copyright (C) 2012,2013 Colin Walters <walters verbum org>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+
+const GSystem = imports.gi.GSystem;
+const Params = imports.params;
+const ProcUtil = imports.procutil;
+const GuestFish = imports.guestfish;
+
+const ModifyBootloaderAppendKernelArg = new Lang.Class({
+    Name: 'ModifyGrub',
+    Extends: GuestFish.GuestFish,
+
+    _init: function(diskpath, params) {
+	this.parent(diskpath, params);
+    },
+    
+    run: function(argsToAppend, cancellable) {
+    }
+});
+



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