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
27 #define WIN32_LEAN_AND_MEAN
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
)
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
);
70 static char *strdupWtoA( UINT cp
, const WCHAR
*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
);
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
, '*' );
95 WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
99 memcpy( dest
, name
, len
);
100 len
+= sprintf( dest
+ len
, "%u", id
);
101 lstrcpynA( dest
+ len
, num
+ 1, CB_MAX_CABINET_NAME
- len
);
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
);
111 static INT_PTR CDECL
fci_open( char *file
, int oflag
, int pmode
, int *err
, void *ptr
)
113 DWORD creation
= 0, sharing
= 0;
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
;
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
)
153 if (!ReadFile( (HANDLE
)hf
, pv
, cb
, &num_read
, NULL
))
155 *err
= GetLastError();
161 static UINT CDECL
fci_write( INT_PTR hf
, void *pv
, UINT cb
, int *err
, void *ptr
)
165 if (!WriteFile( (HANDLE
) hf
, pv
, cb
, &written
, NULL
))
167 *err
= GetLastError();
173 static int CDECL
fci_close( INT_PTR hf
, int *err
, void *ptr
)
175 if (!CloseHandle( (HANDLE
)hf
))
177 *err
= GetLastError();
183 static LONG CDECL
fci_lseek( INT_PTR hf
, LONG dist
, int seektype
, int *err
, void *ptr
)
187 ret
= SetFilePointer( (HANDLE
)hf
, dist
, NULL
, seektype
);
188 if (ret
== INVALID_SET_FILE_POINTER
&& GetLastError())
190 *err
= GetLastError();
196 static int CDECL
fci_delete( char *file
, int *err
, void *ptr
)
198 if (!DeleteFileA( file
))
200 *err
= GetLastError();
206 static BOOL CDECL
fci_get_temp( char *name
, int size
, void *ptr
)
210 if (!GetTempPathA( MAX_PATH
, path
)) return FALSE
;
211 if (!GetTempFileNameA( path
, "cab", 0, name
)) return FALSE
;
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
)
226 static INT_PTR CDECL
fci_get_open_info( char *name
, USHORT
*date
, USHORT
*time
,
227 USHORT
*attribs
, int *err
, void *ptr
)
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
);
242 if (!GetFileInformationByHandle( handle
, &info
))
244 *err
= GetLastError();
245 CloseHandle( handle
);
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
;
254 return (INT_PTR
)handle
;
257 static INT_PTR CDECL
fdi_open( char *file
, int oflag
, int pmode
)
260 return fci_open( file
, oflag
, pmode
, &err
, NULL
);
263 static UINT CDECL
fdi_read( INT_PTR hf
, void *pv
, UINT cb
)
266 return fci_read( hf
, pv
, cb
, &err
, NULL
);
269 static UINT CDECL
fdi_write( INT_PTR hf
, void *pv
, UINT cb
)
272 return fci_write( hf
, pv
, cb
, &err
, NULL
);
275 static int CDECL
fdi_close( INT_PTR hf
)
278 return fci_close( hf
, &err
, NULL
);
281 static LONG CDECL
fdi_lseek( INT_PTR hf
, LONG dist
, int whence
)
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
)
293 /* create the directory/directories */
294 path
= cab_alloc( (lstrlenW(name
) + 1) * sizeof(WCHAR
) );
295 lstrcpyW(path
, name
);
297 p
= wcschr(path
, '\\');
301 if (!CreateDirectoryW( path
, NULL
))
302 WINE_TRACE("Couldn't create directory %s - error: %ld\n", wine_dbgstr_w(path
), GetLastError());
304 p
= wcschr(p
+1, '\\');
309 /* check if file name matches against one of the files specification */
310 static BOOL
match_files( const WCHAR
*name
)
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 */
320 if (wcsnicmp( name
, opt_files
[i
], len
)) continue;
321 if (opt_files
[i
][len
- 1] == '\\' || !name
[len
] || name
[len
] == '\\') return TRUE
;
326 static INT_PTR CDECL
list_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
332 case fdintCABINET_INFO
:
335 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
336 if (match_files( nameW
))
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
);
355 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
360 static int list_cabinet( char *cab_dir
)
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();
372 static INT_PTR CDECL
extract_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
374 WCHAR
*file
, *nameW
, *path
= NULL
;
379 case fdintCABINET_INFO
:
383 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
384 if (opt_preserve_paths
)
387 while (*file
== '\\') file
++; /* remove leading backslashes */
391 if ((file
= wcsrchr( nameW
, '\\' ))) file
++;
397 path
= cab_alloc( (lstrlenW(opt_dest_dir
) + lstrlenW(file
) + 1) * sizeof(WCHAR
) );
398 lstrcpyW( path
, opt_dest_dir
);
399 lstrcatW( 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
);
414 if (path
!= file
) cab_free( path
);
417 case fdintCLOSE_FILE_INFO
:
418 CloseHandle( (HANDLE
)pfdin
->hf
);
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;
429 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
434 static int extract_cabinet( char *cab_dir
)
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
);
450 static BOOL
add_file( HFCI fci
, WCHAR
*name
)
453 char *filename
, *path
= strdupWtoA( CP_UTF8
, name
);
455 if (!opt_preserve_paths
)
457 if ((filename
= strrchr( path
, '\\' ))) filename
++;
458 else 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
);
471 static BOOL
add_directory( HFCI fci
, WCHAR
*dir
)
475 WIN32_FIND_DATAW data
;
478 if (!(buffer
= cab_alloc( (lstrlenW(dir
) + MAX_PATH
+ 2) * sizeof(WCHAR
) ))) return FALSE
;
479 lstrcpyW( buffer
, dir
);
480 p
= buffer
+ lstrlenW( buffer
);
481 if (p
> buffer
&& p
[-1] != '\\') *p
++ = '\\';
484 if ((handle
= FindFirstFileW( buffer
, &data
)) != INVALID_HANDLE_VALUE
)
488 if (data
.cFileName
[0] == '.' && !data
.cFileName
[1]) continue;
489 if (data
.cFileName
[0] == '.' && data
.cFileName
[1] == '.' && !data
.cFileName
[2]) continue;
490 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) continue;
492 lstrcpyW( p
, data
.cFileName
);
493 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
494 ret
= add_directory( fci
, buffer
);
496 ret
= add_file( fci
, buffer
);
498 } while (FindNextFileW( handle
, &data
));
505 static BOOL
add_file_or_directory( HFCI fci
, WCHAR
*name
)
507 DWORD attr
= GetFileAttributesW( name
);
509 if (attr
== INVALID_FILE_ATTRIBUTES
)
511 WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name
) );
514 if (attr
& FILE_ATTRIBUTE_DIRECTORY
)
516 if (opt_recurse
) return add_directory( fci
, name
);
517 WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
518 wine_dbgstr_w(name
) );
521 return add_file( fci
, name
);
524 static int new_cabinet( char *cab_dir
)
532 cab
.cb
= opt_cabinet_size
;
533 cab
.cbFolderThresh
= CB_MAX_DISK
;
534 cab
.cbReserveCFHeader
= opt_reserve_space
;
535 cab
.cbReserveCFFolder
= 0;
536 cab
.cbReserveCFData
= 0;
539 cab
.setID
= opt_cabinet_id
;
542 strcpy( cab
.szCabPath
, cab_dir
);
543 strcat( cab
.szCabPath
, "\\" );
544 format_cab_name( cab
.szCab
, 1, opt_cab_file
);
546 fci
= FCICreate( &erf
, fci_file_placed
, cab_alloc
, cab_free
,fci_open
, fci_read
,
547 fci_write
, fci_close
, fci_lseek
, fci_delete
, fci_get_temp
, &cab
, NULL
);
549 for (file
= opt_files
; *file
; file
++)
551 if (!lstrcmpW( *file
, L
"+" ))
552 FCIFlushFolder( fci
, fci_get_next_cab
, fci_status
);
554 if (!(ret
= add_file_or_directory( fci
, *file
))) break;
559 if (!(ret
= FCIFlushCabinet( fci
, FALSE
, fci_get_next_cab
, fci_status
)))
560 WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file
) );
566 static void usage( void )
569 "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
571 " L List the contents of the cabinet\n"
572 " N Create a new cabinet\n"
573 " X Extract files from the cabinet into dest_dir\n"
575 " -d size Set maximum disk size\n"
576 " -h Display this help\n"
577 " -i id Set cabinet id\n"
578 " -m type Set compression type (mszip|none)\n"
579 " -p Preserve directory names\n"
580 " -r Recurse into directories\n"
581 " -s size Reserve space in the cabinet header\n"
582 " -v More verbose output\n" );
585 int __cdecl
wmain( int argc
, WCHAR
*argv
[] )
588 char buffer
[MAX_PATH
];
589 char filename
[MAX_PATH
];
590 char *cab_file
, *file_part
;
593 while (argv
[1] && argv
[1][0] == '-')
599 opt_cabinet_size
= wcstol( argv
[1], NULL
, 10 );
600 if (opt_cabinet_size
< 50000)
602 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
611 opt_cabinet_id
= wcstol( argv
[1], NULL
, 10 );
615 if (!wcscmp( argv
[1], L
"none" )) opt_compression
= tcompTYPE_NONE
;
616 else if (!wcscmp( argv
[1], L
"mszip" )) opt_compression
= tcompTYPE_MSZIP
;
619 WINE_MESSAGE( "cabarc: Unknown compression type %s\n", debugstr_w(argv
[1]));
624 opt_preserve_paths
= TRUE
;
631 opt_reserve_space
= wcstol( argv
[1], NULL
, 10 );
644 if (argc
< 3 || !command
[0] || command
[1])
649 cab_file
= strdupWtoA( CP_ACP
, argv
[2] );
653 if (!GetFullPathNameA( cab_file
, MAX_PATH
, buffer
, &file_part
) || !file_part
)
655 WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file
));
658 strcpy(filename
, file_part
);
661 /* map slash to backslash in all file arguments */
662 for (i
= 1; i
< argc
; i
++)
663 for (p
= argv
[i
]; *p
; p
++)
664 if (*p
== '/') *p
= '\\';
665 opt_files
= argv
+ 1;
666 opt_cab_file
= filename
;
672 return list_cabinet( buffer
);
675 return new_cabinet( buffer
);
678 if (argc
> 1) /* check for destination dir as last argument */
680 WCHAR
*last
= argv
[argc
- 1];
681 if (last
[0] && last
[lstrlenW(last
) - 1] == '\\')
687 WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file
));
688 return extract_cabinet( buffer
);