/* * Implementation of the Microsoft Installer (msi.dll) * * Copyright 2007 James Hawkins * * 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 #define COBJMACROS #include "windef.h" #include "winbase.h" #include "winerror.h" #include "libmsi.h" #include "objbase.h" #include "msipriv.h" #include "query.h" #include "debug.h" #include "unicode.h" #define NUM_STREAMS_COLS 2 typedef struct tabSTREAM { unsigned str_index; IStream *stream; } STREAM; typedef struct tagMSISTREAMSVIEW { MSIVIEW view; MSIDATABASE *db; STREAM **streams; unsigned max_streams; unsigned num_rows; unsigned row_size; } MSISTREAMSVIEW; static BOOL streams_set_table_size(MSISTREAMSVIEW *sv, unsigned size) { if (size >= sv->max_streams) { sv->max_streams *= 2; sv->streams = msi_realloc_zero(sv->streams, sv->max_streams * sizeof(STREAM *)); if (!sv->streams) return FALSE; } return TRUE; } static STREAM *create_stream(MSISTREAMSVIEW *sv, const WCHAR *name, BOOL encoded, IStream *stm) { STREAM *stream; WCHAR decoded[MAX_STREAM_NAME_LEN]; stream = msi_alloc(sizeof(STREAM)); if (!stream) return NULL; if (encoded) { decode_streamname(name, decoded); TRACE("stream -> %s %s\n", debugstr_w(name), debugstr_w(decoded)); name = decoded; } stream->str_index = msi_addstringW(sv->db->strings, name, -1, 1, StringNonPersistent); stream->stream = stm; return stream; } static unsigned STREAMS_fetch_int(MSIVIEW *view, unsigned row, unsigned col, unsigned *val) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; TRACE("(%p, %d, %d, %p)\n", view, row, col, val); if (col != 1) return ERROR_INVALID_PARAMETER; if (row >= sv->num_rows) return ERROR_NO_MORE_ITEMS; *val = sv->streams[row]->str_index; return ERROR_SUCCESS; } static unsigned STREAMS_fetch_stream(MSIVIEW *view, unsigned row, unsigned col, IStream **stm) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; TRACE("(%p, %d, %d, %p)\n", view, row, col, stm); if (row >= sv->num_rows) return ERROR_FUNCTION_FAILED; IStream_AddRef(sv->streams[row]->stream); *stm = sv->streams[row]->stream; return ERROR_SUCCESS; } static unsigned STREAMS_get_row( MSIVIEW *view, unsigned row, MSIRECORD **rec ) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; TRACE("%p %d %p\n", sv, row, rec); return msi_view_get_row( sv->db, view, row, rec ); } static unsigned STREAMS_set_row(MSIVIEW *view, unsigned row, MSIRECORD *rec, unsigned mask) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; STREAM *stream; IStream *stm; STATSTG stat; WCHAR *encname = NULL; WCHAR *name = NULL; uint16_t *data = NULL; HRESULT hr; unsigned count; unsigned r = ERROR_FUNCTION_FAILED; TRACE("(%p, %d, %p, %08x)\n", view, row, rec, mask); if (row > sv->num_rows) return ERROR_FUNCTION_FAILED; r = MSI_RecordGetIStream(rec, 2, &stm); if (r != ERROR_SUCCESS) return r; hr = IStream_Stat(stm, &stat, STATFLAG_NONAME); if (FAILED(hr)) { WARN("failed to stat stream: %08x\n", hr); goto done; } if (stat.cbSize.QuadPart >> 32) { WARN("stream too large\n"); goto done; } data = msi_alloc(stat.cbSize.QuadPart); if (!data) goto done; hr = IStream_Read(stm, data, stat.cbSize.QuadPart, &count); if (FAILED(hr) || count != stat.cbSize.QuadPart) { WARN("failed to read stream: %08x\n", hr); goto done; } name = strdupW(MSI_RecordGetString(rec, 1)); if (!name) { WARN("failed to retrieve stream name\n"); goto done; } encname = encode_streamname(FALSE, name); msi_destroy_stream(sv->db, encname); r = write_stream_data(sv->db->storage, name, data, count, FALSE); if (r != ERROR_SUCCESS) { WARN("failed to write stream data: %d\n", r); goto done; } stream = create_stream(sv, name, FALSE, NULL); if (!stream) goto done; hr = IStorage_OpenStream(sv->db->storage, encname, 0, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream->stream); if (FAILED(hr)) { WARN("failed to open stream: %08x\n", hr); goto done; } sv->streams[row] = stream; done: msi_free(name); msi_free(data); msi_free(encname); IStream_Release(stm); return r; } static unsigned STREAMS_insert_row(MSIVIEW *view, MSIRECORD *rec, unsigned row, BOOL temporary) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; unsigned i; TRACE("(%p, %p, %d, %d)\n", view, rec, row, temporary); if (!streams_set_table_size(sv, ++sv->num_rows)) return ERROR_FUNCTION_FAILED; if (row == -1) row = sv->num_rows - 1; /* shift the rows to make room for the new row */ for (i = sv->num_rows - 1; i > row; i--) { sv->streams[i] = sv->streams[i - 1]; } return STREAMS_set_row(view, row, rec, 0); } static unsigned STREAMS_delete_row(MSIVIEW *view, unsigned row) { FIXME("(%p %d): stub!\n", view, row); return ERROR_SUCCESS; } static unsigned STREAMS_execute(MSIVIEW *view, MSIRECORD *record) { TRACE("(%p, %p)\n", view, record); return ERROR_SUCCESS; } static unsigned STREAMS_close(MSIVIEW *view) { TRACE("(%p)\n", view); return ERROR_SUCCESS; } static unsigned STREAMS_get_dimensions(MSIVIEW *view, unsigned *rows, unsigned *cols) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; TRACE("(%p, %p, %p)\n", view, rows, cols); if (cols) *cols = NUM_STREAMS_COLS; if (rows) *rows = sv->num_rows; return ERROR_SUCCESS; } static unsigned STREAMS_get_column_info( MSIVIEW *view, unsigned n, const WCHAR **name, unsigned *type, BOOL *temporary, const WCHAR **table_name ) { TRACE("(%p, %d, %p, %p, %p, %p)\n", view, n, name, type, temporary, table_name); if (n == 0 || n > NUM_STREAMS_COLS) return ERROR_INVALID_PARAMETER; switch (n) { case 1: if (name) *name = szName; if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MAX_STREAM_NAME_LEN; break; case 2: if (name) *name = szData; if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE; break; } if (table_name) *table_name = szStreams; if (temporary) *temporary = FALSE; return ERROR_SUCCESS; } static unsigned streams_find_row(MSISTREAMSVIEW *sv, MSIRECORD *rec, unsigned *row) { const WCHAR *str; unsigned r, i, id, data; str = MSI_RecordGetString(rec, 1); r = msi_string2idW(sv->db->strings, str, &id); if (r != ERROR_SUCCESS) return r; for (i = 0; i < sv->num_rows; i++) { STREAMS_fetch_int(&sv->view, i, 1, &data); if (data == id) { *row = i; return ERROR_SUCCESS; } } return ERROR_FUNCTION_FAILED; } static unsigned streams_modify_update(MSIVIEW *view, MSIRECORD *rec) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; unsigned r, row; r = streams_find_row(sv, rec, &row); if (r != ERROR_SUCCESS) return ERROR_FUNCTION_FAILED; return STREAMS_set_row(view, row, rec, 0); } static unsigned streams_modify_assign(MSIVIEW *view, MSIRECORD *rec) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; unsigned r, row; r = streams_find_row(sv, rec, &row); if (r == ERROR_SUCCESS) return streams_modify_update(view, rec); return STREAMS_insert_row(view, rec, -1, FALSE); } static unsigned STREAMS_modify(MSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec, unsigned row) { unsigned r; TRACE("%p %d %p\n", view, eModifyMode, rec); switch (eModifyMode) { case MSIMODIFY_ASSIGN: r = streams_modify_assign(view, rec); break; case MSIMODIFY_INSERT: r = STREAMS_insert_row(view, rec, -1, FALSE); break; case MSIMODIFY_UPDATE: r = streams_modify_update(view, rec); break; case MSIMODIFY_VALIDATE_NEW: case MSIMODIFY_INSERT_TEMPORARY: case MSIMODIFY_REFRESH: case MSIMODIFY_REPLACE: case MSIMODIFY_MERGE: case MSIMODIFY_DELETE: case MSIMODIFY_VALIDATE: case MSIMODIFY_VALIDATE_FIELD: case MSIMODIFY_VALIDATE_DELETE: FIXME("%p %d %p - mode not implemented\n", view, eModifyMode, rec ); r = ERROR_CALL_NOT_IMPLEMENTED; break; default: r = ERROR_INVALID_DATA; } return r; } static unsigned STREAMS_delete(MSIVIEW *view) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; unsigned i; TRACE("(%p)\n", view); for (i = 0; i < sv->num_rows; i++) { if (sv->streams[i]) { if (sv->streams[i]->stream) IStream_Release(sv->streams[i]->stream); msi_free(sv->streams[i]); } } msi_free(sv->streams); msi_free(sv); return ERROR_SUCCESS; } static unsigned STREAMS_find_matching_rows(MSIVIEW *view, unsigned col, unsigned val, unsigned *row, MSIITERHANDLE *handle) { MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; unsigned index = PtrToUlong(*handle); TRACE("(%p, %d, %d, %p, %p)\n", view, col, val, row, handle); if (col == 0 || col > NUM_STREAMS_COLS) return ERROR_INVALID_PARAMETER; while (index < sv->num_rows) { if (sv->streams[index]->str_index == val) { *row = index; break; } index++; } *handle = UlongToPtr(++index); if (index > sv->num_rows) return ERROR_NO_MORE_ITEMS; return ERROR_SUCCESS; } static const MSIVIEWOPS streams_ops = { STREAMS_fetch_int, STREAMS_fetch_stream, STREAMS_get_row, STREAMS_set_row, STREAMS_insert_row, STREAMS_delete_row, STREAMS_execute, STREAMS_close, STREAMS_get_dimensions, STREAMS_get_column_info, STREAMS_modify, STREAMS_delete, STREAMS_find_matching_rows, NULL, NULL, NULL, NULL, NULL, NULL, }; static int add_streams_to_table(MSISTREAMSVIEW *sv) { IEnumSTATSTG *stgenum = NULL; STATSTG stat; STREAM *stream = NULL; HRESULT hr; unsigned r, count = 0, size; WCHAR *encname; hr = IStorage_EnumElements(sv->db->storage, 0, NULL, 0, &stgenum); if (FAILED(hr)) return -1; sv->max_streams = 1; sv->streams = msi_alloc_zero(sizeof(STREAM *)); if (!sv->streams) return -1; while (TRUE) { size = 0; hr = IEnumSTATSTG_Next(stgenum, 1, &stat, &size); if (FAILED(hr) || !size) break; if (stat.type != STGTY_STREAM) { CoTaskMemFree(stat.pwcsName); continue; } /* table streams are not in the _Streams table */ if (*stat.pwcsName == 0x4840) { CoTaskMemFree(stat.pwcsName); continue; } stream = create_stream(sv, stat.pwcsName, TRUE, NULL); if (!stream) { count = -1; CoTaskMemFree(stat.pwcsName); break; } /* these streams appear to be unencoded */ if (*stat.pwcsName == 0x0005) { r = msi_get_raw_stream(sv->db, stat.pwcsName, &stream->stream); } else { encname = encode_streamname(FALSE, stat.pwcsName); r = msi_get_raw_stream(sv->db, encname, &stream->stream); msi_free(encname); } CoTaskMemFree(stat.pwcsName); if (r != ERROR_SUCCESS) { WARN("unable to get stream %u\n", r); count = -1; break; } if (!streams_set_table_size(sv, ++count)) { count = -1; break; } sv->streams[count - 1] = stream; } IEnumSTATSTG_Release(stgenum); return count; } unsigned STREAMS_CreateView(MSIDATABASE *db, MSIVIEW **view) { MSISTREAMSVIEW *sv; int rows; TRACE("(%p, %p)\n", db, view); sv = msi_alloc_zero( sizeof(MSISTREAMSVIEW) ); if (!sv) return ERROR_FUNCTION_FAILED; sv->view.ops = &streams_ops; sv->db = db; rows = add_streams_to_table(sv); if (rows < 0) { msi_free( sv ); return ERROR_FUNCTION_FAILED; } sv->num_rows = rows; *view = (MSIVIEW *)sv; return ERROR_SUCCESS; }