mirror of
				https://github.com/winfsp/winfsp.git
				synced 2025-10-30 19:48:38 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			511 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			511 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * @file CustomActions.cpp
 | |
|  *
 | |
|  * @copyright 2015-2025 Bill Zissimopoulos
 | |
|  */
 | |
| /*
 | |
|  * This file is part of WinFsp.
 | |
|  *
 | |
|  * You can redistribute it and/or modify it under the terms of the GNU
 | |
|  * General Public License version 3 as published by the Free Software
 | |
|  * Foundation.
 | |
|  *
 | |
|  * Licensees holding a valid commercial license may use this software
 | |
|  * in accordance with the commercial license agreement provided in
 | |
|  * conjunction with the software.  The terms and conditions of any such
 | |
|  * commercial license agreement shall govern, supersede, and render
 | |
|  * ineffective any application of the GPLv3 license to this software,
 | |
|  * notwithstanding of any reference thereto in the software or
 | |
|  * associated repository.
 | |
|  */
 | |
| 
 | |
| #define WIN32_LEAN_AND_MEAN
 | |
| #include <windows.h>
 | |
| #include <shellapi.h>
 | |
| #include <msiquery.h>
 | |
| #include <wcautil.h>
 | |
| #include <strutil.h>
 | |
| 
 | |
| static HINSTANCE DllInstance;
 | |
| 
 | |
| UINT __stdcall InstanceID(MSIHANDLE MsiHandle)
 | |
| {
 | |
| #if 0
 | |
|     WCHAR MessageBuf[64];
 | |
|     wsprintfW(MessageBuf, L"PID=%ld", GetCurrentProcessId());
 | |
|     MessageBoxW(0, MessageBuf, L"" __FUNCTION__ " Break", MB_OK);
 | |
| #endif
 | |
| 
 | |
|     HRESULT hr = S_OK;
 | |
|     UINT err = ERROR_SUCCESS;
 | |
|     SYSTEMTIME SystemTime;
 | |
|     WCHAR Result[32+1];
 | |
| 
 | |
|     hr = WcaInitialize(MsiHandle, __FUNCTION__);
 | |
|     ExitOnFailure(hr, "Failed to initialize");
 | |
| 
 | |
|     WcaLog(LOGMSG_STANDARD, "Initialized");
 | |
| 
 | |
|     GetSystemTime(&SystemTime);
 | |
|     wsprintfW(Result, L"%04u%02u%02uT%02u%02u%02uZ",
 | |
|         SystemTime.wYear,
 | |
|         SystemTime.wMonth,
 | |
|         SystemTime.wDay,
 | |
|         SystemTime.wHour,
 | |
|         SystemTime.wMinute,
 | |
|         SystemTime.wSecond);
 | |
| 
 | |
|     /*
 | |
|      * Sleep 1 second to ensure timestamp uniqueness.
 | |
|      *
 | |
|      * Note that this assumes that time is monotonic and users do not change time.
 | |
|      * Disable for now as it is assumed that the installation takes more than 1 second to complete.
 | |
|      */
 | |
|     //Sleep(1000);
 | |
| 
 | |
|     WcaSetProperty(L"" __FUNCTION__, Result);
 | |
| 
 | |
| LExit:
 | |
|     err = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
 | |
|     return WcaFinalize(err);
 | |
| }
 | |
| 
 | |
| UINT __stdcall ServiceRunning(MSIHANDLE MsiHandle)
 | |
