//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1994, 1996 by Borland International, All Rights Reserved
//
/// \file
/// General Registry access & registration implementation
//----------------------------------------------------------------------------
#include <owl/pch.h>
 
#include <owl/registry.h>
#include <owl/system.h>
#include <owl/pointer.h>
#include <utility>
#include <type_traits>
#include <vector>
 
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
#endif
 
using namespace std;
 
namespace owl {
 
//
/// Special predefined root key used by shell and OLE applications (`HKEY_CLASSES_ROOT`).
/// Registry entries subordinate to this key define types (or classes) of documents and the
/// properties associated with those types. Shell and COM applications use the information
/// stored under this key.
/// \sa https://docs.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key
//
auto TRegKey::GetClassesRoot() -> TRegKey&
{
  static TRegKey ClassesRoot(HKEY_CLASSES_ROOT, true, _T("HKEY_CLASSES_ROOT"));
  return ClassesRoot;
}
 
//
/// Commonly used subkey by shell and OLE applications (`HKEY_CLASSES_ROOT\CLSID`).
//
auto TRegKey::GetClassesRootClsid() -> TRegKey&
{
  static TRegKey ClassesRootClsid(GetClassesRoot(), _T("CLSID"), KEY_ALL_ACCESS, NoCreate);
  return ClassesRootClsid;
}
 
//
/// Special predefined root key containing information about the current hardware profile of the
/// local computer system (`HKEY_CURRENT_CONFIG`).
/// The information under this key describes only the differences between the current hardware
/// configuration and the standard configuration. Information about the standard hardware
/// configuration is stored under the Software and System keys of `HKEY_LOCAL_MACHINE`.
/// \sa https://technet.microsoft.com/library/286f12b7-265b-4632-a4e1-987d025023e6
//
auto TRegKey::GetCurrentConfig() -> TRegKey&
{
  static TRegKey CurrentConfig(HKEY_CURRENT_CONFIG, true, _T("HKEY_CURRENT_CONFIG"));
  return CurrentConfig;
}
 
//
/// Special predefined root key defining the preferences of the current user (`HKEY_CURRENT_USER`).
/// These preferences include the settings of environment variables, data about program groups,
/// colors, printers, network connections, and application preferences. This key makes it easier to
/// establish the current user's settings; the key maps to the current user's branch in HKEY_USERS.
/// Under this key, software vendors store the current user-specific preferences to be used within
/// their applications. Microsoft, for example, creates the `HKEY_CURRENT_USER\Software\Microsoft`
/// key for its applications to use, with each application creating its own subkey under the
/// Microsoft key.
/// \sa https://technet.microsoft.com/library/6b6d2dcc-a083-4c49-9000-6f1324b20877
//
auto TRegKey::GetCurrentUser() -> TRegKey&
{
  static TRegKey CurrentUser(HKEY_CURRENT_USER, true, _T("HKEY_CURRENT_USER"));
  return CurrentUser;
}
 
//
/// Special predefined root key defining the physical state of the computer (`HKEY_LOCAL_MACHINE`).
/// Includes data about the bus type, system memory, and installed hardware and software. It
/// contains subkeys that hold current configuration data, including Plug and Play information
/// (the Enum branch, which includes a complete list of all hardware that has ever been on the
/// system), network logon preferences, network security information, software-related information
/// (such as server names and the location of the server), and other system information.
/// \sa https://technet.microsoft.com/library/f4704e81-0b33-4ecd-b2e4-e41b50bb758c
//
auto TRegKey::GetLocalMachine() -> TRegKey&
{
  static TRegKey LocalMachine(HKEY_LOCAL_MACHINE, true, _T("HKEY_LOCAL_MACHINE"));
  return LocalMachine;
}
 
//
/// Special predefined root key used to obtain performance data (`HKEY_PERFORMANCE_DATA`).
/// The data is not actually stored in the registry; the registry functions cause the system to
/// collect the data from its source.
//
auto TRegKey::GetPerformanceData() -> TRegKey&
{
  static TRegKey PerformanceData(HKEY_PERFORMANCE_DATA, true, _T("HKEY_PERFORMANCE_DATA"));
  return PerformanceData;
}
 
//
/// Special predefined root key defining the default user configuration (`HKEY_USERS`).
/// Registry entries subordinate to this key define the default user configuration for new users
/// on the local computer and the user configuration for the current user.
/// \sa TRegKey::GetCurrentUser
//
auto TRegKey::GetUsers() -> TRegKey&
{
  static TRegKey Users(HKEY_USERS, true, _T("HKEY_USERS"));
  return Users;
}
 
//
/// Creates or opens a key given a base key and a subkey name.
/// The user can specify the desired access rights to the key and provide an ok-to-create or
/// open-only indicator. To check whether a subkey exists before creating a TRegKey, you can call
/// TRegKey::HasSubkey on the base key.
///
/// \returns Throws TXRegistry on failure.
///
/// \note Passing an empty string for the key name is allowed but not very useful. It will just
/// give you a new handle to the base key. In most cases, you should use the aliasing constructor
/// instead; TRegKey::TRegKey(THandle aliasKey, bool shouldClose, tstring keyName).
//
TRegKey::TRegKey(THandle baseKey, tstring keyName, REGSAM samDesired, TCreateOK createOK)
  : Key{}, Name{move(keyName)}, SubkeyCount{}, ValueCount{}, ShouldClose{true}
{
  // Use a local lambda to handle SEH system exceptions (not compatible with C++ exception unwinding).
  // A system SEH exception with code EXCEPTION_ACCESS_VIOLATION is translated to ERROR_ACCESS_DENIED.
  // Any other SEH exception is not handled.
  //
  const auto init = [&]() noexcept -> long // Error code.
  {
    __try
    {
      if (createOK == CreateOK)
      {
        auto disposition = DWORD{};
        const auto r = ::RegCreateKeyEx(baseKey, Name.c_str(), 0 /* Reserved */, nullptr /* Class */,
          REG_OPTION_NON_VOLATILE, samDesired, nullptr /* SecurityAttributes */, &Key, &disposition);
        if (r != ERROR_SUCCESS) return r;
      }
      else
      {
        const auto r = ::RegOpenKeyEx(baseKey, Name.c_str(), 0 /* Options */, samDesired, &Key);
        if (r != ERROR_SUCCESS) return r;
      }
      const auto r = QueryInfo(nullptr, nullptr, &SubkeyCount, nullptr, nullptr, &ValueCount, nullptr, nullptr, nullptr, nullptr);
      if (r != ERROR_SUCCESS && r != ERROR_INSUFFICIENT_BUFFER) return r;
      return ERROR_SUCCESS;
    }
    __except (true)
    {
      const auto x = GetExceptionCode();
      if (x == EXCEPTION_ACCESS_VIOLATION) return ERROR_ACCESS_DENIED;
      RaiseException(x, 0, 0, 0);
      return -1; // Never executed.
    }
  };
  const auto r = init();
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TRegKey: Init failed"), Name, r};
  CHECK(Key); // Postcondition.
}
 
//
/// LPCTSTR overload.
/// Forwards to TRegKey::TRegKey(THandle baseKey, tstring keyName, REGSAM, TCreateOK).
/// \note If you pass `nullptr` for the key name, it will be translated to an empty string.
//
TRegKey::TRegKey(THandle baseKey, LPCTSTR keyName, REGSAM samDesired, TCreateOK createOK)
  : TRegKey{baseKey, keyName ? keyName : tstring{}, samDesired, createOK}
{
  CHECK(Key); // Postcondition.
}
 
//
/// Constructs a key given the current position of a TRegKeyIterator.
/// \returns Throws TXRegistry on failure.
//
TRegKey::TRegKey(const TRegKeyIterator& iter, REGSAM samDesired)
  : Key{}, Name{}, SubkeyCount{}, ValueCount{}, ShouldClose{true}
{
  // Use a local lambda to handle SEH system exceptions (not compatible with C++ exception unwinding).
  // A system SEH exception with code EXCEPTION_ACCESS_VIOLATION is translated to ERROR_ACCESS_DENIED.
  // Any other SEH exception is not handled.
  //
  const auto init = [&]() noexcept -> long // Error code.
  {
    __try
    {
      tchar name[_MAX_PATH + 1];
      const auto re = iter.BaseKey().EnumKey(iter.Current(), &name[0], COUNTOF(name));
      if (re != ERROR_SUCCESS) return re;
      Name = name;
      const auto ro = ::RegOpenKeyEx(iter.BaseKey(), Name.c_str(), 0, samDesired, &Key);
      if (ro != ERROR_SUCCESS) return ro;
      const auto rq = QueryInfo(nullptr, nullptr, &SubkeyCount, nullptr, nullptr, &ValueCount, nullptr, nullptr, nullptr, nullptr);
      if (rq != ERROR_SUCCESS && rq != ERROR_INSUFFICIENT_BUFFER) return rq;
      return ERROR_SUCCESS;
    }
    __except (true)
    {
      const auto x = GetExceptionCode();
      if (x == EXCEPTION_ACCESS_VIOLATION) return ERROR_ACCESS_DENIED;
      RaiseException(x, 0, 0, 0);
      return -1; // Never executed.
    }
  };
  const auto r = init();
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TRegKey: Init failed"), Name, r};
  CHECK(Key); // Postcondition.
}
 
//
//
/// Constructs a TRegKey object around an existing key.
/// Commonly used for wrapping the preexisting pseudo-keys, such as HKEY_CURRENT_USER.
/// If `true` is passed for parameter `shouldClose`, then the object takes ownership of the handle,
/// and the handle will be closed when the object is destructed.
///
/// \returns Throws TXRegistry on failure.
///
/// \sa The preexisting pseudo-keys can be accessed by the static member functions
/// TRegKey::GetClassesRoot, TRegKey::GetClassesRootClsid, TRegKey::GetCurrentConfig,
/// TRegKey::GetCurrentUser, TRegKey::GetLocalMachine, TRegKey::GetPerformanceData and
/// TRegKey::GetUsers.
//
TRegKey::TRegKey(THandle aliasKey, bool shouldClose, tstring keyName)
  : Key{aliasKey}, Name{move(keyName)}, SubkeyCount{}, ValueCount{}, ShouldClose{shouldClose}
{
  // Use a local lambda to handle SEH system exceptions (not compatible with C++ exception unwinding).
  // A system SEH exception with code EXCEPTION_ACCESS_VIOLATION is translated to ERROR_ACCESS_DENIED.
  // Any other SEH exception is not handled.
  //
  const auto init = [&]() noexcept -> long // Error code.
  {
    __try
    {
      const auto r = QueryInfo(nullptr, nullptr, &SubkeyCount, nullptr, nullptr, &ValueCount, nullptr, nullptr, nullptr, nullptr);
      if (r != ERROR_SUCCESS && r != ERROR_INSUFFICIENT_BUFFER) return r;
      return ERROR_SUCCESS;
    }
    __except (true)
    {
      const auto x = GetExceptionCode();
      if (x == EXCEPTION_ACCESS_VIOLATION) return ERROR_ACCESS_DENIED;
      RaiseException(x, 0, 0, 0);
      return -1; // Never executed.
    }
  };
  const auto r = init();
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TRegKey: Init failed"), Name, r};
  CHECK(Key); // Postcondition.
}
 
//
/// LPCTSTR overload.
/// Forwards to TRegKey::TRegKey(THandle, bool shouldClose, tstring keyName).
/// \note If you pass `nullptr` for the key name, it will be translated to an empty string.
//
TRegKey::TRegKey(THandle aliasKey, bool shouldClose, LPCTSTR keyName)
  : TRegKey{aliasKey, shouldClose, keyName ? keyName : tstring{}}
{
  CHECK(Key); // Postcondition.
}
 
//
/// Move constructor; steals the innards of the given object.
//
TRegKey::TRegKey(TRegKey&& v) noexcept
  : Key{}, Name{}, SubkeyCount{}, ValueCount{}, ShouldClose{}
{
  using std::swap;
  swap(Name, v.Name);
  swap(Key, v.Key);
  swap(SubkeyCount, v.SubkeyCount);
  swap(ValueCount, v.ValueCount);
  swap(ShouldClose, v.ShouldClose);
  WARN(!Key, _T("TRegKey::TRegKey: Moved from invalid key"));
}
 
//
/// Closes the underlying key handle, if owned.
//
TRegKey::~TRegKey()
{
  if (Key && ShouldClose)
  {
    [[maybe_unused]] const auto r = ::RegCloseKey(Key);
    WARN(r != ERROR_SUCCESS, _T("TRegKey::~TRegKey: RegCloseKey failed, error: ") << r);
  }
}
 
//
/// Retrieves information about this registry key.
/// Wrapper for RegQueryInfoKey in the Windows API.
/// \sa https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeya
//
long TRegKey::QueryInfo(LPTSTR class_, DWORD* classSize,
  DWORD* subkeyCount,  DWORD* maxSubkeyNameSize, DWORD* maxSubkeyClassSize, 
  DWORD* valueCount, DWORD* maxValueNameSize, DWORD* maxValueDataSize,
  DWORD* securityDescriptorSize, PFILETIME lastWriteTime) const
{
  return ::RegQueryInfoKey(Key,
    class_,
    classSize,
    nullptr, // Reserved, and must be NULL.
    subkeyCount,
    maxSubkeyNameSize,
    maxSubkeyClassSize,
    valueCount,
    maxValueNameSize,
    maxValueDataSize,
    securityDescriptorSize,
    lastWriteTime);
}
 
//
/// Functional-style overload.
/// \returns A data structure containing information about the key.
/// If an error occurs, TXRegistry is thrown.
//
auto TRegKey::QueryInfo() const -> TInfo
{
  auto i = TInfo{};
  auto classSize = DWORD{MAX_PATH};
  i.Class.resize(classSize); // Should include space for the null-terminator, according to the Windows API documentation.
  const auto p = const_cast<LPTSTR>(i.Class.data()); // Workaround for pre-C++17 compilers.
  const auto r = QueryInfo(
    p,
    &classSize,
    &i.SubkeyCount,
    &i.MaxSubkeyNameSize,
    &i.MaxSubkeyClassSize,
    &i.ValueCount,
    &i.MaxValueNameSize,
    &i.MaxValueDataSize,
    &i.SecurityDescriptorSize,
    &i.LastWriteTime);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::QueryInfo failed"), *this, r};
  i.Class.resize(classSize); // Returned classSize does not include the null-terminator, according to the Windows API documentation.
  i.Class.shrink_to_fit();
  return i;
}
 
//
/// Returns true if this key has a subkey with the given name.
/// If an error occurs, e.g. access is denied, it is assumed the subkey exists.
/// The key name may include a path with subkey names separated by backslash, e.g. "Software\\Program".
//
auto TRegKey::HasSubkey(const tstring& keyName) const -> bool
{
  PRECONDITION(Key);
  auto subkey = HKEY{};
  const auto r = ::RegOpenKeyEx(Key, keyName.c_str(), 0, KEY_READ, &subkey);
  if (subkey)
    ::RegCloseKey(subkey);
  return r != ERROR_FILE_NOT_FOUND;
}
 
//
/// Returns the subkey with the given name, if it exists.
/// If the key does not exist, std::nullopt is returned.
/// If access is denied, or any other error occurs, a TXRegistry exception is thrown.
/// The key name may include a path with subkey names separated by backslash, e.g. "Software\\Program".
///
/// \param keyName is the name of the key.
/// \param samDesired A mask that specifies the desired access rights to the key to be opened. 
/// For possible values of this parameter, see the Windows API documentation.
/// 
/// \sa https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regopenkeyexa
/// \sa https://docs.microsoft.com/en-gb/windows/win32/sysinfo/registry-key-security-and-access-rights
//
auto TRegKey::GetSubkey(const tstring& keyName, REGSAM samDesired) const -> optional<TRegKey>
{
  PRECONDITION(Key);
  auto subkey = HKEY{};
  const auto r = ::RegOpenKeyEx(Key, keyName.c_str(), 0, samDesired, &subkey);
  if (r != ERROR_SUCCESS && r != ERROR_FILE_NOT_FOUND) throw TXRegistry{_T("TRegKey::GetSubKey: RegOpenKeyEx failed"), keyName, r};
  return subkey ? make_optional<TRegKey>(subkey, true, keyName) : nullopt; // TRegKey takes ownership.
}
 
//
/// Return true if this key has a value with the given name.
/// If the given name is empty, the default value is assumed, which always exists.
/// If an error occurs, e.g. access is denied, it is assumed the value exists.
//
auto TRegKey::HasValue(const tstring& valueName) const -> bool
{
  PRECONDITION(Key);
  return QueryValue(valueName, nullptr, nullptr, nullptr) != ERROR_FILE_NOT_FOUND;
}
 
//
/// Returns the value with the given name, if it exists within this key.
/// If the value does not exist, std::nullopt is returned.
/// If the given name is empty, the default value is assumed, which always exists.
/// If access is denied, or any other error occurs, a TXRegistry exception is thrown.
//
auto TRegKey::GetValue(const tstring& valueName) const -> optional<TRegValue>
{
  PRECONDITION(Key);
  return HasValue(valueName) ? make_optional<TRegValue>(*this, valueName) : nullopt;
}
 
//
/// Completely eliminates a child key, including any of its subkeys. 
//
long TRegKey::NukeKey(LPCTSTR subKeyName)
{
  // DeleteKey fails if a key has subkeys, so we must nuke them first.
  //
  const auto nukeSubkeys = [&](LPCTSTR keyName)
  {
    auto key = GetSubkey(keyName, KEY_ALL_ACCESS);
    if (!key) return ERROR_FILE_NOT_FOUND;
    auto subkeys = vector<tstring>{};
    for (auto i = TRegKeyIterator{*key}; i; ++i)
      subkeys.push_back(TRegKey{i}.GetName());
    for (const auto& s : subkeys)
    {
      const auto r = key->NukeKey(s);
      if (r != ERROR_SUCCESS) return r;
    }
    return ERROR_SUCCESS;
  };
  const auto r = nukeSubkeys(subKeyName);
  if (r != ERROR_SUCCESS) return r;
  return DeleteKey(subKeyName);
}
 
//
/// Retrieves a copy of the security descriptor protecting the specified open registry key.
///
/// To read the owner, group, or discretionary access control list (DACL) from the key's security
/// descriptor, the calling process must have been granted READ_CONTROL access when the handle was
/// opened. To get READ_CONTROL access, the caller must be the owner of the key or the key's DACL
/// must grant the access.
///
/// To read the system access control list (SACL) from the security descriptor, the calling process
/// must have been granted ACCESS_SYSTEM_SECURITY access when the key was opened. The correct way
/// to get this access is to enable the SE_SECURITY_NAME privilege in the caller's current token,
/// open the handle for ACCESS_SYSTEM_SECURITY access, and then disable the privilege.
///
/// \param key is the key for which security information is sought.
/// \param infoRequested indicates the requested security information.
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-reggetkeysecurity">
/// RegGetKeySecurity</a> in the Windows API.
//
TRegKey::TSecurityDescriptor::TSecurityDescriptor(const TRegKey& key, SECURITY_INFORMATION infoRequested)
  : Data(key.QueryInfo().SecurityDescriptorSize)
{
  auto n = static_cast<DWORD>(Data.size());
  const auto r = key.GetSecurity(infoRequested, Data.data(), &n);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TSecurityDescriptor::TSecurityDescriptor: GetSecurity failed"), key, r};
  CHECK(n == static_cast<DWORD>(Data.size()));
}
 
//
/// Determines whether the components of the security descriptor are valid.
/// \returns `true` if the security descriptor is valid.
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-isvalidsecuritydescriptor">
/// IsValidSecurityDescriptor</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::IsValid() const noexcept -> bool
{ 
  return ::IsValidSecurityDescriptor(const_cast<SECURITY_DESCRIPTOR*>(GetData())) != FALSE; 
}
 
//
/// Retrieves the control information from the security descriptor.
/// Call TSecurityDescriptor::GetRevision to get the revision number of the structure.
///
/// \returns If successful, the control information is returned.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorcontrol">
/// GetSecurityDescriptorControl</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetControl() const -> SECURITY_DESCRIPTOR_CONTROL
{
  auto c = SECURITY_DESCRIPTOR_CONTROL{};
  auto revision = DWORD{};
  const auto ok = ::GetSecurityDescriptorControl(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &c, &revision);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetControl: GetSecurityDescriptorControl failed"), tstring{}, static_cast<long>(::GetLastError())};
  return c;
}
 
//
/// Returns the length, in bytes, of the security descriptor.
/// The length includes the length of all associated structures.
/// 
/// \returns If successful, the length is returned.
/// \exception TXRegistry is thrown if the security descriptor is invalid.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorlength">
/// GetSecurityDescriptorLength</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetLength() const -> DWORD
{
  if (!IsValid()) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetLength failed"), tstring{}};
  return ::GetSecurityDescriptorLength(const_cast<SECURITY_DESCRIPTOR*>(GetData()));
}
 
//
/// Retrieves the discretionary access control list (DACL) from the security descriptor.
/// Call TSecurityDescriptor::IsDaclDefaulted to determine whether the DACL was set by default
/// means or user specified.
///
/// \returns If this security descriptor contains a DACL, a `PACL` is returned pointing to the
/// DACL. Note that `nullptr` is a valid value for the `PACL`. A `nullopt` is returned, if a DACL
/// is not present.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note To simply determine whether a DACL is present in the security descriptor, you can call
/// TSecurityDescriptor::IsDaclPresent.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptordacl">
/// GetSecurityDescriptorDacl</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetDacl() const -> optional<PACL>
{
  auto daclPresent = BOOL{};
  auto dacl = PACL{};
  auto daclDefaulted = BOOL{};
  const auto ok = ::GetSecurityDescriptorDacl(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &daclPresent, &dacl, &daclDefaulted);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetDacl: GetSecurityDescriptorDacl failed"), tstring{}, static_cast<long>(::GetLastError())};
  return daclPresent ? make_optional(dacl) : nullopt;
}
 
//
/// Retrieves the primary group information from the security descriptor.
/// Call TSecurityDescriptor::IsGroupDefaulted to determine whether the group was set by default
/// means or user specified.
///
/// \returns If the security descriptor contains a primary group, a `PSID` pointing to the security
/// identifier that identifies the primary group is returned. Otherwise, `nullptr` is returned.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorgroup">
/// GetSecurityDescriptorGroup</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetGroup() const -> PSID
{
  auto group = PSID{};
  auto groupDefaulted = BOOL{};
  const auto ok = ::GetSecurityDescriptorGroup(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &group, &groupDefaulted);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetGroup: GetSecurityDescriptorGroup failed"), tstring{}, static_cast<long>(::GetLastError())};
  return group;
}
 
//
/// Retrieves the owner information from the security descriptor.
/// Call TSecurityDescriptor::IsOwnerDefaulted to determine whether the owner was set by default
/// means or user specified.
///
/// \returns If the security descriptor contains an owner, a `PSID` pointing to the security
/// identifier that identifies the owner is returned. Otherwise, `nullptr` is returned.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorowner">
/// GetSecurityDescriptorOwner</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetOwner() const -> PSID
{
  auto owner = PSID{};
  auto ownerDefaulted = BOOL{};
  const auto ok = ::GetSecurityDescriptorOwner(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &owner, &ownerDefaulted);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetOwner: GetSecurityDescriptorOwner failed"), tstring{}, static_cast<long>(::GetLastError())};
  return owner;
}
 
//
/// Retrieves the revision number of the security descriptor structure.
/// Call TSecurityDescriptor::GetControl to get the control information of the structure.
///
/// \returns If successful, the revision number is returned.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorcontrol">
/// GetSecurityDescriptorControl</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetRevision() const -> DWORD
{
  auto c = SECURITY_DESCRIPTOR_CONTROL{};
  auto revision = DWORD{};
  const auto ok = ::GetSecurityDescriptorControl(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &c, &revision);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetRevision: GetSecurityDescriptorControl failed"), tstring{}, static_cast<long>(::GetLastError())};
  return revision;
}
 
//
/// Retrieves the resource manager control bits from the security descriptor.
/// \returns If the SE_RM_CONTROL_VALID bit flag is set in the security descriptor, a `UCHAR`
/// containing the control bits is returned. Otherwise, a `nullopt` is returned.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note To simply determine whether resource manager control bits are present in the security
/// descriptor, you can call TSecurityDescriptor::IsRmControlValid.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorrmcontrol">
/// GetSecurityDescriptorRMControl</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetRmControl() const -> optional<UCHAR>
{
  auto rmControl = UCHAR{};
  const auto r = ::GetSecurityDescriptorRMControl(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &rmControl);
  if (r == ERROR_INVALID_DATA) return nullopt;
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetRmControl: GetSecurityDescriptorRMControl failed"), tstring{}, static_cast<long>(r)};
  return make_optional(rmControl);
}
 
//
/// Retrieves the system access control list (SACL) from the security descriptor.
/// Call TSecurityDescriptor::IsSaclDefaulted to determine whether the SACL was set by default
/// means or user specified.
///
/// \returns If this security descriptor contains a SACL, a `PACL` is returned pointing to the
/// SACL. Note that `nullptr` is a valid value for the `PACL`. A `nullopt` is returned, if a SACL
/// is not present.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note To simply determine whether a SACL is present in the security descriptor, you can call
/// TSecurityDescriptor::IsSaclPresent.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorsacl">
/// GetSecurityDescriptorSacl</a> in the Windows API.
//
auto TRegKey::TSecurityDescriptor::GetSacl() const -> optional<PACL>
{
  auto saclPresent = BOOL{};
  auto sacl = PACL{};
  auto saclDefaulted = BOOL{};
  const auto ok = ::GetSecurityDescriptorSacl(const_cast<SECURITY_DESCRIPTOR*>(GetData()), &saclPresent, &sacl, &saclDefaulted);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::GetSacl: GetSecurityDescriptorSacl failed"), tstring{}, static_cast<long>(::GetLastError())};
  return saclPresent ? make_optional(sacl) : nullopt;
}
 
//
/// Initializes a new security descriptor.
///
/// The security descriptor is initialized to have no system access control list (SACL), no
/// discretionary access control list (DACL), no owner, no primary group, and all control flags set
/// to `FALSE` (`NULL`). Thus, except for its revision level, it is empty.
///
/// \param revision must be set to SECURITY_DESCRIPTOR_REVISION (the default).
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-initializesecuritydescriptor">
/// InitializeSecurityDescriptor</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::Initialize(DWORD revision)
{
  const auto ok = ::InitializeSecurityDescriptor(GetData(), revision);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::Initialize: InitializeSecurityDescriptor failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Removes the discretionary access control list (DACL) from the security descriptor.
/// The flags SE_DACL_PRESENT and SE_DACL_DEFAULTED are cleared in the SECURITY_DESCRIPTOR_CONTROL
/// structure, and the DACL `PACL` is set to `nullptr`.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl">
/// SetSecurityDescriptorDacl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::RemoveDacl()
{
  const auto ok = ::SetSecurityDescriptorDacl(GetData(), FALSE, nullptr, FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::RemoveDacl: SetSecurityDescriptorDacl failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Removes the primary group information from the security descriptor.
/// Sets the group `PSID` to `nullptr`. This marks the security descriptor as having no primary
/// group. Also, the flag SE_GROUP_DEFAULTED is cleared in the SECURITY_DESCRIPTOR_CONTROL
/// structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorgroup">
/// SetSecurityDescriptorGroup</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::RemoveGroup()
{
  const auto ok = ::SetSecurityDescriptorGroup(GetData(), nullptr, FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::RemoveGroup: SetSecurityDescriptorGroup failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Removes the owner information from the security descriptor.
/// Sets the owner `PSID` to `nullptr`. This marks the security descriptor as having no owner.
/// Also, the flag SE_OWNER_DEFAULTED is cleared in the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner">
/// SetSecurityDescriptorOwner</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::RemoveOwner()
{
  const auto ok = ::SetSecurityDescriptorOwner(GetData(), nullptr, FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::RemoveOwner: SetSecurityDescriptorOwner failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Removes the resource manager control bits from the security descriptor.
/// The flag SE_RM_CONTROL_VALID in cleared in the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorrmcontrol">
/// SetSecurityDescriptorRMControl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::RemoveRmControl()
{
  const auto r = ::SetSecurityDescriptorRMControl(GetData(), nullptr);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::RemoveRmControl: SetSecurityDescriptorRMControl failed"), tstring{}, static_cast<long>(r)};
}
 
//
/// Removes the system access control list (SACL) from the security descriptor.
/// The flags SE_SACL_PRESENT and SE_SACL_DEFAULTED are cleared in the SECURITY_DESCRIPTOR_CONTROL
/// structure, and the SACL `PACL` is set to `nullptr`.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorsacl">
/// SetSecurityDescriptorSacl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::RemoveSacl()
{
  const auto ok = ::SetSecurityDescriptorSacl(GetData(), FALSE, nullptr, FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::RemoveSacl: SetSecurityDescriptorSacl failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Sets the discretionary access control list (DACL) of the security descriptor.
/// If a DACL is already present in the security descriptor, the DACL is replaced.
/// On success, the flag SE_DACL_PRESENT is set in the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \param dacl points to an ACL structure that specifies the DACL for the security descriptor.
/// The DACL is referenced by, not copied into, the security descriptor. If this parameter is a
/// `nullptr`, a **NULL** DACL is assigned to the security descriptor, which allows all access to
/// the object.
///
/// \param daclDefaulted indicates whether the DACL information was derived from a default
/// mechanism or explicitly specified by a user. The function stores this value in the flag
/// SE_DACL_DEFAULTED within the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note Passing a `nullptr` to this function does not remove the DACL; rather it sets a **NULL**
/// DACL. To remove the DACL from the security descriptor, call TSecurityDescriptor::RemoveDacl.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl">
/// SetSecurityDescriptorDacl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::SetDacl(PACL dacl, bool daclDefaulted)
{
  const auto ok = ::SetSecurityDescriptorDacl(GetData(), TRUE, dacl, daclDefaulted ? TRUE : FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::SetDacl: SetSecurityDescriptorDacl failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Sets the primary group information of the security descriptor.
/// Any primary group information already present in the security descriptor is replaced.
///
/// \param group points to a SID structure for the security descriptor's new primary group. The SID
/// structure is referenced by, not copied into, the security descriptor. If this parameter is
/// `nullptr`, the security descriptor's primary group information is cleared. This marks the
/// security descriptor as having no primary group.
///
/// \param groupDefaulted indicates whether the primary group information was derived from a
/// default mechanism or explicitly specified by a user. The function stores this value in the flag
/// SE_GROUP_DEFAULTED within the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note Rather than call this function with a `nullptr`, instead you should prefer to call
/// TSecurityDescriptor::RemoveGroup.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorgroup">
/// SetSecurityDescriptorGroup</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::SetGroup(PSID group, bool groupDefaulted)
{
  const auto ok = ::SetSecurityDescriptorGroup(GetData(), group, groupDefaulted ? TRUE : FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::SetGroup: SetSecurityDescriptorGroup failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Sets the owner information of the security descriptor.
/// Any owner information already present in the security descriptor is replaced.
///
/// \param owner points to a SID structure for the security descriptor's new owner. The SID
/// structure is referenced by, not copied into, the security descriptor. If this parameter is
/// `nullptr`, the security descriptor's primary owner information is cleared. This marks the
/// security descriptor as having no owner.
///
/// \param ownerDefaulted indicates whether the owner information was derived from a default
/// mechanism or explicitly specified by a user. The function stores this value in the flag
/// SE_OWNER_DEFAULTED within the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note Rather than call this function with a `nullptr`, instead you should prefer to call
/// TSecurityDescriptor::RemoveOwner.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner">
/// SetSecurityDescriptorOwner</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::SetOwner(PSID owner, bool ownerDefaulted)
{
  const auto ok = ::SetSecurityDescriptorOwner(GetData(), owner, ownerDefaulted ? TRUE : FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::SetOwner: SetSecurityDescriptorOwner failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Sets the resource manager control bits of the security descriptor.
/// On success, the function also sets the SE_RM_CONTROL_VALID flag in the
/// SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \param rmControl contains the control bits.
///
/// \exception TXRegistry is thrown on failure.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorrmcontrol">
/// SetSecurityDescriptorRMControl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::SetRmControl(UCHAR rmControl)
{
  const auto r = ::SetSecurityDescriptorRMControl(GetData(), &rmControl);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::SetRmControl: SetSecurityDescriptorRMControl failed"), tstring{}, static_cast<long>(r)};
}
 
//
/// Sets the system access control list (SACL) of the security descriptor.
/// If a SACL is already present in the security descriptor, the SACL is replaced.
/// On success, the flag SE_SACL_PRESENT is set in the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \param sacl points to an ACL structure that specifies the SACL for the security descriptor.
/// The SACL is referenced by, not copied into, the security descriptor. If this parameter is a
/// `nullptr`, a **NULL** SACL is assigned to the security descriptor.
///
/// \param saclDefaulted indicates whether the SACL information was derived from a default
/// mechanism or explicitly specified by a user. The function stores this value in the flag
/// SE_SACL_DEFAULTED within the SECURITY_DESCRIPTOR_CONTROL structure.
///
/// \exception TXRegistry is thrown on failure.
///
/// \note Passing a `nullptr` to this function does not remove the SACL; rather it sets a **NULL**
/// SACL. To remove the SACL from the security descriptor, call TSecurityDescriptor::RemoveSacl.
///
/// \sa <a href="https://docs.microsoft.com/en-gb/windows/win32/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorsacl">
/// SetSecurityDescriptorSacl</a> in the Windows API.
//
void TRegKey::TSecurityDescriptor::SetSacl(PACL sacl, bool saclDefaulted)
{
  const auto ok = ::SetSecurityDescriptorSacl(GetData(), TRUE, sacl, saclDefaulted ? TRUE : FALSE);
  if (!ok) throw TXRegistry{_T("TRegKey::TSecurityDescriptor::SetSacl: SetSecurityDescriptorSacl failed"), tstring{}, static_cast<long>(::GetLastError())};
}
 
//
/// Creates a registry value object from the specified registry key and name.
/// If the name is null or empty, the object will represent the key's default value.
//
TRegValue::TRegValue(const TRegKey& key, LPCTSTR name)
:
  Key(key), Name(name ? name : _T("")), DataType(REG_NONE), Data(0), DataSize(0)
{
  QueryTypeAndSize();
}
 
//
/// String-aware overload
/// If the name is empty, the object will represent the key's default value.
//
TRegValue::TRegValue(const TRegKey& key, const tstring& name)
:
  Key(key), Name(name), DataType(REG_NONE), Data(0), DataSize(0)
{
  QueryTypeAndSize();
}
 
#if defined(BI_COMP_BORLANDC) 
 
// Circumvent bug in std::optional (requires default constructor for the contained object type).
// TODO: Remove this when the library is standard compliant.
//
TRegValue::TRegValue() : Key{TRegKey::GetCurrentUser()} {}
 
#endif
 
//
/// Query the registry for the value type and data size, though not the actual data.
/// The value data itself is retrieved lazily; see RetrieveOnDemand.
/// Returns the error code returned by the query.
//
long TRegValue::QueryTypeAndSize()
{
  long r = Key.QueryValue(Name, &DataType, 0, &DataSize);
  WARN(r != ERROR_SUCCESS, _T("TRegValue::QueryTypeAndSize: Query failed: ") << Key.GetName() << _T('\\') << Name << _T(", error: ") << r);
  return r;
}
 
//
/// Creates a registry object from the current location of the specified iterator.
//
TRegValue::TRegValue(const TRegValueIterator& iter)
:
  Key(iter.BaseKey()), Name(), DataType(REG_NONE), Data(0), DataSize(0)
{
  uint32 nameSize = 16383; // See Windows API documentation; http://msdn.microsoft.com/en-us/library/windows/desktop/ms724872.aspx
  std::vector<tchar> nameBuffer(nameSize);
  long r = Key.EnumValue(iter.Current(), &nameBuffer[0], nameSize, &DataType, 0, &DataSize);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::TRegValue(const TRegValueIterator&): EnumValue failed"), Key, r};
  nameBuffer.resize(nameSize); // EnumValue sets nameSize to the number of characters stored, excluding null-termination.
  Name.assign(nameBuffer.begin(), nameBuffer.end());
}
 
//
/// Returns the data associated with this value as a 32-bit unsigned integer.
/// Throws an exception if the value is of incompatible type.
//
TRegValue::operator uint32() const
{
  RetrieveOnDemand();
  if (DataType != REG_DWORD && DataType != REG_DWORD_BIG_ENDIAN)
    throw TXRegistry{_T("TRegValue::operator uint32: Incompatible data type"), Key};
 
  CHECK(DataSize == sizeof(uint32));
  return *reinterpret_cast<uint32*>(&Data[0]);
}
 
//
/// Returns the data associated with this value as a 64-bit unsigned integer.
/// Throws an exception if the value is of incompatible type.
//
TRegValue::operator uint64() const
{
  RetrieveOnDemand();
  if (DataType != REG_QWORD)
    throw TXRegistry{_T("TRegValue::operator uint64: Incompatible data type"), Key};
 
  CHECK(DataSize == sizeof(uint64));
  return *reinterpret_cast<uint64*>(&Data[0]);
}
 
//
/// Returns the data associated with this value as a null-terminated string.
/// Throws an exception if the value is of incompatible type or is otherwise invalid.
//
TRegValue::operator LPCTSTR() const
{
  RetrieveOnDemand();
  if (DataType != REG_SZ &&
    DataType != REG_MULTI_SZ &&
    DataType != REG_EXPAND_SZ &&
    !(DataType == REG_LINK && sizeof(tchar) == 2) // REG_LINK is always a Unicode UTF-16 string.
    )
    throw TXRegistry{_T("TRegValue::operator LPCTSTR: Incompatible data type"), Key};
 
  // Ensure null-termination.
  // The data may have been stored without proper null-termination.
  // Also, check that the string has room for null-termination.
  //
  CHECK(!Data.empty());
  LPTSTR s = reinterpret_cast<LPTSTR>(&Data[0]);
  const size_t n = Data.size() / sizeof(tchar);
  if (n == 0) throw TXRegistry{_T("TRegValue::operator LPCTSTR: Data size is zero"), Key};
  WARN((Data.size() % sizeof(tchar)) != 0, _T("TRegValue::operator LPCTSTR: Oddly sized string"));
  WARN(s[n - 1] != _T('\0'), _T("TRegValue::operator LPCTSTR: String lacks null-termination"));
  s[n - 1] = _T('\0');
  if (DataType == REG_MULTI_SZ && n > 1)
  {
    WARN(s[n - 2] != _T('\0'), _T("TRegValue::operator LPCTSTR: Multi-string lacks double null-termination"));
    s[n - 2] = _T('\0');
  }
  return s;
}
 
//
/// Returns the data associated with this value.
//
const uint8*
TRegValue::GetData() const
{
  RetrieveOnDemand();
  return Data.empty() ? 0 : &Data[0];
}
 
//
/// Sets the data associated with this value.  'type' describes the type of the
/// value. 'data' is the address of the data. 'size' specifies the length in
/// characters.
//
long
TRegValue::Set(uint32 type, const uint8* data, uint32 dataSize)
{
  long r = Key.SetValue(Name, type, data, dataSize);
  if (r != ERROR_SUCCESS) return r;
 
  // If same size, copy new data, otherwise retrieve the data on demand later.
  //
  DataType = type;
  if (dataSize == Data.size() && !Data.empty())
  {
    memcpy(&Data[0], data, Data.size());
  }
  else
  {
    DataSize = dataSize;
    Data.clear();
  }
  return ERROR_SUCCESS;
}
 
//
/// Sets the data associated with this value.
/// The type of the value is set to REG_DWORD.
//
long
TRegValue::Set(uint32 v)
{
  const uint8* data = reinterpret_cast<const uint8*>(&v);
  uint32 dataSize = sizeof(uint32);
  return Set(REG_DWORD, data, dataSize);
}
 
//
/// Sets the data associated with this value.
/// The type of the value is set to REG_QWORD.
//
long
TRegValue::Set(uint64 v)
{
  const uint8* data = reinterpret_cast<const uint8*>(&v);
  uint32 dataSize = sizeof(uint64);
  return Set(REG_QWORD, data, dataSize);
}
 
//
/// Sets the data associated with this value.
/// The type of the value is set to REG_SZ.
//
long
TRegValue::Set(LPCTSTR v)
{
  const uint8* data = reinterpret_cast<const uint8*>(v);
  uint32 dataSize = static_cast<uint32>((::_tcslen(v) + 1) * sizeof(tchar));
  return Set(REG_SZ, data, dataSize);
}
 
//
/// Sets the data associated with this value.
/// If the string contains a terminating null, then it is stored in the registry as a multi-string,
/// i.e. as a REG_MULTI_SZ value type, e.g. "multi\0string\0\0". Else, it is stored as a REG_SZ.
//
long
TRegValue::Set(const tstring& v)
{
  bool isMultiString = (!v.empty() && *v.rbegin() == _T('\0'));
  const uint8* data = reinterpret_cast<const uint8*>(v.c_str());
  uint32 dataSize = static_cast<uint32>((v.size() + 1) * sizeof(tstring::value_type));
  uint32 dataType = isMultiString ? REG_MULTI_SZ : REG_SZ;
  return Set(dataType, data, dataSize);
}
 
//
/// Forwards to Set (uint32).
/// Throws TXRegistry if data assignment fails.
//
TRegValue&
TRegValue::operator =(uint32 v)
{
  long r = Set(v);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::operator =(uint32): Set failed"), Key, r};
  return *this;
}
 
//
/// Forwards to Set (uint64).
/// Throws TXRegistry if data assignment fails.
//
TRegValue&
TRegValue::operator =(uint64 v)
{
  long r = Set(v);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::operator =(uint64): Set failed"), Key, r};
  return *this;
}
 
//
/// Forwards to Set (LPCTSTR).
/// Throws TXRegistry if data assignment fails.
//
TRegValue&
TRegValue::operator =(LPCTSTR v)
{
  long r = Set(v);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::operator =(LPCTSTR): Set failed"), Key, r};
  return *this;
}
 
//
/// Forwards to Set (const tstring&).
/// Throws TXRegistry if data assignment fails.
//
TRegValue&
TRegValue::operator =(const tstring& v)
{
  long r = Set(v);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::operator =(const tstring&): Set failed"), Key, r};
  return *this;
}
 
//
/// Removes the value from its associated key, and clears the state of this object.
//
long
TRegValue::Delete()
{
  long r = Key.DeleteValue(Name);
  if (r != ERROR_SUCCESS) return r;
  DataType = REG_NONE;
  try {std::vector<uint8>().swap(Data);} // This deallocation idiom may throw, so guard.
  catch (...) {Data.clear();} // Simply clear instead, if things go awry.
  DataSize = 0;
  return ERROR_SUCCESS;
}
 
//
/// Retrieves and stores the data for the value when requested for the first time (i.e. lazily).
/// Note that this function may fail due to synchronisation issues; the registry value
/// may have been changed, e.g. by another process, since the object was constructed.
//
void
TRegValue::RetrieveOnDemand() const
{
  if (!Data.empty()) return;
  if (DataType == REG_NONE || DataSize == 0) throw TXRegistry{_T("TRegValue::RetrieveOnDemand: Value is void"), Key};
 
  uint32 dataType = DataType;
  Data.resize(DataSize);
  uint32 dataSize = DataSize;
  long r = Key.QueryValue(Name, &dataType, &Data[0], &dataSize);
  if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegValue::RetrieveOnDemand: QueryValue failed"), Key, r};
  if (dataType != DataType) throw TXRegistry{_T("TRegValue::RetrieveOnDemand: Type changed"), Key};
  if (dataSize != DataSize) throw TXRegistry{_T("TRegValue::RetrieveOnDemand: Size changed"), Key};
}
 
//----------------------------------------------------------------------------
 
//
// Initialize object with the passed-in data.
// Counts the number of items in the list.
//
TRegTemplateList::TRegTemplateList(TRegKey& basekey, LPCTSTR list[])
:
  BaseKey(basekey),
  List(list)
{
  PRECONDITION(List);
 
  for (Count = 0; List[Count]; Count++)
    ;
  EnabledFlags = new int8[Count];
}
 
//
// Destructor deletes any previously allocated memory.
//
TRegTemplateList::~TRegTemplateList()
{
  delete[] EnabledFlags;
}
 
//
// Enable items from the set.
//
void
TRegTemplateList::Enable(LPCTSTR set)
{
  for (LPCTSTR pc = set; *pc != 0; pc++)
    EnabledFlags[*pc - 1] = 0;                 // Selectively allow enable
}
 
//
// Activate the items in the set.
//
void
TRegTemplateList::Activate(LPCTSTR set)
{
  for (LPCTSTR pc = set; *pc != 0; pc++)
    EnabledFlags[*pc - 1]++;
}
 
//----------------------------------------------------------------------------
 
//
// Initialize the object with the passed-in data.
// Counts the number of items in the list.
//
TRegParamList::TRegParamList(const TEntry* list)
:
  List(list)
{
  PRECONDITION(List);
 
  for (Count = 0; List[Count].Param; Count++)
    ;
  typedef LPCTSTR cpchar;
  Values = new cpchar[Count];
}
 
//
// Destructor deletes any previously allocated memory.
//
TRegParamList::~TRegParamList()
{
  delete[] Values;
}
 
//
// Reset all values to their default values.
//
void
TRegParamList::ResetDefaultValues()
{
  for (int i = Count; --i >= 0; )
    Values[i] = List[i].Default;
}
 
//
/// Looks for a given param in this param list.
/// Associative lookup of value by param.
//
int TRegParamList::Find(LPCTSTR param)
{
  PRECONDITION(param);
  if (!param)
    return -1;
 
  int i = Count;
  while (--i >= 0) {
    if (_tcscmp(List[i].Param, param) == 0)
      break;
  }
  return i;
}
 
//----------------------------------------------------------------------------
 
//
// Initialize the symbol table with the data.
//
TRegSymbolTable::TRegSymbolTable(TRegKey& basekey, LPCTSTR tplList[],
                                 const TRegParamList::TEntry* paramList)
:
  Templates(basekey, tplList),
  Params(paramList)
{
  UserKeyCount = 0;
}
 
//
// Enable all templates, or if filter is given enable only those
// Initialize paramater values with defaults
//
void
TRegSymbolTable::Init(LPCTSTR filter)
{
  if (filter) {
    Templates.DisableAll();
    Templates.Enable(filter);
  }
  else {
    Templates.EnableAll();
  }
 
  Params.ResetDefaultValues();
  UserKeyCount = 0;
}
 
//
// Scan through provided items assinging values from the item to the matching
// param. A langId is needed for locale translation on assignment.
//
void
TRegSymbolTable::UpdateParams(TLangId lang, TRegItem* item)
{
  for (; item && item->Key; item++) {
    // Note presence of user-specified key and value, process when streaming
    //
    if (*item->Key == ' ') {
      UserKeyCount++;
    }
    else {
      // Replace default with user-specified parameter value
      //
      const auto k = to_tstring(item->Key);
      int i = Params.Find(k);
      if (i < 0) throw TXRegistry{_T("TRegSymbolTable::UpdateParams failed"), k};
      Params.Value(i) = item->Value.Translate(lang);
 
      // Activate all templates invoked by parameter
      //
      Templates.Activate(Params[i].TemplatesNeeded);
    }
  }
}
 
//
// Scan thru and process enabled templates, substituting filled in parameter
// values and streaming the resulting strings into the out stream provided.
//
void
TRegSymbolTable::StreamOut(TRegItem* item, tostream& out)
{
#if defined(UNICODE)
  _USES_CONVERSION;
   TAPointer<tchar> conv_buf(new tchar[MAX_PATH]);
#  define _A2WB_(val) _A2WB(val,(tchar*)conv_buf)
#else
#  define _A2WB_(val) val
#endif
  for (int itpl = 1; itpl <= Templates.GetCount() || UserKeyCount--; itpl++) {
 
    // Setup pt from standard templates, or from userKeys in item prepended
    // by spaces.
    //
    LPCTSTR userval = 0;
    LPCTSTR pt;
    if (itpl <= Templates.GetCount()) {  // Processing standard template array
      if (!Templates.IsActive(itpl))
        continue;
      pt = Templates[itpl];
    }
    else {     // Now processing user-defined templates
      while (*(pt = _A2WB_(item->Key)) != _T(' '))
        item++;
      pt++;
      userval = item->Value;
    }
 
    // Walk thru template (pt) copying it into buf, replacing <params> on the
    // way.
    //
    const int bufSize  = 512;
    TAPointer<tchar> buf(new tchar[bufSize]);
 
    tchar*    data = 0; // Optional data for keys-value=data
    LPCTSTR   pc;       // Point where param token began
    tchar*   pb = buf; // Working buffer write pointer
    for (;;) {
      tchar  c = *pt++;  // Current character being copied
      switch (c) {
        case _T('<'):
          pc = pb;      // Remember where param token starts
          continue;
        case _T('>'): {
          *pb = 0;        // Terminate param token
 
          // Lookup param in param list
          //
          int i = Params.Find(pc);
          if (i < 0) throw TXRegistry{_T("TRegSymbolTable::StreamOut failed"), tstring{pc}};
 
          // Now get the value obtained above
          //
          pb = (tchar*)pc; // Move buffer ptr back
          pc = Params.Value(i);
          if (!pc) throw TXRegistry{_T("TRegSymbolTable::StreamOut failed"), tstring{Params[i].Param}};
 
          if (*pc == 0 && *pt == _T(' '))
            pt++;
 
          // Copy value to buf
          //
          while (*pc != 0)
            *pb++ = *pc++;
          continue;
        }
        case _T('='):
          while (*(pb-1) == _T(' '))  // Remove trailing space before =
            pb--;
          *pb++ = 0;              // Terminate keys-value
          data = pb;              // Rest of pt will be data written from here
          while (*pt == _T(' '))
            pt++;
          continue;
        default:
          *pb++ = c;  // Copy character to buf. Param name will be overwriten
          continue;
        case 0:
          *pb++ = 0;
          if (!userval)
            break;
          pt = userval;
          userval = 0;
          data = pb;
          continue;
      }
      if (!c)
        break;
    }
    // Now write out a key with an optional assignment value to output stream
    //
    out
      << Templates.GetBaseKey().GetName()
      << _T('\\') << (LPCTSTR)buf;
    if (data)
      out << _T(" = ") << data;
    out << _T('\n');
  }
}
 
//----------------------------------------------------------------------------
 
//
/// Walks through an input stream and uses  basekey\\key\\key=data  lines to set
/// registry entries.
///
/// Has named value support in the form:    basekey\\key\\key|valuename=data
//
void TRegistry::Update(TRegKey& baseKey, tistream& in)
{
  // Loop thru all lines in input stream
  //
  while (in.good()) {
    // Get line into entry buffer, skip over base key if it exists, ignore line
    // if it doesn't
    //
    tchar entry[512];
    in.get(entry, ::_tcslen(baseKey.GetName())+1);
    if (_tcscmp(entry, baseKey.GetName()) != 0)
      continue;
    if (in.peek() == _T('\\'))
      in.ignore();  // Eat the '\' separator.
    in.getline(entry, 512);
 
    // Split entry into keys-value and data strings
    //
    tchar* data = _tcschr(entry, _T('='));
    if (data) {
      tchar* pc = data;
      while (*(pc-1) == _T(' '))
        pc--;
      *pc = _T('\0');
      while (*(++data) == _T(' '))
        ;
    }
    tchar* valName = _tcschr(entry, _T('|'));
    if (valName)
      *valName++ = _T('\0');  // Terminate key at value name separator
 
    TRegKey subKey(baseKey, entry);
    LPCTSTR s = data ? data : _T("");
    const uint8* buf = reinterpret_cast<const uint8*>(s);
    const uint32 bufSize = static_cast<uint32>((_tcslen(s) + 1) * sizeof(*s));
    const auto r = subKey.SetValue(valName, REG_SZ, buf, bufSize);
    if (r != ERROR_SUCCESS) throw TXRegistry{_T("TRegistry::Update: SetValue failed"), subKey};
  }
}
 
//
/// Walks through an input stream and uses  basekey\\key\\key=data  lines to check
/// registry entries.
/// Returns the number of differences. Zero means a complete match.
///
/// Has named value support in the following form:  basekey\\key\\key|valuename=data
//
int TRegistry::Validate(TRegKey& baseKey, tistream& in)
{
  int diffCount = 0;
  while (in.good()) {
    // Get line into entry buffer, skip over base key if it exists, ignore line
    // if it doesn't
    //
    tchar entry[512];
    in.get(entry, ::_tcslen(baseKey.GetName())+1);
    if (_tcscmp(entry, baseKey.GetName()) != 0)
      continue;
    if (in.peek() == _T('\\'))
      in.ignore();  // Eat the '\' separator.
    in.getline(entry, 512);
 
    // Split entry into keys-value and data strings
    //
    tchar* data = _tcschr(entry, _T('='));
    if (data) {
      tchar* pc = data;
      while (*(pc-1) == _T(' '))
        pc--;
      *pc = _T('\0');
      while (*(++data) == _T(' '))
        ;
    }
    tchar* valName = _tcschr(entry, _T('|'));
    if (valName)
      *valName++ = _T('\0');  // Terminate key at value name separator
 
    // Now lookup keys-value part to get its data and see if it matches the
    // data value from the instream
    //
    TRegKey subKey(baseKey, entry);
    uint32 dataType = REG_SZ;
    tchar buf[300];
    uint32 bufSize = 300;
    if (subKey.QueryValue(valName, &dataType, reinterpret_cast<uint8*>(buf), &bufSize) != ERROR_SUCCESS
        || (data && _tcscmp(data, buf) != 0))
      diffCount++;
  }
  return diffCount;
}
 
//
/// Unregisters entries given a reglist.  An optional overrides regItem.  Returns
/// the number of errors from deleting keys.
//
int
TRegistry::Unregister(TRegList& regInfo, const TUnregParams* params, const TRegItem* overrides)
{
#if defined(UNICODE)
  _USES_CONVERSION;
   TAPointer<tchar> conv_buf(new tchar[MAX_PATH]);
#  undef _A2WB_
#  undef _W2AB_
#  define _A2WB_(val) _A2WB(val,(tchar*)conv_buf)
#  define _W2AB_(val) _W2AB(val,(tchar*)conv_buf)
#else
#  undef _A2WB_
#  undef _W2AB_
#  define _A2WB_(val) val
#  define _W2AB_(val) val
#endif
  int  errorCount = 0;
 
  // Loop thru unregister params to nuke each root level key, thus cleaning
  // up all nested keys too
  //
  for (int i = 0; params[i].Name && params[i].BaseKey; i++) {
    LPCTSTR regKey = regInfo[_W2AB_(params[i].Name)];
 
    // If the param not found & it matches the overrides item, then use that
    // value
    //
    if (!regKey && overrides && _tcscmp(_A2WB_(overrides->Key), params[i].Name) == 0)
      regKey = overrides->Value;
 
    // If key-value was found, unregister it
    //
    if (regKey) {
      tchar buff[16];
      if (params[i].Prepend) { // Special case prepending char to key
        buff[0] = _T('.');
        ::_tcscpy(buff+1, regKey);
        regKey = buff;
      }
 
      // Nuke the key, using the basekey as a reference point
      //
      if (params[i].BaseKey->NukeKey(regKey))
        errorCount++;         // Should throw exception if certain errors?
    }
  }
  return errorCount;
}
 
} // OWL namespace
/* ========================================================================== */

V1063 The modulo by 1 operation is meaningless. The result will always be zero.

V614 Uninitialized pointer 'pc' used. Consider checking the first actual argument of the 'Find' function.

V507 Pointer to local array 'buff' is stored outside the scope of this array. Such a pointer will become invalid.

V547 Expression '!c' is always true.

V814 Decreased performance. The 'strlen' function was called multiple times inside the body of a loop.

V814 Decreased performance. The 'strlen' function was called multiple times inside the body of a loop.