From 30bdbedc2dc2628c1d8b23f2204027c8aea69d87 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Wed, 2 Feb 2011 22:44:47 +0100 Subject: [PATCH] cabarc: Initial version of the cabarc.exe tool, with support for creating a cabinet. --- configure | 1 + configure.ac | 1 + programs/cabarc/Makefile.in | 8 + programs/cabarc/cabarc.c | 484 ++++++++++++++++++++++++++++++++++++ 4 files changed, 494 insertions(+) create mode 100644 programs/cabarc/Makefile.in create mode 100644 programs/cabarc/cabarc.c diff --git a/configure b/configure index 64a7c50efdb..c763328945d 100755 --- a/configure +++ b/configure @@ -15370,6 +15370,7 @@ wine_fn_config_makefile libs/wpp enable_libs_wpp wine_fn_config_makefile loader enable_loader wine_fn_config_program aspnet_regiis enable_aspnet_regiis install wine_fn_config_program attrib enable_attrib install +wine_fn_config_program cabarc enable_cabarc install wine_fn_config_program cacls enable_cacls install wine_fn_config_program clock enable_clock po,install wine_fn_config_program cmd enable_cmd po,install diff --git a/configure.ac b/configure.ac index 6d8b5740f21..fa2a6c009d2 100644 --- a/configure.ac +++ b/configure.ac @@ -2871,6 +2871,7 @@ WINE_CONFIG_MAKEFILE([libs/wpp]) WINE_CONFIG_MAKEFILE([loader]) WINE_CONFIG_PROGRAM(aspnet_regiis,,[install]) WINE_CONFIG_PROGRAM(attrib,,[install]) +WINE_CONFIG_PROGRAM(cabarc,,[install]) WINE_CONFIG_PROGRAM(cacls,,[install]) WINE_CONFIG_PROGRAM(clock,,[po,install]) WINE_CONFIG_PROGRAM(cmd,,[po,install]) diff --git a/programs/cabarc/Makefile.in b/programs/cabarc/Makefile.in new file mode 100644 index 00000000000..47d48235b04 --- /dev/null +++ b/programs/cabarc/Makefile.in @@ -0,0 +1,8 @@ +MODULE = cabarc.exe +APPMODE = -mconsole -municode +IMPORTS = cabinet +EXTRADEFS = -DWINE_NO_UNICODE_MACROS + +C_SRCS = cabarc.c + +@MAKE_PROG_RULES@ diff --git a/programs/cabarc/cabarc.c b/programs/cabarc/cabarc.c new file mode 100644 index 00000000000..5a10d2145fb --- /dev/null +++ b/programs/cabarc/cabarc.c @@ -0,0 +1,484 @@ +/* + * 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 "config.h" +#include "wine/port.h" + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#include "fci.h" +#include "fdi.h" + +#include "wine/unicode.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(cabarc); + +/* from msvcrt */ +#define _O_RDONLY 0 +#define _O_WRONLY 1 +#define _O_RDWR 2 +#define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR) +#define _O_APPEND 0x0008 +#define _O_RANDOM 0x0010 +#define _O_SEQUENTIAL 0x0020 +#define _O_TEMPORARY 0x0040 +#define _O_NOINHERIT 0x0080 +#define _O_CREAT 0x0100 +#define _O_TRUNC 0x0200 +#define _O_EXCL 0x0400 +#define _O_SHORT_LIVED 0x1000 +#define _O_TEXT 0x4000 +#define _O_BINARY 0x8000 + +#define _SH_COMPAT 0x00 +#define _SH_DENYRW 0x10 +#define _SH_DENYWR 0x20 +#define _SH_DENYRD 0x30 +#define _SH_DENYNO 0x40 + +#define _A_RDONLY 0x01 +#define _A_HIDDEN 0x02 +#define _A_SYSTEM 0x04 +#define _A_ARCH 0x20 + +/* command-line options */ +static int opt_cabinet_id; +static int opt_compression = tcompTYPE_MSZIP; +static int opt_recurse; +static int opt_preserve_paths; +static int opt_reserve_space; +static int opt_verbose; +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; +} + +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 ) +{ + WINE_ERR( "shouldn't happen\n" ); + return FALSE; +} + +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 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 ) +{ + static const WCHAR wildcardW[] = {'*',0}; + WCHAR *p, *buffer; + HANDLE handle; + WIN32_FIND_DATAW data; + BOOL ret = TRUE; + + if (!(buffer = cab_alloc( (strlenW(dir) + MAX_PATH + 2) * sizeof(WCHAR) ))) return FALSE; + strcpyW( buffer, dir ); + p = buffer + strlenW( buffer ); + if (p > buffer && p[-1] != '\\') *p++ = '\\'; + strcpyW( p, wildcardW ); + + 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; + + strcpyW( 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, char *cab_file ) +{ + WCHAR **file; + ERF erf; + BOOL ret = FALSE; + HFCI fci; + CCAB cab; + + cab.cb = CB_MAX_DISK; + cab.cbFolderThresh = CB_MAX_DISK; + cab.cbReserveCFHeader = opt_reserve_space; + cab.cbReserveCFFolder = 0; + cab.cbReserveCFData = 0; + cab.iCab = 1; + cab.iDisk = 0; + cab.setID = opt_cabinet_id; + cab.szDisk[0] = 0; + + strcpy( cab.szCabPath, cab_dir ); + strcat( cab.szCabPath, "\\" ); + strcpy( cab.szCab, 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 (!(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(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" + " -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 wmain( int argc, WCHAR *argv[] ) +{ + static const WCHAR noneW[] = {'n','o','n','e',0}; + static const WCHAR mszipW[] = {'m','s','z','i','p',0}; + + WCHAR *p, *command; + char buffer[MAX_PATH]; + char *cab_file, *file_part; + int i; + + while (argv[1] && argv[1][0] == '-') + { + switch (argv[1][1]) + { + case 'h': + usage(); + return 0; + case 'i': + argv++; argc--; + opt_cabinet_id = atoiW( argv[1] ); + break; + case 'm': + argv++; argc--; + if (!strcmpiW( argv[1], noneW )) opt_compression = tcompTYPE_NONE; + else if (!strcmpiW( argv[1], mszipW )) opt_compression = tcompTYPE_MSZIP; + else + { + WINE_MESSAGE( "cabarc: Unknown compression type '%s'\n", optarg ); + return 1; + } + break; + case 'p': + opt_preserve_paths = 1; + break; + case 'r': + opt_recurse = 1; + break; + case 's': + argv++; argc--; + opt_reserve_space = atoiW( argv[1] ); + 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; + } + file_part[-1] = 0; + cab_free( cab_file ); + + /* 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; + + switch (*command) + { + case 'l': + case 'L': + WINE_FIXME( "list not implemented\n" ); + return 1; + case 'n': + case 'N': + return new_cabinet( buffer, file_part ); + case 'x': + case 'X': + WINE_FIXME( "extraction not implemented\n" ); + return 1; + default: + usage(); + return 1; + } +}