1 /* directory.cc - Results of directory-based searches from a notmuch database
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see https://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20 
21 #include "notmuch-private.h"
22 #include "database-private.h"
23 
24 /* Create an iterator to iterate over the basenames of files (or
25  * directories) that all share a common parent directory.
26  */
27 static notmuch_filenames_t *
_create_filenames_for_terms_with_prefix(void * ctx,notmuch_database_t * notmuch,const char * prefix)28 _create_filenames_for_terms_with_prefix (void *ctx,
29 					 notmuch_database_t *notmuch,
30 					 const char *prefix)
31 {
32     notmuch_string_list_t *filename_list;
33     Xapian::TermIterator i, end;
34 
35     i = notmuch->xapian_db->allterms_begin ();
36     end = notmuch->xapian_db->allterms_end ();
37     filename_list = _notmuch_database_get_terms_with_prefix (ctx, i, end,
38 							     prefix);
39     if (unlikely (filename_list == NULL))
40 	return NULL;
41 
42     return _notmuch_filenames_create (ctx, filename_list);
43 }
44 
45 struct _notmuch_directory {
46     notmuch_database_t *notmuch;
47     Xapian::docid document_id;
48     Xapian::Document doc;
49     time_t mtime;
50 };
51 
52 #define LOG_XAPIAN_EXCEPTION(directory, error) _log_xapian_exception (__location__, directory, error)
53 
54 static void
_log_xapian_exception(const char * where,notmuch_directory_t * dir,const Xapian::Error error)55 _log_xapian_exception (const char *where, notmuch_directory_t *dir,  const Xapian::Error error)
56 {
57     notmuch_database_t *notmuch = dir->notmuch;
58 
59     _notmuch_database_log (notmuch,
60 			   "A Xapian exception occurred at %s: %s\n",
61 			   where,
62 			   error.get_msg ().c_str ());
63     notmuch->exception_reported = true;
64 }
65 
66 
67 /* We end up having to call the destructor explicitly because we had
68  * to use "placement new" in order to initialize C++ objects within a
69  * block that we allocated with talloc. So C++ is making talloc
70  * slightly less simple to use, (we wouldn't need
71  * talloc_set_destructor at all otherwise).
72  */
73 static int
_notmuch_directory_destructor(notmuch_directory_t * directory)74 _notmuch_directory_destructor (notmuch_directory_t *directory)
75 {
76     directory->doc.~Document ();
77 
78     return 0;
79 }
80 
81 static notmuch_private_status_t
find_directory_document(notmuch_database_t * notmuch,const char * db_path,Xapian::Document * document)82 find_directory_document (notmuch_database_t *notmuch,
83 			 const char *db_path,
84 			 Xapian::Document *document)
85 {
86     notmuch_private_status_t status;
87     Xapian::docid doc_id;
88 
89     status = _notmuch_database_find_unique_doc_id (notmuch, "directory",
90 						   db_path, &doc_id);
91     if (status) {
92 	*document = Xapian::Document ();
93 	return status;
94     }
95 
96     *document = notmuch->xapian_db->get_document (doc_id);
97     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
98 }
99 
100 /* Find or create a directory document.
101  *
102  * 'path' should be a path relative to the path of 'database', or else
103  * should be an absolute path with initial components that match the
104  * path of 'database'.
105  *
106  * If (flags & NOTMUCH_FIND_CREATE), then the directory document will
107  * be created if it does not exist.  Otherwise, if the directory
108  * document does not exist, *status_ret is set to
109  * NOTMUCH_STATUS_SUCCESS and this returns NULL.
110  */
111 notmuch_directory_t *
_notmuch_directory_find_or_create(notmuch_database_t * notmuch,const char * path,notmuch_find_flags_t flags,notmuch_status_t * status_ret)112 _notmuch_directory_find_or_create (notmuch_database_t *notmuch,
113 				   const char *path,
114 				   notmuch_find_flags_t flags,
115 				   notmuch_status_t *status_ret)
116 {
117     notmuch_directory_t *directory;
118     notmuch_private_status_t private_status;
119     const char *db_path;
120     bool create = (flags & NOTMUCH_FIND_CREATE);
121 
122     if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) {
123 	*status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED;
124 	return NULL;
125     }
126 
127     *status_ret = NOTMUCH_STATUS_SUCCESS;
128 
129     path = _notmuch_database_relative_path (notmuch, path);
130 
131     if (create && _notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY)
132 	INTERNAL_ERROR ("Failure to ensure database is writable");
133 
134     directory = talloc (notmuch, notmuch_directory_t);
135     if (unlikely (directory == NULL)) {
136 	*status_ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
137 	return NULL;
138     }
139 
140     directory->notmuch = notmuch;
141 
142     /* "placement new"---not actually allocating memory */
143     new (&directory->doc) Xapian::Document;
144 
145     talloc_set_destructor (directory, _notmuch_directory_destructor);
146 
147     db_path = _notmuch_database_get_directory_db_path (path);
148 
149     try {
150 	Xapian::TermIterator i, end;
151 
152 	private_status = find_directory_document (notmuch, db_path,
153 						  &directory->doc);
154 	directory->document_id = directory->doc.get_docid ();
155 
156 	if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
157 	    if (! create) {
158 		notmuch_directory_destroy (directory);
159 		directory = NULL;
160 		*status_ret = NOTMUCH_STATUS_SUCCESS;
161 		goto DONE;
162 	    }
163 
164 	    void *local = talloc_new (directory);
165 	    const char *parent, *basename;
166 	    Xapian::docid parent_id;
167 	    char *term = talloc_asprintf (local, "%s%s",
168 					  _find_prefix ("directory"), db_path);
169 	    directory->doc.add_term (term, 0);
170 
171 	    directory->doc.set_data (path);
172 
173 	    _notmuch_database_split_path (local, path, &parent, &basename);
174 
175 	    *status_ret = _notmuch_database_find_directory_id (
176 		notmuch, parent, NOTMUCH_FIND_CREATE, &parent_id);
177 	    if (*status_ret) {
178 		notmuch_directory_destroy (directory);
179 		directory = NULL;
180 		goto DONE;
181 	    }
182 
183 	    if (basename) {
184 		term = talloc_asprintf (local, "%s%u:%s",
185 					_find_prefix ("directory-direntry"),
186 					parent_id, basename);
187 		directory->doc.add_term (term, 0);
188 	    }
189 
190 	    directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
191 				      Xapian::sortable_serialise (0));
192 
193 	    directory->document_id = _notmuch_database_generate_doc_id (notmuch);
194 	    directory->notmuch->
195 		writable_xapian_db
196 		-> replace_document (directory->document_id, directory->doc);
197 	    talloc_free (local);
198 	}
199 
200 	directory->mtime = Xapian::sortable_unserialise (
201 	    directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
202     } catch (const Xapian::Error &error) {
203 	_notmuch_database_log (notmuch,
204 			       "A Xapian exception occurred finding/creating a directory: %s.\n",
205 			       error.get_msg ().c_str ());
206 	notmuch->exception_reported = true;
207 	notmuch_directory_destroy (directory);
208 	directory = NULL;
209 	*status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
210     }
211 
212   DONE:
213     if (db_path != path)
214 	free ((char *) db_path);
215 
216     return directory;
217 }
218 
219 unsigned int
_notmuch_directory_get_document_id(notmuch_directory_t * directory)220 _notmuch_directory_get_document_id (notmuch_directory_t *directory)
221 {
222     return directory->document_id;
223 }
224 
225 notmuch_status_t
notmuch_directory_set_mtime(notmuch_directory_t * directory,time_t mtime)226 notmuch_directory_set_mtime (notmuch_directory_t *directory,
227 			     time_t mtime)
228 {
229     notmuch_database_t *notmuch = directory->notmuch;
230     notmuch_status_t status;
231 
232     status = _notmuch_database_ensure_writable (notmuch);
233     if (status)
234 	return status;
235 
236     try {
237 	directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
238 				  Xapian::sortable_serialise (mtime));
239 
240 	directory->notmuch
241 	    ->writable_xapian_db->replace_document (directory->document_id, directory->doc);
242 
243 	directory->mtime = mtime;
244 
245     } catch (const Xapian::Error &error) {
246 	_notmuch_database_log (notmuch,
247 			       "A Xapian exception occurred setting directory mtime: %s.\n",
248 			       error.get_msg ().c_str ());
249 	notmuch->exception_reported = true;
250 	return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
251     }
252 
253     return NOTMUCH_STATUS_SUCCESS;
254 }
255 
256 time_t
notmuch_directory_get_mtime(notmuch_directory_t * directory)257 notmuch_directory_get_mtime (notmuch_directory_t *directory)
258 {
259     return directory->mtime;
260 }
261 
262 notmuch_filenames_t *
notmuch_directory_get_child_files(notmuch_directory_t * directory)263 notmuch_directory_get_child_files (notmuch_directory_t *directory)
264 {
265     char *term;
266     notmuch_filenames_t *child_files = NULL;
267 
268     term = talloc_asprintf (directory, "%s%u:",
269 			    _find_prefix ("file-direntry"),
270 			    directory->document_id);
271 
272     try {
273 	child_files = _create_filenames_for_terms_with_prefix (directory,
274 							       directory->notmuch,
275 							       term);
276     } catch (Xapian::Error &error) {
277 	LOG_XAPIAN_EXCEPTION (directory, error);
278     }
279 
280     talloc_free (term);
281 
282     return child_files;
283 }
284 
285 notmuch_filenames_t *
notmuch_directory_get_child_directories(notmuch_directory_t * directory)286 notmuch_directory_get_child_directories (notmuch_directory_t *directory)
287 {
288     char *term;
289     notmuch_filenames_t *child_directories = NULL;
290 
291     term = talloc_asprintf (directory, "%s%u:",
292 			    _find_prefix ("directory-direntry"),
293 			    directory->document_id);
294 
295     try {
296 	child_directories = _create_filenames_for_terms_with_prefix (directory,
297 								     directory->notmuch, term);
298     } catch (Xapian::Error &error) {
299 	LOG_XAPIAN_EXCEPTION (directory, error);
300     }
301 
302     talloc_free (term);
303 
304     return child_directories;
305 }
306 
307 notmuch_status_t
notmuch_directory_delete(notmuch_directory_t * directory)308 notmuch_directory_delete (notmuch_directory_t *directory)
309 {
310     notmuch_status_t status;
311 
312     status = _notmuch_database_ensure_writable (directory->notmuch);
313     if (status)
314 	return status;
315 
316     try {
317 	directory->notmuch->
318 	    writable_xapian_db->delete_document (directory->document_id);
319     } catch (const Xapian::Error &error) {
320 	_notmuch_database_log (directory->notmuch,
321 			       "A Xapian exception occurred deleting directory entry: %s.\n",
322 			       error.get_msg ().c_str ());
323 	directory->notmuch->exception_reported = true;
324 	status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
325     }
326     notmuch_directory_destroy (directory);
327 
328     return status;
329 }
330 
331 void
notmuch_directory_destroy(notmuch_directory_t * directory)332 notmuch_directory_destroy (notmuch_directory_t *directory)
333 {
334     talloc_free (directory);
335 }
336