quartz/tests: Fix a leak in test_fm2_enummatchingfilters().
[wine.git] / programs / cabarc / cabarc.c
blobd244d6ce27eb08e84f4456032570f26a6b348913
1 /*
2 * Tool to manipulate cabinet files
4 * Copyright 2011 Alexandre Julliard
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.
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.
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
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #include <io.h>
25 #include <share.h>
27 #define WIN32_LEAN_AND_MEAN
28 #include "windows.h"
29 #include "fci.h"
30 #include "fdi.h"
32 #include "wine/debug.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
36 /* command-line options */
37 static int opt_cabinet_size = CB_MAX_DISK;
38 static int opt_cabinet_id;
39 static int opt_compression = tcompTYPE_MSZIP;
40 static BOOL opt_recurse;
41 static BOOL opt_preserve_paths;
42 static int opt_reserve_space;
43 static int opt_verbose;
44 static char *opt_cab_file;
45 static WCHAR *opt_dest_dir;
46 static WCHAR **opt_files;
48 static void * CDECL cab_alloc( ULONG size )
50 return HeapAlloc( GetProcessHeap(), 0, size );
53 static void CDECL cab_free( void *ptr )
55 HeapFree( GetProcessHeap(), 0, ptr );
58 static WCHAR *strdupAtoW( UINT cp, const char *str )
60 WCHAR *ret = NULL;
61 if (str)
63 DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
64 if ((ret = cab_alloc( len * sizeof(WCHAR) )))
65 MultiByteToWideChar( cp, 0, str, -1, ret, len );
67 return ret;
70 static char *strdupWtoA( UINT cp, const WCHAR *str )
72 char *ret = NULL;
73 if (str)
75 DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
76 if ((ret = cab_alloc( len )))
77 WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
79 return ret;
82 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
83 static BOOL format_cab_name( char *dest, int id, const char *name )
85 const char *num = strchr( name, '*' );
86 int len;
88 if (!num)
90 if (id == 1)
92 strcpy( dest, name );
93 return TRUE;
95 WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
96 return FALSE;
98 len = num - name;
99 memcpy( dest, name, len );
100 len += sprintf( dest + len, "%u", id );
101 lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
102 return TRUE;
105 static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
107 if (!continuation && opt_verbose) printf( "adding %s\n", file );
108 return 0;
111 static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
113 DWORD creation = 0, sharing = 0;
114 int ioflag = 0;
115 HANDLE handle;
117 switch (oflag & _O_ACCMODE)
119 case _O_RDONLY: ioflag |= GENERIC_READ; break;
120 case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
121 case _O_RDWR: ioflag |= GENERIC_READ | GENERIC_WRITE; break;
124 if (oflag & _O_CREAT)
126 if (oflag & _O_EXCL) creation = CREATE_NEW;
127 else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
128 else creation = OPEN_ALWAYS;
130 else
132 if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
133 else creation = OPEN_EXISTING;
136 switch (pmode & 0x70)
138 case _SH_DENYRW: sharing = 0; break;
139 case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
140 case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
141 default: sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
144 handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
145 if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
146 return (INT_PTR)handle;
149 static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
151 DWORD num_read;
153 if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
155 *err = GetLastError();
156 return -1;
158 return num_read;
161 static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
163 DWORD written;
165 if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
167 *err = GetLastError();
168 return -1;
170 return written;
173 static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
175 if (!CloseHandle( (HANDLE)hf ))
177 *err = GetLastError();
178 return -1;
180 return 0;
183 static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
185 DWORD ret;
187 ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
188 if (ret == INVALID_SET_FILE_POINTER && GetLastError())
190 *err = GetLastError();
191 return -1;
193 return ret;
196 static int CDECL fci_delete( char *file, int *err, void *ptr )
198 if (!DeleteFileA( file ))
200 *err = GetLastError();
201 return -1;
203 return 0;
206 static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
208 char path[MAX_PATH];
210 if (!GetTempPathA( MAX_PATH, path )) return FALSE;
211 if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
212 DeleteFileA( name );
213 return TRUE;
216 static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
218 return format_cab_name( cab->szCab, cab->iCab + 1, opt_cab_file );
221 static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
223 return 0;
226 static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
227 USHORT *attribs, int *err, void *ptr )
229 HANDLE handle;
230 BY_HANDLE_FILE_INFORMATION info;
231 WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
233 handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
234 NULL, OPEN_EXISTING, 0, NULL );
235 if (handle == INVALID_HANDLE_VALUE)
237 *err = GetLastError();
238 WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
239 cab_free( nameW );
240 return -1;
242 if (!GetFileInformationByHandle( handle, &info ))
244 *err = GetLastError();
245 CloseHandle( handle );
246 cab_free( nameW );
247 return -1;
249 FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
250 *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
251 for (p = nameW; *p; p++) if (*p >= 0x80) break;
252 if (*p) *attribs |= _A_NAME_IS_UTF;
253 cab_free( nameW );
254 return (INT_PTR)handle;
257 static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
259 int err;
260 return fci_open( file, oflag, pmode, &err, NULL );
263 static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
265 int err;
266 return fci_read( hf, pv, cb, &err, NULL );
269 static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
271 int err;
272 return fci_write( hf, pv, cb, &err, NULL );
275 static int CDECL fdi_close( INT_PTR hf )
277 int err;
278 return fci_close( hf, &err, NULL );
281 static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
283 int err;
284 return fci_lseek( hf, dist, whence, &err, NULL );
288 /* create directories leading to a given file */
289 static void create_directories( const WCHAR *name )
291 WCHAR *path, *p;
293 /* create the directory/directories */
294 path = cab_alloc( (lstrlenW(name) + 1) * sizeof(WCHAR) );
295 lstrcpyW(path, name);
297 p = wcschr(path, '\\');
298 while (p != NULL)
300 *p = 0;
301 if (!CreateDirectoryW( path, NULL ))
302 WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path), GetLastError());
303 *p = '\\';
304 p = wcschr(p+1, '\\');
306 cab_free( path );
309 /* check if file name matches against one of the files specification */
310 static BOOL match_files( const WCHAR *name )
312 int i;
314 if (!*opt_files) return TRUE;
315 for (i = 0; opt_files[i]; i++)
317 unsigned int len = lstrlenW( opt_files[i] );
318 /* FIXME: do smarter matching, and wildcards */
319 if (!len) continue;
320 if (wcsnicmp( name, opt_files[i], len )) continue;
321 if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
323 return FALSE;
326 static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
328 WCHAR *nameW;
330 switch (fdint)
332 case fdintCABINET_INFO:
333 return 0;
334 case fdintCOPY_FILE:
335 nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
336 if (match_files( nameW ))
338 if (opt_verbose)
340 WCHAR attrs[] = L"rxash";
341 if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
342 if (!(pfdin->attribs & _A_EXEC)) attrs[1] = '-';
343 if (!(pfdin->attribs & _A_ARCH)) attrs[2] = '-';
344 if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
345 if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
346 wprintf( L" %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs, pfdin->cb,
347 (pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
348 pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
350 wprintf( L"%s\n", nameW );
352 cab_free( nameW );
353 return 0;
354 default:
355 WINE_FIXME( "Unexpected notification type %d.\n", fdint );
356 return 0;
360 static int list_cabinet( char *cab_dir )
362 ERF erf;
363 int ret = 0;
364 HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
365 fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
367 if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
368 FDIDestroy( fdi );
369 return ret;
372 static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
374 WCHAR *file, *nameW, *path = NULL;
375 INT_PTR ret;
377 switch (fdint)
379 case fdintCABINET_INFO:
380 return 0;
382 case fdintCOPY_FILE:
383 nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
384 if (opt_preserve_paths)
386 file = nameW;
387 while (*file == '\\') file++; /* remove leading backslashes */
389 else
391 if ((file = wcsrchr( nameW, '\\' ))) file++;
392 else file = nameW;
395 if (opt_dest_dir)
397 path = cab_alloc( (lstrlenW(opt_dest_dir) + lstrlenW(file) + 1) * sizeof(WCHAR) );
398 lstrcpyW( path, opt_dest_dir );
399 lstrcatW( path, file );
401 else path = file;
403 if (match_files( file ))
405 if (opt_verbose) wprintf( L"extracting %s\n", path );
406 create_directories( path );
407 /* FIXME: check for existing file and overwrite mode */
408 ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
409 NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
411 else ret = 0;
413 cab_free( nameW );
414 if (path != file) cab_free( path );
415 return ret;
417 case fdintCLOSE_FILE_INFO:
418 CloseHandle( (HANDLE)pfdin->hf );
419 return 0;
421 case fdintNEXT_CABINET:
422 WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin->fdie, pfdin->psz3, pfdin->psz1);
423 return pfdin->fdie == FDIERROR_NONE ? 0 : -1;
425 case fdintENUMERATE:
426 return 0;
428 default:
429 WINE_FIXME( "Unexpected notification type %d.\n", fdint );
430 return 0;
434 static int extract_cabinet( char *cab_dir )
436 ERF erf;
437 int ret = 0;
438 HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
439 fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
441 if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL ))
443 ret = GetLastError();
444 WINE_WARN("FDICopy() failed: code %u\n", ret);
446 FDIDestroy( fdi );
447 return ret;
450 static BOOL add_file( HFCI fci, WCHAR *name )
452 BOOL ret;
453 char *filename, *path = strdupWtoA( CP_UTF8, name );
455 if (!opt_preserve_paths)
457 if ((filename = strrchr( path, '\\' ))) filename++;
458 else filename = path;
460 else
462 filename = path;
463 while (*filename == '\\') filename++; /* remove leading backslashes */
465 ret = FCIAddFile( fci, path, filename, FALSE,
466 fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
467 cab_free( path );
468 return ret;
471 static BOOL add_directory( HFCI fci, WCHAR *dir )
473 static const WCHAR wildcardW[] = {'*',0};
474 WCHAR *p, *buffer;
475 HANDLE handle;
476 WIN32_FIND_DATAW data;
477 BOOL ret = TRUE;
479 if (!(buffer = cab_alloc( (lstrlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
480 lstrcpyW( buffer, dir );
481 p = buffer + lstrlenW( buffer );
482 if (p > buffer && p[-1] != '\\') *p++ = '\\';
483 lstrcpyW( p, wildcardW );
485 if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
489 if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
490 if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
491 if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
493 lstrcpyW( p, data.cFileName );
494 if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
495 ret = add_directory( fci, buffer );
496 else
497 ret = add_file( fci, buffer );
498 if (!ret) break;
499 } while (FindNextFileW( handle, &data ));
500 FindClose( handle );
502 cab_free( buffer );
503 return TRUE;
506 static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
508 DWORD attr = GetFileAttributesW( name );
510 if (attr == INVALID_FILE_ATTRIBUTES)
512 WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
513 return FALSE;
515 if (attr & FILE_ATTRIBUTE_DIRECTORY)
517 if (opt_recurse) return add_directory( fci, name );
518 WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
519 wine_dbgstr_w(name) );
520 return FALSE;
522 return add_file( fci, name );
525 static int new_cabinet( char *cab_dir )
527 static const WCHAR plusW[] = {'+',0};
528 WCHAR **file;
529 ERF erf;
530 BOOL ret = FALSE;
531 HFCI fci;
532 CCAB cab;
534 cab.cb = opt_cabinet_size;
535 cab.cbFolderThresh = CB_MAX_DISK;
536 cab.cbReserveCFHeader = opt_reserve_space;
537 cab.cbReserveCFFolder = 0;
538 cab.cbReserveCFData = 0;
539 cab.iCab = 0;
540 cab.iDisk = 0;
541 cab.setID = opt_cabinet_id;
542 cab.szDisk[0] = 0;
544 strcpy( cab.szCabPath, cab_dir );
545 strcat( cab.szCabPath, "\\" );
546 format_cab_name( cab.szCab, 1, opt_cab_file );
548 fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
549 fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
551 for (file = opt_files; *file; file++)
553 if (!lstrcmpW( *file, plusW ))
554 FCIFlushFolder( fci, fci_get_next_cab, fci_status );
555 else
556 if (!(ret = add_file_or_directory( fci, *file ))) break;
559 if (ret)
561 if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
562 WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
564 FCIDestroy( fci );
565 return !ret;
568 static void usage( void )
570 WINE_MESSAGE(
571 "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
572 "\nCommands:\n"
573 " L List the contents of the cabinet\n"
574 " N Create a new cabinet\n"
575 " X Extract files from the cabinet into dest_dir\n"
576 "\nOptions:\n"
577 " -d size Set maximum disk size\n"
578 " -h Display this help\n"
579 " -i id Set cabinet id\n"
580 " -m type Set compression type (mszip|none)\n"
581 " -p Preserve directory names\n"
582 " -r Recurse into directories\n"
583 " -s size Reserve space in the cabinet header\n"
584 " -v More verbose output\n" );
587 int __cdecl wmain( int argc, WCHAR *argv[] )
589 static const WCHAR noneW[] = {'n','o','n','e',0};
590 static const WCHAR mszipW[] = {'m','s','z','i','p',0};
592 WCHAR *p, *command;
593 char buffer[MAX_PATH];
594 char filename[MAX_PATH];
595 char *cab_file, *file_part;
596 int i;
598 while (argv[1] && argv[1][0] == '-')
600 switch (argv[1][1])
602 case 'd':
603 argv++; argc--;
604 opt_cabinet_size = wcstol( argv[1], NULL, 10 );
605 if (opt_cabinet_size < 50000)
607 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
608 return 1;
610 break;
611 case 'h':
612 usage();
613 return 0;
614 case 'i':
615 argv++; argc--;
616 opt_cabinet_id = wcstol( argv[1], NULL, 10 );
617 break;
618 case 'm':
619 argv++; argc--;
620 if (!wcscmp( argv[1], noneW )) opt_compression = tcompTYPE_NONE;
621 else if (!wcscmp( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP;
622 else
624 WINE_MESSAGE( "cabarc: Unknown compression type %s\n", debugstr_w(argv[1]));
625 return 1;
627 break;
628 case 'p':
629 opt_preserve_paths = TRUE;
630 break;
631 case 'r':
632 opt_recurse = TRUE;
633 break;
634 case 's':
635 argv++; argc--;
636 opt_reserve_space = wcstol( argv[1], NULL, 10 );
637 break;
638 case 'v':
639 opt_verbose++;
640 break;
641 default:
642 usage();
643 return 1;
645 argv++; argc--;
648 command = argv[1];
649 if (argc < 3 || !command[0] || command[1])
651 usage();
652 return 1;
654 cab_file = strdupWtoA( CP_ACP, argv[2] );
655 argv += 2;
656 argc -= 2;
658 if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
660 WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
661 return 1;
663 strcpy(filename, file_part);
664 file_part[0] = 0;
666 /* map slash to backslash in all file arguments */
667 for (i = 1; i < argc; i++)
668 for (p = argv[i]; *p; p++)
669 if (*p == '/') *p = '\\';
670 opt_files = argv + 1;
671 opt_cab_file = filename;
673 switch (*command)
675 case 'l':
676 case 'L':
677 return list_cabinet( buffer );
678 case 'n':
679 case 'N':
680 return new_cabinet( buffer );
681 case 'x':
682 case 'X':
683 if (argc > 1) /* check for destination dir as last argument */
685 WCHAR *last = argv[argc - 1];
686 if (last[0] && last[lstrlenW(last) - 1] == '\\')
688 opt_dest_dir = last;
689 argv[--argc] = NULL;
692 WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file));
693 return extract_cabinet( buffer );
694 default:
695 usage();
696 return 1;