1from __future__ import unicode_literals 2 3from dvc.utils.compat import str, open 4 5import os 6 7 8class System(object): 9 @staticmethod 10 def is_unix(): 11 return os.name != "nt" 12 13 @staticmethod 14 def hardlink(source, link_name): 15 import ctypes 16 from dvc.exceptions import DvcException 17 18 if System.is_unix(): 19 try: 20 os.link(source, link_name) 21 return 22 except Exception as exc: 23 raise DvcException("link", cause=exc) 24 25 CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW 26 CreateHardLink.argtypes = [ 27 ctypes.c_wchar_p, 28 ctypes.c_wchar_p, 29 ctypes.c_void_p, 30 ] 31 CreateHardLink.restype = ctypes.wintypes.BOOL 32 33 res = CreateHardLink(link_name, source, None) 34 if res == 0: 35 raise DvcException("CreateHardLinkW", cause=ctypes.WinError()) 36 37 @staticmethod 38 def symlink(source, link_name): 39 import ctypes 40 from dvc.exceptions import DvcException 41 42 if System.is_unix(): 43 try: 44 os.symlink(source, link_name) 45 return 46 except Exception as exc: 47 msg = "failed to symlink '{}' -> '{}': {}" 48 raise DvcException(msg.format(source, link_name, str(exc))) 49 50 flags = 0 51 if source is not None and os.path.isdir(source): 52 flags = 1 53 54 func = ctypes.windll.kernel32.CreateSymbolicLinkW 55 func.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) 56 func.restype = ctypes.c_ubyte 57 58 if func(link_name, source, flags) == 0: 59 raise DvcException("CreateSymbolicLinkW", cause=ctypes.WinError()) 60 61 @staticmethod 62 def _reflink_darwin(src, dst): 63 import ctypes 64 65 clib = ctypes.CDLL("libc.dylib") 66 if not hasattr(clib, "clonefile"): 67 return -1 68 69 clonefile = clib.clonefile 70 clonefile.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int] 71 clonefile.restype = ctypes.c_int 72 73 return clonefile( 74 ctypes.c_char_p(src.encode("utf-8")), 75 ctypes.c_char_p(dst.encode("utf-8")), 76 ctypes.c_int(0), 77 ) 78 79 @staticmethod 80 def _reflink_windows(src, dst): 81 return -1 82 83 @staticmethod 84 def _reflink_linux(src, dst): 85 import os 86 import fcntl 87 88 FICLONE = 0x40049409 89 90 s = open(src, "r") 91 d = open(dst, "w+") 92 93 try: 94 ret = fcntl.ioctl(d.fileno(), FICLONE, s.fileno()) 95 except IOError: 96 s.close() 97 d.close() 98 os.unlink(dst) 99 raise 100 101 s.close() 102 d.close() 103 104 if ret != 0: 105 os.unlink(dst) 106 107 return ret 108 109 @staticmethod 110 def reflink(source, link_name): 111 import platform 112 from dvc.exceptions import DvcException 113 114 system = platform.system() 115 try: 116 if system == "Windows": 117 ret = System._reflink_windows(source, link_name) 118 elif system == "Darwin": 119 ret = System._reflink_darwin(source, link_name) 120 elif system == "Linux": 121 ret = System._reflink_linux(source, link_name) 122 else: 123 ret = -1 124 except IOError: 125 ret = -1 126 127 if ret != 0: 128 raise DvcException("reflink is not supported") 129 130 @staticmethod 131 def getdirinfo(path): 132 import ctypes 133 from ctypes import c_void_p, c_wchar_p, Structure, WinError, POINTER 134 from ctypes.wintypes import DWORD, HANDLE, BOOL 135 136 # NOTE: use this flag to open symlink itself and not the target 137 # See https://docs.microsoft.com/en-us/windows/desktop/api/ 138 # fileapi/nf-fileapi-createfilew#symbolic-link-behavior 139 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 140 141 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 142 FILE_SHARE_READ = 0x00000001 143 OPEN_EXISTING = 3 144 145 class FILETIME(Structure): 146 _fields_ = [("dwLowDateTime", DWORD), ("dwHighDateTime", DWORD)] 147 148 class BY_HANDLE_FILE_INFORMATION(Structure): 149 _fields_ = [ 150 ("dwFileAttributes", DWORD), 151 ("ftCreationTime", FILETIME), 152 ("ftLastAccessTime", FILETIME), 153 ("ftLastWriteTime", FILETIME), 154 ("dwVolumeSerialNumber", DWORD), 155 ("nFileSizeHigh", DWORD), 156 ("nFileSizeLow", DWORD), 157 ("nNumberOfLinks", DWORD), 158 ("nFileIndexHigh", DWORD), 159 ("nFileIndexLow", DWORD), 160 ] 161 162 flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT 163 164 func = ctypes.windll.kernel32.CreateFileW 165 func.argtypes = [ 166 c_wchar_p, 167 DWORD, 168 DWORD, 169 c_void_p, 170 DWORD, 171 DWORD, 172 HANDLE, 173 ] 174 func.restype = HANDLE 175 176 hfile = func( 177 path, 0, FILE_SHARE_READ, None, OPEN_EXISTING, flags, None 178 ) 179 if hfile is None: 180 raise WinError() 181 182 func = ctypes.windll.kernel32.GetFileInformationByHandle 183 func.argtypes = [HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)] 184 func.restype = BOOL 185 186 info = BY_HANDLE_FILE_INFORMATION() 187 rv = func(hfile, info) 188 189 func = ctypes.windll.kernel32.CloseHandle 190 func.argtypes = [HANDLE] 191 func.restype = BOOL 192 193 func(hfile) 194 195 if rv == 0: 196 raise WinError() 197 198 return info 199 200 @staticmethod 201 def inode(path): 202 if System.is_unix(): 203 import ctypes 204 205 inode = os.lstat(path).st_ino 206 # NOTE: See https://bugs.python.org/issue29619 and 207 # https://stackoverflow.com/questions/34643289/ 208 # pythons-os-stat-is-returning-wrong-inode-value 209 inode = ctypes.c_ulong(inode).value 210 else: 211 # getdirinfo from ntfsutils works on both files and dirs 212 info = System.getdirinfo(path) 213 inode = abs( 214 hash( 215 ( 216 info.dwVolumeSerialNumber, 217 info.nFileIndexHigh, 218 info.nFileIndexLow, 219 ) 220 ) 221 ) 222 assert inode >= 0 223 assert inode < 2 ** 64 224 return inode 225 226 @staticmethod 227 def _wait_for_input_windows(timeout): 228 import sys 229 import ctypes 230 import msvcrt 231 from ctypes.wintypes import DWORD, HANDLE 232 233 # https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject 234 WAIT_OBJECT_0 = 0 235 WAIT_TIMEOUT = 0x00000102 236 237 func = ctypes.windll.kernel32.WaitForSingleObject 238 func.argtypes = [HANDLE, DWORD] 239 func.restype = DWORD 240 241 rc = func(msvcrt.get_osfhandle(sys.stdin.fileno()), timeout * 1000) 242 if rc not in [WAIT_OBJECT_0, WAIT_TIMEOUT]: 243 raise RuntimeError(rc) 244 245 @staticmethod 246 def _wait_for_input_posix(timeout): 247 import sys 248 import select 249 250 try: 251 select.select([sys.stdin], [], [], timeout) 252 except select.error: 253 pass 254 255 @staticmethod 256 def wait_for_input(timeout): 257 if System.is_unix(): 258 return System._wait_for_input_posix(timeout) 259 else: 260 return System._wait_for_input_windows(timeout) 261 262 @staticmethod 263 def is_symlink(path): 264 if System.is_unix(): 265 return os.path.islink(path) 266 267 # https://docs.microsoft.com/en-us/windows/desktop/fileio/ 268 # file-attribute-constants 269 FILE_ATTRIBUTE_REPARSE_POINT = 0x400 270 271 if os.path.lexists(path): 272 info = System.getdirinfo(path) 273 return info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT 274 return False 275 276 @staticmethod 277 def is_hardlink(path): 278 if System.is_unix(): 279 return os.stat(path).st_nlink > 1 280 281 info = System.getdirinfo(path) 282 return info.nNumberOfLinks > 1 283