| {
 | |
| #if 0
 | |
|     WCHAR MessageBuf[64];
 | |
|     wsprintfW(MessageBuf, L"PID=%ld", GetCurrentProcessId());
 | |
|     MessageBoxW(0, MessageBuf, L"" __FUNCTION__ " Break", MB_OK);
 | |
| #endif
 | |
| 
 | |
|     HRESULT hr = S_OK;
 | |
|     UINT err = ERROR_SUCCESS;
 | |
|     PWSTR ServiceName = 0;
 | |
|     SC_HANDLE ScmHandle = 0;
 | |
|     SC_HANDLE SvcHandle = 0;
 | |
|     SERVICE_STATUS ServiceStatus;
 | |
|     int Result = 0;
 | |
| 
 | |
|     hr = WcaInitialize(MsiHandle, __FUNCTION__);
 | |
|     ExitOnFailure(hr, "Failed to initialize");
 | |
| 
 | |
|     hr = WcaGetProperty(L"" __FUNCTION__, &ServiceName);
 | |
|     ExitOnFailure(hr, "Failed to get ServiceName");
 | |
| 
 | |
|     WcaLog(LOGMSG_STANDARD, "Initialized: \"%S\"", ServiceName);
 | |
| 
 | |
|     ScmHandle = OpenSCManagerW(0, 0, 0);
 | |
|     ExitOnNullWithLastError(ScmHandle, hr, "Failed to open SCM");
 | |
| 
 | |
|     SvcHandle = OpenServiceW(ScmHandle, ServiceName, SERVICE_QUERY_STATUS);
 | |
|     if (0 != SvcHandle && QueryServiceStatus(SvcHandle, &ServiceStatus))
 | |
|         Result = SERVICE_STOPPED != ServiceStatus.dwCurrentState;
 | |
| 
 | |
|     WcaSetIntProperty(L"" __FUNCTION__, Result);
 | |
| 
 | |
| LExit:
 | |
|     if (0 != SvcHandle)
 | |
|         CloseServiceHandle(SvcHandle);
 | |
|     if (0 != ScmHandle)
 | |
|         CloseServiceHandle(ScmHandle);
 | |
| 
 | |
|     ReleaseStr(ServiceName);
 | |
| 
 | |
|     err = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
 | |
|     return WcaFinalize(err);
 | |
| }
 | |
| 
 | |
| UINT __stdcall DeferredAction(MSIHANDLE MsiHandle)
 | |
| {
 | |
| #if 0
 | |
|     WCHAR MessageBuf[64];
 | |
|     wsprintfW(MessageBuf, L"PID=%ld", GetCurrentProcessId());
 | |
|     MessageBoxW(0, MessageBuf, L"" __FUNCTION__ " Break", MB_OK);
 | |
| #endif
 | |
| 
 | |
|     HRESULT hr = S_OK;
 | |
|     UINT err = ERROR_SUCCESS;
 | |
|     PWSTR CommandLine = 0;
 | |
|     PWSTR *Argv;
 | |
|     int Argc;
 | |
|     CHAR ProcName[64];
 | |
|     FARPROC Proc;
 | |
| 
 | |
|     hr = WcaInitialize(MsiHandle, __FUNCTION__);
 | |
|     ExitOnFailure(hr, "Failed to initialize");
 | |
| 
 | |
|     hr = WcaGetProperty(L"CustomActionData", &CommandLine);
 | |
|     ExitOnFailure(hr, "Failed to get CommandLine");
 | |
| 
 | |
|     WcaLog(LOGMSG_STANDARD, "Initialized: \"%S\"", CommandLine);
 | |
| 
 | |
|     Argv = CommandLineToArgvW(CommandLine, &Argc);
 | |
|     ExitOnNullWithLastError(Argv, hr, "Failed to CommandLineToArgvW");
 | |
| 
 | |
|     if (0 < Argc)
 | |
|     {
 | |
|         if (0 == WideCharToMultiByte(CP_UTF8, 0, Argv[0], -1, ProcName, sizeof ProcName, 0, 0))
 | |
|             ExitWithLastError(hr, "Failed to WideCharToMultiByte");
 | |
| 
 | |
|         Proc = GetProcAddress(DllInstance, ProcName);
 | |
|         ExitOnNullWithLastError(Proc, hr, "Failed to GetProcAddress");
 | |
| 
 | |
|         err = ((HRESULT (*)(int, PWSTR *))Proc)(Argc, Argv);
 | |
|         ExitOnWin32Error(err, hr, "Failed to %s", ProcName);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         hr = E_INVALIDARG;
 | |
|         ExitOnFailure(hr, "Failed to get arguments");
 | |
|     }
 | |
| 
 | |
| LExit:
 | |
|     LocalFree(Argv);
 | |
|     ReleaseStr(CommandLine);
 | |
| 
 | |
|     err = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
 | |
|     return WcaFinalize(err);
 | |
| }
 | |
| 
 | |
| static DWORD MakeSymlink(PWSTR Symlink, PWSTR Target);
 | |
| static DWORD MakeJunction(PWSTR Junction, PWSTR Target);
 | |
| static DWORD CreateJunction(PWSTR Junction, PWSTR Target);
 | |
