/* * 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 */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using LibGSF.Input; using LibGSF.Output; using LibMSI.Internal; using LibMSI.Views; using static LibMSI.LibmsiQuery; using static LibMSI.LibmsiRecord; using static LibMSI.LibmsiSummaryInfo; using static LibMSI.Internal.LibmsiTable; using static LibMSI.Internal.MsiPriv; using static LibMSI.Internal.StringTable; namespace LibMSI { internal class LibmsiTransform { public GsfInfile Stg { get; set; } } internal class LibmsiStorage { public string Name { get; set; } public GsfInfile Stg { get; set; } } internal class LibmsiStream { public string Name { get; set; } public GsfInput Stm { get; set; } } /// /// .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. /// public class LibmsiDatabase { #region Constants internal static readonly byte[] clsid_msi_transform = { 0x82, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }; internal static readonly byte[] clsid_msi_database = { 0x84, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }; internal static readonly byte[] clsid_msi_patch = { 0x86, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }; #endregion #region Classes private class ExportRow { public Stream FD { get; set; } public string TableDir { get; set; } public Exception Error { get; set; } } private class MERGETABLE { public LinkedList Rows { get; set; } public string Name { get; set; } public int NumConflicts { get; set; } public string[] Columns { get; set; } public int NumColumns { get; set; } public string[] Types { get; set; } public int NumTypes { get; set; } public string[] Labels { get; set; } public int NumLabels { get; set; } } private class MERGEROW { public LibmsiRecord Data { get; set; } } private class MERGEDATA { public LibmsiDatabase Database { get; set; } public LibmsiDatabase Merge { get; set; } public MERGETABLE CurTable { get; set; } public LibmsiQuery CurView { get; set; } public LinkedList TableData { get; set; } } private class MsiPrimaryKeyRecordInfo { public int N { get; set; } public LibmsiRecord Rec { get; set; } } #endregion #region Properties internal GsfInfile Infile { get; set; } internal GsfOutfile Outfile { get; set; } internal StringTable Strings { get; set; } internal int BytesPerStrref { get; set; } internal string Path { get; set; } internal string Outpath { get; set; } internal bool RenameOutpath { get; set; } internal LibmsiDbFlags Flags { get; set; } internal int MediaTransformOffset { get; set; } internal int MediaTransformDiskId { get; set; } internal LinkedList Tables { get; set; } internal LinkedList Transforms { get; set; } internal LinkedList Streams { get; set; } internal LinkedList Storages { get; set; } #endregion #region Constructor and Destructor /// /// Private constructor /// private LibmsiDatabase() { } /// /// Create a MSI database or open from . /// /// An MST transform file path /// LibmsiDbFlags opening flags /// Path to output MSI file /// Exception to set on error, or null /// A new LibmsiDatabase on success, null if fail. public static LibmsiDatabase Create(string path, LibmsiDbFlags flags, string persist, ref Exception error) { if (path == null) return null; if (error != null) return null; LibmsiDatabase self = new LibmsiDatabase { Path = path, Outpath = persist, Flags = flags, }; if (!self.Init(ref error)) return null; return self; } /// /// Destructor /// ~LibmsiDatabase() { Close(false); FreeCachedTables(this); Transforms.Clear(); } #endregion #region Functions /// True if the database is read-only public bool IsReadOnly() => Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY); /// An exisiting table name /// Exception to set on error, or null /// A LibmsiRecord containing the names of all the primary key columns. public LibmsiRecord GetPrimaryKeys(string table, ref Exception error) { if (table == null) return null; if (error != null) return null; LibmsiResult r = GetPrimaryKeys(table, out LibmsiRecord rec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) error = new Exception($"LIBMSI_RESULT_ERROR: {r}"); return rec; } /// /// FIXME /// /// An MST transform file path /// Exception to set on error, or null /// True on success public bool ApplyTransform(string file, ref Exception error) { if (file == null) return false; if (error != null) return false; LibmsiResult r = ApplyTransform(file); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) error = new Exception($"LIBMSI_RESULT_ERROR: {r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } /// /// Writes a file containing the table data as tab separated ASCII. /// /// A table name /// A file descriptor /// Exception to set on error, or null /// True on success /// /// The format is as follows: /// /// row1 : colname1 colname2 .... colnameN /// row2 : coltype1 coltype2 .... coltypeN /// row3 : tablename key1 key2 ... keyM /// /// Followed by the data, starting at row 1 with one row per line /// /// row4 : data data data ... data /// public bool Export(string table, Stream fd, ref Exception error) { if (table == null) return false; if (fd == null || !fd.CanWrite) return false; if (error != null) return false; LibmsiResult r = ExportImpl(table, fd, ref error); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) error = new Exception($"LIBMSI_RESULT_ERROR: {r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } /// Adapted from WixToolset public bool ExportAll(string path) { if (string.IsNullOrEmpty(path)) return false; if (!Directory.Exists(path)) Directory.CreateDirectory(path); // SummaryInfo Exception err = null; string summaryInfoPath = System.IO.Path.Combine(path, "_SummaryInformation.idt"); using (Stream summaryInfoStream = File.OpenWrite(summaryInfoPath)) { Export("_SummaryInformation", summaryInfoStream, ref err); } // Tables err = null; LibmsiQuery tableQuery = LibmsiQuery.Create(this, $"SELECT `Name` FROM `_Tables`", ref err); tableQuery.Execute(null, ref err); LibmsiRecord tableRecord = tableQuery.Fetch(ref err); while (tableRecord != null) { err = null; string table = tableRecord.GetString(1); string tablePath = System.IO.Path.Combine(path, $"{table}.idt"); using (Stream tableStream = File.OpenWrite(tablePath)) { Export(table, tableStream, ref err); } tableRecord = tableQuery.Fetch(ref err); } // Streams if (!Directory.Exists(System.IO.Path.Combine(path, "_Streams"))) Directory.CreateDirectory(System.IO.Path.Combine(path, "_Streams")); err = null; LibmsiQuery streamsQuery = LibmsiQuery.Create(this, $"SELECT `Name`, `Data` FROM `_Streams`", ref err); streamsQuery.Execute(null, ref err); LibmsiRecord streamRecord = streamsQuery.Fetch(ref err); while (streamRecord != null) { err = null; string stream = streamRecord.GetString(1); if (stream.EndsWith("SummaryInformation", StringComparison.Ordinal)) continue; int i = stream.IndexOf('.'); if (i >= 0) { if (File.Exists(System.IO.Path.Combine(path, stream.Substring(0, i), stream.Substring(i + 1) + ".ibd"))) continue; } using (Stream streamRecordStream = streamRecord.GetStream(2)) using (Stream streamRecordOutput = File.OpenWrite(System.IO.Path.Combine(path, "_Streams", stream))) { streamRecordStream.CopyTo(streamRecordOutput); } streamRecord = tableQuery.Fetch(ref err); } return true; } /// /// Import a table to the database from file . /// /// Path to a table file /// Exception to set on error, or null /// True on success public bool Import(string path, ref Exception error) { if (path == null) return false; if (error != null) return false; LibmsiResult r = Import(path); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) error = new Exception($"LIBMSI_RESULT_ERROR: {r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } /// An exisiting table name /// Exception to set on error, or null /// True if the is persistent, false if it's temporary public bool IsTablePersistent(string table, ref Exception error) { if (table == null) return false; if (error != null) return false; LibmsiCondition r = LibmsiTable.IsTablePersistent(this, table); if (r == LibmsiCondition.LIBMSI_CONDITION_NONE) error = new InvalidDataException("The table is unknown"); else if (r == LibmsiCondition.LIBMSI_CONDITION_ERROR) error = new Exception("Error"); return r == LibmsiCondition.LIBMSI_CONDITION_TRUE; } /// A LibmsiDatabase to merge /// An optionnal table name /// Exception to set on error, or null /// True on success public bool Merge(LibmsiDatabase merge, string tablename, ref Exception error) { if (merge == null) return false; if (tablename != null && string.IsNullOrEmpty(tablename)) return false; if (error != null) return false; LinkedList tabledata = new LinkedList(); bool conflicts; LibmsiResult r = GatherMergeData(merge, tabledata.First()); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) goto done; conflicts = false; foreach (MERGETABLE table in tabledata) { if (table.NumConflicts != 0) { conflicts = true; r = UpdateMergeErrors(tablename, table.Name, table.NumConflicts); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) break; } else { r = MergeTable(table); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) break; } } foreach (MERGETABLE table in tabledata) { FreeMergeTable(table); } tabledata.Clear(); if (conflicts) r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; done: if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) error = new Exception($"LIBMSI_RESULT_ERROR: {r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } /// Exception to set on error, or null /// True on success public bool Commit(ref Exception error) { if (error != null) return false; LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS; if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY)) return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; // FIXME: lock the database r = Strings.SaveStringTable(this, out int bytes_per_strref); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { error = new Exception($"Failed to save string table r={r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } r = EnumDbStorages(CommitStorage, this); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { error = new Exception($"Failed to save storages r={r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } r = EnumDbStreams(CommitStream, this); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { error = new Exception($"Failed to save streams r={r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } r = DatabseCommitTables(this, bytes_per_strref); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { error = new Exception($"Failed to save tables r={r}"); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } BytesPerStrref = bytes_per_strref; // FIXME: unlock the database Close(true); Flags &= ~LibmsiDbFlags.LIBMSI_DB_FLAGS_CREATE; Flags |= LibmsiDbFlags.LIBMSI_DB_FLAGS_TRANSACT; Open(); StartTransaction(); return r == LibmsiResult.LIBMSI_RESULT_SUCCESS; } #endregion #region Internal Functions internal LibmsiResult OpenStorage(string stname) { LibmsiResult r = LibmsiResult.LIBMSI_RESULT_NOT_ENOUGH_MEMORY; foreach (LibmsiStorage stg in Storages) { if (stname == stg.Name) return r; } LibmsiStorage storage = new LibmsiStorage { Name = stname }; Exception err = null; GsfInput input = Infile.ChildByName(stname, ref err); if (input == null || !(input is GsfInfile)) return r; storage.Stg = input as GsfInfile; if (storage.Stg == null) return r; Storages.AddFirst(storage); return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal LibmsiResult CreateStorage(string stname, GsfInput stm) { if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY)) return LibmsiResult.LIBMSI_RESULT_ACCESS_DENIED; bool found = false; LibmsiStorage storage = null; foreach (LibmsiStorage stg in Storages) { if (stname == stg.Name) { found = true; storage = stg; break; } } if (!found) storage = new LibmsiStorage { Name = stname }; Exception err = null; GsfInfile origstg = GsfInfileMSOle.Create(stm, ref err); if (origstg == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; if (!found) Storages.AddFirst(storage); storage.Stg = origstg; return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal void DestroyStorage(string stname) { foreach (LibmsiStorage storage in Storages) { if (stname == storage.Name) { Storages.Remove(storage); break; } } } internal LibmsiResult WriteRawStreamData(string stname, byte[] data, int sz, out GsfInput outstm) { LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; outstm = null; if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY)) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; foreach (LibmsiStream stream in Streams) { if (stname == stream.Name) { DestroyStream(stname); break; } } byte[] mem = new byte[sz == 0 ? 1 : sz]; if (data != null || sz != 0) Array.Copy(data, mem, sz); GsfInput stm = GsfInputMemory.Create(mem, sz, true); ret = MsiAllocStream(stname, stm); outstm = stm; return ret; } internal LibmsiResult CreateStream(string stname, GsfInput stm) { LibmsiResult r = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; bool found = false; if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY)) return LibmsiResult.LIBMSI_RESULT_ACCESS_DENIED; string encname = EncodeStreamName(false, stname); foreach (LibmsiStream stream in Streams) { if (encname == stream.Name) { stream.Stm = stm; found = true; break; } } if (found) r = LibmsiResult.LIBMSI_RESULT_SUCCESS; else r = MsiAllocStream(encname, stm); return r; } internal LibmsiResult EnumDbStreams(Func fn, object opaque) { foreach (LibmsiStream stream in Streams) { GsfInput stm = stream.Stm; LibmsiResult r = fn(stream.Name, stm, opaque); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal LibmsiResult EnumDbStorages(Func fn, object opaque) { foreach (LibmsiStorage storage in Storages) { GsfInfile stg = storage.Stg; LibmsiResult r = fn(storage.Name, stg, opaque); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal LibmsiResult GetRawStream(string stname, out GsfInput stm) { string decoded = DecodeStreamName(stname); if (CloneInfileStream(stname, out stm) == LibmsiResult.LIBMSI_RESULT_SUCCESS) return LibmsiResult.LIBMSI_RESULT_SUCCESS; LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; foreach (LibmsiTransform transform in Transforms) { Exception err = null; stm = transform.Stg.ChildByName(stname, ref err); if (stm != null) { ret = LibmsiResult.LIBMSI_RESULT_SUCCESS; break; } } return ret; } internal void DestroyStream(string stname) { foreach (LibmsiStream stream in Streams) { if (stname == stream.Name) { Streams.Remove(stream); break; } } } internal void AppendStorageToDb(GsfInfile stg) { Transforms.AddLast(new LibmsiTransform { Stg = stg }); } internal LibmsiResult Close(bool committed) { if (Strings != null) { Strings.Destroy(); Strings = null; } if (Infile != null) Infile = null; if (Outfile != null) { Outfile.Close(); Outfile = null; } Streams.Clear(); Storages.Clear(); if (Outpath != null) { if (!committed) { //unlink(Outpath); } else if (RenameOutpath) { //unlink(Path); File.Move(Outpath, Path); } else { Path = Outpath; } } Outpath = null; return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal LibmsiResult StartTransaction() { if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_READONLY)) return LibmsiResult.LIBMSI_RESULT_SUCCESS; RenameOutpath = false; if (Outpath == null) { string path = Path; if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_TRANSACT)) { path += ".tmp"; RenameOutpath = true; } Outpath = path; } Exception err = null; GsfOutput output = GsfOutputStdio.Create(Outpath, ref err); if (output == null) { Console.Error.WriteLine($"Open file failed for {Outpath}"); return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; } GsfOutfileMSOle stg = GsfOutfileMSOle.Create(output); if (stg == null) { Console.Error.WriteLine($"Open failed for {Outpath}"); return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; } if (!stg.SetClassID(Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_PATCH) ? clsid_msi_patch : clsid_msi_database)) { Console.Error.WriteLine("Set guid failed"); Outfile = null; return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } Outfile = stg; return LibmsiResult.LIBMSI_RESULT_SUCCESS; } internal LibmsiResult Open() { byte[] uuid = new byte[16]; LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; Exception err = null; GsfInput input = GsfInputStdio.Create(Path, ref err); if (input == null) { Console.Error.WriteLine($"Open file failed for {Path}"); return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; } GsfInfile stg = GsfInfileMSOle.Create(input, ref err); if (stg == null) { Console.Error.WriteLine($"Open file failed for {Path}"); return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; } if (!(stg as GsfInfileMSOle).GetClassID(uuid)) { Console.Error.WriteLine("FIXME: Failed to stat storage"); goto end; } if (!uuid.SequenceEqual(clsid_msi_database) && !uuid.SequenceEqual(clsid_msi_patch) && !uuid.SequenceEqual(clsid_msi_transform)) { Console.Error.WriteLine($"Storage GUID is not a MSI database GUID {BitConverter.ToString(uuid).Replace("-", string.Empty)}"); goto end; } if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_PATCH) && !uuid.SequenceEqual(clsid_msi_patch)) { Console.Error.WriteLine($"Storage GUID is not the MSI patch GUID {BitConverter.ToString(uuid).Replace("-", string.Empty)}"); goto end; } Infile = stg; CacheInfileStructure(); Strings = LoadStringTable(Infile, out int bytes_per_strref); if (Strings == null) goto end; BytesPerStrref = bytes_per_strref; ret = LibmsiResult.LIBMSI_RESULT_SUCCESS; end: if (ret != LibmsiResult.LIBMSI_RESULT_SUCCESS) Infile = null; return ret; } internal LibmsiResult ApplyTransform(string szTransformFile) { LibmsiResult ret = LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; byte[] uuid = new byte[16]; Exception err = null; GsfInput input = GsfInputStdio.Create(szTransformFile, ref err); if (input == null) { Console.Error.WriteLine($"Open file failed for transform {szTransformFile}"); return LibmsiResult.LIBMSI_RESULT_OPEN_FAILED; } GsfInfile stg = GsfInfileMSOle.Create(input, ref err); if (!(stg as GsfInfileMSOle).GetClassID(uuid)) { Console.Error.WriteLine("FIXME: Failed to stat storage"); goto end; } if (!uuid.SequenceEqual(clsid_msi_transform)) goto end; bool TRACE_ON = false; // TODO: Make configurable if (TRACE_ON && stg != null) EnumStreamNames(stg); ret = LibmsiTable.ApplyTransform(this, stg); end: return ret; } internal LibmsiResult GetPrimaryKeys(string table, out LibmsiRecord prec) { prec = null; if (!TableViewExists(this, table)) return LibmsiResult.LIBMSI_RESULT_INVALID_TABLE; string sql = $"select * from `_Columns` where `Table` = '{table}'"; LibmsiResult r = QueryOpen(this, out LibmsiQuery query, sql); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; // Count the number of primary key records MsiPrimaryKeyRecordInfo info = new MsiPrimaryKeyRecordInfo { N = 0, Rec = null, }; int count = 0; r = query.IterateRecords(ref count, PrimaryKeyIterator, info); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) { // Allocate a record and fill in the names of the tables info.Rec = LibmsiRecord.Create(info.N); info.N = 0; count = 0; r = query.IterateRecords(ref count, PrimaryKeyIterator, info); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) prec = info.Rec; } return r; } #endregion #region Utilities private LibmsiResult FindInfileStream(string name, out GsfInput stm) { foreach (LibmsiStream stream in Streams) { if (name == stream.Name) { stm = stream.Stm; return LibmsiResult.LIBMSI_RESULT_SUCCESS; } } stm = null; return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } private LibmsiResult MsiAllocStream(string name, GsfInput stm) { LibmsiStream stream = new LibmsiStream { Name = name, Stm = stm, }; Streams.AddFirst(stream); return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private LibmsiResult CloneInfileStream(string name, out GsfInput stm) { stm = null; if (FindInfileStream(name, out GsfInput stream) == LibmsiResult.LIBMSI_RESULT_SUCCESS) { Exception err = null; stream = stream.Duplicate(ref err); if (stream == null) { Console.Error.WriteLine("Failed to clone stream"); return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } stream.Seek(0, SeekOrigin.Begin); stm = stream; return LibmsiResult.LIBMSI_RESULT_SUCCESS; } return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } private string ReadTextArchive(string path, out int len) { try { string data = File.ReadAllText(path); len = data.Length; return data; } catch { len = 0; return null; } } private void ParseLine(ref string line, ref int linePtr, out string[] entries, out int num_entries, ref int len) { int ptr = linePtr; int save; int i, count = 1, chars_left = len; entries = null; // Stay on this line while (chars_left != 0 && line[ptr] != '\n') { // Entries are separated by tabs if (line[ptr] == '\t') count++; ptr++; chars_left--; } entries = new string[count]; // Store pointers into the data chars_left = len; for (i = 0, ptr = linePtr; i < count; i++) { while (chars_left != 0 && line[ptr] == '\r') { ptr++; chars_left--; } save = ptr; while (chars_left != 0 && line[ptr] != '\t' && line[ptr] != '\n' && line[ptr] != '\r') { // Convert embedded nulls to \n if (line[ptr] == '\0') { char[] temp = line.ToCharArray(); temp[ptr] = '\n'; line = new string(temp); } if (ptr > linePtr && line[ptr] == '\x19' && line[ptr - 1] == '\x11') { char[] temp = line.ToCharArray(); temp[ptr] = '\n'; temp[ptr - 1] = '\r'; line = new string(temp); } ptr++; chars_left--; } // null-separate the data if (line[ptr] == '\n' || line[ptr] == '\r') { while (chars_left != 0 && (line[ptr] == '\n' || line[ptr] == '\r')) { char[] temp = line.ToCharArray(); temp[ptr++] = '\0'; chars_left--; line = new string(temp); } } else if (line[ptr] != 0) { char[] temp = line.ToCharArray(); temp[ptr++] = '\0'; chars_left--; line = new string(temp); } entries[i] = new string(line.Skip(save).TakeWhile(c => c != '\0').ToArray()); } // Move to the next line if there's more, else EOF linePtr = ptr; len = chars_left; num_entries = count; } private string BuildCreateSQLPrelude(string table) => $"CREATE TABLE `{table}` ("; private string BuildCreateSQLColumns(string[] columns_data, string[] types, int num_columns) { int sql_size = 1; long len; string columns = string.Empty; for (int i = 0; i < num_columns; i++) { string comma; if (i == num_columns - 1) comma = "\0"; else comma = ","; comma += '\0'; int ptr = 1; // types[i][1]; len = long.Parse(new string(types[i].Skip(1).SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray())); string extra = "\0"; string type = null; string size = "\0"; switch (types[i][0]) { case 'l': extra = " NOT null"; extra += " LOCALIZABLE"; type = "CHAR"; size = $"({ptr})"; break; case 'L': extra += " LOCALIZABLE"; type = "CHAR"; size = $"({ptr})"; break; case 's': extra = " NOT null"; type = "CHAR"; size = $"({ptr})"; break; case 'S': type = "CHAR"; size = $"({ptr})"; break; case 'i': extra = " NOT null"; if (len <= 2) { type = "INT"; } else if (len == 4) { type = "LONG"; } else { Console.Error.WriteLine($"Invalid int width {len}"); return null; } break; case 'I': if (len <= 2) { type = "INT"; } else if (len == 4) { type = "LONG"; } else { Console.Error.WriteLine($"Invalid int width {len}"); return null; } break; case 'v': extra = " NOT null"; type = "OBJECT"; break; case 'V': type = "OBJECT"; break; default: Console.Error.WriteLine($"Unknown type: {types[i][0]}"); return null; } string expanded = $"`{columns_data[i]}` {type}{size}{extra}{comma} "; sql_size += expanded.Length; columns += expanded; } return columns; } private string BuildCreateSQLPostlude(string[] primary_keys, int num_keys) { string keys = string.Join(", ", primary_keys.Select(s => $"`{s}`")); return $"PRIMARY KEY {keys})"; } private LibmsiResult AddTableToDb(string[] columns, string[] types, string[] labels, int num_labels, int num_columns) { string prelude = BuildCreateSQLPrelude(labels[0]); string columns_sql = BuildCreateSQLColumns(columns, types, num_columns); string postlude = BuildCreateSQLPostlude(labels.Skip(1).ToArray(), num_labels - 1); // Skip over table name if (prelude == null || columns_sql == null || postlude == null) return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; int size = prelude.Length + columns_sql.Length + postlude.Length + 1; string create_sql = prelude + columns_sql + postlude; Exception error = null; // FIXME: move error handling to caller LibmsiQuery view = LibmsiQuery.Create(this, create_sql, ref error); if (view == null) { if (error != null) Console.Error.WriteLine(error.Message); error = null; return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; } LibmsiResult r = view.Execute(null); view.Close(ref error); if (error != null) Console.Error.WriteLine(error.Message); error = null; return r; } private LibmsiResult ConstructRecord(int num_columns, string[] types, string[] data, string name, out LibmsiRecord rec) { rec = LibmsiRecord.Create(num_columns); for (int i = 0; i < num_columns; i++) { switch (types[i][0]) { case 'L': case 'l': case 'S': case 's': rec.SetString(i + 1, data[i]); break; case 'I': case 'i': if (data[i] != null) rec.SetInt(i + 1, int.Parse(new string(data[i].Skip(1).SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray()))); break; case 'V': case 'v': if (data[i] != null) { string file = System.IO.Path.Combine(name, data[i]); LibmsiResult r = rec.LoadStreamFromFile(i + 1, file); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } break; default: Console.Error.WriteLine($"Unhandled column type: {types[i][0]}"); return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private LibmsiResult AddRecordsToTable(string[] columns, string[] types, string[] labels, string[][] records, int num_columns, int num_records) { LibmsiResult r = LibmsiTableView.Create(this, labels[0], out LibmsiView view); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = view.GetDimensions(out int num_rows, out int num_cols); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; while (num_rows > 0) { r = view.DeleteRow(--num_rows); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } for (int i = 0; i < num_records; i++) { r = ConstructRecord(num_columns, types, records[i], labels[0], out LibmsiRecord rec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = view.InsertRow(rec, -1, false); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { return r; } } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private LibmsiResult Import(string path) { LibmsiResult r = LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; int num_records = 0; string suminfo = "_SummaryInformation"; string forcecodepage = "_ForceCodepage"; string data = ReadTextArchive(path, out int len); if (data == null) return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; int ptr = 0; // data[0] ParseLine(ref data, ref ptr, out string[] columns, out int num_columns, ref len); ParseLine(ref data, ref ptr, out string[] types, out int num_types, ref len); ParseLine(ref data, ref ptr, out string[] labels, out int num_labels, ref len); if (num_columns == 1 && columns[0][0] == '\0' && num_labels == 1 && labels[0][0] == '\0' && num_types == 2 && types[1] == forcecodepage) return Strings.SetCodePage(int.Parse(new string(types[0].Skip(1).SkipWhile(c => char.IsWhiteSpace(c)).TakeWhile(c => c == '+' || c == '-' || char.IsDigit(c)).ToArray()))); if (num_columns != num_types) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; List records = new List(); // Read in the table records while (len != 0) { ParseLine(ref data, ref ptr, out string[] record, out _, ref len); records.Add(record); } if (labels[0] == suminfo) { r = AddSummaryInfo(this, records.ToArray(), num_records, num_columns); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } else { if (!TableViewExists(this, labels[0])) { r = AddTableToDb(columns, types, labels, num_labels, num_columns); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } r = AddRecordsToTable(columns, types, labels, records.ToArray(), num_columns, num_records); } return r; } private static bool ExportStream(GsfInput gsfin, string table_dir, out string str, ref Exception error) { str = null; if (table_dir == null) return false; try { Directory.CreateDirectory(table_dir); } catch (Exception err) { error = err; return false; } str = gsfin.Name; string file = System.IO.Path.Combine(table_dir, str); try { Stream output = File.OpenWrite(file); LibmsiIStream input = LibmsiIStream.Create(gsfin); // TODO: Figure out how to do a "splice" on the streams // int spliced = g_output_stream_splice (output, input, 0, null, null); // return spliced != -1; return true; } catch (Exception err) { error = err; return false; } } private static LibmsiResult ExportRecord(Stream fd, LibmsiRecord row, int start, string table_dir, ref Exception error) { int count = row.GetFieldCount(); for (int i = start; i <= count; i++) { string str; row.GetGsfInput(i, out GsfInput input); if (input != null) { if (!ExportStream(input, table_dir, out str, ref error)) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; input = null; } else { str = row.GetString(i); if (str == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } try { // TODO full_write fd.Write(Encoding.ASCII.GetBytes(str), 0, str.Length); string sep = (i < count) ? "\t" : "\r\n"; fd.Write(Encoding.ASCII.GetBytes(sep), 0, sep.Length); } catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } // TODO: Move to LibmsiRecord private static LibmsiResult ExportRowImpl(LibmsiRecord row, object arg) { ExportRow export = arg as ExportRow; Exception err = export.Error; LibmsiResult r = ExportRecord(export.FD, row, 1, export.TableDir, ref err); export.Error = err; return r; } private static LibmsiResult ExportForceCodePage(Stream fd, int codepage) { string data = $"\r\n\r\n{codepage}\t_ForceCodepage\r\n\0"; int sz = data.Length; try { fd.Write(Encoding.ASCII.GetBytes(data), 0, sz); return LibmsiResult.LIBMSI_RESULT_SUCCESS; } catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } } private LibmsiResult ExportSummaryInfo(Stream fd, ref Exception error) { LibmsiSummaryInfo si = LibmsiSummaryInfo.Create(this, 0, ref error); if (si == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; string header = "PropertyId\tValue\r\ni2\tl255\r\n_SummaryInformation\tPropertyId\r\n"; int sz = header.Length; try { fd.Write(Encoding.ASCII.GetBytes(header), 0, sz); } catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } for (int i = 0; i < MSI_MAX_PROPS; i++) { if (si.Property[i].VariantType != LibmsiOLEVariantType.OLEVT_EMPTY) { string val = si.SummaryInfoAsString(i); if (val == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; string str = $"{i}\t{val}\r\n"; sz = str.Length; try { fd.Write(Encoding.ASCII.GetBytes(str), 0, sz); } catch { return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } } } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private LibmsiResult ExportImpl(string table, Stream fd, ref Exception error) { if (table == "_ForceCodepage") { int codepage = Strings.GetCodePage(); return ExportForceCodePage(fd, codepage); } else if (table == "_SummaryInformation") { return ExportSummaryInfo(fd, ref error); } string query = $"select * from {table}"; LibmsiResult r = QueryOpen(this, out LibmsiQuery view, query); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) { // Write out row 1, the column names r = view.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_NAMES, out LibmsiRecord rec); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) ExportRecord(fd, rec, 1, null, ref error); // Write out row 2, the column types r = view.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_TYPES, out rec); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) ExportRecord(fd, rec, 1, null, ref error); // Write out row 3, the table name + keys r = GetPrimaryKeys(table, out rec); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS) { rec.SetString(0, table); ExportRecord(fd, rec, 0, null, ref error); } // Write out row 4 onwards, the data ExportRow export = new ExportRow { FD = fd, TableDir = table, Error = error, }; int count = 0; r = view.IterateRecords(ref count, ExportRowImpl, export); } return r; } private static bool MergeTypeMatch(string type1, string 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 type1 == type2; } // TODO: Move to LibmsiQuery private static LibmsiResult MergeVerifyColnames(LibmsiQuery dbview, LibmsiQuery mergeview) { LibmsiResult r = dbview.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_NAMES, out LibmsiRecord dbrec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = mergeview.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_NAMES, out LibmsiRecord mergerec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; int count = dbrec.GetFieldCount(); for (int i = 1; i <= count; i++) { if (mergerec.GetStringRaw(i) == null) break; if (dbrec.GetStringRaw(i) != mergerec.GetStringRaw(i)) return LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH; } r = dbview.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_TYPES, out dbrec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = mergeview.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_TYPES, out mergerec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; count = dbrec.GetFieldCount(); for (int i = 1; i <= count; i++) { if (mergerec.GetStringRaw(i) == null) break; if (!MergeTypeMatch(dbrec.GetStringRaw(i), mergerec.GetStringRaw(i))) { r = LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH; break; } } return r; } private LibmsiResult MergeVerifyPrimaryKeys(LibmsiDatabase mergedb, string table) { LibmsiResult r = GetPrimaryKeys(table, out LibmsiRecord dbrec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = mergedb.GetPrimaryKeys(table, out LibmsiRecord mergerec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; int count = dbrec.GetFieldCount(); if (count != mergerec.GetFieldCount()) return LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH; for (int i = 1; i <= count; i++) { if (dbrec.GetStringRaw(i) != mergerec.GetStringRaw(i)) return LibmsiResult.LIBMSI_RESULT_DATATYPE_MISMATCH; } return r; } // TODO: Move to LibmsiRecord private static string GetKeyValue(LibmsiQuery view, string key, LibmsiRecord rec) { LibmsiResult r = view.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_NAMES, out LibmsiRecord colnames); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return null; int i = 0; bool cmp; do { string temp = colnames.DupRecordField(++i); cmp = (key == temp); } while (!cmp); // Check record field is a string string str; // Quote string record fields if (rec.GetStringRaw(i) != null) str = $"{rec.GetStringRaw(i)}"; else str = rec.GetString(i); return str; } private string CreateDiffRowQuery(LibmsiQuery view, string table, LibmsiRecord rec) { LibmsiResult r = GetPrimaryKeys(table, out LibmsiRecord keys); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return null; string query = $"SELECT * FROM {table} WHERE "; int count = keys.GetFieldCount(); for (int i = 1; i <= count; i++) { string key = keys.GetStringRaw(i); string val = GetKeyValue(view, key, rec); if (i == count) query += $"`{key}` = {val}"; else query += $"`{key}` = {val} AND "; } return query; } // TODO: Move to LibmsiRecord private static LibmsiResult MergeDiffRow(LibmsiRecord rec, object param) { MERGEDATA data = param as MERGEDATA; MERGETABLE table = data.CurTable; MERGEROW mergerow; LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS; Exception err = null; if (TableViewExists(data.Database, table.Name)) { string query = data.Merge.CreateDiffRowQuery(data.CurView, table.Name, rec); if (query == null) return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; LibmsiQuery dbview = LibmsiQuery.Create(data.Database, query, ref err); if (err != null) { Console.Error.WriteLine(err.Message); return r; } r = dbview.Execute(null); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = dbview.Fetch(out LibmsiRecord row); if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS && !RecordCompare(rec, row)) { table.NumConflicts++; return r; } else if (r != LibmsiResult.NO_MORE_ITEMS) { return r; } r = LibmsiResult.LIBMSI_RESULT_SUCCESS; } mergerow = new MERGEROW(); mergerow.Data = rec.Clone(); if (mergerow.Data == null) { r = LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY; return r; } table.Rows.AddFirst(mergerow); return r; } private LibmsiResult GetTableLabels(string table, out string[] labels, out int numlabels) { labels = null; numlabels = 0; LibmsiResult r = GetPrimaryKeys(table, out LibmsiRecord prec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; int count = prec.GetFieldCount(); numlabels = count + 1; labels = new string[numlabels]; labels[0] = table; for (int i = 1; i <= count; i++) { labels[i] = prec.GetStringRaw(i); } return r; } // TODO: Move to LibmsiRecord private static LibmsiResult GetQueryColumns(LibmsiQuery query, out string[] columns, out int numcolumns) { columns = null; numcolumns = 0; LibmsiResult r = query.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_NAMES, out LibmsiRecord prec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; int count = prec.GetFieldCount(); columns = new string[count]; for (int i = 1; i <= count; i++) { columns[i - 1] = prec.GetStringRaw(i); } numcolumns = count; return r; } // TODO: Move to LibmsiRecord private static LibmsiResult GetQueryTypes(LibmsiQuery query, out string[] types, out int numtypes) { types = null; numtypes = 0; LibmsiResult r = query.GetColumnInfo(LibmsiColInfo.LIBMSI_COL_INFO_TYPES, out LibmsiRecord prec); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; int count = prec.GetFieldCount(); types = new string[count]; numtypes = count; for (int i = 1; i <= count; i++) { types[i - 1] = prec.GetStringRaw(i); } return r; } // TODO: Move to MERGETABLE private static void FreeMergeTable(MERGETABLE table) { table.Labels = null; table.Columns = null; table.Types = null; table.Name = null; table.Rows.Clear(); } private LibmsiResult GetMergeTable(string name, out MERGETABLE ptable) { MERGETABLE table = new MERGETABLE(); LibmsiResult r = GetTableLabels(name, out string[] labels, out int num_labels); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) goto err; table.Labels = labels; table.NumLabels = num_labels; string query = $"SELECT * FROM {name}"; r = QueryOpen(this, out LibmsiQuery mergeview, query); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) goto err; r = GetQueryColumns(mergeview, out string[] columns, out int num_columns); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) goto err; table.Columns = columns; table.NumColumns = num_columns; r = GetQueryTypes(mergeview, out string[] types, out int num_types); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) goto err; table.Types = types; table.NumTypes = num_types; table.Rows = new LinkedList(); table.Name = name; table.NumConflicts = 0; ptable = table; return LibmsiResult.LIBMSI_RESULT_SUCCESS; err: FreeMergeTable(table); ptable = null; return r; } // TODO: Move to LibmsiRecord private static LibmsiResult MergeDiffTables(LibmsiRecord rec, object param) { MERGEDATA data = param as MERGEDATA; string name = rec.GetStringRaw(1); string query = $"SELECT * FROM {name}"; LibmsiResult r = QueryOpen(data.Merge, out LibmsiQuery mergeview, query); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; if (TableViewExists(data.Database, name)) { r = QueryOpen(data.Database, out LibmsiQuery dbview, query); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = MergeVerifyColnames(dbview, mergeview); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = data.Database.MergeVerifyPrimaryKeys(data.Merge, name); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } r = data.Merge.GetMergeTable(name, out MERGETABLE table); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; data.CurTable = table; data.CurView = mergeview; int count = 0; r = mergeview.IterateRecords(ref count, MergeDiffRow, data); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) { FreeMergeTable(table); return r; } data.TableData.AddFirst(table); return r; } private LibmsiResult GatherMergeData(LibmsiDatabase merge, MERGETABLE tabledata) { string query = "SELECT * FROM _Tables"; LibmsiResult r = DatabaseOpenQuery(merge, query, out LibmsiQuery view); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; MERGEDATA data = new MERGEDATA { Database = this, Merge = merge, TableData = new LinkedList() }; data.TableData.AddFirst(tabledata); int count = 0; r = view.IterateRecords(ref count, MergeDiffTables, data); return r; } private LibmsiResult MergeTable(MERGETABLE table) { LibmsiResult r; if (!TableViewExists(this, table.Name)) { r = AddTableToDb(table.Columns, table.Types, table.Labels, table.NumLabels, table.NumColumns); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } foreach (MERGEROW row in table.Rows) { r = LibmsiTableView.Create(this, table.Name, out LibmsiView tv); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = tv.InsertRow(row.Data, -1, false); tv.Delete(); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private LibmsiResult UpdateMergeErrors(string error, string table, int numconflicts) { LibmsiResult r; if (!TableViewExists(this, error)) { string create = $"CREATE TABLE `{error}` (`Table` CHAR(255) NOT NULL, `NumRowMergeConflicts` SHORT NOT NULL PRIMARY KEY `Table`)"; r = QueryOpen(this, out LibmsiQuery createView, create); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = createView.Execute(null); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; } string insert = $"INSERT INTO `{error}` (`Table`, `NumRowMergeConflicts`) VALUES ('{table}', {numconflicts})"; r = QueryOpen(this, out LibmsiQuery view, insert); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) return r; r = view.Execute(null); return r; } private void CacheInfileStructure() { LibmsiResult r; int n = Infile.NumChildren(); // TODO: error handling for (int i = 0; i < n; i++) { Exception err = null; GsfInput input = Infile.ChildByIndex(i, ref err); string name = input.Name; byte[] name8 = Encoding.ASCII.GetBytes(name); if (name == null) { Console.Error.WriteLine("Name was null"); continue; } // Table streams are not in the _Streams table if (!(input is GsfInfile) || (input as GsfInfile).NumChildren() == -1) { // UTF-8 encoding of 0x4840. if (name8[0] == 0xe4 && name8[1] == 0xa1 && name8[2] == 0x80) { string decname = DecodeStreamName(name + 3); if (decname == szStringPool || decname == szStringData) continue; r = OpenTable(this, decname, false); if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS) Console.Error.WriteLine($"Error opening table {decname}: {r}"); } else { r = MsiAllocStream(name, input); } } else { OpenStorage(name); } } } private static bool GsfInfileCopy(GsfInfile inf, GsfOutfile outf) { int n = inf.NumChildren(); for (int i = 0; i < n; i++) { Exception err = null; string name = inf.NameByIndex(i); GsfInput child = inf.ChildByName(name, ref err); GsfInfile childf = (child is GsfInfile) ? (child as GsfInfile) : null; bool is_dir = childf != null && childf.NumChildren() > 0; GsfOutput dest = outf.NewChild(name, is_dir); bool ok; if (is_dir) ok = GsfInfileCopy(childf, dest as GsfOutfile); else ok = child.Copy(dest); if (!ok) return false; } return true; } // TODO: Can we avoid the use of `opaque`? private static LibmsiResult CommitStorage(string name, GsfInfile stg, object opaque) { LibmsiDatabase db = opaque as LibmsiDatabase; GsfOutfile outstg = (db.Outfile.NewChild(name, true) as GsfOutfile); if (outstg == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; if (!GsfInfileCopy(stg, outstg)) { outstg.Close(); return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } outstg.Close(); return LibmsiResult.LIBMSI_RESULT_SUCCESS; } // TODO: Can we avoid the use of `opaque`? private static LibmsiResult CommitStream(string name, GsfInput stm, object opaque) { LibmsiDatabase db = opaque as LibmsiDatabase; string decname = DecodeStreamName(name); GsfOutput outstm = db.Outfile.NewChild(name, false); if (outstm == null) return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; stm.Seek(0, SeekOrigin.Begin); outstm.Seek(0, SeekOrigin.Begin); if (!stm.Copy(outstm)) { outstm.Close(); return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED; } outstm.Close(); return LibmsiResult.LIBMSI_RESULT_SUCCESS; } // TODO: Can we avoid the use of `param`? private static LibmsiResult PrimaryKeyIterator(LibmsiRecord rec, object param) { MsiPrimaryKeyRecordInfo info = param as MsiPrimaryKeyRecordInfo; int type = rec.GetInt(4); if ((type & MSITYPE_KEY) != 0) { info.N++; if (info.Rec != null) { if (info.N == 1) { string table = rec.GetStringRaw(1); info.Rec.SetString(0, table); } string name = rec.GetStringRaw(3); info.Rec.SetString(info.N, name); } } return LibmsiResult.LIBMSI_RESULT_SUCCESS; } private bool Init(ref Exception err) { if (Flags.HasFlag(LibmsiDbFlags.LIBMSI_DB_FLAGS_CREATE)) { Strings = InitStringTable(out int bytes_per_strref); BytesPerStrref = bytes_per_strref; } else { if (Open() != LibmsiResult.LIBMSI_RESULT_SUCCESS) return false; } MediaTransformOffset = MSI_INITIAL_MEDIA_TRANSFORM_OFFSET; MediaTransformDiskId = MSI_INITIAL_MEDIA_TRANSFORM_DISKID; bool TRACE_ON = false; // TODO: Make configurable if (TRACE_ON && Infile != null) EnumStreamNames(Infile); LibmsiResult ret = StartTransaction(); return ret == LibmsiResult.LIBMSI_RESULT_SUCCESS; } #endregion } }