symlink-win.cpp revision b06e6c768b741a16534846e399da98ffc59693db
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync/* $Id$ */
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync/** @file
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * IPRT - Symbolic Links, Windows.
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync */
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync/*
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * Copyright (C) 2010 Oracle Corporation
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync *
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * This file is part of VirtualBox Open Source Edition (OSE), as
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * available from http://www.virtualbox.org. This file is free software;
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * you can redistribute it and/or modify it under the terms of the GNU
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * General Public License (GPL) as published by the Free Software
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * Foundation, in version 2 as it comes in the "COPYING" file of the
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync *
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * The contents of this file may alternatively be used under the terms
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * of the Common Development and Distribution License Version 1.0
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * VirtualBox OSE distribution, in which case the provisions of the
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * CDDL are applicable instead of those of the GPL.
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync *
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * You may elect to license modified versions of this file under the
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync * terms and conditions of either the GPL or the CDDL or both.
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync */
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync/*******************************************************************************
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync* Header Files *
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync*******************************************************************************/
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#define LOG_GROUP RTLOGGROUP_SYMLINK
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/symlink.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/assert.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/err.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/log.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/path.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/mem.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <iprt/string.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include "internal/path.h"
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#include <Windows.h>
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync/*******************************************************************************
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync* Structures and Typedefs *
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync*******************************************************************************/
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsynctypedef struct MY_REPARSE_DATA_BUFFER
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync{
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync ULONG ReparseTag;
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#define MY_IO_REPARSE_TAG_SYMLINK 0xa000000c
a3f3701cea1ba388e7c877955252bb7375eedebdvboxsync#define MY_IO_REPARSE_TAG_MOUNT_POINT 0xa0000003
USHORT ReparseDataLength;
USHORT Reserved;
union
{
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
#define MY_SYMLINK_FLAG_RELATIVE 1
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
UCHAR DataBuffer[1];
} GenericReparseBuffer;
};
} MY_REPARSE_DATA_BUFFER;
#define MY_FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS)
RTDECL(bool) RTSymlinkExists(const char *pszSymlink)
{
bool fRc = false;
RTFSOBJINFO ObjInfo;
int rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
if (RT_SUCCESS(rc))
fRc = RTFS_IS_SYMLINK(ObjInfo.Attr.fMode);
LogFlow(("RTSymlinkExists(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc));
return fRc;
}
RTDECL(bool) RTSymlinkIsDangling(const char *pszSymlink)
{
bool fRc = false;
RTFSOBJINFO ObjInfo;
int rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
if (RT_SUCCESS(rc))
{
fRc = RTFS_IS_SYMLINK(ObjInfo.Attr.fMode);
if (fRc)
{
rc = RTPathQueryInfoEx(pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
fRc = !RT_SUCCESS_NP(rc);
}
}
LogFlow(("RTSymlinkIsDangling(%p={%s}): returns %RTbool\n", pszSymlink, pszSymlink, fRc));
return fRc;
}
RTDECL(int) RTSymlinkCreate(const char *pszSymlink, const char *pszTarget, RTSYMLINKTYPE enmType)
{
/*
* Validate the input.
*/
AssertReturn(enmType > RTSYMLINKTYPE_INVALID && enmType < RTSYMLINKTYPE_END, VERR_INVALID_PARAMETER);
AssertPtrReturn(pszSymlink, VERR_INVALID_POINTER);
AssertPtrReturn(pszTarget, VERR_INVALID_POINTER);
/*
* Resolve the API.
*/
typedef BOOLEAN (WINAPI *PFNCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD);
static PFNCREATESYMBOLICLINKW s_pfnCreateSymbolicLinkW = NULL;
static bool s_fTried = FALSE;
if (!s_fTried)
{
HMODULE hmod = LoadLibrary("KERNEL32.DLL");
if (hmod)
{
PFNCREATESYMBOLICLINKW pfn = (PFNCREATESYMBOLICLINKW)GetProcAddress(hmod, "CreateSymbolicLinkW");
if (pfn)
s_pfnCreateSymbolicLinkW = pfn;
}
s_fTried = true;
}
if (!s_pfnCreateSymbolicLinkW)
{
LogFlow(("RTSymlinkCreate(%p={%s}, %p={%s}, %d): returns VERR_NOT_SUPPORTED - Windows API not found\n",
pszSymlink, pszSymlink, pszTarget, pszTarget, enmType));
return VERR_NOT_SUPPORTED;
}
/*
* Convert the paths.
*/
PRTUTF16 pwszNativeSymlink;
int rc = RTStrToUtf16(pszSymlink, &pwszNativeSymlink);
if (RT_SUCCESS(rc))
{
PRTUTF16 pwszNativeTarget;
rc = RTStrToUtf16(pszTarget, &pwszNativeTarget);
if (RT_SUCCESS(rc))
{
/*
* Massage the target path, determin the link type.
*/
size_t cchTarget = strlen(pszTarget);
size_t cchVolSpecTarget = rtPathVolumeSpecLen(pszTarget);
#if 0 /* looks like this isn't needed after all. That makes everything much simper :-) */
if ( cchTarget > RT_MIN(cchVolSpecTarget, 1)
&& RTPATH_IS_SLASH(pszTarget[cchTarget - 1]))
{
size_t cwcNativeTarget = RTUtf16Len(pwszNativeTarget);
size_t offFromEnd = 1;
while ( offFromEnd < cchTarget
&& cchTarget - offFromEnd >= cchVolSpecTarget
&& RTPATH_IS_SLASH(pszTarget[cchTarget - offFromEnd]))
{
Assert(offFromEnd < cwcNativeTarget);
pwszNativeTarget[cwcNativeTarget - offFromEnd] = 0;
offFromEnd++;
}
}
#endif
if (enmType == RTSYMLINKTYPE_UNKNOWN)
{
if ( cchTarget > cchVolSpecTarget
&& RTPATH_IS_SLASH(pszTarget[cchTarget - 1]))
enmType = RTSYMLINKTYPE_DIR;
else if (cchVolSpecTarget)
{
/** @todo this is subject to sharing violations. */
DWORD dwAttr = GetFileAttributesW(pwszNativeTarget);
if ( dwAttr != INVALID_FILE_ATTRIBUTES
&& (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
enmType = RTSYMLINKTYPE_DIR;
}
else
{
/** @todo Join the symlink directory with the target and
* look up the attributes on that. -lazy bird. */
}
}
/*
* Create the link.
*/
if (s_pfnCreateSymbolicLinkW(pwszNativeSymlink, pwszNativeTarget, enmType == RTSYMLINKTYPE_DIR))
rc = VINF_SUCCESS;
else
rc = RTErrConvertFromWin32(GetLastError());
RTUtf16Free(pwszNativeTarget);
}
RTUtf16Free(pwszNativeSymlink);
}
LogFlow(("RTSymlinkCreate(%p={%s}, %p={%s}, %d): returns %Rrc\n", pszSymlink, pszSymlink, pszTarget, pszTarget, enmType, rc));
return rc;
}
RTDECL(int) RTSymlinkDelete(const char *pszSymlink)
{
/*
* Convert the path.
*/
PRTUTF16 pwszNativeSymlink;
int rc = RTStrToUtf16(pszSymlink, &pwszNativeSymlink);
if (RT_SUCCESS(rc))
{
/*
* We have to use different APIs depending on whether this is a
* directory or file link. This means we're subject to one more race
* than on posix at the moment. We could probably avoid this though,
* if we wanted to go talk with the native API layer below Win32...
*/
DWORD dwAttr = GetFileAttributesW(pwszNativeSymlink);
if (dwAttr != INVALID_FILE_ATTRIBUTES)
{
if (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT)
{
BOOL fRc;
if (dwAttr & FILE_ATTRIBUTE_DIRECTORY)
fRc = RemoveDirectoryW(pwszNativeSymlink);
else
fRc = DeleteFileW(pwszNativeSymlink);
if (fRc)
rc = VINF_SUCCESS;
else
rc = RTErrConvertFromWin32(GetLastError());
}
else
rc = VERR_NOT_SYMLINK;
}
else
rc = RTErrConvertFromWin32(GetLastError());
RTUtf16Free(pwszNativeSymlink);
}
LogFlow(("RTSymlinkDelete(%p={%s}): returns %Rrc\n", pszSymlink, pszSymlink, rc));
return rc;
}
RTDECL(int) RTSymlinkRead(const char *pszSymlink, char *pszTarget, size_t cbTarget)
{
char *pszMyTarget;
int rc = RTSymlinkReadA(pszSymlink, &pszMyTarget);
if (RT_SUCCESS(rc))
{
rc = RTStrCopy(pszTarget, cbTarget, pszMyTarget);
RTStrFree(pszMyTarget);
}
LogFlow(("RTSymlinkRead(%p={%s}): returns %Rrc\n", pszSymlink, pszSymlink, rc));
return rc;
}
RTDECL(int) RTSymlinkReadA(const char *pszSymlink, char **ppszTarget)
{
AssertPtr(ppszTarget);
PRTUTF16 pwszNativeSymlink;
int rc = RTStrToUtf16(pszSymlink, &pwszNativeSymlink);
if (RT_SUCCESS(rc))
{
HANDLE hSymlink = CreateFileW(pwszNativeSymlink,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hSymlink != INVALID_HANDLE_VALUE)
{
DWORD cbReturned = 0;
union
{
MY_REPARSE_DATA_BUFFER Buf;
uint8_t abBuf[16*_1K + sizeof(WCHAR)];
} u;
if (DeviceIoControl(hSymlink,
MY_FSCTL_GET_REPARSE_POINT,
NULL /*pInBuffer */,
0 /*cbInBuffer */,
&u.Buf,
sizeof(u) - sizeof(WCHAR),
&cbReturned,
NULL /*pOverlapped*/))
{
if (u.Buf.ReparseTag == MY_IO_REPARSE_TAG_SYMLINK)
{
PWCHAR pwszTarget = &u.Buf.SymbolicLinkReparseBuffer.PathBuffer[0];
pwszTarget += u.Buf.SymbolicLinkReparseBuffer.SubstituteNameOffset / 2;
pwszTarget[u.Buf.SymbolicLinkReparseBuffer.SubstituteNameLength / 2] = 0;
if ( !(u.Buf.SymbolicLinkReparseBuffer.Flags & MY_SYMLINK_FLAG_RELATIVE)
&& pwszTarget[0] == '\\'
&& pwszTarget[1] == '?'
&& pwszTarget[2] == '?'
&& pwszTarget[3] == '\\'
&& pwszTarget[4] != 0
)
pwszTarget += 4;
rc = RTUtf16ToUtf8(pwszTarget, ppszTarget);
}
else
rc = VERR_NOT_SYMLINK;
}
else
rc = RTErrConvertFromWin32(GetLastError());
CloseHandle(hSymlink);
}
else
rc = RTErrConvertFromWin32(GetLastError());
RTUtf16Free(pwszNativeSymlink);
}
if (RT_SUCCESS(rc))
LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc *ppszTarget=%p:{%s}\n", pszSymlink, pszSymlink, ppszTarget, rc, *ppszTarget, *ppszTarget));
else
LogFlow(("RTSymlinkReadA(%p={%s},%p): returns %Rrc\n", pszSymlink, pszSymlink, ppszTarget, rc));
return rc;
}