mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-02-15 13:46:44 +00:00
850 lines
28 KiB
C#
850 lines
28 KiB
C#
/*
|
|
* Implementation of the Microsoft Installer (msi.dll)
|
|
*
|
|
* Copyright 2002-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.Linq;
|
|
using LibGSF.Input;
|
|
using LibMSI.Internal;
|
|
using static LibMSI.LibmsiQuery;
|
|
using static LibMSI.LibmsiTypes;
|
|
using static LibMSI.Internal.LibmsiTable;
|
|
using static LibMSI.Internal.MsiPriv;
|
|
|
|
namespace LibMSI.Views
|
|
{
|
|
internal class LibmsiColumnHashEntry
|
|
{
|
|
public LibmsiColumnHashEntry Next { get; set; }
|
|
|
|
public int Value { get; set; }
|
|
|
|
public int Row { get; set; }
|
|
}
|
|
|
|
internal class LibmsiTableView : LibmsiView
|
|
{
|
|
#region Properties
|
|
|
|
public LibmsiDatabase Database { get; set; }
|
|
|
|
public LibmsiTable Table { get; set; }
|
|
|
|
public LibmsiColumnInfo[] Columns { get; set; }
|
|
|
|
public int NumCols { get; set; }
|
|
|
|
public int RowSize { get; set; }
|
|
|
|
public string Name { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Private constructor
|
|
/// </summary>
|
|
private LibmsiTableView() { }
|
|
|
|
public static LibmsiResult Create(LibmsiDatabase db, string name, out LibmsiView view)
|
|
{
|
|
if (name == szStreams)
|
|
return LibmsiStreamsView.Create(db, out view);
|
|
else if (name == szStorages)
|
|
return LibmsiStorageView.Create(db, out view);
|
|
|
|
LibmsiTableView tv = new LibmsiTableView();
|
|
LibmsiResult r = GetTable(db, name, out LibmsiTable table);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
view = null;
|
|
Console.Error.WriteLine("Table not found");
|
|
return r;
|
|
}
|
|
|
|
// Fill the structure
|
|
tv.Table = table;
|
|
tv.Database = db;
|
|
tv.Columns = tv.Table.ColInfo;
|
|
tv.NumCols = tv.Table.ColCount;
|
|
tv.RowSize = GetRowSize(tv.Table.ColInfo, tv.Table.ColCount, LONG_STR_BYTES);
|
|
tv.Name = name;
|
|
|
|
view = tv;
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Functions
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult FetchInt(int row, int col, out int val)
|
|
{
|
|
val = 0;
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if ((col == 0) || (col > NumCols))
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
// How many rows are there?
|
|
if (row >= Table.RowCount)
|
|
return LibmsiResult.NO_MORE_ITEMS;
|
|
|
|
if (Columns[col - 1].Offset >= RowSize)
|
|
{
|
|
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
|
|
Console.Error.WriteLine($"{this} {Columns}");
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
|
|
int n = BytesPerColumn(Columns[col - 1], LONG_STR_BYTES);
|
|
if (n != 2 && n != 3 && n != 4)
|
|
{
|
|
Console.Error.WriteLine("oops! what is %d bytes per column?\n", n );
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
|
|
int offset = Columns[col-1].Offset;
|
|
val = ReadTableInt(Table.Data, row, offset, n);
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult FetchStream(int row, int col, out GsfInput stm)
|
|
{
|
|
stm = null;
|
|
LibmsiResult r = MsiStreamName(row, out string full_name);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
Console.Error.WriteLine($"Fetching stream, error = {r}");
|
|
return r;
|
|
}
|
|
|
|
string encname = EncodeStreamName(false, full_name).TrimEnd('\0');
|
|
r = Database.GetRawStream(encname, out stm);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
Console.Error.WriteLine($"Fetching stream {full_name}, error = {r}");
|
|
|
|
if (stm != null)
|
|
stm.Name = full_name;
|
|
|
|
return r;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult GetRow(int row, out LibmsiRecord rec)
|
|
{
|
|
rec = null;
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
return MsiViewGetRow(Database, this, row, out rec);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult SetRow(int row, LibmsiRecord rec, int mask)
|
|
{
|
|
LibmsiResult r = LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
// Test if any of the mask bits are invalid
|
|
if (mask >= (1 << NumCols))
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
for (int i = 0; i < NumCols; i++ )
|
|
{
|
|
// Only update the fields specified in the mask
|
|
if ((mask & (1 << i)) == 0)
|
|
continue;
|
|
|
|
bool persistent = (Table.Persistent != LibmsiCondition.LIBMSI_CONDITION_FALSE) && (Table.DataPersistent[row]);
|
|
|
|
// FIXME: should we allow updating keys?
|
|
|
|
int val = 0;
|
|
if (rec.IsNull(i + 1))
|
|
{
|
|
r = GetTableValueFromRecord(rec, i + 1, out val);
|
|
if (MSITYPE_IS_BINARY(Columns[i].Type))
|
|
{
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
r = rec.GetGsfInput(i + 1, out GsfInput stm);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
r = MsiStreamName(row, out string stname);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
r = AddStream(Database, stname, stm);
|
|
if ( r != LibmsiResult.LIBMSI_RESULT_SUCCESS )
|
|
return r;
|
|
}
|
|
else if ((Columns[i].Type & MSITYPE_STRING) != 0)
|
|
{
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
string sval = rec.GetStringRaw(i + 1);
|
|
val = Database.Strings.AddString(sval, -1, 1, persistent ? StringPersistence.StringPersistent : StringPersistence.StringNonPersistent );
|
|
}
|
|
else
|
|
{
|
|
FetchInt(row, i + 1, out int x);
|
|
if (val == x)
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
}
|
|
|
|
r = SetInt(row, i + 1, val);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult Execute(LibmsiRecord record) => LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult Close() => LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult GetDimensions(out int rows, out int cols)
|
|
{
|
|
rows = 0;
|
|
cols = NumCols;
|
|
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
rows = Table.RowCount;
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult GetColumnInfo(int n, out string name, out int type, out bool temporary, out string table_name)
|
|
{
|
|
name = null; type = 0; temporary = false; table_name = null;
|
|
if (n == 0 || n > NumCols)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
name = Columns[n - 1].ColName;
|
|
if (name == null)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
table_name = Columns[n - 1].TableName;
|
|
if (table_name == null)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
type = Columns[n - 1].Type;
|
|
temporary = Columns[n - 1].Temporary;
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult InsertRow(LibmsiRecord rec, int row, bool temporary)
|
|
{
|
|
// Check that the key is unique - can we find a matching row?
|
|
LibmsiResult r = TableValidateNew(rec, out _);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
if (row == -1)
|
|
row = FindInsertIndex(rec);
|
|
|
|
r = TableCreateNewRow(ref row, temporary);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
// Shift the rows to make room for the new row
|
|
for (int i = Table.RowCount - 1; i > row; i--)
|
|
{
|
|
byte[] temp = new byte[RowSize];
|
|
Array.Copy(Table.Data[i - 1], Table.Data[i], RowSize);
|
|
Table.Data[i - 1] = Enumerable.Repeat<byte>(0x00, RowSize).ToArray();
|
|
Table.DataPersistent[i] = Table.DataPersistent[i - 1];
|
|
}
|
|
|
|
// Re-set the persistence flag
|
|
Table.DataPersistent[row] = !temporary;
|
|
return SetRow(row, rec, (1 << NumCols) - 1);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult DeleteRow(int row)
|
|
{
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
LibmsiResult r = GetDimensions(out int num_rows, out int num_cols);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
if (row >= num_rows)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
num_rows = Table.RowCount;
|
|
Table.RowCount--;
|
|
|
|
// Reset the hash tables
|
|
for (int i = 0; i < NumCols; i++)
|
|
{
|
|
Columns[i].HashTable = null;
|
|
}
|
|
|
|
for (int i = row + 1; i < num_rows; i++)
|
|
{
|
|
Array.Copy(Table.Data[i], Table.Data[i - 1], RowSize);
|
|
Table.DataPersistent[i - 1] = Table.DataPersistent[i];
|
|
}
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult Delete()
|
|
{
|
|
Table = null;
|
|
Columns = null;
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult FindMatchingRows(int col, int val, out int row, ref LibmsiColumnHashEntry handle)
|
|
{
|
|
row = 0;
|
|
LibmsiColumnHashEntry entry;
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if (col == 0 || col > NumCols)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if (Columns[col - 1].HashTable == null)
|
|
{
|
|
int num_rows = Table.RowCount;
|
|
if (Columns[col - 1].Offset >= RowSize)
|
|
{
|
|
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
|
|
Console.Error.WriteLine($"{this} {Columns}");
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
|
|
// Allocate contiguous memory for the table and its entries so we
|
|
// don't have to do an expensive cleanup
|
|
LibmsiColumnHashEntry[] hash_table = new LibmsiColumnHashEntry[num_rows];
|
|
Columns[col - 1].HashTable = hash_table;
|
|
|
|
int new_entry = 0;
|
|
for (int i = 0; i < num_rows; i++, new_entry++)
|
|
{
|
|
if (FetchInt(i, col, out int row_value) != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
continue;
|
|
|
|
hash_table[new_entry] = new LibmsiColumnHashEntry
|
|
{
|
|
Next = null,
|
|
Value = row_value,
|
|
Row = i,
|
|
};
|
|
|
|
if (hash_table[row_value] != null)
|
|
{
|
|
LibmsiColumnHashEntry prev_entry = hash_table[row_value];
|
|
while (prev_entry.Next != null)
|
|
{
|
|
prev_entry = prev_entry.Next;
|
|
}
|
|
|
|
prev_entry.Next = hash_table[new_entry];
|
|
}
|
|
else
|
|
{
|
|
hash_table[row_value] = hash_table[new_entry];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (handle == null)
|
|
entry = Columns[col - 1].HashTable[val];
|
|
else
|
|
entry = handle.Next;
|
|
|
|
while (entry != null && entry.Value != val)
|
|
{
|
|
entry = entry.Next;
|
|
}
|
|
|
|
handle = entry;
|
|
if (entry == null)
|
|
return LibmsiResult.NO_MORE_ITEMS;
|
|
|
|
row = entry.Row;
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override int AddRef()
|
|
{
|
|
for (int i = 0; i < Table.ColCount; i++)
|
|
{
|
|
if ((Table.ColInfo[i].Type & MSITYPE_TEMPORARY) != 0)
|
|
Table.ColInfo[i].RefCount++;
|
|
}
|
|
|
|
return ++Table.RefCount;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult RemoveColumn(string table, int number)
|
|
{
|
|
LibmsiRecord rec = LibmsiRecord.Create(2);
|
|
if (rec == null)
|
|
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
|
|
|
|
rec.SetString(1, table);
|
|
rec.SetInt(2, number);
|
|
|
|
LibmsiResult r = Create(Database, szColumns, out LibmsiView columns);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
r = FindRow(columns as LibmsiTableView, rec, out int row, out _);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
columns.Delete();
|
|
return r;
|
|
}
|
|
|
|
r = (columns as LibmsiTableView).DeleteRow(row);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
columns.Delete();
|
|
return r;
|
|
}
|
|
|
|
UpdateTableColumns(Database, table);
|
|
columns.Delete();
|
|
return r;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override int Release()
|
|
{
|
|
int ref_count = Table.RefCount;
|
|
for (int i = 0; i < Table.ColCount; i++)
|
|
{
|
|
if ((Table.ColInfo[i].Type & MSITYPE_TEMPORARY) != 0)
|
|
{
|
|
ref_count = --Table.ColInfo[i].RefCount;
|
|
if (ref_count == 0)
|
|
{
|
|
LibmsiResult r = RemoveColumn(Table.ColInfo[i].TableName, Table.ColInfo[i].Number);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ref_count = --Table.RefCount;
|
|
if (ref_count == 0)
|
|
{
|
|
if (Table.RowCount == 0)
|
|
Delete();
|
|
}
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult AddColumn(string table, int number, string column, int type, bool hold)
|
|
{
|
|
LibmsiRecord rec = LibmsiRecord.Create(4);
|
|
if (rec == null)
|
|
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
|
|
|
|
rec.SetString(1, table);
|
|
rec.SetInt(2, number);
|
|
rec.SetString(3, column);
|
|
rec.SetInt(4, type);
|
|
|
|
LibmsiResult r = InsertRow(rec, -1, false);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
UpdateTableColumns(Database, table);
|
|
|
|
if (!hold)
|
|
return r;
|
|
|
|
LibmsiTable msitable = FindCachedTable(Database, table);
|
|
for (int i = 0; i < msitable.ColCount; i++)
|
|
{
|
|
if (msitable.ColInfo[i].ColName == column)
|
|
{
|
|
msitable.ColInfo[i].RefCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override LibmsiResult Drop()
|
|
{
|
|
LibmsiResult r;
|
|
for (int i = Table.ColCount - 1; i >= 0; i--)
|
|
{
|
|
r = RemoveColumn(Table.ColInfo[i].TableName, Table.ColInfo[i].Number);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
}
|
|
|
|
LibmsiRecord rec = LibmsiRecord.Create(1);
|
|
if (rec == null)
|
|
return LibmsiResult.LIBMSI_RESULT_OUTOFMEMORY;
|
|
|
|
rec.SetString(1, Name);
|
|
|
|
r = Create(Database, szTables, out LibmsiView tables);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return r;
|
|
|
|
r = FindRow((tables as LibmsiTableView), rec, out int row, out _);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
tables.Delete();
|
|
return r;
|
|
}
|
|
|
|
r = tables.DeleteRow(row);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
tables.Delete();
|
|
return r;
|
|
}
|
|
|
|
tables.Delete();
|
|
return r;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
private LibmsiResult MsiStreamName(int row, out string pstname)
|
|
{
|
|
int len = Name.Length + 1;
|
|
string stname = Name;
|
|
|
|
LibmsiResult r;
|
|
for (int i = 0; i < NumCols; i++)
|
|
{
|
|
int type = Columns[i].Type;
|
|
if ((type & MSITYPE_KEY) != 0)
|
|
{
|
|
r = FetchInt(row, i + 1, out int ival);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
pstname = null;
|
|
return r;
|
|
}
|
|
|
|
string sval = string.Empty;
|
|
if ((Columns[i].Type & MSITYPE_STRING) != 0)
|
|
{
|
|
sval = Database.Strings.LookupId(ival );
|
|
if (sval == null)
|
|
{
|
|
pstname = null;
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int n = BytesPerColumn(Columns[i], LONG_STR_BYTES);
|
|
switch (n)
|
|
{
|
|
case 2:
|
|
sval = (ival - 0x8000).ToString();
|
|
break;
|
|
case 4:
|
|
sval = (ival ^ 0x80000000).ToString();
|
|
break;
|
|
default:
|
|
Console.Error.WriteLine($"Oops - unknown column width {n}");
|
|
pstname = null;
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
}
|
|
|
|
len += szDot.Length + sval.Length;
|
|
stname += szDot + sval;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pstname = stname;
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
private LibmsiResult SetInt(int row, int col, int val)
|
|
{
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if (col == 0 || col > NumCols)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if (row >= Table.RowCount)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
if (Columns[col - 1].Offset >= RowSize)
|
|
{
|
|
Console.Error.WriteLine($"Stuffed up {Columns[col - 1].Offset} >= {RowSize}");
|
|
Console.Error.WriteLine($"{this} {Columns}");
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
|
|
Columns[col - 1].HashTable = null;
|
|
|
|
int n = BytesPerColumn(Columns[col - 1], LONG_STR_BYTES);
|
|
if (n != 2 && n != 3 && n != 4)
|
|
{
|
|
Console.Error.WriteLine($"Oops! what is {n} bytes per column?");
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
|
|
int offset = Columns[col - 1].Offset;
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
Table.Data[row][offset + i] = (byte)((val >> i * 8) & 0xff);
|
|
}
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
private LibmsiResult GetTableValueFromRecord(LibmsiRecord rec, int iField, out int pvalue)
|
|
{
|
|
LibmsiResult r;
|
|
|
|
pvalue = 0;
|
|
if (iField <= 0 || iField > NumCols || rec.IsNull(iField))
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
LibmsiColumnInfo columninfo = Columns[iField - 1];
|
|
if (MSITYPE_IS_BINARY(columninfo.Type))
|
|
{
|
|
pvalue = 1; // Refers to the first key column
|
|
}
|
|
else if ((columninfo.Type & MSITYPE_STRING) != 0)
|
|
{
|
|
string sval = rec.GetStringRaw(iField);
|
|
if (sval != null)
|
|
{
|
|
r = Database.Strings.IdFromStringUTF8(sval, out pvalue);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return LibmsiResult.LIBMSI_RESULT_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
pvalue = 0;
|
|
}
|
|
}
|
|
else if (BytesPerColumn(columninfo, LONG_STR_BYTES) == 2)
|
|
{
|
|
pvalue = 0x8000 + rec.GetInt(iField);
|
|
if ((pvalue & 0xffff0000) != 0)
|
|
{
|
|
Console.Error.WriteLine($"Field {iField} value {pvalue - 0x8000} out of range");
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int ival = rec.GetInt(iField);
|
|
pvalue = (int)(ival ^ 0x80000000);
|
|
}
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
private LibmsiResult TableCreateNewRow(ref int num, bool temporary)
|
|
{
|
|
if (Table == null)
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_PARAMETER;
|
|
|
|
byte[] row = new byte[RowSize];
|
|
int data_ptr = 0; // Table.Data[0][]
|
|
int data_persist_ptr = 0; // Table.DataPersistent[0];
|
|
if (num == -1)
|
|
num = Table.RowCount;
|
|
|
|
int sz = Table.RowCount + 1;
|
|
if (Table.Data[data_ptr] != null)
|
|
{
|
|
byte[] p = Table.Data[data_ptr];
|
|
Array.Resize(ref p, sz);
|
|
Table.Data[data_ptr] = p;
|
|
}
|
|
else
|
|
{
|
|
Table.Data[data_ptr] = new byte[sz];
|
|
}
|
|
|
|
sz = Table.RowCount + 1;
|
|
if (Table.DataPersistent != null)
|
|
{
|
|
bool[] b = Table.DataPersistent;
|
|
Array.Resize(ref b, sz);
|
|
Table.DataPersistent = b;
|
|
}
|
|
else
|
|
{
|
|
Table.DataPersistent = new bool[sz];
|
|
}
|
|
|
|
Table.Data[data_ptr + Table.RowCount] = row;
|
|
Table.DataPersistent[data_persist_ptr + Table.RowCount] = !temporary;
|
|
|
|
Table.RowCount++;
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
private LibmsiResult TableValidateNew(LibmsiRecord rec, out int column)
|
|
{
|
|
// Check there's no null values where they're not allowed
|
|
for (int i = 0; i < NumCols; i++ )
|
|
{
|
|
if ((Columns[i].Type & MSITYPE_NULLABLE) != 0)
|
|
continue;
|
|
|
|
if (MSITYPE_IS_BINARY(Columns[i].Type))
|
|
{
|
|
// Skip binary columns
|
|
}
|
|
else if ((Columns[i].Type & MSITYPE_STRING) != 0)
|
|
{
|
|
string str = rec.GetStringRaw(i + 1);
|
|
if (str == null || str[0] == 0)
|
|
{
|
|
column = i;
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_DATA;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int n = rec.GetInt(i + 1);
|
|
if (n == LIBMSI_NULL_INT)
|
|
{
|
|
column = i;
|
|
return LibmsiResult.LIBMSI_RESULT_INVALID_DATA;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check there's no duplicate keys
|
|
LibmsiResult r = FindRow(this, rec, out int row, out column);
|
|
if (r == LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return LibmsiResult.LIBMSI_RESULT_FUNCTION_FAILED;
|
|
|
|
return LibmsiResult.LIBMSI_RESULT_SUCCESS;
|
|
}
|
|
|
|
private int CompareRecord(int row, LibmsiRecord rec)
|
|
{
|
|
for (int i = 0; i < NumCols; i++ )
|
|
{
|
|
if ((Columns[i].Type & MSITYPE_KEY) == 0)
|
|
continue;
|
|
|
|
LibmsiResult r = GetTableValueFromRecord(rec, i + 1, out int ivalue);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
return 1;
|
|
|
|
r = FetchInt(row, i + 1, out int x);
|
|
if (r != LibmsiResult.LIBMSI_RESULT_SUCCESS)
|
|
{
|
|
Console.Error.WriteLine($"FetchInt should not fail here {r}");
|
|
return -1;
|
|
}
|
|
|
|
if (ivalue > x)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (ivalue == x)
|
|
{
|
|
if (i < NumCols - 1)
|
|
continue;
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
private int FindInsertIndex(LibmsiRecord rec)
|
|
{
|
|
int low = 0, high = Table.RowCount - 1;
|
|
while (low <= high)
|
|
{
|
|
int idx = (low + high) / 2;
|
|
int c = CompareRecord(idx, rec);
|
|
|
|
if (c < 0)
|
|
high = idx - 1;
|
|
else if (c > 0)
|
|
low = idx + 1;
|
|
else
|
|
return idx;
|
|
}
|
|
|
|
return high + 1;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |