1 /*$Id: pdixtract.c,v 1.5 2003/02/27 15:08:50 smurf Exp $*/
2 /* Convert Pinnacle Disk Images (i.e. .pdi file sets) to ISO9660 files */
3 /* or extract files from the file sets */
4 
5 /*
6 pdixtract is distributed in the hope that it will be useful, but WITHOUT ANY
7 WARRANTY.  No author or distributor accepts responsibility to anyone for the
8 consequences of using it or for whether it serves any particular purpose or
9 works at all, unless he says so in writing.  Refer to the GNU General Public
10 License (the "GPL") for full details.
11 
12 Everyone is granted permission to copy, modify and redistribute pdixtract,
13 but only under the conditions described in the GPL.  A copy of this license
14 is supposed to have been given to you along with pdixtract so you can know
15 your rights and responsibilities.  It should be in a file named COPYLEFT,
16 or, if there is no file named COPYLEFT, a file named COPYING.  Among other
17 things, the copyright notice and this notice must be preserved on all
18 copies.
19 
20 We explicitly state here what we believe is already implied by the GPL: if
21 the pdixtract program is distributed as a separate set of sources and a
22 separate executable file which are aggregated on a storage medium together
23 with another program, this in itself does not bring the other program under
24 the GPL, nor does the mere fact that such a program or the procedures for
25 constructing it invoke the pdixtract executable bring any other part of the
26 program under the GPL.
27 */
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 
36 #if defined(__WINDOWS__) || defined(__NT__)
37 #include <io.h>
38 #include <direct.h>
39 #define MAKE_DIR(x) mkdir(x)
40 #define PATH_SEP '\\'
41 #define STRCMP(a,b) strcmpi(a,b)
42 #define STRNCMP(a,b,l) strnicmp(a,b,l)
43 #else
44 #define MAKE_DIR(x) mkdir(x,0775)
45 #define STRCMP(a,b) strcasecmp(a,b)
46 #define STRNCMP(a,b,l) strncasecmp(a,b,l)
47 #define PATH_SEP '/'
48 #endif
49 
50 #ifndef O_BINARY
51 #define O_BINARY 0
52 #endif
53 
54 #define DEBUGP(a) /* fprintf a */
55 #define PDI_HEADER     0x130
56 #define PDI_FIRST_DESC (0x22800+PDI_HEADER)
57 #define PDI_DIR_MAX    20480
58 #define MAX_FILES      400
59 #define BUFFSIZE       20*1024*1024
60 
61 /*
62  * The isofs filesystem constants/structures
63  */
64 
65 /* This part borrowed from the bsd386 isofs */
66 #define ISODCL(from, to) (to - from + 1)
67 
68 struct iso_directory_record {
69         unsigned char length                    [ISODCL (1, 1)]; /* length of descriptor */
70         unsigned char ext_attr_length           [ISODCL (2, 2)]; /* length of extended attributes, 0 in .pdi-files */
71         unsigned char extent                    [ISODCL (3, 10)]; /* position of file in image in multiple of sector size, 32Bit-LSB, 32Bit-MSB */
72         unsigned char size                      [ISODCL (11, 18)]; /* size of file, 32Bit-LSB, 32Bit-MSB */
73         unsigned char date                      [ISODCL (19, 25)]; /* Year-1900, month, day, hour,min,sec */
74         unsigned char flags                     [ISODCL (26, 26)]; /* 00 for files */
75         unsigned char file_unit_size            [ISODCL (27, 27)]; /* 00 in .pdi */
76         unsigned char interleave                [ISODCL (28, 28)]; /* 00 in .pdi */
77         unsigned char volume_sequence_number    [ISODCL (29, 32)]; /* 01 in .pdi, 16Bit-LSB, 16Bit-MSB */
78         unsigned char name_len          [ISODCL (33, 33)]; /* length of name */
79         char name                       [1];
80 };
81 
82 struct filedesc {
83   char *name;
84   size_t size; /* size in extents */
85   size_t extent;
86   int hits; /* flag for individual file selection */
87 };
88 
89 char buffer[BUFFSIZE];
90 struct filedesc files[MAX_FILES];
91 
92 char id[]="$Id: pdixtract.c,v 1.5 2003/02/27 15:08:50 smurf Exp $";
93 
94 /* append [ext] to [in] if not already present */
95 char *
file_with_ext(const char * in,const char * ext)96 file_with_ext(const char*in,const char*ext){
97   size_t in_l=strlen(in);
98   size_t ext_l=strlen(ext);
99   char * out;
100   if(STRCMP(in+in_l-ext_l,ext)){
101     out=malloc(in_l+ext_l+1);
102     memcpy(out,in,in_l);
103     memcpy(out+in_l,ext,ext_l+1);
104     return out;
105   }
106   return strdup(in);
107 }
108 
109 /* errexit is called after a fatal error that prevents us from continuing */
errexit(const char * err)110 void errexit(const char*err){
111   perror(err);
112   exit(1);
113 }
114 
115 
116 
117 /* helper for qsort, compares extents of 2 filedescs */
118 int
filedesc_cmp(struct filedesc * a,struct filedesc * b)119 filedesc_cmp(struct filedesc*a,struct filedesc*b){
120   return a->extent-b->extent;
121 }
122 
123 /* read directory from first pdi-file into files, returns no of files */
124 int
pdi_read_dir(int fin)125 pdi_read_dir(int fin){
126   size_t off=0; /* offset into buffer for current dir-record */
127   size_t size,extent;
128   size_t fdscno=0;
129   struct iso_directory_record *dirp;
130   lseek(fin,PDI_FIRST_DESC,SEEK_SET);
131   if(read(fin,buffer,PDI_DIR_MAX)<0)
132     errexit("during read of directory");
133   off=0;
134   printf("[extent ] File Name    Size       Date       Time\n");
135   while(buffer[off]){
136     dirp=(struct iso_directory_record*)&buffer[off];
137     if(!dirp->flags[0]&&dirp->name[0]&&dirp->name_len[0]){
138       size=dirp->size[0]+0x100*dirp->size[1]+
139         0x10000*dirp->size[2]+0x1000000*dirp->size[3];
140       extent=dirp->extent[0]+0x100*dirp->extent[1]+
141         0x10000*dirp->extent[2]+0x1000000*dirp->extent[3];
142       printf("[%7i] %12s %10i %2i.%2i.%4i %02i:%02i:%02i\n",
143              extent,dirp->name,size,
144              dirp->date[2],dirp->date[1],dirp->date[0]+1900,
145              dirp->date[3],dirp->date[4],dirp->date[5]);
146       files[fdscno].size=size/0x800;
147       files[fdscno].name=strdup(dirp->name);
148       files[fdscno].extent=extent;
149       files[fdscno].hits=1;
150       fdscno++;
151     }
152     off+=dirp->length[0];
153     if(!buffer[off]){ /* next directory record to large for current extent */
154       off=(off+0x7ff)&0xfffff800; /* skip to next extent */
155     }
156   }
157   //  qsort(files,fdscno,sizeof(struct filedesc),filedesc_cmp);
158   qsort(files,fdscno,sizeof(struct filedesc),
159         (int (*) (const void *,const void *))filedesc_cmp);
160   return fdscno;
161 }
162 
163 void
usage()164 usage(){
165   fprintf(stderr,
166           "usage: pdixtract [options] <pdi-file-name>[.pdi] [files...]\n"
167           "\n"
168           "options:\n"
169           "-l        list only (directory mode)\n"
170           "-i <iso>  convert .pdi-file-set into .iso-image <iso>[.iso]\n"
171           /*
172           "-n <name> (in combination with -i) set the iso volume name\n"
173           */
174           "-r        remove .pdi files as soon as possible\n"
175           "-d <dir>  extract files to directory <dir>\n"
176           "-m <dir>  create directory <dir> and extract files int it\n"
177           "-v        create VIDEO_TS directory and extract files into it\n"
178           "-x        same as -r -m <pdi-file-name> -v\n"
179           "\n"
180           "a trailing '*' may be used as wildcard for file selection,\n"
181           "e.g. pdixtract -v movie vts_01*\n"
182           "extracts Video Title Set 1 from movie.pdi* to dir VIDEO_TS\n"
183           );
184   exit(1);
185 }
186 
187 /* global data describing the input data */
188 int current_segment=0;    /* number of current input file */
189 int delete_source=0;      /* flag for deletion of un-needed sources */
190 char current_file[0x300]; /* file name of current input */
191 char *infile; /* name of very first input file */
192 int fin; /* os file descriptor of current input */
193 struct {
194   size_t first_extent; /* first whole extent in current file */
195   size_t no_extents; /* number of whole extents in current file */
196   size_t off; /* offset to first extent in current file */
197   size_t size; /* size of current file in bytes */
198 } pdi_segment[6];
199 
200 /* open a new .pdi segment and prepare it for reading */
201 void
load_new_source()202 load_new_source(){
203   long l;
204   close(fin);
205   if(delete_source&&unlink(current_file))
206     perror(current_file); /* failed deletion is not fatal */
207   current_segment++;
208   snprintf(current_file,sizeof(current_file)-1,
209            "%s%02i",infile,current_segment);
210   fin=open(current_file,O_RDONLY+O_BINARY);
211   if(fin<0)
212     errexit(current_file);
213   fprintf(stderr,"(%s)",current_file);
214   /* now check files' size */
215   l=lseek(fin,0,SEEK_END);
216   if(l<0)
217     errexit(current_file);
218   pdi_segment[current_segment].size=l;
219   pdi_segment[current_segment].off=
220     (pdi_segment[current_segment-1].size-pdi_segment[current_segment-1].off)%0x800;
221   pdi_segment[current_segment].first_extent=
222     pdi_segment[current_segment-1].first_extent+
223     (pdi_segment[current_segment-1].size-pdi_segment[current_segment-1].off)/0x800;
224   if(pdi_segment[current_segment].off){
225     pdi_segment[current_segment].off=0x800-pdi_segment[current_segment].off;
226     pdi_segment[current_segment].first_extent++;
227   }
228   pdi_segment[current_segment].no_extents=(l-pdi_segment[current_segment].off)/0x800;
229   DEBUGP((stderr,"\nseg %i:off(%i) first(%i) no(%i) size(%i)\n",
230           current_segment,
231           pdi_segment[current_segment].off,
232           pdi_segment[current_segment].first_extent,
233           pdi_segment[current_segment].no_extents,
234           pdi_segment[current_segment].size));
235 }
236 /* write no_extents blocks of 0x800 to fout, starting with first_extent */
237 /* all requested extents are contained in current input */
238 void
copy_extents(int fout,size_t first_extent,long no_extents)239 copy_extents(int fout,size_t first_extent, long no_extents){
240   DEBUGP((stderr,"copy_segments: first=%i, no=%li\n",
241           first_extent,no_extents));
242   if(0>lseek(fin,
243              pdi_segment[current_segment].off +
244              (first_extent-pdi_segment[current_segment].first_extent)*0x800,
245              SEEK_SET))
246     errexit("during skip of .pdi-data");
247   no_extents*=0x800;
248   while(no_extents>BUFFSIZE){
249     if(BUFFSIZE>read(fin,buffer,BUFFSIZE))
250       errexit("during read of extents");
251     if(BUFFSIZE>write(fout,buffer,BUFFSIZE))
252       errexit("during write of extents");
253     no_extents-=BUFFSIZE;
254     putc('.',stderr);
255   }
256   if(no_extents>read(fin,buffer,no_extents))
257     errexit("during read of extents");
258   if(no_extents>write(fout,buffer,no_extents))
259     errexit("during write of extents");
260 }
261 /* write no_extents blocks of 0x800 to fout, starting with first_extent */
262 /* manage segment boundaries */
263 void
write_to_file(int fout,size_t first_extent,size_t no_extents)264 write_to_file(int fout,size_t first_extent, size_t no_extents)
265 {
266   DEBUGP((stderr,"[%i..%i]",first_extent,first_extent+no_extents-1));
267   /* skip all input files, that do not contain parts of desired output */
268   while(pdi_segment[current_segment].first_extent+
269         pdi_segment[current_segment].no_extents+1<
270         first_extent)
271     load_new_source();
272   while(no_extents){
273     char small_buffer[0x800];
274     size_t extents_from_this_segment;
275     long bytes_in_buffer;
276     if(first_extent+no_extents <=
277        pdi_segment[current_segment].first_extent+pdi_segment[current_segment].no_extents)
278       extents_from_this_segment=no_extents;
279     else
280       extents_from_this_segment=
281         pdi_segment[current_segment].first_extent+
282         pdi_segment[current_segment].no_extents-first_extent;
283     if (extents_from_this_segment){ /* this file contains whole segments */
284       copy_extents(fout,first_extent,extents_from_this_segment);
285       first_extent+=extents_from_this_segment;
286       if(!(no_extents-=extents_from_this_segment))
287         return;
288     }
289     if(0>lseek(fin,
290                pdi_segment[current_segment].off + pdi_segment[current_segment].no_extents*0x800,
291                SEEK_SET))
292       errexit("during skip of .pdi-data");
293     bytes_in_buffer=read(fin,small_buffer,0x800);
294     if(bytes_in_buffer<0)
295       errexit("reading partial extent, first part");
296     load_new_source();
297     if(0>lseek(fin, 0, SEEK_SET))
298       errexit("during rewind of .pdi-data");
299     if(bytes_in_buffer){
300       bytes_in_buffer=read(fin,small_buffer+bytes_in_buffer,0x800-bytes_in_buffer);
301       if(bytes_in_buffer<(long)pdi_segment[current_segment].off)
302         errexit("reading partial extent, second part");
303       if(0x800>write(fout,small_buffer,0x800))
304         errexit("during write of segmented extent");
305       first_extent++;
306       no_extents--;
307     }
308   }
309 }
310 
main(int argc,char ** argv)311 int main(int argc,char **argv){
312   int fout=-1; /* handle to output file */
313   size_t fdscno; /* count of output files */
314   size_t i;
315   long l;
316   char outname[999];
317 
318     /* option flags */
319   int list_only=0;
320   int make_directory=0;
321   int make_VIDEO_TS=0;
322   char* dest_dir=0;
323   char* iso_name=0;
324   int argi=1;
325   char*iso_vol_name=0;
326   char*list_file=0;
327   int list_format=0;
328 
329   fprintf(stderr,"%s\n\n",id);
330   /* option parsing */
331   while(argc>argi&&argv[argi][0]=='-'){
332     switch(argv[argi][1]){
333     case 'l': case 'L': list_only=1; break;
334     case 'r': case 'R': delete_source=1; break;
335     case 'm': case 'M': make_directory=1;
336     case 'd': case 'D': dest_dir=argv[++argi]; break;
337     case 'i': case 'I': iso_name=file_with_ext(argv[++argi],".iso"); break;
338     case 'n': case 'N': iso_vol_name=argv[++argi]; break;
339     case 'v': case 'V': make_VIDEO_TS=1; break;
340     case 'g': case 'G': /* GUI options */
341       switch(argv[argi][2]) {
342       case 'b': case 'B': list_format++; /* format 3: <name> <size>\n */
343       case 'f': case 'F': list_format++; /* format 2: <name>\n<size>\n */
344       case 's': case 'S': list_format++; /* format 1: <name>,<size>\n */
345       case 'l': case 'L':                /* format 0: <name>\n */
346         list_only=1;list_file=argv[++argi]; break;
347       default: usage();
348       }
349       break;
350     case 'x': case 'X': if(argc-argi!=2) usage();
351       make_directory=1; make_VIDEO_TS=1; delete_source=1;
352       dest_dir=strdup(argv[argi+1]);
353       l=strlen(dest_dir)-4;
354       if(l>0&&!STRCMP(dest_dir+l,".pdi"))
355         dest_dir[l]=0; /* strip .pdi extension */
356       break;
357     default: usage();
358     }
359     argi++;
360   }
361 
362   if(argc==argi)
363     usage();
364   if(argc-argi>1&&(iso_name||list_only)){
365     fprintf(stderr,"individual file selection may be used only in file extraction mode\n");
366     usage();
367   }
368   infile=file_with_ext(argv[argi],".pdi");
369   fin=open(infile,O_RDONLY+O_BINARY);
370   if(fin<0)
371     errexit(infile);
372   fdscno=pdi_read_dir(fin);
373 
374   if(list_file){ /* generate listing for PDIgui */
375     FILE*f=fopen(list_file,"wt");
376     char* list_formats[]={"%s\n","%s,%i\n","%s\n%i\n","%s %i\n"};
377     if(!f)errexit(list_file);
378     for(i=0;i<fdscno;i++)
379       fprintf(f,list_formats[list_format],files[i].name,files[i].size*0x800);
380     fclose(f);
381   }
382   if(list_only)
383     exit(0);
384   if(!fdscno){
385     fprintf(stderr,"no files found in %s, exiting.\n",infile);
386     exit(0);
387   }
388   if(argc-argi>1){ /* there are file arguments specified */
389     /* now filter directory files through arguments */
390     int warning=0;
391     while(++argi<argc){
392       int found=0;
393       l=strlen(argv[argi]);
394       if(l&&argv[argi][--l]=='*'){ /* wild card match */
395         for(i=0;i<fdscno;i++)
396           if(!STRNCMP(argv[argi],files[i].name,l))
397             found=files[i].hits++;
398       }
399       else
400         for(i=0;i<fdscno;i++)
401           if(!STRCMP(argv[argi],files[i].name))
402             found=files[i].hits++;
403       if(!found){
404         fprintf(stderr,"Warning: file %s not found in .pdi set\n",argv[argi]);
405         warning++;
406       }
407     }
408     if(warning&&delete_source){
409       fprintf(stderr,"%i warnings issued during file selection.\n"
410               "cowardly clearing delete source flag\n",warning);
411       delete_source=0;
412     }
413     l=0;
414     for(i=0;i<fdscno;i++)
415       l+=--files[i].hits;
416     if(!l){
417       fprintf(stderr,"no files to extract, exiting.\n");
418       exit(0);
419     }
420   }
421   if(make_directory&&MAKE_DIR(dest_dir))
422     perror(dest_dir);
423   if(make_VIDEO_TS){
424     if(dest_dir){
425       snprintf(outname,sizeof(outname)-1,"%s%cVIDEO_TS",dest_dir,PATH_SEP);
426       dest_dir=strdup(outname);
427     }
428     else
429       dest_dir="VIDEO_TS";
430     if(MAKE_DIR(dest_dir))
431       perror(dest_dir);
432   }
433 
434   pdi_segment[0].off=PDI_HEADER;
435   l=lseek(fin,0,SEEK_END);
436   if(l<0)
437     errexit(current_file);
438   pdi_segment[0].size=l;
439   pdi_segment[0].first_extent=0;
440   pdi_segment[0].no_extents=(l-PDI_HEADER)/0x800;
441 
442   if(iso_name){ /* copy directory structure to iso */
443     fdscno--;
444     files[0].size=files[fdscno].extent+files[fdscno].size;
445     files[0].extent=0;
446     files[0].name=iso_name;
447     fdscno=1;
448   }
449   strncpy(current_file,infile,sizeof(current_file)-1);
450   for(i=0;i<fdscno;i++)
451     if(files[i].hits){
452       if(dest_dir)
453         snprintf(outname,sizeof(outname)-1,"%s%c%s",dest_dir,PATH_SEP,files[i].name);
454       else
455         strncpy(outname,files[i].name,sizeof(outname)-1);
456       fprintf(stderr,"writing %s (%i extents) ",outname,files[i].size);
457       fout=open(outname,O_TRUNC+O_WRONLY+O_CREAT+O_BINARY,S_IWUSR+S_IRUSR);
458       if(fout<0)
459         errexit(files[i].name);
460       write_to_file(fout,files[i].extent,files[i].size);
461       close(fout);
462       putc('\n',stderr);
463     }
464   close(fin);
465   if(delete_source&&unlink(current_file))
466     perror(current_file);
467   return 0;
468 }
469 
470 /* $Log: pdixtract.c,v $
471  * Revision 1.5  2003/02/27 15:08:50  smurf
472  * added different list formats to support PDIgui
473  *
474  * Revision 1.4  2003/02/25 19:25:00  smurf
475  * added -i option to extract iso image
476  * added -x "all in wonder" in options
477  * added support for extracting individual files
478  *
479  * Revision 1.3  2003/02/20 12:59:10  smurf
480  * improved error reporting
481  * checked for shadowed directory entries (reported by JJF007)
482  * added command line switches:
483  *     -v create VIDEO_TS
484  *     -x (all in wonder) is -r -m <pdi-file-name> -v
485  *
486  * Revision 1.2  2003/02/18 17:39:03  smurf
487  * Added new command-line switches:
488  * -l       list only
489  * -r       remove source files after extraction
490  * -d <dir> extract files into <dir> instead of current directory
491  * -m <dir> create directory <dir> and extract files into <dir>
492  *
493  * Revision 1.1  2003/02/17 15:08:50  smurf
494  * Initial revision
495  *
496  * */
497