diff --git a/ASS.Server/Helpers/EmetNativeMethods.cs b/ASS.Server/Helpers/EmetNativeMethods.cs
new file mode 100644
index 0000000..e7bcc55
--- /dev/null
+++ b/ASS.Server/Helpers/EmetNativeMethods.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace ASS.Server.Helpers
+{
+
+ // TODO: Remove when https://github.com/joshudson/Emet/pull/3 is merged
+ internal class EmetNativeMethods
+ {
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct dirent64 {
+ internal ulong d_ino;
+ internal ulong d_off;
+ internal ushort d_reclen;
+ internal byte d_type;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst=256)]
+ internal byte[] d_name;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct statbuf64 {
+ internal ulong st_dev;
+ internal ulong st_ino;
+ internal ulong st_nlink;
+ internal uint st_mode;
+ internal uint st_uid;
+ internal uint st_gid;
+ internal ulong st_rdev;
+ internal ulong st_size;
+ internal ulong st_blksize;
+ internal ulong st_blocks; /* number of 512 byte blocks */
+ internal long st_atime;
+ internal ulong st_atime_nsec;
+ internal long st_mtime;
+ internal ulong st_mtime_nsec;
+ internal long st_ctime;
+ internal ulong st_ctime_nsec;
+ internal ulong st_glibc_reserved0;
+ internal ulong st_glibc_reserved1;
+ internal ulong st_glibc_reserved2;
+ };
+
+ internal const int statbuf_version = 1;
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern IntPtr opendir([MarshalAs(UnmanagedType.LPArray)] byte[] path);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern IntPtr closedir(IntPtr dir);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int readdir64_r(IntPtr dir, ref dirent64 entry, out IntPtr result);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int __xstat64(int version, [MarshalAs(UnmanagedType.LPArray)] byte[] file, out statbuf64 buf);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int __lxstat64(int version, [MarshalAs(UnmanagedType.LPArray)] byte[] file, out statbuf64 buf);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int __fxstat64(int version, IntPtr file, out statbuf64 buf);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int rename([MarshalAs(UnmanagedType.LPArray)] byte[] from, [MarshalAs(UnmanagedType.LPArray)] byte[] to);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int link([MarshalAs(UnmanagedType.LPArray)] byte[] filename, [MarshalAs(UnmanagedType.LPArray)] byte[] linktarget);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern int symlink([MarshalAs(UnmanagedType.LPArray)] byte[] filename, [MarshalAs(UnmanagedType.LPArray)] byte[] linktarget);
+
+ [DllImport("libc.so.6", SetLastError=true)]
+ internal static extern long readlink([MarshalAs(UnmanagedType.LPArray)] byte[] filename, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, long buflen);
+
+ internal const int FileFsVolumeInformation = 1;
+ internal const int FileBasicInformation = 4;
+ internal const int FileStandardInformation = 5;
+ internal const int FileInternalInformation = 6;
+ internal const int FileAllInformation = 18;
+ internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
+ internal const uint MOVEFILE_REPLACE_EXISTING = 1;
+ internal const uint FILE_SHARE_READ = 1;
+ internal const uint FILE_SHARE_WRITE = 2;
+ internal const uint FILE_SHARE_DELETE = 4;
+ internal const uint FILE_SHARE_ALL = 7;
+ internal const uint CREATE_ALWAYS = 2;
+ internal const uint CREATE_NEW = 1;
+ internal const uint OPEN_ALWAYS = 4;
+ internal const uint OPEN_EXISTING = 3;
+ internal const uint TRUNCATE_EXISTING = 5;
+ internal const uint FILE_ATTRIBUTE_DIRECTORY = 16;
+ internal const uint FILE_ATTRIBUTE_REPARSE_POINT = 1024;
+ internal const uint FILE_READ_ATTRIBUTES = 128;
+ internal const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
+ internal const uint FILE_FLAG_OPEN_NO_RECALL = 0x1000000;
+ internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x200000;
+ internal const uint SYMBOLIC_LINK_FLAG_DIRECTORY = 1;
+ internal const uint SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 2;
+ internal const uint FSCTL_GET_REPARSE_POINT = 589992;
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct IO_STATUS_BLOCK {
+ internal IntPtr Status;
+ internal UIntPtr Information;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct REPARSE_DATA_BUFFER_SYMLINK {
+ internal uint ReparseTag;
+ internal ushort ReparseDataLength;
+ internal ushort Reserved;
+ internal ushort SubstituteNameOffset;
+ internal ushort SubstituteNameLength;
+ internal ushort PrintNameOffset;
+ internal ushort PrintNameLength;
+ internal uint Flags;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_BASIC_INFORMATION {
+ internal ulong CreationTime; // 100ns units since Jan 1 1601
+ internal ulong LastAccessTime;
+ internal ulong LastWriteTime;
+ internal ulong ChangeTime;
+ internal uint FileAttributes;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_STANDARD_INFORMATION {
+ internal ulong AllocationSize;
+ internal ulong EndOfFile;
+ internal uint NumberOfLinks;
+ internal bool DeletePending;
+ internal bool Directory;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_INTERNAL_INFORMATION {
+ internal ulong file_index;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_FS_VOLUME_INFORMATION {
+ internal ulong VolumeCreationTime;
+ internal uint VolumeSerialNumber;
+ internal uint VolumeLabelLength;
+ internal bool SupportsObjects;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_EA_INFORMATION {
+ internal uint EaSize;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_ACCESS_INFORMATION {
+ internal uint AccessMask;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_POSITION_INFORMATION {
+ internal ulong CurrentByteOffset;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_MODE_INFORMATION {
+ internal uint Mode;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_ALIGNMENT_INFORMATION {
+ internal uint AlignmentRequirement;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILE_ALL_INFORMATION {
+ internal FILE_BASIC_INFORMATION BasicInformation;
+ internal FILE_STANDARD_INFORMATION StandardInformation;
+ internal FILE_INTERNAL_INFORMATION InternalInformation;
+ internal FILE_EA_INFORMATION EaInformation;
+ internal FILE_ACCESS_INFORMATION AccessInformation;
+ internal FILE_POSITION_INFORMATION PositionInformation;
+ internal FILE_MODE_INFORMATION ModeInformation;
+ internal FILE_ALIGNMENT_INFORMATION AlignmentInformation;
+ internal uint FileNameLength; // Variable structure
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FILETIME {
+ internal uint dwLowDateTime;
+ internal uint dwHighDateTime;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
+ internal struct WIN32_FIND_DATA {
+ internal uint dwFileAttributes;
+ internal FILETIME ftCreationTime;
+ internal FILETIME ftLastAccessTime;
+ internal FILETIME ftLastWriteTime;
+ internal uint nFileSizeHigh;
+ internal uint nFileSizeLow;
+ internal uint dwReserved0;
+ internal uint dwReserved1;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)]
+ internal string cFileName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=14)]
+ internal string cAlternateFileName;
+ }
+
+ [DllImport("ntdll.dll")]
+ internal static extern int NtQueryVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK block, out FILE_FS_VOLUME_INFORMATION volume_info, int length, int Class);
+ [DllImport("ntdll.dll")]
+ internal static extern int NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK block, out FILE_BASIC_INFORMATION basic_info, int length, int Class);
+ [DllImport("ntdll.dll")]
+ internal static extern int NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK block, out FILE_STANDARD_INFORMATION standard_info, int length, int Class);
+ [DllImport("ntdll.dll")]
+ internal static extern int NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK block, out FILE_INTERNAL_INFORMATION internal_info, int length, int Class);
+ [DllImport("ntdll.dll")]
+ internal static extern int NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK block, out FILE_ALL_INFORMATION all_info, int length, int Class);
+
+ [DllImport("ntdll.dll")]
+ internal static extern int RtlNtStatusToDosError(int ntstatus);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ internal static extern void SetLastError(int error);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ internal static extern IntPtr FindFirstFileW(string filename, out WIN32_FIND_DATA FindFileData);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ internal static extern byte FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATA FindFileData);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ internal static extern byte FindClose(IntPtr hFindFile);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ internal static extern byte MoveFileEx(string oldfilename, string newfilename, uint flags);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ internal static extern byte CloseHandle(IntPtr hFindFile);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ internal static extern IntPtr CreateFileW(string filename, uint dwDesiredAccess, uint dwShareMode,
+ IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ internal static extern byte DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr inptr, uint inlen,
+ [MarshalAs(UnmanagedType.LPArray)] byte[] outdata, uint outlen, out uint returned, IntPtr overlapped);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ internal static extern byte CreateSymbolicLinkW(string lpSymlinkFileName, string lpTargetFileName, uint dwFlags);
+
+ [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
+ internal static extern byte CreateHardLinkW(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
+ }
+}
diff --git a/ASS.Server/Helpers/FileSystemHelper.cs b/ASS.Server/Helpers/FileSystemHelper.cs
index df4e12c..599aa96 100644
--- a/ASS.Server/Helpers/FileSystemHelper.cs
+++ b/ASS.Server/Helpers/FileSystemHelper.cs
@@ -1,8 +1,10 @@
using ASS.Server.Extensions;
+using Emet.FileSystems;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.InteropServices;
using System.Text;
namespace ASS.Server.Helpers
@@ -22,5 +24,77 @@ namespace ASS.Server.Helpers
return Path.GetFullPath(Path.Combine(extraPaths.PreAppend(paths)));
return Path.GetFullPath(Path.Combine(extraPaths.PreAppend(paths).PreAppend(globalConfig["WorkDir"])));
}
+
+ public static void CreateRelativeSymbolicLink(string target, string link, FileType targetHint = FileType.LinkTargetHintNotAvailable)
+ {
+ if (targetHint == FileType.File || targetHint == FileType.Directory)
+ target = Path.GetRelativePath(Path.Combine(link, ".."), target);
+ CreateSymbolicLink(target, link, targetHint);
+ }
+
+ // TODO: Remove when https://github.com/joshudson/Emet/pull/3 is merged
+ ///Creates a symbolic link to a file
+ ///The path to write to the symbolic link
+ ///The path to create the new link at
+ ///The type of node the link is referring to
+ ///An IO error occurred
+ ///linkpath doesn't exist and targethint was neither File nor Directory and this platform uses explicit link types
+ ///If targetpath doesn't exist and targethint is not provided, this call will fail on Windows.
+ public static void CreateSymbolicLink(string targetpath, string linkpath, FileType targethint = FileType.LinkTargetHintNotAvailable)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ var btargetpath = NameToByteArray(targetpath);
+ var blinkpath = NameToByteArray(linkpath);
+ if (EmetNativeMethods.symlink(btargetpath, blinkpath) != 0)
+ {
+ var errno = Marshal.GetLastWin32Error();
+ var ci = new System.ComponentModel.Win32Exception();
+ throw new IOException(ci.Message, errno);
+ }
+ } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ uint flags = 0;
+ if (targethint != FileType.File && targethint != FileType.Directory)
+ {
+ if (!Path.IsPathRooted(targetpath))
+ {
+ var ldname = Path.GetDirectoryName(linkpath);
+ if (string.IsNullOrEmpty(ldname)) ldname = DirectoryEntry.CurrentDirectoryName;
+ targetpath = Path.Combine(ldname, targetpath);
+ }
+ var node = new DirectoryEntry(targetpath, FileSystem.FollowSymbolicLinks.Never);
+ var hint = (node.FileType == FileType.SymbolicLink) ? node.LinkTargetHint : node.FileType;
+ if (hint == FileType.Directory)
+ flags = EmetNativeMethods.SYMBOLIC_LINK_FLAG_DIRECTORY;
+ else if (hint != FileType.File)
+ throw new PlatformNotSupportedException("Windows can't handle symbolic links to file system nodes that don't exist.");
+ }
+ if (targethint == FileType.Directory)
+ flags = EmetNativeMethods.SYMBOLIC_LINK_FLAG_DIRECTORY;
+ if (0 == EmetNativeMethods.CreateSymbolicLinkW(linkpath, targetpath, flags))
+ {
+ var errno = (int)Marshal.GetLastWin32Error();
+ if (errno == 1314)
+ {
+ flags |= EmetNativeMethods.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ if (0 != EmetNativeMethods.CreateSymbolicLinkW(linkpath, targetpath, flags))
+ return;
+ var errno2 = (int)Marshal.GetLastWin32Error();
+ if (errno2 != 1314 && errno2 != 1 && errno2 != 0xA0)
+ errno = errno2; // Try to get a better error
+ }
+ var ci = new System.ComponentModel.Win32Exception();
+ throw new IOException(ci.Message, unchecked((int)0x80070000 | errno));
+ }
+ }
+ }
+
+ internal static byte[] NameToByteArray(string name)
+ {
+ var count = Encoding.UTF8.GetByteCount(name);
+ var bytes = new byte[count + 1];
+ Encoding.UTF8.GetBytes(name, 0, name.Length, bytes, 0);
+ return bytes;
+ }
}
}
diff --git a/ASS.Server/Services/ByondService.cs b/ASS.Server/Services/ByondService.cs
index 68e0c69..7874516 100644
--- a/ASS.Server/Services/ByondService.cs
+++ b/ASS.Server/Services/ByondService.cs
@@ -90,7 +90,7 @@ namespace ASS.Server.Services
throw new Exception($"Byond version '{version.Major}.{version.Minor}' data mismatches folder name or ByondVersion data. Please manually remove this version.");
if (Directory.Exists(GetByondDirectoryPath()))
Directory.Delete(GetByondDirectoryPath());
- Emet.FileSystems.FileSystem.CreateSymbolicLink(getByondVersionDirectoryName(version), GetByondDirectoryPath());
+ FileSystemHelper.CreateRelativeSymbolicLink(GetByondDirectoryPath(version), GetByondDirectoryPath(), Emet.FileSystems.FileType.Directory);
if (!version.Equals(getVersion()))
throw new Exception($"Byond version switch failed.");
}