1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
15  */
16 
17 #include "evolution-config.h"
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <errno.h>
23 
24 #include <gtk/gtk.h>
25 
26 #include <libedataserver/libedataserver.h>
27 
28 #include "mail-mt.h"
29 
30 /*#define MALLOC_CHECK*/
31 #define d(x)
32 
33 static guint mail_msg_seq; /* sequence number of each message */
34 
35 /* Table of active messages.  Must hold mail_msg_lock to access. */
36 static GHashTable *mail_msg_active_table;
37 static GMutex mail_msg_lock;
38 static GCond mail_msg_cond;
39 
40 static MailMsgCreateActivityFunc create_activity = NULL;
41 static MailMsgSubmitActivityFunc submit_activity = NULL;
42 static MailMsgFreeActivityFunc free_activity = NULL;
43 static MailMsgCompleteActivityFunc complete_activity = NULL;
44 static MailMsgAlertErrorFunc alert_error = NULL;
45 static MailMsgCancelActivityFunc cancel_activity = NULL;
46 static MailMsgGetAlertSinkFunc get_alert_sink = NULL;
47 
48 void
mail_msg_register_activities(MailMsgCreateActivityFunc acreate,MailMsgSubmitActivityFunc asubmit,MailMsgFreeActivityFunc freeact,MailMsgCompleteActivityFunc comp_act,MailMsgCancelActivityFunc cancel_act,MailMsgAlertErrorFunc ealert,MailMsgGetAlertSinkFunc ealertsink)49 mail_msg_register_activities (MailMsgCreateActivityFunc acreate,
50                               MailMsgSubmitActivityFunc asubmit,
51                               MailMsgFreeActivityFunc freeact,
52                               MailMsgCompleteActivityFunc comp_act,
53                               MailMsgCancelActivityFunc cancel_act,
54                               MailMsgAlertErrorFunc ealert,
55                               MailMsgGetAlertSinkFunc ealertsink)
56 {
57 	/* XXX This is an utter hack to keep EActivity out
58 	 *     of EDS and still let Evolution do EActivity. */
59 	create_activity = acreate;
60 	submit_activity = asubmit;
61 	free_activity = freeact;
62 	complete_activity = comp_act;
63 	cancel_activity = cancel_act;
64 	alert_error = ealert;
65 	get_alert_sink = ealertsink;
66 }
67 
68 EAlertSink *
mail_msg_get_alert_sink()69 mail_msg_get_alert_sink ()
70 {
71 	if (get_alert_sink)
72 		return get_alert_sink ();
73 
74 	return NULL;
75 }
76 
77 static void
mail_msg_cancelled(CamelOperation * operation,gpointer user_data)78 mail_msg_cancelled (CamelOperation *operation,
79                     gpointer user_data)
80 {
81 	mail_msg_cancel (GPOINTER_TO_UINT (user_data));
82 }
83 
84 static gboolean
mail_msg_submit(CamelOperation * cancellable)85 mail_msg_submit (CamelOperation *cancellable)
86 {
87 
88 	if (submit_activity)
89 		submit_activity ((GCancellable *) cancellable);
90 	return FALSE;
91 }
92 
93 gpointer
mail_msg_new_with_cancellable(MailMsgInfo * info,GCancellable * cancellable)94 mail_msg_new_with_cancellable (MailMsgInfo *info,
95 			       GCancellable *cancellable)
96 {
97 	MailMsg *msg;
98 
99 	g_mutex_lock (&mail_msg_lock);
100 
101 	msg = g_slice_alloc0 (info->size);
102 	msg->info = info;
103 	msg->ref_count = 1;
104 	msg->seq = mail_msg_seq++;
105 
106 	if (cancellable)
107 		msg->cancellable = g_object_ref (cancellable);
108 	else
109 		msg->cancellable = camel_operation_new ();
110 
111 	if (create_activity)
112 		create_activity (msg->cancellable);
113 
114 	g_signal_connect (
115 		msg->cancellable, "cancelled",
116 		G_CALLBACK (mail_msg_cancelled),
117 		GINT_TO_POINTER (msg->seq));
118 
119 	g_hash_table_insert (
120 		mail_msg_active_table, GINT_TO_POINTER (msg->seq), msg);
121 
122 	d (printf ("New message %p\n", msg));
123 
124 	g_mutex_unlock (&mail_msg_lock);
125 
126 	return msg;
127 }
128 
129 gpointer
mail_msg_new(MailMsgInfo * info)130 mail_msg_new (MailMsgInfo *info)
131 {
132 	return mail_msg_new_with_cancellable (info, NULL);
133 }
134 
135 #ifdef MALLOC_CHECK
136 #include <mcheck.h>
137 
138 static void
checkmem(gpointer p)139 checkmem (gpointer p)
140 {
141 	if (p) {
142 		gint status = mprobe (p);
143 
144 		switch (status) {
145 		case MCHECK_HEAD:
146 			printf ("Memory underrun at %p\n", p);
147 			abort ();
148 		case MCHECK_TAIL:
149 			printf ("Memory overrun at %p\n", p);
150 			abort ();
151 		case MCHECK_FREE:
152 			printf ("Double free %p\n", p);
153 			abort ();
154 		}
155 	}
156 }
157 #endif
158 
159 static gboolean
mail_msg_free(MailMsg * mail_msg)160 mail_msg_free (MailMsg *mail_msg)
161 {
162 	/* This is an idle callback. */
163 
164 	if (free_activity)
165 		free_activity (mail_msg->cancellable);
166 
167 	if (mail_msg->cancellable != NULL)
168 		g_object_unref (mail_msg->cancellable);
169 
170 	if (mail_msg->error != NULL)
171 		g_error_free (mail_msg->error);
172 
173 	g_slice_free1 (mail_msg->info->size, mail_msg);
174 
175 	return FALSE;
176 }
177 
178 gpointer
mail_msg_ref(gpointer msg)179 mail_msg_ref (gpointer msg)
180 {
181 	MailMsg *mail_msg = msg;
182 
183 	g_return_val_if_fail (mail_msg != NULL, msg);
184 	g_return_val_if_fail (mail_msg->ref_count > 0, msg);
185 
186 	g_atomic_int_inc (&mail_msg->ref_count);
187 
188 	return msg;
189 }
190 
191 void
mail_msg_unref(gpointer msg)192 mail_msg_unref (gpointer msg)
193 {
194 	MailMsg *mail_msg = msg;
195 
196 	g_return_if_fail (mail_msg != NULL);
197 	g_return_if_fail (mail_msg->ref_count > 0);
198 
199 	if (g_atomic_int_dec_and_test (&mail_msg->ref_count)) {
200 
201 #ifdef MALLOC_CHECK
202 		checkmem (mail_msg);
203 		checkmem (mail_msg->cancel);
204 		checkmem (mail_msg->priv);
205 #endif
206 		d (printf ("Free message %p\n", msg));
207 
208 		if (mail_msg->info->free)
209 			mail_msg->info->free (mail_msg);
210 
211 		g_mutex_lock (&mail_msg_lock);
212 
213 		g_hash_table_remove (
214 			mail_msg_active_table,
215 			GINT_TO_POINTER (mail_msg->seq));
216 		g_cond_broadcast (&mail_msg_cond);
217 
218 		g_mutex_unlock (&mail_msg_lock);
219 
220 		/* Destroy the message from an idle callback
221 		 * so we know we're in the main loop thread.
222 		 * Prioritize ahead of GTK+ redraws. */
223 		g_idle_add_full (
224 			G_PRIORITY_HIGH_IDLE,
225 			(GSourceFunc) mail_msg_free, mail_msg, NULL);
226 	}
227 }
228 
229 void
mail_msg_check_error(gpointer msg)230 mail_msg_check_error (gpointer msg)
231 {
232 	MailMsg *m = msg;
233 
234 #ifdef MALLOC_CHECK
235 	checkmem (m);
236 	checkmem (m->cancel);
237 	checkmem (m->priv);
238 #endif
239 
240 	if (m->error == NULL)
241 		return;
242 
243 	if (complete_activity)
244 		complete_activity (m->cancellable);
245 
246 	if (g_error_matches (m->error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
247 		if (cancel_activity)
248 			cancel_activity (m->cancellable);
249 		return;
250 	}
251 
252 	/* XXX Hmm, no explanation of why this is needed.  It looks like
253 	 *     a lame hack and will be removed at some point, if only to
254 	 *     reintroduce whatever issue made this necessary so we can
255 	 *     document it in the source code this time. */
256 	if (g_error_matches (
257 		m->error, CAMEL_FOLDER_ERROR,
258 		CAMEL_FOLDER_ERROR_INVALID_UID))
259 		return;
260 
261 	/* FIXME: Submit an error on the dbus */
262 	if (alert_error) {
263 		gchar *what;
264 
265 		if (m->info->desc && (what = m->info->desc (m))) {
266 			alert_error (m->cancellable, what, m->error->message);
267 			g_free (what);
268 		} else
269 			alert_error (m->cancellable, NULL, m->error->message);
270 	}
271 }
272 
273 void
mail_msg_cancel(guint msgid)274 mail_msg_cancel (guint msgid)
275 {
276 	MailMsg *msg;
277 	GCancellable *cancellable = NULL;
278 
279 	g_mutex_lock (&mail_msg_lock);
280 
281 	msg = g_hash_table_lookup (
282 		mail_msg_active_table, GINT_TO_POINTER (msgid));
283 
284 	/* Hold a reference to the GCancellable so it doesn't finalize
285 	 * itself on us between unlocking the mutex and cancelling. */
286 	if (msg != NULL) {
287 		cancellable = msg->cancellable;
288 		if (g_cancellable_is_cancelled (cancellable))
289 			cancellable = NULL;
290 		else
291 			g_object_ref (cancellable);
292 	}
293 
294 	g_mutex_unlock (&mail_msg_lock);
295 
296 	if (cancellable != NULL) {
297 		g_cancellable_cancel (cancellable);
298 		g_object_unref (cancellable);
299 	}
300 }
301 
302 gboolean
mail_msg_active(void)303 mail_msg_active (void)
304 {
305 	gboolean active;
306 
307 	g_mutex_lock (&mail_msg_lock);
308 	active = g_hash_table_size (mail_msg_active_table) > 0;
309 	g_mutex_unlock (&mail_msg_lock);
310 
311 	return active;
312 }
313 
314 /* **************************************** */
315 
316 static guint idle_source_id = 0;
317 G_LOCK_DEFINE_STATIC (idle_source_id);
318 static GAsyncQueue *main_loop_queue = NULL;
319 static GAsyncQueue *msg_reply_queue = NULL;
320 static GThread *main_thread = NULL;
321 
322 static gboolean
mail_msg_idle_cb(void)323 mail_msg_idle_cb (void)
324 {
325 	MailMsg *msg;
326 
327 	g_return_val_if_fail (main_loop_queue != NULL, FALSE);
328 	g_return_val_if_fail (msg_reply_queue != NULL, FALSE);
329 
330 	G_LOCK (idle_source_id);
331 	idle_source_id = 0;
332 	G_UNLOCK (idle_source_id);
333 	/* check the main loop queue */
334 	while ((msg = g_async_queue_try_pop (main_loop_queue)) != NULL) {
335 		GCancellable *cancellable;
336 
337 		cancellable = msg->cancellable;
338 
339 		g_idle_add_full (
340 			G_PRIORITY_DEFAULT,
341 			(GSourceFunc) mail_msg_submit,
342 			g_object_ref (msg->cancellable),
343 			(GDestroyNotify) g_object_unref);
344 		if (msg->info->exec != NULL)
345 			msg->info->exec (msg, cancellable, &msg->error);
346 		if (msg->info->done != NULL)
347 			msg->info->done (msg);
348 		mail_msg_unref (msg);
349 	}
350 
351 	/* check the reply queue */
352 	while ((msg = g_async_queue_try_pop (msg_reply_queue)) != NULL) {
353 		if (msg->info->done != NULL)
354 			msg->info->done (msg);
355 		mail_msg_check_error (msg);
356 		mail_msg_unref (msg);
357 	}
358 	return FALSE;
359 }
360 
361 static void
mail_msg_proxy(MailMsg * msg)362 mail_msg_proxy (MailMsg *msg)
363 {
364 	GCancellable *cancellable;
365 
366 	cancellable = msg->cancellable;
367 
368 	if (msg->info->desc != NULL) {
369 		gchar *text = msg->info->desc (msg);
370 		camel_operation_push_message (cancellable, "%s", text);
371 		g_free (text);
372 	}
373 
374 	g_idle_add_full (
375 		G_PRIORITY_DEFAULT,
376 		(GSourceFunc) mail_msg_submit,
377 		g_object_ref (msg->cancellable),
378 		(GDestroyNotify) g_object_unref);
379 
380 	if (msg->info->exec != NULL)
381 		msg->info->exec (msg, cancellable, &msg->error);
382 
383 	if (msg->info->desc != NULL)
384 		camel_operation_pop_message (cancellable);
385 
386 	g_async_queue_push (msg_reply_queue, msg);
387 
388 	G_LOCK (idle_source_id);
389 	if (idle_source_id == 0)
390 		/* Prioritize ahead of GTK+ redraws. */
391 		idle_source_id = g_idle_add_full (
392 			G_PRIORITY_HIGH_IDLE,
393 			(GSourceFunc) mail_msg_idle_cb, NULL, NULL);
394 	G_UNLOCK (idle_source_id);
395 }
396 
397 void
mail_msg_init(void)398 mail_msg_init (void)
399 {
400 	g_mutex_init (&mail_msg_lock);
401 	g_cond_init (&mail_msg_cond);
402 
403 	main_loop_queue = g_async_queue_new ();
404 	msg_reply_queue = g_async_queue_new ();
405 
406 	mail_msg_active_table = g_hash_table_new (NULL, NULL);
407 	main_thread = g_thread_self ();
408 }
409 
410 static gint
mail_msg_compare(const MailMsg * msg1,const MailMsg * msg2)411 mail_msg_compare (const MailMsg *msg1,
412                   const MailMsg *msg2)
413 {
414 	gint priority1 = msg1->priority;
415 	gint priority2 = msg2->priority;
416 
417 	if (priority1 == priority2)
418 		return 0;
419 
420 	return (priority1 < priority2) ? 1 : -1;
421 }
422 
423 static gpointer
create_thread_pool(gpointer data)424 create_thread_pool (gpointer data)
425 {
426 	GThreadPool *thread_pool;
427 	gint max_threads = GPOINTER_TO_INT (data);
428 
429 	/* once created, run forever */
430 	thread_pool = g_thread_pool_new (
431 		(GFunc) mail_msg_proxy, NULL, max_threads, FALSE, NULL);
432 	g_thread_pool_set_sort_function (
433 		thread_pool, (GCompareDataFunc) mail_msg_compare, NULL);
434 
435 	return thread_pool;
436 }
437 
438 void
mail_msg_main_loop_push(gpointer msg)439 mail_msg_main_loop_push (gpointer msg)
440 {
441 	g_async_queue_push_sorted (
442 		main_loop_queue, msg,
443 		(GCompareDataFunc) mail_msg_compare, NULL);
444 
445 	G_LOCK (idle_source_id);
446 	if (idle_source_id == 0)
447 		/* Prioritize ahead of GTK+ redraws. */
448 		idle_source_id = g_idle_add_full (
449 			G_PRIORITY_HIGH_IDLE,
450 			(GSourceFunc) mail_msg_idle_cb, NULL, NULL);
451 	G_UNLOCK (idle_source_id);
452 }
453 
454 void
mail_msg_unordered_push(gpointer msg)455 mail_msg_unordered_push (gpointer msg)
456 {
457 	static GOnce once = G_ONCE_INIT;
458 
459 	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (10));
460 
461 	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
462 }
463 
464 void
mail_msg_fast_ordered_push(gpointer msg)465 mail_msg_fast_ordered_push (gpointer msg)
466 {
467 	static GOnce once = G_ONCE_INIT;
468 
469 	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
470 
471 	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
472 }
473 
474 void
mail_msg_slow_ordered_push(gpointer msg)475 mail_msg_slow_ordered_push (gpointer msg)
476 {
477 	static GOnce once = G_ONCE_INIT;
478 
479 	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));
480 
481 	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
482 }
483 
484 gboolean
mail_in_main_thread(void)485 mail_in_main_thread (void)
486 {
487 	return (g_thread_self () == main_thread);
488 }
489 
490 /* ********************************************************************** */
491 
492 struct _call_msg {
493 	MailMsg base;
494 
495 	mail_call_t type;
496 	MailMainFunc func;
497 	gpointer ret;
498 	va_list ap;
499 	EFlag *done;
500 };
501 
502 static void
do_call(struct _call_msg * m,GCancellable * cancellable,GError ** error)503 do_call (struct _call_msg *m,
504          GCancellable *cancellable,
505          GError **error)
506 {
507 	gpointer p1, *p2, *p3, *p4, *p5;
508 	gint i1;
509 	va_list ap;
510 
511 	G_VA_COPY (ap, m->ap);
512 
513 	switch (m->type) {
514 	case MAIL_CALL_p_p:
515 		p1 = va_arg (ap, gpointer);
516 		m->ret = m->func (p1);
517 		break;
518 	case MAIL_CALL_p_pp:
519 		p1 = va_arg (ap, gpointer);
520 		p2 = va_arg (ap, gpointer);
521 		m->ret = m->func (p1, p2);
522 		break;
523 	case MAIL_CALL_p_ppp:
524 		p1 = va_arg (ap, gpointer);
525 		p2 = va_arg (ap, gpointer);
526 		p3 = va_arg (ap, gpointer);
527 		m->ret = m->func (p1, p2, p3);
528 		break;
529 	case MAIL_CALL_p_pppp:
530 		p1 = va_arg (ap, gpointer);
531 		p2 = va_arg (ap, gpointer);
532 		p3 = va_arg (ap, gpointer);
533 		p4 = va_arg (ap, gpointer);
534 		m->ret = m->func (p1, p2, p3, p4);
535 		break;
536 	case MAIL_CALL_p_ppppp:
537 		p1 = va_arg (ap, gpointer);
538 		p2 = va_arg (ap, gpointer);
539 		p3 = va_arg (ap, gpointer);
540 		p4 = va_arg (ap, gpointer);
541 		p5 = va_arg (ap, gpointer);
542 		m->ret = m->func (p1, p2, p3, p4, p5);
543 		break;
544 	case MAIL_CALL_p_ppippp:
545 		p1 = va_arg (ap, gpointer);
546 		p2 = va_arg (ap, gpointer);
547 		i1 = va_arg (ap, gint);
548 		p3 = va_arg (ap, gpointer);
549 		p4 = va_arg (ap, gpointer);
550 		p5 = va_arg (ap, gpointer);
551 		m->ret = m->func (p1, p2, i1, p3, p4, p5);
552 		break;
553 	}
554 
555 	va_end (ap);
556 
557 	if (g_cancellable_is_cancelled (cancellable)) {
558 		if (cancel_activity)
559 			cancel_activity (cancellable);
560 	} else {
561 		if (complete_activity)
562 			complete_activity (cancellable);
563 	}
564 
565 	if (m->done != NULL)
566 		e_flag_set (m->done);
567 }
568 
569 static void
do_free(struct _call_msg * msg)570 do_free (struct _call_msg *msg)
571 {
572 	va_end (msg->ap);
573 }
574 
575 static MailMsgInfo mail_call_info = {
576 	sizeof (struct _call_msg),
577 	(MailMsgDescFunc) NULL,
578 	(MailMsgExecFunc) do_call,
579 	(MailMsgDoneFunc) NULL,
580 	(MailMsgFreeFunc) do_free
581 };
582 
583 gpointer
mail_call_main(mail_call_t type,MailMainFunc func,...)584 mail_call_main (mail_call_t type,
585                 MailMainFunc func,
586                 ...)
587 {
588 	GCancellable *cancellable;
589 	struct _call_msg *m;
590 	gpointer ret;
591 	va_list ap;
592 
593 	va_start (ap, func);
594 
595 	m = mail_msg_new (&mail_call_info);
596 	m->type = type;
597 	m->func = func;
598 	G_VA_COPY (m->ap, ap);
599 
600 	cancellable = m->base.cancellable;
601 
602 	if (mail_in_main_thread ())
603 		do_call (m, cancellable, &m->base.error);
604 	else {
605 		mail_msg_ref (m);
606 		m->done = e_flag_new ();
607 		mail_msg_main_loop_push (m);
608 		e_flag_wait (m->done);
609 		e_flag_free (m->done);
610 	}
611 
612 	va_end (ap);
613 
614 	ret = m->ret;
615 	mail_msg_unref (m);
616 
617 	/* the m->ap is freed on the message end, at do_free() above */
618 	/* coverity[missing_va_end] */
619 	return ret;
620 }
621 
622