// Windows/FileLink.cpp | |
#include "StdAfx.h" | |
#include "../../C/CpuArch.h" | |
#ifdef SUPPORT_DEVICE_FILE | |
#include "../../C/Alloc.h" | |
#endif | |
#include "FileDir.h" | |
#include "FileFind.h" | |
#include "FileIO.h" | |
#include "FileName.h" | |
#ifndef _UNICODE | |
extern bool g_IsNT; | |
#endif | |
namespace NWindows { | |
namespace NFile { | |
using namespace NName; | |
/* | |
Reparse Points (Junctions and Symbolic Links): | |
struct | |
{ | |
UInt32 Tag; | |
UInt16 Size; // not including starting 8 bytes | |
UInt16 Reserved; // = 0 | |
UInt16 SubstituteOffset; // offset in bytes from start of namesChars | |
UInt16 SubstituteLen; // size in bytes, it doesn't include tailed NUL | |
UInt16 PrintOffset; // offset in bytes from start of namesChars | |
UInt16 PrintLen; // size in bytes, it doesn't include tailed NUL | |
[UInt32] Flags; // for Symbolic Links only. | |
UInt16 namesChars[] | |
} | |
MOUNT_POINT (Junction point): | |
1) there is NUL wchar after path | |
2) Default Order in table: | |
Substitute Path | |
Print Path | |
3) pathnames can not contain dot directory names | |
SYMLINK: | |
1) there is no NUL wchar after path | |
2) Default Order in table: | |
Print Path | |
Substitute Path | |
*/ | |
/* | |
static const UInt32 kReparseFlags_Alias = (1 << 29); | |
static const UInt32 kReparseFlags_HighLatency = (1 << 30); | |
static const UInt32 kReparseFlags_Microsoft = ((UInt32)1 << 31); | |
#define _my_IO_REPARSE_TAG_HSM (0xC0000004L) | |
#define _my_IO_REPARSE_TAG_HSM2 (0x80000006L) | |
#define _my_IO_REPARSE_TAG_SIS (0x80000007L) | |
#define _my_IO_REPARSE_TAG_WIM (0x80000008L) | |
#define _my_IO_REPARSE_TAG_CSV (0x80000009L) | |
#define _my_IO_REPARSE_TAG_DFS (0x8000000AL) | |
#define _my_IO_REPARSE_TAG_DFSR (0x80000012L) | |
*/ | |
#define Get16(p) GetUi16(p) | |
#define Get32(p) GetUi32(p) | |
#define Set16(p, v) SetUi16(p, v) | |
#define Set32(p, v) SetUi32(p, v) | |
static const wchar_t *k_LinkPrefix = L"\\??\\"; | |
static const unsigned k_LinkPrefix_Size = 4; | |
static const bool IsLinkPrefix(const wchar_t *s) | |
{ | |
return IsString1PrefixedByString2(s, k_LinkPrefix); | |
} | |
/* | |
static const wchar_t *k_VolumePrefix = L"Volume{"; | |
static const bool IsVolumeName(const wchar_t *s) | |
{ | |
return IsString1PrefixedByString2(s, k_VolumePrefix); | |
} | |
*/ | |
void WriteString(Byte *dest, const wchar_t *path) | |
{ | |
for (;;) | |
{ | |
wchar_t c = *path++; | |
if (c == 0) | |
return; | |
Set16(dest, (UInt16)c); | |
dest += 2; | |
} | |
} | |
bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink) | |
{ | |
bool isAbs = IsAbsolutePath(path); | |
if (!isAbs && !isSymLink) | |
return false; | |
bool needPrintName = true; | |
if (IsSuperPath(path)) | |
{ | |
path += kSuperPathPrefixSize; | |
if (!IsDrivePath(path)) | |
needPrintName = false; | |
} | |
const unsigned add_Prefix_Len = isAbs ? k_LinkPrefix_Size : 0; | |
unsigned len2 = MyStringLen(path) * 2; | |
const unsigned len1 = len2 + add_Prefix_Len * 2; | |
if (!needPrintName) | |
len2 = 0; | |
unsigned totalNamesSize = (len1 + len2); | |
/* some WIM imagex software uses old scheme for symbolic links. | |
so we can old scheme for byte to byte compatibility */ | |
bool newOrderScheme = isSymLink; | |
// newOrderScheme = false; | |
if (!newOrderScheme) | |
totalNamesSize += 2 * 2; | |
const size_t size = 8 + 8 + (isSymLink ? 4 : 0) + totalNamesSize; | |
dest.Alloc(size); | |
memset(dest, 0, size); | |
const UInt32 tag = isSymLink ? | |
_my_IO_REPARSE_TAG_SYMLINK : | |
_my_IO_REPARSE_TAG_MOUNT_POINT; | |
Byte *p = dest; | |
Set32(p, tag); | |
Set16(p + 4, (UInt16)(size - 8)); | |
Set16(p + 6, 0); | |
p += 8; | |
unsigned subOffs = 0; | |
unsigned printOffs = 0; | |
if (newOrderScheme) | |
subOffs = len2; | |
else | |
printOffs = len1 + 2; | |
Set16(p + 0, (UInt16)subOffs); | |
Set16(p + 2, (UInt16)len1); | |
Set16(p + 4, (UInt16)printOffs); | |
Set16(p + 6, (UInt16)len2); | |
p += 8; | |
if (isSymLink) | |
{ | |
UInt32 flags = isAbs ? 0 : _my_SYMLINK_FLAG_RELATIVE; | |
Set32(p, flags); | |
p += 4; | |
} | |
if (add_Prefix_Len != 0) | |
WriteString(p + subOffs, k_LinkPrefix); | |
WriteString(p + subOffs + add_Prefix_Len * 2, path); | |
if (needPrintName) | |
WriteString(p + printOffs, path); | |
return true; | |
} | |
static void GetString(const Byte *p, unsigned len, UString &res) | |
{ | |
wchar_t *s = res.GetBuffer(len); | |
for (unsigned i = 0; i < len; i++) | |
s[i] = Get16(p + i * 2); | |
s[len] = 0; | |
res.ReleaseBuffer(); | |
} | |
bool CReparseAttr::Parse(const Byte *p, size_t size) | |
{ | |
if (size < 8) | |
return false; | |
Tag = Get32(p); | |
UInt32 len = Get16(p + 4); | |
if (len + 8 > size) | |
return false; | |
/* | |
if ((type & kReparseFlags_Alias) == 0 || | |
(type & kReparseFlags_Microsoft) == 0 || | |
(type & 0xFFFF) != 3) | |
*/ | |
if (Tag != _my_IO_REPARSE_TAG_MOUNT_POINT && | |
Tag != _my_IO_REPARSE_TAG_SYMLINK) | |
// return true; | |
return false; | |
if (Get16(p + 6) != 0) // padding | |
return false; | |
p += 8; | |
size -= 8; | |
if (len != size) // do we need that check? | |
return false; | |
if (len < 8) | |
return false; | |
unsigned subOffs = Get16(p); | |
unsigned subLen = Get16(p + 2); | |
unsigned printOffs = Get16(p + 4); | |
unsigned printLen = Get16(p + 6); | |
len -= 8; | |
p += 8; | |
Flags = 0; | |
if (Tag == _my_IO_REPARSE_TAG_SYMLINK) | |
{ | |
if (len < 4) | |
return false; | |
Flags = Get32(p); | |
len -= 4; | |
p += 4; | |
} | |
if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen) | |
return false; | |
if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen) | |
return false; | |
GetString(p + subOffs, subLen >> 1, SubsName); | |
GetString(p + printOffs, printLen >> 1, PrintName); | |
return true; | |
} | |
bool CReparseShortInfo::Parse(const Byte *p, size_t size) | |
{ | |
const Byte *start = p; | |
Offset= 0; | |
Size = 0; | |
if (size < 8) | |
return false; | |
UInt32 Tag = Get32(p); | |
UInt32 len = Get16(p + 4); | |
if (len + 8 > size) | |
return false; | |
/* | |
if ((type & kReparseFlags_Alias) == 0 || | |
(type & kReparseFlags_Microsoft) == 0 || | |
(type & 0xFFFF) != 3) | |
*/ | |
if (Tag != _my_IO_REPARSE_TAG_MOUNT_POINT && | |
Tag != _my_IO_REPARSE_TAG_SYMLINK) | |
// return true; | |
return false; | |
if (Get16(p + 6) != 0) // padding | |
return false; | |
p += 8; | |
size -= 8; | |
if (len != size) // do we need that check? | |
return false; | |
if (len < 8) | |
return false; | |
unsigned subOffs = Get16(p); | |
unsigned subLen = Get16(p + 2); | |
unsigned printOffs = Get16(p + 4); | |
unsigned printLen = Get16(p + 6); | |
len -= 8; | |
p += 8; | |
// UInt32 Flags = 0; | |
if (Tag == _my_IO_REPARSE_TAG_SYMLINK) | |
{ | |
if (len < 4) | |
return false; | |
// Flags = Get32(p); | |
len -= 4; | |
p += 4; | |
} | |
if ((subOffs & 1) != 0 || subOffs > len || len - subOffs < subLen) | |
return false; | |
if ((printOffs & 1) != 0 || printOffs > len || len - printOffs < printLen) | |
return false; | |
Offset = (unsigned)(p - start) + subOffs; | |
Size = subLen; | |
return true; | |
} | |
bool CReparseAttr::IsOkNamePair() const | |
{ | |
if (IsLinkPrefix(SubsName)) | |
{ | |
if (!IsDrivePath(SubsName.Ptr(k_LinkPrefix_Size))) | |
return PrintName.IsEmpty(); | |
if (wcscmp(SubsName.Ptr(k_LinkPrefix_Size), PrintName) == 0) | |
return true; | |
} | |
return wcscmp(SubsName, PrintName) == 0; | |
} | |
/* | |
bool CReparseAttr::IsVolume() const | |
{ | |
if (!IsLinkPrefix(SubsName)) | |
return false; | |
return IsVolumeName(SubsName.Ptr(k_LinkPrefix_Size)); | |
} | |
*/ | |
UString CReparseAttr::GetPath() const | |
{ | |
UString s = SubsName; | |
if (IsLinkPrefix(s)) | |
{ | |
s.ReplaceOneCharAtPos(1, '\\'); | |
if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) | |
s.DeleteFrontal(k_LinkPrefix_Size); | |
} | |
return s; | |
} | |
#ifdef SUPPORT_DEVICE_FILE | |
namespace NSystem | |
{ | |
bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); | |
} | |
#endif | |
#ifndef UNDER_CE | |
namespace NIO { | |
bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMATION *fileInfo) | |
{ | |
reparseData.Free(); | |
CInFile file; | |
if (!file.OpenReparse(path)) | |
return false; | |
if (fileInfo) | |
file.GetFileInformation(fileInfo); | |
const unsigned kBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; | |
CByteArr buf(kBufSize); | |
DWORD returnedSize; | |
if (!file.DeviceIoControlOut(my_FSCTL_GET_REPARSE_POINT, buf, kBufSize, &returnedSize)) | |
return false; | |
reparseData.CopyFrom(buf, returnedSize); | |
return true; | |
} | |
static bool CreatePrefixDirOfFile(CFSTR path) | |
{ | |
FString path2 = path; | |
int pos = path2.ReverseFind(FCHAR_PATH_SEPARATOR); | |
if (pos < 0) | |
return true; | |
#ifdef _WIN32 | |
if (pos == 2 && path2[1] == L':') | |
return true; // we don't create Disk folder; | |
#endif | |
path2.DeleteFrom(pos); | |
return NDir::CreateComplexDir(path2); | |
} | |
// If there is Reprase data already, it still writes new Reparse data | |
bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) | |
{ | |
NFile::NFind::CFileInfo fi; | |
if (fi.Find(path)) | |
{ | |
if (fi.IsDir() != isDir) | |
{ | |
::SetLastError(ERROR_DIRECTORY); | |
return false; | |
} | |
} | |
else | |
{ | |
if (isDir) | |
{ | |
if (!NDir::CreateComplexDir(path)) | |
return false; | |
} | |
else | |
{ | |
CreatePrefixDirOfFile(path); | |
COutFile file; | |
if (!file.Create(path, CREATE_NEW)) | |
return false; | |
} | |
} | |
COutFile file; | |
if (!file.Open(path, | |
FILE_SHARE_WRITE, | |
OPEN_EXISTING, | |
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)) | |
return false; | |
DWORD returnedSize; | |
if (!file.DeviceIoControl(my_FSCTL_SET_REPARSE_POINT, (void *)data, size, NULL, 0, &returnedSize)) | |
return false; | |
return true; | |
} | |
} | |
#endif | |
}} |