]> git.sesse.net Git - vlc/blob - src/pulse/mainloop.c
Fix corner case crash on corrupt plugin with callbacks
[vlc] / src / pulse / mainloop.c
1 /*****************************************************************************
2  * vlcpulse.c : PulseAudio support library for LibVLC plugins
3  *****************************************************************************
4  * Copyright (C) 2008 the VideoLAN team
5  * Copyright (C) 2009-2011 RĂ©mi Denis-Courmont
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #define MODULE_STRING "pulse"
27 #include <vlc_common.h>
28 #include <pulse/pulseaudio.h>
29
30 #include <vlc_pulse.h>
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <locale.h>
34 #include <unistd.h>
35 #include <pwd.h>
36
37 #undef vlc_pa_error
38 void vlc_pa_error (vlc_object_t *obj, const char *msg, pa_context *ctx)
39 {
40     msg_Err (obj, "%s: %s", msg, pa_strerror (pa_context_errno (ctx)));
41 }
42
43 static pa_threaded_mainloop *vlc_pa_mainloop;
44 static unsigned refs = 0;
45 static vlc_mutex_t lock = VLC_STATIC_MUTEX;
46
47 /**
48  * Creates and references the VLC PulseAudio threaded main loop.
49  * @return 0 on success, -1 on failure
50  */
51 static int vlc_pa_mainloop_init (void)
52 {
53     vlc_mutex_lock (&lock);
54     if (refs == 0)
55     {
56         vlc_pa_mainloop = pa_threaded_mainloop_new ();
57         if (unlikely(vlc_pa_mainloop == NULL))
58             goto err;
59
60         if (pa_threaded_mainloop_start (vlc_pa_mainloop) < 0)
61         {
62             pa_threaded_mainloop_free (vlc_pa_mainloop);
63             goto err;
64         }
65     }
66     else
67     {
68         if (unlikely(refs >= UINT_MAX))
69             goto err;
70     }
71     refs++;
72     vlc_mutex_unlock (&lock);
73     return 0;
74 err:
75     vlc_mutex_unlock (&lock);
76     return -1;
77 }
78
79 /**
80  * Releases a reference to the VLC PulseAudio main loop.
81  */
82 static void vlc_pa_mainloop_deinit (void)
83 {
84     vlc_mutex_lock (&lock);
85     assert (refs > 0);
86     if (--refs > 0)
87     {
88         pa_threaded_mainloop_stop (vlc_pa_mainloop);
89         pa_threaded_mainloop_free (vlc_pa_mainloop);
90     }
91     vlc_mutex_unlock (&lock);
92 }
93
94 /**
95  * Acquires the main loop lock.
96  */
97 void vlc_pa_lock (void)
98 {
99     pa_threaded_mainloop_lock (vlc_pa_mainloop);
100 }
101
102 /**
103  * Releases the main loop lock.
104  */
105 void vlc_pa_unlock (void)
106 {
107     pa_threaded_mainloop_unlock (vlc_pa_mainloop);
108 }
109
110 /**
111  * Signals the main loop.
112  */
113 void vlc_pa_signal (int do_wait)
114 {
115     pa_threaded_mainloop_signal (vlc_pa_mainloop, do_wait);
116 }
117
118 /**
119  * Waits for the main loop to be signaled.
120  */
121 void vlc_pa_wait (void)
122 {
123     pa_threaded_mainloop_wait (vlc_pa_mainloop);
124 }
125
126
127 static void context_state_cb (pa_context *ctx, void *userdata)
128 {
129     switch (pa_context_get_state(ctx))
130     {
131         case PA_CONTEXT_READY:
132         case PA_CONTEXT_FAILED:
133         case PA_CONTEXT_TERMINATED:
134             vlc_pa_signal (0);
135         default:
136             break;
137     }
138     (void) userdata;
139 }
140
141 static bool context_wait (pa_context *ctx)
142 {
143     pa_context_state_t state;
144
145     while ((state = pa_context_get_state (ctx)) != PA_CONTEXT_READY)
146     {
147         if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED)
148             return -1;
149         vlc_pa_wait ();
150     }
151     return 0;
152 }
153
154 /**
155  * Initializes the PulseAudio main loop and connects to the PulseAudio server.
156  * @return a PulseAudio context on success, or NULL on error
157  */
158 pa_context *vlc_pa_connect (vlc_object_t *obj)
159 {
160     if (unlikely(vlc_pa_mainloop_init ()))
161         return NULL;
162
163     msg_Dbg (obj, "using library version %s", pa_get_library_version ());
164     msg_Dbg (obj, " (compiled with version %s, protocol %u)",
165              pa_get_headers_version (), PA_PROTOCOL_VERSION);
166
167     char *ua = var_InheritString (obj, "user-agent");
168     pa_context *ctx;
169
170     /* Fill in context (client) properties */
171     pa_proplist *props = pa_proplist_new ();
172     if (likely(props != NULL))
173     {
174         pa_proplist_sets (props, PA_PROP_APPLICATION_NAME, ua);
175         pa_proplist_sets (props, PA_PROP_APPLICATION_ID, "org.VideoLAN.VLC");
176         pa_proplist_sets (props, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
177         pa_proplist_sets (props, PA_PROP_APPLICATION_ICON_NAME, PACKAGE_NAME);
178         //pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE, _("C"));
179         pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE,
180                           setlocale (LC_MESSAGES, NULL));
181
182         pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_ID, "%lu",
183                           (unsigned long) getpid ());
184         //pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_BINARY,
185         //                  PACKAGE_NAME);
186
187         char buf[sysconf (_SC_GETPW_R_SIZE_MAX)];
188         struct passwd pwbuf, *pw;
189
190         if (getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw) == 0
191          && pw != NULL)
192             pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_USER,
193                               pw->pw_name);
194
195         char hostname[sysconf (_SC_HOST_NAME_MAX)];
196         if (gethostname (hostname, sizeof (hostname)) == 0)
197             pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_HOST,
198                               hostname);
199
200         const char *session = getenv ("XDG_SESSION_COOKIE");
201         if (session != NULL)
202         {
203             pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_MACHINE_ID,
204                               "%.32s", session); /* XXX: is this valid? */
205             pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_SESSION_ID,
206                               session);
207         }
208     }
209
210     /* Connect to PulseAudio daemon */
211     vlc_pa_lock ();
212
213     ctx = pa_context_new_with_proplist (pa_threaded_mainloop_get_api (vlc_pa_mainloop), ua, props);
214     free (ua);
215     if (props != NULL)
216         pa_proplist_free(props);
217     if (unlikely(ctx == NULL))
218         goto fail;
219
220     pa_context_set_state_callback (ctx, context_state_cb, NULL);
221     if (pa_context_connect (ctx, NULL, 0, NULL) < 0
222      || context_wait (ctx))
223     {
224         vlc_pa_error (obj, "PulseAudio server connection failure", ctx);
225         pa_context_unref (ctx);
226         goto fail;
227     }
228     msg_Dbg (obj, "connected %s to %s as client #%"PRIu32,
229              pa_context_is_local (ctx) ? "locally" : "remotely",
230              pa_context_get_server (ctx), pa_context_get_index (ctx));
231     msg_Dbg (obj, "using protocol %"PRIu32", server protocol %"PRIu32,
232              pa_context_get_protocol_version (ctx),
233              pa_context_get_server_protocol_version (ctx));
234
235     vlc_pa_unlock ();
236     return ctx;
237
238 fail:
239     vlc_pa_unlock ();
240     vlc_pa_mainloop_deinit ();
241     return NULL;
242 }
243
244 /**
245  * Closes a connection to PulseAudio.
246  */
247 void vlc_pa_disconnect (vlc_object_t *obj, pa_context *ctx)
248 {
249     vlc_pa_lock ();
250     pa_context_disconnect (ctx);
251     pa_context_set_state_callback (ctx, NULL, NULL);
252     pa_context_unref (ctx);
253     vlc_pa_unlock ();
254
255     vlc_pa_mainloop_deinit ();
256     (void) obj;
257 }
258
259 /**
260  * Frees a timer event.
261  * \note Timer events can be created with pa_context_rttime_new().
262  * \warning This function must be called from the mainloop,
263  * or with the mainloop lock held.
264  */
265 void vlc_pa_rttime_free(pa_time_event *e)
266 {
267     (pa_threaded_mainloop_get_api (vlc_pa_mainloop))->time_free (e);
268 }