--
James Hunt
____________________________________
http://upstart.ubuntu.com/cookbook
http://upstart.ubuntu.com/cookbook/upstart_cookbook.pdf
=== modified file 'ChangeLog'
--- ChangeLog	2012-03-16 09:03:02 +0000
+++ ChangeLog	2012-03-16 21:02:13 +0000
@@ -12,6 +12,73 @@
 	  - test_spawn():
 	    - umask reset.
 	    - New test "ensure multi processes output logged".
+	* dbus/com.ubuntu.Upstart.xml:
+	  - added 'NotifyDiskWriteable' method.
+	* init/control.c:
+	  - control_notify_disk_writeable(): New function to flush early job log.
+	* init/job_process.c:
+	  - job_process_terminated(): Call log_handle_unflushed() to potentially
+	    add log object to unflushed list (the early job log) in certain
+	    scenarios.
+	* init/log.c:
+	  - log_flushed: bool indicating successful flush of early job log.
+	  - log_unflushed_files: The "early job log" list.
+	  - log_new(): Call log_unflushed_init() and initialize new log members.
+	  - log_flush(): Only call log_read_watch() conditionally now.
+	  - log_io_reader(): More careful consideration of errno by
+	    using saved value from log member.
+	  - log_io_error_handler(): Set remote_closed for the benefit of
+	    log_flushed() (to avoid flushing multiple times).
+	  - log_file_open: Now saves errno value from open(2).
+	  - log_read_watch(): Removed log->unflushed->len assert since it was
+	    erroneous: even if unflushed data exists, it will be written in
+	    order when log_io_reader() calls log_file_write().
+	  - log_unflushed_init(): New function to initialise the
+	    log_unflushed_files list.
+	  - log_handle_unflushed(): New function that potentially adds log
+	    object to the log_unflushed_files list to allow the data to be
+	    flushed _after_ the parent object has been destroyed.
+	  - log_clear_unflushed(): New function to clear the
+	    log_unflushed_files list by attempting to flush the data to disk.
+	* init/log.h:
+	  - Added new Log members: detached, remote_closed and open_errno.
+	  - Updated documentation.
+	  - extern for log_unflushed_files.
+	  - Added prototypes for new functions: log_handle_unflushed(),
+	    log_clear_unflushed() and log_unflushed_init().
+	* init/tests/test_job_process.c:
+	  - test_run():
+	    - Call log_unflushed_init().
+	    - Corrected grammar in error messages for "ensure sane fds" tests.
+	    - "with single line command writing fast and exiting": Call
+	      nih_child_add_watch().
+	    - added waitid() calls to ensure log data not added to
+	      unflushed list.
+	  - test_spawn():
+	    - Call log_unflushed_init().
+	    - Corrected grammar in error messages for "ensure sane fds" tests.
+	    - Added TEST_ALLOC_SAFE() to "simple test" to ensure
+	      destructors run correctly.
+	    - "read single null byte with 'console log'": Call
+	      log_handle_unflushed() and added missing free.
+	    - "read data from forked process": Call
+	      log_handle_unflushed().
+	* init/tests/test_log.c: 
+	  - Updated documentation.
+	  - Added calls to log_unflushed_init().
+	  - "ensure logger flushes cached data on request": New test
+	    for log_handle_unflushed().
+	* util/initctl.c:
+	  - notify_disk_writeable_action(): New function to notify
+	    Upstart that the disk is writeable.
+	  - commands: Added new command "notify-disk-writeable".
+	* util/man/initctl.8: Updated for new notify-disk-writeable command.
+	* util/tests/test_initctl.c:
+	  - STOP_UPSTART(): Check return from kill(2).
+	  - test_show_config(): Adding missing rmdir(2).
+	  - test_check_config(): Adding missing rmdir(2).
+	  - test_notify_disk_writeable(): New function embodying new test
+	    "with job ending before log disk writeable".
 
 	[ Steve Langasek <[email protected]> ]
 	* init/tests/test_job_process:

=== modified file 'dbus/com.ubuntu.Upstart.xml'
--- dbus/com.ubuntu.Upstart.xml	2010-12-10 07:18:34 +0000
+++ dbus/com.ubuntu.Upstart.xml	2012-03-16 21:02:13 +0000
@@ -57,6 +57,9 @@
       <arg name="file" type="h" direction="in" />
     </method>
 
+    <method name="NotifyDiskWriteable">
+    </method>
+
     <!-- Basic information about Upstart -->
     <property name="version" type="s" access="read" />
     <property name="log_priority" type="s" access="readwrite" />

=== modified file 'init/control.c'
--- init/control.c	2011-06-15 13:20:41 +0000
+++ init/control.c	2012-03-16 21:02:13 +0000
@@ -761,3 +761,48 @@
 	if (use_session_bus)
 		nih_debug ("Using session bus");
 }
+/**
+ * control_notify_disk_writeable:
+ * @data: not used,
+ * @message: D-Bus connection and message received,
+ *
+ * Implements the NotifyDiskWriteable method of the
+ * com.ubuntu.Upstart interface.
+ *
+ * Called to flush the job logs for all jobs that ended before the log
+ * disk became writeable.
+ *
+ * Returns: zero on success, negative value on raised error.
+ **/
+int
+control_notify_disk_writeable (void   *data,
+		     NihDBusMessage *message)
+{
+	int       ret;
+	Session  *session;
+
+	nih_assert (message != NULL);
+
+	/* Get the relevant session */
+	session = session_from_dbus (NULL, message);
+
+	if (session && session->user) {
+		nih_dbus_error_raise_printf (
+			DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
+			_("You do not have permission to notify disk is writeable"));
+		return -1;
+	}
+
+	/* "nop" when run from a chroot */
+	if (session && session->chroot)
+		return 0;
+
+	ret = log_clear_unflushed ();
+
+	if (ret < 0) {
+		nih_error_raise_system ();
+		return -1;
+	}
+
+	return 0;
+}

=== modified file 'init/job_process.c'
--- init/job_process.c	2012-03-16 09:03:02 +0000
+++ init/job_process.c	2012-03-16 21:02:13 +0000
@@ -1604,12 +1604,28 @@
 	}
 
 	if (job->class->console == CONSOLE_LOG && job->log[process]) {
+		int  ret;
+
 		/* It is imperative that we free the log at this stage to ensure
 		 * that jobs which respawn have their log written _now_
 		 * (and not just when the overall Job object is freed at
 		 * some distant future point).
 		 */
-		nih_free (job->log[process]);
+		ret = log_handle_unflushed (job->log, job->log[process]);
+
+		if (ret != 0) {
+			if (ret < 0) {
+				/* Any lingering data will now be lost in what
+				 * is probably a low-memory scenario.
+				 */
+				nih_warn (_("Failed to add log to unflushed queue"));
+			}
+			nih_free (job->log[process]);
+		}
+
+		/* Either the log has been freed, or it needs to be
+		 * severed from its parent job fully.
+		 */
 		job->log[process] = NULL;
 	}
 

