55#include <sys/socket.h> // for socket and read
66#include <errno.h>
77
8+ /*
9+ * Implements the read callback.
10+ * Called when data has been sent by signald and is ready to be handled.
11+ *
12+ * Should probably be moved in another module.
13+ */
814void
915signald_read_cb (gpointer data , gint source , PurpleInputCondition cond )
1016{
@@ -50,78 +56,103 @@ signald_read_cb(gpointer data, gint source, PurpleInputCondition cond)
5056 }
5157}
5258
53- gboolean sockaddr_from_path (struct sockaddr_un * address , const gchar * path ) {
54- memset (address , 0 , sizeof (struct sockaddr_un ));
55- address -> sun_family = AF_UNIX ;
56- if (strlen (path )- 1 > sizeof address -> sun_path ) {
57- purple_debug_error (SIGNALD_PLUGIN_ID , "socket path %s exceeds maximum length %lu!\n" , path , sizeof address -> sun_path ); // TODO: show error in ui
58- return FALSE;
59- } else {
60- strcpy (address -> sun_path , path );
61- return TRUE;
62- }
63- }
64-
59+ /*
60+ * This struct exchanges data between threads, see @try_connect.
61+ */
6562typedef struct {
6663 SignaldAccount * sa ;
6764 gchar * socket_path ;
6865 gchar * message ;
69- } SignaldConnection ;
66+ } SignaldConnectionAttempt ;
7067
68+ /*
69+ * See @execute_on_main_thread.
70+ */
7171static gboolean
7272display_connection_error (void * data ) {
73- SignaldConnection * sc = data ;
73+ SignaldConnectionAttempt * sc = data ;
7474 purple_connection_error (sc -> sa -> pc , PURPLE_CONNECTION_ERROR_NETWORK_ERROR , sc -> message );
7575 g_free (sc -> message );
7676 g_free (sc );
7777 return FALSE;
7878}
7979
80+ /*
81+ * See @execute_on_main_thread.
82+ */
8083static gboolean
8184display_debug_info (void * data ) {
82- SignaldConnection * sc = data ;
83- purple_debug_info (SIGNALD_PLUGIN_ID , "%s" ,sc -> message );
85+ SignaldConnectionAttempt * sc = data ;
86+ purple_debug_info (SIGNALD_PLUGIN_ID , "%s" , sc -> message );
8487 g_free (sc -> message );
8588 g_free (sc );
8689 return FALSE;
8790}
8891
92+ /*
93+ * Every function writing to the GTK UI must be executed from the GTK main thread.
94+ * This function is a crutch for wrapping some purple functions:
95+ *
96+ * * purple_debug_info in display_debug_info
97+ * * purple_connection_error in display_connection_error
98+ *
99+ * Can only handle one message string instead of variardic arguments.
100+ */
101+ static void
102+ execute_on_main_thread (GSourceFunc function , SignaldConnectionAttempt * sc , gchar * message ) {
103+ sc -> message = message ;
104+ purple_timeout_add (0 , function , g_memdup2 (sc , sizeof * sc ));
105+ }
106+
107+ /*
108+ * Tries to connect to a socket at a given location.
109+ * It is ought to be executed in a thread.
110+ * Only in case it does noes not succeed AND is the last thread to stop trying,
111+ * the situation is considered a connection failure.
112+ */
89113static void *
90114do_try_connect (void * arg ) {
91- SignaldConnection * sc = arg ;
115+ SignaldConnectionAttempt * sc = arg ;
116+
92117 struct sockaddr_un address ;
93- if (sockaddr_from_path (& address , sc -> socket_path ))
94- {
118+ if (strlen (sc -> socket_path )- 1 > sizeof address .sun_path ) {
119+ execute_on_main_thread (display_connection_error , sc , g_strdup_printf ("socket path %s exceeds maximum length %lu!\n" , sc -> socket_path , sizeof address .sun_path ));
120+ } else {
121+ // convert path to sockaddr
122+ memset (& address , 0 , sizeof (struct sockaddr_un ));
123+ address .sun_family = AF_UNIX ;
124+ strcpy (address .sun_path , sc -> socket_path );
125+
95126 // create a socket
96127 int fd = socket (AF_UNIX , SOCK_STREAM , 0 );
97128 if (fd < 0 ) {
98- sc -> message = g_strdup_printf ("Could not create socket: %s" , strerror (errno ));
99- purple_timeout_add (0 , display_connection_error , g_memdup2 (sc , sizeof * sc ));
129+ execute_on_main_thread (display_connection_error , sc , g_strdup_printf ("Could not create socket: %s" , strerror (errno )));
100130 } else {
101131 int32_t err = -1 ;
132+
102133 // connect our socket to signald socket
103134 for (int try = 1 ; try <= SIGNALD_TIMEOUT_SECONDS && err != 0 && sc -> sa -> fd == 0 ; try ++ ) {
104135 err = connect (fd , (struct sockaddr * ) & address , sizeof (struct sockaddr_un ));
105- sc -> message = g_strdup_printf ("Connecting to %s (try #%d)...\n" , address .sun_path , try );
106- purple_timeout_add (0 , display_debug_info , g_memdup2 (sc , sizeof * sc ));
136+ execute_on_main_thread (display_debug_info , sc , g_strdup_printf ("Connecting to %s (try #%d)...\n" , address .sun_path , try ));
107137 sleep (1 ); // altogether wait SIGNALD_TIMEOUT_SECONDS
108138 }
109139
110140 if (err == 0 ) {
111141 // successfully connected, tell purple to use our socket
112- sc -> message = g_strdup_printf ("Connected to %s.\n" , address .sun_path );
113- purple_timeout_add (0 , display_debug_info , g_memdup2 (sc , sizeof * sc ));
142+ execute_on_main_thread (display_debug_info , sc , g_strdup_printf ("Connected to %s.\n" , address .sun_path ));
114143 sc -> sa -> fd = fd ;
115144 sc -> sa -> watcher = purple_input_add (fd , PURPLE_INPUT_READ , signald_read_cb , sc -> sa );
116145 }
117146 if (sc -> sa -> fd == 0 ) {
118- sc -> message = g_strdup_printf ("No connection to %s after %d tries.\n" , address .sun_path , SIGNALD_TIMEOUT_SECONDS );
119- purple_timeout_add (0 , display_debug_info , g_memdup2 (sc , sizeof * sc ));
120- sc -> sa -> socket_paths_count -- ;
147+ // no concurrent connection attempt has been successful by now
148+ execute_on_main_thread (display_debug_info , sc , g_strdup_printf ("No connection to %s after %d tries.\n" , address .sun_path , SIGNALD_TIMEOUT_SECONDS ));
149+
150+ sc -> sa -> socket_paths_count -- ; // this tread gives up trying
151+ // NOTE: although unlikely, it is possible that above decrement and other modifications or checks happen concurrently.
152+ // TODO: use a mutex where appropriate.
121153 if (sc -> sa -> socket_paths_count == 0 ) {
122-
123- sc -> message = g_strdup ("Unable to connect to any socket location." );
124- purple_timeout_add (0 , display_connection_error , g_memdup2 (sc , sizeof * sc ));
154+ // no trying threads are remaining
155+ execute_on_main_thread (display_connection_error , sc , sc -> message = g_strdup ("Unable to connect to any socket location." ));
125156 }
126157 }
127158 }
@@ -131,16 +162,28 @@ do_try_connect(void * arg) {
131162 return NULL ;
132163}
133164
165+ /*
166+ * Starts a connection attempt in background.
167+ */
134168static void
135169try_connect (SignaldAccount * sa , gchar * socket_path ) {
136- SignaldConnection * sc = g_new0 (SignaldConnection , 1 );
170+ SignaldConnectionAttempt * sc = g_new0 (SignaldConnectionAttempt , 1 );
137171 sc -> sa = sa ;
138172 sc -> socket_path = socket_path ;
139173 pthread_t try_connect_thread ;
140- // TODO: handle error int err =
141- pthread_create (& try_connect_thread , NULL , do_try_connect , (void * )sc );
174+ int err = pthread_create (& try_connect_thread , NULL , do_try_connect , (void * )sc );
175+ if (err != 0 ) {
176+ gchar * errmsg = g_strdup_printf ("Could not create thread for connecting in background: %s" , strerror (err ));
177+ purple_connection_error (sa -> pc , PURPLE_CONNECTION_ERROR_NETWORK_ERROR , errmsg );
178+ g_free (errmsg );
179+ }
142180}
143181
182+ /*
183+ * Connect to signald socket.
184+ * Tries multiple possible default socket location at once in background.
185+ * In case the user has explicitly defined a socket location, only that one is considered.
186+ */
144187void
145188signald_connect_socket (SignaldAccount * sa ) {
146189 purple_connection_set_state (sa -> pc , PURPLE_CONNECTION_CONNECTING );
0 commit comments