]> git.sesse.net Git - vlc/blob - plugins/dvd/dvd_access.c
* ./extras/MacOSX_dvdioctl: removed outdated files.
[vlc] / plugins / dvd / dvd_access.c
1 /* dvd_access.c: DVD access plugin.
2  *****************************************************************************
3  * This plugins should handle all the known specificities of the DVD format,
4  * especially the 2048 bytes logical block size.
5  * It depends on:
6  *  -libdvdcss for access and unscrambling
7  *  -dvd_ifo for ifo parsing and analyse
8  *  -dvd_udf to find files
9  *****************************************************************************
10  * Copyright (C) 1998-2001 VideoLAN
11  * $Id: dvd_access.c,v 1.9 2002/03/18 19:14:52 sam Exp $
12  *
13  * Author: Stéphane Borel <stef@via.ecp.fr>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  * 
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <videolan/vlc.h>
38
39 #ifdef HAVE_UNISTD_H
40 #   include <unistd.h>
41 #endif
42
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <string.h>
47 #include <errno.h>
48
49 #ifdef STRNCASECMP_IN_STRINGS_H
50 #   include <strings.h>
51 #endif
52
53 #ifdef GOD_DAMN_DMCA
54 #   include "dummy_dvdcss.h"
55 #else
56 #   include <videolan/dvdcss.h>
57 #endif
58
59 #include "stream_control.h"
60 #include "input_ext-intf.h"
61 #include "input_ext-dec.h"
62 #include "input_ext-plugins.h"
63
64 #include "dvd.h"
65 #include "dvd_es.h"
66 #include "dvd_seek.h"
67 #include "dvd_ifo.h"
68 #include "dvd_summary.h"
69 #include "iso_lang.h"
70
71 #include "debug.h"
72
73 /*****************************************************************************
74  * Local prototypes
75  *****************************************************************************/
76
77 /* called from outside */
78 static int  DVDOpen         ( struct input_thread_s * );
79 static void DVDClose        ( struct input_thread_s * );
80 static int  DVDSetArea      ( struct input_thread_s *, struct input_area_s * );
81 static int  DVDSetProgram   ( struct input_thread_s *, pgrm_descriptor_t * );
82 static int  DVDRead         ( struct input_thread_s *, byte_t *, size_t );
83 static void DVDSeek         ( struct input_thread_s *, off_t );
84
85 static char * DVDParse( input_thread_t * );
86
87 /*****************************************************************************
88  * Functions exported as capabilities. They are declared as static so that
89  * we don't pollute the namespace too much.
90  *****************************************************************************/
91 void _M( access_getfunctions)( function_list_t * p_function_list )
92 {
93 #define input p_function_list->functions.access
94     input.pf_open             = DVDOpen;
95     input.pf_close            = DVDClose;
96     input.pf_read             = DVDRead;
97     input.pf_set_area         = DVDSetArea;
98     input.pf_set_program      = DVDSetProgram;
99     input.pf_seek             = DVDSeek;
100 #undef input
101 }
102
103 /*
104  * Data access functions
105  */
106
107 #define DVDLB     p_dvd->i_vts_start + p_dvd->i_vts_lb
108 #define DVDTell   LB2OFF( p_dvd->i_vts_start + p_dvd->i_vts_lb ) \
109                   - p_input->stream.p_selected_area->i_start
110
111 /*****************************************************************************
112  * DVDOpen: open dvd
113  *****************************************************************************/
114 static int DVDOpen( struct input_thread_s *p_input )
115 {
116     char *               psz_device;
117     thread_dvd_data_t *  p_dvd;
118     input_area_t *       p_area;
119     int                  i;
120
121     p_dvd = malloc( sizeof(thread_dvd_data_t) );
122     if( p_dvd == NULL )
123     {
124         intf_ErrMsg( "dvd error: out of memory" );
125         return -1;
126     }
127     p_input->p_access_data = (void *)p_dvd;
128     
129     /* Parse command line */
130     if( !( psz_device = DVDParse( p_input ) ) )
131     {
132         free( p_dvd );
133         return -1;
134     }
135     
136     /* 
137      * set up input
138      */ 
139     p_input->i_mtu = 0;
140
141     /*
142      *  get plugin ready
143      */ 
144     p_dvd->dvdhandle = dvdcss_open( psz_device );
145     
146     /* free allocated string */
147     free( psz_device );
148
149     if( p_dvd->dvdhandle == NULL )
150     {
151         intf_ErrMsg( "dvd error: dvdcss can't open device" );
152         free( p_dvd );
153         return -1;
154     }
155
156     if( dvdcss_seek( p_dvd->dvdhandle, 0, DVDCSS_NOFLAGS ) < 0 )
157     {
158         intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
159         dvdcss_close( p_dvd->dvdhandle );
160         free( p_dvd );
161         return -1;
162     }
163
164     /* Ifo allocation & initialisation */
165     if( IfoCreate( p_dvd ) < 0 )
166     {
167         intf_ErrMsg( "dvd error: allcation error in ifo" );
168         dvdcss_close( p_dvd->dvdhandle );
169         free( p_dvd );
170         return -1;
171     }
172
173     if( IfoInit( p_dvd->p_ifo ) < 0 )
174     {
175         intf_ErrMsg( "dvd error: fatal failure in ifo" );
176         IfoDestroy( p_dvd->p_ifo );
177         dvdcss_close( p_dvd->dvdhandle );
178         free( p_dvd );
179         return -1;
180     }
181
182     /* Set stream and area data */
183     vlc_mutex_lock( &p_input->stream.stream_lock );
184
185     p_input->stream.i_method = INPUT_METHOD_DVD;
186     p_input->stream.b_pace_control = 1;
187     p_input->stream.b_seekable = 1;
188     p_input->stream.p_selected_area->i_size = 0;
189     p_input->stream.p_selected_area->i_tell = 0;
190
191     /* Initialize ES structures */
192     input_InitStream( p_input, sizeof( stream_ps_data_t ) );
193
194 #define title_inf p_dvd->p_ifo->vmg.title_inf
195     intf_WarnMsg( 3, "dvd info: number of titles: %d", title_inf.i_title_nb );
196
197 #define area p_input->stream.pp_areas
198     /* We start from 1 here since the default area 0
199      * is reserved for video_ts.vob */
200     for( i = 1 ; i <= title_inf.i_title_nb ; i++ )
201     {
202         input_AddArea( p_input );
203
204         /* Titles are Program Chains */
205         area[i]->i_id = i;
206
207         /* Absolute start offset and size 
208          * We can only set that with vts ifo, so we do it during the
209          * first call to DVDSetArea */
210         area[i]->i_start = 0;
211         area[i]->i_size = 0;
212
213         /* Number of chapters */
214         area[i]->i_part_nb = title_inf.p_attr[i-1].i_chapter_nb;
215         area[i]->i_part = 1;
216
217         /* Offset to vts_i_0.ifo */
218         area[i]->i_plugin_data = p_dvd->p_ifo->i_start +
219                        title_inf.p_attr[i-1].i_start_sector;
220     }   
221 #undef area
222     
223     p_dvd->i_title = p_dvd->i_title <= title_inf.i_title_nb ?
224                      p_dvd->i_title : 1;
225 #undef title_inf
226
227     p_area = p_input->stream.pp_areas[p_dvd->i_title];
228     
229     p_area->i_part = p_dvd->i_chapter <= p_area->i_part_nb ?
230                      p_dvd->i_chapter : 1;
231     p_dvd->i_chapter = 1;
232     
233     p_dvd->b_new_chapter = 0;
234     p_dvd->i_audio_nb = 0;
235     p_dvd->i_spu_nb = 0;
236     
237     /* set title, chapter, audio and subpic */
238     if( DVDSetArea( p_input, p_area ) < 0 )
239     {
240         vlc_mutex_unlock( &p_input->stream.stream_lock );
241         IfoDestroy( p_dvd->p_ifo );
242         dvdcss_close( p_dvd->dvdhandle );
243         free( p_dvd );
244         return -1;
245     }
246
247     vlc_mutex_unlock( &p_input->stream.stream_lock );
248
249     p_input->psz_demux = "dvd";
250
251     return 0;
252 }
253
254 /*****************************************************************************
255  * DVDClose: close dvd
256  *****************************************************************************/
257 static void DVDClose( struct input_thread_s *p_input )
258 {
259     thread_dvd_data_t *p_dvd = (thread_dvd_data_t*)p_input->p_access_data;
260
261     IfoDestroy( p_dvd->p_ifo );
262     dvdcss_close( p_dvd->dvdhandle );
263     free( p_dvd );
264 }
265
266 /*****************************************************************************
267  * DVDSetProgram: used to change angle
268  *****************************************************************************/
269 static int DVDSetProgram( input_thread_t    * p_input,
270                           pgrm_descriptor_t * p_program ) 
271 {
272     if( p_input->stream.p_selected_program != p_program )
273     {
274         thread_dvd_data_t *  p_dvd;
275         int                  i_angle;
276     
277         p_dvd   = (thread_dvd_data_t*)(p_input->p_access_data);
278         i_angle = p_program->i_number;
279
280         /* DVD is actually mono-program: we only need the current angle
281          * number, so copy the data between programs */
282         memcpy( p_program,
283                 p_input->stream.p_selected_program,
284                 sizeof(pgrm_descriptor_t) );
285         p_program->i_number                = i_angle;
286         p_input->stream.p_selected_program = p_program;
287
288 #define title \
289     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
290         if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 )
291         {
292             if( ( p_program->i_number - p_dvd->i_angle ) < 0 )
293             {
294                 /* we have to go backwards */
295                 p_dvd->i_map_cell = 0;
296             }
297             p_dvd->i_prg_cell += ( p_program->i_number - p_dvd->i_angle );
298             p_dvd->i_map_cell =  CellPrg2Map( p_dvd );
299             p_dvd->i_map_cell += p_dvd->i_angle_cell;
300             p_dvd->i_vts_lb   =  CellStartSector( p_dvd );
301             p_dvd->i_end_lb   =  CellEndSector( p_dvd );
302             p_dvd->i_angle    =  p_program->i_number;
303         }
304         else
305         {
306             p_dvd->i_angle    =  p_program->i_number;
307         }
308 #undef title
309         intf_WarnMsg( 3, "dvd info: angle %d selected", p_dvd->i_angle );
310     }
311
312     return 0;
313 }
314
315 /*****************************************************************************
316  * DVDSetArea: initialize input data for title x, chapter y.
317  * It should be called for each user navigation request.
318  *****************************************************************************
319  * Take care that i_title starts from 0 (vmg) and i_chapter start from 1.
320  * Note that you have to take the lock before entering here.
321  *****************************************************************************/
322 #define vmg p_dvd->p_ifo->vmg
323 #define vts p_dvd->p_ifo->vts
324
325 static void DVDFlushStream( input_thread_t * p_input )
326 {
327     if( p_input->stream.pp_programs != NULL )
328     {
329         /* We don't use input_EndStream here since
330          * we keep area structures */
331         while( p_input->stream.i_es_number )
332         {
333             input_DelES( p_input, p_input->stream.pp_es[0] );
334         }
335         
336         while( p_input->stream.i_pgrm_number )
337         {
338             input_DelProgram( p_input, p_input->stream.pp_programs[0] );
339         }
340
341         if( p_input->stream.pp_selected_es )
342         {
343             free( p_input->stream.pp_selected_es );
344             p_input->stream.pp_selected_es = NULL;
345         }
346         p_input->stream.i_selected_es_number = 0;
347     }
348
349     return;
350 }
351
352 static int DVDReadAngle( input_thread_t * p_input )
353 {
354     thread_dvd_data_t * p_dvd;
355     int                 i_angle_nb;
356     int                 i;
357
358     p_dvd      = (thread_dvd_data_t*)(p_input->p_access_data);
359     i_angle_nb = vmg.title_inf.p_attr[p_dvd->i_title-1].i_angle_nb;
360     
361     input_AddProgram( p_input, 1, sizeof( stream_ps_data_t ) );
362     p_input->stream.p_selected_program = p_input->stream.pp_programs[0];
363
364     for( i = 1 ; i < i_angle_nb ; i++ )
365     {
366         input_AddProgram( p_input, i+1, 0 );
367     }
368
369     return i_angle_nb;
370 }
371
372 static int DVDSetArea( input_thread_t * p_input, input_area_t * p_area )
373 {
374     thread_dvd_data_t *  p_dvd;
375
376     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
377
378     /* we can't use the interface slider until initilization is complete */
379     p_input->stream.b_seekable = 0;
380
381     if( p_area != p_input->stream.p_selected_area )
382     {
383         int     i_vts_title;
384         u32     i_start;
385         u32     i_size;
386
387         /* Reset the Chapter position of the old title */
388         p_input->stream.p_selected_area->i_part = 1;
389         p_input->stream.p_selected_area         = p_area;
390
391         /*
392          *  We have to load all title information
393          */
394
395         /* title number as it appears in the interface list */
396         p_dvd->i_title      = p_area->i_id;
397         p_dvd->i_chapter_nb = p_area->i_part_nb;
398
399         if( IfoTitleSet( p_dvd->p_ifo, p_dvd->i_title ) < 0 )
400         {
401             intf_ErrMsg( "dvd error: fatal error in vts ifo" );
402             free( p_dvd );
403             return -1;
404         }
405
406         /* title position inside the selected vts */
407         i_vts_title       = vmg.title_inf.p_attr[p_dvd->i_title-1].i_title_num;
408         p_dvd->i_title_id =
409             vts.title_inf.p_title_start[i_vts_title-1].i_title_id;
410
411         intf_WarnMsg( 3, "dvd: title %d vts_title %d pgc %d",
412                       p_dvd->i_title, i_vts_title, p_dvd->i_title_id );
413
414         /* title set offset XXX: convert to block values */
415         p_dvd->i_vts_start =
416             vts.i_pos + vts.manager_inf.i_title_vob_start_sector;
417
418         /* last cell */
419         p_dvd->i_prg_cell = -1 +
420             vts.title_unit.p_title[p_dvd->i_title_id-1].title.i_cell_nb;
421         p_dvd->i_map_cell = 0;
422         p_dvd->i_map_cell = CellPrg2Map( p_dvd );
423         i_size            = CellEndSector( p_dvd );
424
425         /* first cell */
426         p_dvd->i_prg_cell   = 0;
427         p_dvd->i_map_cell   = 0;
428         p_dvd->i_angle_cell = 0;
429         p_dvd->i_map_cell   = CellPrg2Map    ( p_dvd );
430         p_dvd->i_vts_lb     = CellStartSector( p_dvd );
431         p_dvd->i_end_lb     = CellEndSector  ( p_dvd );
432
433         /* Force libdvdcss to check its title key.
434          * It is only useful for title cracking method. Methods using the
435          * decrypted disc key are fast enough to check the key at each seek */
436         if( ( i_start = dvdcss_seek( p_dvd->dvdhandle, DVDLB,
437                                      DVDCSS_SEEK_KEY ) ) < 0 )
438         {
439             intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
440             return -1;
441         }
442
443         i_size -= p_dvd->i_vts_lb + 1;
444
445         /* Area definition */
446         p_input->stream.p_selected_area->i_start = LB2OFF( i_start );
447         p_input->stream.p_selected_area->i_size  = LB2OFF( i_size );
448
449         /* Destroy obsolete ES by reinitializing programs */
450         DVDFlushStream( p_input );
451         
452         /* Angle management: angles are handled through programs */
453         p_dvd->i_angle_nb = DVDReadAngle( p_input );
454         if( ( p_dvd->i_angle <= 0 ) || p_dvd->i_angle > p_dvd->i_angle_nb )
455         {
456             p_dvd->i_angle = 1;
457         }
458        
459         DVDSetProgram( p_input,
460                        p_input->stream.pp_programs[p_dvd->i_angle-1] ); 
461
462         intf_WarnMsg( 3, "dvd info: title start: %d size: %d",
463                          i_start, i_size );
464         IfoPrintTitle( p_dvd );
465
466         /* No PSM to read in DVD mode, we already have all information */
467         p_input->stream.p_selected_program->b_is_ok = 1;
468
469         /* Find all ES in title with ifo data */
470         DVDReadVideo( p_input );
471         DVDReadAudio( p_input );
472         DVDReadSPU  ( p_input );
473    
474         if( p_input->p_demux_module )
475         {
476             DVDLaunchDecoders( p_input );
477         }
478
479     } /* i_title >= 0 */
480     else
481     {
482         p_area = p_input->stream.p_selected_area;
483     }
484
485     /* Chapter selection */
486     p_dvd->i_chapter = DVDSetChapter( p_dvd, p_area->i_part );
487     
488     p_input->stream.p_selected_area->i_tell = DVDTell;
489
490     /* warn interface that something has changed */
491     p_input->stream.b_seekable = 1;
492     p_input->stream.b_changed  = 1;
493
494     return 0;
495 }
496 #undef vts
497 #undef vmg
498
499 #define title \
500     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
501     
502 /*****************************************************************************
503  * DVDRead: reads data packets.
504  *****************************************************************************
505  * Returns -1 in case of error, 0 in case of EOF, otherwise the number of
506  * bytes.
507  *****************************************************************************/
508 static int DVDRead( input_thread_t * p_input,
509                     byte_t * p_buffer, size_t i_count )
510 {
511     thread_dvd_data_t *     p_dvd;
512     int                     i_read;
513     int                     i_blocks;
514     int                     i_block_once = 0;
515
516     p_dvd = (thread_dvd_data_t *)(p_input->p_access_data);
517
518     i_read = 0;
519     i_blocks = OFF2LB(i_count);
520
521     while( i_blocks )
522     {
523         if( ( i_block_once = __MIN( LbMaxOnce( p_dvd ), i_blocks ) ) <= 0 )
524         {
525             /* EOT */
526             break;
527         }
528
529         if( i_block_once != dvdcss_read( p_dvd->dvdhandle, p_buffer,
530                                          i_block_once, DVDCSS_READ_DECRYPT ) )
531         {
532             return -1;
533         }
534
535         i_blocks -= i_block_once;
536         i_read += i_block_once;
537         p_buffer += LB2OFF( i_block_once );
538
539         /* Update global position */
540         p_dvd->i_vts_lb += i_block_once;
541     }
542
543     vlc_mutex_lock( &p_input->stream.stream_lock );
544
545     p_input->stream.p_selected_area->i_tell += LB2OFF( i_read );
546     if( p_dvd->b_new_chapter )
547     {
548         p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
549         p_dvd->b_new_chapter                    = 0;
550     }
551
552     if( ( p_input->stream.p_selected_area->i_tell
553             >= p_input->stream.p_selected_area->i_size )
554        || ( i_block_once  <= 0 ) )
555     {
556         if( ( p_dvd->i_title + 1 ) >= p_input->stream.i_area_nb )
557         {
558             /* EOF */
559             vlc_mutex_unlock( &p_input->stream.stream_lock );
560             return 0;
561         }
562
563         /* EOT */
564         intf_WarnMsg( 4, "dvd info: new title" );
565         p_dvd->i_title++;
566         DVDSetArea( p_input, p_input->stream.pp_areas[p_dvd->i_title] );
567     }
568
569     vlc_mutex_unlock( &p_input->stream.stream_lock );
570
571     return LB2OFF( i_read );
572 }
573
574 /*****************************************************************************
575  * DVDSeek : Goes to a given position on the stream.
576  *****************************************************************************
577  * This one is used by the input and translate chronological position from
578  * input to logical position on the device.
579  * The lock should be taken before calling this function.
580  *****************************************************************************/
581 static void DVDSeek( input_thread_t * p_input, off_t i_off )
582 {
583     thread_dvd_data_t *     p_dvd;
584     
585     p_dvd = ( thread_dvd_data_t * )(p_input->p_access_data);
586
587     vlc_mutex_lock( &p_input->stream.stream_lock );
588     p_dvd->i_vts_lb = OFF2LB(i_off + p_input->stream.p_selected_area->i_start)
589                        - p_dvd->i_vts_start;
590     vlc_mutex_unlock( &p_input->stream.stream_lock );
591
592     p_dvd->i_prg_cell = Lb2CellPrg( p_dvd );
593     p_dvd->i_map_cell = Lb2CellMap( p_dvd );
594     
595     if( CellIsInterleaved( p_dvd ) )
596     {
597         /* if we're inside a multi-angle zone, we have to choose i_sector
598          * in the current angle ; we can't do it all the time since cells
599          * can be very wide out of such zones */
600         p_dvd->i_vts_lb = CellStartSector( p_dvd );
601     }
602     
603     p_dvd->i_end_lb   = CellEndSector  ( p_dvd );
604     p_dvd->i_chapter  = CellPrg2Chapter( p_dvd );
605
606     if( dvdcss_seek( p_dvd->dvdhandle, DVDLB,
607                      DVDCSS_SEEK_MPEG ) < 0 )
608     {
609         intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
610         p_input->b_error = 1;
611         return;
612     }
613
614     vlc_mutex_lock( &p_input->stream.stream_lock );
615     p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
616     p_input->stream.p_selected_area->i_tell = DVDTell;
617     vlc_mutex_unlock( &p_input->stream.stream_lock );
618
619     intf_WarnMsg( 4, "Program Cell: %d Cell: %d Chapter: %d tell %lld",
620                      p_dvd->i_prg_cell, p_dvd->i_map_cell, p_dvd->i_chapter, DVDTell );
621
622     return;
623 }
624
625 /*****************************************************************************
626  * DVDParse: parse command line
627  *****************************************************************************/
628 static char * DVDParse( input_thread_t * p_input )
629 {
630     thread_dvd_data_t *  p_dvd;
631     struct stat          stat_info;
632     char *               psz_parser;
633     char *               psz_device;
634     char *               psz_raw;
635     char *               psz_next;
636     boolean_t            b_options = 0;
637     int                  i_title = 1;
638     int                  i_chapter = 1;
639     int                  i_angle = 1;
640     int                  i;
641     
642     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
643
644     psz_parser = psz_device = strdup( p_input->psz_name );
645     if( !psz_parser )
646     {
647         return NULL;
648     }
649
650     /* Parse input string :
651      * [device][@rawdevice][@[title][,[chapter][,angle]]] */
652     while( *psz_parser && *psz_parser != '@' )
653     {
654         psz_parser++;
655     }
656
657     if( *psz_parser == '@' )
658     {
659         /* Maybe found raw device or option list */
660         *psz_parser = '\0';
661         psz_raw = ++psz_parser;
662     }
663     else
664     {
665         psz_raw = "";
666     }
667
668     if( *psz_parser && !strtol( psz_parser, NULL, 10 ) )
669     {
670         /* what we've found is either a raw device or a partial option
671          * list e.g. @,29 or both a device and a list ; search end of string */
672         while( *psz_parser && *psz_parser != '@' )
673         {
674             psz_parser++;
675         }
676         
677         if( *psz_parser == '@' )
678         {
679             /* found end of raw device, and beginning of options */
680             *psz_parser = '\0';
681             ++psz_parser;
682             b_options = 1;
683         }
684         else
685         {
686             psz_parser = psz_raw + 1;
687             for( i=0 ; i<3 ; i++ )
688             {
689                 if( !*psz_parser )
690                 {
691                     /* we have only a raw device */
692                     break;
693                 }
694                 if( strtol( psz_parser, NULL, 10 ) )
695                 {
696                     /* we have only a partial list of options, no device */
697                     psz_parser = psz_raw;
698                     psz_raw = "";
699                     b_options = 1;
700                     break;
701                 }
702                 psz_parser++;
703             }
704         }
705     }
706     else
707     {
708         /* found beginning of options ; no raw device specified */
709         psz_raw = "";
710         b_options = 1;
711     }
712
713     if( b_options )
714     {
715         /* Found options */
716         i_title = (int)strtol( psz_parser, &psz_next, 10 );
717         if( *psz_next )
718         {
719             psz_parser = psz_next + 1;
720             i_chapter = (int)strtol( psz_parser, &psz_next, 10 );
721             if( *psz_next )
722             {
723                 i_angle = (int)strtol( psz_next + 1, NULL, 10 );
724             }
725         }
726
727         p_dvd->i_title = i_title ? i_title : 1;
728         p_dvd->i_chapter = i_chapter ? i_chapter : 1;
729         p_dvd->i_angle = i_angle ? i_angle : 1;
730     }
731
732     if( *psz_raw )
733     {
734         if( *psz_raw )
735         {
736             /* check the raw device */
737             if( stat( psz_raw, &stat_info ) == -1 )
738             {
739                 intf_WarnMsg( 3, "dvd warning: cannot stat() raw"
740                                 " device `%s' (%s)",
741                              psz_raw, strerror(errno));
742                 /* put back '@' */
743                 *(psz_raw - 1) = '@';
744                 psz_raw = "";
745             }
746             else
747             {
748                 char * psz_env;
749                 
750 #ifndef WIN32    
751                 if( !S_ISCHR(stat_info.st_mode) )
752                 {
753                     intf_WarnMsg( 3, "dvd warning: raw device %s is"
754                                      " not a valid char device", psz_raw );
755                     /* put back '@' */
756                     *(psz_raw - 1) = '@';
757                     psz_raw = "";
758                 }
759                 else
760 #endif
761                 {
762                     psz_env = malloc( strlen("DVDCSS_RAW_DEVICE=")
763                                     + strlen( psz_raw ) + 1 );
764                     sprintf( psz_env, "DVDCSS_RAW_DEVICE=%s", psz_raw );
765                     putenv( psz_env );
766                 }
767             }
768         }
769         else
770         {
771             psz_raw = "";
772         }
773     }
774     
775     if( !*psz_device )
776     {
777         free( psz_device );
778         
779         if( !p_input->psz_access )
780         {
781             /* no device and no access specified: we probably don't want DVD */
782             return NULL;
783         }
784         psz_device = config_GetPszVariable( "dvd_device" );
785     }
786
787     /* check block device */
788     if( stat( psz_device, &stat_info ) == -1 )
789     {
790         intf_ErrMsg( "dvd error: cannot stat() device `%s' (%s)",
791                      psz_device, strerror(errno));
792         return NULL;                    
793     }
794     
795 #ifndef WIN32    
796     if( !S_ISBLK(stat_info.st_mode) && !S_ISCHR(stat_info.st_mode) )
797     {
798         intf_WarnMsg( 3, "input: DVD plugin discarded"
799                          " (not a valid block device)" );
800         return NULL;
801     }
802 #endif
803     
804     intf_WarnMsg( 2, "input: dvd=%s raw=%s title=%d chapter=%d angle=%d",
805                   psz_device, psz_raw, p_dvd->i_title,
806                   p_dvd->i_chapter, p_dvd->i_angle );
807
808     return psz_device;
809