1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Michael Zucchi <notzed@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 
29 #include <glib/gi18n-lib.h>
30 #include <glib/gstdio.h>
31 
32 #include "camel-mbox-folder.h"
33 #include "camel-mbox-store.h"
34 
35 #define d(x)
36 
37 G_DEFINE_TYPE (CamelMboxStore, camel_mbox_store, CAMEL_TYPE_LOCAL_STORE)
38 
39 static const gchar *extensions[] = {
40 	".msf",
41 	".ev-summary",
42 	".ev-summary-meta",
43 	".ibex.index",
44 	".ibex.index.data",
45 	".cmeta",
46 	".lock",
47 	".db",
48 	".journal"
49 };
50 
51 /* used to find out where we've visited already */
52 struct _inode {
53 	dev_t dnode;
54 	ino_t inode;
55 };
56 
57 static guint
inode_hash(gconstpointer d)58 inode_hash (gconstpointer d)
59 {
60 	const struct _inode *v = d;
61 
62 	return v->inode ^ v->dnode;
63 }
64 
65 static gboolean
inode_equal(gconstpointer a,gconstpointer b)66 inode_equal (gconstpointer a,
67              gconstpointer b)
68 {
69 	const struct _inode *v1 = a, *v2 = b;
70 
71 	return v1->inode == v2->inode && v1->dnode == v2->dnode;
72 }
73 
74 static struct _inode *
inode_new(const struct _inode * src)75 inode_new (const struct _inode *src)
76 {
77 	struct _inode *inode;
78 
79 	inode = g_slice_new0 (struct _inode);
80 	if (src) {
81 		inode->dnode = src->dnode;
82 		inode->inode = src->inode;
83 	}
84 
85 	return inode;
86 }
87 
88 static void
inode_free(gpointer ptr)89 inode_free (gpointer ptr)
90 {
91 	g_slice_free (struct _inode, ptr);
92 }
93 
94 static gboolean
ignore_file(const gchar * filename,gboolean sbd)95 ignore_file (const gchar *filename,
96              gboolean sbd)
97 {
98 	gint flen, len, i;
99 
100 	/* TODO: Should probably just be 1 regex */
101 	flen = strlen (filename);
102 	if (flen > 0 && filename[flen - 1] == '~')
103 		return TRUE;
104 
105 	for (i = 0; i < G_N_ELEMENTS (extensions); i++) {
106 		len = strlen (extensions[i]);
107 		if (len < flen && !strcmp (filename + flen - len, extensions[i]))
108 			return TRUE;
109 	}
110 
111 	if (sbd && flen > 4 && !strcmp (filename + flen - 4, ".sbd"))
112 		return TRUE;
113 
114 	return FALSE;
115 }
116 
117 /* NB: duplicated in maildir store */
118 static void
fill_fi(CamelStore * store,CamelFolderInfo * fi,guint32 flags)119 fill_fi (CamelStore *store,
120          CamelFolderInfo *fi,
121          guint32 flags)
122 {
123 	CamelFolder *folder;
124 
125 	fi->unread = -1;
126 	fi->total = -1;
127 	folder = camel_object_bag_peek (camel_store_get_folders_bag (store), fi->full_name);
128 	if (folder) {
129 		if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
130 			camel_folder_refresh_info_sync (folder, NULL, NULL);
131 		fi->unread = camel_folder_get_unread_message_count (folder);
132 		fi->total = camel_folder_get_message_count (folder);
133 		g_object_unref (folder);
134 	} else {
135 		CamelLocalStore *local_store;
136 		gchar *folderpath;
137 		CamelMboxSummary *mbs;
138 
139 		local_store = CAMEL_LOCAL_STORE (store);
140 
141 		/* This should be fast enough not
142 		 * to have to test for INFO_FAST. */
143 		folderpath = camel_local_store_get_full_path (
144 			local_store, fi->full_name);
145 
146 		mbs = (CamelMboxSummary *) camel_mbox_summary_new (
147 			NULL, folderpath, NULL);
148 		/* FIXME[disk-summary] track exception */
149 		if (camel_folder_summary_header_load ((CamelFolderSummary *) mbs, store, fi->full_name, NULL)) {
150 			fi->unread = camel_folder_summary_get_unread_count (
151 				(CamelFolderSummary *) mbs);
152 			fi->total = camel_folder_summary_get_saved_count (
153 				(CamelFolderSummary *) mbs);
154 		}
155 
156 		g_object_unref (mbs);
157 		g_free (folderpath);
158 	}
159 
160 	if (camel_local_store_is_main_store (CAMEL_LOCAL_STORE (store)) && fi->full_name
161 	    && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
162 		fi->flags =
163 			(fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
164 			camel_local_store_get_folder_type_by_full_name (
165 			CAMEL_LOCAL_STORE (store), fi->full_name);
166 }
167 
168 static CamelFolderInfo *
scan_dir(CamelStore * store,GHashTable * visited,CamelFolderInfo * parent,const gchar * root,const gchar * name,guint32 flags,GError ** error)169 scan_dir (CamelStore *store,
170           GHashTable *visited,
171           CamelFolderInfo *parent,
172           const gchar *root,
173           const gchar *name,
174           guint32 flags,
175           GError **error)
176 {
177 	CamelFolderInfo *folders, *tail, *fi;
178 	GHashTable *folder_hash;
179 	const gchar *dent;
180 	GDir *dir;
181 
182 	tail = folders = NULL;
183 
184 	if (!(dir = g_dir_open (root, 0, NULL)))
185 		return NULL;
186 
187 	folder_hash = g_hash_table_new (g_str_hash, g_str_equal);
188 
189 	/* FIXME: it would be better if we queue'd up the recursive
190 	 * scans till the end so that we can limit the number of
191 	 * directory descriptors open at any given time... */
192 
193 	while ((dent = g_dir_read_name (dir))) {
194 		gchar *short_name, *full_name, *path, *ext;
195 		struct stat st;
196 
197 		if (dent[0] == '.')
198 			continue;
199 
200 		if (ignore_file (dent, FALSE))
201 			continue;
202 
203 		path = g_strdup_printf ("%s/%s", root, dent);
204 		if (g_stat (path, &st) == -1) {
205 			g_free (path);
206 			continue;
207 		}
208 #ifndef G_OS_WIN32
209 		if (S_ISDIR (st.st_mode)) {
210 			struct _inode in = { st.st_dev, st.st_ino };
211 
212 			if (g_hash_table_lookup (visited, &in)) {
213 				g_free (path);
214 				continue;
215 			}
216 		}
217 #endif
218 		short_name = g_strdup (dent);
219 		if ((ext = strrchr (short_name, '.')) && !strcmp (ext, ".sbd"))
220 			*ext = '\0';
221 
222 		if (name != NULL)
223 			full_name = g_strdup_printf ("%s/%s", name, short_name);
224 		else
225 			full_name = g_strdup (short_name);
226 
227 		if ((fi = g_hash_table_lookup (folder_hash, short_name)) != NULL) {
228 			g_free (short_name);
229 			g_free (full_name);
230 
231 			if (S_ISDIR (st.st_mode)) {
232 				fi->flags = (fi->flags & ~CAMEL_FOLDER_NOCHILDREN) | CAMEL_FOLDER_CHILDREN;
233 			} else {
234 				fi->flags &= ~CAMEL_FOLDER_NOSELECT;
235 			}
236 		} else {
237 			fi = camel_folder_info_new ();
238 			fi->parent = parent;
239 
240 			fi->full_name = full_name;
241 			fi->display_name = short_name;
242 			fi->unread = -1;
243 			fi->total = -1;
244 
245 			if (S_ISDIR (st.st_mode))
246 				fi->flags = CAMEL_FOLDER_NOSELECT;
247 			else
248 				fi->flags = CAMEL_FOLDER_NOCHILDREN;
249 
250 			if (tail == NULL)
251 				folders = fi;
252 			else
253 				tail->next = fi;
254 
255 			tail = fi;
256 
257 			g_hash_table_insert (folder_hash, fi->display_name, fi);
258 		}
259 
260 		if (!S_ISDIR (st.st_mode)) {
261 			fill_fi (store, fi, flags);
262 		} else if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)) {
263 			struct _inode in = { st.st_dev, st.st_ino };
264 
265 			if (g_hash_table_lookup (visited, &in) == NULL) {
266 #ifndef G_OS_WIN32
267 				struct _inode *inew = inode_new (&in);
268 
269 				g_hash_table_insert (visited, inew, inew);
270 #endif
271 				if ((fi->child = scan_dir (store, visited, fi, path, fi->full_name, flags, error)))
272 					fi->flags |= CAMEL_FOLDER_CHILDREN;
273 				else
274 					fi->flags = (fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
275 			}
276 		}
277 
278 		g_free (path);
279 	}
280 
281 	g_dir_close (dir);
282 
283 	g_hash_table_destroy (folder_hash);
284 
285 	return folders;
286 }
287 
288 static gint
xrename(CamelStore * store,const gchar * old_name,const gchar * new_name,const gchar * ext,gboolean missingok)289 xrename (CamelStore *store,
290          const gchar *old_name,
291          const gchar *new_name,
292          const gchar *ext,
293          gboolean missingok)
294 {
295 	CamelLocalStore *ls = (CamelLocalStore *) store;
296 	gchar *oldpath, *newpath;
297 	struct stat st;
298 	gint ret = -1;
299 
300 	if (ext != NULL) {
301 		oldpath = camel_local_store_get_meta_path (ls, old_name, ext);
302 		newpath = camel_local_store_get_meta_path (ls, new_name, ext);
303 	} else {
304 		oldpath = camel_local_store_get_full_path (ls, old_name);
305 		newpath = camel_local_store_get_full_path (ls, new_name);
306 	}
307 
308 	if (g_stat (oldpath, &st) == -1) {
309 		if (missingok && errno == ENOENT) {
310 			ret = 0;
311 		} else {
312 			ret = -1;
313 		}
314 #ifndef G_OS_WIN32
315 	} else if (S_ISDIR (st.st_mode)) {
316 		/* use rename for dirs */
317 		if (g_rename (oldpath, newpath) == 0 || g_stat (newpath, &st) == 0) {
318 			ret = 0;
319 		} else {
320 			ret = -1;
321 		}
322 	} else if (link (oldpath, newpath) == 0 /* and link for files */
323 		   ||(g_stat (newpath, &st) == 0 && st.st_nlink == 2)) {
324 		if (unlink (oldpath) == 0) {
325 			ret = 0;
326 		} else {
327 			unlink (newpath);
328 			ret = -1;
329 		}
330 	} else {
331 		ret = -1;
332 #else
333 	} else if ((!g_file_test (newpath, G_FILE_TEST_EXISTS) || g_remove (newpath) == 0) &&
334 		   g_rename (oldpath, newpath) == 0) {
335 		ret = 0;
336 	} else {
337 		ret = -1;
338 #endif
339 	}
340 
341 	g_free (oldpath);
342 	g_free (newpath);
343 
344 	return ret;
345 }
346 
347 static CamelFolder *
mbox_store_get_folder_sync(CamelStore * store,const gchar * folder_name,CamelStoreGetFolderFlags flags,GCancellable * cancellable,GError ** error)348 mbox_store_get_folder_sync (CamelStore *store,
349                             const gchar *folder_name,
350                             CamelStoreGetFolderFlags flags,
351                             GCancellable *cancellable,
352                             GError **error)
353 {
354 	CamelStoreClass *store_class;
355 	CamelLocalStore *local_store;
356 	struct stat st;
357 	gchar *name;
358 
359 	/* Chain up to parent's get_folder_sync() method. */
360 	store_class = CAMEL_STORE_CLASS (camel_mbox_store_parent_class);
361 	if (!store_class->get_folder_sync (store, folder_name, flags, cancellable, error))
362 		return NULL;
363 
364 	local_store = CAMEL_LOCAL_STORE (store);
365 	name = camel_local_store_get_full_path (local_store, folder_name);
366 
367 	if (g_stat (name, &st) == -1) {
368 		gchar *basename;
369 		gchar *dirname;
370 		gint fd;
371 
372 		if (errno != ENOENT) {
373 			g_set_error (
374 				error, G_IO_ERROR,
375 				g_io_error_from_errno (errno),
376 				_("Cannot get folder “%s”: %s"),
377 				folder_name, g_strerror (errno));
378 			g_free (name);
379 			return NULL;
380 		}
381 
382 		if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
383 			g_set_error (
384 				error, CAMEL_STORE_ERROR,
385 				CAMEL_STORE_ERROR_NO_FOLDER,
386 				_("Cannot get folder “%s”: folder does not exist."),
387 				folder_name);
388 			g_free (name);
389 			return NULL;
390 		}
391 
392 		/* sanity check the folder name */
393 		basename = g_path_get_basename (folder_name);
394 
395 		if (basename[0] == '.' || ignore_file (basename, TRUE)) {
396 			g_set_error (
397 				error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
398 				_("Cannot create a folder by this name."));
399 			g_free (name);
400 			g_free (basename);
401 			return NULL;
402 		}
403 		g_free (basename);
404 
405 		dirname = g_path_get_dirname (name);
406 		if (g_mkdir_with_parents (dirname, 0700) == -1 && errno != EEXIST) {
407 			g_set_error (
408 				error, G_IO_ERROR,
409 				g_io_error_from_errno (errno),
410 				_("Cannot create folder “%s”: %s"),
411 				folder_name, g_strerror (errno));
412 			g_free (dirname);
413 			g_free (name);
414 			return NULL;
415 		}
416 
417 		g_free (dirname);
418 
419 		fd = g_open (
420 			name,
421 			O_LARGEFILE |
422 			O_WRONLY |
423 			O_CREAT |
424 			O_APPEND |
425 			O_BINARY, 0666);
426 
427 		if (fd == -1) {
428 			g_set_error (
429 				error, G_IO_ERROR,
430 				g_io_error_from_errno (errno),
431 				_("Cannot create folder “%s”: %s"),
432 				folder_name, g_strerror (errno));
433 			g_free (name);
434 			return NULL;
435 		}
436 
437 		g_free (name);
438 		close (fd);
439 	} else if (!S_ISREG (st.st_mode)) {
440 		g_set_error (
441 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
442 			_("Cannot get folder “%s”: not a regular file."),
443 			folder_name);
444 		g_free (name);
445 		return NULL;
446 	} else
447 		g_free (name);
448 
449 	return camel_mbox_folder_new (store, folder_name, flags, cancellable, error);
450 }
451 
452 static CamelFolderInfo *
mbox_store_get_folder_info_sync(CamelStore * store,const gchar * top,CamelStoreGetFolderInfoFlags flags,GCancellable * cancellable,GError ** error)453 mbox_store_get_folder_info_sync (CamelStore *store,
454                                  const gchar *top,
455                                  CamelStoreGetFolderInfoFlags flags,
456                                  GCancellable *cancellable,
457                                  GError **error)
458 {
459 	CamelLocalStore *local_store;
460 	GHashTable *visited;
461 #ifndef G_OS_WIN32
462 	struct _inode *inode;
463 #endif
464 	gchar *path, *subdir;
465 	CamelFolderInfo *fi;
466 	gchar *basename;
467 	struct stat st;
468 
469 	if (top == NULL)
470 		top = "";
471 
472 	local_store = CAMEL_LOCAL_STORE (store);
473 	path = camel_local_store_get_full_path (local_store, top);
474 
475 	if (*top == '\0') {
476 		/* requesting root dir scan */
477 		if (g_stat (path, &st) == -1 || !S_ISDIR (st.st_mode)) {
478 			g_free (path);
479 			return NULL;
480 		}
481 
482 		visited = g_hash_table_new_full (inode_hash, inode_equal, inode_free, NULL);
483 #ifndef G_OS_WIN32
484 		inode = inode_new (NULL);
485 		inode->dnode = st.st_dev;
486 		inode->inode = st.st_ino;
487 
488 		g_hash_table_insert (visited, inode, inode);
489 #endif
490 		fi = scan_dir (store, visited, NULL, path, NULL, flags, error);
491 		g_hash_table_destroy (visited);
492 		g_free (path);
493 
494 		return fi;
495 	}
496 
497 	/* requesting scan of specific folder */
498 	if (g_stat (path, &st) == -1 || !S_ISREG (st.st_mode)) {
499 		gchar *test_if_subdir = g_strdup_printf ("%s.sbd", path);
500 
501 		if (g_stat (test_if_subdir, &st) == -1) {
502 			g_free (path);
503 			g_free (test_if_subdir);
504 			return NULL;
505 		}
506 		g_free (test_if_subdir);
507 	}
508 
509 	visited = g_hash_table_new_full (inode_hash, inode_equal, inode_free, NULL);
510 
511 	basename = g_path_get_basename (top);
512 
513 	fi = camel_folder_info_new ();
514 	fi->parent = NULL;
515 	fi->full_name = g_strdup (top);
516 	fi->display_name = basename;
517 	fi->unread = -1;
518 	fi->total = -1;
519 
520 	fill_fi (store, fi, flags);
521 
522 	subdir = g_strdup_printf ("%s.sbd", path);
523 	if (g_stat (subdir, &st) == 0) {
524 		if  (S_ISDIR (st.st_mode))
525 			fi->child = scan_dir (store, visited, fi, subdir, top, flags, error);
526 	}
527 
528 	if (fi->child)
529 		fi->flags |= CAMEL_FOLDER_CHILDREN;
530 	else
531 		fi->flags |= CAMEL_FOLDER_NOCHILDREN;
532 
533 	g_free (subdir);
534 
535 	g_hash_table_destroy (visited);
536 	g_free (path);
537 
538 	return fi;
539 }
540 
541 static CamelFolderInfo *
mbox_store_create_folder_sync(CamelStore * store,const gchar * parent_name,const gchar * folder_name,GCancellable * cancellable,GError ** error)542 mbox_store_create_folder_sync (CamelStore *store,
543                                const gchar *parent_name,
544                                const gchar *folder_name,
545                                GCancellable *cancellable,
546                                GError **error)
547 {
548 	/* FIXME This is almost an exact copy of
549 	 *       CamelLocalStore::create_folder() except that we use
550 	 *       different path schemes - need to find a way to share
551 	 *       parent's code? */
552 	CamelLocalSettings *local_settings;
553 	CamelLocalStore *local_store;
554 	CamelFolderInfo *info = NULL;
555 	CamelSettings *settings;
556 	CamelService *service;
557 	gchar *root_path = NULL;
558 	gchar *name = NULL;
559 	gchar *path = NULL;
560 	gchar *dir;
561 	CamelFolder *folder;
562 	struct stat st;
563 
564 	service = CAMEL_SERVICE (store);
565 
566 	settings = camel_service_ref_settings (service);
567 
568 	local_settings = CAMEL_LOCAL_SETTINGS (settings);
569 	root_path = camel_local_settings_dup_path (local_settings);
570 
571 	g_object_unref (settings);
572 
573 	local_store = CAMEL_LOCAL_STORE (store);
574 
575 	if (!g_path_is_absolute (root_path)) {
576 		g_set_error (
577 			error, CAMEL_STORE_ERROR,
578 			CAMEL_STORE_ERROR_NO_FOLDER,
579 			_("Store root %s is not an absolute path"),
580 			root_path);
581 		goto exit;
582 	}
583 
584 	if (folder_name[0] == '.' || ignore_file (folder_name, TRUE)) {
585 		g_set_error (
586 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
587 			_("Cannot create a folder by this name."));
588 		goto exit;
589 	}
590 
591 	if (parent_name && *parent_name)
592 		name = g_strdup_printf ("%s/%s", parent_name, folder_name);
593 	else
594 		name = g_strdup (folder_name);
595 
596 	path = camel_local_store_get_full_path (local_store, name);
597 
598 	dir = g_path_get_dirname (path);
599 	if (g_mkdir_with_parents (dir, 0777) == -1 && errno != EEXIST) {
600 		g_set_error (
601 			error, G_IO_ERROR,
602 			g_io_error_from_errno (errno),
603 			_("Cannot create directory “%s”: %s."),
604 			dir, g_strerror (errno));
605 		g_free (dir);
606 		goto exit;
607 	}
608 
609 	g_free (dir);
610 
611 	if (g_stat (path, &st) == 0 || errno != ENOENT) {
612 		g_set_error (
613 			error, G_IO_ERROR,
614 			g_io_error_from_errno (errno),
615 			_("Cannot create folder: %s: %s"),
616 			path, errno ? g_strerror (errno) :
617 			_("Folder already exists"));
618 		goto exit;
619 	}
620 
621 	folder = CAMEL_STORE_GET_CLASS (store)->get_folder_sync (
622 		store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
623 	if (folder) {
624 		g_object_unref (folder);
625 		info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
626 			store, name, 0, cancellable, error);
627 	}
628 
629 exit:
630 	g_free (root_path);
631 	g_free (name);
632 	g_free (path);
633 
634 	return info;
635 }
636 
637 static gboolean
mbox_store_delete_folder_sync(CamelStore * store,const gchar * folder_name,GCancellable * cancellable,GError ** error)638 mbox_store_delete_folder_sync (CamelStore *store,
639                                const gchar *folder_name,
640                                GCancellable *cancellable,
641                                GError **error)
642 {
643 	CamelLocalStore *local_store;
644 	CamelFolderInfo *fi;
645 	CamelFolder *lf;
646 	gchar *name, *path;
647 	struct stat st;
648 
649 	local_store = CAMEL_LOCAL_STORE (store);
650 	name = camel_local_store_get_full_path (local_store, folder_name);
651 	path = g_strdup_printf ("%s.sbd", name);
652 
653 	if (g_rmdir (path) == -1 && errno != ENOENT) {
654 		g_set_error (
655 			error, G_IO_ERROR,
656 			g_io_error_from_errno (errno),
657 			_("Could not delete folder “%s”:\n%s"),
658 			folder_name, g_strerror (errno));
659 		g_free (path);
660 		g_free (name);
661 		return FALSE;
662 	}
663 
664 	g_free (path);
665 
666 	if (g_stat (name, &st) == -1) {
667 		g_set_error (
668 			error, G_IO_ERROR,
669 			g_io_error_from_errno (errno),
670 			_("Could not delete folder “%s”:\n%s"),
671 			folder_name, g_strerror (errno));
672 		g_free (name);
673 		return FALSE;
674 	}
675 
676 	if (!S_ISREG (st.st_mode)) {
677 		g_set_error (
678 			error, CAMEL_STORE_ERROR,
679 			CAMEL_STORE_ERROR_NO_FOLDER,
680 			_("“%s” is not a regular file."), name);
681 		g_free (name);
682 		return FALSE;
683 	}
684 
685 	if (st.st_size != 0) {
686 		g_set_error (
687 			error, CAMEL_FOLDER_ERROR,
688 			CAMEL_FOLDER_ERROR_NON_EMPTY,
689 			_("Folder “%s” is not empty. Not deleted."),
690 			folder_name);
691 		g_free (name);
692 		return FALSE;
693 	}
694 
695 	if (g_unlink (name) == -1 && errno != ENOENT) {
696 		g_set_error (
697 			error, G_IO_ERROR,
698 			g_io_error_from_errno (errno),
699 			_("Could not delete folder “%s”:\n%s"),
700 			name, g_strerror (errno));
701 		g_free (name);
702 		return FALSE;
703 	}
704 
705 	/* FIXME: we have to do our own meta cleanup here rather than
706 	 * calling our parent class' delete_folder() method since our
707 	 * naming convention is different. Need to find a way for
708 	 * CamelLocalStore to be able to construct the folder & meta
709 	 * paths itself */
710 	path = camel_local_store_get_meta_path (
711 		local_store, folder_name, ".ev-summary");
712 	if (g_unlink (path) == -1 && errno != ENOENT) {
713 		g_set_error (
714 			error, G_IO_ERROR,
715 			g_io_error_from_errno (errno),
716 			_("Could not delete folder summary file “%s”: %s"),
717 			path, g_strerror (errno));
718 		g_free (path);
719 		g_free (name);
720 		return FALSE;
721 	}
722 
723 	g_free (path);
724 
725 	path = camel_local_store_get_meta_path (
726 		local_store, folder_name, ".ev-summary-meta");
727 	if (g_unlink (path) == -1 && errno != ENOENT) {
728 		g_set_error (
729 			error, G_IO_ERROR,
730 			g_io_error_from_errno (errno),
731 			_("Could not delete folder summary file “%s”: %s"),
732 			path, g_strerror (errno));
733 		g_free (path);
734 		g_free (name);
735 		return FALSE;
736 	}
737 
738 	g_free (path);
739 
740 	path = camel_local_store_get_meta_path (
741 		local_store, folder_name, ".ibex");
742 	if (camel_text_index_remove (path) == -1 && errno != ENOENT) {
743 		g_set_error (
744 			error, G_IO_ERROR,
745 			g_io_error_from_errno (errno),
746 			_("Could not delete folder index file “%s”: %s"),
747 			path, g_strerror (errno));
748 		g_free (path);
749 		g_free (name);
750 		return FALSE;
751 	}
752 
753 	g_free (path);
754 
755 	path = NULL;
756 	if ((lf = camel_store_get_folder_sync (store, folder_name, 0, cancellable, NULL))) {
757 		CamelObject *object = CAMEL_OBJECT (lf);
758 		const gchar *state_filename;
759 
760 		state_filename = camel_object_get_state_filename (object);
761 		path = g_strdup (state_filename);
762 
763 		camel_object_set_state_filename (object, NULL);
764 
765 		g_object_unref (lf);
766 	}
767 
768 	if (path == NULL)
769 		path = camel_local_store_get_meta_path (
770 			local_store, folder_name, ".cmeta");
771 
772 	if (g_unlink (path) == -1 && errno != ENOENT) {
773 		g_set_error (
774 			error, G_IO_ERROR,
775 			g_io_error_from_errno (errno),
776 			_("Could not delete folder meta file “%s”: %s"),
777 			path, g_strerror (errno));
778 
779 		g_free (path);
780 		g_free (name);
781 		return FALSE;
782 	}
783 
784 	g_free (path);
785 	g_free (name);
786 
787 	fi = camel_folder_info_new ();
788 	fi->full_name = g_strdup (folder_name);
789 	fi->display_name = g_path_get_basename (folder_name);
790 	fi->unread = -1;
791 
792 	camel_store_folder_deleted (store, fi);
793 	camel_folder_info_free (fi);
794 
795 	return TRUE;
796 }
797 
798 static gboolean
mbox_store_rename_folder_sync(CamelStore * store,const gchar * old,const gchar * new,GCancellable * cancellable,GError ** error)799 mbox_store_rename_folder_sync (CamelStore *store,
800                                const gchar *old,
801                                const gchar *new,
802                                GCancellable *cancellable,
803                                GError **error)
804 {
805 	CamelLocalStore *local_store;
806 	CamelLocalFolder *folder = NULL;
807 	gchar *oldibex, *newibex, *newdir;
808 	gint errnosav;
809 
810 	if (new[0] == '.' || ignore_file (new, TRUE)) {
811 		g_set_error (
812 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
813 			_("The new folder name is illegal."));
814 		return FALSE;
815 	}
816 
817 	/* try to rollback failures, has obvious races */
818 
819 	local_store = CAMEL_LOCAL_STORE (store);
820 	oldibex = camel_local_store_get_meta_path (local_store, old, ".ibex");
821 	newibex = camel_local_store_get_meta_path (local_store, new, ".ibex");
822 
823 	newdir = g_path_get_dirname (newibex);
824 	if (g_mkdir_with_parents (newdir, 0700) == -1) {
825 		if (errno != EEXIST) {
826 			g_set_error (
827 				error, G_IO_ERROR,
828 				g_io_error_from_errno (errno),
829 				_("Could not rename “%s”: “%s”: %s"),
830 				old, new, g_strerror (errno));
831 			g_free (oldibex);
832 			g_free (newibex);
833 			g_free (newdir);
834 
835 			return FALSE;
836 		}
837 
838 		g_free (newdir);
839 		newdir = NULL;
840 	}
841 
842 	folder = camel_object_bag_get (camel_store_get_folders_bag (store), old);
843 	if (folder && folder->index) {
844 		if (camel_index_rename (folder->index, newibex) == -1 && errno != ENOENT) {
845 			errnosav = errno;
846 			goto ibex_failed;
847 		}
848 	} else {
849 		/* TODO camel_text_index_rename should find out
850 		 *      if we have an active index itself? */
851 		if (camel_text_index_rename (oldibex, newibex) == -1 && errno != ENOENT) {
852 			errnosav = errno;
853 			goto ibex_failed;
854 		}
855 	}
856 
857 	if (xrename (store, old, new, ".ev-summary", TRUE) == -1) {
858 		errnosav = errno;
859 		goto summary_failed;
860 	}
861 
862 	if (xrename (store, old, new, ".ev-summary-meta", TRUE) == -1) {
863 		errnosav = errno;
864 		goto summary_failed;
865 	}
866 
867 	if (xrename (store, old, new, ".cmeta", TRUE) == -1) {
868 		errnosav = errno;
869 		goto cmeta_failed;
870 	}
871 
872 	if (xrename (store, old, new, ".sbd", TRUE) == -1) {
873 		errnosav = errno;
874 		goto subdir_failed;
875 	}
876 
877 	if (xrename (store, old, new, NULL, FALSE) == -1) {
878 		errnosav = errno;
879 		goto base_failed;
880 	}
881 
882 	g_free (oldibex);
883 	g_free (newibex);
884 
885 	if (folder)
886 		g_object_unref (folder);
887 
888 	return TRUE;
889 
890 base_failed:
891 	xrename (store, new, old, ".sbd", TRUE);
892 subdir_failed:
893 	xrename (store, new, old, ".cmeta", TRUE);
894 cmeta_failed:
895 	xrename (store, new, old, ".ev-summary", TRUE);
896 	xrename (store, new, old, ".ev-summary-meta", TRUE);
897 summary_failed:
898 	if (folder) {
899 		if (folder->index)
900 			camel_index_rename (folder->index, oldibex);
901 	} else
902 		camel_text_index_rename (newibex, oldibex);
903 ibex_failed:
904 	if (newdir) {
905 		/* newdir is only non-NULL if we needed to mkdir */
906 		g_rmdir (newdir);
907 		g_free (newdir);
908 	}
909 
910 	g_set_error (
911 		error, G_IO_ERROR,
912 		g_io_error_from_errno (errnosav),
913 		_("Could not rename “%s” to %s: %s"),
914 		old, new, g_strerror (errnosav));
915 
916 	g_free (newibex);
917 	g_free (oldibex);
918 
919 	if (folder)
920 		g_object_unref (folder);
921 
922 	return FALSE;
923 }
924 
925 static gchar *
mbox_store_get_full_path(CamelLocalStore * ls,const gchar * full_name)926 mbox_store_get_full_path (CamelLocalStore *ls,
927                           const gchar *full_name)
928 {
929 	CamelLocalSettings *local_settings;
930 	CamelSettings *settings;
931 	CamelService *service;
932 	GString *full_path;
933 	gchar *root_path;
934 	const gchar *cp;
935 
936 	service = CAMEL_SERVICE (ls);
937 
938 	settings = camel_service_ref_settings (service);
939 
940 	local_settings = CAMEL_LOCAL_SETTINGS (settings);
941 	root_path = camel_local_settings_dup_path (local_settings);
942 
943 	g_object_unref (settings);
944 
945 	g_return_val_if_fail (root_path != NULL, NULL);
946 
947 	full_path = g_string_new (root_path);
948 
949 	/* Root path may or may not have a trailing separator. */
950 	if (!g_str_has_suffix (root_path, G_DIR_SEPARATOR_S))
951 		g_string_append_c (full_path, G_DIR_SEPARATOR);
952 
953 	cp = full_name;
954 	while (*cp != '\0') {
955 		if (G_IS_DIR_SEPARATOR (*cp)) {
956 			g_string_append (full_path, ".sbd");
957 			g_string_append_c (full_path, *cp++);
958 
959 			/* Skip extranaeous separators. */
960 			while (G_IS_DIR_SEPARATOR (*cp))
961 				cp++;
962 		} else {
963 			g_string_append_c (full_path, *cp++);
964 		}
965 	}
966 
967 	g_free (root_path);
968 
969 	return g_string_free (full_path, FALSE);
970 }
971 
972 static gchar *
mbox_store_get_meta_path(CamelLocalStore * ls,const gchar * full_name,const gchar * ext)973 mbox_store_get_meta_path (CamelLocalStore *ls,
974                           const gchar *full_name,
975                           const gchar *ext)
976 {
977 /*#define USE_HIDDEN_META_FILES*/
978 #ifdef USE_HIDDEN_META_FILES
979 	gchar *name, *slash;
980 	gsize name_len;
981 
982 	name_len = strlen (full_name) + strlen (ext) + 2;
983 	name = g_alloca (name_len);
984 	if ((slash = strrchr (full_name, '/')))
985 		g_snprintf (
986 			name, name_len, "%.*s.%s%s",
987 			slash - full_name + 1,
988 			full_name, slash + 1, ext);
989 	else
990 		g_snprintf (name, name_len, ".%s%s", full_name, ext);
991 
992 	return mbox_store_get_full_path (ls, name);
993 #else
994 	gchar *full_path, *path;
995 
996 	full_path = mbox_store_get_full_path (ls, full_name);
997 	path = g_strdup_printf ("%s%s", full_path, ext);
998 	g_free (full_path);
999 
1000 	return path;
1001 #endif
1002 }
1003 
1004 static void
camel_mbox_store_class_init(CamelMboxStoreClass * class)1005 camel_mbox_store_class_init (CamelMboxStoreClass *class)
1006 {
1007 	CamelStoreClass *store_class;
1008 	CamelLocalStoreClass *local_store_class;
1009 
1010 	store_class = CAMEL_STORE_CLASS (class);
1011 	store_class->get_folder_sync = mbox_store_get_folder_sync;
1012 	store_class->get_folder_info_sync = mbox_store_get_folder_info_sync;
1013 	store_class->create_folder_sync = mbox_store_create_folder_sync;
1014 	store_class->delete_folder_sync = mbox_store_delete_folder_sync;
1015 	store_class->rename_folder_sync = mbox_store_rename_folder_sync;
1016 
1017 	local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
1018 	local_store_class->get_full_path = mbox_store_get_full_path;
1019 	local_store_class->get_meta_path = mbox_store_get_meta_path;
1020 }
1021 
1022 static void
camel_mbox_store_init(CamelMboxStore * mbox_store)1023 camel_mbox_store_init (CamelMboxStore *mbox_store)
1024 {
1025 }
1026 
1027