mount-encrypted: provide umount option for shutdown

When shutting the system down, mount-encrypted can be used to clean up
all its bind mounts and devices.

BUG=None
TEST=x86-alex build, manual testing

Change-Id: I025ce8c16c55f8556d7fff45eb6ac2b7a835101a
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/21913
Reviewed-by: Elly Jones <ellyjones@chromium.org>
diff --git a/utility/mount-encrypted.c b/utility/mount-encrypted.c
index 37c4d60..a62de85 100644
--- a/utility/mount-encrypted.c
+++ b/utility/mount-encrypted.c
@@ -829,6 +829,34 @@
 	return 0;
 }
 
+/* Clean up all bind mounts, mounts, attaches, etc. Only the final
+ * action informs the return value. This makes it so that failures
+ * can be cleaned up from, and continue the shutdown process on a
+ * second call. If the loopback cannot be found, claim success.
+ */
+static int shutdown(void)
+{
+	struct bind_mount *bind;
+
+	for (bind = bind_mounts; bind->src; ++ bind) {
+		INFO("Unmounting %s.", bind->dst);
+		if (umount(bind->dst))
+			PERROR("umount(%s)", bind->dst);
+	}
+
+	/* TODO(keescook): this can actually succeed with binds mounted. */
+	INFO("Unmounting %s.", kEncryptedMount);
+	if (umount(kEncryptedMount))
+		PERROR("umount(%s)", kEncryptedMount);
+
+	INFO("Removing %s.", kCryptDev);
+	if (!dm_teardown(kCryptDev))
+		ERROR("dm_teardown(%s)", kCryptDev);
+
+	INFO("Unlooping %s.", kEncryptedBlock);
+	return loop_detach_name(kEncryptedBlock) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
 
 static void check_mount_states(void)
 {
@@ -909,12 +937,14 @@
 	tpm_init();
 
 	if (argc > 1) {
+		if (!strcmp(argv[1], "umount"))
+			return shutdown();
 		if (!strcmp(argv[1], "device"))
 			return device_details();
 		if (!strcmp(argv[1], "finalize"))
 			return finalize_from_cmdline(argc > 2 ? argv[2] : NULL);
 
-		fprintf(stderr, "Usage: %s [device|finalize]\n",
+		fprintf(stderr, "Usage: %s [device|finalize|umount]\n",
 			argv[0]);
 		return 1;
 	}
@@ -929,7 +959,7 @@
 		okay = setup_encrypted();
 	}
 
-	INFO("Done.");
+	INFO_DONE("Done.");
 
 	/* Continue boot. */
 	return !okay;
diff --git a/utility/mount-encrypted.h b/utility/mount-encrypted.h
index d84de4f..e3a85d9 100644
--- a/utility/mount-encrypted.h
+++ b/utility/mount-encrypted.h
@@ -7,7 +7,8 @@
 #ifndef _MOUNT_ENCRYPTED_H_
 #define _MOUNT_ENCRYPTED_H_
 
-#define DEBUG_ENABLED 0
+#define DEBUG_ENABLED 1
+#define DEBUG_TIME_DELTA 1
 
 #include <openssl/err.h>
 #include <openssl/sha.h>
@@ -15,8 +16,8 @@
 #define DIGEST_LENGTH SHA256_DIGEST_LENGTH
 
 #define _ERROR(f, a...)	do { \
-	fprintf(stderr, "ERROR %s (%s, %d): ", \
-			__func__, __FILE__, __LINE__); \
+	fprintf(stderr, "ERROR[pid:%d] %s (%s, %d): ", \
+			getpid(), __func__, __FILE__, __LINE__); \
 	fprintf(stderr, f, ## a); \
 } while (0)
 #define ERROR(f, a...)	do { \
@@ -39,48 +40,87 @@
 
 #if DEBUG_ENABLED
 static struct timeval tick;
-# define TICK_INIT() gettimeofday(&tick, NULL)
+static struct timeval tick_start;
+# define TICK_INIT() do { \
+	gettimeofday(&tick, NULL); \
+	tick_start = tick; \
+} while (0)
 # ifdef DEBUG_TIME_DELTA
+/* This timeval helper copied from glibc manual. */
+static inline int timeval_subtract(struct timeval *result,
+				   struct timeval *x,
+				   struct timeval *y)
+{
+	/* Perform the carry for the later subtraction by updating y. */
+	if (x->tv_usec < y->tv_usec) {
+		int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+		y->tv_usec -= 1000000 * nsec;
+		y->tv_sec += nsec;
+	}
+	if (x->tv_usec - y->tv_usec > 1000000) {
+		int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+		y->tv_usec += 1000000 * nsec;
+		y->tv_sec -= nsec;
+	}
+
+	/* Compute the time remaining to wait.
+	 * tv_usec is certainly positive.
+	 */
+	result->tv_sec = x->tv_sec - y->tv_sec;
+	result->tv_usec = x->tv_usec - y->tv_usec;
+
+	/* Return 1 if result is negative. */
+	return x->tv_sec < y->tv_sec;
+}
 #  define TICK_REPORT() do { \
 	struct timeval now, diff; \
 	gettimeofday(&now, NULL); \
-	diff.tv_sec = now.tv_sec - tick.tv_sec; \
-	if (tick.tv_usec > now.tv_usec) { \
-		diff.tv_sec -= 1; \
-		diff.tv_usec = 1000000 - tick.tv_usec + now.tv_usec; \
-	} else { \
-		diff.tv_usec = now.tv_usec - tick.tv_usec; \
-	} \
+	timeval_subtract(&diff, &now, &tick); \
+	printf("\tTook: [pid:%d, %2lu.%06lus]\n", getpid(), \
+		(unsigned long)diff.tv_sec, (unsigned long)diff.tv_usec); \
 	tick = now; \
-	printf("\tTook: [%2d.%06d]\n", (int)diff.tv_sec, (int)diff.tv_usec); \
 } while (0)
 # else
 #  define TICK_REPORT() do { \
 	gettimeofday(&tick, NULL); \
