Release 1.5.29.
[wine] / programs / cabarc / cabarc.c
1 /*
2  * Tool to manipulate cabinet files
3  *
4  * Copyright 2011 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #define WIN32_LEAN_AND_MEAN
28 #include "windows.h"
29 #include "fci.h"
30 #include "fdi.h"
31
32 #include "wine/unicode.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
36
37 /* from msvcrt */
38 #ifndef _O_RDONLY
39 #define _O_RDONLY      0
40 #define _O_WRONLY      1
41 #define _O_RDWR        2
42 #define _O_ACCMODE     (_O_RDONLY|_O_WRONLY|_O_RDWR)
43 #define _O_APPEND      0x0008
44 #define _O_RANDOM      0x0010
45 #define _O_SEQUENTIAL  0x0020
46 #define _O_TEMPORARY   0x0040
47 #define _O_NOINHERIT   0x0080
48 #define _O_CREAT       0x0100
49 #define _O_TRUNC       0x0200
50 #define _O_EXCL        0x0400
51 #define _O_SHORT_LIVED 0x1000
52 #define _O_TEXT        0x4000
53 #define _O_BINARY      0x8000
54 #endif
55
56 #ifndef _SH_COMPAT
57 #define _SH_COMPAT     0x00
58 #define _SH_DENYRW     0x10
59 #define _SH_DENYWR     0x20
60 #define _SH_DENYRD     0x30
61 #define _SH_DENYNO     0x40
62 #endif
63
64 #ifndef _A_RDONLY
65 #define _A_RDONLY      0x01
66 #define _A_HIDDEN      0x02
67 #define _A_SYSTEM      0x04
68 #define _A_ARCH        0x20
69 #endif
70
71 /* command-line options */
72 static int opt_cabinet_size = CB_MAX_DISK;
73 static int opt_cabinet_id;
74 static int opt_compression = tcompTYPE_MSZIP;
75 static int opt_recurse;
76 static int opt_preserve_paths;
77 static int opt_reserve_space;
78 static int opt_verbose;
79 static char *opt_cab_file;
80 static WCHAR *opt_dest_dir;
81 static WCHAR **opt_files;
82
83 static void * CDECL cab_alloc( ULONG size )
84 {
85     return HeapAlloc( GetProcessHeap(), 0, size );
86 }
87
88 static void CDECL cab_free( void *ptr )
89 {
90     HeapFree( GetProcessHeap(), 0, ptr );
91 }
92
93 static WCHAR *strdupAtoW( UINT cp, const char *str )
94 {
95     WCHAR *ret = NULL;
96     if (str)
97     {
98         DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
99         if ((ret = cab_alloc( len * sizeof(WCHAR) )))
100             MultiByteToWideChar( cp, 0, str, -1, ret, len );
101     }
102     return ret;
103 }
104
105 static char *strdupWtoA( UINT cp, const WCHAR *str )
106 {
107     char *ret = NULL;
108     if (str)
109     {
110         DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
111         if ((ret = cab_alloc( len )))
112             WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
113     }
114     return ret;
115 }
116
117 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
118 static BOOL format_cab_name( char *dest, int id, const char *name )
119 {
120     const char *num = strchr( name, '*' );
121     int len;
122
123     if (!num)
124     {
125         if (id == 1)
126         {
127             strcpy( dest, name );
128             return TRUE;
129         }
130         WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
131         return FALSE;
132     }
133     len = num - name;
134     memcpy( dest, name, len );
135     len += sprintf( dest + len, "%u", id );
136     lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
137     return TRUE;
138 }
139
140 static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
141 {
142     if (!continuation && opt_verbose) printf( "adding %s\n", file );
143     return 0;
144 }
145
146 static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
147 {
148     DWORD creation = 0, sharing = 0;
149     int ioflag = 0;
150     HANDLE handle;
151
152     switch (oflag & _O_ACCMODE)
153     {
154     case _O_RDONLY: ioflag |= GENERIC_READ; break;
155     case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
156     case _O_RDWR:   ioflag |= GENERIC_READ | GENERIC_WRITE; break;
157     }
158
159     if (oflag & _O_CREAT)
160     {
161         if (oflag & _O_EXCL) creation = CREATE_NEW;
162         else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
163         else creation = OPEN_ALWAYS;
164     }
165     else
166     {
167         if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
168         else creation = OPEN_EXISTING;
169     }
170
171     switch (pmode & 0x70)
172     {
173     case _SH_DENYRW: sharing = 0; break;
174     case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
175     case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
176     default:         sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
177     }
178
179     handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
180     if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
181     return (INT_PTR)handle;
182 }
183
184 static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
185 {
186     DWORD num_read;
187
188     if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
189     {
190         *err = GetLastError();
191         return -1;
192     }
193     return num_read;
194 }
195
196 static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
197 {
198     DWORD written;
199
200     if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
201     {
202         *err = GetLastError();
203         return -1;
204     }
205     return written;
206 }
207
208 static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
209 {
210     if (!CloseHandle( (HANDLE)hf ))
211     {
212         *err = GetLastError();
213         return -1;
214     }
215     return 0;
216 }
217
218 static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
219 {
220     DWORD ret;
221
222     ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
223     if (ret == INVALID_SET_FILE_POINTER && GetLastError())
224     {
225         *err = GetLastError();
226         return -1;
227     }
228     return ret;
229 }
230
231 static int CDECL fci_delete( char *file, int *err, void *ptr )
232 {
233     if (!DeleteFileA( file ))
234     {
235         *err = GetLastError();
236         return -1;
237     }
238     return 0;
239 }
240
241 static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
242 {
243     char path[MAX_PATH];
244
245     if (!GetTempPathA( MAX_PATH, path )) return FALSE;
246     if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
247     DeleteFileA( name );
248     return TRUE;
249 }
250
251 static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
252 {
253     return format_cab_name( cab->szCab, cab->iCab + 1, opt_cab_file );
254 }
255
256 static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
257 {
258     return 0;
259 }
260
261 static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
262                                         USHORT *attribs, int *err, void *ptr )
263 {
264     HANDLE handle;
265     BY_HANDLE_FILE_INFORMATION info;
266     WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
267
268     handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
269                           NULL, OPEN_EXISTING, 0, NULL );
270     if (handle == INVALID_HANDLE_VALUE)
271     {
272         *err = GetLastError();
273         WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
274         cab_free( nameW );
275         return -1;
276     }
277     if (!GetFileInformationByHandle( handle, &info ))
278     {
279         *err = GetLastError();
280         CloseHandle( handle );
281         cab_free( nameW );
282         return -1;
283     }
284     FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
285     *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
286     for (p = nameW; *p; p++) if (*p >= 0x80) break;
287     if (*p) *attribs |= _A_NAME_IS_UTF;
288     cab_free( nameW );
289     return (INT_PTR)handle;
290 }
291
292 static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
293 {
294     int err;
295     return fci_open( file, oflag, pmode, &err, NULL );
296 }
297
298 static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
299 {
300     int err;
301     return fci_read( hf, pv, cb, &err, NULL );
302 }
303
304 static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
305 {
306     int err;
307     return fci_write( hf, pv, cb, &err, NULL );
308 }
309
310 static int CDECL fdi_close( INT_PTR hf )
311 {
312     int err;
313     return fci_close( hf, &err, NULL );
314 }
315
316 static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
317 {
318     int err;
319     return fci_lseek( hf, dist, whence, &err, NULL );
320 }
321
322
323 /* create directories leading to a given file */
324 static void create_directories( const WCHAR *name )
325 {
326     WCHAR *path, *p;
327
328     /* create the directory/directories */
329     path = cab_alloc( (strlenW(name) + 1) * sizeof(WCHAR) );
330     strcpyW(path, name);
331
332     p = strchrW(path, '\\');
333     while (p != NULL)
334     {
335         *p = 0;
336         if (!CreateDirectoryW( path, NULL ))
337             WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path), GetLastError());
338         *p = '\\';
339         p = strchrW(p+1, '\\');
340     }
341     cab_free( path );
342 }
343
344 /* check if file name matches against one of the files specification */
345 static BOOL match_files( const WCHAR *name )
346 {
347     int i;
348
349     if (!*opt_files) return TRUE;
350     for (i = 0; opt_files[i]; i++)
351     {
352         unsigned int len = strlenW( opt_files[i] );
353         /* FIXME: do smarter matching, and wildcards */
354         if (!len) continue;
355         if (strncmpiW( name, opt_files[i], len )) continue;
356         if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
357     }
358     return FALSE;
359 }
360
361 static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
362 {
363     WCHAR *nameW;
364
365     switch (fdint)
366     {
367     case fdintCABINET_INFO:
368         return 0;
369     case fdintCOPY_FILE:
370         nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
371         if (match_files( nameW ))
372         {
373             char *nameU = strdupWtoA( CP_UNIXCP, nameW );
374             if (opt_verbose)
375             {
376                 char attrs[] = "rxash";
377                 if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
378                 if (!(pfdin->attribs & _A_EXEC))   attrs[1] = '-';
379                 if (!(pfdin->attribs & _A_ARCH))   attrs[2] = '-';
380                 if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
381                 if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
382                 printf( " %s %9u  %04u/%02u/%02u %02u:%02u:%02u  ", attrs, pfdin->cb,
383                         (pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
384                         pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
385             }
386             printf( "%s\n", nameU );
387             cab_free( nameU );
388         }
389         cab_free( nameW );
390         return 0;
391     default:
392         WINE_FIXME( "Unexpected notification type %d.\n", fdint );
393         return 0;
394     }
395 }
396
397 static int list_cabinet( char *cab_dir )
398 {
399     ERF erf;
400     int ret = 0;
401     HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
402                           fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
403
404     if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
405     FDIDestroy( fdi );
406     return ret;
407 }
408
409 static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
410 {
411     WCHAR *file, *nameW, *path = NULL;
412     INT_PTR ret;
413
414     switch (fdint)
415     {
416     case fdintCABINET_INFO:
417         return 0;
418
419     case fdintCOPY_FILE:
420         nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
421         if (opt_preserve_paths)
422         {
423             file = nameW;
424             while (*file == '\\') file++;  /* remove leading backslashes */
425         }
426         else
427         {
428             if ((file = strrchrW( nameW, '\\' ))) file++;
429             else file = nameW;
430         }
431
432         if (opt_dest_dir)
433         {
434             path = cab_alloc( (strlenW(opt_dest_dir) + strlenW(file) + 1) * sizeof(WCHAR) );
435             strcpyW( path, opt_dest_dir );
436             strcatW( path, file );
437         }
438         else path = file;
439
440         if (match_files( file ))
441         {
442             if (opt_verbose)
443             {
444                 char *nameU = strdupWtoA( CP_UNIXCP, path );
445                 printf( "extracting %s\n", nameU );
446                 cab_free( nameU );
447             }
448             create_directories( path );
449             /* FIXME: check for existing file and overwrite mode */
450             ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
451                                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
452         }
453         else ret = 0;
454
455         cab_free( nameW );
456         if (path != file) cab_free( path );
457         return ret;
458
459     case fdintCLOSE_FILE_INFO:
460         CloseHandle( (HANDLE)pfdin->hf );
461         return 0;
462
463     case fdintNEXT_CABINET:
464         WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin->fdie, pfdin->psz3, pfdin->psz1);
465         return pfdin->fdie == FDIERROR_NONE ? 0 : -1;
466
467     case fdintENUMERATE:
468         return 0;
469
470     default:
471         WINE_FIXME( "Unexpected notification type %d.\n", fdint );
472         return 0;
473     }
474 }
475
476 static int extract_cabinet( char *cab_dir )
477 {
478     ERF erf;
479     int ret = 0;
480     HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
481                           fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
482
483     if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL ))
484     {
485         ret = GetLastError();
486         WINE_WARN("FDICopy() failed: code %u\n", ret);
487     }
488     FDIDestroy( fdi );
489     return ret;
490 }
491
492 static BOOL add_file( HFCI fci, WCHAR *name )
493 {
494     BOOL ret;
495     char *filename, *path = strdupWtoA( CP_UTF8, name );
496
497     if (!opt_preserve_paths)
498     {
499         if ((filename = strrchr( path, '\\' ))) filename++;
500         else filename = path;
501     }
502     else
503     {
504         filename = path;
505         while (*filename == '\\') filename++;  /* remove leading backslashes */
506     }
507     ret = FCIAddFile( fci, path, filename, FALSE,
508                       fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
509     cab_free( path );
510     return ret;
511 }
512
513 static BOOL add_directory( HFCI fci, WCHAR *dir )
514 {
515     static const WCHAR wildcardW[] = {'*',0};
516     WCHAR *p, *buffer;
517     HANDLE handle;
518     WIN32_FIND_DATAW data;
519     BOOL ret = TRUE;
520
521     if (!(buffer = cab_alloc( (strlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
522     strcpyW( buffer, dir );
523     p = buffer + strlenW( buffer );
524     if (p > buffer && p[-1] != '\\') *p++ = '\\';
525     strcpyW( p, wildcardW );
526
527     if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
528     {
529         do
530         {
531             if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
532             if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
533             if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
534
535             strcpyW( p, data.cFileName );
536             if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
537                 ret = add_directory( fci, buffer );
538             else
539                 ret = add_file( fci, buffer );
540             if (!ret) break;
541         } while (FindNextFileW( handle, &data ));
542         FindClose( handle );
543     }
544     cab_free( buffer );
545     return TRUE;
546 }
547
548 static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
549 {
550     DWORD attr = GetFileAttributesW( name );
551
552     if (attr == INVALID_FILE_ATTRIBUTES)
553     {
554         WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
555         return FALSE;
556     }
557     if (attr & FILE_ATTRIBUTE_DIRECTORY)
558     {
559         if (opt_recurse) return add_directory( fci, name );
560         WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
561                       wine_dbgstr_w(name) );
562         return FALSE;
563     }
564     return add_file( fci, name );
565 }
566
567 static int new_cabinet( char *cab_dir )
568 {
569     static const WCHAR plusW[] = {'+',0};
570     WCHAR **file;
571     ERF erf;
572     BOOL ret = FALSE;
573     HFCI fci;
574     CCAB cab;
575
576     cab.cb                = opt_cabinet_size;
577     cab.cbFolderThresh    = CB_MAX_DISK;
578     cab.cbReserveCFHeader = opt_reserve_space;
579     cab.cbReserveCFFolder = 0;
580     cab.cbReserveCFData   = 0;
581     cab.iCab              = 0;
582     cab.iDisk             = 0;
583     cab.setID             = opt_cabinet_id;
584     cab.szDisk[0]         = 0;
585
586     strcpy( cab.szCabPath, cab_dir );
587     strcat( cab.szCabPath, "\\" );
588     format_cab_name( cab.szCab, 1, opt_cab_file );
589
590     fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
591                      fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
592
593     for (file = opt_files; *file; file++)
594     {
595         if (!strcmpW( *file, plusW ))
596             FCIFlushFolder( fci, fci_get_next_cab, fci_status );
597         else
598             if (!(ret = add_file_or_directory( fci, *file ))) break;
599     }
600
601     if (ret)
602     {
603         if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
604             WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
605     }
606     FCIDestroy( fci );
607     return !ret;
608 }
609
610 static void usage( void )
611 {
612     WINE_MESSAGE(
613         "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
614         "\nCommands:\n"
615         "   L   List the contents of the cabinet\n"
616         "   N   Create a new cabinet\n"
617         "   X   Extract files from the cabinet into dest_dir\n"
618         "\nOptions:\n"
619         "  -d size  Set maximum disk size\n"
620         "  -h       Display this help\n"
621         "  -i id    Set cabinet id\n"
622         "  -m type  Set compression type (mszip|none)\n"
623         "  -p       Preserve directory names\n"
624         "  -r       Recurse into directories\n"
625         "  -s size  Reserve space in the cabinet header\n"
626         "  -v       More verbose output\n" );
627 }
628
629 int wmain( int argc, WCHAR *argv[] )
630 {
631     static const WCHAR noneW[] = {'n','o','n','e',0};
632     static const WCHAR mszipW[] = {'m','s','z','i','p',0};
633
634     WCHAR *p, *command;
635     char buffer[MAX_PATH];
636     char filename[MAX_PATH];
637     char *cab_file, *file_part;
638     int i;
639
640     while (argv[1] && argv[1][0] == '-')
641     {
642         switch (argv[1][1])
643         {
644         case 'd':
645             argv++; argc--;
646             opt_cabinet_size = atoiW( argv[1] );
647             if (opt_cabinet_size < 50000)
648             {
649                 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
650                 return 1;
651             }
652             break;
653         case 'h':
654             usage();
655             return 0;
656         case 'i':
657             argv++; argc--;
658             opt_cabinet_id = atoiW( argv[1] );
659             break;
660         case 'm':
661             argv++; argc--;
662             if (!strcmpiW( argv[1], noneW )) opt_compression = tcompTYPE_NONE;
663             else if (!strcmpiW( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP;
664             else
665             {
666                 WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", optarg );
667                 return 1;
668             }
669             break;
670         case 'p':
671             opt_preserve_paths = 1;
672             break;
673         case 'r':
674             opt_recurse = 1;
675             break;
676         case 's':
677             argv++; argc--;
678             opt_reserve_space = atoiW( argv[1] );
679             break;
680         case 'v':
681             opt_verbose++;
682             break;
683         default:
684             usage();
685             return 1;
686         }
687         argv++; argc--;
688     }
689
690     command = argv[1];
691     if (argc < 3 || !command[0] || command[1])
692     {
693         usage();
694         return 1;
695     }
696     cab_file = strdupWtoA( CP_ACP, argv[2] );
697     argv += 2;
698     argc -= 2;
699
700     if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
701     {
702         WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
703         return 1;
704     }
705     strcpy(filename, file_part);
706     file_part[0] = 0;
707
708     /* map slash to backslash in all file arguments */
709     for (i = 1; i < argc; i++)
710         for (p = argv[i]; *p; p++)
711             if (*p == '/') *p = '\\';
712     opt_files = argv + 1;
713     opt_cab_file = filename;
714
715     switch (*command)
716     {
717     case 'l':
718     case 'L':
719         return list_cabinet( buffer );
720     case 'n':
721     case 'N':
722         return new_cabinet( buffer );
723     case 'x':
724     case 'X':
725         if (argc > 1)  /* check for destination dir as last argument */
726         {
727             WCHAR *last = argv[argc - 1];
728             if (last[0] && last[strlenW(last) - 1] == '\\')
729             {
730                 opt_dest_dir = last;
731                 argv[--argc] = NULL;
732             }
733         }
734         WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file));
735         return extract_cabinet( buffer );
736     default:
737         usage();
738         return 1;
739     }
740 }