=== modified file 'init/log.c'
--- init/log.c	2012-02-13 12:08:33 +0000
+++ init/log.c	2012-03-16 21:02:13 +0000
@@ -39,6 +39,21 @@
 static void log_flush       (Log *log);
 
 /**
+ * log_flushed:
+ *
+ * TRUE if log_clear_unflushed() has been called successfully.
+ **/
+static int log_flushed = 0;
+
+/**
+ * log_unflushed_files:
+ *
+ * List of known sources of configuration; each item is an
+ * NihListEntry.
+ **/
+NihList *log_unflushed_files = NULL;
+
+/**
  * log_new:
  *
  * @parent: parent for new job class,
@@ -101,10 +116,15 @@
 	if (! log)
 		return NULL;
 
-	log->fd          = -1;
-	log->uid         = uid;
-	log->unflushed   = NULL;
-	log->io          = NULL;
+	log_unflushed_init ();
+
+	log->fd            = -1;
+	log->uid           = uid;
+	log->unflushed     = NULL;
+	log->io            = NULL;
+	log->detached      = 0;
+	log->remote_closed = 0;
+	log->open_errno    = 0;
 
 	log->path = nih_strndup (log, path, len);
 	if (! log->path)
@@ -223,7 +243,8 @@
 		 * 
 		 * Therefore, attempt to read from the watch fd until we get an error.
 		 */
-		log_read_watch (log);
+		if (! log->remote_closed)
+			log_read_watch (log);
 
 		flags = fcntl (log->io->watch->fd, F_GETFL);
 
@@ -306,8 +327,10 @@
 	 */
 	nih_assert (sizeof (size_t) == sizeof (ssize_t));
 
-	if (log_file_open (log) < 0) {
-		if (errno != ENOSPC) {
+	ret = log_file_open (log);
+
+	if (ret < 0) {
+		if (log->open_errno != ENOSPC) {
 			/* Add new data to unflushed buffer */
 			if (nih_io_buffer_push (log->unflushed, buf, len) < 0)
 				return;
@@ -360,6 +383,8 @@
 	/* Ensure the NihIo is closed */
 	nih_free (log->io);
 	log->io = NULL;
+
+	log->remote_closed = 1;
 }
 
 /**
@@ -390,7 +415,7 @@
 	ret = fstat (log->fd, &statbuf);
 
 	/* Already open */
-	if (log->fd > -1 && (!ret && statbuf.st_nlink))
+	if (log->fd > -1 && (! ret && statbuf.st_nlink))
 		return 0;
 
 	/* File was deleted. This isn't a problem for
@@ -418,6 +443,8 @@
 	 */
 	log->fd = open (log->path, flags, mode);
 
+	log->open_errno = errno;
+
 	/* Open may have failed due to path being unaccessible
 	 * (disk might not be mounted yet).
 	 */
@@ -597,8 +624,6 @@
 	/* Must not be called if there is unflushed data as the log
 	 * would then not be written in order.
 	 */
-	nih_assert (! log->unflushed->len);
-
 	io = log->io;
 
 	if (! io)
@@ -661,3 +686,116 @@
 		}
 	}
 }
+
+/**
+ * log_unflushed_init:
+ *
+ * Initialise the log_unflushed_files list.
+ **/
+void
+log_unflushed_init (void)
+{
+	if (! log_unflushed_files)
+		log_unflushed_files = NIH_MUST (nih_list_new (NULL));
+}
+
+/**
+ * log_handle_unflushed:
+ * @parent: parent of log,
+ * @log: log.
+ *
+ * Potentially add specified log to list of unflushed log files
+ * (for processing when a disk becomes writeable).
+ *
+ * This function should be called for each log object at the time the
+ * associated process exits to ensure that all data from that process is
+ * captured to the log.
+ *
+ * Returns: 0 on success (log added to list), 1 if log does not need to
+ * be added to the list, or -1 on error.
+ **/
+int
+log_handle_unflushed (void *parent, Log *log)
+{
+	NihListEntry  *elem;
+
+	nih_assert (log);
+	nih_assert (log->detached == 0);
+
+	log_read_watch (log);
+
+	if (! log->unflushed->len)
+		return 1;
+
+	if ((log->open_errno != EROFS && log->open_errno != EPERM
+			&& log->open_errno != EACCES) || log_flushed)
+		return 1;
+
+	log_unflushed_init ();
+
+	/* re-parent */
+	nih_ref (log, log_unflushed_files);
+	nih_unref (log, parent);
+
+	elem = nih_list_entry_new (log);
+	if (! elem) {
+		/* If memory is low, we discard the unflushed
+		 * data buffer too.
+		 */
+		nih_unref (log, log_unflushed_files);
+		return -1;
+	}
+
+	/* Indicate separation from parent */
+	log->detached = 1;
+
+	elem->data = log;    
+	nih_list_add_after (log_unflushed_files, &elem->entry);
+
+	return 0;
+}
+
+/* log_clear_unflushed:
+ *
+ * Attempt to flush all unflushed log buffers to persistent storage.
+ *
+ * Call once the log disk partition is mounted as read-write.
+ *
+ * Returns: 0 on success, -1 on error.
+ */
+int
+log_clear_unflushed (void)
+{
+	log_unflushed_init ();
+
+	NIH_LIST_FOREACH_SAFE (log_unflushed_files, iter) {
+		NihListEntry  *elem;
+		Log           *log;
+
+		elem = (NihListEntry *)iter;
+		log = elem->data;
+
+		/* We expect 'an' error (as otherwise why would the log be
+		 * in this list?), but don't assert EROFS specifically
+		 * as a precaution (since an attempt to flush the log at
+		 * another time may result in some other errno value).
+		 */
+		nih_assert (log->open_errno);
+
+		nih_assert (log->unflushed->len);
+		nih_assert (log->remote_closed);
+		nih_assert (log->detached);
+
+		if (log_file_open (log) != 0)
+			return -1;
+
+		if (log_file_write (log, NULL, 0) < 0)
+			return -1;
+
+		nih_free (log);
+	}
+
+	log_flushed = 1;
+
+	return 0;
+}

=== modified file 'init/log.h'
--- init/log.h	2012-01-26 08:59:08 +0000
+++ init/log.h	2012-02-14 16:47:34 +0000
@@ -52,9 +52,12 @@
  *
  * @fd: Write file descriptor associated with @path,
  * @path: Full path to log file,
- * @io: NihIo associated with jobs stdout and stderr.
+ * @io: NihIo associated with jobs stdout and stderr,
  * @uid: User ID of caller,
- * @unflushed: Unflushed data.
+ * @unflushed: Unflushed data,
+ * @detached: TRUE if log is no longer associated with a parent,(job),
+ * @remote_closed: TRUE if remote end of pty has been closed,
+ * @open_errno: value of errno immediately after last attempt to open @path.
  **/
 typedef struct log {
 	int          fd;
@@ -62,16 +65,25 @@
 	NihIo       *io;
 	uid_t        uid;
 	NihIoBuffer *unflushed;
+	int          detached;
+	int          remote_closed;
+	int          open_errno;
 } Log;
 
 NIH_BEGIN_EXTERN
 
+extern NihList *log_unflushed_files;
+
 Log  *log_new                (const void *parent, const char *path,
 			      int fd, uid_t uid)
 	__attribute__ ((warn_unused_result, malloc));
 void  log_io_reader          (Log *log, NihIo *io, const char *buf, size_t len);
 void  log_io_error_handler   (Log *log, NihIo *io);
 int   log_destroy            (Log *log);
+int   log_handle_unflushed   (void *parent, Log *log);
+int   log_clear_unflushed    (void);
+void  log_unflushed_init     (void);
+
 
 NIH_END_EXTERN
 

=== modified file 'init/tests/test_job_process.c'
--- init/tests/test_job_process.c	2012-03-16 09:03:02 +0000
+++ init/tests/test_job_process.c	2012-03-16 21:02:13 +0000
@@ -444,6 +444,9 @@
 	char             buffer[1024];
 	pid_t            pid;
 	int              i;
