From f93b7218c3e2d11c5b3cdd4c367a42ca7a7ede77 Mon Sep 17 00:00:00 2001
From: Carl Worth <cworth@cworth.org>
Date: Thu, 7 Jan 2010 10:19:44 -0800
Subject: [PATCH] lib: Consolidate checks for read-only database.

Previously, many checks were deep in the library just before a cast
operation. These have now been replaced with internal errors and new
checks have instead been added at the beginning of all top-levelentry
points requiring a read-write database.

The new checks now also use a single function for checking and
printing the error message. This will give us a convenient location to
extend the check, (such as based on database version as well).
---
 lib/database.cc       | 28 ++++++++++++------
 lib/directory.cc      | 15 ++++------
 lib/message.cc        | 67 +++++++++++++++++++++++++++++++------------
 lib/notmuch-private.h |  3 ++
 lib/notmuch.h         | 29 +++++++++++++++++--
 5 files changed, 104 insertions(+), 38 deletions(-)

diff --git a/lib/database.cc b/lib/database.cc
index 125b37eb..807e39ed 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -475,6 +475,17 @@ notmuch_database_create (const char *path)
     return notmuch;
 }
 
+notmuch_status_t
+_notmuch_database_ensure_writable (notmuch_database_t *notmuch)
+{
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+	fprintf (stderr, "Cannot write to a read-only database.\n");
+	return NOTMUCH_STATUS_READONLY_DATABASE;
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 notmuch_database_t *
 notmuch_database_open (const char *path,
 		       notmuch_database_mode_t mode)
@@ -1034,11 +1045,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
     if (message_ret)
 	*message_ret = NULL;
 
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+	return ret;
+
     message_file = notmuch_message_file_open (filename);
-    if (message_file == NULL) {
-	ret = NOTMUCH_STATUS_FILE_ERROR;
-	goto DONE;
-    }
+    if (message_file == NULL)
+	return NOTMUCH_STATUS_FILE_ERROR;
 
     notmuch_message_file_restrict_headers (message_file,
 					   "date",
@@ -1173,10 +1186,9 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
     Xapian::Document document;
     notmuch_status_t status;
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
-	fprintf (stderr, "Attempted to update a read-only database.\n");
-	return NOTMUCH_STATUS_READONLY_DATABASE;
-    }
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+	return status;
 
     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
 
diff --git a/lib/directory.cc b/lib/directory.cc
index d5015e0a..461c0cc3 100644
--- a/lib/directory.cc
+++ b/lib/directory.cc
@@ -182,11 +182,8 @@ _notmuch_directory_create (notmuch_database_t *notmuch,
 
     path = _notmuch_database_relative_path (notmuch, path);
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
-	fprintf (stderr, "Attempted to update a read-only database.\n");
-	*status_ret = NOTMUCH_STATUS_READONLY_DATABASE;
-	return NULL;
-    }
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+	INTERNAL_ERROR ("Failure to ensure database is writable");
 
     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
 
@@ -268,11 +265,11 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory,
 {
     notmuch_database_t *notmuch = directory->notmuch;
     Xapian::WritableDatabase *db;
+    notmuch_status_t status;
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
-	fprintf (stderr, "Attempted to update a read-only database.\n");
-	return NOTMUCH_STATUS_READONLY_DATABASE;
-    }
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+	return status;
 
     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
 
diff --git a/lib/message.cc b/lib/message.cc
index 0fc54668..ea9c8bd9 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -174,11 +174,6 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
     unsigned int doc_id;
     char *term;
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
-	*status_ret = NOTMUCH_PRIVATE_STATUS_READONLY_DATABASE;
-	return NULL;
-    }
-
     *status_ret = NOTMUCH_PRIVATE_STATUS_SUCCESS;
 
     message = notmuch_database_find_message (notmuch, message_id);
@@ -192,6 +187,9 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
 	return NULL;
     }
 
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+	INTERNAL_ERROR ("Failure to ensure database is writable.");
+
     db = static_cast<Xapian::WritableDatabase *> (notmuch->xapian_db);
     try {
 	doc.add_term (term);
@@ -706,7 +704,12 @@ _notmuch_message_remove_term (notmuch_message_t *message,
 notmuch_status_t
 notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+	return status;
 
     if (tag == NULL)
 	return NOTMUCH_STATUS_NULL_POINTER;
@@ -714,10 +717,10 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
     if (strlen (tag) > NOTMUCH_TAG_MAX)
 	return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    status = _notmuch_message_add_term (message, "tag", tag);
-    if (status) {
+    private_status = _notmuch_message_add_term (message, "tag", tag);
+    if (private_status) {
 	INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
-			status);
+			private_status);
     }
 
     if (! message->frozen)
@@ -729,7 +732,12 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
 notmuch_status_t
 notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+	return status;
 
     if (tag == NULL)
 	return NOTMUCH_STATUS_NULL_POINTER;
@@ -737,10 +745,10 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     if (strlen (tag) > NOTMUCH_TAG_MAX)
 	return NOTMUCH_STATUS_TAG_TOO_LONG;
 
-    status = _notmuch_message_remove_term (message, "tag", tag);
-    if (status) {
+    private_status = _notmuch_message_remove_term (message, "tag", tag);
+    if (private_status) {
 	INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-			status);
+			private_status);
     }
 
     if (! message->frozen)
