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
22 #include "wine/port.h"
27 #define WIN32_LEAN_AND_MEAN
32 #include "wine/unicode.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cabarc
);
41 #define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
42 #define _O_APPEND 0x0008
43 #define _O_RANDOM 0x0010
44 #define _O_SEQUENTIAL 0x0020
45 #define _O_TEMPORARY 0x0040
46 #define _O_NOINHERIT 0x0080
47 #define _O_CREAT 0x0100
48 #define _O_TRUNC 0x0200
49 #define _O_EXCL 0x0400
50 #define _O_SHORT_LIVED 0x1000
51 #define _O_TEXT 0x4000
52 #define _O_BINARY 0x8000
54 #define _SH_COMPAT 0x00
55 #define _SH_DENYRW 0x10
56 #define _SH_DENYWR 0x20
57 #define _SH_DENYRD 0x30
58 #define _SH_DENYNO 0x40
60 #define _A_RDONLY 0x01
61 #define _A_HIDDEN 0x02
62 #define _A_SYSTEM 0x04
65 /* command-line options */
66 static int opt_cabinet_size
= CB_MAX_DISK
;
67 static int opt_cabinet_id
;
68 static int opt_compression
= tcompTYPE_MSZIP
;
69 static int opt_recurse
;
70 static int opt_preserve_paths
;
71 static int opt_reserve_space
;
72 static int opt_verbose
;
73 static char *opt_cab_file
;
74 static WCHAR
*opt_dest_dir
;
75 static WCHAR
**opt_files
;
77 static void * CDECL
cab_alloc( ULONG size
)
79 return HeapAlloc( GetProcessHeap(), 0, size
);
82 static void CDECL
cab_free( void *ptr
)
84 HeapFree( GetProcessHeap(), 0, ptr
);
87 static WCHAR
*strdupAtoW( UINT cp
, const char *str
)
92 DWORD len
= MultiByteToWideChar( cp
, 0, str
, -1, NULL
, 0 );
93 if ((ret
= cab_alloc( len
* sizeof(WCHAR
) )))
94 MultiByteToWideChar( cp
, 0, str
, -1, ret
, len
);
99 static char *strdupWtoA( UINT cp
, const WCHAR
*str
)
104 DWORD len
= WideCharToMultiByte( cp
, 0, str
, -1, NULL
, 0, NULL
, NULL
);
105 if ((ret
= cab_alloc( len
)))
106 WideCharToMultiByte( cp
, 0, str
, -1, ret
, len
, NULL
, NULL
);
111 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
112 static BOOL
format_cab_name( char *dest
, int id
, const char *name
)
114 const char *num
= strchr( name
, '*' );
121 strcpy( dest
, name
);
124 WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
128 memcpy( dest
, name
, len
);
129 len
+= sprintf( dest
+ len
, "%u", id
);
130 lstrcpynA( dest
+ len
, num
+ 1, CB_MAX_CABINET_NAME
- len
);
134 static int CDECL
fci_file_placed( CCAB
*cab
, char *file
, LONG size
, BOOL continuation
, void *ptr
)
136 if (!continuation
&& opt_verbose
) printf( "adding %s\n", file
);
140 static INT_PTR CDECL
fci_open( char *file
, int oflag
, int pmode
, int *err
, void *ptr
)
142 DWORD creation
= 0, sharing
= 0;
146 switch (oflag
& _O_ACCMODE
)
148 case _O_RDONLY
: ioflag
|= GENERIC_READ
; break;
149 case _O_WRONLY
: ioflag
|= GENERIC_WRITE
; break;
150 case _O_RDWR
: ioflag
|= GENERIC_READ
| GENERIC_WRITE
; break;
153 if (oflag
& _O_CREAT
)
155 if (oflag
& _O_EXCL
) creation
= CREATE_NEW
;
156 else if (oflag
& _O_TRUNC
) creation
= CREATE_ALWAYS
;
157 else creation
= OPEN_ALWAYS
;
161 if (oflag
& _O_TRUNC
) creation
= TRUNCATE_EXISTING
;
162 else creation
= OPEN_EXISTING
;
165 switch (pmode
& 0x70)
167 case _SH_DENYRW
: sharing
= 0; break;
168 case _SH_DENYWR
: sharing
= FILE_SHARE_READ
; break;
169 case _SH_DENYRD
: sharing
= FILE_SHARE_WRITE
; break;
170 default: sharing
= FILE_SHARE_READ
| FILE_SHARE_WRITE
; break;
173 handle
= CreateFileA( file
, ioflag
, sharing
, NULL
, creation
, FILE_ATTRIBUTE_NORMAL
, NULL
);
174 if (handle
== INVALID_HANDLE_VALUE
) *err
= GetLastError();
175 return (INT_PTR
)handle
;
178 static UINT CDECL
fci_read( INT_PTR hf
, void *pv
, UINT cb
, int *err
, void *ptr
)
182 if (!ReadFile( (HANDLE
)hf
, pv
, cb
, &num_read
, NULL
))
184 *err
= GetLastError();
190 static UINT CDECL
fci_write( INT_PTR hf
, void *pv
, UINT cb
, int *err
, void *ptr
)
194 if (!WriteFile( (HANDLE
) hf
, pv
, cb
, &written
, NULL
))
196 *err
= GetLastError();
202 static int CDECL
fci_close( INT_PTR hf
, int *err
, void *ptr
)
204 if (!CloseHandle( (HANDLE
)hf
))
206 *err
= GetLastError();
212 static LONG CDECL
fci_lseek( INT_PTR hf
, LONG dist
, int seektype
, int *err
, void *ptr
)
216 ret
= SetFilePointer( (HANDLE
)hf
, dist
, NULL
, seektype
);
217 if (ret
== INVALID_SET_FILE_POINTER
&& GetLastError())
219 *err
= GetLastError();
225 static int CDECL
fci_delete( char *file
, int *err
, void *ptr
)
227 if (!DeleteFileA( file
))
229 *err
= GetLastError();
235 static BOOL CDECL
fci_get_temp( char *name
, int size
, void *ptr
)
239 if (!GetTempPathA( MAX_PATH
, path
)) return FALSE
;
240 if (!GetTempFileNameA( path
, "cab", 0, name
)) return FALSE
;
245 static BOOL CDECL
fci_get_next_cab( CCAB
*cab
, ULONG prev_size
, void *ptr
)
247 return format_cab_name( cab
->szCab
, cab
->iCab
+ 1, opt_cab_file
);
250 static LONG CDECL
fci_status( UINT type
, ULONG cb1
, ULONG cb2
, void *ptr
)
255 static INT_PTR CDECL
fci_get_open_info( char *name
, USHORT
*date
, USHORT
*time
,
256 USHORT
*attribs
, int *err
, void *ptr
)
259 BY_HANDLE_FILE_INFORMATION info
;
260 WCHAR
*p
, *nameW
= strdupAtoW( CP_UTF8
, name
);
262 handle
= CreateFileW( nameW
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
263 NULL
, OPEN_EXISTING
, 0, NULL
);
264 if (handle
== INVALID_HANDLE_VALUE
)
266 *err
= GetLastError();
267 WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW
), *err
);
271 if (!GetFileInformationByHandle( handle
, &info
))
273 *err
= GetLastError();
274 CloseHandle( handle
);
278 FileTimeToDosDateTime( &info
.ftLastWriteTime
, date
, time
);
279 *attribs
= info
.dwFileAttributes
& (_A_RDONLY
| _A_HIDDEN
| _A_SYSTEM
| _A_ARCH
);
280 for (p
= nameW
; *p
; p
++) if (*p
>= 0x80) break;
281 if (*p
) *attribs
|= _A_NAME_IS_UTF
;
283 return (INT_PTR
)handle
;
286 static INT_PTR CDECL
fdi_open( char *file
, int oflag
, int pmode
)
289 return fci_open( file
, oflag
, pmode
, &err
, NULL
);
292 static UINT CDECL
fdi_read( INT_PTR hf
, void *pv
, UINT cb
)
295 return fci_read( hf
, pv
, cb
, &err
, NULL
);
298 static UINT CDECL
fdi_write( INT_PTR hf
, void *pv
, UINT cb
)
301 return fci_write( hf
, pv
, cb
, &err
, NULL
);
304 static int CDECL
fdi_close( INT_PTR hf
)
307 return fci_close( hf
, &err
, NULL
);
310 static LONG CDECL
fdi_lseek( INT_PTR hf
, LONG dist
, int whence
)
313 return fci_lseek( hf
, dist
, whence
, &err
, NULL
);
317 /* create directories leading to a given file */
318 static void create_directories( const WCHAR
*name
)
322 /* create the directory/directories */
323 path
= cab_alloc( (strlenW(name
) + 1) * sizeof(WCHAR
) );
326 p
= strchrW(path
, '\\');
330 if (!CreateDirectoryW( path
, NULL
))
331 WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path
), GetLastError());
333 p
= strchrW(p
+1, '\\');
338 /* check if file name matches against one of the files specification */
339 static BOOL
match_files( const WCHAR
*name
)
343 if (!*opt_files
) return TRUE
;
344 for (i
= 0; opt_files
[i
]; i
++)
346 unsigned int len
= strlenW( opt_files
[i
] );
347 /* FIXME: do smarter matching, and wildcards */
349 if (strncmpiW( name
, opt_files
[i
], len
)) continue;
350 if (opt_files
[i
][len
- 1] == '\\' || !name
[len
] || name
[len
] == '\\') return TRUE
;
355 static INT_PTR CDECL
list_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
361 case fdintCABINET_INFO
:
364 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
365 if (match_files( nameW
))
367 char *nameU
= strdupWtoA( CP_UNIXCP
, nameW
);
370 char attrs
[] = "rxash";
371 if (!(pfdin
->attribs
& _A_RDONLY
)) attrs
[0] = '-';
372 if (!(pfdin
->attribs
& _A_EXEC
)) attrs
[1] = '-';
373 if (!(pfdin
->attribs
& _A_ARCH
)) attrs
[2] = '-';
374 if (!(pfdin
->attribs
& _A_SYSTEM
)) attrs
[3] = '-';
375 if (!(pfdin
->attribs
& _A_HIDDEN
)) attrs
[4] = '-';
376 printf( " %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs
, pfdin
->cb
,
377 (pfdin
->date
>> 9) + 1980, (pfdin
->date
>> 5) & 0x0f, pfdin
->date
& 0x1f,
378 pfdin
->time
>> 11, (pfdin
->time
>> 5) & 0x3f, (pfdin
->time
& 0x1f) * 2 );
380 printf( "%s\n", nameU
);
386 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
391 static int list_cabinet( char *cab_dir
)
395 HFDI fdi
= FDICreate( cab_alloc
, cab_free
, fdi_open
, fdi_read
,
396 fdi_write
, fdi_close
, fdi_lseek
, cpuUNKNOWN
, &erf
);
398 if (!FDICopy( fdi
, opt_cab_file
, cab_dir
, 0, list_notify
, NULL
, NULL
)) ret
= GetLastError();
403 static INT_PTR CDECL
extract_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
405 WCHAR
*file
, *nameW
, *path
= NULL
;
410 case fdintCABINET_INFO
:
414 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
415 if (opt_preserve_paths
)
418 while (*file
== '\\') file
++; /* remove leading backslashes */
422 if ((file
= strrchrW( nameW
, '\\' ))) file
++;
428 path
= cab_alloc( (strlenW(opt_dest_dir
) + strlenW(file
) + 1) * sizeof(WCHAR
) );
429 strcpyW( path
, opt_dest_dir
);
430 strcatW( path
, file
);
434 if (match_files( file
))
438 char *nameU
= strdupWtoA( CP_UNIXCP
, path
);
439 printf( "extracting %s\n", nameU
);
442 create_directories( path
);
443 /* FIXME: check for existing file and overwrite mode */
444 ret
= (INT_PTR
)CreateFileW( path
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
445 NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
450 if (path
!= file
) cab_free( path
);
453 case fdintCLOSE_FILE_INFO
:
454 CloseHandle( (HANDLE
)pfdin
->hf
);
458 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
463 static int extract_cabinet( char *cab_dir
)
467 HFDI fdi
= FDICreate( cab_alloc
, cab_free
, fdi_open
, fdi_read
,
468 fdi_write
, fdi_close
, fdi_lseek
, cpuUNKNOWN
, &erf
);
470 if (!FDICopy( fdi
, opt_cab_file
, cab_dir
, 0, extract_notify
, NULL
, NULL
)) ret
= GetLastError();
475 static BOOL
add_file( HFCI fci
, WCHAR
*name
)
478 char *filename
, *path
= strdupWtoA( CP_UTF8
, name
);
480 if (!opt_preserve_paths
)
482 if ((filename
= strrchr( path
, '\\' ))) filename
++;
483 else filename
= path
;
488 while (*filename
== '\\') filename
++; /* remove leading backslashes */
490 ret
= FCIAddFile( fci
, path
, filename
, FALSE
,
491 fci_get_next_cab
, fci_status
, fci_get_open_info
, opt_compression
);
496 static BOOL
add_directory( HFCI fci
, WCHAR
*dir
)
498 static const WCHAR wildcardW
[] = {'*',0};
501 WIN32_FIND_DATAW data
;
504 if (!(buffer
= cab_alloc( (strlenW(dir
) + MAX_PATH
+ 2) * sizeof(WCHAR
) ))) return FALSE
;
505 strcpyW( buffer
, dir
);
506 p
= buffer
+ strlenW( buffer
);
507 if (p
> buffer
&& p
[-1] != '\\') *p
++ = '\\';
508 strcpyW( p
, wildcardW
);
510 if ((handle
= FindFirstFileW( buffer
, &data
)) != INVALID_HANDLE_VALUE
)
514 if (data
.cFileName
[0] == '.' && !data
.cFileName
[1]) continue;
515 if (data
.cFileName
[0] == '.' && data
.cFileName
[1] == '.' && !data
.cFileName
[2]) continue;
516 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) continue;
518 strcpyW( p
, data
.cFileName
);
519 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
520 ret
= add_directory( fci
, buffer
);
522 ret
= add_file( fci
, buffer
);
524 } while (FindNextFileW( handle
, &data
));
531 static BOOL
add_file_or_directory( HFCI fci
, WCHAR
*name
)
533 DWORD attr
= GetFileAttributesW( name
);
535 if (attr
== INVALID_FILE_ATTRIBUTES
)
537 WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name
) );
540 if (attr
& FILE_ATTRIBUTE_DIRECTORY
)
542 if (opt_recurse
) return add_directory( fci
, name
);
543 WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
544 wine_dbgstr_w(name
) );
547 return add_file( fci
, name
);
550 static int new_cabinet( char *cab_dir
)
552 static const WCHAR plusW
[] = {'+',0};
559 cab
.cb
= opt_cabinet_size
;
560 cab
.cbFolderThresh
= CB_MAX_DISK
;
561 cab
.cbReserveCFHeader
= opt_reserve_space
;
562 cab
.cbReserveCFFolder
= 0;
563 cab
.cbReserveCFData
= 0;
566 cab
.setID
= opt_cabinet_id
;
569 strcpy( cab
.szCabPath
, cab_dir
);
570 strcat( cab
.szCabPath
, "\\" );
571 format_cab_name( cab
.szCab
, 1, opt_cab_file
);
573 fci
= FCICreate( &erf
, fci_file_placed
, cab_alloc
, cab_free
,fci_open
, fci_read
,
574 fci_write
, fci_close
, fci_lseek
, fci_delete
, fci_get_temp
, &cab
, NULL
);
576 for (file
= opt_files
; *file
; file
++)
578 if (!strcmpW( *file
, plusW
))
579 FCIFlushFolder( fci
, fci_get_next_cab
, fci_status
);
581 if (!(ret
= add_file_or_directory( fci
, *file
))) break;
586 if (!(ret
= FCIFlushCabinet( fci
, FALSE
, fci_get_next_cab
, fci_status
)))
587 WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file
) );
593 static void usage( void )
596 "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
598 " L List the contents of the cabinet\n"
599 " N Create a new cabinet\n"
600 " X Extract files from the cabinet into dest_dir\n"
602 " -d size Set maximum disk size\n"
603 " -h Display this help\n"
604 " -i id Set cabinet id\n"
605 " -m type Set compression type (mszip|none)\n"
606 " -p Preserve directory names\n"
607 " -r Recurse into directories\n"
608 " -s size Reserve space in the cabinet header\n"
609 " -v More verbose output\n" );
612 int wmain( int argc
, WCHAR
*argv
[] )
614 static const WCHAR noneW
[] = {'n','o','n','e',0};
615 static const WCHAR mszipW
[] = {'m','s','z','i','p',0};
618 char buffer
[MAX_PATH
];
619 char *cab_file
, *file_part
;
622 while (argv
[1] && argv
[1][0] == '-')
628 opt_cabinet_size
= atoiW( argv
[1] );
629 if (opt_cabinet_size
< 50000)
631 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
640 opt_cabinet_id
= atoiW( argv
[1] );
644 if (!strcmpiW( argv
[1], noneW
)) opt_compression
= tcompTYPE_NONE
;
645 else if (!strcmpiW( argv
[1], mszipW
)) opt_compression
= tcompTYPE_MSZIP
;
648 WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", optarg
);
653 opt_preserve_paths
= 1;
660 opt_reserve_space
= atoiW( argv
[1] );
673 if (argc
< 3 || !command
[0] || command
[1])
678 cab_file
= strdupWtoA( CP_ACP
, argv
[2] );
682 if (!GetFullPathNameA( cab_file
, MAX_PATH
, buffer
, &file_part
) || !file_part
)
684 WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file
));
688 cab_free( cab_file
);
690 /* map slash to backslash in all file arguments */
691 for (i
= 1; i
< argc
; i
++)
692 for (p
= argv
[i
]; *p
; p
++)
693 if (*p
== '/') *p
= '\\';
694 opt_files
= argv
+ 1;
695 opt_cab_file
= file_part
;
701 return list_cabinet( buffer
);
704 return new_cabinet( buffer
);
707 if (argc
> 1) /* check for destination dir as last argument */
709 WCHAR
*last
= argv
[argc
- 1];
710 if (last
[0] && last
[strlenW(last
) - 1] == '\\')
716 return extract_cabinet( buffer
);