diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2012-12-10 17:30:32 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2012-12-10 17:30:32 +0100 |
commit | 9a1151d6474ac7daebc263edc2f37caf3b4c9955 (patch) | |
tree | d11965d58c2eb20c0470fc5f9c25de42ab545375 /libmsi/libmsi-database.c | |
parent | 2ea5d49da27c3569251896884786649a72bd6df3 (diff) | |
parent | 6b7407cb031cf245a4998d8c79009039ead38fc5 (diff) | |
download | msitools-9a1151d6474ac7daebc263edc2f37caf3b4c9955.tar.gz msitools-9a1151d6474ac7daebc263edc2f37caf3b4c9955.tar.xz msitools-9a1151d6474ac7daebc263edc2f37caf3b4c9955.zip |
Merge branch 'pre-gsf'
Conflicts:
libmsi/Makefile.am
libmsi/libmsi-summary-info.c
libmsi/msipriv.h
Diffstat (limited to 'libmsi/libmsi-database.c')
-rw-r--r-- | libmsi/libmsi-database.c | 2434 |
1 files changed, 2434 insertions, 0 deletions
diff --git a/libmsi/libmsi-database.c b/libmsi/libmsi-database.c new file mode 100644 index 0000000..e4a99a9 --- /dev/null +++ b/libmsi/libmsi-database.c @@ -0,0 +1,2434 @@ +/* + * Implementation of the Microsoft Installer (msi.dll) + * + * Copyright 2002,2003,2004,2005 Mike McCormack for CodeWeavers + * + * 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 <stdarg.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include "debug.h" +#include "libmsi.h" +#include "msipriv.h" +#include "query.h" + +const uint8_t clsid_msi_transform[16] = { 0x82, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46 }; +const uint8_t clsid_msi_database[16] = { 0x84, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46 }; +const uint8_t clsid_msi_patch[16] = { 0x86, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46 }; + +/* + * .MSI file format + * + * An .msi file is a structured storage file. + * It contains a number of streams. + * A stream for each table in the database. + * Two streams for the string table in the database. + * Any binary data in a table is a reference to a stream. + */ + +#define IS_INTMSIDBOPEN(x) \ + ((x) >= LIBMSI_DB_OPEN_READONLY && (x) <= LIBMSI_DB_OPEN_CREATE) + +typedef struct _LibmsiTransform { + struct list entry; + GsfInfile *stg; +} LibmsiTransform; + +typedef struct _LibmsiStorage { + struct list entry; + char *name; + GsfInfile *stg; +} LibmsiStorage; + +typedef struct _LibmsiStream { + struct list entry; + char *name; + GsfInput *stm; +} LibmsiStream; + +unsigned msi_open_storage( LibmsiDatabase *db, const char *stname ) +{ + unsigned r = LIBMSI_RESULT_NOT_ENOUGH_MEMORY; + LibmsiStorage *storage; + GsfInput *in; + + LIST_FOR_EACH_ENTRY( storage, &db->storages, LibmsiStorage, entry ) + { + if( !strcmp( stname, storage->name ) ) + { + TRACE("found %s\n", debugstr_a(stname)); + return; + } + } + + if (!(storage = msi_alloc_zero( sizeof(LibmsiStorage) ))) return LIBMSI_RESULT_NOT_ENOUGH_MEMORY; + storage->name = strdup( stname ); + if (!storage->name) + goto done; + + in = gsf_infile_child_by_name(db->infile, stname); + if (!GSF_IS_INFILE(in)) + goto done; + + storage->stg = GSF_INFILE(in); + if (!storage->stg) + goto done; + + list_add_tail( &db->storages, &storage->entry ); + r = LIBMSI_RESULT_SUCCESS; + +done: + if (r != LIBMSI_RESULT_SUCCESS) { + msi_free(storage->name); + msi_free(storage); + } + + return r; +} + +unsigned msi_create_storage( LibmsiDatabase *db, const char *stname, GsfInput *stm ) +{ + LibmsiStorage *storage; + GsfInfile *origstg = NULL; + bool found = false; + unsigned r; + + if ( db->mode == LIBMSI_DB_OPEN_READONLY ) + return LIBMSI_RESULT_ACCESS_DENIED; + + LIST_FOR_EACH_ENTRY( storage, &db->storages, LibmsiStorage, entry ) + { + if( !strcmp( stname, storage->name ) ) + { + TRACE("found %s\n", debugstr_a(stname)); + found = true; + break; + } + } + + if (!found) { + if (!(storage = msi_alloc_zero( sizeof(LibmsiStorage) ))) return LIBMSI_RESULT_NOT_ENOUGH_MEMORY; + storage->name = strdup( stname ); + if (!storage->name) + { + msi_free(storage); + return LIBMSI_RESULT_NOT_ENOUGH_MEMORY; + } + } + + origstg = gsf_infile_msole_new(stm, NULL); + if (origstg == NULL) + goto done; + + if (found) { + if (storage->stg) + g_object_unref(G_OBJECT(storage->stg)); + } else { + list_add_tail( &db->storages, &storage->entry ); + } + + storage->stg = origstg; + g_object_ref(G_OBJECT(storage->stg)); + + r = LIBMSI_RESULT_SUCCESS; + +done: + if (r != LIBMSI_RESULT_SUCCESS) { + if (!found) { + msi_free(storage->name); + msi_free(storage); + } + } + + if (origstg) + g_object_unref(G_OBJECT(origstg)); + + return r; +} + +void msi_destroy_storage( LibmsiDatabase *db, const char *stname ) +{ + LibmsiStorage *storage, *storage2; + + LIST_FOR_EACH_ENTRY_SAFE( storage, storage2, &db->storages, LibmsiStorage, entry ) + { + if (!strcmp( stname, storage->name )) + { + TRACE("destroying %s\n", debugstr_a(stname)); + + list_remove( &storage->entry ); + g_object_unref(G_OBJECT(storage->stg)); + msi_free( storage ); + break; + } + } +} + +static unsigned find_infile_stream( LibmsiDatabase *db, const char *name, GsfInput **stm ) +{ + LibmsiStream *stream; + + LIST_FOR_EACH_ENTRY( stream, &db->streams, LibmsiStream, entry ) + { + if( !strcmp( name, stream->name ) ) + { + TRACE("found %s\n", debugstr_a(name)); + *stm = stream->stm; + return LIBMSI_RESULT_SUCCESS; + } + } + + return LIBMSI_RESULT_FUNCTION_FAILED; +} + +static unsigned msi_alloc_stream( LibmsiDatabase *db, const char *stname, GsfInput *stm) +{ + LibmsiStream *stream; + + TRACE("%p %s %p", db, debugstr_a(stname), stm); + if (!(stream = msi_alloc( sizeof(LibmsiStream) ))) return LIBMSI_RESULT_NOT_ENOUGH_MEMORY; + stream->name = strdup( stname ); + stream->stm = stm; + g_object_ref(G_OBJECT(stm)); + list_add_tail( &db->streams, &stream->entry ); + return LIBMSI_RESULT_SUCCESS; +} + +unsigned write_raw_stream_data( LibmsiDatabase *db, const char *stname, + const void *data, unsigned sz, GsfInput **outstm ) +{ + unsigned ret = LIBMSI_RESULT_FUNCTION_FAILED; + GsfInput *stm = NULL; + char *mem; + LibmsiStream *stream; + + if (db->mode == LIBMSI_DB_OPEN_READONLY) + return LIBMSI_RESULT_FUNCTION_FAILED; + + LIST_FOR_EACH_ENTRY( stream, &db->streams, LibmsiStream, entry ) + { + if( !strcmp( stname, stream->name ) ) + { + msi_destroy_stream( db, stname ); + break; + } + } + + mem = g_try_malloc(sz == 0 ? 1 : sz); + if (!mem) + return LIBMSI_RESULT_FUNCTION_FAILED; + + if (data || sz) + memcpy(mem, data, sz); + + stm = gsf_input_memory_new(mem, sz, true); + ret = msi_alloc_stream( db, stname, stm); + *outstm = stm; + return ret; +} + +unsigned msi_create_stream( LibmsiDatabase *db, const char *stname, GsfInput *stm ) +{ + LibmsiStream *stream; + char *encname = NULL; + unsigned r = LIBMSI_RESULT_FUNCTION_FAILED; + bool found = false; + + if ( db->mode == LIBMSI_DB_OPEN_READONLY ) + return LIBMSI_RESULT_ACCESS_DENIED; + + encname = encode_streamname(false, stname); + + LIST_FOR_EACH_ENTRY( stream, &db->streams, LibmsiStream, entry ) + { + if( !strcmp( encname, stream->name ) ) + { + found = true; + break; + } + } + + if (found) { + if (stream->stm) + g_object_unref(G_OBJECT(stream->stm)); + stream->stm = stm; + g_object_ref(G_OBJECT(stream->stm)); + r = LIBMSI_RESULT_SUCCESS; + } else + r = msi_alloc_stream( db, encname, stm ); + + msi_free(encname); + return r; +} + +static void cache_infile_structure( LibmsiDatabase *db ) +{ + int i, n; + char decname[0x40]; + unsigned r; + + n = gsf_infile_num_children(db->infile); + + /* TODO: error handling */ + + for (i = 0; i < n; i++) + { + GsfInput *in = gsf_infile_child_by_index(db->infile, i); + const uint8_t *name = (const uint8_t *) gsf_input_name(in); + + /* table streams are not in the _Streams table */ + if (!GSF_IS_INFILE(in) || gsf_infile_num_children(GSF_INFILE(in)) == -1) { + /* UTF-8 encoding of 0x4840. */ + if (name[0] == 0xe4 && name[1] == 0xa1 && name[2] == 0x80) + { + decode_streamname( name + 3, decname ); + if ( !strcmp( decname, szStringPool ) || + !strcmp( decname, szStringData ) ) + continue; + + r = _libmsi_open_table( db, decname, false ); + } + else + { + r = msi_alloc_stream(db, name, GSF_INPUT(in)); + g_object_unref(G_OBJECT(in)); + } + } else { + msi_open_storage(db, name); + } + + } +} + +unsigned msi_enum_db_streams(LibmsiDatabase *db, + unsigned (*fn)(const char *, GsfInput *, void *), + void *opaque) +{ + unsigned r; + LibmsiStream *stream, *stream2; + + LIST_FOR_EACH_ENTRY_SAFE( stream, stream2, &db->streams, LibmsiStream, entry ) + { + GsfInput *stm; + + stm = stream->stm; + g_object_ref(G_OBJECT(stm)); + r = fn( stream->name, stm, opaque); + g_object_unref(G_OBJECT(stm)); + + if (r) { + return r; + } + } + + return LIBMSI_RESULT_SUCCESS; +} + +unsigned msi_enum_db_storages(LibmsiDatabase *db, + unsigned (*fn)(const char *, GsfInfile *, void *), + void *opaque) +{ + unsigned r; + LibmsiStorage *storage, *storage2; + + LIST_FOR_EACH_ENTRY_SAFE( storage, storage2, &db->storages, LibmsiStorage, entry ) + { + GsfInfile *stg; + + stg = storage->stg; + g_object_ref(G_OBJECT(stg)); + r = fn( storage->name, stg, opaque); + g_object_unref(G_OBJECT(stg)); + + if (r) { + return r; + } + } + + return LIBMSI_RESULT_SUCCESS; +} + +unsigned clone_infile_stream( LibmsiDatabase *db, const char *name, GsfInput **stm ) +{ + GsfInput *stream; + + if (find_infile_stream( db, name, &stream ) == LIBMSI_RESULT_SUCCESS) + { + stream = gsf_input_dup( stream, NULL ); + if( !stream ) + { + WARN("failed to clone stream\n"); + return LIBMSI_RESULT_FUNCTION_FAILED; + } + + gsf_input_seek( stream, 0, G_SEEK_SET ); + *stm = stream; + return LIBMSI_RESULT_SUCCESS; + } + + return LIBMSI_RESULT_FUNCTION_FAILED; +} + +unsigned msi_get_raw_stream( LibmsiDatabase *db, const char *stname, GsfInput **stm ) +{ + unsigned ret = LIBMSI_RESULT_FUNCTION_FAILED; + char decoded[MAX_STREAM_NAME_LEN]; + LibmsiTransform *transform; + + decode_streamname( stname, decoded ); + TRACE("%s -> %s\n", debugstr_a(stname), debugstr_a(decoded)); + + if (clone_infile_stream( db, stname, stm ) == LIBMSI_RESULT_SUCCESS) + return LIBMSI_RESULT_SUCCESS; + + LIST_FOR_EACH_ENTRY( transform, &db->transforms, LibmsiTransform, entry ) + { + *stm = gsf_infile_child_by_name( transform->stg, stname ); + if (*stm) + { + ret = LIBMSI_RESULT_SUCCESS; + break; + } + } + + return ret; +} + +static void free_transforms( LibmsiDatabase *db ) +{ + while( !list_empty( &db->transforms ) ) + { + LibmsiTransform *t = LIST_ENTRY( list_head( &db->transforms ), + LibmsiTransform, entry ); + list_remove( &t->entry ); + g_object_unref(G_OBJECT(t->stg)); + msi_free( t ); + } +} + +void msi_destroy_stream( LibmsiDatabase *db, const char *stname ) +{ + LibmsiStream *stream, *stream2; + + LIST_FOR_EACH_ENTRY_SAFE( stream, stream2, &db->streams, LibmsiStream, entry ) + { + if (!strcmp( stname, stream->name )) + { + TRACE("destroying %s\n", debugstr_a(stname)); + + list_remove( &stream->entry ); + g_object_unref(G_OBJECT(stream->stm)); + msi_free( stream ); + break; + } + } +} + +static void free_storages( LibmsiDatabase *db ) +{ + while( !list_empty( &db->storages ) ) + { + LibmsiStorage *s = LIST_ENTRY(list_head( &db->storages ), LibmsiStorage, entry); + list_remove( &s->entry ); + g_object_unref(G_OBJECT(s->stg)); + msi_free( s->name ); + msi_free( s ); + } +} + +static void free_streams( LibmsiDatabase *db ) +{ + while( !list_empty( &db->streams ) ) + { + LibmsiStream *s = LIST_ENTRY(list_head( &db->streams ), LibmsiStream, entry); + list_remove( &s->entry ); + g_object_unref(G_OBJECT(s->stm)); + msi_free( s->name ); + msi_free( s ); + } +} + +void append_storage_to_db( LibmsiDatabase *db, GsfInfile *stg ) +{ + LibmsiTransform *t; + + t = msi_alloc( sizeof *t ); + t->stg = stg; + g_object_ref(G_OBJECT(t->stg)); + list_add_head( &db->transforms, &t->entry ); + +#if 0 + /* the transform may add or replace streams... + * + * FIXME: Hmm, the MSI is always searched before the transform though. + * For now disable this. */ + free_streams( db ); +#endif +} + +static void _libmsi_database_destroy( LibmsiObject *arg ) +{ + LibmsiDatabase *db = (LibmsiDatabase *) arg; + + _libmsi_database_close( db, false ); + free_cached_tables( db ); + free_transforms( db ); + msi_free(db->path); +} + +LibmsiResult _libmsi_database_close(LibmsiDatabase *db, bool committed) +{ + TRACE("%p %d\n", db, committed); + + if ( db->strings ) + { + msi_destroy_stringtable( db->strings); + db->strings = NULL; + } + + if ( db->infile ) + { + g_object_unref(G_OBJECT(db->infile)); + db->infile = NULL; + } + + if ( db->outfile ) + { + gsf_output_close(GSF_OUTPUT(db->outfile)); + g_object_unref(G_OBJECT(db->outfile)); + db->outfile = NULL; + } + free_streams( db ); + free_storages( db ); + + if (db->outpath) { + if (!committed) { + unlink( db->outpath ); + msi_free( db->outpath ); + } else if (db->rename_outpath) { + unlink(db->path); + rename(db->outpath, db->path); + msi_free( db->outpath ); + } else { + msi_free( db->path ); + db->path = db->outpath; + } + } + db->outpath = NULL; +} + +LibmsiResult _libmsi_database_open(LibmsiDatabase *db) +{ + GsfInput *in; + GsfInfile *stg; + uint8_t uuid[16]; + unsigned ret = LIBMSI_RESULT_OPEN_FAILED; + + TRACE("%p %s\n", db, db->path); + + in = gsf_input_stdio_new(db->path, NULL); + if (!in) + { + WARN("open file failed for %s\n", debugstr_a(db->path)); + return LIBMSI_RESULT_OPEN_FAILED; + } + stg = gsf_infile_msole_new( in, NULL ); + g_object_unref(G_OBJECT(in)); + if( !stg ) + { + WARN("open failed for %s\n", debugstr_a(db->path)); + return LIBMSI_RESULT_OPEN_FAILED; + } + + if( !gsf_infile_msole_get_class_id (GSF_INFILE_MSOLE(stg), uuid)) + { + FIXME("Failed to stat storage\n"); + goto end; + } + + if ( memcmp( uuid, clsid_msi_database, 16 ) != 0 && + memcmp( uuid, clsid_msi_patch, 16 ) != 0 && + memcmp( uuid, clsid_msi_transform, 16 ) != 0 ) + { + ERR("storage GUID is not a MSI database GUID %s\n", + debugstr_guid(uuid) ); + goto end; + } + + if ( db->patch && memcmp( uuid, clsid_msi_patch, 16 ) != 0 ) + { + ERR("storage GUID is not the MSI patch GUID %s\n", + debugstr_guid(uuid) ); + goto end; + } + + db->infile = stg; + g_object_ref(G_OBJECT(db->infile)); + + cache_infile_structure( db ); + + db->strings = msi_load_string_table( db->infile, &db->bytes_per_strref ); + if( !db->strings ) + goto end; + + ret = LIBMSI_RESULT_SUCCESS; +end: + if (ret) { + if (db->infile) + g_object_unref(G_OBJECT(db->infile)); + db->infile = NULL; + } + g_object_unref(G_OBJECT(stg)); + return ret; +} + +LibmsiResult _libmsi_database_start_transaction(LibmsiDatabase *db, const char *szPersist) +{ + unsigned ret = LIBMSI_RESULT_SUCCESS; + GsfOutput *out; + GsfOutfile *stg = NULL; + char *tmpfile = NULL; + char path[PATH_MAX]; + + if( db->mode == LIBMSI_DB_OPEN_READONLY ) + return LIBMSI_RESULT_SUCCESS; + + if( szPersist == LIBMSI_DB_OPEN_TRANSACT ) + { + strcpy( path, db->path ); + strcat( path, ".tmp" ); + tmpfile = strdup(path); + szPersist = tmpfile; + } + else if( IS_INTMSIDBOPEN(szPersist) ) + { + ERR("unknown flag %p\n",szPersist); + return LIBMSI_RESULT_INVALID_PARAMETER; + } + + TRACE("%p %s\n", db, szPersist); + + out = gsf_output_stdio_new(szPersist, NULL); + if (!out) + { + WARN("open file failed for %s\n", debugstr_a(szPersist)); + return LIBMSI_RESULT_OPEN_FAILED; + } + stg = gsf_outfile_msole_new(out); + g_object_unref(G_OBJECT(out)); + if (!stg) + { + WARN("open failed for %s\n", debugstr_a(szPersist)); + return LIBMSI_RESULT_OPEN_FAILED; + } + + if (!gsf_outfile_msole_set_class_id(GSF_OUTFILE_MSOLE(stg), + db->patch ? clsid_msi_patch : clsid_msi_database )) + { + WARN("set guid failed\n"); + ret = LIBMSI_RESULT_FUNCTION_FAILED; + goto end; + } + + db->outfile = stg; + g_object_ref(G_OBJECT(db->outfile)); + + if (!strstr( szPersist, G_DIR_SEPARATOR_S )) + { + getcwd( path, MAX_PATH ); + strcat( path, G_DIR_SEPARATOR_S ); + strcat( path, szPersist ); + } + else + strcpy( path, szPersist ); + + db->outpath = strdup( path ); + db->rename_outpath = (tmpfile != NULL); + +end: + if (ret) { + if (db->outfile) + g_object_unref(G_OBJECT(db->outfile)); + db->outfile = NULL; + } + if (stg) + g_object_unref(G_OBJECT(stg)); + msi_free(tmpfile); + return ret; +} + +LibmsiResult libmsi_database_open(const char *szDBPath, const char *szPersist, LibmsiDatabase **pdb) +{ + LibmsiDatabase *db = NULL; + unsigned ret = LIBMSI_RESULT_SUCCESS; + const char *szMode; + bool patch = false; + char path[MAX_PATH]; + + TRACE("%s %p\n",debugstr_a(szDBPath),szPersist ); + + if( !pdb ) + return LIBMSI_RESULT_INVALID_PARAMETER; + + if (IS_INTMSIDBOPEN(szPersist - LIBMSI_DB_OPEN_PATCHFILE)) + { + TRACE("Database is a patch\n"); + szPersist -= LIBMSI_DB_OPEN_PATCHFILE; + patch = true; + } + + szMode = szPersist; + db = alloc_msiobject( sizeof (LibmsiDatabase), _libmsi_database_destroy ); + if( !db ) + { + FIXME("Failed to allocate a handle\n"); + goto end; + } + + if (!strstr( szDBPath, G_DIR_SEPARATOR_S )) + { + getcwd( path, MAX_PATH ); + strcat( path, G_DIR_SEPARATOR_S ); + strcat( path, szDBPath ); + } + else + strcpy( path, szDBPath ); + + db->patch = patch; + db->mode = szMode; + list_init( &db->tables ); + list_init( &db->transforms ); + list_init( &db->streams ); + list_init( &db->storages ); + + if( szPersist != LIBMSI_DB_OPEN_CREATE ) + { + db->path = strdup( path ); + ret = _libmsi_database_open(db); + if (ret) + goto end; + } else { + szPersist = szDBPath; + db->strings = msi_init_string_table( &db->bytes_per_strref ); + } + + db->media_transform_offset = MSI_INITIAL_MEDIA_TRANSFORM_OFFSET; + db->media_transform_disk_id = MSI_INITIAL_MEDIA_TRANSFORM_DISKID; + + if( TRACE_ON( msi ) ) + enum_stream_names( db->infile ); + + if( szPersist == LIBMSI_DB_OPEN_CREATE ) + ret = _libmsi_database_start_transaction(db, szDBPath); + else + ret = _libmsi_database_start_transaction(db, szPersist); + if (ret) + goto end; + + ret = LIBMSI_RESULT_SUCCESS; + + msiobj_addref( &db->hdr ); + *pdb = db; + +end: + if( db ) + msiobj_release( &db->hdr ); + + return ret; +} + +static char *msi_read_text_archive(const char *path, unsigned *len) +{ + char *data; + size_t nread; + + if (!g_file_get_contents(path, &data, &nread, NULL)) + return NULL; + + while (!data[nread - 1]) nread--; + *len = nread; + return data; +} + +static void msi_parse_line(char **line, char ***entries, unsigned *num_entries, unsigned *len) +{ + char *ptr = *line; + char *save; + unsigned i, count = 1, chars_left = *len; + + *entries = NULL; + + /* stay on this line */ + while (chars_left && *ptr != '\n') + { + /* entries are separated by tabs */ + if (*ptr == '\t') + count++; + + ptr++; + chars_left--; + } + + *entries = msi_alloc(count * sizeof(char *)); + if (!*entries) + return; + + /* store pointers into the data */ + chars_left = *len; + for (i = 0, ptr = *line; i < count; i++) + { + while (chars_left && *ptr == '\r') + { + ptr++; + chars_left--; + } + save = ptr; + + while (chars_left && *ptr != '\t' && *ptr != '\n' && *ptr != '\r') + { + if (!*ptr) *ptr = '\n'; /* convert embedded nulls to \n */ + if (ptr > *line && *ptr == '\x19' && *(ptr - 1) == '\x11') + { + *ptr = '\n'; + *(ptr - 1) = '\r'; + } + ptr++; + chars_left--; + } + + /* NULL-separate the data */ + if (*ptr == '\n' || *ptr == '\r') + { + while (chars_left && (*ptr == '\n' || *ptr == '\r')) + { + *(ptr++) = 0; + chars_left--; + } + } + else if (*ptr) + { + *(ptr++) = 0; + chars_left--; + } + (*entries)[i] = save; + } + + /* move to the next line if there's more, else EOF */ + *line = ptr; + *len = chars_left; + if (num_entries) + *num_entries = count; +} + +static char *msi_build_createsql_prelude(char *table) +{ + char *prelude; + unsigned size; + + static const char create_fmt[] = "CREATE TABLE `%s` ("; + + size = sizeof(create_fmt)/sizeof(create_fmt[0]) + strlen(table) - 2; + prelude = msi_alloc(size * sizeof(char)); + if (!prelude) + return NULL; + + sprintf(prelude, create_fmt, table); + return prelude; +} + +static char *msi_build_createsql_columns(char **columns_data, char **types, unsigned num_columns) +{ + char *columns; + char *p; + const char *type; + unsigned sql_size = 1, i, len; + char expanded[128], *ptr; + char size[10], comma[2], extra[30]; + + static const char column_fmt[] = "`%s` %s%s%s%s "; + static const char size_fmt[] = "(%s)"; + static const char type_char[] = "CHAR"; + static const char type_int[] = "INT"; + static const char type_long[] = "LONG"; + static const char type_object[] = "OBJECT"; + static const char type_notnull[] = " NOT NULL"; + static const char localizable[] = " LOCALIZABLE"; + + columns = msi_alloc_zero(sql_size * sizeof(char)); + if (!columns) + return NULL; + + for (i = 0; i < num_columns; i++) + { + type = NULL; + comma[1] = size[0] = extra[0] = '\0'; + + if (i == num_columns - 1) + comma[0] = '\0'; + else + comma[0] = ','; + + ptr = &types[i][1]; + len = atol(ptr); + extra[0] = '\0'; + + switch (types[i][0]) + { + case 'l': + strcpy(extra, type_notnull); + /* fall through */ + case 'L': + strcat(extra, localizable); + type = type_char; + sprintf(size, size_fmt, ptr); + break; + case 's': + strcpy(extra, type_notnull); + /* fall through */ + case 'S': + type = type_char; + sprintf(size, size_fmt, ptr); + break; + case 'i': + strcpy(extra, type_notnull); + /* fall through */ + case 'I': + if (len <= 2) + type = type_int; + else if (len == 4) + type = type_long; + else + { + WARN("invalid int width %u\n", len); + msi_free(columns); + return NULL; + } + break; + case 'v': + strcpy(extra, type_notnull); + /* fall through */ + case 'V': + type = type_object; + break; + default: + ERR("Unknown type: %c\n", types[i][0]); + msi_free(columns); + return NULL; + } + + sprintf(expanded, column_fmt, columns_data[i], type, size, extra, comma); + sql_size += strlen(expanded); + + p = msi_realloc(columns, sql_size * sizeof(char)); + if (!p) + { + msi_free(columns); + return NULL; + } + columns = p; + + strcat(columns, expanded); + } + + return columns; +} + +static char *msi_build_createsql_postlude(char **primary_keys, unsigned num_keys) +{ + char *postlude; + char *keys; + char *ptr; + unsigned size, key_size, i; + + static const char key_fmt[] = "`%s`, "; + static const char postlude_fmt[] = "PRIMARY KEY %s)"; + + for (i = 0, size = 1; i < num_keys; i++) + size += strlen(key_fmt) + strlen(primary_keys[i]) - 2; + + keys = msi_alloc(size * sizeof(char)); + if (!keys) + return NULL; + + for (i = 0, ptr = keys; i < num_keys; i++) + { + key_size = strlen(key_fmt) + strlen(primary_keys[i]) -2; + sprintf(ptr, key_fmt, primary_keys[i]); + ptr += key_size; + } + + /* remove final ', ' */ + *(ptr - 2) = '\0'; + + size = strlen(postlude_fmt) + size - 1; + postlude = msi_alloc(size * sizeof(char)); + if (!postlude) + goto done; + + sprintf(postlude, postlude_fmt, keys); + +done: + msi_free(keys); + return postlude; +} + +static unsigned msi_add_table_to_db(LibmsiDatabase *db, char **columns, char **types, char **labels, unsigned num_labels, unsigned num_columns) +{ + unsigned r = LIBMSI_RESULT_OUTOFMEMORY; + unsigned size; + LibmsiQuery *view; + char *create_sql = NULL; + char *prelude; + char *columns_sql; + char *postlude; + + prelude = msi_build_createsql_prelude(labels[0]); + columns_sql = msi_build_createsql_columns(columns, types, num_columns); + postlude = msi_build_createsql_postlude(labels + 1, num_labels - 1); /* skip over table name */ + + if (!prelude || !columns_sql || !postlude) + goto done; + + size = strlen(prelude) + strlen(columns_sql) + strlen(postlude) + 1; + create_sql = msi_alloc(size * sizeof(char)); + if (!create_sql) + goto done; + + strcpy(create_sql, prelude); + strcat(create_sql, columns_sql); + strcat(create_sql, postlude); + + r = _libmsi_database_open_query( db, create_sql, &view ); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = _libmsi_query_execute(view, NULL); + libmsi_query_close(view); + msiobj_release(&view->hdr); + +done: + msi_free(prelude); + msi_free(columns_sql); + msi_free(postlude); + msi_free(create_sql); + return r; +} + +static char *msi_import_stream_filename(const char *path, const char *name) +{ + unsigned len; + char *fullname; + char *ptr; + + len = strlen(path) + strlen(name) + 1; + fullname = msi_alloc(len); + if (!fullname) + return NULL; + + strcpy( fullname, path ); + + /* chop off extension from path */ + ptr = strrchr(fullname, '.'); + if (!ptr) + { + msi_free (fullname); + return NULL; + } + strcpy( ptr, G_DIR_SEPARATOR_S ); + strcat( ptr, name ); + return fullname; +} + +static unsigned construct_record(unsigned num_columns, char **types, + char **data, const char *path, LibmsiRecord **rec) +{ + unsigned i; + + *rec = libmsi_record_create(num_columns); + if (!*rec) + return LIBMSI_RESULT_OUTOFMEMORY; + + for (i = 0; i < num_columns; i++) + { + switch (types[i][0]) + { + case 'L': case 'l': case 'S': case 's': + libmsi_record_set_string(*rec, i + 1, data[i]); + break; + case 'I': case 'i': + if (*data[i]) + libmsi_record_set_int(*rec, i + 1, atoi(data[i])); + break; + case 'V': case 'v': + if (*data[i]) + { + unsigned r; + char *file = msi_import_stream_filename(path, data[i]); + if (!file) + return LIBMSI_RESULT_FUNCTION_FAILED; + + r = _libmsi_record_load_stream_from_file(*rec, i + 1, file); + msi_free (file); + if (r != LIBMSI_RESULT_SUCCESS) + return LIBMSI_RESULT_FUNCTION_FAILED; + } + break; + default: + ERR("Unhandled column type: %c\n", types[i][0]); + msiobj_release(&(*rec)->hdr); + return LIBMSI_RESULT_FUNCTION_FAILED; + } + } + + return LIBMSI_RESULT_SUCCESS; +} + +static unsigned msi_add_records_to_table(LibmsiDatabase *db, char **columns, char **types, + char **labels, char ***records, + int num_columns, int num_records, + const char *path) +{ + unsigned r, num_rows, num_cols; + int i; + LibmsiView *view; + LibmsiRecord *rec; + + r = table_view_create(db, labels[0], &view); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = view->ops->get_dimensions( view, &num_rows, &num_cols ); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + while (num_rows > 0) + { + r = view->ops->delete_row(view, --num_rows); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + } + + for (i = 0; i < num_records; i++) + { + r = construct_record(num_columns, types, records[i], path, &rec); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = view->ops->insert_row(view, rec, -1, false); + if (r != LIBMSI_RESULT_SUCCESS) + { + msiobj_release(&rec->hdr); + goto done; + } + + msiobj_release(&rec->hdr); + } + +done: + msi_free(view); + return r; +} + +static unsigned _libmsi_database_import(LibmsiDatabase *db, const char *folder, const char *file) +{ + unsigned r = LIBMSI_RESULT_OUTOFMEMORY; + unsigned len, i; + unsigned num_labels, num_types; + unsigned num_columns, num_records = 0; + char *path; + char **columns; + char **types; + char **labels; + char *ptr; + char *data; + char ***records = NULL; + char ***temp_records; + + static const char suminfo[] = "_SummaryInformation"; + static const char forcecodepage[] = "_ForceCodepage"; + + TRACE("%p %s %s\n", db, debugstr_a(folder), debugstr_a(file) ); + + if( folder == NULL || file == NULL ) + return LIBMSI_RESULT_INVALID_PARAMETER; + + len = strlen(folder) + 1 + strlen(file) + 1; + path = msi_alloc( len ); + if (!path) + return LIBMSI_RESULT_OUTOFMEMORY; + + strcpy( path, folder ); + strcat( path, G_DIR_SEPARATOR_S ); + strcat( path, file ); + + data = msi_read_text_archive( path, &len ); + if (!data) + goto done; + + ptr = data; + msi_parse_line( &ptr, &columns, &num_columns, &len ); + msi_parse_line( &ptr, &types, &num_types, &len ); + msi_parse_line( &ptr, &labels, &num_labels, &len ); + + if (num_columns == 1 && !columns[0][0] && num_labels == 1 && !labels[0][0] && + num_types == 2 && !strcmp( types[1], forcecodepage )) + { + r = msi_set_string_table_codepage( db->strings, atoi( types[0] ) ); + goto done; + } + + if (num_columns != num_types) + { + r = LIBMSI_RESULT_FUNCTION_FAILED; + goto done; + } + + records = msi_alloc(sizeof(char **)); + if (!records) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto done; + } + + /* read in the table records */ + while (len) + { + msi_parse_line( &ptr, &records[num_records], NULL, &len ); + + num_records++; + temp_records = msi_realloc(records, (num_records + 1) * sizeof(char **)); + if (!temp_records) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto done; + } + records = temp_records; + } + + if (!strcmp(labels[0], suminfo)) + { + r = msi_add_suminfo( db, records, num_records, num_columns ); + if (r != LIBMSI_RESULT_SUCCESS) + { + r = LIBMSI_RESULT_FUNCTION_FAILED; + goto done; + } + } + else + { + if (!table_view_exists(db, labels[0])) + { + r = msi_add_table_to_db( db, columns, types, labels, num_labels, num_columns ); + if (r != LIBMSI_RESULT_SUCCESS) + { + r = LIBMSI_RESULT_FUNCTION_FAILED; + goto done; + } + } + + r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records, path ); + } + +done: + msi_free(path); + msi_free(data); + msi_free(columns); + msi_free(types); + msi_free(labels); + + for (i = 0; i < num_records; i++) + msi_free(records[i]); + + msi_free(records); + + return r; +} + +LibmsiResult libmsi_database_import(LibmsiDatabase *db, const char *szFolder, const char *szFilename) +{ + unsigned r; + + TRACE("%x %s %s\n",db,debugstr_a(szFolder), debugstr_a(szFilename)); + + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref( &db->hdr ); + r = _libmsi_database_import( db, szFolder, szFilename ); + msiobj_release( &db->hdr ); + return r; +} + +static unsigned msi_export_record( int fd, LibmsiRecord *row, unsigned start ) +{ + unsigned i, count, len, r = LIBMSI_RESULT_SUCCESS; + const char *sep; + char *buffer; + unsigned sz; + + len = 0x100; + buffer = msi_alloc( len ); + if ( !buffer ) + return LIBMSI_RESULT_OUTOFMEMORY; + + count = libmsi_record_get_field_count( row ); + for ( i=start; i<=count; i++ ) + { + sz = len; + r = libmsi_record_get_string( row, i, buffer, &sz ); + if (r == LIBMSI_RESULT_MORE_DATA) + { + char *p = msi_realloc( buffer, sz + 1 ); + if (!p) + break; + len = sz + 1; + buffer = p; + } + sz = len; + r = libmsi_record_get_string( row, i, buffer, &sz ); + if (r != LIBMSI_RESULT_SUCCESS) + break; + + /* TODO full_write */ + if (write( fd, buffer, sz ) != sz) + { + r = LIBMSI_RESULT_FUNCTION_FAILED; + break; + } + + sep = (i < count) ? "\t" : "\r\n"; + if (write( fd, sep, strlen(sep) ) != strlen(sep)) + { + r = LIBMSI_RESULT_FUNCTION_FAILED; + break; + } + } + msi_free( buffer ); + return r; +} + +static unsigned msi_export_row( LibmsiRecord *row, void *arg ) +{ + return msi_export_record( (intptr_t) arg, row, 1 ); +} + +static unsigned msi_export_forcecodepage( int fd, unsigned codepage ) +{ + static const char fmt[] = "\r\n\r\n%u\t_ForceCodepage\r\n"; + char data[sizeof(fmt) + 10]; + unsigned sz; + + sprintf( data, fmt, codepage ); + + sz = strlen(data) + 1; + if (write( fd, data, sz ) != sz) + return LIBMSI_RESULT_FUNCTION_FAILED; + + return LIBMSI_RESULT_SUCCESS; +} + +static unsigned _libmsi_database_export( LibmsiDatabase *db, const char *table, + int fd) +{ + static const char query[] = "select * from %s"; + static const char forcecodepage[] = "_ForceCodepage"; + LibmsiRecord *rec = NULL; + LibmsiQuery *view = NULL; + unsigned r; + + TRACE("%p %s %d\n", db, debugstr_a(table), fd ); + + if (!strcmp( table, forcecodepage )) + { + unsigned codepage = msi_get_string_table_codepage( db->strings ); + r = msi_export_forcecodepage( fd, codepage ); + goto done; + } + + r = _libmsi_query_open( db, &view, query, table ); + if (r == LIBMSI_RESULT_SUCCESS) + { + /* write out row 1, the column names */ + r = _libmsi_query_get_column_info(view, LIBMSI_COL_INFO_NAMES, &rec); + if (r == LIBMSI_RESULT_SUCCESS) + { + msi_export_record( fd, rec, 1 ); + msiobj_release( &rec->hdr ); + } + + /* write out row 2, the column types */ + r = _libmsi_query_get_column_info(view, LIBMSI_COL_INFO_TYPES, &rec); + if (r == LIBMSI_RESULT_SUCCESS) + { + msi_export_record( fd, rec, 1 ); + msiobj_release( &rec->hdr ); + } + + /* write out row 3, the table name + keys */ + r = _libmsi_database_get_primary_keys( db, table, &rec ); + if (r == LIBMSI_RESULT_SUCCESS) + { + libmsi_record_set_string( rec, 0, table ); + msi_export_record( fd, rec, 0 ); + msiobj_release( &rec->hdr ); + } + + /* write out row 4 onwards, the data */ + r = _libmsi_query_iterate_records( view, 0, msi_export_row, (void *)(intptr_t) fd ); + msiobj_release( &view->hdr ); + } + +done: + return r; +} + +/*********************************************************************** + * MsiExportDatabase [MSI.@] + * + * Writes a file containing the table data as tab separated ASCII. + * + * The format is as follows: + * + * row1 : colname1 <tab> colname2 <tab> .... colnameN <cr> <lf> + * row2 : coltype1 <tab> coltype2 <tab> .... coltypeN <cr> <lf> + * row3 : tablename <tab> key1 <tab> key2 <tab> ... keyM <cr> <lf> + * + * Followed by the data, starting at row 1 with one row per line + * + * row4 : data <tab> data <tab> data <tab> ... data <cr> <lf> + */ +LibmsiResult libmsi_database_export( LibmsiDatabase *db, const char *szTable, + int fd ) +{ + unsigned r = LIBMSI_RESULT_OUTOFMEMORY; + + TRACE("%x %s %d\n", db, debugstr_a(szTable), fd); + + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref ( &db->hdr ); + r = _libmsi_database_export( db, szTable, fd ); + msiobj_release( &db->hdr ); + return r; +} + +typedef struct _tagMERGETABLE +{ + struct list entry; + struct list rows; + char *name; + unsigned numconflicts; + char **columns; + unsigned numcolumns; + char **types; + unsigned numtypes; + char **labels; + unsigned numlabels; +} MERGETABLE; + +typedef struct _tagMERGEROW +{ + struct list entry; + LibmsiRecord *data; +} MERGEROW; + +typedef struct _tagMERGEDATA +{ + LibmsiDatabase *db; + LibmsiDatabase *merge; + MERGETABLE *curtable; + LibmsiQuery *curview; + struct list *tabledata; +} MERGEDATA; + +static bool merge_type_match(const char *type1, const char *type2) +{ + if (((type1[0] == 'l') || (type1[0] == 's')) && + ((type2[0] == 'l') || (type2[0] == 's'))) + return true; + + if (((type1[0] == 'L') || (type1[0] == 'S')) && + ((type2[0] == 'L') || (type2[0] == 'S'))) + return true; + + return !strcmp( type1, type2 ); +} + +static unsigned merge_verify_colnames(LibmsiQuery *dbview, LibmsiQuery *mergeview) +{ + LibmsiRecord *dbrec, *mergerec; + unsigned r, i, count; + + r = _libmsi_query_get_column_info(dbview, LIBMSI_COL_INFO_NAMES, &dbrec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = _libmsi_query_get_column_info(mergeview, LIBMSI_COL_INFO_NAMES, &mergerec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + count = libmsi_record_get_field_count(dbrec); + for (i = 1; i <= count; i++) + { + if (!_libmsi_record_get_string_raw(mergerec, i)) + break; + + if (strcmp( _libmsi_record_get_string_raw( dbrec, i ), _libmsi_record_get_string_raw( mergerec, i ) )) + { + r = LIBMSI_RESULT_DATATYPE_MISMATCH; + goto done; + } + } + + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + dbrec = mergerec = NULL; + + r = _libmsi_query_get_column_info(dbview, LIBMSI_COL_INFO_TYPES, &dbrec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = _libmsi_query_get_column_info(mergeview, LIBMSI_COL_INFO_TYPES, &mergerec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + count = libmsi_record_get_field_count(dbrec); + for (i = 1; i <= count; i++) + { + if (!_libmsi_record_get_string_raw(mergerec, i)) + break; + + if (!merge_type_match(_libmsi_record_get_string_raw(dbrec, i), + _libmsi_record_get_string_raw(mergerec, i))) + { + r = LIBMSI_RESULT_DATATYPE_MISMATCH; + break; + } + } + +done: + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + + return r; +} + +static unsigned merge_verify_primary_keys(LibmsiDatabase *db, LibmsiDatabase *mergedb, + const char *table) +{ + LibmsiRecord *dbrec, *mergerec = NULL; + unsigned r, i, count; + + r = _libmsi_database_get_primary_keys(db, table, &dbrec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = _libmsi_database_get_primary_keys(mergedb, table, &mergerec); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + count = libmsi_record_get_field_count(dbrec); + if (count != libmsi_record_get_field_count(mergerec)) + { + r = LIBMSI_RESULT_DATATYPE_MISMATCH; + goto done; + } + + for (i = 1; i <= count; i++) + { + if (strcmp( _libmsi_record_get_string_raw( dbrec, i ), _libmsi_record_get_string_raw( mergerec, i ) )) + { + r = LIBMSI_RESULT_DATATYPE_MISMATCH; + goto done; + } + } + +done: + msiobj_release(&dbrec->hdr); + msiobj_release(&mergerec->hdr); + + return r; +} + +static char *get_key_value(LibmsiQuery *view, const char *key, LibmsiRecord *rec) +{ + LibmsiRecord *colnames; + char *str; + char *val; + unsigned r, i = 0, sz = 0; + int cmp; + + r = _libmsi_query_get_column_info(view, LIBMSI_COL_INFO_NAMES, &colnames); + if (r != LIBMSI_RESULT_SUCCESS) + return NULL; + + do + { + str = msi_dup_record_field(colnames, ++i); + cmp = strcmp( key, str ); + msi_free(str); + } while (cmp); + + msiobj_release(&colnames->hdr); + + r = _libmsi_record_get_string(rec, i, NULL, &sz); + if (r != LIBMSI_RESULT_SUCCESS) + return NULL; + sz++; + + if (_libmsi_record_get_string_raw(rec, i)) /* check record field is a string */ + { + /* quote string record fields */ + const char szQuote[] = "'"; + sz += 2; + val = msi_alloc(sz*sizeof(char)); + if (!val) + return NULL; + + strcpy(val, szQuote); + r = _libmsi_record_get_string(rec, i, val+1, &sz); + strcpy(val+1+sz, szQuote); + } + else + { + /* do not quote integer record fields */ + val = msi_alloc(sz*sizeof(char)); + if (!val) + return NULL; + + r = _libmsi_record_get_string(rec, i, val, &sz); + } + + if (r != LIBMSI_RESULT_SUCCESS) + { + ERR("failed to get string!\n"); + msi_free(val); + return NULL; + } + + return val; +} + +static char *create_diff_row_query(LibmsiDatabase *merge, LibmsiQuery *view, + char *table, LibmsiRecord *rec) +{ + char *query = NULL; + char *clause = NULL; + char *val; + const char *setptr; + const char *key; + unsigned size, oldsize; + LibmsiRecord *keys; + unsigned r, i, count; + + static const char keyset[] = "`%s` = %s AND"; + static const char lastkeyset[] = "`%s` = %s "; + static const char fmt[] = "SELECT * FROM %s WHERE %s"; + + r = _libmsi_database_get_primary_keys(merge, table, &keys); + if (r != LIBMSI_RESULT_SUCCESS) + return NULL; + + clause = msi_alloc_zero(sizeof(char)); + if (!clause) + goto done; + + size = 1; + count = libmsi_record_get_field_count(keys); + for (i = 1; i <= count; i++) + { + key = _libmsi_record_get_string_raw(keys, i); + val = get_key_value(view, key, rec); + + if (i == count) + setptr = lastkeyset; + else + setptr = keyset; + + oldsize = size; + size += strlen(setptr) + strlen(key) + strlen(val) - 4; + clause = msi_realloc(clause, size * sizeof (char)); + if (!clause) + { + msi_free(val); + goto done; + } + + sprintf(clause + oldsize - 1, setptr, key, val); + msi_free(val); + } + + size = strlen(fmt) + strlen(table) + strlen(clause) + 1; + query = msi_alloc(size * sizeof(char)); + if (!query) + goto done; + + sprintf(query, fmt, table, clause); + +done: + msi_free(clause); + msiobj_release(&keys->hdr); + return query; +} + +static unsigned merge_diff_row(LibmsiRecord *rec, void *param) +{ + MERGEDATA *data = param; + MERGETABLE *table = data->curtable; + MERGEROW *mergerow; + LibmsiQuery *dbview = NULL; + LibmsiRecord *row = NULL; + char *query = NULL; + unsigned r = LIBMSI_RESULT_SUCCESS; + + if (table_view_exists(data->db, table->name)) + { + query = create_diff_row_query(data->merge, data->curview, table->name, rec); + if (!query) + return LIBMSI_RESULT_OUTOFMEMORY; + + r = _libmsi_database_open_query(data->db, query, &dbview); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = _libmsi_query_execute(dbview, NULL); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = _libmsi_query_fetch(dbview, &row); + if (r == LIBMSI_RESULT_SUCCESS && !_libmsi_record_compare(rec, row)) + { + table->numconflicts++; + goto done; + } + else if (r != LIBMSI_RESULT_NO_MORE_ITEMS) + goto done; + + r = LIBMSI_RESULT_SUCCESS; + } + + mergerow = msi_alloc(sizeof(MERGEROW)); + if (!mergerow) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto done; + } + + mergerow->data = _libmsi_record_clone(rec); + if (!mergerow->data) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + msi_free(mergerow); + goto done; + } + + list_add_tail(&table->rows, &mergerow->entry); + +done: + msi_free(query); + msiobj_release(&row->hdr); + msiobj_release(&dbview->hdr); + return r; +} + +static unsigned msi_get_table_labels(LibmsiDatabase *db, const char *table, char ***labels, unsigned *numlabels) +{ + unsigned r, i, count; + LibmsiRecord *prec = NULL; + + r = _libmsi_database_get_primary_keys(db, table, &prec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + count = libmsi_record_get_field_count(prec); + *numlabels = count + 1; + *labels = msi_alloc((*numlabels)*sizeof(char *)); + if (!*labels) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto end; + } + + (*labels)[0] = strdup(table); + for (i=1; i<=count; i++ ) + { + (*labels)[i] = strdup(_libmsi_record_get_string_raw(prec, i)); + } + +end: + msiobj_release( &prec->hdr ); + return r; +} + +static unsigned msi_get_query_columns(LibmsiQuery *query, char ***columns, unsigned *numcolumns) +{ + unsigned r, i, count; + LibmsiRecord *prec = NULL; + + r = _libmsi_query_get_column_info(query, LIBMSI_COL_INFO_NAMES, &prec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + count = libmsi_record_get_field_count(prec); + *columns = msi_alloc(count*sizeof(char *)); + if (!*columns) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto end; + } + + for (i=1; i<=count; i++ ) + { + (*columns)[i-1] = strdup(_libmsi_record_get_string_raw(prec, i)); + } + + *numcolumns = count; + +end: + msiobj_release( &prec->hdr ); + return r; +} + +static unsigned msi_get_query_types(LibmsiQuery *query, char ***types, unsigned *numtypes) +{ + unsigned r, i, count; + LibmsiRecord *prec = NULL; + + r = _libmsi_query_get_column_info(query, LIBMSI_COL_INFO_TYPES, &prec); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + count = libmsi_record_get_field_count(prec); + *types = msi_alloc(count*sizeof(char *)); + if (!*types) + { + r = LIBMSI_RESULT_OUTOFMEMORY; + goto end; + } + + *numtypes = count; + for (i=1; i<=count; i++ ) + { + (*types)[i-1] = strdup(_libmsi_record_get_string_raw(prec, i)); + } + +end: + msiobj_release( &prec->hdr ); + return r; +} + +static void merge_free_rows(MERGETABLE *table) +{ + struct list *item, *cursor; + + LIST_FOR_EACH_SAFE(item, cursor, &table->rows) + { + MERGEROW *row = LIST_ENTRY(item, MERGEROW, entry); + + list_remove(&row->entry); + msiobj_release(&row->data->hdr); + msi_free(row); + } +} + +static void free_merge_table(MERGETABLE *table) +{ + unsigned i; + + if (table->labels != NULL) + { + for (i = 0; i < table->numlabels; i++) + msi_free(table->labels[i]); + + msi_free(table->labels); + } + + if (table->columns != NULL) + { + for (i = 0; i < table->numcolumns; i++) + msi_free(table->columns[i]); + + msi_free(table->columns); + } + + if (table->types != NULL) + { + for (i = 0; i < table->numtypes; i++) + msi_free(table->types[i]); + + msi_free(table->types); + } + + msi_free(table->name); + merge_free_rows(table); + + msi_free(table); +} + +static unsigned msi_get_merge_table (LibmsiDatabase *db, const char *name, MERGETABLE **ptable) +{ + unsigned r; + MERGETABLE *table; + LibmsiQuery *mergeview = NULL; + + static const char query[] = "SELECT * FROM %s"; + + table = msi_alloc_zero(sizeof(MERGETABLE)); + if (!table) + { + *ptable = NULL; + return LIBMSI_RESULT_OUTOFMEMORY; + } + + r = msi_get_table_labels(db, name, &table->labels, &table->numlabels); + if (r != LIBMSI_RESULT_SUCCESS) + goto err; + + r = _libmsi_query_open(db, &mergeview, query, name); + if (r != LIBMSI_RESULT_SUCCESS) + goto err; + + r = msi_get_query_columns(mergeview, &table->columns, &table->numcolumns); + if (r != LIBMSI_RESULT_SUCCESS) + goto err; + + r = msi_get_query_types(mergeview, &table->types, &table->numtypes); + if (r != LIBMSI_RESULT_SUCCESS) + goto err; + + list_init(&table->rows); + + table->name = strdup(name); + table->numconflicts = 0; + + msiobj_release(&mergeview->hdr); + *ptable = table; + return LIBMSI_RESULT_SUCCESS; + +err: + msiobj_release(&mergeview->hdr); + free_merge_table(table); + *ptable = NULL; + return r; +} + +static unsigned merge_diff_tables(LibmsiRecord *rec, void *param) +{ + MERGEDATA *data = param; + MERGETABLE *table; + LibmsiQuery *dbview = NULL; + LibmsiQuery *mergeview = NULL; + const char *name; + unsigned r; + + static const char query[] = "SELECT * FROM %s"; + + name = _libmsi_record_get_string_raw(rec, 1); + + r = _libmsi_query_open(data->merge, &mergeview, query, name); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + if (table_view_exists(data->db, name)) + { + r = _libmsi_query_open(data->db, &dbview, query, name); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = merge_verify_colnames(dbview, mergeview); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + r = merge_verify_primary_keys(data->db, data->merge, name); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + } + + r = msi_get_merge_table(data->merge, name, &table); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + data->curtable = table; + data->curview = mergeview; + r = _libmsi_query_iterate_records(mergeview, NULL, merge_diff_row, data); + if (r != LIBMSI_RESULT_SUCCESS) + { + free_merge_table(table); + goto done; + } + + list_add_tail(data->tabledata, &table->entry); + +done: + msiobj_release(&dbview->hdr); + msiobj_release(&mergeview->hdr); + return r; +} + +static unsigned gather_merge_data(LibmsiDatabase *db, LibmsiDatabase *merge, + struct list *tabledata) +{ + static const char query[] = "SELECT * FROM _Tables"; + LibmsiQuery *view; + MERGEDATA data; + unsigned r; + + r = _libmsi_database_open_query(merge, query, &view); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + data.db = db; + data.merge = merge; + data.tabledata = tabledata; + r = _libmsi_query_iterate_records(view, NULL, merge_diff_tables, &data); + msiobj_release(&view->hdr); + return r; +} + +static unsigned merge_table(LibmsiDatabase *db, MERGETABLE *table) +{ + unsigned r; + MERGEROW *row; + LibmsiView *tv; + + if (!table_view_exists(db, table->name)) + { + r = msi_add_table_to_db(db, table->columns, table->types, + table->labels, table->numlabels, table->numcolumns); + if (r != LIBMSI_RESULT_SUCCESS) + return LIBMSI_RESULT_FUNCTION_FAILED; + } + + LIST_FOR_EACH_ENTRY(row, &table->rows, MERGEROW, entry) + { + r = table_view_create(db, table->name, &tv); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = tv->ops->insert_row(tv, row->data, -1, false); + tv->ops->delete(tv); + + if (r != LIBMSI_RESULT_SUCCESS) + return r; + } + + return LIBMSI_RESULT_SUCCESS; +} + +static unsigned update_merge_errors(LibmsiDatabase *db, const char *error, + char *table, unsigned numconflicts) +{ + unsigned r; + LibmsiQuery *view; + + static const char create[] = + "CREATE TABLE `%s` (`Table` CHAR(255) NOT NULL, " + "`NumRowMergeConflicts` SHORT NOT NULL PRIMARY KEY `Table`)"; + static const char insert[] = + "INSERT INTO `%s` (`Table`, `NumRowMergeConflicts`) VALUES ('%s', %d)"; + + if (!table_view_exists(db, error)) + { + r = _libmsi_query_open(db, &view, create, error); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = _libmsi_query_execute(view, NULL); + msiobj_release(&view->hdr); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + } + + r = _libmsi_query_open(db, &view, insert, error, table, numconflicts); + if (r != LIBMSI_RESULT_SUCCESS) + return r; + + r = _libmsi_query_execute(view, NULL); + msiobj_release(&view->hdr); + return r; +} + +LibmsiResult libmsi_database_merge(LibmsiDatabase *db, LibmsiDatabase *merge, + const char *szTableName) +{ + struct list tabledata = LIST_INIT(tabledata); + struct list *item, *cursor; + MERGETABLE *table; + bool conflicts; + unsigned r; + + TRACE("(%d, %d, %s)\n", db, merge, + debugstr_a(szTableName)); + + if (szTableName && !*szTableName) + return LIBMSI_RESULT_INVALID_TABLE; + + if (!db || !merge) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref( &db->hdr ); + msiobj_addref( &merge->hdr ); + r = gather_merge_data(db, merge, &tabledata); + if (r != LIBMSI_RESULT_SUCCESS) + goto done; + + conflicts = false; + LIST_FOR_EACH_ENTRY(table, &tabledata, MERGETABLE, entry) + { + if (table->numconflicts) + { + conflicts = true; + + r = update_merge_errors(db, szTableName, table->name, + table->numconflicts); + if (r != LIBMSI_RESULT_SUCCESS) + break; + } + else + { + r = merge_table(db, table); + if (r != LIBMSI_RESULT_SUCCESS) + break; + } + } + + LIST_FOR_EACH_SAFE(item, cursor, &tabledata) + { + MERGETABLE *table = LIST_ENTRY(item, MERGETABLE, entry); + list_remove(&table->entry); + free_merge_table(table); + } + + if (conflicts) + r = LIBMSI_RESULT_FUNCTION_FAILED; + +done: + msiobj_release(&db->hdr); + msiobj_release(&merge->hdr); + return r; +} + +LibmsiDBState libmsi_database_get_state( LibmsiDatabase *db ) +{ + LibmsiDBState ret = LIBMSI_DB_STATE_READ; + + TRACE("%d\n", db); + + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref( &db->hdr ); + if (db->mode != LIBMSI_DB_OPEN_READONLY ) + ret = LIBMSI_DB_STATE_WRITE; + msiobj_release( &db->hdr ); + + return ret; +} + +unsigned _libmsi_database_apply_transform( LibmsiDatabase *db, + const char *szTransformFile, int iErrorCond ) +{ + unsigned ret = LIBMSI_RESULT_FUNCTION_FAILED; + GsfInput *in; + GsfInfile *stg; + uint8_t uuid[16]; + + TRACE("%p %s %d\n", db, debugstr_a(szTransformFile), iErrorCond); + in = gsf_input_stdio_new(szTransformFile, NULL); + if (!in) + { + WARN("open file failed for transform %s\n", debugstr_a(szTransformFile)); + return LIBMSI_RESULT_OPEN_FAILED; + } + stg = gsf_infile_msole_new( in, NULL ); + g_object_unref(G_OBJECT(in)); + + if( !gsf_infile_msole_get_class_id (GSF_INFILE_MSOLE(stg), uuid)) + { + FIXME("Failed to stat storage\n"); + goto end; + } + + if ( memcmp( uuid, clsid_msi_transform, 16 ) != 0 ) + goto end; + + if( TRACE_ON( msi ) ) + enum_stream_names( stg ); + + ret = msi_table_apply_transform( db, stg ); + +end: + g_object_unref(G_OBJECT(stg)); + + return ret; +} + +LibmsiResult libmsi_database_apply_transform( LibmsiDatabase *db, + const char *szTransformFile, int iErrorCond) +{ + unsigned r; + + msiobj_addref( &db->hdr ); + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + r = _libmsi_database_apply_transform( db, szTransformFile, iErrorCond ); + msiobj_release( &db->hdr ); + return r; +} + +static int gsf_infile_copy(GsfInfile *inf, GsfOutfile *outf) +{ + int n = gsf_infile_num_children(inf); + int i; + + for (i = 0; i < n; i++) { + const char *name = gsf_infile_name_by_index(inf, i); + GsfInput *child = gsf_infile_child_by_index(inf, i); + GsfInfile *childf = GSF_IS_INFILE (child) ? GSF_INFILE (child) : NULL; + gboolean is_dir = childf && gsf_infile_num_children (childf) > 0; + GsfOutput *dest = gsf_outfile_new_child(outf, name, is_dir); + gboolean ok; + + if (is_dir) + ok = gsf_infile_copy(childf, GSF_OUTFILE(dest)); + else + ok = gsf_input_copy(child, dest); + + g_object_unref(G_OBJECT(child)); + g_object_unref(G_OBJECT(dest)); + if (!ok) + return false; + } + return true; +} + +static unsigned commit_storage( const char *name, GsfInfile *stg, void *opaque) +{ + LibmsiDatabase *db = opaque; + GsfOutfile *outstg; + unsigned ret = LIBMSI_RESULT_FUNCTION_FAILED; + + TRACE("%s %p %p\n", debugstr_a(name), stg, opaque); + + outstg = GSF_OUTFILE(gsf_outfile_new_child( db->outfile, name, true )); + if ( !outstg ) + return LIBMSI_RESULT_FUNCTION_FAILED; + + if ( !gsf_infile_copy( stg, outstg ) ) + goto end; + + ret = LIBMSI_RESULT_SUCCESS; + +end: + gsf_output_close(GSF_OUTPUT(outstg)); + g_object_unref(G_OBJECT(outstg)); + return ret; +} + +static unsigned commit_stream( const char *name, GsfInput *stm, void *opaque) +{ + LibmsiDatabase *db = opaque; + GsfOutput *outstm; + unsigned ret = LIBMSI_RESULT_FUNCTION_FAILED; + char decname[0x40]; + + decode_streamname(name, decname); + TRACE("%s(%s) %p %p\n", debugstr_a(name), debugstr_a(decname), stm, opaque); + + outstm = gsf_outfile_new_child( db->outfile, name, false ); + if ( !outstm ) + return LIBMSI_RESULT_FUNCTION_FAILED; + + gsf_input_seek (stm, 0, G_SEEK_SET); + gsf_output_seek (outstm, 0, G_SEEK_SET); + if ( !gsf_input_copy( stm, outstm )) + goto end; + + ret = LIBMSI_RESULT_SUCCESS; + +end: + gsf_output_close(GSF_OUTPUT(outstm)); + g_object_unref(G_OBJECT(outstm)); + return ret; +} + +LibmsiResult libmsi_database_commit( LibmsiDatabase *db ) +{ + unsigned r = LIBMSI_RESULT_SUCCESS; + unsigned bytes_per_strref; + + TRACE("%d\n", db); + + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref( &db->hdr ); + if (db->mode == LIBMSI_DB_OPEN_READONLY) + goto end; + + /* FIXME: lock the database */ + + r = msi_save_string_table( db->strings, db, &bytes_per_strref ); + if( r != LIBMSI_RESULT_SUCCESS ) + { + WARN("failed to save string table r=%08x\n",r); + goto end; + } + + r = msi_enum_db_storages( db, commit_storage, db ); + if (r != LIBMSI_RESULT_SUCCESS) + { + WARN("failed to save storages r=%08x\n",r); + goto end; + } + + r = msi_enum_db_streams( db, commit_stream, db ); + if (r != LIBMSI_RESULT_SUCCESS) + { + WARN("failed to save streams r=%08x\n",r); + goto end; + } + + r = _libmsi_database_commit_tables( db, bytes_per_strref ); + if (r != LIBMSI_RESULT_SUCCESS) + { + WARN("failed to save tables r=%08x\n",r); + goto end; + } + + db->bytes_per_strref = bytes_per_strref; + + /* FIXME: unlock the database */ + + _libmsi_database_close(db, true); + _libmsi_database_open(db); + _libmsi_database_start_transaction(db, LIBMSI_DB_OPEN_TRANSACT); + +end: + msiobj_release( &db->hdr ); + + return r; +} + +struct msi_primary_key_record_info +{ + unsigned n; + LibmsiRecord *rec; +}; + +static unsigned msi_primary_key_iterator( LibmsiRecord *rec, void *param ) +{ + struct msi_primary_key_record_info *info = param; + const char *name; + const char *table; + unsigned type; + + type = libmsi_record_get_integer( rec, 4 ); + if( type & MSITYPE_KEY ) + { + info->n++; + if( info->rec ) + { + if ( info->n == 1 ) + { + table = _libmsi_record_get_string_raw( rec, 1 ); + libmsi_record_set_string( info->rec, 0, table); + } + + name = _libmsi_record_get_string_raw( rec, 3 ); + libmsi_record_set_string( info->rec, info->n, name ); + } + } + + return LIBMSI_RESULT_SUCCESS; +} + +unsigned _libmsi_database_get_primary_keys( LibmsiDatabase *db, + const char *table, LibmsiRecord **prec ) +{ + static const char sql[] = "select * from `_Columns` where `Table` = '%s'"; + struct msi_primary_key_record_info info; + LibmsiQuery *query = NULL; + unsigned r; + + if (!table_view_exists( db, table )) + return LIBMSI_RESULT_INVALID_TABLE; + + r = _libmsi_query_open( db, &query, sql, table ); + if( r != LIBMSI_RESULT_SUCCESS ) + return r; + + /* count the number of primary key records */ + info.n = 0; + info.rec = 0; + r = _libmsi_query_iterate_records( query, 0, msi_primary_key_iterator, &info ); + if( r == LIBMSI_RESULT_SUCCESS ) + { + TRACE("Found %d primary keys\n", info.n ); + + /* allocate a record and fill in the names of the tables */ + info.rec = libmsi_record_create( info.n ); + info.n = 0; + r = _libmsi_query_iterate_records( query, 0, msi_primary_key_iterator, &info ); + if( r == LIBMSI_RESULT_SUCCESS ) + *prec = info.rec; + else + msiobj_release( &info.rec->hdr ); + } + msiobj_release( &query->hdr ); + + return r; +} + +LibmsiResult libmsi_database_get_primary_keys(LibmsiDatabase *db, + const char *table, LibmsiRecord **prec) +{ + unsigned r; + + TRACE("%d %s %p\n", db, debugstr_a(table), prec); + + if( !db ) + return LIBMSI_RESULT_INVALID_HANDLE; + + msiobj_addref( &db->hdr ); + r = _libmsi_database_get_primary_keys( db, table, prec ); + msiobj_release( &db->hdr ); + + return r; +} + +LibmsiCondition libmsi_database_is_table_persistent( + LibmsiDatabase *db, const char *szTableName) +{ + LibmsiCondition r; + + TRACE("%x %s\n", db, debugstr_a(szTableName)); + + msiobj_addref( &db->hdr ); + if( !db ) + return LIBMSI_CONDITION_ERROR; + + r = _libmsi_database_is_table_persistent( db, szTableName ); + + msiobj_release( &db->hdr ); + + return r; +} |