-	printf("[%d:%2d.%06d] ", getpid(), (int)tick.tv_sec, (int)tick.tv_usec); \
+	printf("[%2d.%06d] ", (int)tick.tv_sec, (int)tick.tv_usec); \
 } while (0)
 # endif
+# define TICK_DONE() do { \
+	struct timeval tick_done; \
+	TICK_REPORT(); \
+	timeval_subtract(&tick_done, &tick, &tick_start); \
+	printf("Process Lifetime: [pid:%d, %2d.%06ds]\n", getpid(), \
+		(int)tick_done.tv_sec, (int)tick_done.tv_usec); \
+} while (0)
 #else
 # define TICK_INIT() do { } while (0)
 # define TICK_REPORT() do { } while (0)
+# define TICK_DONE() do { } while (0)
 #endif
 
-#define INFO(f, a...) do { \
-	TICK_REPORT(); \
+#define _INFO(f, a...) do { \
+	printf("[pid:%d] ", getpid()); \
 	printf(f, ## a); \
 	printf("\n"); \
 	fflush(stdout); \
 } while (0)
+#define INFO(f, a...) do { \
+	TICK_REPORT(); \
+	_INFO(f, ## a); \
+} while (0)
 #define INFO_INIT(f, a...) do { \
 	TICK_INIT(); \
 	INFO(f, ## a); \
 } while (0)
+#define INFO_DONE(f, a...) do { \
+	TICK_DONE(); \
+	INFO(f, ## a); \
+} while (0)
 #if DEBUG_ENABLED
 # define DEBUG(f, a...) do { \
 	TICK_REPORT(); \
-	printf(f, ## a); \
-	printf("\n"); \
-	fflush(stdout); \
+	_INFO(f, ## a); \
 } while (0)
 #else
 # define DEBUG(f, a...) do { } while (0)
diff --git a/utility/mount-helpers.c b/utility/mount-helpers.c
index bd7a521..0521497 100644
--- a/utility/mount-helpers.c
+++ b/utility/mount-helpers.c
@@ -155,23 +155,29 @@
 		major(info.st_rdev) == kLoopMajor);
 }
 
-static int loop_is_attached(int fd)
+static int loop_is_attached(int fd, struct loop_info64 *info)
 {
-	struct loop_info info;
+	struct loop_info64 local_info;
 
-	errno = 0;
-	if (ioctl(fd, LOOP_GET_STATUS, &info) && errno == ENXIO)
-		return 0;
-
-	return 1;
+	return ioctl(fd, LOOP_GET_STATUS64, info ? info : &local_info) == 0;
 }
 