| static DWORD RemoveFile(PWSTR FileName);
 | |
| 
 | |
| DWORD InstallSymlinks(int Argc, PWSTR *Argv)
 | |
| {
 | |
|     /* usage: InstallSymlinks/InstallJunctions SourceDir TargetDir Name... */
 | |
| 
 | |
|     DWORD Result;
 | |
|     BOOL Junctions;
 | |
|     PWSTR SourceDir, TargetDir;
 | |
|     WCHAR SourcePath[MAX_PATH], TargetPath[MAX_PATH];
 | |
|     int SourceDirLen, TargetDirLen, Len;
 | |
| 
 | |
|     if (4 > Argc)
 | |
|     {
 | |
|         Result = ERROR_INVALID_PARAMETER;
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Junctions = 0 == lstrcmpW(L"InstallJunctions", Argv[0]);
 | |
|     SourceDir = Argv[1];
 | |
|     TargetDir = Argv[2];
 | |
|     SourceDirLen = lstrlenW(SourceDir);
 | |
|     TargetDirLen = lstrlenW(TargetDir);
 | |
| 
 | |
|     for (int Argi = 3; Argc > Argi; Argi++)
 | |
|     {
 | |
|         Len = lstrlenW(Argv[Argi]);
 | |
|         if (MAX_PATH < SourceDirLen + Len + 1 || MAX_PATH < TargetDirLen + Len + 1)
 | |
|         {
 | |
|             Result = ERROR_FILENAME_EXCED_RANGE;
 | |
|             goto exit;
 | |
|         }
 | |
| 
 | |
|         memcpy(SourcePath, SourceDir, SourceDirLen * sizeof(WCHAR));
 | |
|         memcpy(SourcePath + SourceDirLen, Argv[Argi], Len * sizeof(WCHAR));
 | |
|         SourcePath[SourceDirLen + Len] = L'\0';
 | |
| 
 | |
|         memcpy(TargetPath, TargetDir, TargetDirLen * sizeof(WCHAR));
 | |
|         memcpy(TargetPath + TargetDirLen, Argv[Argi], Len * sizeof(WCHAR));
 | |
|         TargetPath[TargetDirLen + Len] = L'\0';
 | |
| 
 | |
|         if (!Junctions)
 | |
|             Result = MakeSymlink(SourcePath, TargetPath);
 | |
|         else
 | |
|             Result = MakeJunction(SourcePath, TargetPath);
 | |
| #if 0
 | |
|     WCHAR MessageBuf[1024];
 | |
|     wsprintfW(MessageBuf, L"MakeSymlink(\"%s\", \"%s\") = %lu", SourcePath, TargetPath, Result);
 | |
|     MessageBoxW(0, MessageBuf, L"TRACE", MB_OK);
 | |
| #endif
 | |
|         if (ERROR_SUCCESS != Result)
 | |
|             goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| DWORD InstallJunctions(int Argc, PWSTR *Argv)
 | |
| {
 | |
|     return InstallSymlinks(Argc, Argv);
 | |
| }
 | |
| 
 | |
| DWORD RemoveFiles(int Argc, PWSTR *Argv)
 | |
| {
 | |
|     /* usage: RemoveFiles Dir Name... */
 | |
| 
 | |
|     DWORD Result;
 | |
|     PWSTR Dir;
 | |
|     WCHAR Path[MAX_PATH];
 | |
|     int DirLen, Len;
 | |
| 
 | |
|     if (3 > Argc)
 | |
|     {
 | |
|         Result = ERROR_INVALID_PARAMETER;
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Dir = Argv[1];
 | |
|     DirLen = lstrlenW(Dir);
 | |
| 
 | |
|     for (int Argi = 2; Argc > Argi; Argi++)
 | |
|     {
 | |
|         Len = lstrlenW(Argv[Argi]);
 | |
|         if (MAX_PATH < DirLen + Len + 1)
 | |
|         {
 | |
|             Result = ERROR_FILENAME_EXCED_RANGE;
 | |
|             goto exit;
 | |
|         }
 | |
| 
 | |
|         memcpy(Path, Dir, DirLen * sizeof(WCHAR));
 | |
|         memcpy(Path + DirLen, Argv[Argi], Len * sizeof(WCHAR));
 | |
|         Path[DirLen + Len] = L'\0';
 | |
| 
 | |
|         Result = RemoveFile(Path);
 | |
| #if 0
 | |
|     WCHAR MessageBuf[1024];
 | |
|     wsprintfW(MessageBuf, L"RemoveFile(\"%s\") = %lu", Path, Result);
 | |
|     MessageBoxW(0, MessageBuf, L"TRACE", MB_OK);
 | |
| #endif
 | |
|         if (ERROR_SUCCESS != Result)
 | |
|             goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| static DWORD MakeSymlink(PWSTR Symlink, PWSTR Target)
 | |
| {
 | |
|     DWORD Result;
 | |
|     DWORD FileAttributes, Flags;
 | |
| 
 | |
|     RemoveFile(Symlink);
 | |
| 
 | |
|     FileAttributes = GetFileAttributesW(Target);
 | |
|     if (INVALID_FILE_ATTRIBUTES == FileAttributes)
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         goto exit;
 | |
|     }
 | |
|     Flags = 0 != (FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
 | |
| 
 | |
|     if (!CreateSymbolicLinkW(Symlink, Target, Flags))
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         RemoveFile(Symlink);
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| static DWORD MakeJunction(PWSTR Junction, PWSTR Target)
 | |
| {
 | |
|     DWORD Result;
 | |
|     DWORD FileAttributes;
 | |
| 
 | |
|     RemoveFile(Junction);
 | |
| 
 | |
|     FileAttributes = GetFileAttributesW(Target);
 | |
|     if (INVALID_FILE_ATTRIBUTES == FileAttributes)
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         goto exit;
 | |
|     }
 | |
|     if (0 == (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
 | |
|     {
 | |
|         Result = ERROR_DIRECTORY;
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = CreateJunction(Junction, Target);
 | |
|     if (ERROR_SUCCESS != Result)
 | |
|     {
 | |
|         RemoveFile(Junction);
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| static DWORD CreateJunction(PWSTR Junction, PWSTR Target)
 | |
| {
 | |
|     /*
 | |
|      * The REPARSE_DATA_BUFFER definitions appear to be missing from the user mode headers.
 | |
|      */
 | |
|     typedef struct _REPARSE_DATA_BUFFER
 | |
|     {
 | |
|         ULONG ReparseTag;
 | |
|         USHORT ReparseDataLength;
 | |
|         USHORT Reserved;
 | |
|         union
 | |
|         {
 | |
|             struct
 | |
|             {
 | |
|                 USHORT SubstituteNameOffset;
 | |
|                 USHORT SubstituteNameLength;
 | |
|                 USHORT PrintNameOffset;
 | |
|                 USHORT PrintNameLength;
 | |
|                 ULONG Flags;
 | |
|                 WCHAR PathBuffer[1];
 | |
|             } SymbolicLinkReparseBuffer;
 | |
|             struct
 | |
|             {
 | |
|                 USHORT SubstituteNameOffset;
 | |
|                 USHORT SubstituteNameLength;
 | |
|                 USHORT PrintNameOffset;
 | |
|                 USHORT PrintNameLength;
 | |
|                 WCHAR PathBuffer[1];
 | |
|             } MountPointReparseBuffer;
 | |
|             struct
 | |
|             {
 | |
|                 UCHAR DataBuffer[1];
 | |
|             } GenericReparseBuffer;
 | |
|         } DUMMYUNIONNAME;
 | |
|     } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
 | |
|     const LONG REPARSE_DATA_BUFFER_HEADER_SIZE =
 | |
|         FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer);
 | |
|     const DWORD FSCTL_SET_REPARSE_POINT = 0x000900a4;
 | |
| 
 | |
|     DWORD Result;
 | |
|     HANDLE Handle = INVALID_HANDLE_VALUE;
 | |
|     USHORT TargetLength, ReparseDataLength;
 | |
|     PREPARSE_DATA_BUFFER ReparseData = 0;
 | |
|     PWSTR PathBuffer;
 | |
|     DWORD Bytes;
 | |
| 
 | |
|     if (!(
 | |
|         ((L'A' <= Target[0] && Target[0] <= L'Z') || (L'a' <= Target[0] && Target[0] <= L'z')) &&
 | |
|         L':' == Target[1]
 | |
|         ))
 | |
|     {
 | |
|         Result = ERROR_INVALID_NAME;
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Handle = CreateFileW(Junction,
 | |
|         FILE_WRITE_ATTRIBUTES,
 | |
|         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
 | |
|         0,
 | |
|         CREATE_NEW,
 | |
|         FILE_ATTRIBUTE_DIRECTORY |
 | |
|             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS,
 | |
|         0);
 | |
|     if (INVALID_HANDLE_VALUE == Handle)
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     TargetLength = (USHORT)lstrlenW(Target) * sizeof(WCHAR);
 | |
|     ReparseDataLength = (USHORT)(
 | |
|         FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) -
 | |
|         FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer)) +
 | |
|         4 * sizeof(WCHAR) + 2 * (TargetLength + sizeof(WCHAR));
 | |
|     ReparseData = (PREPARSE_DATA_BUFFER)
 | |
|         HeapAlloc(GetProcessHeap(), 0, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength);
 | |
|     if (0 == ReparseData)
 | |
|     {
 | |
|         Result = ERROR_NO_SYSTEM_RESOURCES;
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     ReparseData->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
 | |
|     ReparseData->ReparseDataLength = ReparseDataLength;
 | |
|     ReparseData->Reserved = 0;
 | |
|     ReparseData->MountPointReparseBuffer.SubstituteNameOffset = 0;
 | |
|     ReparseData->MountPointReparseBuffer.SubstituteNameLength =
 | |
|         4 * sizeof(WCHAR) + TargetLength;
 | |
|     ReparseData->MountPointReparseBuffer.PrintNameOffset =
 | |
|         ReparseData->MountPointReparseBuffer.SubstituteNameLength + sizeof(WCHAR);
 | |
|     ReparseData->MountPointReparseBuffer.PrintNameLength =
 | |
|         TargetLength;
 | |
| 
 | |
|     PathBuffer = ReparseData->MountPointReparseBuffer.PathBuffer;
 | |
|     PathBuffer[0] = L'\\';
 | |
|     PathBuffer[1] = L'?';
 | |
|     PathBuffer[2] = L'?';
 | |
|     PathBuffer[3] = L'\\';
 | |
|     memcpy(PathBuffer + 4, Target, TargetLength);
 | |
|     PathBuffer[4 + TargetLength / sizeof(WCHAR)] = L'\0';
 | |
| 
 | |
|     PathBuffer = ReparseData->MountPointReparseBuffer.PathBuffer +
 | |
|         (ReparseData->MountPointReparseBuffer.PrintNameOffset) / sizeof(WCHAR);
 | |
|     memcpy(PathBuffer, Target, TargetLength);
 | |
|     PathBuffer[TargetLength / sizeof(WCHAR)] = L'\0';
 | |
| 
 | |
|     if (!DeviceIoControl(Handle, FSCTL_SET_REPARSE_POINT,
 | |
|         ReparseData, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseData->ReparseDataLength,
 | |
|         0, 0,
 | |
|         &Bytes, 0))
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     if (INVALID_HANDLE_VALUE != Handle)
 | |
|         CloseHandle(Handle);
 | |
| 
 | |
|     if (0 != ReparseData)
 | |
|         HeapFree(GetProcessHeap(), 0, ReparseData);
 | |
| 
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| static DWORD RemoveFile(PWSTR FileName)
 | |
| {
 | |
|     DWORD Result;
 | |
| 
 | |
|     if (!RemoveDirectoryW(FileName))
 | |
|     {
 | |
|         Result = GetLastError();
 | |
|         if (ERROR_DIRECTORY != Result)
 | |
|             goto exit;
 | |
| 
 | |
|         if (!DeleteFileW(FileName))
 | |
|         {
 | |
|             Result = GetLastError();
 | |
|             goto exit;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Result = ERROR_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| extern "C"
 | |
| BOOL WINAPI DllMain(HINSTANCE Instance, DWORD Reason, PVOID Reserved)
 | |
| {
 | |
|     switch(Reason)
 | |
|     {
 | |
|     case DLL_PROCESS_ATTACH:
 | |
|         DllInstance = Instance;
 | |
|         WcaGlobalInitialize(Instance);
 | |
|         break;
 | |
|     case DLL_PROCESS_DETACH:
 | |
|         WcaGlobalFinalize();
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     return TRUE;
 | |
| }
 |