+	siginfo_t        siginfo;
+
+	log_unflushed_init ();
 
 	TEST_FUNCTION ("job_process_run");
 
@@ -1186,7 +1189,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -1253,7 +1256,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -1320,7 +1323,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -1387,7 +1390,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -3564,6 +3567,15 @@
 			TEST_CMD_ECHO);
 	class->process[PROCESS_MAIN]->script = FALSE;
 
+	/* XXX: Manually add the class so job_process_find() works */
+	nih_hash_add (job_classes, &class->entry);
+
+	NIH_MUST (nih_child_add_watch (NULL,
+				-1,
+				NIH_CHILD_ALL,
+				job_process_handler,
+				NULL)); 
+
 	job = job_new (class, "");
 	job->goal = JOB_START;
 	job->state = JOB_SPAWNED;
@@ -3695,6 +3707,14 @@
 	}
 
 	TEST_EQ (kill (pid, 0), 0);
+
+	/* Wait until the process is in a known state. This ensures that
+	 * when job_process_terminated() calls log_handle_unflushed(), 
+	 * the log object will _not_ get added to the unflushed list,
+	 * meaning it will get destroyed immediately.
+	 */
+	waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT);
+
 	nih_child_poll ();
 
 	/* The process should now be dead */
@@ -3785,7 +3805,12 @@
 	TEST_FREE_TAG (job);
 	TEST_FREE_TAG (job->log);
 
-	TEST_FORCE_WATCH_UPDATE ();
+	/* Wait until the process is in a known state. This ensures that
+	 * when job_process_terminated() calls log_handle_unflushed(), 
+	 * the log object will _not_ get added to the unflushed list,
+	 * meaning it will get destroyed immediately.
+	 */
+	waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT);
 
 	nih_child_poll ();
 
@@ -3993,6 +4018,9 @@
 	JobProcessError  *perr;
 	int               status;
 	struct stat       statbuf;
+	int               ret;
+
+	log_unflushed_init ();
 
 	/* reset */
 	(void) umask (0);