@@ -749,39 +757,60 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
     return NOTMUCH_STATUS_SUCCESS;
 }
 
-void
+notmuch_status_t
 notmuch_message_remove_all_tags (notmuch_message_t *message)
 {
-    notmuch_private_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
     notmuch_tags_t *tags;
     const char *tag;
 
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+	return status;
+
     for (tags = notmuch_message_get_tags (message);
 	 notmuch_tags_has_more (tags);
 	 notmuch_tags_advance (tags))
     {
 	tag = notmuch_tags_get (tags);
 
-	status = _notmuch_message_remove_term (message, "tag", tag);
-	if (status) {
+	private_status = _notmuch_message_remove_term (message, "tag", tag);
+	if (private_status) {
 	    INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-			    status);
+			    private_status);
 	}
     }
 
     if (! message->frozen)
 	_notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
-void
+notmuch_status_t
 notmuch_message_freeze (notmuch_message_t *message)
 {
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+	return status;
+
     message->frozen++;
+
+    return NOTMUCH_STATUS_SUCCESS;
 }
 
 notmuch_status_t
 notmuch_message_thaw (notmuch_message_t *message)
 {
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+	return status;
+
     if (message->frozen > 0) {
 	message->frozen--;
 	if (message->frozen == 0)
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
index 8b582640..4eb82619 100644
--- a/lib/notmuch-private.h
+++ b/lib/notmuch-private.h
@@ -151,6 +151,9 @@ typedef enum _notmuch_private_status {
 const char *
 _find_prefix (const char *name);
 
+notmuch_status_t
+_notmuch_database_ensure_writable (notmuch_database_t *notmuch);
+
 const char *
 _notmuch_database_relative_path (notmuch_database_t *notmuch,
 				 const char *path);
diff --git a/lib/notmuch.h b/lib/notmuch.h
index f3e1d286..d0337304 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -229,6 +229,9 @@ notmuch_database_get_directory (notmuch_database_t *database,
  *
  * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
  *	like an email message. Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so no message can be added.
  */
 notmuch_status_t
 notmuch_database_add_message (notmuch_database_t *database,
@@ -252,6 +255,9 @@ notmuch_database_add_message (notmuch_database_t *database,
  * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but
  *	the message persists in the database with at least one other
  *	filename.
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so no message can be removed.
  */
 notmuch_status_t
 notmuch_database_remove_message (notmuch_database_t *database,
@@ -789,6 +795,9 @@ notmuch_message_get_tags (notmuch_message_t *message);
  *
  * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
  *	(exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so message cannot be modified.
  */
 notmuch_status_t
 notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
@@ -803,6 +812,9 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
  *
  * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
  *	(exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so message cannot be modified.
  */
 notmuch_status_t
 notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
@@ -811,8 +823,11 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
  *
  * See notmuch_message_freeze for an example showing how to safely
  * replace tag values.
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so message cannot be modified.
  */
-void
+notmuch_status_t
 notmuch_message_remove_all_tags (notmuch_message_t *message);
 
 /* Freeze the current state of 'message' within the database.
@@ -847,8 +862,15 @@ notmuch_message_remove_all_tags (notmuch_message_t *message);
  * somehow getting interrupted. This could result in the message being
  * left with no tags if the interruption happened after
  * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully frozen.
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so message cannot be modified.
  */
-void
+notmuch_status_t
 notmuch_message_freeze (notmuch_message_t *message);
 
 /* Thaw the current 'message', synchronizing any changes that may have
@@ -957,6 +979,9 @@ notmuch_tags_destroy (notmuch_tags_t *tags);
  *
  * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception
  *	occurred, mtime not stored.
+ *
+ * NOTMUCH_STATUS_READONLY_DATABASE: Database was opened in read-only
+ *	mode so directory mtime cannot be modified.
  */
 notmuch_status_t
 notmuch_directory_set_mtime (notmuch_directory_t *directory,
-- 
2.45.2