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
);
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
56 #define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
60 #define _SH_COMPAT 0x00
61 #define _SH_DENYRW 0x10
62 #define _SH_DENYWR 0x20
63 #define _SH_DENYRD 0x30
64 #define _SH_DENYNO 0x40
68 #define _A_RDONLY 0x01
69 #define _A_HIDDEN 0x02
70 #define _A_SYSTEM 0x04
74 /* command-line options */
75 static int opt_cabinet_size
= CB_MAX_DISK
;
76 static int opt_cabinet_id
;
77 static int opt_compression
= tcompTYPE_MSZIP
;
78 static BOOL opt_recurse
;
79 static BOOL opt_preserve_paths
;
80 static int opt_reserve_space
;
81 static int opt_verbose
;
82 static char *opt_cab_file
;
83 static WCHAR
*opt_dest_dir
;
84 static WCHAR
**opt_files
;
86 static void * CDECL
cab_alloc( ULONG size
)
88 return HeapAlloc( GetProcessHeap(), 0, size
);
91 static void CDECL
cab_free( void *ptr
)
93 HeapFree( GetProcessHeap(), 0, ptr
);
96 static WCHAR
*strdupAtoW( UINT cp
, const char *str
)
101 DWORD len
= MultiByteToWideChar( cp
, 0, str
, -1, NULL
, 0 );
102 if ((ret
= cab_alloc( len
* sizeof(WCHAR
) )))
103 MultiByteToWideChar( cp
, 0, str
, -1, ret
, len
);
108 static char *strdupWtoA( UINT cp
, const WCHAR
*str
)
113 DWORD len
= WideCharToMultiByte( cp
, 0, str
, -1, NULL
, 0, NULL
, NULL
);
114 if ((ret
= cab_alloc( len
)))
115 WideCharToMultiByte( cp
, 0, str
, -1, ret
, len
, NULL
, NULL
);
120 /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
121 static BOOL
format_cab_name( char *dest
, int id
, const char *name
)
123 const char *num
= strchr( name
, '*' );
130 strcpy( dest
, name
);
133 WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
137 memcpy( dest
, name
, len
);
138 len
+= sprintf( dest
+ len
, "%u", id
);
139 lstrcpynA( dest
+ len
, num
+ 1, CB_MAX_CABINET_NAME
- len
);
143 static int CDECL
fci_file_placed( CCAB
*cab
, char *file
, LONG size
, BOOL continuation
, void *ptr
)
145 if (!continuation
&& opt_verbose
) printf( "adding %s\n", file
);
149 static INT_PTR CDECL
fci_open( char *file
, int oflag
, int pmode
, int *err
, void *ptr
)
151 DWORD creation
= 0, sharing
= 0;
155 switch (oflag
& _O_ACCMODE
)
157 case _O_RDONLY
: ioflag
|= GENERIC_READ
; break;
158 case _O_WRONLY
: ioflag
|= GENERIC_WRITE
; break;
159 case _O_RDWR
: ioflag
|= GENERIC_READ
| GENERIC_WRITE
; break;
162 if (oflag
& _O_CREAT
)
164 if (oflag
& _O_EXCL
) creation
= CREATE_NEW
;
165 else if (oflag
& _O_TRUNC
) creation
= CREATE_ALWAYS
;
166 else creation
= OPEN_ALWAYS
;
170 if (oflag
& _O_TRUNC
) creation
= TRUNCATE_EXISTING
;
171 else creation
= OPEN_EXISTING
;
174 switch (pmode
& 0x70)
176 case _SH_DENYRW
: sharing
= 0; break;
177 case _SH_DENYWR
: sharing
= FILE_SHARE_READ
; break;
178 case _SH_DENYRD
: sharing
= FILE_SHARE_WRITE
; break;
179 default: sharing
= FILE_SHARE_READ
| FILE_SHARE_WRITE
; break;
182 handle
= CreateFileA( file
, ioflag
, sharing
, NULL
, creation
, FILE_ATTRIBUTE_NORMAL
, NULL
);
183 if (handle
== INVALID_HANDLE_VALUE
) *err
= GetLastError();
184 return (INT_PTR
)handle
;
187 static UINT CDECL
fci_read( INT_PTR hf
, void *pv
, UINT cb
, int *err
, void *ptr
)
191 if (!ReadFile( (HANDLE
)hf
, pv
, cb
, &num_read
, NULL
))
193 *err
= GetLastError();
199 static UINT CDECL
fci_write( INT_PTR hf
, void *pv
, UINT cb
, int *err
, void *ptr
)
203 if (!WriteFile( (HANDLE
) hf
, pv
, cb
, &written
, NULL
))
205 *err
= GetLastError();
211 static int CDECL
fci_close( INT_PTR hf
, int *err
, void *ptr
)
213 if (!CloseHandle( (HANDLE
)hf
))
215 *err
= GetLastError();
221 static LONG CDECL
fci_lseek( INT_PTR hf
, LONG dist
, int seektype
, int *err
, void *ptr
)
225 ret
= SetFilePointer( (HANDLE
)hf
, dist
, NULL
, seektype
);
226 if (ret
== INVALID_SET_FILE_POINTER
&& GetLastError())
228 *err
= GetLastError();
234 static int CDECL
fci_delete( char *file
, int *err
, void *ptr
)
236 if (!DeleteFileA( file
))
238 *err
= GetLastError();
244 static BOOL CDECL
fci_get_temp( char *name
, int size
, void *ptr
)
248 if (!GetTempPathA( MAX_PATH
, path
)) return FALSE
;
249 if (!GetTempFileNameA( path
, "cab", 0, name
)) return FALSE
;
254 static BOOL CDECL
fci_get_next_cab( CCAB
*cab
, ULONG prev_size
, void *ptr
)
256 return format_cab_name( cab
->szCab
, cab
->iCab
+ 1, opt_cab_file
);
259 static LONG CDECL
fci_status( UINT type
, ULONG cb1
, ULONG cb2
, void *ptr
)
264 static INT_PTR CDECL
fci_get_open_info( char *name
, USHORT
*date
, USHORT
*time
,
265 USHORT
*attribs
, int *err
, void *ptr
)
268 BY_HANDLE_FILE_INFORMATION info
;
269 WCHAR
*p
, *nameW
= strdupAtoW( CP_UTF8
, name
);
271 handle
= CreateFileW( nameW
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
272 NULL
, OPEN_EXISTING
, 0, NULL
);
273 if (handle
== INVALID_HANDLE_VALUE
)
275 *err
= GetLastError();
276 WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW
), *err
);
280 if (!GetFileInformationByHandle( handle
, &info
))
282 *err
= GetLastError();
283 CloseHandle( handle
);
287 FileTimeToDosDateTime( &info
.ftLastWriteTime
, date
, time
);
288 *attribs
= info
.dwFileAttributes
& (_A_RDONLY
| _A_HIDDEN
| _A_SYSTEM
| _A_ARCH
);
289 for (p
= nameW
; *p
; p
++) if (*p
>= 0x80) break;
290 if (*p
) *attribs
|= _A_NAME_IS_UTF
;
292 return (INT_PTR
)handle
;
295 static INT_PTR CDECL
fdi_open( char *file
, int oflag
, int pmode
)
298 return fci_open( file
, oflag
, pmode
, &err
, NULL
);
301 static UINT CDECL
fdi_read( INT_PTR hf
, void *pv
, UINT cb
)
304 return fci_read( hf
, pv
, cb
, &err
, NULL
);
307 static UINT CDECL
fdi_write( INT_PTR hf
, void *pv
, UINT cb
)
310 return fci_write( hf
, pv
, cb
, &err
, NULL
);
313 static int CDECL
fdi_close( INT_PTR hf
)
316 return fci_close( hf
, &err
, NULL
);
319 static LONG CDECL
fdi_lseek( INT_PTR hf
, LONG dist
, int whence
)
322 return fci_lseek( hf
, dist
, whence
, &err
, NULL
);
326 /* create directories leading to a given file */
327 static void create_directories( const WCHAR
*name
)
331 /* create the directory/directories */
332 path
= cab_alloc( (strlenW(name
) + 1) * sizeof(WCHAR
) );
335 p
= strchrW(path
, '\\');
339 if (!CreateDirectoryW( path
, NULL
))
340 WINE_TRACE("Couldn't create directory %s - error: %d\n", wine_dbgstr_w(path
), GetLastError());
342 p
= strchrW(p
+1, '\\');
347 /* check if file name matches against one of the files specification */
348 static BOOL
match_files( const WCHAR
*name
)
352 if (!*opt_files
) return TRUE
;
353 for (i
= 0; opt_files
[i
]; i
++)
355 unsigned int len
= strlenW( opt_files
[i
] );
356 /* FIXME: do smarter matching, and wildcards */
358 if (strncmpiW( name
, opt_files
[i
], len
)) continue;
359 if (opt_files
[i
][len
- 1] == '\\' || !name
[len
] || name
[len
] == '\\') return TRUE
;
364 static INT_PTR CDECL
list_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
370 case fdintCABINET_INFO
:
373 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
374 if (match_files( nameW
))
376 char *nameU
= strdupWtoA( CP_UNIXCP
, nameW
);
379 char attrs
[] = "rxash";
380 if (!(pfdin
->attribs
& _A_RDONLY
)) attrs
[0] = '-';
381 if (!(pfdin
->attribs
& _A_EXEC
)) attrs
[1] = '-';
382 if (!(pfdin
->attribs
& _A_ARCH
)) attrs
[2] = '-';
383 if (!(pfdin
->attribs
& _A_SYSTEM
)) attrs
[3] = '-';
384 if (!(pfdin
->attribs
& _A_HIDDEN
)) attrs
[4] = '-';
385 printf( " %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs
, pfdin
->cb
,
386 (pfdin
->date
>> 9) + 1980, (pfdin
->date
>> 5) & 0x0f, pfdin
->date
& 0x1f,
387 pfdin
->time
>> 11, (pfdin
->time
>> 5) & 0x3f, (pfdin
->time
& 0x1f) * 2 );
389 printf( "%s\n", nameU
);
395 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
400 static int list_cabinet( char *cab_dir
)
404 HFDI fdi
= FDICreate( cab_alloc
, cab_free
, fdi_open
, fdi_read
,
405 fdi_write
, fdi_close
, fdi_lseek
, cpuUNKNOWN
, &erf
);
407 if (!FDICopy( fdi
, opt_cab_file
, cab_dir
, 0, list_notify
, NULL
, NULL
)) ret
= GetLastError();
412 static INT_PTR CDECL
extract_notify( FDINOTIFICATIONTYPE fdint
, PFDINOTIFICATION pfdin
)
414 WCHAR
*file
, *nameW
, *path
= NULL
;
419 case fdintCABINET_INFO
:
423 nameW
= strdupAtoW( (pfdin
->attribs
& _A_NAME_IS_UTF
) ? CP_UTF8
: CP_ACP
, pfdin
->psz1
);
424 if (opt_preserve_paths
)
427 while (*file
== '\\') file
++; /* remove leading backslashes */
431 if ((file
= strrchrW( nameW
, '\\' ))) file
++;
437 path
= cab_alloc( (strlenW(opt_dest_dir
) + strlenW(file
) + 1) * sizeof(WCHAR
) );
438 strcpyW( path
, opt_dest_dir
);
439 strcatW( path
, file
);
443 if (match_files( file
))
447 char *nameU
= strdupWtoA( CP_UNIXCP
, path
);
448 printf( "extracting %s\n", nameU
);
451 create_directories( path
);
452 /* FIXME: check for existing file and overwrite mode */
453 ret
= (INT_PTR
)CreateFileW( path
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
454 NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
459 if (path
!= file
) cab_free( path
);
462 case fdintCLOSE_FILE_INFO
:
463 CloseHandle( (HANDLE
)pfdin
->hf
);
466 case fdintNEXT_CABINET
:
467 WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin
->fdie
, pfdin
->psz3
, pfdin
->psz1
);
468 return pfdin
->fdie
== FDIERROR_NONE
? 0 : -1;
474 WINE_FIXME( "Unexpected notification type %d.\n", fdint
);
479 static int extract_cabinet( char *cab_dir
)
483 HFDI fdi
= FDICreate( cab_alloc
, cab_free
, fdi_open
, fdi_read
,
484 fdi_write
, fdi_close
, fdi_lseek
, cpuUNKNOWN
, &erf
);
486 if (!FDICopy( fdi
, opt_cab_file
, cab_dir
, 0, extract_notify
, NULL
, NULL
))
488 ret
= GetLastError();
489 WINE_WARN("FDICopy() failed: code %u\n", ret
);
495 static BOOL
add_file( HFCI fci
, WCHAR
*name
)
498 char *filename
, *path
= strdupWtoA( CP_UTF8
, name
);
500 if (!opt_preserve_paths
)
502 if ((filename
= strrchr( path
, '\\' ))) filename
++;
503 else filename
= path
;
508 while (*filename
== '\\') filename
++; /* remove leading backslashes */
510 ret
= FCIAddFile( fci
, path
, filename
, FALSE
,
511 fci_get_next_cab
, fci_status
, fci_get_open_info
, opt_compression
);
516 static BOOL
add_directory( HFCI fci
, WCHAR
*dir
)
518 static const WCHAR wildcardW
[] = {'*',0};
521 WIN32_FIND_DATAW data
;
524 if (!(buffer
= cab_alloc( (strlenW(dir
) + MAX_PATH
+ 2) * sizeof(WCHAR
) ))) return FALSE
;
525 strcpyW( buffer
, dir
);
526 p
= buffer
+ strlenW( buffer
);
527 if (p
> buffer
&& p
[-1] != '\\') *p
++ = '\\';
528 strcpyW( p
, wildcardW
);
530 if ((handle
= FindFirstFileW( buffer
, &data
)) != INVALID_HANDLE_VALUE
)
534 if (data
.cFileName
[0] == '.' && !data
.cFileName
[1]) continue;
535 if (data
.cFileName
[0] == '.' && data
.cFileName
[1] == '.' && !data
.cFileName
[2]) continue;
536 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) continue;
538 strcpyW( p
, data
.cFileName
);
539 if (data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
540 ret
= add_directory( fci
, buffer
);
542 ret
= add_file( fci
, buffer
);
544 } while (FindNextFileW( handle
, &data
));
551 static BOOL
add_file_or_directory( HFCI fci
, WCHAR
*name
)
553 DWORD attr
= GetFileAttributesW( name
);
555 if (attr
== INVALID_FILE_ATTRIBUTES
)
557 WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name
) );
560 if (attr
& FILE_ATTRIBUTE_DIRECTORY
)
562 if (opt_recurse
) return add_directory( fci
, name
);
563 WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
564 wine_dbgstr_w(name
) );
567 return add_file( fci
, name
);
570 static int new_cabinet( char *cab_dir
)
572 static const WCHAR plusW
[] = {'+',0};
579 cab
.cb
= opt_cabinet_size
;
580 cab
.cbFolderThresh
= CB_MAX_DISK
;
581 cab
.cbReserveCFHeader
= opt_reserve_space
;
582 cab
.cbReserveCFFolder
= 0;
583 cab
.cbReserveCFData
= 0;
586 cab
.setID
= opt_cabinet_id
;
589 strcpy( cab
.szCabPath
, cab_dir
);
590 strcat( cab
.szCabPath
, "\\" );
591 format_cab_name( cab
.szCab
, 1, opt_cab_file
);
593 fci
= FCICreate( &erf
, fci_file_placed
, cab_alloc
, cab_free
,fci_open
, fci_read
,
594 fci_write
, fci_close
, fci_lseek
, fci_delete
, fci_get_temp
, &cab
, NULL
);
596 for (file
= opt_files
; *file
; file
++)
598 if (!strcmpW( *file
, plusW
))
599 FCIFlushFolder( fci
, fci_get_next_cab
, fci_status
);
601 if (!(ret
= add_file_or_directory( fci
, *file
))) break;
606 if (!(ret
= FCIFlushCabinet( fci
, FALSE
, fci_get_next_cab
, fci_status
)))
607 WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file
) );
613 static void usage( void )
616 "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
618 " L List the contents of the cabinet\n"
619 " N Create a new cabinet\n"
620 " X Extract files from the cabinet into dest_dir\n"
622 " -d size Set maximum disk size\n"
623 " -h Display this help\n"
624 " -i id Set cabinet id\n"
625 " -m type Set compression type (mszip|none)\n"
626 " -p Preserve directory names\n"
627 " -r Recurse into directories\n"
628 " -s size Reserve space in the cabinet header\n"
629 " -v More verbose output\n" );
632 int wmain( int argc
, WCHAR
*argv
[] )
634 static const WCHAR noneW
[] = {'n','o','n','e',0};
635 static const WCHAR mszipW
[] = {'m','s','z','i','p',0};
638 char buffer
[MAX_PATH
];
639 char filename
[MAX_PATH
];
640 char *cab_file
, *file_part
;
643 while (argv
[1] && argv
[1][0] == '-')
649 opt_cabinet_size
= atoiW( argv
[1] );
650 if (opt_cabinet_size
< 50000)
652 WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
661 opt_cabinet_id
= atoiW( argv
[1] );
665 if (!strcmpiW( argv
[1], noneW
)) opt_compression
= tcompTYPE_NONE
;
666 else if (!strcmpiW( argv
[1], mszipW
)) opt_compression
= tcompTYPE_MSZIP
;
669 char *arg
= strdupWtoA( CP_ACP
, argv
[1] );
670 WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", arg
);
675 opt_preserve_paths
= TRUE
;
682 opt_reserve_space
= atoiW( argv
[1] );
695 if (argc
< 3 || !command
[0] || command
[1])
700 cab_file
= strdupWtoA( CP_ACP
, argv
[2] );
704 if (!GetFullPathNameA( cab_file
, MAX_PATH
, buffer
, &file_part
) || !file_part
)
706 WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file
));
709 strcpy(filename
, file_part
);
712 /* map slash to backslash in all file arguments */
713 for (i
= 1; i
< argc
; i
++)
714 for (p
= argv
[i
]; *p
; p
++)
715 if (*p
== '/') *p
= '\\';
716 opt_files
= argv
+ 1;
717 opt_cab_file
= filename
;
723 return list_cabinet( buffer
);
726 return new_cabinet( buffer
);
729 if (argc
> 1) /* check for destination dir as last argument */
731 WCHAR
*last
= argv
[argc
- 1];
732 if (last
[0] && last
[strlenW(last
) - 1] == '\\')
738 WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file
));
739 return extract_cabinet( buffer
);