From e56ff9656dd2a3601480b7183675c592ab8ea56d Mon Sep 17 00:00:00 2001 From: Kaido Kert Date: Thu, 15 Aug 2024 12:24:39 -0700 Subject: [PATCH] Also delete win symlinking code --- starboard/tools/port_symlink.py | 47 +--- starboard/tools/port_symlink_test.py | 3 +- starboard/tools/win_symlink.py | 288 ----------------------- starboard/tools/win_symlink_fast.py | 223 ------------------ starboard/tools/win_symlink_fast_test.py | 54 ----- starboard/tools/win_symlink_test.py | 85 ------- 6 files changed, 12 insertions(+), 688 deletions(-) delete mode 100644 starboard/tools/win_symlink.py delete mode 100644 starboard/tools/win_symlink_fast.py delete mode 100644 starboard/tools/win_symlink_fast_test.py delete mode 100644 starboard/tools/win_symlink_test.py diff --git a/starboard/tools/port_symlink.py b/starboard/tools/port_symlink.py index 26be7a68b17d..93a3cbe12ad9 100644 --- a/starboard/tools/port_symlink.py +++ b/starboard/tools/port_symlink.py @@ -21,30 +21,19 @@ import sys from starboard.tools import util -from starboard.tools import win_symlink # This file is still executed with Python2 in CI. # pylint:disable=consider-using-f-string -def IsWindows(): - return sys.platform in ['win32', 'cygwin'] - - def ToLongPath(path): """Converts to a path that supports long filenames.""" - if IsWindows(): - return win_symlink.ToDevicePath(path) - else: - return path + return path def IsSymLink(path): """Platform neutral version os os.path.islink().""" - if IsWindows(): - return win_symlink.IsReparsePoint(path) - else: - return os.path.islink(path) + return os.path.islink(path) def MakeSymLink(target_path, link_path): @@ -57,37 +46,26 @@ def MakeSymLink(target_path, link_path): Returns: None """ - if IsWindows(): - win_symlink.CreateReparsePoint(target_path, link_path) - else: - util.MakeDirs(os.path.dirname(link_path)) - os.symlink(target_path, link_path) + util.MakeDirs(os.path.dirname(link_path)) + os.symlink(target_path, link_path) def ReadSymLink(link_path): """Returns the path (abs. or rel.) to the folder referred to by link_path.""" - if IsWindows(): - path = win_symlink.ReadReparsePoint(link_path) - else: - try: - path = os.readlink(link_path) - except OSError: - path = None + try: + path = os.readlink(link_path) + except OSError: + path = None return path def DelSymLink(link_path): - if IsWindows(): - win_symlink.UnlinkReparsePoint(link_path) - else: - os.unlink(link_path) + os.unlink(link_path) def Rmtree(path): """See Rmtree() for documentation of this function.""" - if IsWindows(): - func = win_symlink.RmtreeShallow - elif not os.path.islink(path): + if not os.path.islink(path): func = shutil.rmtree else: os.unlink(path) @@ -97,10 +75,7 @@ def Rmtree(path): def OsWalk(root_dir, topdown=True, onerror=None, followlinks=False): - if IsWindows(): - return win_symlink.OsWalk(root_dir, topdown, onerror, followlinks) - else: - return os.walk(root_dir, topdown, onerror, followlinks) + return os.walk(root_dir, topdown, onerror, followlinks) def _CreateArgumentParser(): diff --git a/starboard/tools/port_symlink_test.py b/starboard/tools/port_symlink_test.py index 304c6e954dcd..f6bae33312c6 100644 --- a/starboard/tools/port_symlink_test.py +++ b/starboard/tools/port_symlink_test.py @@ -107,8 +107,7 @@ def testRmtreeRemovesBrokenLink(self): # os.path.exists() will return false for broken links (not true for reparse # points on Windows) since their target does not exist. Rmtree # implementations should still be able to remove the link. - if not port_symlink.IsWindows(): - self.assertFalse(os.path.exists(self.link_dir)) + self.assertFalse(os.path.exists(self.link_dir)) self.assertTrue(IsSymLink(self.link_dir)) Rmtree(self.link_dir) self.assertFalse(IsSymLink(self.link_dir)) diff --git a/starboard/tools/win_symlink.py b/starboard/tools/win_symlink.py deleted file mode 100644 index 8c0505ded818..000000000000 --- a/starboard/tools/win_symlink.py +++ /dev/null @@ -1,288 +0,0 @@ -#!/usr/bin/python -# Copyright 2018 The Cobalt Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Provides functions for symlinking on Windows. - -Reparse points: Are os-level symlinks for folders which can be created without -admin access. Symlinks for folders are supported using this mechanism. Note -that reparse points require special care for traversal, because reparse points -are often skipped or treated as files by the various python path manipulation -functions in os and shutil modules. rmtree() as a replacement for -shutil.rmtree() is provided. - -""" - -import logging -import os -import re -import shutil -import stat -import subprocess -import time - -_RETRY_TIMES = 10 - - -def ToDevicePath(dos_path, encoding=None): - r"""Convert to a device path to avoid MAX_PATH limits on Windows. - - https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation - - Args: - dos_path: Path to a file that's not already a device path. - encoding: Optional character encoding of dos_path, if it's not unicode. - - Returns: - Absolute device path starting with "\\?\". - """ - # This type is compatible with both python 2 and 3. - unicode_type = type('') - if not isinstance(dos_path, unicode_type) and encoding is not None: - dos_path = dos_path.decode(encoding) - path = os.path.abspath(dos_path) - if path.startswith('\\\\'): - return '\\\\?\\UNC\\' + path[2:] - return '\\\\?\\' + path - - -def _RemoveEmptyDirectory(path): - """Removes a directory with retry amounts.""" - for i in range(0, _RETRY_TIMES): - try: - os.chmod(path, stat.S_IWRITE) - os.rmdir(path) - return - except Exception: # pylint: disable=broad-except - if i == _RETRY_TIMES - 1: - raise - else: - time.sleep(.1) - - -def _RmtreeOsWalk(root_dir): - """Walks the directory structure to delete directories and files.""" - del_dirs = [] # Defer deletion of directories. - if IsReparsePoint(root_dir): - UnlinkReparsePoint(root_dir) - return - for root, dirs, files in OsWalk(root_dir, followlinks=False): - for name in files: - path = os.path.join(root, name) - os.remove(path) - for name in dirs: - path = os.path.join(root, name) - if IsReparsePoint(path): - UnlinkReparsePoint(path) - else: - del_dirs.append(path) - # At this point, all files should be deleted and all symlinks should be - # unlinked. - for d in del_dirs + [root_dir]: - try: - if os.path.isdir(d): - shutil.rmtree(d) - except Exception as err: # pylint: disable=broad-except - logging.exception('Error while deleting: %s', err) - - -def _RmtreeShellCmd(root_dir): - subprocess.call(['cmd', '/c', 'rmdir', '/S', '/Q', root_dir]) - - -def RmtreeShallow(root_dir): - """Emulates shutil.rmtree on linux. - - Will delete symlinks but doesn't follow them. Note that shutil.rmtree on - windows will follow the symlink and delete the files in the original - directory! - - Args: - root_dir: The start path to delete files. - """ - try: - # This can fail if there are very long file names. - _RmtreeOsWalk(root_dir) - except OSError: - # This fallback will handle very long file. Note that it is VERY slow - # in comparison to the _RmtreeOsWalk() version. - _RmtreeShellCmd(root_dir) - if os.path.isdir(root_dir): - logging.error('Directory %s still exists.', root_dir) - - -def ReadReparsePointShell(path): - """Implements reading a reparse point via a shell command.""" - cmd_parts = ['cmd', '/C', 'dir', os.path.dirname(path)] - try: - out = subprocess.check_output(cmd_parts) - except subprocess.CalledProcessError: - # Expected if the link doesn't exist. - return None - try: - pattern = re.compile(f'.*[ ]+{os.path.basename(path)} \\[(.*)]') - for l in out.splitlines(): - m = pattern.match(l) - if m: - return m.group(1) - except Exception as err: # pylint: disable=broad-except - logging.exception(err) - return None - - -def ReadReparsePoint(path): - """Mimics os.readlink for usage.""" - try: - # pylint: disable=import-outside-toplevel - from starboard.tools import win_symlink_fast - return win_symlink_fast.FastReadReparseLink(path) - except Exception as err: # pylint: disable=broad-except - logging.exception(' error: %s, falling back to command line version.', err) - return ReadReparsePointShell(path) - - -def IsReparsePoint(path): - """Mimics os.islink for usage.""" - try: - # pylint: disable=import-outside-toplevel - from starboard.tools import win_symlink_fast - return win_symlink_fast.FastIsReparseLink(path) - except Exception as err: # pylint: disable=broad-except - logging.exception(' error: %s, falling back to command line version.', err) - return None is not ReadReparsePointShell(path) - - -def CreateReparsePoint(from_folder, link_folder): - """Mimics os.symlink for usage. - - Args: - from_folder: Path of target directory. - link_folder: Path to create link. - - Returns: - None. - - Raises: - OSError: if link cannot be created - """ - if os.path.isdir(link_folder): - _RemoveEmptyDirectory(link_folder) - else: - UnlinkReparsePoint(link_folder) # Deletes if it exists. - try: - # pylint: disable=import-outside-toplevel - from starboard.tools import win_symlink_fast - win_symlink_fast.FastCreateReparseLink(from_folder, link_folder) - return - except OSError: - pass - except Exception as err: # pylint: disable=broad-except - logging.exception( - 'unexpected error: %s, from=%s, link=%s, falling back to ' - 'command line version.', err, from_folder, link_folder) - par_dir = os.path.dirname(link_folder) - if not os.path.isdir(par_dir): - os.makedirs(par_dir) - try: - subprocess.check_output( - ['cmd', '/c', 'mklink', '/d', link_folder, from_folder], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - # Fallback to junction points, which require less privileges to create. - subprocess.check_output( - ['cmd', '/c', 'mklink', '/j', link_folder, from_folder]) - if not IsReparsePoint(link_folder): - raise OSError(f'Could not create sym link {link_folder} to {from_folder}') - - -def UnlinkReparsePoint(link_dir): - """Mimics os.unlink for usage. The sym link_dir is removed.""" - if not IsReparsePoint(link_dir): - return - cmd_parts = ['fsutil', 'reparsepoint', 'delete', link_dir] - subprocess.check_output(cmd_parts) - # The folder will now be unlinked, but will still exist. - if os.path.isdir(link_dir): - try: - _RemoveEmptyDirectory(link_dir) - except Exception as err: # pylint: disable=broad-except - logging.exception('could not remove %s because of %s', link_dir, err) - if IsReparsePoint(link_dir): - raise IOError(f'Link still exists: {ReadReparsePoint(link_dir)}') - if os.path.isdir(link_dir): - logging.info('WARNING - Link as folder still exists: %s', link_dir) - - -def _IsSamePath(p1, p2): - """Returns true if p1 and p2 represent the same path.""" - if not p1: - p1 = None - if not p2: - p2 = None - if p1 == p2: - return True - if (not p1) or (not p2): - return False - p1 = os.path.abspath(os.path.normpath(p1)) - p2 = os.path.abspath(os.path.normpath(p2)) - if p1 == p2: - return True - try: - return os.stat(p1) == os.stat(p2) - except Exception: # pylint: disable=broad-except - return False - - -def OsWalk(top, topdown=True, onerror=None, followlinks=False): - """Emulates os.walk() on linux. - - Args: - top: see os.walk(...) - topdown: see os.walk(...) - onerror: see os.walk(...) - followlinks: see os.walk(...) - - Yields: - see os.walk(...) - - Correctly handles windows reparse points as symlinks. - All symlink directories are returned in the directory list and the caller must - call IsReparsePoint() on the path to determine whether the directory is - real or a symlink. - """ - # Need an absolute path to use listdir and isdir with long paths. - top_abs_path = top - if not os.path.isabs(top_abs_path): - top_abs_path = os.path.join(os.getcwd(), top_abs_path) - top_abs_path = ToDevicePath(top) - try: - names = os.listdir(top_abs_path) - except OSError as err: - if onerror is not None: - onerror(err) - return - dirs, nondirs = [], [] - for name in names: - if os.path.isdir(os.path.join(top_abs_path, name)): - dirs.append(name) - else: - nondirs.append(name) - if topdown: - yield top, dirs, nondirs - for name in dirs: - new_path = os.path.join(top, name) - if followlinks or not IsReparsePoint(new_path): - for x in OsWalk(new_path, topdown, onerror, followlinks): - yield x - if not topdown: - yield top, dirs, nondirs diff --git a/starboard/tools/win_symlink_fast.py b/starboard/tools/win_symlink_fast.py deleted file mode 100644 index 8c5e9407b174..000000000000 --- a/starboard/tools/win_symlink_fast.py +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/python -# Copyright 2019 The Cobalt Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Provides functions for symlinking on Windows.""" - -import ctypes -from ctypes import wintypes -import os - -DWORD = wintypes.DWORD -LPCWSTR = wintypes.LPCWSTR -HANDLE = wintypes.HANDLE -LPVOID = wintypes.LPVOID -BOOL = wintypes.BOOL -USHORT = wintypes.USHORT -ULONG = wintypes.ULONG -WCHAR = wintypes.WCHAR - -kernel32 = ctypes.windll.kernel32 -LPDWORD = ctypes.POINTER(DWORD) -UCHAR = ctypes.c_ubyte - -GetFileAttributesW = kernel32.GetFileAttributesW -GetFileAttributesW.restype = DWORD -GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In - -INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF -FILE_ATTRIBUTE_REPARSE_POINT = 0x00400 - -CreateFileW = kernel32.CreateFileW -CreateFileW.restype = HANDLE -CreateFileW.argtypes = ( - LPCWSTR, # lpFileName In - DWORD, # dwDesiredAccess In - DWORD, # dwShareMode In - LPVOID, # lpSecurityAttributes In_opt - DWORD, # dwCreationDisposition In - DWORD, # dwFlagsAndAttributes In - HANDLE) # hTemplateFile In_opt - -CloseHandle = kernel32.CloseHandle -CloseHandle.restype = BOOL -CloseHandle.argtypes = (HANDLE,) # hObject In - -INVALID_HANDLE_VALUE = HANDLE(-1).value # pylint:disable=invalid-name -OPEN_EXISTING = 3 -FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 -FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - -DeviceIoControl = kernel32.DeviceIoControl -DeviceIoControl.restype = BOOL -DeviceIoControl.argtypes = ( - HANDLE, # hDevice In - DWORD, # dwIoControlCode In - LPVOID, # lpInBuffer In_opt - DWORD, # nInBufferSize In - LPVOID, # lpOutBuffer Out_opt - DWORD, # nOutBufferSize In - LPDWORD, # lpBytesReturned Out_opt - LPVOID) # lpOverlapped Inout_opt - -FSCTL_GET_REPARSE_POINT = 0x000900A8 -IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 -IO_REPARSE_TAG_SYMLINK = 0xA000000C -MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 -SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 -# Developer Mode must be enabled in order to use the following flag. -SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 -SYMLINK_FLAG_RELATIVE = 0x1 - - -class GenericReparseBuffer(ctypes.Structure): - """Win32 api data structure.""" - _fields_ = (('DataBuffer', UCHAR * 1),) - - -class SymbolicLinkReparseBuffer(ctypes.Structure): - """Win32 api data structure.""" - - _fields_ = (('SubstituteNameOffset', USHORT), - ('SubstituteNameLength', USHORT), ('PrintNameOffset', USHORT), - ('PrintNameLength', USHORT), ('Flags', ULONG), ('PathBuffer', - WCHAR * 1)) - - @property - def print_name(self): - arrayt = WCHAR * (self.PrintNameLength // 2) - offset = type(self).PathBuffer.offset + self.PrintNameOffset - return arrayt.from_address(ctypes.addressof(self) + offset).value - - @property - def substitute_name(self): - arrayt = WCHAR * (self.SubstituteNameLength // 2) - offset = type(self).PathBuffer.offset + self.SubstituteNameOffset - return arrayt.from_address(ctypes.addressof(self) + offset).value - - @property - def is_relative_path(self): - return bool(self.Flags & SYMLINK_FLAG_RELATIVE) - - -class MountPointReparseBuffer(ctypes.Structure): - """Win32 api data structure.""" - _fields_ = (('SubstituteNameOffset', USHORT), - ('SubstituteNameLength', USHORT), ('PrintNameOffset', USHORT), - ('PrintNameLength', USHORT), ('PathBuffer', WCHAR * 1)) - - @property - def print_name(self): - arrayt = WCHAR * (self.PrintNameLength // 2) - offset = type(self).PathBuffer.offset + self.PrintNameOffset - return arrayt.from_address(ctypes.addressof(self) + offset).value - - @property - def substitute_name(self): - arrayt = WCHAR * (self.SubstituteNameLength // 2) - offset = type(self).PathBuffer.offset + self.SubstituteNameOffset - return arrayt.from_address(ctypes.addressof(self) + offset).value - - -class ReparseDataBuffer(ctypes.Structure): - """Win32 api data structure.""" - - class ReparseBuffer(ctypes.Union): - """Win32 api data structure.""" - _fields_ = (('SymbolicLinkReparseBuffer', SymbolicLinkReparseBuffer), - ('MountPointReparseBuffer', MountPointReparseBuffer), - ('GenericReparseBuffer', GenericReparseBuffer)) - - _fields_ = (('ReparseTag', ULONG), ('ReparseDataLength', USHORT), - ('Reserved', USHORT), ('ReparseBuffer', ReparseBuffer)) - _anonymous_ = ('ReparseBuffer',) - - -def _ToUnicode(s): - try: - return s.decode('utf-8') - except AttributeError: - return s - - -_kdll = None - - -def _GetKernel32Dll(): - global _kdll - if _kdll: - return _kdll - _kdll = ctypes.windll.LoadLibrary('kernel32.dll') - return _kdll - - -def FastCreateReparseLink(from_folder, link_folder): - """Creates a reparse link. - - Args: - from_folder: The folder that the link will point to. - link_folder: The path of the link to be created. - - Returns: - None - - Raises: - OSError: if link cannot be created - """ - from_folder = _ToUnicode(from_folder) - link_folder = _ToUnicode(link_folder) - par_dir = os.path.dirname(link_folder) - if not os.path.isdir(par_dir): - os.makedirs(par_dir) - kdll = _GetKernel32Dll() - # Only supported from Windows 10 Insiders build 14972 - flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | \ - SYMBOLIC_LINK_FLAG_DIRECTORY - ok = kdll.CreateSymbolicLinkW(link_folder, from_folder, flags) - if not ok or not FastIsReparseLink(link_folder): - raise OSError('Could not create sym link ' + link_folder + ' to ' + - from_folder) - - -def FastIsReparseLink(path): - path = _ToUnicode(path) - result = GetFileAttributesW(path) - if result == INVALID_FILE_ATTRIBUTES: - return False - return bool(result & FILE_ATTRIBUTE_REPARSE_POINT) - - -def FastReadReparseLink(path): - """See api docstring, above.""" - path = _ToUnicode(path) - reparse_point_handle = CreateFileW( - path, 0, 0, None, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, None) - if reparse_point_handle == INVALID_HANDLE_VALUE: - return None - # Remove false positive below. - # pylint: disable=deprecated-method - target_buffer = ctypes.c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) - n_bytes_returned = DWORD() - io_result = DeviceIoControl(reparse_point_handle, FSCTL_GET_REPARSE_POINT, - None, 0, target_buffer, len(target_buffer), - ctypes.byref(n_bytes_returned), None) - CloseHandle(reparse_point_handle) - if not io_result: - return None - rdb = ReparseDataBuffer.from_buffer(target_buffer) - if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: - return rdb.SymbolicLinkReparseBuffer.print_name - elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT: - return rdb.MountPointReparseBuffer.print_name - return None diff --git a/starboard/tools/win_symlink_fast_test.py b/starboard/tools/win_symlink_fast_test.py deleted file mode 100644 index f828de54dc2f..000000000000 --- a/starboard/tools/win_symlink_fast_test.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2019 The Cobalt Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests the win_symlink_fast functionality.""" - -import sys -import unittest - -if __name__ == '__main__' and sys.platform == 'win32': - from starboard.tools import port_symlink_test # pylint: disable=g-import-not-at-top - from starboard.tools import util # pylint: disable=g-import-not-at-top - from starboard.tools import win_symlink # pylint: disable=g-import-not-at-top - from starboard.tools import win_symlink_fast # pylint: disable=g-import-not-at-top - - # Override the port_symlink_test symlink functions to point to our win_symlink - # and win_symlink_fast versions. - def MakeSymLink(*args, **kwargs): - return win_symlink_fast.FastCreateReparseLink(*args, **kwargs) - - def IsSymLink(*args, **kwargs): - return win_symlink_fast.FastIsReparseLink(*args, **kwargs) - - def ReadSymLink(*args, **kwargs): - return win_symlink_fast.FastReadReparseLink(*args, **kwargs) - - def Rmtree(*args, **kwargs): - return win_symlink.RmtreeShallow(*args, **kwargs) - - def OsWalk(*args, **kwargs): - return win_symlink.OsWalk(*args, **kwargs) - - port_symlink_test.MakeSymLink = MakeSymLink - port_symlink_test.IsSymLink = IsSymLink - port_symlink_test.ReadSymLink = ReadSymLink - port_symlink_test.Rmtree = Rmtree - port_symlink_test.OsWalk = OsWalk - - # Makes a unit test available to the unittest.main (through magic). - class WinSymlinkTest(port_symlink_test.PortSymlinkTest): - pass - - util.SetupDefaultLoggingConfig() - unittest.main(verbosity=2) diff --git a/starboard/tools/win_symlink_test.py b/starboard/tools/win_symlink_test.py deleted file mode 100644 index 4ea889a82b4b..000000000000 --- a/starboard/tools/win_symlink_test.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2018 The Cobalt Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests win_symlink.""" - -import os -import shutil -import sys -import tempfile -import unittest - -if __name__ == '__main__' and sys.platform == 'win32': - from starboard.tools import port_symlink_test # pylint: disable=g-import-not-at-top - from starboard.tools import util # pylint: disable=g-import-not-at-top - from starboard.tools import win_symlink # pylint: disable=g-import-not-at-top - - # Override the port_symlink_test symlink functions to point to our win_symlink - # versions. - def MakeSymLink(*args, **kwargs): - return win_symlink.CreateReparsePoint(*args, **kwargs) - - def IsSymLink(*args, **kwargs): - return win_symlink.IsReparsePoint(*args, **kwargs) - - def ReadSymLink(*args, **kwargs): - return win_symlink.ReadReparsePoint(*args, **kwargs) - - def Rmtree(*args, **kwargs): - return win_symlink.RmtreeShallow(*args, **kwargs) - - def OsWalk(*args, **kwargs): - return win_symlink.OsWalk(*args, **kwargs) - - port_symlink_test.MakeSymLink = MakeSymLink - port_symlink_test.IsSymLink = IsSymLink - port_symlink_test.ReadSymLink = ReadSymLink - port_symlink_test.Rmtree = Rmtree - port_symlink_test.OsWalk = OsWalk - - # Makes a unit test available to the unittest.main (through magic). - class WinSymlinkTest(port_symlink_test.PortSymlinkTest): - - def testRmtreeOsWalkDoesNotFollowSymlinks(self): - """_RmtreeOsWalk(...) will delete the symlink and not the target.""" - external_temp_dir = tempfile.mkdtemp() - try: - external_temp_file = os.path.join(external_temp_dir, 'test.txt') - with open(external_temp_file, 'w', encoding='utf-8') as fd: - fd.write('HI') - link_dir = os.path.join(self.tmp_dir, 'foo', 'link_dir') - MakeSymLink(external_temp_file, link_dir) - win_symlink._RmtreeOsWalk(self.tmp_dir) # pylint:disable=protected-access - # The target file should still exist - self.assertTrue(os.path.isfile(external_temp_file)) - finally: - shutil.rmtree(external_temp_dir, ignore_errors=True) - - def testRmtreeCmdShellDoesNotFollowSymlinks(self): - """_RmtreeShellCmd(...) will delete the symlink and not the target.""" - external_temp_dir = tempfile.mkdtemp() - try: - external_temp_file = os.path.join(external_temp_dir, 'test.txt') - with open(external_temp_file, 'w', encoding='utf-8') as fd: - fd.write('HI') - link_dir = os.path.join(self.tmp_dir, 'foo', 'link_dir') - MakeSymLink(external_temp_file, link_dir) - win_symlink._RmtreeShellCmd(self.tmp_dir) # pylint:disable=protected-access - # The target file should still exist - self.assertTrue(os.path.isfile(external_temp_file)) - finally: - shutil.rmtree(external_temp_dir, ignore_errors=True) - - util.SetupDefaultLoggingConfig() - unittest.main(verbosity=2)