123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- /*
- * Tool to manipulate cabinet files
- *
- * Copyright 2011 Alexandre Julliard
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <io.h>
- #include <share.h>
- #define WIN32_LEAN_AND_MEAN
- #include "windows.h"
- #include "fci.h"
- #include "fdi.h"
- #include "wine/debug.h"
- WINE_DEFAULT_DEBUG_CHANNEL(cabarc);
- /* command-line options */
- static int opt_cabinet_size = CB_MAX_DISK;
- static int opt_cabinet_id;
- static int opt_compression = tcompTYPE_MSZIP;
- static BOOL opt_recurse;
- static BOOL opt_preserve_paths;
- static int opt_reserve_space;
- static int opt_verbose;
- static char *opt_cab_file;
- static WCHAR *opt_dest_dir;
- static WCHAR **opt_files;
- static void * CDECL cab_alloc( ULONG size )
- {
- return HeapAlloc( GetProcessHeap(), 0, size );
- }
- static void CDECL cab_free( void *ptr )
- {
- HeapFree( GetProcessHeap(), 0, ptr );
- }
- static WCHAR *strdupAtoW( UINT cp, const char *str )
- {
- WCHAR *ret = NULL;
- if (str)
- {
- DWORD len = MultiByteToWideChar( cp, 0, str, -1, NULL, 0 );
- if ((ret = cab_alloc( len * sizeof(WCHAR) )))
- MultiByteToWideChar( cp, 0, str, -1, ret, len );
- }
- return ret;
- }
- static char *strdupWtoA( UINT cp, const WCHAR *str )
- {
- char *ret = NULL;
- if (str)
- {
- DWORD len = WideCharToMultiByte( cp, 0, str, -1, NULL, 0, NULL, NULL );
- if ((ret = cab_alloc( len )))
- WideCharToMultiByte( cp, 0, str, -1, ret, len, NULL, NULL );
- }
- return ret;
- }
- /* format a cabinet name by replacing the '*' wildcard by the cabinet id */
- static BOOL format_cab_name( char *dest, int id, const char *name )
- {
- const char *num = strchr( name, '*' );
- int len;
- if (!num)
- {
- if (id == 1)
- {
- strcpy( dest, name );
- return TRUE;
- }
- WINE_MESSAGE( "cabarc: Cabinet name must contain a '*' character\n" );
- return FALSE;
- }
- len = num - name;
- memcpy( dest, name, len );
- len += sprintf( dest + len, "%u", id );
- lstrcpynA( dest + len, num + 1, CB_MAX_CABINET_NAME - len );
- return TRUE;
- }
- static int CDECL fci_file_placed( CCAB *cab, char *file, LONG size, BOOL continuation, void *ptr )
- {
- if (!continuation && opt_verbose) printf( "adding %s\n", file );
- return 0;
- }
- static INT_PTR CDECL fci_open( char *file, int oflag, int pmode, int *err, void *ptr )
- {
- DWORD creation = 0, sharing = 0;
- int ioflag = 0;
- HANDLE handle;
- switch (oflag & _O_ACCMODE)
- {
- case _O_RDONLY: ioflag |= GENERIC_READ; break;
- case _O_WRONLY: ioflag |= GENERIC_WRITE; break;
- case _O_RDWR: ioflag |= GENERIC_READ | GENERIC_WRITE; break;
- }
- if (oflag & _O_CREAT)
- {
- if (oflag & _O_EXCL) creation = CREATE_NEW;
- else if (oflag & _O_TRUNC) creation = CREATE_ALWAYS;
- else creation = OPEN_ALWAYS;
- }
- else
- {
- if (oflag & _O_TRUNC) creation = TRUNCATE_EXISTING;
- else creation = OPEN_EXISTING;
- }
- switch (pmode & 0x70)
- {
- case _SH_DENYRW: sharing = 0; break;
- case _SH_DENYWR: sharing = FILE_SHARE_READ; break;
- case _SH_DENYRD: sharing = FILE_SHARE_WRITE; break;
- default: sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; break;
- }
- handle = CreateFileA( file, ioflag, sharing, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL );
- if (handle == INVALID_HANDLE_VALUE) *err = GetLastError();
- return (INT_PTR)handle;
- }
- static UINT CDECL fci_read( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
- {
- DWORD num_read;
- if (!ReadFile( (HANDLE)hf, pv, cb, &num_read, NULL ))
- {
- *err = GetLastError();
- return -1;
- }
- return num_read;
- }
- static UINT CDECL fci_write( INT_PTR hf, void *pv, UINT cb, int *err, void *ptr )
- {
- DWORD written;
- if (!WriteFile( (HANDLE) hf, pv, cb, &written, NULL ))
- {
- *err = GetLastError();
- return -1;
- }
- return written;
- }
- static int CDECL fci_close( INT_PTR hf, int *err, void *ptr )
- {
- if (!CloseHandle( (HANDLE)hf ))
- {
- *err = GetLastError();
- return -1;
- }
- return 0;
- }
- static LONG CDECL fci_lseek( INT_PTR hf, LONG dist, int seektype, int *err, void *ptr )
- {
- DWORD ret;
- ret = SetFilePointer( (HANDLE)hf, dist, NULL, seektype );
- if (ret == INVALID_SET_FILE_POINTER && GetLastError())
- {
- *err = GetLastError();
- return -1;
- }
- return ret;
- }
- static int CDECL fci_delete( char *file, int *err, void *ptr )
- {
- if (!DeleteFileA( file ))
- {
- *err = GetLastError();
- return -1;
- }
- return 0;
- }
- static BOOL CDECL fci_get_temp( char *name, int size, void *ptr )
- {
- char path[MAX_PATH];
- if (!GetTempPathA( MAX_PATH, path )) return FALSE;
- if (!GetTempFileNameA( path, "cab", 0, name )) return FALSE;
- DeleteFileA( name );
- return TRUE;
- }
- static BOOL CDECL fci_get_next_cab( CCAB *cab, ULONG prev_size, void *ptr )
- {
- return format_cab_name( cab->szCab, cab->iCab + 1, opt_cab_file );
- }
- static LONG CDECL fci_status( UINT type, ULONG cb1, ULONG cb2, void *ptr )
- {
- return 0;
- }
- static INT_PTR CDECL fci_get_open_info( char *name, USHORT *date, USHORT *time,
- USHORT *attribs, int *err, void *ptr )
- {
- HANDLE handle;
- BY_HANDLE_FILE_INFORMATION info;
- WCHAR *p, *nameW = strdupAtoW( CP_UTF8, name );
- handle = CreateFileW( nameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
- NULL, OPEN_EXISTING, 0, NULL );
- if (handle == INVALID_HANDLE_VALUE)
- {
- *err = GetLastError();
- WINE_ERR( "failed to open %s: error %u\n", wine_dbgstr_w(nameW), *err );
- cab_free( nameW );
- return -1;
- }
- if (!GetFileInformationByHandle( handle, &info ))
- {
- *err = GetLastError();
- CloseHandle( handle );
- cab_free( nameW );
- return -1;
- }
- FileTimeToDosDateTime( &info.ftLastWriteTime, date, time );
- *attribs = info.dwFileAttributes & (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_ARCH);
- for (p = nameW; *p; p++) if (*p >= 0x80) break;
- if (*p) *attribs |= _A_NAME_IS_UTF;
- cab_free( nameW );
- return (INT_PTR)handle;
- }
- static INT_PTR CDECL fdi_open( char *file, int oflag, int pmode )
- {
- int err;
- return fci_open( file, oflag, pmode, &err, NULL );
- }
- static UINT CDECL fdi_read( INT_PTR hf, void *pv, UINT cb )
- {
- int err;
- return fci_read( hf, pv, cb, &err, NULL );
- }
- static UINT CDECL fdi_write( INT_PTR hf, void *pv, UINT cb )
- {
- int err;
- return fci_write( hf, pv, cb, &err, NULL );
- }
- static int CDECL fdi_close( INT_PTR hf )
- {
- int err;
- return fci_close( hf, &err, NULL );
- }
- static LONG CDECL fdi_lseek( INT_PTR hf, LONG dist, int whence )
- {
- int err;
- return fci_lseek( hf, dist, whence, &err, NULL );
- }
- /* create directories leading to a given file */
- static void create_directories( const WCHAR *name )
- {
- WCHAR *path, *p;
- /* create the directory/directories */
- path = cab_alloc( (lstrlenW(name) + 1) * sizeof(WCHAR) );
- lstrcpyW(path, name);
- p = wcschr(path, '\\');
- while (p != NULL)
- {
- *p = 0;
- if (!CreateDirectoryW( path, NULL ))
- WINE_TRACE("Couldn't create directory %s - error: %ld\n", wine_dbgstr_w(path), GetLastError());
- *p = '\\';
- p = wcschr(p+1, '\\');
- }
- cab_free( path );
- }
- /* check if file name matches against one of the files specification */
- static BOOL match_files( const WCHAR *name )
- {
- int i;
- if (!*opt_files) return TRUE;
- for (i = 0; opt_files[i]; i++)
- {
- unsigned int len = lstrlenW( opt_files[i] );
- /* FIXME: do smarter matching, and wildcards */
- if (!len) continue;
- if (wcsnicmp( name, opt_files[i], len )) continue;
- if (opt_files[i][len - 1] == '\\' || !name[len] || name[len] == '\\') return TRUE;
- }
- return FALSE;
- }
- static INT_PTR CDECL list_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
- {
- WCHAR *nameW;
- switch (fdint)
- {
- case fdintCABINET_INFO:
- return 0;
- case fdintCOPY_FILE:
- nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
- if (match_files( nameW ))
- {
- if (opt_verbose)
- {
- WCHAR attrs[] = L"rxash";
- if (!(pfdin->attribs & _A_RDONLY)) attrs[0] = '-';
- if (!(pfdin->attribs & _A_EXEC)) attrs[1] = '-';
- if (!(pfdin->attribs & _A_ARCH)) attrs[2] = '-';
- if (!(pfdin->attribs & _A_SYSTEM)) attrs[3] = '-';
- if (!(pfdin->attribs & _A_HIDDEN)) attrs[4] = '-';
- wprintf( L" %s %9u %04u/%02u/%02u %02u:%02u:%02u ", attrs, pfdin->cb,
- (pfdin->date >> 9) + 1980, (pfdin->date >> 5) & 0x0f, pfdin->date & 0x1f,
- pfdin->time >> 11, (pfdin->time >> 5) & 0x3f, (pfdin->time & 0x1f) * 2 );
- }
- wprintf( L"%s\n", nameW );
- }
- cab_free( nameW );
- return 0;
- default:
- WINE_FIXME( "Unexpected notification type %d.\n", fdint );
- return 0;
- }
- }
- static int list_cabinet( char *cab_dir )
- {
- ERF erf;
- int ret = 0;
- HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
- fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
- if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, list_notify, NULL, NULL )) ret = GetLastError();
- FDIDestroy( fdi );
- return ret;
- }
- static INT_PTR CDECL extract_notify( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
- {
- WCHAR *file, *nameW, *path = NULL;
- INT_PTR ret;
- switch (fdint)
- {
- case fdintCABINET_INFO:
- return 0;
- case fdintCOPY_FILE:
- nameW = strdupAtoW( (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP, pfdin->psz1 );
- if (opt_preserve_paths)
- {
- file = nameW;
- while (*file == '\\') file++; /* remove leading backslashes */
- }
- else
- {
- if ((file = wcsrchr( nameW, '\\' ))) file++;
- else file = nameW;
- }
- if (opt_dest_dir)
- {
- path = cab_alloc( (lstrlenW(opt_dest_dir) + lstrlenW(file) + 1) * sizeof(WCHAR) );
- lstrcpyW( path, opt_dest_dir );
- lstrcatW( path, file );
- }
- else path = file;
- if (match_files( file ))
- {
- if (opt_verbose) wprintf( L"extracting %s\n", path );
- create_directories( path );
- /* FIXME: check for existing file and overwrite mode */
- ret = (INT_PTR)CreateFileW( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
- NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
- }
- else ret = 0;
- cab_free( nameW );
- if (path != file) cab_free( path );
- return ret;
- case fdintCLOSE_FILE_INFO:
- CloseHandle( (HANDLE)pfdin->hf );
- return 0;
- case fdintNEXT_CABINET:
- WINE_TRACE("Next cab: status %u, path '%s', file '%s'\n", pfdin->fdie, pfdin->psz3, pfdin->psz1);
- return pfdin->fdie == FDIERROR_NONE ? 0 : -1;
- case fdintENUMERATE:
- return 0;
- default:
- WINE_FIXME( "Unexpected notification type %d.\n", fdint );
- return 0;
- }
- }
- static int extract_cabinet( char *cab_dir )
- {
- ERF erf;
- int ret = 0;
- HFDI fdi = FDICreate( cab_alloc, cab_free, fdi_open, fdi_read,
- fdi_write, fdi_close, fdi_lseek, cpuUNKNOWN, &erf );
- if (!FDICopy( fdi, opt_cab_file, cab_dir, 0, extract_notify, NULL, NULL ))
- {
- ret = GetLastError();
- WINE_WARN("FDICopy() failed: code %u\n", ret);
- }
- FDIDestroy( fdi );
- return ret;
- }
- static BOOL add_file( HFCI fci, WCHAR *name )
- {
- BOOL ret;
- char *filename, *path = strdupWtoA( CP_UTF8, name );
- if (!opt_preserve_paths)
- {
- if ((filename = strrchr( path, '\\' ))) filename++;
- else filename = path;
- }
- else
- {
- filename = path;
- while (*filename == '\\') filename++; /* remove leading backslashes */
- }
- ret = FCIAddFile( fci, path, filename, FALSE,
- fci_get_next_cab, fci_status, fci_get_open_info, opt_compression );
- cab_free( path );
- return ret;
- }
- static BOOL add_directory( HFCI fci, WCHAR *dir )
- {
- WCHAR *p, *buffer;
- HANDLE handle;
- WIN32_FIND_DATAW data;
- BOOL ret = TRUE;
- if (!(buffer = cab_alloc( (lstrlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE;
- lstrcpyW( buffer, dir );
- p = buffer + lstrlenW( buffer );
- if (p > buffer && p[-1] != '\\') *p++ = '\\';
- lstrcpyW( p, L"*" );
- if ((handle = FindFirstFileW( buffer, &data )) != INVALID_HANDLE_VALUE)
- {
- do
- {
- if (data.cFileName[0] == '.' && !data.cFileName[1]) continue;
- if (data.cFileName[0] == '.' && data.cFileName[1] == '.' && !data.cFileName[2]) continue;
- if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) continue;
- lstrcpyW( p, data.cFileName );
- if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- ret = add_directory( fci, buffer );
- else
- ret = add_file( fci, buffer );
- if (!ret) break;
- } while (FindNextFileW( handle, &data ));
- FindClose( handle );
- }
- cab_free( buffer );
- return TRUE;
- }
- static BOOL add_file_or_directory( HFCI fci, WCHAR *name )
- {
- DWORD attr = GetFileAttributesW( name );
- if (attr == INVALID_FILE_ATTRIBUTES)
- {
- WINE_MESSAGE( "cannot open %s\n", wine_dbgstr_w(name) );
- return FALSE;
- }
- if (attr & FILE_ATTRIBUTE_DIRECTORY)
- {
- if (opt_recurse) return add_directory( fci, name );
- WINE_MESSAGE( "cabarc: Cannot add %s, it's a directory (use -r for recursive add)\n",
- wine_dbgstr_w(name) );
- return FALSE;
- }
- return add_file( fci, name );
- }
- static int new_cabinet( char *cab_dir )
- {
- WCHAR **file;
- ERF erf;
- BOOL ret = FALSE;
- HFCI fci;
- CCAB cab;
- cab.cb = opt_cabinet_size;
- cab.cbFolderThresh = CB_MAX_DISK;
- cab.cbReserveCFHeader = opt_reserve_space;
- cab.cbReserveCFFolder = 0;
- cab.cbReserveCFData = 0;
- cab.iCab = 0;
- cab.iDisk = 0;
- cab.setID = opt_cabinet_id;
- cab.szDisk[0] = 0;
- strcpy( cab.szCabPath, cab_dir );
- strcat( cab.szCabPath, "\\" );
- format_cab_name( cab.szCab, 1, opt_cab_file );
- fci = FCICreate( &erf, fci_file_placed, cab_alloc, cab_free,fci_open, fci_read,
- fci_write, fci_close, fci_lseek, fci_delete, fci_get_temp, &cab, NULL );
- for (file = opt_files; *file; file++)
- {
- if (!lstrcmpW( *file, L"+" ))
- FCIFlushFolder( fci, fci_get_next_cab, fci_status );
- else
- if (!(ret = add_file_or_directory( fci, *file ))) break;
- }
- if (ret)
- {
- if (!(ret = FCIFlushCabinet( fci, FALSE, fci_get_next_cab, fci_status )))
- WINE_MESSAGE( "cabarc: Failed to create cabinet %s\n", wine_dbgstr_a(opt_cab_file) );
- }
- FCIDestroy( fci );
- return !ret;
- }
- static void usage( void )
- {
- WINE_MESSAGE(
- "Usage: cabarc [options] command file.cab [files...] [dest_dir\\]\n"
- "\nCommands:\n"
- " L List the contents of the cabinet\n"
- " N Create a new cabinet\n"
- " X Extract files from the cabinet into dest_dir\n"
- "\nOptions:\n"
- " -d size Set maximum disk size\n"
- " -h Display this help\n"
- " -i id Set cabinet id\n"
- " -m type Set compression type (mszip|none)\n"
- " -p Preserve directory names\n"
- " -r Recurse into directories\n"
- " -s size Reserve space in the cabinet header\n"
- " -v More verbose output\n" );
- }
- int __cdecl wmain( int argc, WCHAR *argv[] )
- {
- WCHAR *p, *command;
- char buffer[MAX_PATH];
- char filename[MAX_PATH];
- char *cab_file, *file_part;
- int i;
- while (argv[1] && argv[1][0] == '-')
- {
- switch (argv[1][1])
- {
- case 'd':
- argv++; argc--;
- opt_cabinet_size = wcstol( argv[1], NULL, 10 );
- if (opt_cabinet_size < 50000)
- {
- WINE_MESSAGE( "cabarc: Cabinet size must be at least 50000\n" );
- return 1;
- }
- break;
- case 'h':
- usage();
- return 0;
- case 'i':
- argv++; argc--;
- opt_cabinet_id = wcstol( argv[1], NULL, 10 );
- break;
- case 'm':
- argv++; argc--;
- if (!wcscmp( argv[1], L"none" )) opt_compression = tcompTYPE_NONE;
- else if (!wcscmp( argv[1], L"mszip" )) opt_compression = tcompTYPE_MSZIP;
- else
- {
- WINE_MESSAGE( "cabarc: Unknown compression type %s\n", debugstr_w(argv[1]));
- return 1;
- }
- break;
- case 'p':
- opt_preserve_paths = TRUE;
- break;
- case 'r':
- opt_recurse = TRUE;
- break;
- case 's':
- argv++; argc--;
- opt_reserve_space = wcstol( argv[1], NULL, 10 );
- break;
- case 'v':
- opt_verbose++;
- break;
- default:
- usage();
- return 1;
- }
- argv++; argc--;
- }
- command = argv[1];
- if (argc < 3 || !command[0] || command[1])
- {
- usage();
- return 1;
- }
- cab_file = strdupWtoA( CP_ACP, argv[2] );
- argv += 2;
- argc -= 2;
- if (!GetFullPathNameA( cab_file, MAX_PATH, buffer, &file_part ) || !file_part)
- {
- WINE_ERR( "cannot get full name for %s\n", wine_dbgstr_a( cab_file ));
- return 1;
- }
- strcpy(filename, file_part);
- file_part[0] = 0;
- /* map slash to backslash in all file arguments */
- for (i = 1; i < argc; i++)
- for (p = argv[i]; *p; p++)
- if (*p == '/') *p = '\\';
- opt_files = argv + 1;
- opt_cab_file = filename;
- switch (*command)
- {
- case 'l':
- case 'L':
- return list_cabinet( buffer );
- case 'n':
- case 'N':
- return new_cabinet( buffer );
- case 'x':
- case 'X':
- if (argc > 1) /* check for destination dir as last argument */
- {
- WCHAR *last = argv[argc - 1];
- if (last[0] && last[lstrlenW(last) - 1] == '\\')
- {
- opt_dest_dir = last;
- argv[--argc] = NULL;
- }
- }
- WINE_TRACE("Extracting file(s) from cabinet %s\n", wine_dbgstr_a(cab_file));
- return extract_cabinet( buffer );
- default:
- usage();
- return 1;
- }
- }
|