phobos
phobos copied to clipboard
Adds symlink to windows
Feel free to improve however you see fit. I found it a little lacking to not have Windows support. I can't understand how to write in phobos way, but the base implementation is there.
Thanks for your pull request and interest in making D better, @MrcSnm! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Please verify that your PR follows this checklist:
- My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
- My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
- I have provided a detailed rationale explaining my changes
- New or modified functions have Ddoc comments (with
Params:
andReturns:
)
Please see CONTRIBUTING.md for more information.
If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.
Bugzilla references
Your PR doesn't reference any Bugzilla issue.
If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.
Testing this PR locally
If you don't have a local development environment setup, you can use Digger to test this PR:
dub run digger -- build "master + phobos#8794"
The doc says $(BLUE This function is POSIX-Only.)
so that line should be perhaps be changed to: POSIX and Windows Only
OK, here is a draft of a start:
version (StdDdoc)
{
/++
$(BLUE This function is Windows-Only.)
Creates a Windows symbolic link.
"Developer Mode" may need to be first enabled on the local
machine before unprivileged processes are allowed to create
symbolic links.
Note that symbolic links on Windows have slightly different
semantics than those on POSIX. For example, it must be known
at creation time whether the symbolic link points / will point
to a file or directory. To delete a Windows symbolic link,
`rmdir` must be used instead of `remove` if it was created as
pointing at a directory.
Params:
original = The location that is being linked. This is the
target path that's stored in the reparse point. The
path may be relative, in which case it is relative to
the symbolic link's parent directory.
link = The location of the junction to create. A relative
path is relative to the current working directory.
Throws:
$(LREF WindowsException) on error.
+/
// TODO: isDir(original) is not correct when original is a relative path
void createWindowsSymlink(in char[] original, in char[] link, bool originalIsDir = isDir(original));
/++
$(BLUE This function is Windows-Only.)
Creates a Windows filesystem junction.
Junctions are similar to symbolic links, but cannot be
relative and cannot point to files; on the other hand, they
can be created even without special privileges or "Developer
Mode".
Params:
original = The location that is being linked. This is the
target path that's stored in the reparse point. A
relative path is first resolved to an absolute path.
link = The location of the junction to create. A relative
path is relative to the current working directory.
Throws:
$(LREF WindowsException) on error.
+/
void createJunction(in char[] original, in char[] link);
}
else
version (Windows)
{
/// Create an NTFS reparse point (junction or symbolic link).
private void createReparsePoint
(string reparseBufferName, string extraInitialization, string reparseTagName)
(in char[] target, in char[] print, in char[] link)
{
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winioctl;
enum SYMLINK_FLAG_RELATIVE = 1;
HANDLE hLink = CreateFileW(
link.toUTF16z(),
GENERIC_READ | GENERIC_WRITE,
0, null,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
null);
wenforce(hLink && hLink != INVALID_HANDLE_VALUE, "CreateFileW");
scope(exit) CloseHandle(hLink);
enum pathOffset =
mixin(q{REPARSE_DATA_BUFFER.} ~ reparseBufferName) .offsetof +
mixin(q{REPARSE_DATA_BUFFER.} ~ reparseBufferName)._PathBuffer.offsetof;
auto targetW = target.toUTF16();
auto printW = print .toUTF16();
// Despite MSDN, two NUL-terminating characters are needed, one for each string.
auto pathBufferSize = targetW.length + 1 + printW.length + 1; // in chars
auto buf = new ubyte[pathOffset + pathBufferSize * WCHAR.sizeof];
auto r = cast(REPARSE_DATA_BUFFER*)buf.ptr;
r.ReparseTag = mixin(reparseTagName);
r.ReparseDataLength = to!WORD(buf.length - mixin(q{r.} ~ reparseBufferName).offsetof);
auto pathBuffer = mixin(q{r.} ~ reparseBufferName).PathBuffer;
auto p = pathBuffer;
mixin(q{r.} ~ reparseBufferName).SubstituteNameOffset = to!WORD((p-pathBuffer) * WCHAR.sizeof);
mixin(q{r.} ~ reparseBufferName).SubstituteNameLength = to!WORD(targetW.length * WCHAR.sizeof);
p[0..targetW.length] = targetW;
p += targetW.length;
*p++ = 0;
mixin(q{r.} ~ reparseBufferName).PrintNameOffset = to!WORD((p-pathBuffer) * WCHAR.sizeof);
mixin(q{r.} ~ reparseBufferName).PrintNameLength = to!WORD(printW .length * WCHAR.sizeof);
p[0..printW.length] = printW;
p += printW.length;
*p++ = 0;
assert(p-pathBuffer == pathBufferSize);
mixin(extraInitialization);
DWORD dwRet; // Needed despite MSDN
DeviceIoControl(hLink, FSCTL_SET_REPARSE_POINT, buf.ptr, buf.length.to!DWORD(), null, 0, &dwRet, null).wenforce("DeviceIoControl");
}
void createWindowsSymlink(in char[] original, in char[] link, bool originalIsDir = isDir(original))
{
import core.sys.windows.winnt;
if (originalIsDir)
mkdir(link);
else
write(link, "");
scope (failure)
if (originalIsDir)
rmdir(link);
else
remove(link);
createReparsePoint!(
q{SymbolicLinkReparseBuffer},
q{r.SymbolicLinkReparseBuffer.Flags = link.isAbsolute() ? 0 : SYMLINK_FLAG_RELATIVE;},
q{IO_REPARSE_TAG_SYMLINK}
)(original, original, link);
}
void createJunction(in char[] original, in char[] link)
{
mkdir(link);
scope(failure) rmdir(link);
auto target = `\??\` ~ (cast(string)original).absolutePath((cast(string)link.dirName).absolutePath).buildNormalizedPath;
if (target[$-1] != '\\')
target ~= '\\';
createReparsePoint!(
q{MountPointReparseBuffer},
q{},
q{IO_REPARSE_TAG_MOUNT_POINT}
)(target, null, link);
}
}
OK, here is a draft of a start:
I have changed to use your approach, also added the enum WindowsSymlinkHint
for making it clearer on the purpose.
I would like to ask what was your intent with isDir(original)
not being enough though.
Great, thanks!
I would like to ask what was your intent with
isDir(original)
not being enough though.
If original
is relative, it needs to be relative to the parent directory of the link object, not of the program's current working directory.
@atilaneves What do you think about adding these two Windows-only functions for symlinks?
I don't think OS-specific code like this belongs in Phobos.
I don't think OS-specific code like this belongs in Phobos.
symlink
is OS-specific code to Posix, std.windows.registry
is also. Having them in the std is incredibly useful.
Also phobos don't give other ways to create symlinks for Windows even though this is a pretty common operation, by being common IMO it already deserves that.