snap: Added jack plug
[vlc.git] / src / misc / update.c
1 /*****************************************************************************
2  * update.c: VLC update checking and downloading
3  *****************************************************************************
4  * Copyright © 2005-2008 VLC authors and VideoLAN
5  *
6  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
7  *          Rémi Duraffort <ivoire at via.ecp.fr>
8             Rafaël Carré <funman@videolanorg>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either release 2 of the License, or
13  * (at your option) any later release.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /**
26  *   \file
27  *   This file contains functions related to VLC update management
28  */
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <vlc_common.h>
39 #include <vlc_update.h>
40
41 #include <assert.h>
42
43 #include <vlc_pgpkey.h>
44 #include <vlc_stream.h>
45 #include <vlc_strings.h>
46 #include <vlc_fs.h>
47 #include <vlc_dialog.h>
48 #include <vlc_interface.h>
49
50 #include <gcrypt.h>
51 #include <vlc_gcrypt.h>
52 #ifdef _WIN32
53 #include <shellapi.h>
54 #endif
55 #include "update.h"
56 #include "../libvlc.h"
57
58 /*****************************************************************************
59  * Misc defines
60  *****************************************************************************/
61
62 /*
63  * Here is the format of these "status files" :
64  * First line is the last version: "X.Y.Z.E" where:
65  *      * X is the major number
66  *      * Y is the minor number
67  *      * Z is the revision number
68  *      * .E is an OPTIONAL extra number
69  *      * IE "1.2.0" or "1.1.10.1"
70  * Second line is a url of the binary for this last version
71  * Remaining text is a required description of the update
72  */
73
74 #if defined( _WIN64 )
75 # define UPDATE_OS_SUFFIX "-win-x64"
76 #elif defined( _WIN32 )
77 # define UPDATE_OS_SUFFIX "-win-x86"
78 #else
79 # define UPDATE_OS_SUFFIX ""
80 #endif
81
82 #ifndef NDEBUG
83 # define UPDATE_VLC_STATUS_URL "http://update-test.videolan.org/vlc/status-win-x86"
84 #else
85 # define UPDATE_VLC_STATUS_URL "http://update.videolan.org/vlc/status" UPDATE_OS_SUFFIX
86 #endif
87
88 #define dialog_FatalWait( p_obj, psz_title, psz_fmt, ... ) \
89     vlc_dialog_wait_question( p_obj, VLC_DIALOG_QUESTION_CRITICAL, "OK", NULL, \
90                               NULL, psz_title, psz_fmt, ##__VA_ARGS__ );
91
92 /*****************************************************************************
93  * Update_t functions
94  *****************************************************************************/
95
96 #undef update_New
97 /**
98  * Create a new update VLC struct
99  *
100  * \param p_this the calling vlc_object
101  * \return pointer to new update_t or NULL
102  */
103 update_t *update_New( vlc_object_t *p_this )
104 {
105     update_t *p_update;
106     assert( p_this );
107
108     p_update = (update_t *)malloc( sizeof( update_t ) );
109     if( !p_update ) return NULL;
110
111     vlc_mutex_init( &p_update->lock );
112
113     p_update->p_libvlc = vlc_object_instance(p_this);
114
115     p_update->release.psz_url = NULL;
116     p_update->release.psz_desc = NULL;
117
118     p_update->p_download = NULL;
119     p_update->p_check = NULL;
120
121     p_update->p_pkey = NULL;
122     vlc_gcrypt_init();
123
124     return p_update;
125 }
126
127 /**
128  * Delete an update_t struct
129  *
130  * \param p_update update_t* pointer
131  * \return nothing
132  */
133 void update_Delete( update_t *p_update )
134 {
135     assert( p_update );
136
137     if( p_update->p_check )
138     {
139         vlc_join( p_update->p_check->thread, NULL );
140         free( p_update->p_check );
141     }
142
143     if( p_update->p_download )
144     {
145         atomic_store( &p_update->p_download->aborted, true );
146         vlc_join( p_update->p_download->thread, NULL );
147         vlc_object_delete(p_update->p_download);
148     }
149
150     vlc_mutex_destroy( &p_update->lock );
151
152     free( p_update->release.psz_url );
153     free( p_update->release.psz_desc );
154     free( p_update->p_pkey );
155
156     free( p_update );
157 }
158
159 /**
160  * Empty the release struct
161  *
162  * \param p_update update_t* pointer
163  * \return nothing
164  */
165 static void EmptyRelease( update_t *p_update )
166 {
167     p_update->release.i_major = 0;
168     p_update->release.i_minor = 0;
169     p_update->release.i_revision = 0;
170
171     FREENULL( p_update->release.psz_url );
172     FREENULL( p_update->release.psz_desc );
173 }
174
175 /**
176  * Get the update file and parse it
177  * p_update has to be locked when calling this function
178  *
179  * \param p_update pointer to update struct
180  * \return true if the update is valid and authenticated
181  */
182 static bool GetUpdateFile( update_t *p_update )
183 {
184     stream_t *p_stream = NULL;
185     char *psz_version_line = NULL;
186     char *psz_update_data = NULL;
187
188     p_stream = vlc_stream_NewURL( p_update->p_libvlc, UPDATE_VLC_STATUS_URL );
189     if( !p_stream )
190     {
191         msg_Err( p_update->p_libvlc, "Failed to open %s for reading",
192                  UPDATE_VLC_STATUS_URL );
193         goto error;
194     }
195
196     uint64_t i_read;
197     if( vlc_stream_GetSize( p_stream, &i_read ) || i_read >= UINT16_MAX )
198     {
199         msg_Err(p_update->p_libvlc, "Status file too large");
200         goto error;
201     }
202
203     psz_update_data = malloc( i_read + 1 ); /* terminating '\0' */
204     if( !psz_update_data )
205         goto error;
206
207     if( vlc_stream_Read( p_stream, psz_update_data,
208                          i_read ) != (ssize_t)i_read )
209     {
210         msg_Err( p_update->p_libvlc, "Couldn't download update file %s",
211                 UPDATE_VLC_STATUS_URL );
212         goto error;
213     }
214     psz_update_data[i_read] = '\0';
215
216     vlc_stream_Delete( p_stream );
217     p_stream = NULL;
218
219     /* first line : version number */
220     char *psz_update_data_parser = psz_update_data;
221     size_t i_len = strcspn( psz_update_data, "\r\n" );
222     psz_update_data_parser += i_len;
223     while( *psz_update_data_parser == '\r' || *psz_update_data_parser == '\n' )
224         psz_update_data_parser++;
225
226     if( !(psz_version_line = malloc( i_len + 1)) )
227         goto error;
228     strncpy( psz_version_line, psz_update_data, i_len );
229     psz_version_line[i_len] = '\0';
230
231     p_update->release.i_extra = 0;
232     int ret = sscanf( psz_version_line, "%i.%i.%i.%i",
233                     &p_update->release.i_major, &p_update->release.i_minor,
234                     &p_update->release.i_revision, &p_update->release.i_extra);
235     if( ret != 3 && ret != 4 )
236     {
237             msg_Err( p_update->p_libvlc, "Update version false formatted" );
238             goto error;
239     }
240
241     /* second line : URL */
242     i_len = strcspn( psz_update_data_parser, "\r\n" );
243     if( i_len == 0 )
244     {
245         msg_Err( p_update->p_libvlc, "Update file %s is corrupted: URL missing",
246                  UPDATE_VLC_STATUS_URL );
247
248         goto error;
249     }
250
251     if( !(p_update->release.psz_url = malloc( i_len + 1)) )
252         goto error;
253     strncpy( p_update->release.psz_url, psz_update_data_parser, i_len );
254     p_update->release.psz_url[i_len] = '\0';
255
256     psz_update_data_parser += i_len;
257     while( *psz_update_data_parser == '\r' || *psz_update_data_parser == '\n' )
258         psz_update_data_parser++;
259
260     /* Remaining data : description */
261     i_len = strlen( psz_update_data_parser );
262     if( i_len == 0 )
263     {
264         msg_Err( p_update->p_libvlc,
265                 "Update file %s is corrupted: description missing",
266                 UPDATE_VLC_STATUS_URL );
267         goto error;
268     }
269
270     if( !(p_update->release.psz_desc = malloc( i_len + 1)) )
271         goto error;
272     strncpy( p_update->release.psz_desc, psz_update_data_parser, i_len );
273     p_update->release.psz_desc[i_len] = '\0';
274
275     /* Now that we know the status is valid, we must download its signature
276      * to authenticate it */
277     signature_packet_t sign;
278     if( download_signature( VLC_OBJECT( p_update->p_libvlc ), &sign,
279             UPDATE_VLC_STATUS_URL ) != VLC_SUCCESS )
280     {
281         msg_Err( p_update->p_libvlc, "Couldn't download signature of status file" );
282         goto error;
283     }
284
285     if( sign.type != BINARY_SIGNATURE && sign.type != TEXT_SIGNATURE )
286     {
287         msg_Err( p_update->p_libvlc, "Invalid signature type" );
288         goto error;
289     }
290
291     p_update->p_pkey = (public_key_t*)malloc( sizeof( public_key_t ) );
292     if( !p_update->p_pkey )
293         goto error;
294
295     if( parse_public_key( videolan_public_key, sizeof( videolan_public_key ),
296                         p_update->p_pkey, NULL ) != VLC_SUCCESS )
297     {
298         msg_Err( p_update->p_libvlc, "Couldn't parse embedded public key, something went really wrong..." );
299         FREENULL( p_update->p_pkey );
300         goto error;
301     }
302
303     memcpy( p_update->p_pkey->longid, videolan_public_key_longid, 8 );
304
305     if( memcmp( sign.issuer_longid, p_update->p_pkey->longid , 8 ) != 0 )
306     {
307         msg_Dbg( p_update->p_libvlc, "Need to download the GPG key" );
308         public_key_t *p_new_pkey = download_key(
309                 VLC_OBJECT(p_update->p_libvlc),
310                 sign.issuer_longid, videolan_public_key_longid );
311         if( !p_new_pkey )
312         {
313             msg_Err( p_update->p_libvlc, "Couldn't download GPG key" );
314             FREENULL( p_update->p_pkey );
315             goto error;
316         }
317
318         uint8_t *p_hash = hash_from_public_key( p_new_pkey );
319         if( !p_hash )
320         {
321             msg_Err( p_update->p_libvlc, "Failed to hash signature" );
322             free( p_new_pkey );
323             FREENULL( p_update->p_pkey );
324             goto error;
325         }
326
327         if( verify_signature( &p_new_pkey->sig,
328                     &p_update->p_pkey->key, p_hash ) == VLC_SUCCESS )
329         {
330             free( p_hash );
331             msg_Info( p_update->p_libvlc, "Key authenticated" );
332             free( p_update->p_pkey );
333             p_update->p_pkey = p_new_pkey;
334         }
335         else
336         {
337             free( p_hash );
338             msg_Err( p_update->p_libvlc, "Key signature invalid !" );
339             goto error;
340         }
341     }
342
343     uint8_t *p_hash = hash_from_text( psz_update_data, &sign );
344     if( !p_hash )
345     {
346         msg_Warn( p_update->p_libvlc, "Can't compute hash for status file" );
347         goto error;
348     }
349
350     else if( p_hash[0] != sign.hash_verification[0] ||
351         p_hash[1] != sign.hash_verification[1] )
352     {
353         msg_Warn( p_update->p_libvlc, "Bad hash for status file" );
354         free( p_hash );
355         goto error;
356     }
357
358     else if( verify_signature( &sign, &p_update->p_pkey->key, p_hash )
359             != VLC_SUCCESS )
360     {
361         msg_Err( p_update->p_libvlc, "BAD SIGNATURE for status file" );
362         free( p_hash );
363         goto error;
364     }
365
366     else
367     {
368         msg_Info( p_update->p_libvlc, "Status file authenticated" );
369         free( p_hash );
370         free( psz_version_line );
371         free( psz_update_data );
372         return true;
373     }
374
375 error:
376     if( p_stream )
377         vlc_stream_Delete( p_stream );
378     free( psz_version_line );
379     free( psz_update_data );
380     return false;
381 }
382
383 static void* update_CheckReal( void * );
384
385 /**
386  * Check for updates
387  *
388  * \param p_update pointer to update struct
389  * \param pf_callback pointer to a function to call when the update_check is finished
390  * \param p_data pointer to some datas to give to the callback
391  * \returns nothing
392  */
393 void update_Check( update_t *p_update, void (*pf_callback)( void*, bool ), void *p_data )
394 {
395     assert( p_update );
396
397     // If the object already exist, destroy it
398     if( p_update->p_check )
399     {
400         vlc_join( p_update->p_check->thread, NULL );
401         free( p_update->p_check );
402     }
403
404     update_check_thread_t *p_uct = calloc( 1, sizeof( *p_uct ) );
405     if( !p_uct ) return;
406
407     p_uct->p_update = p_update;
408     p_update->p_check = p_uct;
409     p_uct->pf_callback = pf_callback;
410     p_uct->p_data = p_data;
411
412     vlc_clone( &p_uct->thread, update_CheckReal, p_uct, VLC_THREAD_PRIORITY_LOW );
413 }
414
415 void* update_CheckReal( void *obj )
416 {
417     update_check_thread_t *p_uct = (update_check_thread_t *)obj;
418     bool b_ret;
419     int canc;
420
421     canc = vlc_savecancel ();
422     vlc_mutex_lock( &p_uct->p_update->lock );
423
424     EmptyRelease( p_uct->p_update );
425     b_ret = GetUpdateFile( p_uct->p_update );
426     vlc_mutex_unlock( &p_uct->p_update->lock );
427
428     if( p_uct->pf_callback )
429         (p_uct->pf_callback)( p_uct->p_data, b_ret );
430
431     vlc_restorecancel (canc);
432     return NULL;
433 }
434
435 bool update_NeedUpgrade( update_t *p_update )
436 {
437     assert( p_update );
438
439     static const int current[4] = {
440         PACKAGE_VERSION_MAJOR,
441         PACKAGE_VERSION_MINOR,
442         PACKAGE_VERSION_REVISION,
443         PACKAGE_VERSION_EXTRA
444     };
445     const int latest[4] = {
446         p_update->release.i_major,
447         p_update->release.i_minor,
448         p_update->release.i_revision,
449         p_update->release.i_extra
450     };
451
452     for (unsigned i = 0; i < sizeof latest / sizeof *latest; i++) {
453         /* there is a new version available */
454         if (latest[i] > current[i])
455             return true;
456
457         /* current version is more recent than the latest version ?! */
458         if (latest[i] < current[i])
459             return false;
460     }
461
462     /* current version is not a release, it's a -git or -rc version */
463     if (*PACKAGE_VERSION_DEV)
464         return true;
465
466     /* current version is latest version */
467     return false;
468 }
469
470 /**
471  * Convert a long int size in bytes to a string
472  *
473  * \param l_size the size in bytes
474  * \return the size as a string
475  */
476 static char *size_str( uint64_t l_size )
477 {
478     char *psz_tmp = NULL;
479     int i_retval = 0;
480     if( l_size >> 30 )
481         i_retval = asprintf( &psz_tmp, _("%.1f GiB"), (float)l_size/(1<<30) );
482     else if( l_size >> 20 )
483         i_retval = asprintf( &psz_tmp, _("%.1f MiB"), (float)l_size/(1<<20) );
484     else if( l_size >> 10 )
485         i_retval = asprintf( &psz_tmp, _("%.1f KiB"), (float)l_size/(1<<10) );
486     else
487         i_retval = asprintf( &psz_tmp, _("%"PRIu64" B"), l_size );
488
489     return i_retval == -1 ? NULL : psz_tmp;
490 }
491
492 static void* update_DownloadReal( void * );
493
494 /**
495  * Download the file given in the update_t
496  *
497  * \param p_update structure
498  * \param dir to store the download file
499  * \return nothing
500  */
501 void update_Download( update_t *p_update, const char *psz_destdir )
502 {
503     assert( p_update );
504
505     // If the object already exist, destroy it
506     if( p_update->p_download )
507     {
508         atomic_store( &p_update->p_download->aborted, true );
509         vlc_join( p_update->p_download->thread, NULL );
510         vlc_object_delete(p_update->p_download);
511     }
512
513     update_download_thread_t *p_udt =
514         vlc_custom_create( p_update->p_libvlc, sizeof( *p_udt ),
515                            "update download" );
516     if( !p_udt )
517         return;
518
519     p_udt->p_update = p_update;
520     p_update->p_download = p_udt;
521     p_udt->psz_destdir = psz_destdir ? strdup( psz_destdir ) : NULL;
522
523     atomic_store(&p_udt->aborted, false);
524     vlc_clone( &p_udt->thread, update_DownloadReal, p_udt, VLC_THREAD_PRIORITY_LOW );
525 }
526
527 static void* update_DownloadReal( void *obj )
528 {
529     update_download_thread_t *p_udt = (update_download_thread_t *)obj;
530     uint64_t l_size;
531     uint64_t l_downloaded = 0;
532     float f_progress;
533     char *psz_downloaded = NULL;
534     char *psz_size = NULL;
535     char *psz_destfile = NULL;
536     char *psz_tmpdestfile = NULL;
537
538     FILE *p_file = NULL;
539     stream_t *p_stream = NULL;
540     void* p_buffer = NULL;
541     int i_read;
542     int canc;
543
544     vlc_dialog_id *p_dialog_id = NULL;
545     update_t *p_update = p_udt->p_update;
546     char *psz_destdir = p_udt->psz_destdir;
547
548     msg_Dbg( p_udt, "Opening Stream '%s'", p_update->release.psz_url );
549     canc = vlc_savecancel ();
550
551     /* Open the stream */
552     p_stream = vlc_stream_NewURL( p_udt, p_update->release.psz_url );
553     if( !p_stream )
554     {
555         msg_Err( p_udt, "Failed to open %s for reading", p_update->release.psz_url );
556         goto end;
557     }
558
559     /* Get the stream size */
560     if( vlc_stream_GetSize( p_stream, &l_size ) || l_size == 0 )
561         goto end;
562
563     /* Get the file name and open it*/
564     psz_tmpdestfile = strrchr( p_update->release.psz_url, '/' );
565     if( !psz_tmpdestfile )
566     {
567         msg_Err( p_udt, "The URL %s is badly formatted",
568                  p_update->release.psz_url );
569         goto end;
570     }
571     psz_tmpdestfile++;
572     if( asprintf( &psz_destfile, "%s%s", psz_destdir, psz_tmpdestfile ) == -1 )
573         goto end;
574
575     p_file = vlc_fopen( psz_destfile, "w" );
576     if( !p_file )
577     {
578         msg_Err( p_udt, "Failed to open %s for writing", psz_destfile );
579         dialog_FatalWait( p_udt, _("Saving file failed"),
580             _("Failed to open \"%s\" for writing"),
581              psz_destfile );
582         goto end;
583     }
584
585     /* Create a buffer and fill it with the downloaded file */
586     p_buffer = (void *)malloc( 1 << 10 );
587     if( unlikely(p_buffer == NULL) )
588         goto end;
589
590     msg_Dbg( p_udt, "Downloading Stream '%s'", p_update->release.psz_url );
591
592     psz_size = size_str( l_size );
593
594     p_dialog_id =
595         vlc_dialog_display_progress( p_udt, false, 0.0, _("Cancel"),
596                                      ( "Downloading..."),
597                                      _("%s\nDownloading... %s/%s %.1f%% done"),
598                                      p_update->release.psz_url, "0.0", psz_size,
599                                      0.0 );
600
601     if( p_dialog_id == NULL )
602         goto end;
603
604     while( !atomic_load( &p_udt->aborted ) &&
605            ( i_read = vlc_stream_Read( p_stream, p_buffer, 1 << 10 ) ) &&
606            !vlc_dialog_is_cancelled( p_udt, p_dialog_id ) )
607     {
608         if( fwrite( p_buffer, i_read, 1, p_file ) < 1 )
609         {
610             msg_Err( p_udt, "Failed to write into %s", psz_destfile );
611             break;
612         }
613
614         l_downloaded += i_read;
615         psz_downloaded = size_str( l_downloaded );
616         f_progress = (float)l_downloaded/(float)l_size;
617
618         vlc_dialog_update_progress_text( p_udt, p_dialog_id, f_progress,
619                                          "%s\nDownloading... %s/%s - %.1f%% done",
620                                          p_update->release.psz_url,
621                                          psz_downloaded, psz_size,
622                                          f_progress*100 );
623         free( psz_downloaded );
624     }
625
626     /* Finish the progress bar or delete the file if the user had canceled */
627     fclose( p_file );
628     p_file = NULL;
629
630     if( !atomic_load( &p_udt->aborted ) &&
631         !vlc_dialog_is_cancelled( p_udt, p_dialog_id ) )
632     {
633         vlc_dialog_release( p_udt, p_dialog_id );
634         p_dialog_id = NULL;
635     }
636     else
637     {
638         vlc_unlink( psz_destfile );
639         goto end;
640     }
641
642     signature_packet_t sign;
643     if( download_signature( VLC_OBJECT( p_udt ), &sign,
644             p_update->release.psz_url ) != VLC_SUCCESS )
645     {
646         vlc_unlink( psz_destfile );
647
648         dialog_FatalWait( p_udt, _("File could not be verified"),
649             _("It was not possible to download a cryptographic signature for "
650               "the downloaded file \"%s\". Thus, it was deleted."),
651             psz_destfile );
652         msg_Err( p_udt, "Couldn't download signature of downloaded file" );
653         goto end;
654     }
655
656     if( memcmp( sign.issuer_longid, p_update->p_pkey->longid, 8 ) )
657     {
658         vlc_unlink( psz_destfile );
659         msg_Err( p_udt, "Invalid signature issuer" );
660         dialog_FatalWait( p_udt, _("Invalid signature"),
661             _("The cryptographic signature for the downloaded file \"%s\" was "
662               "invalid and could not be used to securely verify it. Thus, the "
663               "file was deleted."),
664             psz_destfile );
665         goto end;
666     }
667
668     if( sign.type != BINARY_SIGNATURE )
669     {
670         vlc_unlink( psz_destfile );
671         msg_Err( p_udt, "Invalid signature type" );
672         dialog_FatalWait( p_udt, _("Invalid signature"),
673             _("The cryptographic signature for the downloaded file \"%s\" was "
674               "invalid and could not be used to securely verify it. Thus, the "
675               "file was deleted."),
676             psz_destfile );
677         goto end;
678     }
679
680     uint8_t *p_hash = hash_from_file( psz_destfile, &sign );
681     if( !p_hash )
682     {
683         msg_Err( p_udt, "Unable to hash %s", psz_destfile );
684         vlc_unlink( psz_destfile );
685         dialog_FatalWait( p_udt, _("File not verifiable"),
686             _("It was not possible to securely verify the downloaded file"
687               " \"%s\". Thus, it was deleted."),
688             psz_destfile );
689
690         goto end;
691     }
692
693     if( p_hash[0] != sign.hash_verification[0] ||
694         p_hash[1] != sign.hash_verification[1] )
695     {
696         vlc_unlink( psz_destfile );
697         dialog_FatalWait( p_udt, _("File corrupted"),
698             _("Downloaded file \"%s\" was corrupted. Thus, it was deleted."),
699              psz_destfile );
700         msg_Err( p_udt, "Bad hash for %s", psz_destfile );
701         free( p_hash );
702         goto end;
703     }
704
705     if( verify_signature( &sign, &p_update->p_pkey->key, p_hash )
706             != VLC_SUCCESS )
707     {
708         vlc_unlink( psz_destfile );
709         dialog_FatalWait( p_udt, _("File corrupted"),
710             _("Downloaded file \"%s\" was corrupted. Thus, it was deleted."),
711              psz_destfile );
712         msg_Err( p_udt, "BAD SIGNATURE for %s", psz_destfile );
713         free( p_hash );
714         goto end;
715     }
716
717     msg_Info( p_udt, "%s authenticated", psz_destfile );
718     free( p_hash );
719
720 #ifdef _WIN32
721     const char *psz_msg =
722         _("The new version was successfully downloaded. "
723           "Do you want to close VLC and install it now?");
724     int answer = vlc_dialog_wait_question( p_udt, VLC_DIALOG_QUESTION_NORMAL,
725                                            _("Cancel"), _("Install"), NULL,
726                                            _("Update VLC media player"), "%s",
727                                            psz_msg );
728     if(answer == 1)
729     {
730         wchar_t psz_wdestfile[MAX_PATH];
731         MultiByteToWideChar( CP_UTF8, 0, psz_destfile, -1, psz_wdestfile, MAX_PATH );
732         answer = (int)ShellExecuteW( NULL, L"open", psz_wdestfile, NULL, NULL, SW_SHOW);
733         if(answer > 32)
734             libvlc_Quit(vlc_object_instance(p_udt));
735     }
736 #endif
737 end:
738     if( p_dialog_id != NULL )
739         vlc_dialog_release( p_udt, p_dialog_id );
740     if( p_stream )
741         vlc_stream_Delete( p_stream );
742     if( p_file )
743         fclose( p_file );
744     free( psz_destdir );
745     free( psz_destfile );
746     free( p_buffer );
747     free( psz_size );
748
749     vlc_restorecancel( canc );
750     return NULL;
751 }
752
753 update_release_t *update_GetRelease( update_t *p_update )
754 {
755     return &p_update->release;
756 }