@@ -4457,7 +4485,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -4511,7 +4539,7 @@
 			/* 0, 1, 2 */
 			if (fd < 3) {
 				if (! valid)
-					TEST_FAILED ("fd %d is unexpected invalid", fd);
+					TEST_FAILED ("fd %d is unexpectedly invalid", fd);
 			} else {
 				if (valid)
 					TEST_FAILED ("fd %d is unexpectedly valid", fd);
@@ -4640,10 +4668,10 @@
 	TEST_EQ (setenv ("UPSTART_LOGDIR", dirname, 1), 0);
 	TEST_ALLOC_FAIL {
 		TEST_ALLOC_SAFE {
-			class = job_class_new (NULL, "test", NULL);
+			class = job_class_new (NULL, "simple-test", NULL);
 			TEST_NE_P (class, NULL);
 
-			TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+			TEST_GT (sprintf (filename, "%s/simple-test.log", dirname), 0);
 			job = job_new (class, "");
 			TEST_NE_P (job, NULL);
 
@@ -4664,16 +4692,21 @@
 		pid = job_process_spawn (job, args_array, NULL, FALSE, -1, PROCESS_MAIN);
 
 		if (test_alloc_failed) {
+			TEST_LT (pid, 0);
 			err = nih_error_get ();
 			TEST_NE_P (err, NULL);
 			TEST_EQ (err->number, ENOMEM);
 			nih_free (err);
-			TEST_LT (pid, 0);
 		} else {
 			TEST_GT (pid, 0);
 			TEST_EQ (unlink (script), 0);
 			unlink (filename);
 		}
+
+		TEST_ALLOC_SAFE {
+			/* May alloc space if there is log data */
+			nih_free (class);
+		}
 	}
 
 	/************************************************************/
@@ -4691,10 +4724,10 @@
 	 */
 	TEST_EQ (setenv ("UPSTART_LOGDIR", dirname, 1), 0);
 
-	class = job_class_new (NULL, "test", NULL);
+	class = job_class_new (NULL, "with-single-line-script-and-console-log", NULL);
 	TEST_NE_P (class, NULL);
 
-	TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+	TEST_GT (sprintf (filename, "%s/with-single-line-script-and-console-log.log", dirname), 0);
 	job = job_new (class, "");
 	TEST_NE_P (job, NULL);
 
@@ -4745,10 +4778,10 @@
 
 	TEST_EQ (setenv ("UPSTART_LOGDIR", dirname, 1), 0);
 
-	class = job_class_new (NULL, "test", NULL);
+	class = job_class_new (NULL, "with-multi-line-script-and-console-log", NULL);
 	TEST_NE_P (class, NULL);
 
-	TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+	TEST_GT (sprintf (filename, "%s/with-multi-line-script-and-console-log.log", dirname), 0);
 	job = job_new (class, "");
 	TEST_NE_P (job, NULL);
 
@@ -4801,10 +4834,10 @@
 
 	TEST_EQ (setenv ("UPSTART_LOGDIR", dirname, 1), 0);
 
-	class = job_class_new (NULL, "test", NULL);
+	class = job_class_new (NULL, "read-single-null-bytes-with-console-log", NULL);
 	TEST_NE_P (class, NULL);
 
-	TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+	TEST_GT (sprintf (filename, "%s/read-single-null-bytes-with-console-log.log", dirname), 0);
 	job = job_new (class, "");
 	TEST_NE_P (job, NULL);
 
@@ -4821,7 +4854,8 @@
 	TEST_EQ (waitpid (pid, &status, 0), pid);
 	TEST_TRUE (WIFEXITED (status));
 
-	TEST_FORCE_WATCH_UPDATE ();
+	ret = log_handle_unflushed (job->log, job->log[PROCESS_MAIN]);
+	TEST_EQ (ret, 1);
 
 	output = fopen (filename, "r");
 	TEST_NE_P (output, NULL);
@@ -4838,6 +4872,7 @@
 	TEST_EQ (unsetenv ("UPSTART_LOGDIR"), 0);
 
 	nih_free (job);
+	nih_free (class);
 
 	/************************************************************/
 	TEST_FEATURE ("read data from forked process");
@@ -4848,10 +4883,10 @@
 
 	TEST_EQ (setenv ("UPSTART_LOGDIR", dirname, 1), 0);
 
-	class = job_class_new (NULL, "test", NULL);
+	class = job_class_new (NULL, "read-data-from-forked-process", NULL);
 	TEST_NE_P (class, NULL);
 
-	TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+	TEST_GT (sprintf (filename, "%s/read-data-from-forked-process.log", dirname), 0);
 	job = job_new (class, "");
 	TEST_NE_P (job, NULL);
 
@@ -4872,12 +4907,18 @@
 
 	TEST_EQ (waitpid (pid, &status, 0), pid);
 	TEST_TRUE (WIFEXITED (status));
+	TEST_EQ (WEXITSTATUS (status), 0);
+
+	ret = log_handle_unflushed (job->log, job->log[PROCESS_MAIN]);
+	TEST_EQ (ret, 1);
 
 	TEST_FORCE_WATCH_UPDATE ();
 
 	/* This will eventually call the log destructor */
 	nih_free (class);
 
+	TEST_EQ (stat (filename, &statbuf), 0);
+
 	output = fopen (filename, "r");
 	TEST_NE_P (output, NULL);
 
@@ -8221,7 +8262,7 @@
 		TEST_TRUE (WIFEXITED (status));
 		TEST_EQ (WEXITSTATUS (status), 0);
 
-		/* Now carray on with the test */
+		/* Now carry on with the test */
 		job->goal = JOB_START;
 		job->state = JOB_SPAWNED;
 		job->pid[PROCESS_MAIN] = pid;

=== modified file 'init/tests/test_log.c'
--- init/tests/test_log.c	2012-02-03 13:17:24 +0000
+++ init/tests/test_log.c	2012-03-16 21:02:13 +0000
@@ -81,6 +81,9 @@
  *       nih_new
  *         __nih_alloc # XXX: call 7
  *
+ * (There is actually an 8th call to log_unflushed_init(), but we handle
+ * that by calling log_unflushed_init() prior to any tests).
+ *
  * XXX: Unfortunately, having created a log, we cannot intelligently test the
  * memory failure handling of the asynchronously called log_io_reader() due to the
  * underlying complexities of the way NIH re-allocs memory at particular
@@ -116,20 +119,23 @@
 
 	/* XXX:
 	 *
-	 * It is *essential* we call this prior to any TEST_ALLOC_FAIL
-	 * blocks since TEST_ALLOC_FAIL tracks calls to memory
-	 * allocation routines and expects the function under test to
-	 * call said routines *the same number of times* on each loop.
-	 * NIH will attempt to initialise internal data structures
-	 * lazily so force it to not be lazy to avoid surprises wrt
-	 * number of malloc calls.
+	 * It is *essential* we call these functions prior to any
+	 * TEST_ALLOC_FAIL blocks since TEST_ALLOC_FAIL tracks calls to
+	 * memory allocation routines and expects the function under
+	 * test to call said routines *the same number of times* on each
+	 * loop.  NIH will attempt to initialise internal data
+	 * structures lazily so force it to not be lazy to avoid
+	 * surprises wrt number of malloc calls.
 	 */
 	nih_io_init ();
 	nih_error_init ();
+	log_unflushed_init ();
 
 	/************************************************************/
 	TEST_FEATURE ("object checks with uid 0");
 
+	TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+
 	TEST_ALLOC_FAIL {
 		TEST_EQ (openpty (&pty_master, &pty_slave, NULL, NULL, NULL), 0);
 		log = log_new (NULL, path, pty_master, 0);
@@ -157,10 +163,12 @@
 		TEST_EQ (log->io->watch->fd, pty_master);
 		TEST_EQ (log->uid, 0);
 		TEST_LT (log->fd, 0);
+		TEST_NE (log_unflushed_files, NULL);
+		TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
 
 		close (pty_slave);
 
-		/* frees fds[0] */
+		/* frees pty_master */
 		nih_free (log);
 		log = NULL;
 	}
@@ -254,12 +262,21 @@
 		if (test_alloc_failed == 1+LOG_NEW_ALLOC_CALLS) {
 			TEST_NE_P (log, NULL);         
 			close (pty_slave);
+			TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+			ret = log_handle_unflushed (NULL, log);
+			TEST_EQ (ret, 1);
+			TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
 			nih_free (log);
 			TEST_EQ (unlink (filename), 0);
 			continue;
 		}
 
 		close (pty_slave);
+		TEST_FORCE_WATCH_UPDATE ();
+		ret = log_handle_unflushed (NULL, log);
+		TEST_EQ (ret, 1);
+		TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+
 		nih_free (log);
 
 		TEST_EQ (stat (filename, &statbuf), 0);
@@ -573,6 +590,85 @@
 	TEST_EQ (unlink (filename), 0);
 
 	/************************************************************/
+	TEST_FEATURE ("ensure logger flushes cached data on request");
+
+	TEST_EQ (openpty (&pty_master, &pty_slave, NULL, NULL, NULL), 0);
+
+	TEST_GT (sprintf (filename, "%s/test.log", dirname), 0);
+
+	TEST_NE (log_unflushed_files, NULL);
+	TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+
+	TEST_EQ (stat (dirname, &statbuf), 0);
+
+	/* Save */
+	old_perms = statbuf.st_mode;
+
+	/* Make file inaccessible */
+	TEST_EQ (chmod (dirname, 0x0), 0);
+
+	log = log_new (NULL, filename, pty_master, 0);
+	TEST_NE_P (log, NULL);
+
+	ret = write (pty_slave, str, strlen (str));
+	TEST_GT (ret, 0);
+	ret = write (pty_slave, "\n", 1);
+	TEST_EQ (ret, 1);
+
+	close (pty_slave);
+
+	TEST_FORCE_WATCH_UPDATE ();
+
+	/* Ensure no log file written */
+	TEST_LT (stat (filename, &statbuf), 0);
+
+	TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+
+	TEST_FREE_TAG (log);
+
+	TEST_EQ (log_handle_unflushed (NULL, log), 0);
+
+	TEST_FALSE (NIH_LIST_EMPTY (log_unflushed_files));
+
+	/* Again, ensure no log file written */
+	TEST_LT (stat (filename, &statbuf), 0);
+
+	TEST_EQ (log_clear_unflushed (), -1);
+
+	/* Restore access */
+	TEST_EQ (chmod (dirname, old_perms), 0);
+
+	/* Force flush */
+	TEST_EQ (log_clear_unflushed (), 0);
+
+	TEST_TRUE (NIH_LIST_EMPTY (log_unflushed_files));
+
+	TEST_EQ (stat (filename, &statbuf), 0);
+
+	TEST_TRUE  (S_ISREG (statbuf.st_mode));
+	TEST_TRUE  (statbuf.st_mode & S_IRUSR);
+	TEST_TRUE  (statbuf.st_mode & S_IWUSR);
+	TEST_FALSE (statbuf.st_mode & S_IXUSR);
+
+	TEST_TRUE  (statbuf.st_mode & S_IRGRP);
+	TEST_FALSE (statbuf.st_mode & S_IWGRP);
+	TEST_FALSE (statbuf.st_mode & S_IXGRP);
+
+	TEST_FALSE (statbuf.st_mode & S_IROTH);
+	TEST_FALSE (statbuf.st_mode & S_IWOTH);
+	TEST_FALSE (statbuf.st_mode & S_IXOTH);
+
+	output = fopen (filename, "r");
+	TEST_NE_P (output, NULL);
+
+	TEST_FILE_EQ (output, "hello, world!\r\n");
+	TEST_FILE_END (output);
+	fclose (output);
+
+	TEST_EQ (unlink (filename), 0);
+	TEST_FREE (log);
+
+	/************************************************************/
 	TEST_FEATURE ("ensure logger flushes when destroyed with uid 0");
 
 	TEST_EQ (openpty (&pty_master, &pty_slave, NULL, NULL, NULL), 0);
@@ -650,7 +746,9 @@
 	TEST_EQ (ret, 1);
 
 	close (pty_slave);
-	nih_free (log);
+
+	ret = log_handle_unflushed (NULL, log);
+	TEST_EQ (ret, 1);
 
 	output = fopen (filename, "r");
 	TEST_NE_P (output, NULL);
@@ -1163,10 +1261,9 @@
 	TEST_FREE (log->unflushed);
 }
 
-
-	int
+int
 main (int   argc,
-		char *argv[])
+      char *argv[])
 {
 	/* run tests in legacy (pre-session support) mode */
 	setenv ("UPSTART_NO_SESSIONS", "1", 1);

=== modified file 'po/upstart.pot'
--- po/upstart.pot	2012-03-01 11:27:33 +0000
+++ po/upstart.pot	2012-03-16 21:02:13 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: upstart 1.5\n"
 "Report-Msgid-Bugs-To: [email protected]\n"
-"POT-Creation-Date: 2012-03-01 11:24+0000\n"
+"POT-Creation-Date: 2012-03-16 20:58+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <[email protected]>\n"
@@ -85,6 +85,10 @@
 msgid "The log priority given was not recognised"
 msgstr ""
 
+#: init/control.c:792
+msgid "You do not have permission to notify disk is writeable"
+msgstr ""
+
 #: init/errors.h:60
 msgid "Illegal parameter"
 msgstr ""
@@ -171,97 +175,97 @@
 msgid "Event failed"
 msgstr ""
 
-#: init/job.c:235
+#: init/job.c:246
 #, c-format
 msgid "%s goal changed from %s to %s"
 msgstr ""
 
-#: init/job.c:306
+#: init/job.c:317
 #, c-format
 msgid "%s state changed from %s to %s"
 msgstr ""
 
-#: init/job.c:725 init/job.c:763
+#: init/job.c:736 init/job.c:774
 msgid "Job failed to start"
 msgstr ""
 
-#: init/job.c:738 init/job.c:774
+#: init/job.c:749 init/job.c:785
 msgid "Job failed while stopping"
 msgstr ""
 
-#: init/job.c:750 init/job.c:785
+#: init/job.c:761 init/job.c:796
 msgid "Job failed to restart"
 msgstr ""
 
-#: init/job.c:979
+#: init/job.c:990
 msgid "stop"
 msgstr ""
 
-#: init/job.c:981
+#: init/job.c:992
 msgid "start"
 msgstr ""
 
-#: init/job.c:983
+#: init/job.c:994
 msgid "respawn"
 msgstr ""
 
-#: init/job.c:1028
+#: init/job.c:1039
 msgid "waiting"
 msgstr ""
 
-#: init/job.c:1030
+#: init/job.c:1041
 msgid "starting"
 msgstr ""
 
-#: init/job.c:1032 init/process.c:80
+#: init/job.c:1043 init/process.c:80
 msgid "pre-start"
 msgstr ""
 
-#: init/job.c:1034
+#: init/job.c:1045
 msgid "spawned"
 msgstr ""
 
-#: init/job.c:1036 init/process.c:82
+#: init/job.c:1047 init/process.c:82
 msgid "post-start"
 msgstr ""
 
-#: init/job.c:1038
+#: init/job.c:1049
 msgid "running"
 msgstr ""
 
-#: init/job.c:1040 init/process.c:84
+#: init/job.c:1051 init/process.c:84
 msgid "pre-stop"
 msgstr ""
 
-#: init/job.c:1042
+#: init/job.c:1053
 msgid "stopping"
 msgstr ""
 
-#: init/job.c:1044
+#: init/job.c:1055
 msgid "killed"
 msgstr ""
 
-#: init/job.c:1046 init/process.c:86
+#: init/job.c:1057 init/process.c:86
 msgid "post-stop"
 msgstr ""
 
-#: init/job.c:1129 init/job.c:1204 init/job.c:1280 init/job_class.c:754
+#: init/job.c:1140 init/job.c:1215 init/job.c:1291 init/job_class.c:754
 #: init/job_class.c:894 init/job_class.c:1029
 #, c-format
 msgid "You do not have permission to modify job: %s"
 msgstr ""
 
-#: init/job.c:1137 init/job_class.c:818
+#: init/job.c:1148 init/job_class.c:818
 #, c-format
 msgid "Job is already running: %s"
 msgstr ""
 
-#: init/job.c:1212 init/job.c:1288 init/job_class.c:948 init/job_class.c:1082
+#: init/job.c:1223 init/job.c:1299 init/job_class.c:948 init/job_class.c:1082
 #, c-format
 msgid "Job has already been stopped: %s"
 msgstr ""
 
-#: init/job_class.c:581 init/job_class.c:791 util/initctl.c:1383
+#: init/job_class.c:582 init/job_class.c:792 util/initctl.c:1383
 msgid "Usage"
 msgstr ""
 
@@ -285,214 +289,218 @@
 msgid "%s %s process (%d)"
 msgstr ""
 
-#: init/job_process.c:461
-msgid "No available ptys"
+#: init/job_process.c:469
+msgid "Failed to create pty - disabling logging for job"
 msgstr ""
 
-#: init/job_process.c:503
+#: init/job_process.c:516
 #, c-format
 msgid "Pausing %s (%d) [pre-exec] for debug"
 msgstr ""
 
-#: init/job_process.c:705
+#: init/job_process.c:718
 #, c-format
 msgid "Failed to open system console: %s"
 msgstr ""
 
-#: init/job_process.c:1013
+#: init/job_process.c:1026
 #, c-format
 msgid "unable to move script fd: %s"
 msgstr ""
 
-#: init/job_process.c:1018
+#: init/job_process.c:1031
 #, c-format
 msgid "unable to open console: %s"
 msgstr ""
 
-#: init/job_process.c:1073
+#: init/job_process.c:1086
 #, c-format
 msgid "unable to set \"%s\" resource limit: %s"
 msgstr ""
 
-#: init/job_process.c:1078
+#: init/job_process.c:1091
 #, c-format
 msgid "unable to set priority: %s"
 msgstr ""
 
-#: init/job_process.c:1083
+#: init/job_process.c:1096
 #, c-format
 msgid "unable to set oom adjustment: %s"
 msgstr ""
 
-#: init/job_process.c:1088
+#: init/job_process.c:1101
 #, c-format
 msgid "unable to change root directory: %s"
 msgstr ""
 
-#: init/job_process.c:1093
+#: init/job_process.c:1106
 #, c-format
 msgid "unable to change working directory: %s"
 msgstr ""
 
-#: init/job_process.c:1098
+#: init/job_process.c:1111
 #, c-format
 msgid "unable to set trace: %s"
 msgstr ""
 
-#: init/job_process.c:1103
+#: init/job_process.c:1116
 #, c-format
 msgid "unable to execute: %s"
 msgstr ""
 
-#: init/job_process.c:1108
+#: init/job_process.c:1121
 #, c-format
 msgid "unable to getpwnam: %s"
 msgstr ""
 
-#: init/job_process.c:1113
+#: init/job_process.c:1126
 #, c-format
 msgid "unable to getgrnam: %s"
 msgstr ""
 
-#: init/job_process.c:1118
+#: init/job_process.c:1131
 msgid "unable to find setuid user"
 msgstr ""
 
-#: init/job_process.c:1122
+#: init/job_process.c:1135
 msgid "unable to find setgid group"
 msgstr ""
 
-#: init/job_process.c:1126
+#: init/job_process.c:1139
 #, c-format
 msgid "unable to setuid: %s"
 msgstr ""
 
-#: init/job_process.c:1131
+#: init/job_process.c:1144
 #, c-format
 msgid "unable to setgid: %s"
 msgstr ""
 
-#: init/job_process.c:1136
+#: init/job_process.c:1149
 #, c-format
 msgid "unable to chown: %s"
 msgstr ""
 
-#: init/job_process.c:1141
+#: init/job_process.c:1154
 #, c-format
 msgid "unable to open pt master: %s"
 msgstr ""
 
-#: init/job_process.c:1146
+#: init/job_process.c:1159
 #, c-format
 msgid "unable to unlockpt: %s"
 msgstr ""
 
-#: init/job_process.c:1151
+#: init/job_process.c:1164
 #, c-format
 msgid "unable to get ptsname: %s"
 msgstr ""
 
-#: init/job_process.c:1156
+#: init/job_process.c:1169
 #, c-format
 msgid "unable to open pt slave: %s"
 msgstr ""
 
-#: init/job_process.c:1187 init/job_process.c:1237
+#: init/job_process.c:1200 init/job_process.c:1250
 #, c-format
 msgid "Sending %s signal to %s %s process (%d)"
 msgstr ""
 
-#: init/job_process.c:1196 init/job_process.c:1246
+#: init/job_process.c:1209 init/job_process.c:1259
 #, c-format
 msgid "Failed to send %s signal to %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1307
+#: init/job_process.c:1320
 #, c-format
 msgid "%s %s process (%d) terminated with status %d"
 msgstr ""
 
-#: init/job_process.c:1312
+#: init/job_process.c:1325
 #, c-format
 msgid "%s %s process (%d) exited normally"
 msgstr ""
 
-#: init/job_process.c:1327
+#: init/job_process.c:1340
 #, c-format
 msgid "%s %s process (%d) killed by %s signal"
 msgstr ""
 
-#: init/job_process.c:1331
+#: init/job_process.c:1344
 #, c-format
 msgid "%s %s process (%d) killed by signal %d"
 msgstr ""
 
-#: init/job_process.c:1345
+#: init/job_process.c:1358
 #, c-format
 msgid "%s %s process (%d) stopped by %s signal"
 msgstr ""
 
-#: init/job_process.c:1349
+#: init/job_process.c:1362
 #, c-format
 msgid "%s %s process (%d) stopped by signal %d"
 msgstr ""
 
-#: init/job_process.c:1363
+#: init/job_process.c:1376
 #, c-format
 msgid "%s %s process (%d) continued by %s signal"
 msgstr ""
 
-#: init/job_process.c:1367
+#: init/job_process.c:1380
 #, c-format
 msgid "%s %s process (%d) continued by signal %d"
 msgstr ""
 
-#: init/job_process.c:1502
+#: init/job_process.c:1515
 #, c-format
 msgid "%s respawning too fast, stopped"
 msgstr ""
 
-#: init/job_process.c:1508
+#: init/job_process.c:1521
 #, c-format
 msgid "%s %s process ended, respawning"
 msgstr ""
 
-#: init/job_process.c:1760
+#: init/job_process.c:1621
+msgid "Failed to add log to unflushed queue"
+msgstr ""
+
+#: init/job_process.c:1787
 #, c-format
 msgid "Failed to set ptrace options for %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1773 init/job_process.c:1968
+#: init/job_process.c:1800 init/job_process.c:1995
 #, c-format
 msgid "Failed to continue traced %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1813 init/job_process.c:1904 init/job_process.c:1959
+#: init/job_process.c:1840 init/job_process.c:1931 init/job_process.c:1986
 #, c-format
 msgid "Failed to detach traced %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1853
+#: init/job_process.c:1880
 #, c-format
 msgid "Failed to deliver signal to traced %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1888
+#: init/job_process.c:1915
 #, c-format
 msgid "Failed to obtain child process id for %s %s process (%d): %s"
 msgstr ""
 
-#: init/job_process.c:1895
+#: init/job_process.c:1922
 #, c-format
 msgid "%s %s process (%d) became new process (%d)"
 msgstr ""
 
-#: init/job_process.c:1954
+#: init/job_process.c:1981
 #, c-format
 msgid "%s %s process (%d) executable changed"
 msgstr ""
 
-#: init/log.c:327
+#: init/log.c:350
 msgid "Failed to write to log file"
 msgstr ""
 
@@ -645,74 +653,74 @@
 msgid "Invalid job class"
 msgstr ""
 
-#: util/initctl.c:2212
+#: util/initctl.c:2248
 msgid "unknown event"
 msgstr ""
 
-#: util/initctl.c:2216
+#: util/initctl.c:2252
 msgid "unknown job"
 msgstr ""
 
-#: util/initctl.c:2317
+#: util/initctl.c:2353
 msgid "use D-Bus session bus to connect to init daemon (for testing)"
 msgstr ""
 
-#: util/initctl.c:2319
+#: util/initctl.c:2355
 msgid "use D-Bus system bus to connect to init daemon"
 msgstr ""
 
-#: util/initctl.c:2321
+#: util/initctl.c:2357
 msgid "destination well-known name on D-Bus bus"
 msgstr ""
 
-#: util/initctl.c:2334
+#: util/initctl.c:2370
 msgid "do not wait for job to start before exiting"
 msgstr ""
 
-#: util/initctl.c:2346
+#: util/initctl.c:2382
 msgid "do not wait for job to stop before exiting"
 msgstr ""
 
-#: util/initctl.c:2358
+#: util/initctl.c:2394
 msgid "do not wait for job to restart before exiting"
 msgstr ""
 
-#: util/initctl.c:2397
+#: util/initctl.c:2433
 msgid "do not wait for event to finish before exiting"
 msgstr ""
 
-#: util/initctl.c:2438
+#: util/initctl.c:2474
 msgid ""
 "enumerate list of events and jobs causing job created from job config to "
 "start/stop"
 msgstr ""
 
-#: util/initctl.c:2451
+#: util/initctl.c:2487
 msgid "ignore specified list of events (comma-separated)"
 msgstr ""
 
-#: util/initctl.c:2453
+#: util/initctl.c:2489
 msgid "Generate warning for any unreachable events/jobs"
 msgstr ""
 
-#: util/initctl.c:2472
+#: util/initctl.c:2508
 msgid "Job"
 msgstr ""
 
-#: util/initctl.c:2479
+#: util/initctl.c:2515
 msgid "Event"
 msgstr ""
 
-#: util/initctl.c:2487 util/initctl.c:2499 util/initctl.c:2510
-#: util/initctl.c:2521 util/initctl.c:2528
+#: util/initctl.c:2523 util/initctl.c:2535 util/initctl.c:2546
+#: util/initctl.c:2557 util/initctl.c:2564
 msgid "JOB [KEY=VALUE]..."
 msgstr ""
 
-#: util/initctl.c:2488
+#: util/initctl.c:2524
 msgid "Start job."
 msgstr ""
 
-#: util/initctl.c:2489
+#: util/initctl.c:2525
 msgid ""
 "JOB is the name of the job that is to be started, this may be followed by "
 "zero or more environment variables to be defined in the new job.\n"
@@ -722,11 +730,11 @@
 "an existing instance is already running."
 msgstr ""
 
-#: util/initctl.c:2500
+#: util/initctl.c:2536
 msgid "Stop job."
 msgstr ""
 
-#: util/initctl.c:2501
+#: util/initctl.c:2537
 msgid ""
 "JOB is the name of the job that is to be stopped, this may be followed by "
 "zero or more environment variables to be passed to the job's pre-stop and "
@@ -736,11 +744,11 @@
 "decide which of multiple instances will be stopped."
 msgstr ""
 
-#: util/initctl.c:2511
+#: util/initctl.c:2547
 msgid "Restart job."
 msgstr ""
 
-#: util/initctl.c:2512
+#: util/initctl.c:2548
 msgid ""
 "JOB is the name of the job that is to be restarted, this may be followed by "
 "zero or more environment variables to be defined in the job after "
@@ -750,66 +758,66 @@
 "decide which of multiple instances will be restarted."
 msgstr ""
 
-#: util/initctl.c:2522
+#: util/initctl.c:2558
 msgid "Send HUP signal to job."
 msgstr ""
 
-#: util/initctl.c:2523
+#: util/initctl.c:2559
 msgid ""
 "JOB is the name of the job that is to be sent the signal, this may be "
 "followed by zero or more environment variables to distinguish between job "
 "instances.\n"
 msgstr ""
 
-#: util/initctl.c:2529
+#: util/initctl.c:2565
 msgid "Query status of job."
 msgstr ""
 
-#: util/initctl.c:2530
+#: util/initctl.c:2566
 msgid ""
 "JOB is the name of the job that is to be queried, this may be followed by "
 "zero or more environment variables to distguish between job instances.\n"
 msgstr ""
 
-#: util/initctl.c:2536
+#: util/initctl.c:2572
 msgid "List known jobs."
 msgstr ""
 
-#: util/initctl.c:2537
+#: util/initctl.c:2573
 msgid "The known jobs and their current status will be output."
 msgstr ""
 
-#: util/initctl.c:2540
+#: util/initctl.c:2576
 msgid "EVENT [KEY=VALUE]..."
 msgstr ""
 
-#: util/initctl.c:2541
+#: util/initctl.c:2577
 msgid "Emit an event."
 msgstr ""
 
-#: util/initctl.c:2542
+#: util/initctl.c:2578
 msgid ""
 "EVENT is the name of an event the init daemon should emit, this may be "
 "followed by zero or more environment variables to be included in the event.\n"
 msgstr ""
 
-#: util/initctl.c:2548
+#: util/initctl.c:2584
 msgid "Reload the configuration of the init daemon."
 msgstr ""
 
-#: util/initctl.c:2552
+#: util/initctl.c:2588
 msgid "Request the version of the init daemon."
 msgstr ""
 
-#: util/initctl.c:2555
+#: util/initctl.c:2591
 msgid "[PRIORITY]"
 msgstr ""
 
-#: util/initctl.c:2556
+#: util/initctl.c:2592
 msgid "Change the minimum priority of log messages from the init daemon"
 msgstr ""
 
-#: util/initctl.c:2558
+#: util/initctl.c:2594
 msgid ""
 "PRIORITY may be one of:\n"
 "  `debug' (messages useful for debugging upstart are logged, equivalent to --"
@@ -826,42 +834,52 @@
 "Without arguments, this outputs the current log priority."
 msgstr ""
 
-#: util/initctl.c:2575 util/initctl.c:2581
+#: util/initctl.c:2611 util/initctl.c:2617
 msgid "[CONF]"
 msgstr ""
 
-#: util/initctl.c:2576
+#: util/initctl.c:2612
 msgid "Show emits, start on and stop on details for job configurations."
 msgstr ""
 
-#: util/initctl.c:2577
+#: util/initctl.c:2613
 msgid ""
 "If CONF specified, show configuration details for single job configuration, "
 "else show details for all jobs configurations.\n"
 msgstr ""
 
-#: util/initctl.c:2582
+#: util/initctl.c:2618
 msgid "Check for unreachable jobs/event conditions."
 msgstr ""
 
-#: util/initctl.c:2583
+#: util/initctl.c:2619
 msgid ""
 "List all jobs and events which cannot be satisfied by currently available "
 "job configuration files"
 msgstr ""
 
-#: util/initctl.c:2587
+#: util/initctl.c:2623
 msgid "JOB"
 msgstr ""
 
-#: util/initctl.c:2588
+#: util/initctl.c:2624
 msgid "Show job usage message if available."
 msgstr ""
 
-#: util/initctl.c:2589
+#: util/initctl.c:2625
 msgid "JOB is the name of the job which usage is to be shown.\n"
 msgstr ""
 
+#: util/initctl.c:2629
+msgid "Inform Upstart that disk is now writeable."
+msgstr ""
+
+#: util/initctl.c:2630
+msgid ""
+"Run to ensure output from jobs ending before disk is writeable are flushed "
+"to disk"
+msgstr ""
+
 #: util/reboot.c:113
 msgid "don't sync before reboot or halt"
 msgstr ""

=== modified file 'util/initctl.c'
--- util/initctl.c	2012-02-16 15:55:57 +0000
+++ util/initctl.c	2012-03-16 21:02:13 +0000
@@ -112,20 +112,20 @@
 #endif
 
 /* Prototypes for option and command functions */
-int start_action                (NihCommand *command, char * const *args);
-int stop_action                 (NihCommand *command, char * const *args);
-int restart_action              (NihCommand *command, char * const *args);
-int reload_action               (NihCommand *command, char * const *args);
-int status_action               (NihCommand *command, char * const *args);
-int list_action                 (NihCommand *command, char * const *args);
-int emit_action                 (NihCommand *command, char * const *args);
-int reload_configuration_action (NihCommand *command, char * const *args);
-int version_action              (NihCommand *command, char * const *args);
-int log_priority_action         (NihCommand *command, char * const *args);
-int show_config_action          (NihCommand *command, char * const *args);
-int check_config_action         (NihCommand *command, char * const *args);
-int usage_action                (NihCommand *command, char * const *args);
-
+int start_action                  (NihCommand *command, char * const *args);
+int stop_action                   (NihCommand *command, char * const *args);
+int restart_action                (NihCommand *command, char * const *args);
+int reload_action                 (NihCommand *command, char * const *args);
+int status_action                 (NihCommand *command, char * const *args);
+int list_action                   (NihCommand *command, char * const *args);
+int emit_action                   (NihCommand *command, char * const *args);
+int reload_configuration_action   (NihCommand *command, char * const *args);
+int version_action                (NihCommand *command, char * const *args);
+int log_priority_action           (NihCommand *command, char * const *args);
+int show_config_action            (NihCommand *command, char * const *args);
+int check_config_action           (NihCommand *command, char * const *args);
+int usage_action                  (NihCommand *command, char * const *args);
+int notify_disk_writeable_action  (NihCommand *command, char * const *args);
 
 /**
  * use_dbus:
@@ -1643,6 +1643,42 @@
 	return ret ? 1 : 0;
 }
 
+/**
+ * notify_disk_writeable_action:
+ * @command: NihCommand invoked,
+ * @args: command-line arguments.
+ *
+ * This function is called for the "notify-disk-writeable" command.
+ *
+ * Returns: command exit status.
+ **/
+int
+notify_disk_writeable_action (NihCommand *command,
+		char * const *args)
+{
+	nih_local NihDBusProxy *upstart = NULL;
+	NihError *              err;
+
+	nih_assert (command != NULL);
+	nih_assert (args != NULL);
+
+	upstart = upstart_open (NULL);
+	if (! upstart)
+		return 1;
+
+	if (upstart_notify_disk_writeable_sync (NULL, upstart) < 0)
+		goto error;
+
+	return 0;
+
+error:
+	err = nih_error_get ();
+	nih_error ("%s", err->message);
+	nih_free (err);
+
+	return 1;
+}
+
 static void
 start_reply_handler (char **         job_path,
 		     NihDBusMessage *message,
@@ -2589,6 +2625,11 @@
 	  N_("JOB is the name of the job which usage is to be shown.\n" ),
 	  NULL, usage_options, usage_action },
 
+	{ "notify-disk-writeable", NULL,
+	  N_("Inform Upstart that disk is now writeable."),
+	  N_("Run to ensure output from jobs ending before "
+			  "disk is writeable are flushed to disk"),
+	  NULL, NULL, notify_disk_writeable_action },
 
 	NIH_COMMAND_LAST
 };

