##// END OF EJS Templates
win32: correctly break hardlinks on network drives (issue761)...
Patrick Mezard -
r11991:50523b44 stable
parent child Browse files
Show More
@@ -1,204 +1,205 b''
1 # win32.py - utility functions that use win32 API
1 # win32.py - utility functions that use win32 API
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Utility functions that use win32 API.
8 """Utility functions that use win32 API.
9
9
10 Mark Hammond's win32all package allows better functionality on
10 Mark Hammond's win32all package allows better functionality on
11 Windows. This module overrides definitions in util.py. If not
11 Windows. This module overrides definitions in util.py. If not
12 available, import of this module will fail, and generic code will be
12 available, import of this module will fail, and generic code will be
13 used.
13 used.
14 """
14 """
15
15
16 import win32api
16 import win32api
17
17
18 import errno, os, sys, pywintypes, win32con, win32file, win32process
18 import errno, os, sys, pywintypes, win32con, win32file, win32process
19 import winerror, win32gui, win32console
19 import winerror, win32gui, win32console
20 import osutil, encoding
20 import osutil, encoding
21 from win32com.shell import shell, shellcon
21 from win32com.shell import shell, shellcon
22
22
23 def os_link(src, dst):
23 def os_link(src, dst):
24 try:
24 try:
25 win32file.CreateHardLink(dst, src)
25 win32file.CreateHardLink(dst, src)
26 # CreateHardLink sometimes succeeds on mapped drives but
27 # following nlinks() returns 1. Check it now and bail out.
28 if nlinks(src) < 2:
29 try:
30 win32file.DeleteFile(dst)
31 except:
32 pass
33 # Fake hardlinking error
34 raise OSError(errno.EINVAL, 'Hardlinking not supported')
35 except pywintypes.error:
26 except pywintypes.error:
36 raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
27 raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
37 except NotImplementedError: # Another fake error win Win98
28 except NotImplementedError: # Another fake error win Win98
38 raise OSError(errno.EINVAL, 'Hardlinking not supported')
29 raise OSError(errno.EINVAL, 'Hardlinking not supported')
39
30
40 def _getfileinfo(pathname):
31 def _getfileinfo(pathname):
41 """Return number of hardlinks for the given file."""
32 """Return number of hardlinks for the given file."""
42 try:
33 try:
43 fh = win32file.CreateFile(pathname,
34 fh = win32file.CreateFile(pathname,
44 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
35 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
45 None, win32file.OPEN_EXISTING, 0, None)
36 None, win32file.OPEN_EXISTING, 0, None)
46 try:
37 try:
47 return win32file.GetFileInformationByHandle(fh)
38 return win32file.GetFileInformationByHandle(fh)
48 finally:
39 finally:
49 fh.Close()
40 fh.Close()
50 except pywintypes.error:
41 except pywintypes.error:
51 return None
42 return None
52
43
53 def nlinks(pathname):
44 def nlinks(pathname):
54 """Return number of hardlinks for the given file."""
45 """Return number of hardlinks for the given file."""
55 res = _getfileinfo(pathname)
46 res = _getfileinfo(pathname)
56 if res is not None:
47 if res is not None:
57 return res[7]
48 links = res[7]
58 else:
49 else:
59 return os.lstat(pathname).st_nlink
50 links = os.lstat(pathname).st_nlink
51 if links < 2:
52 # Known to be wrong for most network drives
53 dirname = os.path.dirname(pathname)
54 if not dirname:
55 dirname = '.'
56 dt = win32file.GetDriveType(dirname + '\\')
57 if dt == 4 or dt == 1:
58 # Fake hardlink to force COW for network drives
59 links = 2
60 return links
60
61
61 def samefile(fpath1, fpath2):
62 def samefile(fpath1, fpath2):
62 """Returns whether fpath1 and fpath2 refer to the same file. This is only
63 """Returns whether fpath1 and fpath2 refer to the same file. This is only
63 guaranteed to work for files, not directories."""
64 guaranteed to work for files, not directories."""
64 res1 = _getfileinfo(fpath1)
65 res1 = _getfileinfo(fpath1)
65 res2 = _getfileinfo(fpath2)
66 res2 = _getfileinfo(fpath2)
66 if res1 is not None and res2 is not None:
67 if res1 is not None and res2 is not None:
67 # Index 4 is the volume serial number, and 8 and 9 contain the file ID
68 # Index 4 is the volume serial number, and 8 and 9 contain the file ID
68 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9]
69 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9]
69 else:
70 else:
70 return False
71 return False
71
72
72 def samedevice(fpath1, fpath2):
73 def samedevice(fpath1, fpath2):
73 """Returns whether fpath1 and fpath2 are on the same device. This is only
74 """Returns whether fpath1 and fpath2 are on the same device. This is only
74 guaranteed to work for files, not directories."""
75 guaranteed to work for files, not directories."""
75 res1 = _getfileinfo(fpath1)
76 res1 = _getfileinfo(fpath1)
76 res2 = _getfileinfo(fpath2)
77 res2 = _getfileinfo(fpath2)
77 if res1 is not None and res2 is not None:
78 if res1 is not None and res2 is not None:
78 return res1[4] == res2[4]
79 return res1[4] == res2[4]
79 else:
80 else:
80 return False
81 return False
81
82
82 def testpid(pid):
83 def testpid(pid):
83 '''return True if pid is still running or unable to
84 '''return True if pid is still running or unable to
84 determine, False otherwise'''
85 determine, False otherwise'''
85 try:
86 try:
86 handle = win32api.OpenProcess(
87 handle = win32api.OpenProcess(
87 win32con.PROCESS_QUERY_INFORMATION, False, pid)
88 win32con.PROCESS_QUERY_INFORMATION, False, pid)
88 if handle:
89 if handle:
89 status = win32process.GetExitCodeProcess(handle)
90 status = win32process.GetExitCodeProcess(handle)
90 return status == win32con.STILL_ACTIVE
91 return status == win32con.STILL_ACTIVE
91 except pywintypes.error, details:
92 except pywintypes.error, details:
92 return details[0] != winerror.ERROR_INVALID_PARAMETER
93 return details[0] != winerror.ERROR_INVALID_PARAMETER
93 return True
94 return True
94
95
95 def lookup_reg(key, valname=None, scope=None):
96 def lookup_reg(key, valname=None, scope=None):
96 ''' Look up a key/value name in the Windows registry.
97 ''' Look up a key/value name in the Windows registry.
97
98
98 valname: value name. If unspecified, the default value for the key
99 valname: value name. If unspecified, the default value for the key
99 is used.
100 is used.
100 scope: optionally specify scope for registry lookup, this can be
101 scope: optionally specify scope for registry lookup, this can be
101 a sequence of scopes to look up in order. Default (CURRENT_USER,
102 a sequence of scopes to look up in order. Default (CURRENT_USER,
102 LOCAL_MACHINE).
103 LOCAL_MACHINE).
103 '''
104 '''
104 try:
105 try:
105 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
106 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
106 QueryValueEx, OpenKey
107 QueryValueEx, OpenKey
107 except ImportError:
108 except ImportError:
108 return None
109 return None
109
110
110 if scope is None:
111 if scope is None:
111 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
112 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
112 elif not isinstance(scope, (list, tuple)):
113 elif not isinstance(scope, (list, tuple)):
113 scope = (scope,)
114 scope = (scope,)
114 for s in scope:
115 for s in scope:
115 try:
116 try:
116 val = QueryValueEx(OpenKey(s, key), valname)[0]
117 val = QueryValueEx(OpenKey(s, key), valname)[0]
117 # never let a Unicode string escape into the wild
118 # never let a Unicode string escape into the wild
118 return encoding.tolocal(val.encode('UTF-8'))
119 return encoding.tolocal(val.encode('UTF-8'))
119 except EnvironmentError:
120 except EnvironmentError:
120 pass
121 pass
121
122
122 def system_rcpath_win32():
123 def system_rcpath_win32():
123 '''return default os-specific hgrc search path'''
124 '''return default os-specific hgrc search path'''
124 proc = win32api.GetCurrentProcess()
125 proc = win32api.GetCurrentProcess()
125 try:
126 try:
126 # This will fail on windows < NT
127 # This will fail on windows < NT
127 filename = win32process.GetModuleFileNameEx(proc, 0)
128 filename = win32process.GetModuleFileNameEx(proc, 0)
128 except:
129 except:
129 filename = win32api.GetModuleFileName(0)
130 filename = win32api.GetModuleFileName(0)
130 # Use mercurial.ini found in directory with hg.exe
131 # Use mercurial.ini found in directory with hg.exe
131 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
132 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
132 if os.path.isfile(progrc):
133 if os.path.isfile(progrc):
133 return [progrc]
134 return [progrc]
134 # Use hgrc.d found in directory with hg.exe
135 # Use hgrc.d found in directory with hg.exe
135 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
136 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
136 if os.path.isdir(progrcd):
137 if os.path.isdir(progrcd):
137 rcpath = []
138 rcpath = []
138 for f, kind in osutil.listdir(progrcd):
139 for f, kind in osutil.listdir(progrcd):
139 if f.endswith('.rc'):
140 if f.endswith('.rc'):
140 rcpath.append(os.path.join(progrcd, f))
141 rcpath.append(os.path.join(progrcd, f))
141 return rcpath
142 return rcpath
142 # else look for a system rcpath in the registry
143 # else look for a system rcpath in the registry
143 try:
144 try:
144 value = win32api.RegQueryValue(
145 value = win32api.RegQueryValue(
145 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
146 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
146 rcpath = []
147 rcpath = []
147 for p in value.split(os.pathsep):
148 for p in value.split(os.pathsep):
148 if p.lower().endswith('mercurial.ini'):
149 if p.lower().endswith('mercurial.ini'):
149 rcpath.append(p)
150 rcpath.append(p)
150 elif os.path.isdir(p):
151 elif os.path.isdir(p):
151 for f, kind in osutil.listdir(p):
152 for f, kind in osutil.listdir(p):
152 if f.endswith('.rc'):
153 if f.endswith('.rc'):
153 rcpath.append(os.path.join(p, f))
154 rcpath.append(os.path.join(p, f))
154 return rcpath
155 return rcpath
155 except pywintypes.error:
156 except pywintypes.error:
156 return []
157 return []
157
158
158 def user_rcpath_win32():
159 def user_rcpath_win32():
159 '''return os-specific hgrc search path to the user dir'''
160 '''return os-specific hgrc search path to the user dir'''
160 userdir = os.path.expanduser('~')
161 userdir = os.path.expanduser('~')
161 if sys.getwindowsversion()[3] != 2 and userdir == '~':
162 if sys.getwindowsversion()[3] != 2 and userdir == '~':
162 # We are on win < nt: fetch the APPDATA directory location and use
163 # We are on win < nt: fetch the APPDATA directory location and use
163 # the parent directory as the user home dir.
164 # the parent directory as the user home dir.
164 appdir = shell.SHGetPathFromIDList(
165 appdir = shell.SHGetPathFromIDList(
165 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
166 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
166 userdir = os.path.dirname(appdir)
167 userdir = os.path.dirname(appdir)
167 return [os.path.join(userdir, 'mercurial.ini'),
168 return [os.path.join(userdir, 'mercurial.ini'),
168 os.path.join(userdir, '.hgrc')]
169 os.path.join(userdir, '.hgrc')]
169
170
170 def getuser():
171 def getuser():
171 '''return name of current user'''
172 '''return name of current user'''
172 return win32api.GetUserName()
173 return win32api.GetUserName()
173
174
174 def set_signal_handler_win32():
175 def set_signal_handler_win32():
175 """Register a termination handler for console events including
176 """Register a termination handler for console events including
176 CTRL+C. python signal handlers do not work well with socket
177 CTRL+C. python signal handlers do not work well with socket
177 operations.
178 operations.
178 """
179 """
179 def handler(event):
180 def handler(event):
180 win32process.ExitProcess(1)
181 win32process.ExitProcess(1)
181 win32api.SetConsoleCtrlHandler(handler)
182 win32api.SetConsoleCtrlHandler(handler)
182
183
183 def hidewindow():
184 def hidewindow():
184 def callback(*args, **kwargs):
185 def callback(*args, **kwargs):
185 hwnd, pid = args
186 hwnd, pid = args
186 wpid = win32process.GetWindowThreadProcessId(hwnd)[1]
187 wpid = win32process.GetWindowThreadProcessId(hwnd)[1]
187 if pid == wpid:
188 if pid == wpid:
188 win32gui.ShowWindow(hwnd, win32con.SW_HIDE)
189 win32gui.ShowWindow(hwnd, win32con.SW_HIDE)
189
190
190 pid = win32process.GetCurrentProcessId()
191 pid = win32process.GetCurrentProcessId()
191 win32gui.EnumWindows(callback, pid)
192 win32gui.EnumWindows(callback, pid)
192
193
193 def termwidth_():
194 def termwidth_():
194 try:
195 try:
195 # Query stderr to avoid problems with redirections
196 # Query stderr to avoid problems with redirections
196 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE)
197 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE)
197 try:
198 try:
198 window = screenbuf.GetConsoleScreenBufferInfo()['Window']
199 window = screenbuf.GetConsoleScreenBufferInfo()['Window']
199 width = window.Right - window.Left
200 width = window.Right - window.Left
200 return width
201 return width
201 finally:
202 finally:
202 screenbuf.Detach()
203 screenbuf.Detach()
203 except pywintypes.error:
204 except pywintypes.error:
204 return 79
205 return 79
General Comments 0
You need to be logged in to leave comments. Login now