From 913b69bdf4e9e6576f7363bc0402c5a81350e3c5 Mon Sep 17 00:00:00 2001 From: Karolis2011 Date: Mon, 19 Aug 2019 23:35:43 +0300 Subject: [PATCH] Added a hacky, but working way to have directory simbolic links for now. --- ASS.Server/Helpers/EmetNativeMethods.cs | 259 ++++++++++++++++++++++++ ASS.Server/Helpers/FileSystemHelper.cs | 74 +++++++ ASS.Server/Services/ByondService.cs | 2 +- 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 ASS.Server/Helpers/EmetNativeMethods.cs 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."); }