=== modified file 'util/man/initctl.8'
--- util/man/initctl.8	2012-02-16 15:45:41 +0000
+++ util/man/initctl.8	2012-03-16 21:02:13 +0000
@@ -510,7 +510,21 @@
 \fBstarting\fP(7)) are automatically ignored.
 .IP "\fB-w\fP, \fB\-\-warn\fP"
 If specified, treat \fIany\fP unknown jobs and events as errors.
-
+.RE
+.\"
+.TP
+.B notify\-disk\-writeable
+Notify the
+.BR init (8)
+daemon that the disk is now writeable. This currently causes the
+.BR init (8)
+daemon to flush its internal cache of \(aqearly job\(aq output data.
+An early job is any job which
+.I finishes
+before the log disk becomes writeable. If job logging is not disabled,
+this command should be called once the log disk becomes writeable
+to ensure that output from all early jobs is flushed. If the data is
+written successfully to disk, the internal cache is deleted.
 .RE
 .\"
 .TP
@@ -537,6 +551,8 @@
 .SH AUTHOR
 Written by Scott James Remnant
 .RB < [email protected] >
+and James Hunt
+.RB < [email protected] > .
 .\"
 .SH REPORTING BUGS
 Report bugs at

=== modified file 'util/tests/test_initctl.c'
--- util/tests/test_initctl.c	2012-02-16 15:45:41 +0000
+++ util/tests/test_initctl.c	2012-03-16 21:02:13 +0000
@@ -123,12 +123,12 @@
 	assert (pid);                                                \
 	                                                             \
 	if (kill (pid, 0) == 0) {                                    \
-		kill (pid, SIGTERM);                                 \
+		TEST_EQ (kill (pid, SIGTERM), 0);                    \
 		sleep (1);                                           \
 	}                                                            \
 	                                                             \
 	if (kill (pid, 0) == 0) {                                    \
-		kill (pid, SIGKILL);                                 \
+		TEST_EQ (kill (pid, SIGKILL), 0);                    \
 	}                                                            \
 }
 
@@ -169,7 +169,7 @@
 		TEST_NE_P (ret, NULL);                               \
 	}                                                            \
 	                                                             \
