mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
660 lines
15 KiB
C
660 lines
15 KiB
C
/**
|
|
* @file fsptool/fsptool.c
|
|
*
|
|
* @copyright 2015-2024 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.
|
|
*/
|
|
|
|
#include <winfsp/winfsp.h>
|
|
#include <shared/um/minimal.h>
|
|
#include <aclapi.h>
|
|
#include <sddl.h>
|
|
|
|
#define PROGNAME "fsptool"
|
|
|
|
#define info(format, ...) printlog(GetStdHandle(STD_OUTPUT_HANDLE), format, __VA_ARGS__)
|
|
#define warn(format, ...) printlog(GetStdHandle(STD_ERROR_HANDLE), format, __VA_ARGS__)
|
|
#define fatal(ExitCode, format, ...) (warn(format, __VA_ARGS__), ExitProcess(ExitCode))
|
|
|
|
static void vprintlog(HANDLE h, const char *format, va_list ap)
|
|
{
|
|
char buf[1024];
|
|
/* wvsprintf is only safe with a 1024 byte buffer */
|
|
size_t len;
|
|
DWORD BytesTransferred;
|
|
|
|
wvsprintfA(buf, format, ap);
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
len = lstrlenA(buf);
|
|
buf[len++] = '\n';
|
|
|
|
WriteFile(h, buf, (DWORD)len, &BytesTransferred, 0);
|
|
}
|
|
|
|
static void printlog(HANDLE h, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
vprintlog(h, format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
fatal(ERROR_INVALID_PARAMETER,
|
|
"usage: %s COMMAND ARGS\n"
|
|
"\n"
|
|
"commands:\n"
|
|
" lsvol list file system devices (volumes)\n"
|
|
" id [NAME|SID|UID] print user id\n"
|
|
" perm [PATH|SDDL|UID:GID:MODE] print permissions\n"
|
|
" lsdrv list drivers\n"
|
|
" load load driver\n"
|
|
" unload unload driver (requires load driver priv)\n"
|
|
" ver print version\n",
|
|
PROGNAME);
|
|
}
|
|
|
|
static NTSTATUS FspToolGetVolumeList(PWSTR DeviceName,
|
|
PWCHAR *PVolumeListBuf, PSIZE_T PVolumeListSize)
|
|
{
|
|
NTSTATUS Result;
|
|
PWCHAR VolumeListBuf;
|
|
SIZE_T VolumeListSize;
|
|
|
|
*PVolumeListBuf = 0;
|
|
*PVolumeListSize = 0;
|
|
|
|
for (VolumeListSize = 1024;; VolumeListSize *= 2)
|
|
{
|
|
VolumeListBuf = MemAlloc(VolumeListSize);
|
|
if (0 == VolumeListBuf)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
Result = FspFsctlGetVolumeList(DeviceName,
|
|
VolumeListBuf, &VolumeListSize);
|
|
if (NT_SUCCESS(Result))
|
|
{
|
|
*PVolumeListBuf = VolumeListBuf;
|
|
*PVolumeListSize = VolumeListSize;
|
|
return Result;
|
|
}
|
|
|
|
MemFree(VolumeListBuf);
|
|
|
|
if (STATUS_BUFFER_TOO_SMALL != Result)
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
static WCHAR FspToolGetDriveLetter(PDWORD PLogicalDrives, PWSTR VolumeName)
|
|
{
|
|
WCHAR VolumeNameBuf[MAX_PATH];
|
|
WCHAR LocalNameBuf[3];
|
|
WCHAR Drive;
|
|
|
|
if (0 == *PLogicalDrives)
|
|
return 0;
|
|
|
|
LocalNameBuf[1] = L':';
|
|
LocalNameBuf[2] = L'\0';
|
|
|
|
for (Drive = 'Z'; 'A' <= Drive; Drive--)
|
|
if (0 != (*PLogicalDrives & (1 << (Drive - 'A'))))
|
|
{
|
|
LocalNameBuf[0] = Drive;
|
|
if (QueryDosDeviceW(LocalNameBuf, VolumeNameBuf, sizeof VolumeNameBuf / sizeof(WCHAR)))
|
|
{
|
|
if (0 == invariant_wcscmp(VolumeNameBuf, VolumeName))
|
|
{
|
|
*PLogicalDrives &= ~(1 << (Drive - 'A'));
|
|
return Drive;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
NTSTATUS FspToolGetTokenInfo(HANDLE Token,
|
|
TOKEN_INFORMATION_CLASS TokenInformationClass, PVOID *PInfo)
|
|
{
|
|
PVOID Info = 0;
|
|
DWORD Size;
|
|
NTSTATUS Result;
|
|
|
|
if (GetTokenInformation(Token, TokenInformationClass, 0, 0, &Size))
|
|
{
|
|
Result = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
}
|
|
|
|
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
|
|
{
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
goto exit;
|
|
}
|
|
|
|
Info = MemAlloc(Size);
|
|
if (0 == Info)
|
|
{
|
|
Result = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto exit;
|
|
}
|
|
|
|
if (!GetTokenInformation(Token, TokenInformationClass, Info, Size, &Size))
|
|
{
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
goto exit;
|
|
}
|
|
|
|
*PInfo = Info;
|
|
Result = STATUS_SUCCESS;
|
|
|
|
exit:
|
|
if (!NT_SUCCESS(Result))
|
|
MemFree(Info);
|
|
|
|
return Result;
|
|
}
|
|
|
|
NTSTATUS FspToolGetNameFromSid(PSID Sid, PWSTR *PName)
|
|
{
|
|
WCHAR Name[256], Domn[256];
|
|
DWORD NameSize, DomnSize;
|
|
SID_NAME_USE Use;
|
|
PWSTR P;
|
|
|
|
NameSize = sizeof Name / sizeof Name[0];
|
|
DomnSize = sizeof Domn / sizeof Domn[0];
|
|
if (!LookupAccountSidW(0, Sid, Name, &NameSize, Domn, &DomnSize, &Use))
|
|
{
|
|
Name[0] = L'\0';
|
|
Domn[0] = L'\0';
|
|
}
|
|
|
|
NameSize = lstrlenW(Name);
|
|
DomnSize = lstrlenW(Domn);
|
|
|
|
P = *PName = MemAlloc((DomnSize + 1 + NameSize + 1) * sizeof(WCHAR));
|
|
if (0 == P)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
if (0 < DomnSize)
|
|
{
|
|
memcpy(P, Domn, DomnSize * sizeof(WCHAR));
|
|
P[DomnSize] = L'\\';
|
|
P += DomnSize + 1;
|
|
}
|
|
memcpy(P, Name, NameSize * sizeof(WCHAR));
|
|
P[NameSize] = L'\0';
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS FspToolGetSidFromName(PWSTR Name, PSID *PSid)
|
|
{
|
|
PSID Sid;
|
|
WCHAR Domn[256];
|
|
DWORD SidSize, DomnSize;
|
|
SID_NAME_USE Use;
|
|
|
|
SidSize = 0;
|
|
DomnSize = sizeof Domn / sizeof Domn[0];
|
|
if (LookupAccountNameW(0, Name, 0, &SidSize, Domn, &DomnSize, &Use))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
Sid = MemAlloc(SidSize);
|
|
if (0 == Sid)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
DomnSize = sizeof Domn / sizeof Domn[0];
|
|
if (!LookupAccountNameW(0, Name, Sid, &SidSize, Domn, &DomnSize, &Use))
|
|
{
|
|
MemFree(Sid);
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
}
|
|
|
|
*PSid = Sid;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static int ver(int argc, wchar_t **argv)
|
|
{
|
|
if (1 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
UINT32 Version;
|
|
PWSTR SxsIdent;
|
|
|
|
Result = FspVersion(&Version);
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
SxsIdent = FspSxsIdent();
|
|
|
|
if (L'\0' == SxsIdent[0])
|
|
info("%u.%u", Version >> 16, Version & 0xFFFF);
|
|
else
|
|
info("%u.%u (SxS=%S)", Version >> 16, Version & 0xFFFF, SxsIdent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static NTSTATUS lsvol_dev(PWSTR DeviceName)
|
|
{
|
|
NTSTATUS Result;
|
|
PWCHAR VolumeListBuf, VolumeListBufEnd;
|
|
SIZE_T VolumeListSize;
|
|
DWORD LogicalDrives;
|
|
WCHAR Drive[3] = L"\0:";
|
|
|
|
Result = FspToolGetVolumeList(DeviceName, &VolumeListBuf, &VolumeListSize);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
VolumeListBufEnd = (PVOID)((PUINT8)VolumeListBuf + VolumeListSize);
|
|
|
|
LogicalDrives = GetLogicalDrives();
|
|
for (PWCHAR P = VolumeListBuf, VolumeName = P; VolumeListBufEnd > P; P++)
|
|
if (L'\0' == *P)
|
|
{
|
|
Drive[0] = FspToolGetDriveLetter(&LogicalDrives, VolumeName);
|
|
info("%-4S%S", Drive[0] ? Drive : L"-", VolumeName);
|
|
VolumeName = P + 1;
|
|
}
|
|
|
|
MemFree(VolumeListBuf);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static int lsvol(int argc, wchar_t **argv)
|
|
{
|
|
if (1 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
|
|
Result = lsvol_dev(L"" FSP_FSCTL_DISK_DEVICE_NAME);
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
Result = lsvol_dev(L"" FSP_FSCTL_NET_DEVICE_NAME);
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static NTSTATUS id_print_sid(const char *format, PSID Sid)
|
|
{
|
|
PWSTR Str = 0;
|
|
PWSTR Name = 0;
|
|
UINT32 Uid;
|
|
NTSTATUS Result;
|
|
|
|
if (!ConvertSidToStringSidW(Sid, &Str))
|
|
{
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
goto exit;
|
|
}
|
|
|
|
Result = FspToolGetNameFromSid(Sid, &Name);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
Result = FspPosixMapSidToUid(Sid, &Uid);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
info(format, Str, Name, Uid);
|
|
|
|
Result = STATUS_SUCCESS;
|
|
|
|
exit:
|
|
MemFree(Name);
|
|
LocalFree(Str);
|
|
|
|
return Result;
|
|
}
|
|
|
|
static NTSTATUS id_name(PWSTR Name)
|
|
{
|
|
PSID Sid = 0;
|
|
NTSTATUS Result;
|
|
|
|
Result = FspToolGetSidFromName(Name, &Sid);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
id_print_sid("%S(%S) (uid=%u)", Sid);
|
|
|
|
MemFree(Sid);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS id_sid(PWSTR SidStr)
|
|
{
|
|
PSID Sid = 0;
|
|
|
|
if (!ConvertStringSidToSid(SidStr, &Sid))
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
id_print_sid("%S(%S) (uid=%u)", Sid);
|
|
|
|
LocalFree(Sid);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS id_uid(PWSTR UidStr)
|
|
{
|
|
PSID Sid = 0;
|
|
UINT32 Uid;
|
|
NTSTATUS Result;
|
|
|
|
Uid = wcstouint(UidStr, &UidStr, 10, 0);
|
|
if (L'\0' != *UidStr)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
Result = FspPosixMapUidToSid(Uid, &Sid);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
id_print_sid("%S(%S) (uid=%u)", Sid);
|
|
|
|
FspDeleteSid(Sid, FspPosixMapUidToSid);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS id_user(void)
|
|
{
|
|
HANDLE Token = 0;
|
|
TOKEN_USER *Uinfo = 0;
|
|
TOKEN_OWNER *Oinfo = 0;
|
|
TOKEN_PRIMARY_GROUP *Ginfo = 0;
|
|
NTSTATUS Result;
|
|
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &Token))
|
|
{
|
|
Result = FspNtStatusFromWin32(GetLastError());
|
|
goto exit;
|
|
}
|
|
|
|
Result = FspToolGetTokenInfo(Token, TokenUser, &Uinfo);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
Result = FspToolGetTokenInfo(Token, TokenOwner, &Oinfo);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
Result = FspToolGetTokenInfo(Token, TokenPrimaryGroup, &Ginfo);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
id_print_sid("User=%S(%S) (uid=%u)", Uinfo->User.Sid);
|
|
id_print_sid("Owner=%S(%S) (uid=%u)", Oinfo->Owner);
|
|
id_print_sid("Group=%S(%S) (gid=%u)", Ginfo->PrimaryGroup);
|
|
|
|
Result = STATUS_SUCCESS;
|
|
|
|
exit:
|
|
MemFree(Ginfo);
|
|
MemFree(Oinfo);
|
|
MemFree(Uinfo);
|
|
|
|
if (0 != Token)
|
|
CloseHandle(Token);
|
|
|
|
return Result;
|
|
}
|
|
|
|
static int id(int argc, wchar_t **argv)
|
|
{
|
|
if (2 < argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
|
|
if (2 == argc)
|
|
{
|
|
if (L'S' == argv[1][0] && L'-' == argv[1][1] && L'1' == argv[1][2] && L'-' == argv[1][3])
|
|
Result = id_sid(argv[1]);
|
|
else
|
|
{
|
|
Result = id_uid(argv[1]);
|
|
if (STATUS_INVALID_PARAMETER == Result)
|
|
Result = id_name(argv[1]);
|
|
}
|
|
}
|
|
else
|
|
Result = id_user();
|
|
|
|
return FspWin32FromNtStatus(Result);
|
|
}
|
|
|
|
static NTSTATUS perm_print_sd(PSECURITY_DESCRIPTOR SecurityDescriptor)
|
|
{
|
|
UINT32 Uid, Gid, Mode;
|
|
PWSTR Sddl = 0;
|
|
NTSTATUS Result;
|
|
|
|
Result = FspPosixMapSecurityDescriptorToPermissions(SecurityDescriptor, &Uid, &Gid, &Mode);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
if (!ConvertSecurityDescriptorToStringSecurityDescriptorW(
|
|
SecurityDescriptor, SDDL_REVISION_1,
|
|
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
|
&Sddl, 0))
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
info("%S (perm=%u:%u:%d%d%d%d)",
|
|
Sddl, Uid, Gid, (Mode >> 9) & 7, (Mode >> 6) & 7, (Mode >> 3) & 7, Mode & 7);
|
|
|
|
LocalFree(Sddl);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS perm_path(PWSTR Path)
|
|
{
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = 0;
|
|
int ErrorCode;
|
|
|
|
ErrorCode = GetNamedSecurityInfoW(Path, SE_FILE_OBJECT,
|
|
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
|
0, 0, 0, 0, &SecurityDescriptor);
|
|
if (0 != ErrorCode)
|
|
return FspNtStatusFromWin32(ErrorCode);
|
|
|
|
perm_print_sd(SecurityDescriptor);
|
|
|
|
LocalFree(SecurityDescriptor);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS perm_sddl(PWSTR Sddl)
|
|
{
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = 0;
|
|
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
|
Sddl, SDDL_REVISION_1, &SecurityDescriptor, 0))
|
|
return FspNtStatusFromWin32(GetLastError());
|
|
|
|
perm_print_sd(SecurityDescriptor);
|
|
|
|
LocalFree(SecurityDescriptor);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS perm_mode(PWSTR PermStr)
|
|
{
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = 0;
|
|
UINT32 Uid, Gid, Mode;
|
|
NTSTATUS Result;
|
|
|
|
Uid = wcstouint(PermStr, &PermStr, 10, 0);
|
|
if (L':' != *PermStr)
|
|
return STATUS_INVALID_PARAMETER;
|
|
Gid = wcstouint(PermStr + 1, &PermStr, 10, 0);
|
|
if (L':' != *PermStr)
|
|
return STATUS_INVALID_PARAMETER;
|
|
Mode = wcstouint(PermStr + 1, &PermStr, 8, 0);
|
|
if (L'\0' != *PermStr)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
Result = FspPosixMapPermissionsToSecurityDescriptor(Uid, Gid, Mode, &SecurityDescriptor);
|
|
if (!NT_SUCCESS(Result))
|
|
return Result;
|
|
|
|
perm_print_sd(SecurityDescriptor);
|
|
|
|
FspDeleteSecurityDescriptor(SecurityDescriptor,
|
|
FspPosixMapPermissionsToSecurityDescriptor);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static int perm(int argc, wchar_t **argv)
|
|
{
|
|
if (2 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
PWSTR P;
|
|
|
|
for (P = argv[1]; *P; P++)
|
|
if (L'\\' == *P)
|
|
break;
|
|
|
|
if (L'\\' == *P)
|
|
Result = perm_path(argv[1]);
|
|
else
|
|
{
|
|
Result = perm_mode(argv[1]);
|
|
if (STATUS_INVALID_PARAMETER == Result)
|
|
Result = perm_sddl(argv[1]);
|
|
}
|
|
|
|
return FspWin32FromNtStatus(Result);
|
|
}
|
|
|
|
static VOID lsdrv_enumfn(PVOID Context, PWSTR ServiceName, BOOLEAN Running)
|
|
{
|
|
info("%-4s%S", Running ? "R" : "-", ServiceName);
|
|
}
|
|
|
|
static int lsdrv(int argc, wchar_t **argv)
|
|
{
|
|
if (1 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
|
|
Result = FspFsctlEnumServices(lsdrv_enumfn, 0);
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load(int argc, wchar_t **argv)
|
|
{
|
|
if (1 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
|
|
Result = FspFsctlStartService();
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
return 0;
|
|
}
|
|
static int unload(int argc, wchar_t **argv)
|
|
{
|
|
if (1 != argc)
|
|
usage();
|
|
|
|
NTSTATUS Result;
|
|
|
|
Result = FspFsctlStopService();
|
|
if (!NT_SUCCESS(Result))
|
|
return FspWin32FromNtStatus(Result);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int wmain(int argc, wchar_t **argv)
|
|
{
|
|
argc--;
|
|
argv++;
|
|
|
|
if (0 == argc)
|
|
usage();
|
|
|
|
if (0 == invariant_wcscmp(L"ver", argv[0]))
|
|
return ver(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"lsvol", argv[0]))
|
|
return lsvol(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"id", argv[0]))
|
|
return id(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"perm", argv[0]))
|
|
return perm(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"lsdrv", argv[0]))
|
|
return lsdrv(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"load", argv[0]))
|
|
return load(argc, argv);
|
|
else
|
|
if (0 == invariant_wcscmp(L"unload", argv[0]))
|
|
return unload(argc, argv);
|
|
else
|
|
usage();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void wmainCRTStartup(void)
|
|
{
|
|
DWORD Argc;
|
|
PWSTR *Argv;
|
|
|
|
Argv = CommandLineToArgvW(GetCommandLineW(), &Argc);
|
|
if (0 == Argv)
|
|
ExitProcess(GetLastError());
|
|
|
|
ExitProcess(wmain(Argc, Argv));
|
|
}
|