programs: Add winemsibuilder.
[wine] / programs / winemsibuilder / main.c
1 /*
2  * winemsibuilder - tool to build MSI packages
3  *
4  * Copyright 2010 Hans Leidekker for CodeWeavers
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #define WIN32_LEAN_AND_MEAN
22 #define COBJMACROS
23
24 #include <stdio.h>
25 #include <windows.h>
26 #include <msi.h>
27 #include <msiquery.h>
28 #include <objbase.h>
29
30 #include "wine/debug.h"
31 #include "wine/unicode.h"
32
33 WINE_DEFAULT_DEBUG_CHANNEL(winemsibuilder);
34
35 static UINT open_database( const WCHAR *msifile, MSIHANDLE *handle )
36 {
37     UINT r;
38     MSIHANDLE hdb;
39
40     if (GetFileAttributesW( msifile ) == INVALID_FILE_ATTRIBUTES)
41     {
42         r = MsiOpenDatabaseW( msifile, MSIDBOPEN_CREATE, &hdb );
43         if (r != ERROR_SUCCESS)
44         {
45             WINE_ERR( "failed to create package database %s (%u)\n", wine_dbgstr_w(msifile), r );
46             return r;
47         }
48         r = MsiDatabaseCommit( hdb );
49         if (r != ERROR_SUCCESS)
50         {
51             WINE_ERR( "failed to commit database (%u)\n", r );
52             MsiCloseHandle( hdb );
53             return r;
54         }
55     }
56     else
57     {
58         r = MsiOpenDatabaseW( msifile, MSIDBOPEN_TRANSACT, &hdb );
59         if (r != ERROR_SUCCESS)
60         {
61             WINE_ERR( "failed to open package database %s (%u)\n", wine_dbgstr_w(msifile), r );
62             return r;
63         }
64     }
65
66     *handle = hdb;
67     return ERROR_SUCCESS;
68 }
69
70 static int import_tables( const WCHAR *msifile, WCHAR **tables )
71 {
72     UINT r;
73     MSIHANDLE hdb;
74     WCHAR *dir;
75     DWORD len;
76
77     r = open_database( msifile, &hdb );
78     if (r != ERROR_SUCCESS) return 1;
79
80     len = GetCurrentDirectoryW( 0, NULL );
81     if (!(dir = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) )))
82     {
83         MsiCloseHandle( hdb );
84         return 1;
85     }
86     GetCurrentDirectoryW( len + 1, dir );
87
88     while (*tables)
89     {
90         r = MsiDatabaseImportW( hdb, dir, *tables );
91         if (r != ERROR_SUCCESS)
92         {
93             WINE_ERR( "failed to import table %s (%u)\n", wine_dbgstr_w(*tables), r );
94             break;
95         }
96         tables++;
97     }
98
99     if (r == ERROR_SUCCESS)
100     {
101         r = MsiDatabaseCommit( hdb );
102         if (r != ERROR_SUCCESS)
103             WINE_ERR( "failed to commit changes (%u)\n", r );
104     }
105
106     HeapFree( GetProcessHeap(), 0, dir );
107     MsiCloseHandle( hdb );
108     return (r != ERROR_SUCCESS);
109 }
110
111 /* taken from dlls/msi/table.c */
112 static int utf2mime( int x )
113 {
114     if (x >= '0' && x <= '9')
115         return x - '0';
116     if (x >= 'A' && x <= 'Z')
117         return x - 'A' + 10;
118     if (x >= 'a' && x <= 'z')
119         return x - 'a' + 10 + 26;
120     if (x == '.')
121         return 10 + 26 + 26;
122     if (x == '_')
123         return 10 + 26 + 26 + 1;
124     return -1;
125 }
126
127 #define MAX_STREAM_NAME 0x1f
128
129 static WCHAR *encode_stream( const WCHAR *in )
130 {
131     DWORD c, next, count;
132     WCHAR *out, *p;
133
134     count = strlenW( in );
135     if (count > MAX_STREAM_NAME)
136         return NULL;
137
138     count += 2;
139     if (!(out = HeapAlloc( GetProcessHeap(), 0, count * sizeof(WCHAR) ))) return NULL;
140     p = out;
141     while (count--)
142     {
143         c = *in++;
144         if (!c)
145         {
146             *p = c;
147             return out;
148         }
149         if (c < 0x80 && utf2mime( c ) >= 0)
150         {
151             c = utf2mime( c ) + 0x4800;
152             next = *in;
153             if (next && next < 0x80)
154             {
155                 next = utf2mime( next );
156                 if (next != -1)
157                 {
158                      next += 0x3ffffc0;
159                      c += next << 6;
160                      in++;
161                 }
162             }
163         }
164         *p++ = c;
165     }
166     HeapFree( GetProcessHeap(), 0, out );
167     return NULL;
168 }
169
170 static int add_stream( const WCHAR *msifile, const WCHAR *stream, const WCHAR *file )
171 {
172     UINT r;
173     HRESULT hr;
174     MSIHANDLE hdb;
175     IStorage *stg;
176     IStream *stm = NULL;
177     HANDLE handle;
178     char buffer[4096];
179     ULARGE_INTEGER size;
180     DWORD low, high, read;
181     WCHAR *encname;
182     int ret = 1;
183
184     /* make sure we have the right type of file  */
185     r = open_database( msifile, &hdb );
186     if (r != ERROR_SUCCESS) return 1;
187     MsiCloseHandle( hdb );
188
189     hr = StgOpenStorage( msifile, NULL, STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg );
190     if (hr != S_OK)
191     {
192         WINE_WARN( "failed to open storage %s (0x%08x)\n", wine_dbgstr_w(msifile), hr );
193         return 1;
194     }
195     encname = encode_stream( stream );
196     if (!encname)
197     {
198         WINE_WARN( "failed to encode stream name %s\n", wine_dbgstr_w(stream) );
199         goto done;
200     }
201     hr = IStorage_CreateStream( stg, encname, STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, 0, 0, &stm );
202     if (hr != S_OK)
203     {
204         WINE_WARN( "failed to create stream %s (0x%08x)\n", wine_dbgstr_w(encname), hr );
205         goto done;
206     }
207     handle = CreateFileW( file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
208     if (handle == INVALID_HANDLE_VALUE)
209     {
210         WINE_WARN( "failed to open file %s (%u)\n", wine_dbgstr_w(file), GetLastError() );
211         goto done;
212     }
213     low = GetFileSize( handle, &high );
214     if (low == INVALID_FILE_SIZE || high)
215     {
216         WINE_WARN( "file %s too big\n", wine_dbgstr_w(file) );
217         CloseHandle( handle );
218         goto done;
219     }
220     size.QuadPart = low;
221     hr = IStream_SetSize( stm, size );
222     if (hr != S_OK) goto done;
223
224     while (ReadFile( handle, buffer, sizeof(buffer), &read, NULL ) && read)
225     {
226         hr = IStream_Write( stm, buffer, read, NULL );
227         if (hr != S_OK) break;
228         size.QuadPart -= read;
229     }
230     CloseHandle( handle );
231     if (size.QuadPart)
232     {
233         WINE_WARN( "failed to write stream contents\n" );
234         goto done;
235     }
236     IStorage_Commit( stg, 0 );
237     ret = 0;
238
239 done:
240     HeapFree( GetProcessHeap(), 0, encname );
241     if (stm) IStream_Release( stm );
242     IStorage_Release( stg );
243     return ret;
244 }
245
246 static void show_usage( void )
247 {
248     WINE_MESSAGE(
249         "Usage: winemsibuilder [OPTION] [MSIFILE] ...\n"
250         "Options:\n"
251         "  -i package.msi table1.idt [table2.idt ...]    Import one or more tables into the database.\n"
252         "  -a package.msi stream file                    Add 'stream' to storage with contents of 'file'.\n"
253         "\nExisting tables or streams will be overwritten. If package.msi does not exist a new file\n"
254         "will be created with an empty database.\n"
255     );
256 }
257
258 int wmain( int argc, WCHAR *argv[] )
259 {
260     if (argc < 3 || argv[1][0] != '-')
261     {
262         show_usage();
263         return 1;
264     }
265
266     switch (argv[1][1])
267     {
268     case 'i':
269         if (argc < 4) break;
270         return import_tables( argv[2], argv + 3 );
271     case 'a':
272         if (argc < 5) break;
273         return add_stream( argv[2], argv[3], argv[4] );
274     default:
275         WINE_WARN( "unknown option\n" );
276         break;
277     }
278
279     show_usage();
280     return 1;
281 }