-	TEST_NE ( pclose (f), -1);                                   \
+	TEST_NE (pclose (f), -1);                                    \
 }
 
 /**
@@ -11386,6 +11386,7 @@
 	STOP_UPSTART (upstart_pid);
 	TEST_EQ (unsetenv ("UPSTART_CONFDIR"), 0);
 	TEST_DBUS_END (dbus_pid);
+        TEST_EQ (rmdir (dirname), 0);
 }
 
 void
@@ -11842,6 +11843,122 @@
 	STOP_UPSTART (upstart_pid);
 	TEST_EQ (unsetenv ("UPSTART_CONFDIR"), 0);
 	TEST_DBUS_END (dbus_pid);
+        TEST_EQ (rmdir (dirname), 0);
+}
+
+void
+test_notify_disk_writeable (void)
+{
+	char             confdir_name[PATH_MAX];
+	char             logdir_name[PATH_MAX];
+	nih_local char  *logfile_name;
+	pid_t            upstart_pid = 0;
+	pid_t            dbus_pid;
+	nih_local char  *cmd;
+	char           **output;
+	size_t           lines;
+	struct stat      statbuf;
+	mode_t           old_perms;
+	FILE            *file;
+
+        TEST_FILENAME (confdir_name);
+        TEST_EQ (mkdir (confdir_name, 0755), 0);
+
+        TEST_FILENAME (logdir_name);
+        TEST_EQ (mkdir (logdir_name, 0755), 0);
+
+	TEST_EQ (stat (logdir_name, &statbuf), 0);
+	old_perms = statbuf.st_mode;
+
+	/* Make inaccessible */
+	TEST_EQ (chmod (logdir_name, 0x0), 0);
+
+	/* Use the "secret" interfaces */
+	TEST_EQ (setenv ("UPSTART_CONFDIR", confdir_name, 1), 0);
+	TEST_EQ (setenv ("UPSTART_LOGDIR", logdir_name, 1), 0);
+
+	TEST_FUNCTION ("notify-disk-writeable");
+
+	TEST_FEATURE ("with job ending before log disk writeable");
+
+	CREATE_FILE (confdir_name, "foo.conf",
+			"console log\n"
+			"exec echo hello world\n");
+
+	logfile_name = NIH_MUST (nih_sprintf (NULL, "%s/%s",
+				logdir_name,
+				"foo.log"));
+
+	TEST_DBUS (dbus_pid);
+	START_UPSTART (upstart_pid);
+
+	cmd = nih_sprintf (NULL, "%s start %s 2>&1",
+			INITCTL_BINARY, "foo");
+	TEST_NE_P (cmd, NULL);
+
+	RUN_COMMAND (NULL, cmd, &output, &lines);
+	TEST_EQ (lines, 1);
+
+	/* Give Upstart a chance to respond */
+	{
+		int i   = 0;
+		int max = 5;
+		int ret;
+
+		for (i=0; i < max; ++i) {
+			nih_free (output);
+			cmd = nih_sprintf (NULL, "%s status %s 2>&1",
+					INITCTL_BINARY, "foo");
+			TEST_NE_P (cmd, NULL);
+
+			RUN_COMMAND (NULL, cmd, &output, &lines);
+			TEST_EQ (lines, 1);
+
+			ret = fnmatch ("foo stop/waiting", output[0], 0);
+
+			if (! ret) {
+				break;
+			}
+
+			sleep (1);
+		}
+	}
+
+	TEST_EQ (fnmatch ("foo stop/waiting", output[0], 0), 0);
+
+	/* Ensure no log file written */
+	TEST_LT (stat (logfile_name, &statbuf), 0);
+
+	/* Restore access */
+	TEST_EQ (chmod (logdir_name, old_perms), 0);
+
+	/* Ensure again that no log file written */
+	TEST_LT (stat (logfile_name, &statbuf), 0);
+
+	cmd = nih_sprintf (NULL, "%s notify-disk-writeable 2>&1", INITCTL_BINARY);
+	TEST_NE_P (cmd, NULL);
+	RUN_COMMAND (NULL, cmd, &output, &lines);
+	TEST_EQ (lines, 0);
+
+	/* Ensure file written now */
+	TEST_EQ (stat (logfile_name, &statbuf), 0);
+
+	file = fopen (logfile_name, "r");
+	TEST_NE_P (file, NULL);
+	TEST_FILE_EQ (file, "hello world\r\n");
+	TEST_FILE_END (file);
+	TEST_EQ (fclose (file), 0);
+
+	STOP_UPSTART (upstart_pid);
+	TEST_EQ (unsetenv ("UPSTART_CONFDIR"), 0);
+	TEST_EQ (unsetenv ("UPSTART_LOGDIR"), 0);
+	TEST_DBUS_END (dbus_pid);
+
+	DELETE_FILE (confdir_name, "foo.conf");
+	DELETE_FILE (logdir_name, "foo.log");
+
+	TEST_EQ (rmdir (confdir_name), 0);
+	TEST_EQ (rmdir (logdir_name), 0);
 }
 
 
@@ -14569,13 +14686,14 @@
 
 	if (in_chroot () && !dbus_configured ()) {
 		fprintf(stderr, "\n\n"
-				"WARNING: not running show-config "
-				"and check-config tests within chroot "
+				"WARNING: not running show-config, "
+				"check-config & notify-disk-writeable tests within chroot "
 				"as no D-Bus, or D-Bus not configured (lp:#728988)"
 				"\n\n");
 	} else {
 		test_show_config ();
 		test_check_config ();
+		test_notify_disk_writeable ();
 	}
 
 	return 0;

-- 
upstart-devel mailing list
[email protected]
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/upstart-devel

Reply via email to