934 lines
29 KiB
C++
934 lines
29 KiB
C++
/*
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
This file contains a sample app showing basic manipulations of a CIM Image.
|
|
Topics to cover:
|
|
- Create a new CIM image
|
|
- Commit changes to the image
|
|
- Add a file from the local filesystem to the image
|
|
- Mount and validate image contents
|
|
- Add a Hardlink to an existing file in the image
|
|
- Fork from the base image
|
|
- Delete a file from the forked CIM image
|
|
*/
|
|
|
|
#include <aclapi.h>
|
|
#include <cstddef>
|
|
#include <exception>
|
|
#include <iostream>
|
|
#include <rpcdce.h>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <wil/resource.h>
|
|
#include <wil/result.h>
|
|
#include <wil/safecast.h>
|
|
#include <windows.h>
|
|
#include <winioctl.h>
|
|
#include <winnt.h>
|
|
#include <CimFs.h>
|
|
|
|
#define BUFFERSIZE 65536 //64 KB
|
|
|
|
// Keep information of a file's alternate streams
|
|
struct StreamData
|
|
{
|
|
std::wstring Name;
|
|
LONGLONG Size;
|
|
};
|
|
|
|
struct AlternateDataStreams
|
|
{
|
|
std::vector<StreamData> StreamData;
|
|
};
|
|
|
|
struct CimFileData
|
|
{
|
|
// The handle used to get the information of the file
|
|
wil::unique_hfile FileHandle;
|
|
|
|
// While getting the data we also need to keep the sd alive
|
|
wil::unique_hlocal_security_descriptor Sd;
|
|
|
|
// A copy from filesystem's attributes and metadata that Cimfs
|
|
// needs when creating a file in the image
|
|
CIMFS_FILE_METADATA MetaData;
|
|
|
|
// Alternate streams information
|
|
std::vector<StreamData> StreamData;
|
|
|
|
FILE_ID_INFO FileIdInfo;
|
|
|
|
// buffer for reparse point data
|
|
std::vector<byte> ReparsePointData;
|
|
};
|
|
|
|
struct MountedCimInformation
|
|
{
|
|
// Guid used to mount the volume
|
|
GUID VolumeId;
|
|
|
|
// In the form of \\?\Volume{VolumeId}\
|
|
std::wstring VolumeRootPath;
|
|
};
|
|
|
|
// RAII adapter for a CIM image handle
|
|
using unique_cimfs_image_handle =
|
|
wil::unique_any<CIMFS_IMAGE_HANDLE, decltype(&CimCloseImage), &CimCloseImage>;
|
|
|
|
// RAII adapter for a CIM stream handle
|
|
using unique_cimfs_stream_handle =
|
|
wil::unique_any<CIMFS_STREAM_HANDLE, decltype(&CimCloseStream), &CimCloseStream>;
|
|
|
|
void
|
|
CopyFileContentsToCim(_In_ CIMFS_STREAM_HANDLE streamHandle, _In_ HANDLE file)
|
|
//
|
|
// Routine Description:
|
|
// Copies the contents of an open file to a Writer.
|
|
//
|
|
// Parameters:
|
|
// streamHandle - Cim image writer handle, obtained by a call to CimAddFile.
|
|
//
|
|
// file - Opened handle to the file to copy data from.
|
|
//
|
|
{
|
|
std::wcout << "Copying data from file / stream" << std::endl;
|
|
|
|
std::vector<byte> buffer(65536);
|
|
|
|
for (;;)
|
|
{
|
|
DWORD read;
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(ReadFile(file, buffer.data(), static_cast<DWORD>(buffer.size()), &read, nullptr));
|
|
|
|
if (read == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
std::wcout << "\tRead " << read << " bytes, writing in image's stream ..." << std::endl;
|
|
|
|
THROW_IF_FAILED(CimWriteStream(streamHandle, buffer.data(), read));
|
|
}
|
|
}
|
|
|
|
AlternateDataStreams
|
|
GetAlternateDataStreams(_In_ const std::wstring& filePath)
|
|
//
|
|
// Routine Description:
|
|
// Gets the list of alternate data streams for an open handle.
|
|
//
|
|
// Parameters:
|
|
// filePath - Path to the file to get the data from.
|
|
//
|
|
{
|
|
AlternateDataStreams streams{};
|
|
WIN32_FIND_STREAM_DATA findData{};
|
|
|
|
wil::unique_hfind findStream{
|
|
FindFirstStreamW(filePath.c_str(), FindStreamInfoStandard, &findData, 0)};
|
|
|
|
if (findStream)
|
|
{
|
|
do
|
|
{
|
|
StreamData data{};
|
|
data.Name = std::wstring(findData.cStreamName);
|
|
data.Size = findData.StreamSize.QuadPart;
|
|
|
|
// Skip the default data stream
|
|
if (data.Name != L"::$DATA")
|
|
{
|
|
streams.StreamData.push_back(data);
|
|
}
|
|
|
|
} while (FindNextStreamW(findStream.get(), &findData));
|
|
}
|
|
else
|
|
{
|
|
THROW_LAST_ERROR_IF(GetLastError() != ERROR_HANDLE_EOF);
|
|
}
|
|
|
|
return streams;
|
|
}
|
|
|
|
CimFileData
|
|
GetFileData(_In_ const std::wstring& filePath)
|
|
//
|
|
// Routine Description:
|
|
// Gets the meta data, attributes and stream information from
|
|
// an existing file that will be copied to a Cim image.
|
|
//
|
|
// Parameters:
|
|
// filePath - Path to the file to get the data from.
|
|
//
|
|
// fileData - Structure that will hold the required attributes and stream info.
|
|
//
|
|
{
|
|
CimFileData fileData{};
|
|
|
|
std::wcout << "Getting data for file: " << filePath.c_str() << std::endl;
|
|
|
|
// Open the file, ensure in case of a symlink we open the symlink itself
|
|
wil::unique_hfile file{CreateFileW(filePath.c_str(),
|
|
GENERIC_READ | ACCESS_SYSTEM_SECURITY,
|
|
FILE_SHARE_READ,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
|
nullptr)};
|
|
|
|
THROW_LAST_ERROR_IF(!file);
|
|
|
|
// Get the in formation we need from the source file that is required when
|
|
// adding a file to the image
|
|
|
|
FILE_ID_INFO fileID{};
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
GetFileInformationByHandleEx(file.get(), FileIdInfo, &fileID, sizeof(fileID)));
|
|
|
|
FILE_BASIC_INFO basicInfo;
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
GetFileInformationByHandleEx(file.get(), FileBasicInfo, &basicInfo, sizeof(basicInfo)));
|
|
|
|
fileData.MetaData.Attributes = basicInfo.FileAttributes;
|
|
fileData.MetaData.CreationTime = basicInfo.CreationTime;
|
|
fileData.MetaData.LastWriteTime = basicInfo.LastWriteTime;
|
|
fileData.MetaData.ChangeTime = basicInfo.ChangeTime;
|
|
fileData.MetaData.LastAccessTime = basicInfo.LastAccessTime;
|
|
|
|
fileData.FileIdInfo = fileID;
|
|
|
|
if (basicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
{
|
|
std::wcout << "\t\tFile is a reparse point, getting reparse info" << std::endl;
|
|
|
|
std::vector<byte> reparseBuffer;
|
|
reparseBuffer.resize(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
DWORD bytes;
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
DeviceIoControl(file.get(),
|
|
FSCTL_GET_REPARSE_POINT,
|
|
nullptr,
|
|
0,
|
|
reparseBuffer.data(),
|
|
static_cast<DWORD>(reparseBuffer.size()),
|
|
&bytes,
|
|
nullptr));
|
|
|
|
fileData.ReparsePointData = std::move(reparseBuffer);
|
|
fileData.MetaData.ReparseDataBuffer = fileData.ReparsePointData.data();
|
|
fileData.MetaData.ReparseDataSize = bytes;
|
|
}
|
|
|
|
if (basicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
fileData.MetaData.FileSize = 0;
|
|
}
|
|
else
|
|
{
|
|
LARGE_INTEGER fileSize{};
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(file.get(), &fileSize));
|
|
fileData.MetaData.FileSize = fileSize.QuadPart;
|
|
}
|
|
|
|
wil::unique_hlocal_security_descriptor sd;
|
|
|
|
// Retrieve the security descriptor.
|
|
auto secInfo = DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION |
|
|
GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION |
|
|
SACL_SECURITY_INFORMATION;
|
|
|
|
THROW_IF_WIN32_ERROR(GetSecurityInfo(file.get(),
|
|
SE_FILE_OBJECT,
|
|
secInfo,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
&sd));
|
|
|
|
fileData.MetaData.SecurityDescriptorBuffer = sd.get();
|
|
fileData.MetaData.SecurityDescriptorSize = GetSecurityDescriptorLength(sd.get());
|
|
fileData.Sd = std::move(sd);
|
|
|
|
// Retrieve Alternate streams info
|
|
fileData.StreamData = std::move(GetAlternateDataStreams(filePath).StreamData);
|
|
|
|
// Finally keep the handle used to retrieve the information
|
|
fileData.FileHandle = std::move(file);
|
|
|
|
return fileData;
|
|
}
|
|
|
|
void
|
|
WriteFileEntry(_In_ CIMFS_IMAGE_HANDLE cimHandle,
|
|
_In_ const std::wstring& filePath,
|
|
_In_ const std::wstring& imageRelativePath,
|
|
_Out_ ULONG& fileAttributes)
|
|
//
|
|
// Routine Description:
|
|
// Retrieves the information of an existing file and writes it into
|
|
// the Cim Image.
|
|
//
|
|
// Parameters:
|
|
// cimHandle - Opened handle to the Cim Image by calling CimCreateImage.
|
|
//
|
|
// filePath - Source Path in the local filesystem to copy the data from.
|
|
//
|
|
// imageRelativePath - Destination path in the CIM Image
|
|
//
|
|
// fileAttributes - Attributes of the copied file
|
|
//
|
|
{
|
|
|
|
unique_cimfs_stream_handle streamHandle = nullptr;
|
|
CimFileData cimFileData;
|
|
|
|
std::wcout << "Adding file:" << filePath.c_str() << " as " << imageRelativePath.c_str()
|
|
<< " in image" << std::endl;
|
|
|
|
// Get the information we need from the source file
|
|
cimFileData = GetFileData(filePath);
|
|
|
|
THROW_IF_FAILED(
|
|
CimCreateFile(cimHandle, imageRelativePath.c_str(), &cimFileData.MetaData, &streamHandle));
|
|
|
|
// Write the payload data.
|
|
if (cimFileData.MetaData.FileSize > 0)
|
|
{
|
|
CopyFileContentsToCim(streamHandle.get(), cimFileData.FileHandle.get());
|
|
}
|
|
|
|
CimCloseStream(streamHandle.release());
|
|
|
|
// Write alternate data streams.
|
|
|
|
if (cimFileData.StreamData.size() > 0)
|
|
{
|
|
|
|
for (const auto& streamData : cimFileData.StreamData)
|
|
{
|
|
|
|
// stream.Name is of the format ":name:$TYPE"
|
|
// no need to check for $DATA as we skip it when
|
|
// collecting data for alternate data streams
|
|
auto end = streamData.Name.find(':', 1);
|
|
if (end != std::wstring::npos)
|
|
{
|
|
auto streamName = filePath + streamData.Name.substr(0, end);
|
|
|
|
wil::unique_hfile stream(CreateFileW(streamName.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
0,
|
|
nullptr));
|
|
|
|
THROW_LAST_ERROR_IF(!stream);
|
|
|
|
unique_cimfs_stream_handle alternateStreamHandle;
|
|
|
|
// This time we're not creating a new file
|
|
// but adding a stream
|
|
THROW_IF_FAILED(CimCreateAlternateStream(cimHandle,
|
|
streamName.c_str(),
|
|
streamData.Size,
|
|
&alternateStreamHandle));
|
|
|
|
if (streamData.Size > 0)
|
|
{
|
|
CopyFileContentsToCim(alternateStreamHandle.get(), stream.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fileAttributes = cimFileData.MetaData.Attributes;
|
|
}
|
|
|
|
void
|
|
AddFileToNewCim(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& filePath,
|
|
_In_ const std::wstring& imageRelativePath)
|
|
//
|
|
// Routine Description:
|
|
// Copies the content of an existing file into a new Cim.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a directory to contain the CIM image.
|
|
// For example: C:\MyCimImage
|
|
//
|
|
// imageName - Name of the image. The image should not already exist
|
|
// cimPath.
|
|
// For example: image0.cim
|
|
//
|
|
// filePath - Source Path in the local filesystem to copy the data from.
|
|
// For example: C:\dir\file1.txt
|
|
//
|
|
// imageRelativePath - Destination path relative to the root in the CIM Image.
|
|
// For example: dir\file1.txt
|
|
//
|
|
{
|
|
unique_cimfs_image_handle imageHandle = nullptr;
|
|
ULONG fileAttributes;
|
|
|
|
std::wcout << "Creating new image " << imageName << " in directory " << cimPath << std::endl;
|
|
|
|
auto hr = CimCreateImage(cimPath.c_str(), nullptr, imageName.c_str(), &imageHandle);
|
|
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
|
|
{
|
|
std::wcout << "ERROR: image " << imageName << " already exists in directory " << cimPath << std::endl;
|
|
}
|
|
|
|
THROW_IF_FAILED(hr);
|
|
|
|
WriteFileEntry(imageHandle.get(), filePath, imageRelativePath, fileAttributes);
|
|
|
|
if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
std::wcout << "Directory " << filePath << " added as an empty directory in CIM Image as ";
|
|
std::wcout << imageRelativePath << "(add files separately if desired)" << std::endl;
|
|
}
|
|
|
|
// Commit changes to a new CIM
|
|
THROW_IF_FAILED(CimCommitImage(imageHandle.get()));
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
AddHardLinkInCim(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& existingImageRelativePath,
|
|
_In_ const std::wstring& imageRelativePath)
|
|
//
|
|
// Routine Description:
|
|
// Adds a Hard Link entry in the CIM image to an existing file in the Cim Image.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image directory.
|
|
//
|
|
// imageName - Name of the existing image the file will be added to.
|
|
//
|
|
// existingImageRelativePath - An existing Path in the CIM image that the hardlink will point to.
|
|
//
|
|
// imageRelativePath - Name of the hardlink
|
|
//
|
|
{
|
|
unique_cimfs_image_handle imageHandle = nullptr;
|
|
|
|
std::wcout << "Opening image " << imageName << " in directory " << cimPath
|
|
<< " to add a hardlink " << std::endl;
|
|
|
|
// extend the parent CIM
|
|
THROW_IF_FAILED(CimCreateImage(cimPath.c_str(),
|
|
imageName.c_str(),
|
|
imageName.c_str(),
|
|
&imageHandle));
|
|
|
|
std::wcout << "Adding HardLink " << imageRelativePath.c_str() << " -> " << existingImageRelativePath.c_str();
|
|
std::wcout << std::endl;
|
|
|
|
HRESULT hr = CimCreateHardLink(imageHandle.get(), imageRelativePath.c_str(), existingImageRelativePath.c_str());
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// Can't create a hardlink to a directory
|
|
if (hr == E_ACCESSDENIED)
|
|
{
|
|
std::wcout << "Error, can't add hardlink to a directory" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
THROW_IF_FAILED(hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
THROW_IF_FAILED(CimCommitImage(imageHandle.get()));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
DeletePathFromCimFork(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& imageRelativePath,
|
|
_In_ const std::wstring& forkedImageName)
|
|
//
|
|
// Routine Description:
|
|
// Deletes a file or directory from and existing image creating a fork of the image
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image directory.
|
|
//
|
|
// imageName - Name of the existing image the file will be deleted from.
|
|
//
|
|
// imageRelativePath - An existing path in the CIM image that will be deleted.
|
|
//
|
|
// forkedImageName - Name of the image fork to be created.
|
|
//
|
|
{
|
|
unique_cimfs_image_handle imageHandle = nullptr;
|
|
|
|
std::wcout << "Opening image " << imageName << " in directory " << cimPath
|
|
<< " to delete a file " << std::endl;
|
|
|
|
|
|
auto hr = CimCreateImage(cimPath.c_str(), imageName.c_str(), forkedImageName.c_str(), &imageHandle);
|
|
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
|
|
{
|
|
std::wcout << "ERROR: image " << forkedImageName << " already exists in directory " << cimPath << std::endl;
|
|
}
|
|
THROW_IF_FAILED(hr);
|
|
|
|
std::wcout << "Deleting " << imageRelativePath.c_str() << " from image" << std::endl;
|
|
|
|
THROW_IF_FAILED(CimDeletePath(imageHandle.get(), imageRelativePath.c_str()));
|
|
|
|
// Fork the parent CIM by specifying a new name in the commit
|
|
|
|
THROW_IF_FAILED(CimCommitImage(imageHandle.get()));
|
|
|
|
return;
|
|
}
|
|
|
|
MountedCimInformation
|
|
MountImage(_In_ const std::wstring& cimPath, _In_ const std::wstring& imageName)
|
|
//
|
|
// Routine Description:
|
|
// Mounts an existing Cim image and returns the path the image was mounted to.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image, the image has been created already.
|
|
//
|
|
// imageName - Name of the image.
|
|
//
|
|
{
|
|
MountedCimInformation volumeInfo{};
|
|
|
|
GUID uuid;
|
|
wil::unique_rpc_wstr uuidString;
|
|
|
|
UuidCreate(&uuid);
|
|
UuidToStringW(&uuid, &uuidString);
|
|
THROW_IF_NULL_ALLOC(uuidString);
|
|
|
|
auto uuidWstring = reinterpret_cast<wchar_t*>(uuidString.get());
|
|
|
|
THROW_IF_FAILED(CimMountImage(cimPath.c_str(), imageName.c_str(), CIM_MOUNT_IMAGE_NONE, &uuid));
|
|
|
|
volumeInfo.VolumeId = uuid;
|
|
volumeInfo.VolumeRootPath = std::wstring(L"\\\\?\\Volume{") + uuidWstring + L"}\\";
|
|
|
|
return volumeInfo;
|
|
}
|
|
|
|
void
|
|
DismountImage(_In_ const GUID& volumeId)
|
|
{
|
|
THROW_IF_FAILED(CimDismountImage(&volumeId));
|
|
}
|
|
|
|
bool
|
|
CompareStreams(_In_ const std::wstring& source, _In_ const std::wstring& target)
|
|
//
|
|
// Routine Description:
|
|
// A basic routine that compares the contents of 2 streams.
|
|
// This routine assumes both paths are normal files (no directories or reparse points)
|
|
//
|
|
// Parameters:
|
|
// source - Path to a source file.
|
|
//
|
|
// target - Path to a target file.
|
|
//
|
|
// Returns:
|
|
// bool, true if both streams have the same content.
|
|
//
|
|
{
|
|
std::vector<byte> sourceBuffer(4096);
|
|
std::vector<byte> targetBuffer(4096);
|
|
|
|
wil::unique_hfile sourceHandle(CreateFileW(source.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
nullptr));
|
|
|
|
THROW_LAST_ERROR_IF(!sourceHandle);
|
|
|
|
wil::unique_hfile targetHandle(CreateFileW(target.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
nullptr));
|
|
|
|
THROW_LAST_ERROR_IF(!targetHandle);
|
|
|
|
for (;;)
|
|
{
|
|
DWORD sourceRead, targetRead;
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
ReadFile(sourceHandle.get(),
|
|
sourceBuffer.data(),
|
|
static_cast<DWORD>(sourceBuffer.size()),
|
|
&sourceRead,
|
|
nullptr));
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
ReadFile(targetHandle.get(),
|
|
targetBuffer.data(),
|
|
static_cast<DWORD>(targetBuffer.size()),
|
|
&targetRead,
|
|
nullptr));
|
|
|
|
if (targetRead != sourceRead)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sourceRead == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (memcmp(sourceBuffer.data(), targetBuffer.data(), sourceRead) != 0)
|
|
{
|
|
std::cerr << "\tContents do not match" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CompareFileWithCimFile(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& imageRelativePath,
|
|
_In_ const std::wstring& filePath)
|
|
//
|
|
// Routine Description:
|
|
// Compares the content of a file in the local filesystem against a
|
|
// file in a target CIM image as well as some basic attributes.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image directory.
|
|
//
|
|
// imageName - Name of the image that will be mounted and used to compare.
|
|
//
|
|
// imageRelativePath - Destination path in the CIM Image to compare
|
|
//
|
|
// filePath - Source Path in the local filesystem to compare.
|
|
//
|
|
{
|
|
std::wstring cimFilePath;
|
|
MountedCimInformation volumeInfo;
|
|
|
|
volumeInfo = MountImage(cimPath, imageName);
|
|
auto cleanup = wil::scope_exit([&] { DismountImage(volumeInfo.VolumeId); });
|
|
|
|
cimFilePath = volumeInfo.VolumeRootPath + imageRelativePath;
|
|
|
|
CimFileData fileData{GetFileData(filePath)};
|
|
CimFileData cimFileData{GetFileData(cimFilePath)};
|
|
|
|
// start by comparing attributes
|
|
if (fileData.MetaData.Attributes != cimFileData.MetaData.Attributes)
|
|
{
|
|
std::cerr << "Attributes do not match" << std::endl;
|
|
}
|
|
|
|
// if file is a reparse point compare reparse buffer
|
|
if (fileData.MetaData.Attributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
{
|
|
if (fileData.MetaData.ReparseDataSize != cimFileData.MetaData.ReparseDataSize)
|
|
{
|
|
std::cerr << "\tReparse buffer sizes do not match" << std::endl;
|
|
}
|
|
|
|
if (memcmp(fileData.MetaData.ReparseDataBuffer,
|
|
cimFileData.MetaData.ReparseDataBuffer,
|
|
fileData.MetaData.ReparseDataSize) != 0)
|
|
{
|
|
std::cerr << "\tReparse buffer contents do not match" << std::endl;
|
|
}
|
|
}
|
|
|
|
// if file is not a directory compare file contents
|
|
if (!(fileData.MetaData.Attributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
if (fileData.MetaData.FileSize != cimFileData.MetaData.FileSize)
|
|
{
|
|
std::cerr << "File sizes do not match" << std::endl;
|
|
}
|
|
|
|
if (fileData.MetaData.CreationTime.QuadPart != cimFileData.MetaData.CreationTime.QuadPart)
|
|
{
|
|
std::cerr << "Creation times do not match" << std::endl;
|
|
}
|
|
|
|
// Compare file contents
|
|
|
|
if (!CompareStreams(filePath, cimFilePath))
|
|
{
|
|
std::cerr << "Content does not match" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ValidateHardLinkInCim(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& existingImagePath,
|
|
_In_ const std::wstring& imageLinkPath)
|
|
//
|
|
// Routine Description:
|
|
// Validates if linkPath is a hardlink of filePath.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image, the image has been created already.
|
|
//
|
|
// imageName - Name of the image that will be mounted and used to compare.
|
|
//
|
|
// existingImagePath - An existing Path in the CIM image that the hardlink poins to.
|
|
//
|
|
// imageLinkPath - An existing Path in the im age previously added via CimAddLink
|
|
//
|
|
{
|
|
std::wstring cimFilePath, cimLinkPath;
|
|
MountedCimInformation volumeInfo;
|
|
|
|
volumeInfo = MountImage(cimPath, imageName);
|
|
auto cleanup = wil::scope_exit([&] { DismountImage(volumeInfo.VolumeId); });
|
|
|
|
cimFilePath = volumeInfo.VolumeRootPath + existingImagePath;
|
|
cimLinkPath = volumeInfo.VolumeRootPath + imageLinkPath;
|
|
|
|
{
|
|
CimFileData cimFileData{GetFileData(cimFilePath)};
|
|
CimFileData cimLinkData{GetFileData(cimLinkPath)};
|
|
|
|
if (memcmp(cimFileData.FileIdInfo.FileId.Identifier,
|
|
cimLinkData.FileIdInfo.FileId.Identifier,
|
|
sizeof(FILE_ID_128)) != 0)
|
|
{
|
|
std::cerr << "did not match" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
TestFileExistsInCim(_In_ const std::wstring& cimPath,
|
|
_In_ const std::wstring& imageName,
|
|
_In_ const std::wstring& imageRelativePath)
|
|
// Routine Description:
|
|
// Checks if the file exists or not in the provided image.
|
|
//
|
|
// Parameters:
|
|
// cimPath - Path to a CIM image.
|
|
//
|
|
// imageName - Name of the image that will be mounted and used to check
|
|
//
|
|
// imageRelativePath - Relative path from the root in the CIM image
|
|
//
|
|
{
|
|
MountedCimInformation volumeInfo;
|
|
|
|
volumeInfo = MountImage(cimPath, imageName);
|
|
auto cleanup = wil::scope_exit([&] { DismountImage(volumeInfo.VolumeId); });
|
|
|
|
std::wstring cimImageFilePath = volumeInfo.VolumeRootPath + imageRelativePath;
|
|
ULONG attributes = GetFileAttributes(cimImageFilePath.c_str());
|
|
|
|
// fail for any reason other than is the file was not found
|
|
THROW_LAST_ERROR_IF(attributes == INVALID_FILE_ATTRIBUTES && GetLastError() != ERROR_FILE_NOT_FOUND);
|
|
|
|
return (attributes != INVALID_FILE_ATTRIBUTES);
|
|
}
|
|
|
|
bool
|
|
TogglePrivilege(std::wstring_view privilegeName, bool enable)
|
|
//
|
|
// Routine Description:
|
|
// Attempts to enable or disable a given privilege. Returns the previous state for the privilege.
|
|
//
|
|
{
|
|
wil::unique_handle token;
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
OpenProcessToken(GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), &token));
|
|
|
|
struct
|
|
{
|
|
union {
|
|
TOKEN_PRIVILEGES TokenPrivileges;
|
|
struct
|
|
{
|
|
DWORD PrivilegeCount;
|
|
LUID_AND_ATTRIBUTES Privileges[1];
|
|
} TokenStruct;
|
|
} TokenUnion;
|
|
} oneTokenPrivilege;
|
|
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
LookupPrivilegeValue(nullptr,
|
|
std::wstring{privilegeName}.c_str(),
|
|
&oneTokenPrivilege.TokenUnion.TokenStruct.Privileges[0].Luid));
|
|
oneTokenPrivilege.TokenUnion.TokenStruct.PrivilegeCount = 1;
|
|
oneTokenPrivilege.TokenUnion.TokenStruct.Privileges[0].Attributes =
|
|
(enable ? SE_PRIVILEGE_ENABLED : 0);
|
|
|
|
DWORD cb;
|
|
THROW_IF_WIN32_BOOL_FALSE(
|
|
AdjustTokenPrivileges(token.get(),
|
|
false,
|
|
&oneTokenPrivilege.TokenUnion.TokenPrivileges,
|
|
sizeof(oneTokenPrivilege.TokenUnion.TokenPrivileges),
|
|
&oneTokenPrivilege.TokenUnion.TokenPrivileges,
|
|
&cb));
|
|
|
|
|
|
bool previouslyEnabled = ((cb == sizeof(oneTokenPrivilege.TokenUnion.TokenPrivileges)) &&
|
|
(oneTokenPrivilege.TokenUnion.TokenStruct.PrivilegeCount == 1) &&
|
|
(oneTokenPrivilege.TokenUnion.TokenStruct.Privileges[0].Attributes &
|
|
(SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT)));
|
|
|
|
return previouslyEnabled;
|
|
}
|
|
|
|
bool
|
|
ReadFileFromCim(_In_ const std::wstring& imagePath,
|
|
_In_ const std::wstring& filePath,
|
|
_In_ const int offset)
|
|
// Routine Description:
|
|
// Reads a file from the provided CIM image and writes it to STDOUT.
|
|
//
|
|
// Parameters:
|
|
// imagePath - Path to a CIM image.
|
|
//
|
|
// filePath - Relative path of the file from the root in the CIM Image.
|
|
//
|
|
// offset - offset at which to start reading the file
|
|
//
|
|
{
|
|
// Get the file size
|
|
FILE_STAT_BASIC_INFORMATION statInfo{};
|
|
THROW_IF_FAILED(CimGetFileStatBasicInformation(imagePath.c_str(),
|
|
filePath.c_str(),
|
|
&statInfo));
|
|
uint64_t filesize = statInfo.EndOfFile.QuadPart;
|
|
uint64_t length = filesize - offset;
|
|
uint64_t readoffset = offset;
|
|
|
|
std::wcout << "The content of the file in the CIM are as follows: " << std::endl;
|
|
|
|
while (length > 0)
|
|
{
|
|
std::byte buffer[BUFFERSIZE];
|
|
uint64_t readbytes = 0;
|
|
uint64_t remainingbytes = 0;
|
|
THROW_IF_FAILED(CimReadFile(imagePath.c_str(),
|
|
filePath.c_str(),
|
|
readoffset,
|
|
&buffer,
|
|
BUFFERSIZE,
|
|
&readbytes,
|
|
&remainingbytes));
|
|
|
|
if(readbytes == 0)
|
|
{
|
|
std::wcerr << "Unable to read file" << std::endl;
|
|
break;
|
|
}
|
|
|
|
DWORD dummy;
|
|
THROW_IF_WIN32_BOOL_FALSE(WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
|
|
buffer,
|
|
wil::safe_cast<uint32_t>(readbytes),
|
|
&dummy,
|
|
nullptr));
|
|
readoffset += readbytes;
|
|
length = remainingbytes;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int __cdecl wmain(int argc, const wchar_t** argv) try
|
|
{
|
|
if (argc != 5)
|
|
{
|
|
std::wcerr << "Usage:" << argv[0]
|
|
<< " <cim_path> <image_name> <file_to_add_path> <image_file_path>" << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
TogglePrivilege(SE_SECURITY_NAME, true);
|
|
TogglePrivilege(SE_BACKUP_NAME, true);
|
|
|
|
std::wstring cimPath(argv[1]), imageName(argv[2]), filePath(argv[3]), imageRelativePath(argv[4]);
|
|
|
|
// Create a new image and add a file
|
|
AddFileToNewCim(cimPath, imageName, filePath, imageRelativePath);
|
|
|
|
CompareFileWithCimFile(cimPath, imageName, imageRelativePath, filePath);
|
|
|
|
std::wstring imagepath = cimPath + L"\\" + imageName;
|
|
ReadFileFromCim(imagepath, imageRelativePath, 0);
|
|
|
|
auto attributes = GetFileAttributes(filePath.c_str());
|
|
std::wstring imageHardLinkPath(L"link");
|
|
|
|
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
// Extend the existing image by adding a hardlink
|
|
AddHardLinkInCim(cimPath, imageName, imageRelativePath, imageHardLinkPath);
|
|
|
|
ValidateHardLinkInCim(cimPath, imageName, imageRelativePath, imageHardLinkPath);
|
|
|
|
CompareFileWithCimFile(cimPath, imageName, imageHardLinkPath, filePath);
|
|
}
|
|
|
|
std::wstring forkImageName = imageName + L"_fork";
|
|
|
|
// Create a fork of the image where the path has been deleted
|
|
DeletePathFromCimFork(cimPath, imageName, imageRelativePath, forkImageName);
|
|
|
|
if (TestFileExistsInCim(cimPath, forkImageName, imageRelativePath))
|
|
{
|
|
std::wcerr << "The file " << imageRelativePath << " should have been removed in image "
|
|
<< forkImageName << std::endl;
|
|
}
|
|
|
|
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
// check the link still exists in the base image
|
|
if (!TestFileExistsInCim(cimPath, imageName, imageHardLinkPath))
|
|
{
|
|
std::wcerr << "The file " << imageHardLinkPath << " should not have been removed in image "
|
|
<< imageName << std::endl;
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception const& exception)
|
|
{
|
|
std::fputs(exception.what(), stderr);
|
|
RETURN_CAUGHT_EXCEPTION();
|
|
}
|