-static int loop_allocate(gchar **loopback)
+/* Returns either the matching loopback name, or next available, if NULL. */
+static int loop_locate(gchar **loopback, const char *name)
 {
-	int i, fd;
+	int i, fd, namelen = 0;
+
+	if (name) {
+		namelen = strlen(name);
+		if (namelen >= LO_NAME_SIZE)
+			return -1;
+	}
 
 	*loopback = NULL;
 	for (i = 0; i < kLoopMax; ++i) {
+		struct loop_info64 info;
+		int attached;
+
 		g_free(*loopback);
 		*loopback = g_strdup_printf(kLoopTemplate, i);
 		if (!*loopback) {
@@ -184,13 +190,24 @@
 			PERROR("open(%s)", *loopback);
 			goto failed;
 		}
-		if (is_loop_device(fd) && !loop_is_attached(fd)) {
+		if (!is_loop_device(fd)) {
 			close(fd);
+			continue;
+		}
+
+		memset(&info, 0, sizeof(info));
+		attached = loop_is_attached(fd, &info);
+		close(fd);
+
+		if ((attached && name &&
+		     strncmp((char *)info.lo_file_name, name, namelen) == 0) ||
+		    (!attached && !name)) {
+			/* Reopen for working on it. */
 			fd = open(*loopback, O_RDWR | O_NOFOLLOW);
-			if (is_loop_device(fd) && !loop_is_attached(fd))
+			if (is_loop_device(fd) &&
+			    loop_is_attached(fd, NULL) == attached)
 				return fd;
 		}
-		close(fd);
 	}
 	ERROR("Ran out of loopback devices");
 
@@ -200,37 +217,55 @@
 	return -1;
 }
 
+static int loop_detach_fd(int fd)
+{
+	if (ioctl(fd, LOOP_CLR_FD, 0)) {
+		PERROR("LOOP_CLR_FD");
+		return 0;
+	}
+	return 1;
+}
+
 int loop_detach(const gchar *loopback)
 {
-	int fd;
+	int fd, rc = 1;
 
 	fd = open(loopback, O_RDONLY | O_NOFOLLOW);
 	if (fd < 0) {
 		PERROR("open(%s)", loopback);
 		return 0;
 	}
-	if (!is_loop_device(fd) || !loop_is_attached(fd))
-		goto failed;
-	if (ioctl(fd, LOOP_CLR_FD, 0)) {
-		PERROR("LOOP_CLR_FD");
-		goto failed;
-	}
+	if (!is_loop_device(fd) || !loop_is_attached(fd, NULL) ||
+	    !loop_detach_fd(fd))
+		rc = 0;
 
 	close (fd);
-	return 1;
-
-failed:
-	close(fd);
-	return 0;
+	return rc;
 }
 
+int loop_detach_name(const char *name)
+{
+	gchar *loopback = NULL;
+	int loopfd, rc;
+
+	loopfd = loop_locate(&loopback, name);
+	if (loopfd < 0)
+		return 0;
+	rc = loop_detach_fd(loopfd);
+
+	close(loopfd);
+	g_free(loopback);
+	return rc;
+}
+
+/* Closes fd, returns name of loopback device pathname. */
 gchar *loop_attach(int fd, const char *name)
 {
 	gchar *loopback = NULL;
 	int loopfd;
 	struct loop_info64 info;
 
-	loopfd = loop_allocate(&loopback);
+	loopfd = loop_locate(&loopback, NULL);
 	if (loopfd < 0)
 		return NULL;
 	if (ioctl(loopfd, LOOP_SET_FD, fd) < 0) {
@@ -295,7 +330,7 @@
 	return 1;
 }
 
-void dm_teardown(const gchar *device)
+int dm_teardown(const gchar *device)
 {
 	const char *argv[] = {
 		"/sbin/dmsetup",
@@ -304,7 +339,9 @@
 		NULL
 	};
 	/* TODO(keescook): replace with call to libdevmapper. */
-	runcmd(argv, NULL);
+	if (runcmd(argv, NULL) != 0)
+		return 0;
+	return 1;
 }
 
 char *dm_get_key(const gchar *device)
diff --git a/utility/mount-helpers.h b/utility/mount-helpers.h
index 66fb102..19f6242 100644
--- a/utility/mount-helpers.h
+++ b/utility/mount-helpers.h
@@ -18,11 +18,12 @@
 /* Loopback device attach/detach helpers. */
 gchar *loop_attach(int fd, const char *name);
 int loop_detach(const gchar *loopback);
+int loop_detach_name(const char *name);
 
 /* Encrypted device mapper setup/teardown. */
 int dm_setup(size_t sectors, const gchar *encryption_key, const char *name,
 		const gchar *device, const char *path);
-void dm_teardown(const gchar *device);
+int dm_teardown(const gchar *device);
 char *dm_get_key(const gchar *device);
 
 /* Sparse file creation. */