##// END OF EJS Templates
typing: add type hints to mercurial/win32.py...
Matt Harbison -
r50705:a9faacdc default
parent child Browse files
Show More
@@ -1,753 +1,753 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2009 Olivia Mackall <olivia@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
8
9 import errno
9 import errno
10 import fcntl
10 import fcntl
11 import getpass
11 import getpass
12 import grp
12 import grp
13 import os
13 import os
14 import pwd
14 import pwd
15 import re
15 import re
16 import select
16 import select
17 import stat
17 import stat
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import unicodedata
20 import unicodedata
21
21
22 from .i18n import _
22 from .i18n import _
23 from .pycompat import (
23 from .pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27 from . import (
27 from . import (
28 encoding,
28 encoding,
29 error,
29 error,
30 policy,
30 policy,
31 pycompat,
31 pycompat,
32 )
32 )
33
33
34 osutil = policy.importmod('osutil')
34 osutil = policy.importmod('osutil')
35
35
36 normpath = os.path.normpath
36 normpath = os.path.normpath
37 samestat = os.path.samestat
37 samestat = os.path.samestat
38 abspath = os.path.abspath # re-exports
38 abspath = os.path.abspath # re-exports
39
39
40 try:
40 try:
41 oslink = os.link
41 oslink = os.link
42 except AttributeError:
42 except AttributeError:
43 # Some platforms build Python without os.link on systems that are
43 # Some platforms build Python without os.link on systems that are
44 # vaguely unix-like but don't have hardlink support. For those
44 # vaguely unix-like but don't have hardlink support. For those
45 # poor souls, just say we tried and that it failed so we fall back
45 # poor souls, just say we tried and that it failed so we fall back
46 # to copies.
46 # to copies.
47 def oslink(src, dst):
47 def oslink(src, dst):
48 raise OSError(
48 raise OSError(
49 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
49 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
50 )
50 )
51
51
52
52
53 readlink = os.readlink
53 readlink = os.readlink
54 unlink = os.unlink
54 unlink = os.unlink
55 rename = os.rename
55 rename = os.rename
56 removedirs = os.removedirs
56 removedirs = os.removedirs
57 expandglobs = False
57 expandglobs = False
58
58
59 umask = os.umask(0)
59 umask = os.umask(0)
60 os.umask(umask)
60 os.umask(umask)
61
61
62 posixfile = open
62 posixfile = open
63
63
64
64
65 def split(p):
65 def split(p):
66 """Same as posixpath.split, but faster
66 """Same as posixpath.split, but faster
67
67
68 >>> import posixpath
68 >>> import posixpath
69 >>> for f in [b'/absolute/path/to/file',
69 >>> for f in [b'/absolute/path/to/file',
70 ... b'relative/path/to/file',
70 ... b'relative/path/to/file',
71 ... b'file_alone',
71 ... b'file_alone',
72 ... b'path/to/directory/',
72 ... b'path/to/directory/',
73 ... b'/multiple/path//separators',
73 ... b'/multiple/path//separators',
74 ... b'/file_at_root',
74 ... b'/file_at_root',
75 ... b'///multiple_leading_separators_at_root',
75 ... b'///multiple_leading_separators_at_root',
76 ... b'']:
76 ... b'']:
77 ... assert split(f) == posixpath.split(f), f
77 ... assert split(f) == posixpath.split(f), f
78 """
78 """
79 ht = p.rsplit(b'/', 1)
79 ht = p.rsplit(b'/', 1)
80 if len(ht) == 1:
80 if len(ht) == 1:
81 return b'', p
81 return b'', p
82 nh = ht[0].rstrip(b'/')
82 nh = ht[0].rstrip(b'/')
83 if nh:
83 if nh:
84 return nh, ht[1]
84 return nh, ht[1]
85 return ht[0] + b'/', ht[1]
85 return ht[0] + b'/', ht[1]
86
86
87
87
88 def openhardlinks():
88 def openhardlinks():
89 '''return true if it is safe to hold open file handles to hardlinks'''
89 '''return true if it is safe to hold open file handles to hardlinks'''
90 return True
90 return True
91
91
92
92
93 def nlinks(name):
93 def nlinks(name):
94 '''return number of hardlinks for the given file'''
94 '''return number of hardlinks for the given file'''
95 return os.lstat(name).st_nlink
95 return os.lstat(name).st_nlink
96
96
97
97
98 def parsepatchoutput(output_line):
98 def parsepatchoutput(output_line):
99 """parses the output produced by patch and returns the filename"""
99 """parses the output produced by patch and returns the filename"""
100 pf = output_line[14:]
100 pf = output_line[14:]
101 if pycompat.sysplatform == b'OpenVMS':
101 if pycompat.sysplatform == b'OpenVMS':
102 if pf[0] == b'`':
102 if pf[0] == b'`':
103 pf = pf[1:-1] # Remove the quotes
103 pf = pf[1:-1] # Remove the quotes
104 else:
104 else:
105 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
105 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
106 pf = pf[1:-1] # Remove the quotes
106 pf = pf[1:-1] # Remove the quotes
107 return pf
107 return pf
108
108
109
109
110 def sshargs(sshcmd, host, user, port):
110 def sshargs(sshcmd, host, user, port):
111 '''Build argument list for ssh'''
111 '''Build argument list for ssh'''
112 args = user and (b"%s@%s" % (user, host)) or host
112 args = user and (b"%s@%s" % (user, host)) or host
113 if b'-' in args[:1]:
113 if b'-' in args[:1]:
114 raise error.Abort(
114 raise error.Abort(
115 _(b'illegal ssh hostname or username starting with -: %s') % args
115 _(b'illegal ssh hostname or username starting with -: %s') % args
116 )
116 )
117 args = shellquote(args)
117 args = shellquote(args)
118 if port:
118 if port:
119 args = b'-p %s %s' % (shellquote(port), args)
119 args = b'-p %s %s' % (shellquote(port), args)
120 return args
120 return args
121
121
122
122
123 def isexec(f):
123 def isexec(f):
124 """check whether a file is executable"""
124 """check whether a file is executable"""
125 return os.lstat(f).st_mode & 0o100 != 0
125 return os.lstat(f).st_mode & 0o100 != 0
126
126
127
127
128 def setflags(f, l, x):
128 def setflags(f, l, x):
129 st = os.lstat(f)
129 st = os.lstat(f)
130 s = st.st_mode
130 s = st.st_mode
131 if l:
131 if l:
132 if not stat.S_ISLNK(s):
132 if not stat.S_ISLNK(s):
133 # switch file to link
133 # switch file to link
134 with open(f, b'rb') as fp:
134 with open(f, b'rb') as fp:
135 data = fp.read()
135 data = fp.read()
136 unlink(f)
136 unlink(f)
137 try:
137 try:
138 os.symlink(data, f)
138 os.symlink(data, f)
139 except OSError:
139 except OSError:
140 # failed to make a link, rewrite file
140 # failed to make a link, rewrite file
141 with open(f, b"wb") as fp:
141 with open(f, b"wb") as fp:
142 fp.write(data)
142 fp.write(data)
143
143
144 # no chmod needed at this point
144 # no chmod needed at this point
145 return
145 return
146 if stat.S_ISLNK(s):
146 if stat.S_ISLNK(s):
147 # switch link to file
147 # switch link to file
148 data = os.readlink(f)
148 data = os.readlink(f)
149 unlink(f)
149 unlink(f)
150 with open(f, b"wb") as fp:
150 with open(f, b"wb") as fp:
151 fp.write(data)
151 fp.write(data)
152 s = 0o666 & ~umask # avoid restatting for chmod
152 s = 0o666 & ~umask # avoid restatting for chmod
153
153
154 sx = s & 0o100
154 sx = s & 0o100
155 if st.st_nlink > 1 and bool(x) != bool(sx):
155 if st.st_nlink > 1 and bool(x) != bool(sx):
156 # the file is a hardlink, break it
156 # the file is a hardlink, break it
157 with open(f, b"rb") as fp:
157 with open(f, b"rb") as fp:
158 data = fp.read()
158 data = fp.read()
159 unlink(f)
159 unlink(f)
160 with open(f, b"wb") as fp:
160 with open(f, b"wb") as fp:
161 fp.write(data)
161 fp.write(data)
162
162
163 if x and not sx:
163 if x and not sx:
164 # Turn on +x for every +r bit when making a file executable
164 # Turn on +x for every +r bit when making a file executable
165 # and obey umask.
165 # and obey umask.
166 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
166 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
167 elif not x and sx:
167 elif not x and sx:
168 # Turn off all +x bits
168 # Turn off all +x bits
169 os.chmod(f, s & 0o666)
169 os.chmod(f, s & 0o666)
170
170
171
171
172 def copymode(src, dst, mode=None, enforcewritable=False):
172 def copymode(src, dst, mode=None, enforcewritable=False):
173 """Copy the file mode from the file at path src to dst.
173 """Copy the file mode from the file at path src to dst.
174 If src doesn't exist, we're using mode instead. If mode is None, we're
174 If src doesn't exist, we're using mode instead. If mode is None, we're
175 using umask."""
175 using umask."""
176 try:
176 try:
177 st_mode = os.lstat(src).st_mode & 0o777
177 st_mode = os.lstat(src).st_mode & 0o777
178 except FileNotFoundError:
178 except FileNotFoundError:
179 st_mode = mode
179 st_mode = mode
180 if st_mode is None:
180 if st_mode is None:
181 st_mode = ~umask
181 st_mode = ~umask
182 st_mode &= 0o666
182 st_mode &= 0o666
183
183
184 new_mode = st_mode
184 new_mode = st_mode
185
185
186 if enforcewritable:
186 if enforcewritable:
187 new_mode |= stat.S_IWUSR
187 new_mode |= stat.S_IWUSR
188
188
189 os.chmod(dst, new_mode)
189 os.chmod(dst, new_mode)
190
190
191
191
192 def checkexec(path):
192 def checkexec(path):
193 """
193 """
194 Check whether the given path is on a filesystem with UNIX-like exec flags
194 Check whether the given path is on a filesystem with UNIX-like exec flags
195
195
196 Requires a directory (like /foo/.hg)
196 Requires a directory (like /foo/.hg)
197 """
197 """
198
198
199 # VFAT on some Linux versions can flip mode but it doesn't persist
199 # VFAT on some Linux versions can flip mode but it doesn't persist
200 # a FS remount. Frequently we can detect it if files are created
200 # a FS remount. Frequently we can detect it if files are created
201 # with exec bit on.
201 # with exec bit on.
202
202
203 try:
203 try:
204 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
204 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
205 basedir = os.path.join(path, b'.hg')
205 basedir = os.path.join(path, b'.hg')
206 cachedir = os.path.join(basedir, b'wcache')
206 cachedir = os.path.join(basedir, b'wcache')
207 storedir = os.path.join(basedir, b'store')
207 storedir = os.path.join(basedir, b'store')
208 if not os.path.exists(cachedir):
208 if not os.path.exists(cachedir):
209 try:
209 try:
210 # we want to create the 'cache' directory, not the '.hg' one.
210 # we want to create the 'cache' directory, not the '.hg' one.
211 # Automatically creating '.hg' directory could silently spawn
211 # Automatically creating '.hg' directory could silently spawn
212 # invalid Mercurial repositories. That seems like a bad idea.
212 # invalid Mercurial repositories. That seems like a bad idea.
213 os.mkdir(cachedir)
213 os.mkdir(cachedir)
214 if os.path.exists(storedir):
214 if os.path.exists(storedir):
215 copymode(storedir, cachedir)
215 copymode(storedir, cachedir)
216 else:
216 else:
217 copymode(basedir, cachedir)
217 copymode(basedir, cachedir)
218 except (IOError, OSError):
218 except (IOError, OSError):
219 # we other fallback logic triggers
219 # we other fallback logic triggers
220 pass
220 pass
221 if os.path.isdir(cachedir):
221 if os.path.isdir(cachedir):
222 checkisexec = os.path.join(cachedir, b'checkisexec')
222 checkisexec = os.path.join(cachedir, b'checkisexec')
223 checknoexec = os.path.join(cachedir, b'checknoexec')
223 checknoexec = os.path.join(cachedir, b'checknoexec')
224
224
225 try:
225 try:
226 m = os.stat(checkisexec).st_mode
226 m = os.stat(checkisexec).st_mode
227 except FileNotFoundError:
227 except FileNotFoundError:
228 # checkisexec does not exist - fall through ...
228 # checkisexec does not exist - fall through ...
229 pass
229 pass
230 else:
230 else:
231 # checkisexec exists, check if it actually is exec
231 # checkisexec exists, check if it actually is exec
232 if m & EXECFLAGS != 0:
232 if m & EXECFLAGS != 0:
233 # ensure checkisexec exists, check it isn't exec
233 # ensure checkisexec exists, check it isn't exec
234 try:
234 try:
235 m = os.stat(checknoexec).st_mode
235 m = os.stat(checknoexec).st_mode
236 except FileNotFoundError:
236 except FileNotFoundError:
237 open(checknoexec, b'w').close() # might fail
237 open(checknoexec, b'w').close() # might fail
238 m = os.stat(checknoexec).st_mode
238 m = os.stat(checknoexec).st_mode
239 if m & EXECFLAGS == 0:
239 if m & EXECFLAGS == 0:
240 # check-exec is exec and check-no-exec is not exec
240 # check-exec is exec and check-no-exec is not exec
241 return True
241 return True
242 # checknoexec exists but is exec - delete it
242 # checknoexec exists but is exec - delete it
243 unlink(checknoexec)
243 unlink(checknoexec)
244 # checkisexec exists but is not exec - delete it
244 # checkisexec exists but is not exec - delete it
245 unlink(checkisexec)
245 unlink(checkisexec)
246
246
247 # check using one file, leave it as checkisexec
247 # check using one file, leave it as checkisexec
248 checkdir = cachedir
248 checkdir = cachedir
249 else:
249 else:
250 # check directly in path and don't leave checkisexec behind
250 # check directly in path and don't leave checkisexec behind
251 checkdir = path
251 checkdir = path
252 checkisexec = None
252 checkisexec = None
253 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
253 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
254 try:
254 try:
255 os.close(fh)
255 os.close(fh)
256 m = os.stat(fn).st_mode
256 m = os.stat(fn).st_mode
257 if m & EXECFLAGS == 0:
257 if m & EXECFLAGS == 0:
258 os.chmod(fn, m & 0o777 | EXECFLAGS)
258 os.chmod(fn, m & 0o777 | EXECFLAGS)
259 if os.stat(fn).st_mode & EXECFLAGS != 0:
259 if os.stat(fn).st_mode & EXECFLAGS != 0:
260 if checkisexec is not None:
260 if checkisexec is not None:
261 os.rename(fn, checkisexec)
261 os.rename(fn, checkisexec)
262 fn = None
262 fn = None
263 return True
263 return True
264 finally:
264 finally:
265 if fn is not None:
265 if fn is not None:
266 unlink(fn)
266 unlink(fn)
267 except (IOError, OSError):
267 except (IOError, OSError):
268 # we don't care, the user probably won't be able to commit anyway
268 # we don't care, the user probably won't be able to commit anyway
269 return False
269 return False
270
270
271
271
272 def checklink(path):
272 def checklink(path):
273 """check whether the given path is on a symlink-capable filesystem"""
273 """check whether the given path is on a symlink-capable filesystem"""
274 # mktemp is not racy because symlink creation will fail if the
274 # mktemp is not racy because symlink creation will fail if the
275 # file already exists
275 # file already exists
276 while True:
276 while True:
277 cachedir = os.path.join(path, b'.hg', b'wcache')
277 cachedir = os.path.join(path, b'.hg', b'wcache')
278 checklink = os.path.join(cachedir, b'checklink')
278 checklink = os.path.join(cachedir, b'checklink')
279 # try fast path, read only
279 # try fast path, read only
280 if os.path.islink(checklink):
280 if os.path.islink(checklink):
281 return True
281 return True
282 if os.path.isdir(cachedir):
282 if os.path.isdir(cachedir):
283 checkdir = cachedir
283 checkdir = cachedir
284 else:
284 else:
285 checkdir = path
285 checkdir = path
286 cachedir = None
286 cachedir = None
287 name = tempfile.mktemp(
287 name = tempfile.mktemp(
288 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
288 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
289 )
289 )
290 name = pycompat.fsencode(name)
290 name = pycompat.fsencode(name)
291 try:
291 try:
292 fd = None
292 fd = None
293 if cachedir is None:
293 if cachedir is None:
294 fd = pycompat.namedtempfile(
294 fd = pycompat.namedtempfile(
295 dir=checkdir, prefix=b'hg-checklink-'
295 dir=checkdir, prefix=b'hg-checklink-'
296 )
296 )
297 target = os.path.basename(fd.name)
297 target = os.path.basename(fd.name)
298 else:
298 else:
299 # create a fixed file to link to; doesn't matter if it
299 # create a fixed file to link to; doesn't matter if it
300 # already exists.
300 # already exists.
301 target = b'checklink-target'
301 target = b'checklink-target'
302 try:
302 try:
303 fullpath = os.path.join(cachedir, target)
303 fullpath = os.path.join(cachedir, target)
304 open(fullpath, b'w').close()
304 open(fullpath, b'w').close()
305 except PermissionError:
305 except PermissionError:
306 # If we can't write to cachedir, just pretend
306 # If we can't write to cachedir, just pretend
307 # that the fs is readonly and by association
307 # that the fs is readonly and by association
308 # that the fs won't support symlinks. This
308 # that the fs won't support symlinks. This
309 # seems like the least dangerous way to avoid
309 # seems like the least dangerous way to avoid
310 # data loss.
310 # data loss.
311 return False
311 return False
312 try:
312 try:
313 os.symlink(target, name)
313 os.symlink(target, name)
314 if cachedir is None:
314 if cachedir is None:
315 unlink(name)
315 unlink(name)
316 else:
316 else:
317 try:
317 try:
318 os.rename(name, checklink)
318 os.rename(name, checklink)
319 except OSError:
319 except OSError:
320 unlink(name)
320 unlink(name)
321 return True
321 return True
322 except FileExistsError:
322 except FileExistsError:
323 # link creation might race, try again
323 # link creation might race, try again
324 continue
324 continue
325 finally:
325 finally:
326 if fd is not None:
326 if fd is not None:
327 fd.close()
327 fd.close()
328 except AttributeError:
328 except AttributeError:
329 return False
329 return False
330 except OSError as inst:
330 except OSError as inst:
331 # sshfs might report failure while successfully creating the link
331 # sshfs might report failure while successfully creating the link
332 if inst.errno == errno.EIO and os.path.exists(name):
332 if inst.errno == errno.EIO and os.path.exists(name):
333 unlink(name)
333 unlink(name)
334 return False
334 return False
335
335
336
336
337 def checkosfilename(path):
337 def checkosfilename(path):
338 """Check that the base-relative path is a valid filename on this platform.
338 """Check that the base-relative path is a valid filename on this platform.
339 Returns None if the path is ok, or a UI string describing the problem."""
339 Returns None if the path is ok, or a UI string describing the problem."""
340 return None # on posix platforms, every path is ok
340 return None # on posix platforms, every path is ok
341
341
342
342
343 def getfsmountpoint(dirpath):
343 def getfsmountpoint(dirpath):
344 """Get the filesystem mount point from a directory (best-effort)
344 """Get the filesystem mount point from a directory (best-effort)
345
345
346 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
346 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
347 """
347 """
348 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
348 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
349
349
350
350
351 def getfstype(dirpath):
351 def getfstype(dirpath):
352 """Get the filesystem type name from a directory (best-effort)
352 """Get the filesystem type name from a directory (best-effort)
353
353
354 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
354 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
355 """
355 """
356 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
356 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
357
357
358
358
359 def get_password():
359 def get_password():
360 return encoding.strtolocal(getpass.getpass(''))
360 return encoding.strtolocal(getpass.getpass(''))
361
361
362
362
363 def setbinary(fd):
363 def setbinary(fd):
364 pass
364 pass
365
365
366
366
367 def pconvert(path):
367 def pconvert(path):
368 return path
368 return path
369
369
370
370
371 def localpath(path):
371 def localpath(path):
372 return path
372 return path
373
373
374
374
375 def samefile(fpath1, fpath2):
375 def samefile(fpath1, fpath2):
376 """Returns whether path1 and path2 refer to the same file. This is only
376 """Returns whether path1 and path2 refer to the same file. This is only
377 guaranteed to work for files, not directories."""
377 guaranteed to work for files, not directories."""
378 return os.path.samefile(fpath1, fpath2)
378 return os.path.samefile(fpath1, fpath2)
379
379
380
380
381 def samedevice(fpath1, fpath2):
381 def samedevice(fpath1, fpath2):
382 """Returns whether fpath1 and fpath2 are on the same device. This is only
382 """Returns whether fpath1 and fpath2 are on the same device. This is only
383 guaranteed to work for files, not directories."""
383 guaranteed to work for files, not directories."""
384 st1 = os.lstat(fpath1)
384 st1 = os.lstat(fpath1)
385 st2 = os.lstat(fpath2)
385 st2 = os.lstat(fpath2)
386 return st1.st_dev == st2.st_dev
386 return st1.st_dev == st2.st_dev
387
387
388
388
389 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
389 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
390 def normcase(path):
390 def normcase(path):
391 return path.lower()
391 return path.lower()
392
392
393
393
394 # what normcase does to ASCII strings
394 # what normcase does to ASCII strings
395 normcasespec = encoding.normcasespecs.lower
395 normcasespec = encoding.normcasespecs.lower
396 # fallback normcase function for non-ASCII strings
396 # fallback normcase function for non-ASCII strings
397 normcasefallback = normcase
397 normcasefallback = normcase
398
398
399 if pycompat.isdarwin:
399 if pycompat.isdarwin:
400
400
401 def normcase(path):
401 def normcase(path):
402 """
402 """
403 Normalize a filename for OS X-compatible comparison:
403 Normalize a filename for OS X-compatible comparison:
404 - escape-encode invalid characters
404 - escape-encode invalid characters
405 - decompose to NFD
405 - decompose to NFD
406 - lowercase
406 - lowercase
407 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
407 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
408
408
409 >>> normcase(b'UPPER')
409 >>> normcase(b'UPPER')
410 'upper'
410 'upper'
411 >>> normcase(b'Caf\\xc3\\xa9')
411 >>> normcase(b'Caf\\xc3\\xa9')
412 'cafe\\xcc\\x81'
412 'cafe\\xcc\\x81'
413 >>> normcase(b'\\xc3\\x89')
413 >>> normcase(b'\\xc3\\x89')
414 'e\\xcc\\x81'
414 'e\\xcc\\x81'
415 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
415 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
416 '%b8%ca%c3\\xca\\xbe%c8.jpg'
416 '%b8%ca%c3\\xca\\xbe%c8.jpg'
417 """
417 """
418
418
419 try:
419 try:
420 return encoding.asciilower(path) # exception for non-ASCII
420 return encoding.asciilower(path) # exception for non-ASCII
421 except UnicodeDecodeError:
421 except UnicodeDecodeError:
422 return normcasefallback(path)
422 return normcasefallback(path)
423
423
424 normcasespec = encoding.normcasespecs.lower
424 normcasespec = encoding.normcasespecs.lower
425
425
426 def normcasefallback(path):
426 def normcasefallback(path):
427 try:
427 try:
428 u = path.decode('utf-8')
428 u = path.decode('utf-8')
429 except UnicodeDecodeError:
429 except UnicodeDecodeError:
430 # OS X percent-encodes any bytes that aren't valid utf-8
430 # OS X percent-encodes any bytes that aren't valid utf-8
431 s = b''
431 s = b''
432 pos = 0
432 pos = 0
433 l = len(path)
433 l = len(path)
434 while pos < l:
434 while pos < l:
435 try:
435 try:
436 c = encoding.getutf8char(path, pos)
436 c = encoding.getutf8char(path, pos)
437 pos += len(c)
437 pos += len(c)
438 except ValueError:
438 except ValueError:
439 c = b'%%%02X' % ord(path[pos : pos + 1])
439 c = b'%%%02X' % ord(path[pos : pos + 1])
440 pos += 1
440 pos += 1
441 s += c
441 s += c
442
442
443 u = s.decode('utf-8')
443 u = s.decode('utf-8')
444
444
445 # Decompose then lowercase (HFS+ technote specifies lower)
445 # Decompose then lowercase (HFS+ technote specifies lower)
446 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
446 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
447 # drop HFS+ ignored characters
447 # drop HFS+ ignored characters
448 return encoding.hfsignoreclean(enc)
448 return encoding.hfsignoreclean(enc)
449
449
450
450
451 if pycompat.sysplatform == b'cygwin':
451 if pycompat.sysplatform == b'cygwin':
452 # workaround for cygwin, in which mount point part of path is
452 # workaround for cygwin, in which mount point part of path is
453 # treated as case sensitive, even though underlying NTFS is case
453 # treated as case sensitive, even though underlying NTFS is case
454 # insensitive.
454 # insensitive.
455
455
456 # default mount points
456 # default mount points
457 cygwinmountpoints = sorted(
457 cygwinmountpoints = sorted(
458 [
458 [
459 b"/usr/bin",
459 b"/usr/bin",
460 b"/usr/lib",
460 b"/usr/lib",
461 b"/cygdrive",
461 b"/cygdrive",
462 ],
462 ],
463 reverse=True,
463 reverse=True,
464 )
464 )
465
465
466 # use upper-ing as normcase as same as NTFS workaround
466 # use upper-ing as normcase as same as NTFS workaround
467 def normcase(path):
467 def normcase(path):
468 pathlen = len(path)
468 pathlen = len(path)
469 if (pathlen == 0) or (path[0] != pycompat.ossep):
469 if (pathlen == 0) or (path[0] != pycompat.ossep):
470 # treat as relative
470 # treat as relative
471 return encoding.upper(path)
471 return encoding.upper(path)
472
472
473 # to preserve case of mountpoint part
473 # to preserve case of mountpoint part
474 for mp in cygwinmountpoints:
474 for mp in cygwinmountpoints:
475 if not path.startswith(mp):
475 if not path.startswith(mp):
476 continue
476 continue
477
477
478 mplen = len(mp)
478 mplen = len(mp)
479 if mplen == pathlen: # mount point itself
479 if mplen == pathlen: # mount point itself
480 return mp
480 return mp
481 if path[mplen] == pycompat.ossep:
481 if path[mplen] == pycompat.ossep:
482 return mp + encoding.upper(path[mplen:])
482 return mp + encoding.upper(path[mplen:])
483
483
484 return encoding.upper(path)
484 return encoding.upper(path)
485
485
486 normcasespec = encoding.normcasespecs.other
486 normcasespec = encoding.normcasespecs.other
487 normcasefallback = normcase
487 normcasefallback = normcase
488
488
489 # Cygwin translates native ACLs to POSIX permissions,
489 # Cygwin translates native ACLs to POSIX permissions,
490 # but these translations are not supported by native
490 # but these translations are not supported by native
491 # tools, so the exec bit tends to be set erroneously.
491 # tools, so the exec bit tends to be set erroneously.
492 # Therefore, disable executable bit access on Cygwin.
492 # Therefore, disable executable bit access on Cygwin.
493 def checkexec(path):
493 def checkexec(path):
494 return False
494 return False
495
495
496 # Similarly, Cygwin's symlink emulation is likely to create
496 # Similarly, Cygwin's symlink emulation is likely to create
497 # problems when Mercurial is used from both Cygwin and native
497 # problems when Mercurial is used from both Cygwin and native
498 # Windows, with other native tools, or on shared volumes
498 # Windows, with other native tools, or on shared volumes
499 def checklink(path):
499 def checklink(path):
500 return False
500 return False
501
501
502
502
503 _needsshellquote = None
503 _needsshellquote = None
504
504
505
505
506 def shellquote(s):
506 def shellquote(s):
507 if pycompat.sysplatform == b'OpenVMS':
507 if pycompat.sysplatform == b'OpenVMS':
508 return b'"%s"' % s
508 return b'"%s"' % s
509 global _needsshellquote
509 global _needsshellquote
510 if _needsshellquote is None:
510 if _needsshellquote is None:
511 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
511 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
512 if s and not _needsshellquote(s):
512 if s and not _needsshellquote(s):
513 # "s" shouldn't have to be quoted
513 # "s" shouldn't have to be quoted
514 return s
514 return s
515 else:
515 else:
516 return b"'%s'" % s.replace(b"'", b"'\\''")
516 return b"'%s'" % s.replace(b"'", b"'\\''")
517
517
518
518
519 def shellsplit(s):
519 def shellsplit(s):
520 """Parse a command string in POSIX shell way (best-effort)"""
520 """Parse a command string in POSIX shell way (best-effort)"""
521 return pycompat.shlexsplit(s, posix=True)
521 return pycompat.shlexsplit(s, posix=True)
522
522
523
523
524 def testpid(pid):
524 def testpid(pid):
525 '''return False if pid dead, True if running or not sure'''
525 '''return False if pid dead, True if running or not sure'''
526 if pycompat.sysplatform == b'OpenVMS':
526 if pycompat.sysplatform == b'OpenVMS':
527 return True
527 return True
528 try:
528 try:
529 os.kill(pid, 0)
529 os.kill(pid, 0)
530 return True
530 return True
531 except OSError as inst:
531 except OSError as inst:
532 return inst.errno != errno.ESRCH
532 return inst.errno != errno.ESRCH
533
533
534
534
535 def isowner(st):
535 def isowner(st):
536 """Return True if the stat object st is from the current user."""
536 """Return True if the stat object st is from the current user."""
537 return st.st_uid == os.getuid()
537 return st.st_uid == os.getuid()
538
538
539
539
540 def findexe(command):
540 def findexe(command):
541 """Find executable for command searching like which does.
541 """Find executable for command searching like which does.
542 If command is a basename then PATH is searched for command.
542 If command is a basename then PATH is searched for command.
543 PATH isn't searched if command is an absolute or relative path.
543 PATH isn't searched if command is an absolute or relative path.
544 If command isn't found None is returned."""
544 If command isn't found None is returned."""
545 if pycompat.sysplatform == b'OpenVMS':
545 if pycompat.sysplatform == b'OpenVMS':
546 return command
546 return command
547
547
548 def findexisting(executable):
548 def findexisting(executable):
549 b'Will return executable if existing file'
549 b'Will return executable if existing file'
550 if os.path.isfile(executable) and os.access(executable, os.X_OK):
550 if os.path.isfile(executable) and os.access(executable, os.X_OK):
551 return executable
551 return executable
552 return None
552 return None
553
553
554 if pycompat.ossep in command:
554 if pycompat.ossep in command:
555 return findexisting(command)
555 return findexisting(command)
556
556
557 if pycompat.sysplatform == b'plan9':
557 if pycompat.sysplatform == b'plan9':
558 return findexisting(os.path.join(b'/bin', command))
558 return findexisting(os.path.join(b'/bin', command))
559
559
560 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
560 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
561 executable = findexisting(os.path.join(path, command))
561 executable = findexisting(os.path.join(path, command))
562 if executable is not None:
562 if executable is not None:
563 return executable
563 return executable
564 return None
564 return None
565
565
566
566
567 def setsignalhandler():
567 def setsignalhandler():
568 pass
568 pass
569
569
570
570
571 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
571 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
572
572
573
573
574 def statfiles(files):
574 def statfiles(files):
575 """Stat each file in files. Yield each stat, or None if a file does not
575 """Stat each file in files. Yield each stat, or None if a file does not
576 exist or has a type we don't care about."""
576 exist or has a type we don't care about."""
577 lstat = os.lstat
577 lstat = os.lstat
578 getkind = stat.S_IFMT
578 getkind = stat.S_IFMT
579 for nf in files:
579 for nf in files:
580 try:
580 try:
581 st = lstat(nf)
581 st = lstat(nf)
582 if getkind(st.st_mode) not in _wantedkinds:
582 if getkind(st.st_mode) not in _wantedkinds:
583 st = None
583 st = None
584 except (FileNotFoundError, NotADirectoryError):
584 except (FileNotFoundError, NotADirectoryError):
585 st = None
585 st = None
586 yield st
586 yield st
587
587
588
588
589 def getuser():
589 def getuser():
590 '''return name of current user'''
590 '''return name of current user'''
591 return pycompat.fsencode(getpass.getuser())
591 return pycompat.fsencode(getpass.getuser())
592
592
593
593
594 def username(uid=None):
594 def username(uid=None):
595 """Return the name of the user with the given uid.
595 """Return the name of the user with the given uid.
596
596
597 If uid is None, return the name of the current user."""
597 If uid is None, return the name of the current user."""
598
598
599 if uid is None:
599 if uid is None:
600 uid = os.getuid()
600 uid = os.getuid()
601 try:
601 try:
602 return pycompat.fsencode(pwd.getpwuid(uid)[0])
602 return pycompat.fsencode(pwd.getpwuid(uid)[0])
603 except KeyError:
603 except KeyError:
604 return b'%d' % uid
604 return b'%d' % uid
605
605
606
606
607 def groupname(gid=None):
607 def groupname(gid=None):
608 """Return the name of the group with the given gid.
608 """Return the name of the group with the given gid.
609
609
610 If gid is None, return the name of the current group."""
610 If gid is None, return the name of the current group."""
611
611
612 if gid is None:
612 if gid is None:
613 gid = os.getgid()
613 gid = os.getgid()
614 try:
614 try:
615 return pycompat.fsencode(grp.getgrgid(gid)[0])
615 return pycompat.fsencode(grp.getgrgid(gid)[0])
616 except KeyError:
616 except KeyError:
617 return pycompat.bytestr(gid)
617 return pycompat.bytestr(gid)
618
618
619
619
620 def groupmembers(name):
620 def groupmembers(name):
621 """Return the list of members of the group with the given
621 """Return the list of members of the group with the given
622 name, KeyError if the group does not exist.
622 name, KeyError if the group does not exist.
623 """
623 """
624 name = pycompat.fsdecode(name)
624 name = pycompat.fsdecode(name)
625 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
625 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
626
626
627
627
628 def spawndetached(args):
628 def spawndetached(args) -> int:
629 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
629 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
630
630
631
631
632 def gethgcmd():
632 def gethgcmd():
633 return sys.argv[:1]
633 return sys.argv[:1]
634
634
635
635
636 def makedir(path, notindexed):
636 def makedir(path, notindexed):
637 os.mkdir(path)
637 os.mkdir(path)
638
638
639
639
640 def lookupreg(key, name=None, scope=None):
640 def lookupreg(key, name=None, scope=None):
641 return None
641 return None
642
642
643
643
644 def hidewindow():
644 def hidewindow():
645 """Hide current shell window.
645 """Hide current shell window.
646
646
647 Used to hide the window opened when starting asynchronous
647 Used to hide the window opened when starting asynchronous
648 child process under Windows, unneeded on other systems.
648 child process under Windows, unneeded on other systems.
649 """
649 """
650 pass
650 pass
651
651
652
652
653 class cachestat:
653 class cachestat:
654 def __init__(self, path):
654 def __init__(self, path):
655 self.stat = os.stat(path)
655 self.stat = os.stat(path)
656
656
657 def cacheable(self):
657 def cacheable(self):
658 return bool(self.stat.st_ino)
658 return bool(self.stat.st_ino)
659
659
660 __hash__ = object.__hash__
660 __hash__ = object.__hash__
661
661
662 def __eq__(self, other):
662 def __eq__(self, other):
663 try:
663 try:
664 # Only dev, ino, size, mtime and atime are likely to change. Out
664 # Only dev, ino, size, mtime and atime are likely to change. Out
665 # of these, we shouldn't compare atime but should compare the
665 # of these, we shouldn't compare atime but should compare the
666 # rest. However, one of the other fields changing indicates
666 # rest. However, one of the other fields changing indicates
667 # something fishy going on, so return False if anything but atime
667 # something fishy going on, so return False if anything but atime
668 # changes.
668 # changes.
669 return (
669 return (
670 self.stat.st_mode == other.stat.st_mode
670 self.stat.st_mode == other.stat.st_mode
671 and self.stat.st_ino == other.stat.st_ino
671 and self.stat.st_ino == other.stat.st_ino
672 and self.stat.st_dev == other.stat.st_dev
672 and self.stat.st_dev == other.stat.st_dev
673 and self.stat.st_nlink == other.stat.st_nlink
673 and self.stat.st_nlink == other.stat.st_nlink
674 and self.stat.st_uid == other.stat.st_uid
674 and self.stat.st_uid == other.stat.st_uid
675 and self.stat.st_gid == other.stat.st_gid
675 and self.stat.st_gid == other.stat.st_gid
676 and self.stat.st_size == other.stat.st_size
676 and self.stat.st_size == other.stat.st_size
677 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
677 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
678 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
678 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
679 )
679 )
680 except AttributeError:
680 except AttributeError:
681 return False
681 return False
682
682
683 def __ne__(self, other):
683 def __ne__(self, other):
684 return not self == other
684 return not self == other
685
685
686
686
687 def statislink(st):
687 def statislink(st):
688 '''check whether a stat result is a symlink'''
688 '''check whether a stat result is a symlink'''
689 return st and stat.S_ISLNK(st.st_mode)
689 return st and stat.S_ISLNK(st.st_mode)
690
690
691
691
692 def statisexec(st):
692 def statisexec(st):
693 '''check whether a stat result is an executable file'''
693 '''check whether a stat result is an executable file'''
694 return st and (st.st_mode & 0o100 != 0)
694 return st and (st.st_mode & 0o100 != 0)
695
695
696
696
697 def poll(fds):
697 def poll(fds):
698 """block until something happens on any file descriptor
698 """block until something happens on any file descriptor
699
699
700 This is a generic helper that will check for any activity
700 This is a generic helper that will check for any activity
701 (read, write. exception) and return the list of touched files.
701 (read, write. exception) and return the list of touched files.
702
702
703 In unsupported cases, it will raise a NotImplementedError"""
703 In unsupported cases, it will raise a NotImplementedError"""
704 try:
704 try:
705 res = select.select(fds, fds, fds)
705 res = select.select(fds, fds, fds)
706 except ValueError: # out of range file descriptor
706 except ValueError: # out of range file descriptor
707 raise NotImplementedError()
707 raise NotImplementedError()
708 return sorted(list(set(sum(res, []))))
708 return sorted(list(set(sum(res, []))))
709
709
710
710
711 def readpipe(pipe):
711 def readpipe(pipe):
712 """Read all available data from a pipe."""
712 """Read all available data from a pipe."""
713 # We can't fstat() a pipe because Linux will always report 0.
713 # We can't fstat() a pipe because Linux will always report 0.
714 # So, we set the pipe to non-blocking mode and read everything
714 # So, we set the pipe to non-blocking mode and read everything
715 # that's available.
715 # that's available.
716 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
716 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
717 flags |= os.O_NONBLOCK
717 flags |= os.O_NONBLOCK
718 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
718 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
719
719
720 try:
720 try:
721 chunks = []
721 chunks = []
722 while True:
722 while True:
723 try:
723 try:
724 s = pipe.read()
724 s = pipe.read()
725 if not s:
725 if not s:
726 break
726 break
727 chunks.append(s)
727 chunks.append(s)
728 except IOError:
728 except IOError:
729 break
729 break
730
730
731 return b''.join(chunks)
731 return b''.join(chunks)
732 finally:
732 finally:
733 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
733 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
734
734
735
735
736 def bindunixsocket(sock, path):
736 def bindunixsocket(sock, path):
737 """Bind the UNIX domain socket to the specified path"""
737 """Bind the UNIX domain socket to the specified path"""
738 # use relative path instead of full path at bind() if possible, since
738 # use relative path instead of full path at bind() if possible, since
739 # AF_UNIX path has very small length limit (107 chars) on common
739 # AF_UNIX path has very small length limit (107 chars) on common
740 # platforms (see sys/un.h)
740 # platforms (see sys/un.h)
741 dirname, basename = os.path.split(path)
741 dirname, basename = os.path.split(path)
742 bakwdfd = None
742 bakwdfd = None
743
743
744 try:
744 try:
745 if dirname:
745 if dirname:
746 bakwdfd = os.open(b'.', os.O_DIRECTORY)
746 bakwdfd = os.open(b'.', os.O_DIRECTORY)
747 os.chdir(dirname)
747 os.chdir(dirname)
748 sock.bind(basename)
748 sock.bind(basename)
749 if bakwdfd:
749 if bakwdfd:
750 os.fchdir(bakwdfd)
750 os.fchdir(bakwdfd)
751 finally:
751 finally:
752 if bakwdfd:
752 if bakwdfd:
753 os.close(bakwdfd)
753 os.close(bakwdfd)
@@ -1,795 +1,801 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10
10
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import io
13 import io
14 import os
14 import os
15 import signal
15 import signal
16 import subprocess
16 import subprocess
17 import sys
17 import sys
18 import threading
18 import threading
19 import time
19 import time
20
20
21 from typing import (
21 from typing import (
22 BinaryIO,
22 BinaryIO,
23 )
23 )
24
24
25 from ..i18n import _
25 from ..i18n import _
26 from ..pycompat import (
26 from ..pycompat import (
27 getattr,
27 getattr,
28 open,
28 open,
29 )
29 )
30
30
31 from .. import (
31 from .. import (
32 encoding,
32 encoding,
33 error,
33 error,
34 policy,
34 policy,
35 pycompat,
35 pycompat,
36 typelib,
36 typelib,
37 )
37 )
38
38
39 # Import like this to keep import-checker happy
39 # Import like this to keep import-checker happy
40 from ..utils import resourceutil
40 from ..utils import resourceutil
41
41
42 osutil = policy.importmod('osutil')
42 osutil = policy.importmod('osutil')
43
43
44 if pycompat.iswindows:
44 if pycompat.iswindows:
45 from .. import windows as platform
45 from .. import windows as platform
46 else:
46 else:
47 from .. import posix as platform
47 from .. import posix as platform
48
48
49
49
50 def isatty(fp):
50 def isatty(fp):
51 try:
51 try:
52 return fp.isatty()
52 return fp.isatty()
53 except AttributeError:
53 except AttributeError:
54 return False
54 return False
55
55
56
56
57 class BadFile(io.RawIOBase):
57 class BadFile(io.RawIOBase):
58 """Dummy file object to simulate closed stdio behavior"""
58 """Dummy file object to simulate closed stdio behavior"""
59
59
60 def readinto(self, b):
60 def readinto(self, b):
61 raise IOError(errno.EBADF, 'Bad file descriptor')
61 raise IOError(errno.EBADF, 'Bad file descriptor')
62
62
63 def write(self, b):
63 def write(self, b):
64 raise IOError(errno.EBADF, 'Bad file descriptor')
64 raise IOError(errno.EBADF, 'Bad file descriptor')
65
65
66
66
67 class LineBufferedWrapper:
67 class LineBufferedWrapper:
68 def __init__(self, orig):
68 def __init__(self, orig):
69 self.orig = orig
69 self.orig = orig
70
70
71 def __getattr__(self, attr):
71 def __getattr__(self, attr):
72 return getattr(self.orig, attr)
72 return getattr(self.orig, attr)
73
73
74 def write(self, s):
74 def write(self, s):
75 orig = self.orig
75 orig = self.orig
76 res = orig.write(s)
76 res = orig.write(s)
77 if s.endswith(b'\n'):
77 if s.endswith(b'\n'):
78 orig.flush()
78 orig.flush()
79 return res
79 return res
80
80
81
81
82 # pytype: disable=attribute-error
82 # pytype: disable=attribute-error
83 io.BufferedIOBase.register(LineBufferedWrapper)
83 io.BufferedIOBase.register(LineBufferedWrapper)
84 # pytype: enable=attribute-error
84 # pytype: enable=attribute-error
85
85
86
86
87 def make_line_buffered(stream):
87 def make_line_buffered(stream):
88 # First, check if we need to wrap the stream.
88 # First, check if we need to wrap the stream.
89 check_stream = stream
89 check_stream = stream
90 while True:
90 while True:
91 if isinstance(check_stream, WriteAllWrapper):
91 if isinstance(check_stream, WriteAllWrapper):
92 check_stream = check_stream.orig
92 check_stream = check_stream.orig
93 elif pycompat.iswindows and isinstance(
93 elif pycompat.iswindows and isinstance(
94 check_stream,
94 check_stream,
95 # pytype: disable=module-attr
95 # pytype: disable=module-attr
96 platform.winstdout
96 platform.winstdout
97 # pytype: enable=module-attr
97 # pytype: enable=module-attr
98 ):
98 ):
99 check_stream = check_stream.fp
99 check_stream = check_stream.fp
100 else:
100 else:
101 break
101 break
102 if isinstance(check_stream, io.RawIOBase):
102 if isinstance(check_stream, io.RawIOBase):
103 # The stream is unbuffered, we don't need to emulate line buffering.
103 # The stream is unbuffered, we don't need to emulate line buffering.
104 return stream
104 return stream
105 elif isinstance(check_stream, io.BufferedIOBase):
105 elif isinstance(check_stream, io.BufferedIOBase):
106 # The stream supports some kind of buffering. We can't assume that
106 # The stream supports some kind of buffering. We can't assume that
107 # lines are flushed. Fall back to wrapping the stream.
107 # lines are flushed. Fall back to wrapping the stream.
108 pass
108 pass
109 else:
109 else:
110 raise NotImplementedError(
110 raise NotImplementedError(
111 "can't determine whether stream is buffered or not"
111 "can't determine whether stream is buffered or not"
112 )
112 )
113
113
114 if isinstance(stream, LineBufferedWrapper):
114 if isinstance(stream, LineBufferedWrapper):
115 return stream
115 return stream
116 return LineBufferedWrapper(stream)
116 return LineBufferedWrapper(stream)
117
117
118
118
119 def unwrap_line_buffered(stream):
119 def unwrap_line_buffered(stream):
120 if isinstance(stream, LineBufferedWrapper):
120 if isinstance(stream, LineBufferedWrapper):
121 assert not isinstance(stream.orig, LineBufferedWrapper)
121 assert not isinstance(stream.orig, LineBufferedWrapper)
122 return stream.orig
122 return stream.orig
123 return stream
123 return stream
124
124
125
125
126 class WriteAllWrapper(typelib.BinaryIO_Proxy):
126 class WriteAllWrapper(typelib.BinaryIO_Proxy):
127 def __init__(self, orig: BinaryIO):
127 def __init__(self, orig: BinaryIO):
128 self.orig = orig
128 self.orig = orig
129
129
130 def __getattr__(self, attr):
130 def __getattr__(self, attr):
131 return getattr(self.orig, attr)
131 return getattr(self.orig, attr)
132
132
133 def write(self, s):
133 def write(self, s):
134 write1 = self.orig.write
134 write1 = self.orig.write
135 m = memoryview(s)
135 m = memoryview(s)
136 total_to_write = len(s)
136 total_to_write = len(s)
137 total_written = 0
137 total_written = 0
138 while total_written < total_to_write:
138 while total_written < total_to_write:
139 c = write1(m[total_written:])
139 c = write1(m[total_written:])
140 if c:
140 if c:
141 total_written += c
141 total_written += c
142 return total_written
142 return total_written
143
143
144
144
145 # pytype: disable=attribute-error
145 # pytype: disable=attribute-error
146 io.IOBase.register(WriteAllWrapper)
146 io.IOBase.register(WriteAllWrapper)
147 # pytype: enable=attribute-error
147 # pytype: enable=attribute-error
148
148
149
149
150 def _make_write_all(stream):
150 def _make_write_all(stream):
151 if isinstance(stream, WriteAllWrapper):
151 if isinstance(stream, WriteAllWrapper):
152 return stream
152 return stream
153 if isinstance(stream, io.BufferedIOBase):
153 if isinstance(stream, io.BufferedIOBase):
154 # The io.BufferedIOBase.write() contract guarantees that all data is
154 # The io.BufferedIOBase.write() contract guarantees that all data is
155 # written.
155 # written.
156 return stream
156 return stream
157 # In general, the write() method of streams is free to write only part of
157 # In general, the write() method of streams is free to write only part of
158 # the data.
158 # the data.
159 return WriteAllWrapper(stream)
159 return WriteAllWrapper(stream)
160
160
161
161
162 # Python 3 implements its own I/O streams. Unlike stdio of C library,
162 # Python 3 implements its own I/O streams. Unlike stdio of C library,
163 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
163 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
164
164
165 # TODO: .buffer might not exist if std streams were replaced; we'll need
165 # TODO: .buffer might not exist if std streams were replaced; we'll need
166 # a silly wrapper to make a bytes stream backed by a unicode one.
166 # a silly wrapper to make a bytes stream backed by a unicode one.
167
167
168 if sys.stdin is None:
168 if sys.stdin is None:
169 stdin = BadFile()
169 stdin = BadFile()
170 else:
170 else:
171 stdin = sys.stdin.buffer
171 stdin = sys.stdin.buffer
172 if sys.stdout is None:
172 if sys.stdout is None:
173 stdout = BadFile()
173 stdout = BadFile()
174 else:
174 else:
175 stdout = _make_write_all(sys.stdout.buffer)
175 stdout = _make_write_all(sys.stdout.buffer)
176 if sys.stderr is None:
176 if sys.stderr is None:
177 stderr = BadFile()
177 stderr = BadFile()
178 else:
178 else:
179 stderr = _make_write_all(sys.stderr.buffer)
179 stderr = _make_write_all(sys.stderr.buffer)
180
180
181 if pycompat.iswindows:
181 if pycompat.iswindows:
182 # Work around Windows bugs.
182 # Work around Windows bugs.
183 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
183 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
184 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
184 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
185 if isatty(stdout):
185 if isatty(stdout):
186 # The standard library doesn't offer line-buffered binary streams.
186 # The standard library doesn't offer line-buffered binary streams.
187 stdout = make_line_buffered(stdout)
187 stdout = make_line_buffered(stdout)
188
188
189 findexe = platform.findexe
189 findexe = platform.findexe
190 _gethgcmd = platform.gethgcmd
190 _gethgcmd = platform.gethgcmd
191 getuser = platform.getuser
191 getuser = platform.getuser
192 getpid = os.getpid
192 getpid = os.getpid
193 hidewindow = platform.hidewindow
193 hidewindow = platform.hidewindow
194 readpipe = platform.readpipe
194 readpipe = platform.readpipe
195 setbinary = platform.setbinary
195 setbinary = platform.setbinary
196 setsignalhandler = platform.setsignalhandler
196 setsignalhandler = platform.setsignalhandler
197 shellquote = platform.shellquote
197 shellquote = platform.shellquote
198 shellsplit = platform.shellsplit
198 shellsplit = platform.shellsplit
199 spawndetached = platform.spawndetached
199 spawndetached = platform.spawndetached
200 sshargs = platform.sshargs
200 sshargs = platform.sshargs
201 testpid = platform.testpid
201 testpid = platform.testpid
202
202
203 try:
203 try:
204 setprocname = osutil.setprocname
204 setprocname = osutil.setprocname
205 except AttributeError:
205 except AttributeError:
206 pass
206 pass
207 try:
207 try:
208 unblocksignal = osutil.unblocksignal
208 unblocksignal = osutil.unblocksignal
209 except AttributeError:
209 except AttributeError:
210 pass
210 pass
211
211
212 closefds = pycompat.isposix
212 closefds = pycompat.isposix
213
213
214
214
215 def explainexit(code):
215 def explainexit(code):
216 """return a message describing a subprocess status
216 """return a message describing a subprocess status
217 (codes from kill are negative - not os.system/wait encoding)"""
217 (codes from kill are negative - not os.system/wait encoding)"""
218 if code >= 0:
218 if code >= 0:
219 return _(b"exited with status %d") % code
219 return _(b"exited with status %d") % code
220 return _(b"killed by signal %d") % -code
220 return _(b"killed by signal %d") % -code
221
221
222
222
223 class _pfile:
223 class _pfile:
224 """File-like wrapper for a stream opened by subprocess.Popen()"""
224 """File-like wrapper for a stream opened by subprocess.Popen()"""
225
225
226 def __init__(self, proc, fp):
226 def __init__(self, proc, fp):
227 self._proc = proc
227 self._proc = proc
228 self._fp = fp
228 self._fp = fp
229
229
230 def close(self):
230 def close(self):
231 # unlike os.popen(), this returns an integer in subprocess coding
231 # unlike os.popen(), this returns an integer in subprocess coding
232 self._fp.close()
232 self._fp.close()
233 return self._proc.wait()
233 return self._proc.wait()
234
234
235 def __iter__(self):
235 def __iter__(self):
236 return iter(self._fp)
236 return iter(self._fp)
237
237
238 def __getattr__(self, attr):
238 def __getattr__(self, attr):
239 return getattr(self._fp, attr)
239 return getattr(self._fp, attr)
240
240
241 def __enter__(self):
241 def __enter__(self):
242 return self
242 return self
243
243
244 def __exit__(self, exc_type, exc_value, exc_tb):
244 def __exit__(self, exc_type, exc_value, exc_tb):
245 self.close()
245 self.close()
246
246
247
247
248 def popen(cmd, mode=b'rb', bufsize=-1):
248 def popen(cmd, mode=b'rb', bufsize=-1):
249 if mode == b'rb':
249 if mode == b'rb':
250 return _popenreader(cmd, bufsize)
250 return _popenreader(cmd, bufsize)
251 elif mode == b'wb':
251 elif mode == b'wb':
252 return _popenwriter(cmd, bufsize)
252 return _popenwriter(cmd, bufsize)
253 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
253 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
254
254
255
255
256 def _popenreader(cmd, bufsize):
256 def _popenreader(cmd, bufsize):
257 p = subprocess.Popen(
257 p = subprocess.Popen(
258 tonativestr(cmd),
258 tonativestr(cmd),
259 shell=True,
259 shell=True,
260 bufsize=bufsize,
260 bufsize=bufsize,
261 close_fds=closefds,
261 close_fds=closefds,
262 stdout=subprocess.PIPE,
262 stdout=subprocess.PIPE,
263 )
263 )
264 return _pfile(p, p.stdout)
264 return _pfile(p, p.stdout)
265
265
266
266
267 def _popenwriter(cmd, bufsize):
267 def _popenwriter(cmd, bufsize):
268 p = subprocess.Popen(
268 p = subprocess.Popen(
269 tonativestr(cmd),
269 tonativestr(cmd),
270 shell=True,
270 shell=True,
271 bufsize=bufsize,
271 bufsize=bufsize,
272 close_fds=closefds,
272 close_fds=closefds,
273 stdin=subprocess.PIPE,
273 stdin=subprocess.PIPE,
274 )
274 )
275 return _pfile(p, p.stdin)
275 return _pfile(p, p.stdin)
276
276
277
277
278 def popen2(cmd, env=None):
278 def popen2(cmd, env=None):
279 # Setting bufsize to -1 lets the system decide the buffer size.
279 # Setting bufsize to -1 lets the system decide the buffer size.
280 # The default for bufsize is 0, meaning unbuffered. This leads to
280 # The default for bufsize is 0, meaning unbuffered. This leads to
281 # poor performance on Mac OS X: http://bugs.python.org/issue4194
281 # poor performance on Mac OS X: http://bugs.python.org/issue4194
282 p = subprocess.Popen(
282 p = subprocess.Popen(
283 tonativestr(cmd),
283 tonativestr(cmd),
284 shell=True,
284 shell=True,
285 bufsize=-1,
285 bufsize=-1,
286 close_fds=closefds,
286 close_fds=closefds,
287 stdin=subprocess.PIPE,
287 stdin=subprocess.PIPE,
288 stdout=subprocess.PIPE,
288 stdout=subprocess.PIPE,
289 env=tonativeenv(env),
289 env=tonativeenv(env),
290 )
290 )
291 return p.stdin, p.stdout
291 return p.stdin, p.stdout
292
292
293
293
294 def popen3(cmd, env=None):
294 def popen3(cmd, env=None):
295 stdin, stdout, stderr, p = popen4(cmd, env)
295 stdin, stdout, stderr, p = popen4(cmd, env)
296 return stdin, stdout, stderr
296 return stdin, stdout, stderr
297
297
298
298
299 def popen4(cmd, env=None, bufsize=-1):
299 def popen4(cmd, env=None, bufsize=-1):
300 p = subprocess.Popen(
300 p = subprocess.Popen(
301 tonativestr(cmd),
301 tonativestr(cmd),
302 shell=True,
302 shell=True,
303 bufsize=bufsize,
303 bufsize=bufsize,
304 close_fds=closefds,
304 close_fds=closefds,
305 stdin=subprocess.PIPE,
305 stdin=subprocess.PIPE,
306 stdout=subprocess.PIPE,
306 stdout=subprocess.PIPE,
307 stderr=subprocess.PIPE,
307 stderr=subprocess.PIPE,
308 env=tonativeenv(env),
308 env=tonativeenv(env),
309 )
309 )
310 return p.stdin, p.stdout, p.stderr, p
310 return p.stdin, p.stdout, p.stderr, p
311
311
312
312
313 def pipefilter(s, cmd):
313 def pipefilter(s, cmd):
314 '''filter string S through command CMD, returning its output'''
314 '''filter string S through command CMD, returning its output'''
315 p = subprocess.Popen(
315 p = subprocess.Popen(
316 tonativestr(cmd),
316 tonativestr(cmd),
317 shell=True,
317 shell=True,
318 close_fds=closefds,
318 close_fds=closefds,
319 stdin=subprocess.PIPE,
319 stdin=subprocess.PIPE,
320 stdout=subprocess.PIPE,
320 stdout=subprocess.PIPE,
321 )
321 )
322 pout, perr = p.communicate(s)
322 pout, perr = p.communicate(s)
323 return pout
323 return pout
324
324
325
325
326 def tempfilter(s, cmd):
326 def tempfilter(s, cmd):
327 """filter string S through a pair of temporary files with CMD.
327 """filter string S through a pair of temporary files with CMD.
328 CMD is used as a template to create the real command to be run,
328 CMD is used as a template to create the real command to be run,
329 with the strings INFILE and OUTFILE replaced by the real names of
329 with the strings INFILE and OUTFILE replaced by the real names of
330 the temporary files generated."""
330 the temporary files generated."""
331 inname, outname = None, None
331 inname, outname = None, None
332 try:
332 try:
333 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
333 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
334 fp = os.fdopen(infd, 'wb')
334 fp = os.fdopen(infd, 'wb')
335 fp.write(s)
335 fp.write(s)
336 fp.close()
336 fp.close()
337 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
337 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
338 os.close(outfd)
338 os.close(outfd)
339 cmd = cmd.replace(b'INFILE', inname)
339 cmd = cmd.replace(b'INFILE', inname)
340 cmd = cmd.replace(b'OUTFILE', outname)
340 cmd = cmd.replace(b'OUTFILE', outname)
341 code = system(cmd)
341 code = system(cmd)
342 if pycompat.sysplatform == b'OpenVMS' and code & 1:
342 if pycompat.sysplatform == b'OpenVMS' and code & 1:
343 code = 0
343 code = 0
344 if code:
344 if code:
345 raise error.Abort(
345 raise error.Abort(
346 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
346 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
347 )
347 )
348 with open(outname, b'rb') as fp:
348 with open(outname, b'rb') as fp:
349 return fp.read()
349 return fp.read()
350 finally:
350 finally:
351 try:
351 try:
352 if inname:
352 if inname:
353 os.unlink(inname)
353 os.unlink(inname)
354 except OSError:
354 except OSError:
355 pass
355 pass
356 try:
356 try:
357 if outname:
357 if outname:
358 os.unlink(outname)
358 os.unlink(outname)
359 except OSError:
359 except OSError:
360 pass
360 pass
361
361
362
362
363 _filtertable = {
363 _filtertable = {
364 b'tempfile:': tempfilter,
364 b'tempfile:': tempfilter,
365 b'pipe:': pipefilter,
365 b'pipe:': pipefilter,
366 }
366 }
367
367
368
368
369 def filter(s, cmd):
369 def filter(s, cmd):
370 """filter a string through a command that transforms its input to its
370 """filter a string through a command that transforms its input to its
371 output"""
371 output"""
372 for name, fn in _filtertable.items():
372 for name, fn in _filtertable.items():
373 if cmd.startswith(name):
373 if cmd.startswith(name):
374 return fn(s, cmd[len(name) :].lstrip())
374 return fn(s, cmd[len(name) :].lstrip())
375 return pipefilter(s, cmd)
375 return pipefilter(s, cmd)
376
376
377
377
378 _hgexecutable = None
378 _hgexecutable = None
379
379
380
380
381 def hgexecutable():
381 def hgexecutable():
382 """return location of the 'hg' executable.
382 """return location of the 'hg' executable.
383
383
384 Defaults to $HG or 'hg' in the search path.
384 Defaults to $HG or 'hg' in the search path.
385 """
385 """
386 if _hgexecutable is None:
386 if _hgexecutable is None:
387 hg = encoding.environ.get(b'HG')
387 hg = encoding.environ.get(b'HG')
388 mainmod = sys.modules['__main__']
388 mainmod = sys.modules['__main__']
389 if hg:
389 if hg:
390 _sethgexecutable(hg)
390 _sethgexecutable(hg)
391 elif resourceutil.mainfrozen():
391 elif resourceutil.mainfrozen():
392 if getattr(sys, 'frozen', None) == 'macosx_app':
392 if getattr(sys, 'frozen', None) == 'macosx_app':
393 # Env variable set by py2app
393 # Env variable set by py2app
394 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
394 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
395 else:
395 else:
396 _sethgexecutable(pycompat.sysexecutable)
396 _sethgexecutable(pycompat.sysexecutable)
397 elif (
397 elif (
398 not pycompat.iswindows
398 not pycompat.iswindows
399 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
399 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
400 ):
400 ):
401 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
401 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
402 else:
402 else:
403 _sethgexecutable(
403 _sethgexecutable(
404 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
404 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
405 )
405 )
406 return _hgexecutable
406 return _hgexecutable
407
407
408
408
409 def _sethgexecutable(path):
409 def _sethgexecutable(path):
410 """set location of the 'hg' executable"""
410 """set location of the 'hg' executable"""
411 global _hgexecutable
411 global _hgexecutable
412 _hgexecutable = path
412 _hgexecutable = path
413
413
414
414
415 def _testfileno(f, stdf):
415 def _testfileno(f, stdf):
416 fileno = getattr(f, 'fileno', None)
416 fileno = getattr(f, 'fileno', None)
417 try:
417 try:
418 return fileno and fileno() == stdf.fileno()
418 return fileno and fileno() == stdf.fileno()
419 except io.UnsupportedOperation:
419 except io.UnsupportedOperation:
420 return False # fileno() raised UnsupportedOperation
420 return False # fileno() raised UnsupportedOperation
421
421
422
422
423 def isstdin(f):
423 def isstdin(f):
424 return _testfileno(f, sys.__stdin__)
424 return _testfileno(f, sys.__stdin__)
425
425
426
426
427 def isstdout(f):
427 def isstdout(f):
428 return _testfileno(f, sys.__stdout__)
428 return _testfileno(f, sys.__stdout__)
429
429
430
430
431 def protectstdio(uin, uout):
431 def protectstdio(uin, uout):
432 """Duplicate streams and redirect original if (uin, uout) are stdio
432 """Duplicate streams and redirect original if (uin, uout) are stdio
433
433
434 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
434 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
435 redirected to stderr so the output is still readable.
435 redirected to stderr so the output is still readable.
436
436
437 Returns (fin, fout) which point to the original (uin, uout) fds, but
437 Returns (fin, fout) which point to the original (uin, uout) fds, but
438 may be copy of (uin, uout). The returned streams can be considered
438 may be copy of (uin, uout). The returned streams can be considered
439 "owned" in that print(), exec(), etc. never reach to them.
439 "owned" in that print(), exec(), etc. never reach to them.
440 """
440 """
441 uout.flush()
441 uout.flush()
442 fin, fout = uin, uout
442 fin, fout = uin, uout
443 if _testfileno(uin, stdin):
443 if _testfileno(uin, stdin):
444 newfd = os.dup(uin.fileno())
444 newfd = os.dup(uin.fileno())
445 nullfd = os.open(os.devnull, os.O_RDONLY)
445 nullfd = os.open(os.devnull, os.O_RDONLY)
446 os.dup2(nullfd, uin.fileno())
446 os.dup2(nullfd, uin.fileno())
447 os.close(nullfd)
447 os.close(nullfd)
448 fin = os.fdopen(newfd, 'rb')
448 fin = os.fdopen(newfd, 'rb')
449 if _testfileno(uout, stdout):
449 if _testfileno(uout, stdout):
450 newfd = os.dup(uout.fileno())
450 newfd = os.dup(uout.fileno())
451 os.dup2(stderr.fileno(), uout.fileno())
451 os.dup2(stderr.fileno(), uout.fileno())
452 fout = os.fdopen(newfd, 'wb')
452 fout = os.fdopen(newfd, 'wb')
453 return fin, fout
453 return fin, fout
454
454
455
455
456 def restorestdio(uin, uout, fin, fout):
456 def restorestdio(uin, uout, fin, fout):
457 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
457 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
458 uout.flush()
458 uout.flush()
459 for f, uif in [(fin, uin), (fout, uout)]:
459 for f, uif in [(fin, uin), (fout, uout)]:
460 if f is not uif:
460 if f is not uif:
461 os.dup2(f.fileno(), uif.fileno())
461 os.dup2(f.fileno(), uif.fileno())
462 f.close()
462 f.close()
463
463
464
464
465 def shellenviron(environ=None):
465 def shellenviron(environ=None):
466 """return environ with optional override, useful for shelling out"""
466 """return environ with optional override, useful for shelling out"""
467
467
468 def py2shell(val):
468 def py2shell(val):
469 """convert python object into string that is useful to shell"""
469 """convert python object into string that is useful to shell"""
470 if val is None or val is False:
470 if val is None or val is False:
471 return b'0'
471 return b'0'
472 if val is True:
472 if val is True:
473 return b'1'
473 return b'1'
474 return pycompat.bytestr(val)
474 return pycompat.bytestr(val)
475
475
476 env = dict(encoding.environ)
476 env = dict(encoding.environ)
477 if environ:
477 if environ:
478 env.update((k, py2shell(v)) for k, v in environ.items())
478 env.update((k, py2shell(v)) for k, v in environ.items())
479 env[b'HG'] = hgexecutable()
479 env[b'HG'] = hgexecutable()
480 return env
480 return env
481
481
482
482
483 if pycompat.iswindows:
483 if pycompat.iswindows:
484
484
485 def shelltonative(cmd, env):
485 def shelltonative(cmd, env):
486 return platform.shelltocmdexe( # pytype: disable=module-attr
486 return platform.shelltocmdexe( # pytype: disable=module-attr
487 cmd, shellenviron(env)
487 cmd, shellenviron(env)
488 )
488 )
489
489
490 tonativestr = encoding.strfromlocal
490 tonativestr = encoding.strfromlocal
491 else:
491 else:
492
492
493 def shelltonative(cmd, env):
493 def shelltonative(cmd, env):
494 return cmd
494 return cmd
495
495
496 tonativestr = pycompat.identity
496 tonativestr = pycompat.identity
497
497
498
498
499 def tonativeenv(env):
499 def tonativeenv(env):
500 """convert the environment from bytes to strings suitable for Popen(), etc."""
500 """convert the environment from bytes to strings suitable for Popen(), etc."""
501 return pycompat.rapply(tonativestr, env)
501 return pycompat.rapply(tonativestr, env)
502
502
503
503
504 def system(cmd, environ=None, cwd=None, out=None):
504 def system(cmd, environ=None, cwd=None, out=None):
505 """enhanced shell command execution.
505 """enhanced shell command execution.
506 run with environment maybe modified, maybe in different dir.
506 run with environment maybe modified, maybe in different dir.
507
507
508 if out is specified, it is assumed to be a file-like object that has a
508 if out is specified, it is assumed to be a file-like object that has a
509 write() method. stdout and stderr will be redirected to out."""
509 write() method. stdout and stderr will be redirected to out."""
510 try:
510 try:
511 stdout.flush()
511 stdout.flush()
512 except Exception:
512 except Exception:
513 pass
513 pass
514 env = shellenviron(environ)
514 env = shellenviron(environ)
515 if out is None or isstdout(out):
515 if out is None or isstdout(out):
516 rc = subprocess.call(
516 rc = subprocess.call(
517 tonativestr(cmd),
517 tonativestr(cmd),
518 shell=True,
518 shell=True,
519 close_fds=closefds,
519 close_fds=closefds,
520 env=tonativeenv(env),
520 env=tonativeenv(env),
521 cwd=pycompat.rapply(tonativestr, cwd),
521 cwd=pycompat.rapply(tonativestr, cwd),
522 )
522 )
523 else:
523 else:
524 proc = subprocess.Popen(
524 proc = subprocess.Popen(
525 tonativestr(cmd),
525 tonativestr(cmd),
526 shell=True,
526 shell=True,
527 close_fds=closefds,
527 close_fds=closefds,
528 env=tonativeenv(env),
528 env=tonativeenv(env),
529 cwd=pycompat.rapply(tonativestr, cwd),
529 cwd=pycompat.rapply(tonativestr, cwd),
530 stdout=subprocess.PIPE,
530 stdout=subprocess.PIPE,
531 stderr=subprocess.STDOUT,
531 stderr=subprocess.STDOUT,
532 )
532 )
533 for line in iter(proc.stdout.readline, b''):
533 for line in iter(proc.stdout.readline, b''):
534 out.write(line)
534 out.write(line)
535 proc.wait()
535 proc.wait()
536 rc = proc.returncode
536 rc = proc.returncode
537 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
537 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
538 rc = 0
538 rc = 0
539 return rc
539 return rc
540
540
541
541
542 _is_gui = None
542 _is_gui = None
543
543
544
544
545 def _gui():
545 def _gui():
546 '''Are we running in a GUI?'''
546 '''Are we running in a GUI?'''
547 if pycompat.isdarwin:
547 if pycompat.isdarwin:
548 if b'SSH_CONNECTION' in encoding.environ:
548 if b'SSH_CONNECTION' in encoding.environ:
549 # handle SSH access to a box where the user is logged in
549 # handle SSH access to a box where the user is logged in
550 return False
550 return False
551 elif getattr(osutil, 'isgui', None):
551 elif getattr(osutil, 'isgui', None):
552 # check if a CoreGraphics session is available
552 # check if a CoreGraphics session is available
553 return osutil.isgui()
553 return osutil.isgui()
554 else:
554 else:
555 # pure build; use a safe default
555 # pure build; use a safe default
556 return True
556 return True
557 else:
557 else:
558 return (
558 return (
559 pycompat.iswindows
559 pycompat.iswindows
560 or encoding.environ.get(b"DISPLAY")
560 or encoding.environ.get(b"DISPLAY")
561 or encoding.environ.get(b"WAYLAND_DISPLAY")
561 or encoding.environ.get(b"WAYLAND_DISPLAY")
562 )
562 )
563
563
564
564
565 def gui():
565 def gui():
566 global _is_gui
566 global _is_gui
567 if _is_gui is None:
567 if _is_gui is None:
568 _is_gui = _gui()
568 _is_gui = _gui()
569 return _is_gui
569 return _is_gui
570
570
571
571
572 def hgcmd():
572 def hgcmd():
573 """Return the command used to execute current hg
573 """Return the command used to execute current hg
574
574
575 This is different from hgexecutable() because on Windows we want
575 This is different from hgexecutable() because on Windows we want
576 to avoid things opening new shell windows like batch files, so we
576 to avoid things opening new shell windows like batch files, so we
577 get either the python call or current executable.
577 get either the python call or current executable.
578 """
578 """
579 if resourceutil.mainfrozen():
579 if resourceutil.mainfrozen():
580 if getattr(sys, 'frozen', None) == 'macosx_app':
580 if getattr(sys, 'frozen', None) == 'macosx_app':
581 # Env variable set by py2app
581 # Env variable set by py2app
582 return [encoding.environ[b'EXECUTABLEPATH']]
582 return [encoding.environ[b'EXECUTABLEPATH']]
583 else:
583 else:
584 return [pycompat.sysexecutable]
584 return [pycompat.sysexecutable]
585 return _gethgcmd()
585 return _gethgcmd()
586
586
587
587
588 def rundetached(args, condfn):
588 def rundetached(args, condfn) -> int:
589 """Execute the argument list in a detached process.
589 """Execute the argument list in a detached process.
590
590
591 condfn is a callable which is called repeatedly and should return
591 condfn is a callable which is called repeatedly and should return
592 True once the child process is known to have started successfully.
592 True once the child process is known to have started successfully.
593 At this point, the child process PID is returned. If the child
593 At this point, the child process PID is returned. If the child
594 process fails to start or finishes before condfn() evaluates to
594 process fails to start or finishes before condfn() evaluates to
595 True, return -1.
595 True, return -1.
596 """
596 """
597 # Windows case is easier because the child process is either
597 # Windows case is easier because the child process is either
598 # successfully starting and validating the condition or exiting
598 # successfully starting and validating the condition or exiting
599 # on failure. We just poll on its PID. On Unix, if the child
599 # on failure. We just poll on its PID. On Unix, if the child
600 # process fails to start, it will be left in a zombie state until
600 # process fails to start, it will be left in a zombie state until
601 # the parent wait on it, which we cannot do since we expect a long
601 # the parent wait on it, which we cannot do since we expect a long
602 # running process on success. Instead we listen for SIGCHLD telling
602 # running process on success. Instead we listen for SIGCHLD telling
603 # us our child process terminated.
603 # us our child process terminated.
604 terminated = set()
604 terminated = set()
605
605
606 def handler(signum, frame):
606 def handler(signum, frame):
607 terminated.add(os.wait())
607 terminated.add(os.wait())
608
608
609 prevhandler = None
609 prevhandler = None
610 SIGCHLD = getattr(signal, 'SIGCHLD', None)
610 SIGCHLD = getattr(signal, 'SIGCHLD', None)
611 if SIGCHLD is not None:
611 if SIGCHLD is not None:
612 prevhandler = signal.signal(SIGCHLD, handler)
612 prevhandler = signal.signal(SIGCHLD, handler)
613 try:
613 try:
614 pid = spawndetached(args)
614 pid = spawndetached(args)
615 while not condfn():
615 while not condfn():
616 if (pid in terminated or not testpid(pid)) and not condfn():
616 if (pid in terminated or not testpid(pid)) and not condfn():
617 return -1
617 return -1
618 time.sleep(0.1)
618 time.sleep(0.1)
619 return pid
619 return pid
620 finally:
620 finally:
621 if prevhandler is not None:
621 if prevhandler is not None:
622 signal.signal(signal.SIGCHLD, prevhandler)
622 signal.signal(signal.SIGCHLD, prevhandler)
623
623
624 # pytype seems to get confused by not having a return in the finally
625 # block, and thinks the return value should be Optional[int] here. It
626 # appears to be https://github.com/google/pytype/issues/938, without
627 # the `with` clause.
628 pass # pytype: disable=bad-return-type
629
624
630
625 @contextlib.contextmanager
631 @contextlib.contextmanager
626 def uninterruptible(warn):
632 def uninterruptible(warn):
627 """Inhibit SIGINT handling on a region of code.
633 """Inhibit SIGINT handling on a region of code.
628
634
629 Note that if this is called in a non-main thread, it turns into a no-op.
635 Note that if this is called in a non-main thread, it turns into a no-op.
630
636
631 Args:
637 Args:
632 warn: A callable which takes no arguments, and returns True if the
638 warn: A callable which takes no arguments, and returns True if the
633 previous signal handling should be restored.
639 previous signal handling should be restored.
634 """
640 """
635
641
636 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
642 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
637 shouldbail = []
643 shouldbail = []
638
644
639 def disabledsiginthandler(*args):
645 def disabledsiginthandler(*args):
640 if warn():
646 if warn():
641 signal.signal(signal.SIGINT, oldsiginthandler[0])
647 signal.signal(signal.SIGINT, oldsiginthandler[0])
642 del oldsiginthandler[0]
648 del oldsiginthandler[0]
643 shouldbail.append(True)
649 shouldbail.append(True)
644
650
645 try:
651 try:
646 try:
652 try:
647 signal.signal(signal.SIGINT, disabledsiginthandler)
653 signal.signal(signal.SIGINT, disabledsiginthandler)
648 except ValueError:
654 except ValueError:
649 # wrong thread, oh well, we tried
655 # wrong thread, oh well, we tried
650 del oldsiginthandler[0]
656 del oldsiginthandler[0]
651 yield
657 yield
652 finally:
658 finally:
653 if oldsiginthandler:
659 if oldsiginthandler:
654 signal.signal(signal.SIGINT, oldsiginthandler[0])
660 signal.signal(signal.SIGINT, oldsiginthandler[0])
655 if shouldbail:
661 if shouldbail:
656 raise KeyboardInterrupt
662 raise KeyboardInterrupt
657
663
658
664
659 if pycompat.iswindows:
665 if pycompat.iswindows:
660 # no fork on Windows, but we can create a detached process
666 # no fork on Windows, but we can create a detached process
661 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
667 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
662 # No stdlib constant exists for this value
668 # No stdlib constant exists for this value
663 DETACHED_PROCESS = 0x00000008
669 DETACHED_PROCESS = 0x00000008
664 # Following creation flags might create a console GUI window.
670 # Following creation flags might create a console GUI window.
665 # Using subprocess.CREATE_NEW_CONSOLE might helps.
671 # Using subprocess.CREATE_NEW_CONSOLE might helps.
666 # See https://phab.mercurial-scm.org/D1701 for discussion
672 # See https://phab.mercurial-scm.org/D1701 for discussion
667 _creationflags = (
673 _creationflags = (
668 DETACHED_PROCESS
674 DETACHED_PROCESS
669 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
675 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
670 )
676 )
671
677
672 def runbgcommand(
678 def runbgcommand(
673 script,
679 script,
674 env,
680 env,
675 shell=False,
681 shell=False,
676 stdout=None,
682 stdout=None,
677 stderr=None,
683 stderr=None,
678 ensurestart=True,
684 ensurestart=True,
679 record_wait=None,
685 record_wait=None,
680 stdin_bytes=None,
686 stdin_bytes=None,
681 ):
687 ):
682 '''Spawn a command without waiting for it to finish.'''
688 '''Spawn a command without waiting for it to finish.'''
683 # we can't use close_fds *and* redirect stdin. I'm not sure that we
689 # we can't use close_fds *and* redirect stdin. I'm not sure that we
684 # need to because the detached process has no console connection.
690 # need to because the detached process has no console connection.
685
691
686 try:
692 try:
687 stdin = None
693 stdin = None
688 if stdin_bytes is not None:
694 if stdin_bytes is not None:
689 stdin = pycompat.unnamedtempfile()
695 stdin = pycompat.unnamedtempfile()
690 stdin.write(stdin_bytes)
696 stdin.write(stdin_bytes)
691 stdin.flush()
697 stdin.flush()
692 stdin.seek(0)
698 stdin.seek(0)
693
699
694 p = subprocess.Popen(
700 p = subprocess.Popen(
695 pycompat.rapply(tonativestr, script),
701 pycompat.rapply(tonativestr, script),
696 shell=shell,
702 shell=shell,
697 env=tonativeenv(env),
703 env=tonativeenv(env),
698 close_fds=True,
704 close_fds=True,
699 creationflags=_creationflags,
705 creationflags=_creationflags,
700 stdin=stdin,
706 stdin=stdin,
701 stdout=stdout,
707 stdout=stdout,
702 stderr=stderr,
708 stderr=stderr,
703 )
709 )
704 if record_wait is not None:
710 if record_wait is not None:
705 record_wait(p.wait)
711 record_wait(p.wait)
706 finally:
712 finally:
707 if stdin is not None:
713 if stdin is not None:
708 stdin.close()
714 stdin.close()
709
715
710
716
711 else:
717 else:
712
718
713 def runbgcommand(
719 def runbgcommand(
714 cmd,
720 cmd,
715 env,
721 env,
716 shell=False,
722 shell=False,
717 stdout=None,
723 stdout=None,
718 stderr=None,
724 stderr=None,
719 ensurestart=True,
725 ensurestart=True,
720 record_wait=None,
726 record_wait=None,
721 stdin_bytes=None,
727 stdin_bytes=None,
722 ):
728 ):
723 """Spawn a command without waiting for it to finish.
729 """Spawn a command without waiting for it to finish.
724
730
725
731
726 When `record_wait` is not None, the spawned process will not be fully
732 When `record_wait` is not None, the spawned process will not be fully
727 detached and the `record_wait` argument will be called with a the
733 detached and the `record_wait` argument will be called with a the
728 `Subprocess.wait` function for the spawned process. This is mostly
734 `Subprocess.wait` function for the spawned process. This is mostly
729 useful for developers that need to make sure the spawned process
735 useful for developers that need to make sure the spawned process
730 finished before a certain point. (eg: writing test)"""
736 finished before a certain point. (eg: writing test)"""
731 if pycompat.isdarwin:
737 if pycompat.isdarwin:
732 # avoid crash in CoreFoundation in case another thread
738 # avoid crash in CoreFoundation in case another thread
733 # calls gui() while we're calling fork().
739 # calls gui() while we're calling fork().
734 gui()
740 gui()
735
741
736 if shell:
742 if shell:
737 script = cmd
743 script = cmd
738 else:
744 else:
739 if isinstance(cmd, bytes):
745 if isinstance(cmd, bytes):
740 cmd = [cmd]
746 cmd = [cmd]
741 script = b' '.join(shellquote(x) for x in cmd)
747 script = b' '.join(shellquote(x) for x in cmd)
742 if record_wait is None:
748 if record_wait is None:
743 # double-fork to completely detach from the parent process
749 # double-fork to completely detach from the parent process
744 script = b'( %s ) &' % script
750 script = b'( %s ) &' % script
745 start_new_session = True
751 start_new_session = True
746 else:
752 else:
747 start_new_session = False
753 start_new_session = False
748 ensurestart = True
754 ensurestart = True
749
755
750 stdin = None
756 stdin = None
751
757
752 try:
758 try:
753 if stdin_bytes is None:
759 if stdin_bytes is None:
754 stdin = subprocess.DEVNULL
760 stdin = subprocess.DEVNULL
755 else:
761 else:
756 stdin = pycompat.unnamedtempfile()
762 stdin = pycompat.unnamedtempfile()
757 stdin.write(stdin_bytes)
763 stdin.write(stdin_bytes)
758 stdin.flush()
764 stdin.flush()
759 stdin.seek(0)
765 stdin.seek(0)
760 if stdout is None:
766 if stdout is None:
761 stdout = subprocess.DEVNULL
767 stdout = subprocess.DEVNULL
762 if stderr is None:
768 if stderr is None:
763 stderr = subprocess.DEVNULL
769 stderr = subprocess.DEVNULL
764
770
765 p = subprocess.Popen(
771 p = subprocess.Popen(
766 script,
772 script,
767 shell=True,
773 shell=True,
768 env=env,
774 env=env,
769 close_fds=True,
775 close_fds=True,
770 stdin=stdin,
776 stdin=stdin,
771 stdout=stdout,
777 stdout=stdout,
772 stderr=stderr,
778 stderr=stderr,
773 start_new_session=start_new_session,
779 start_new_session=start_new_session,
774 )
780 )
775 except Exception:
781 except Exception:
776 if record_wait is not None:
782 if record_wait is not None:
777 record_wait(255)
783 record_wait(255)
778 raise
784 raise
779 finally:
785 finally:
780 if stdin_bytes is not None and stdin is not None:
786 if stdin_bytes is not None and stdin is not None:
781 assert not isinstance(stdin, int)
787 assert not isinstance(stdin, int)
782 stdin.close()
788 stdin.close()
783 if not ensurestart:
789 if not ensurestart:
784 # Even though we're not waiting on the child process,
790 # Even though we're not waiting on the child process,
785 # we still must call waitpid() on it at some point so
791 # we still must call waitpid() on it at some point so
786 # it's not a zombie/defunct. This is especially relevant for
792 # it's not a zombie/defunct. This is especially relevant for
787 # chg since the parent process won't die anytime soon.
793 # chg since the parent process won't die anytime soon.
788 # We use a thread to make the overhead tiny.
794 # We use a thread to make the overhead tiny.
789 t = threading.Thread(target=lambda: p.wait)
795 t = threading.Thread(target=lambda: p.wait)
790 t.daemon = True
796 t.daemon = True
791 t.start()
797 t.start()
792 else:
798 else:
793 returncode = p.wait
799 returncode = p.wait
794 if record_wait is not None:
800 if record_wait is not None:
795 record_wait(returncode)
801 record_wait(returncode)
@@ -1,764 +1,771 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 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2009 Olivia Mackall <olivia@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
8
9 import ctypes
9 import ctypes
10 import ctypes.wintypes as wintypes
10 import ctypes.wintypes as wintypes
11 import errno
11 import errno
12 import msvcrt
12 import msvcrt
13 import os
13 import os
14 import random
14 import random
15 import subprocess
15 import subprocess
16
16
17 from typing import (
18 List,
19 NoReturn,
20 Optional,
21 Tuple,
22 )
23
17 from . import (
24 from . import (
18 encoding,
25 encoding,
19 pycompat,
26 pycompat,
20 )
27 )
21
28
22 # pytype: disable=module-attr
29 # pytype: disable=module-attr
23 _kernel32 = ctypes.windll.kernel32
30 _kernel32 = ctypes.windll.kernel32
24 _advapi32 = ctypes.windll.advapi32
31 _advapi32 = ctypes.windll.advapi32
25 _user32 = ctypes.windll.user32
32 _user32 = ctypes.windll.user32
26 _crypt32 = ctypes.windll.crypt32
33 _crypt32 = ctypes.windll.crypt32
27 # pytype: enable=module-attr
34 # pytype: enable=module-attr
28
35
29 _BOOL = ctypes.c_long
36 _BOOL = ctypes.c_long
30 _WORD = ctypes.c_ushort
37 _WORD = ctypes.c_ushort
31 _DWORD = ctypes.c_ulong
38 _DWORD = ctypes.c_ulong
32 _UINT = ctypes.c_uint
39 _UINT = ctypes.c_uint
33 _LONG = ctypes.c_long
40 _LONG = ctypes.c_long
34 _LPCSTR = _LPSTR = ctypes.c_char_p
41 _LPCSTR = _LPSTR = ctypes.c_char_p
35 _HANDLE = ctypes.c_void_p
42 _HANDLE = ctypes.c_void_p
36 _HWND = _HANDLE
43 _HWND = _HANDLE
37 _PCCERT_CONTEXT = ctypes.c_void_p
44 _PCCERT_CONTEXT = ctypes.c_void_p
38 _MAX_PATH = wintypes.MAX_PATH
45 _MAX_PATH = wintypes.MAX_PATH
39
46
40 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
47 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
41
48
42 # GetLastError
49 # GetLastError
43 _ERROR_SUCCESS = 0
50 _ERROR_SUCCESS = 0
44 _ERROR_NO_MORE_FILES = 18
51 _ERROR_NO_MORE_FILES = 18
45 _ERROR_INVALID_PARAMETER = 87
52 _ERROR_INVALID_PARAMETER = 87
46 _ERROR_BROKEN_PIPE = 109
53 _ERROR_BROKEN_PIPE = 109
47 _ERROR_INSUFFICIENT_BUFFER = 122
54 _ERROR_INSUFFICIENT_BUFFER = 122
48 _ERROR_NO_DATA = 232
55 _ERROR_NO_DATA = 232
49
56
50 # WPARAM is defined as UINT_PTR (unsigned type)
57 # WPARAM is defined as UINT_PTR (unsigned type)
51 # LPARAM is defined as LONG_PTR (signed type)
58 # LPARAM is defined as LONG_PTR (signed type)
52 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
59 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
53 _WPARAM = ctypes.c_ulong
60 _WPARAM = ctypes.c_ulong
54 _LPARAM = ctypes.c_long
61 _LPARAM = ctypes.c_long
55 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
62 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
56 _WPARAM = ctypes.c_ulonglong
63 _WPARAM = ctypes.c_ulonglong
57 _LPARAM = ctypes.c_longlong
64 _LPARAM = ctypes.c_longlong
58
65
59
66
60 class _FILETIME(ctypes.Structure):
67 class _FILETIME(ctypes.Structure):
61 _fields_ = [('dwLowDateTime', _DWORD), ('dwHighDateTime', _DWORD)]
68 _fields_ = [('dwLowDateTime', _DWORD), ('dwHighDateTime', _DWORD)]
62
69
63
70
64 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
71 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
65 _fields_ = [
72 _fields_ = [
66 ('dwFileAttributes', _DWORD),
73 ('dwFileAttributes', _DWORD),
67 ('ftCreationTime', _FILETIME),
74 ('ftCreationTime', _FILETIME),
68 ('ftLastAccessTime', _FILETIME),
75 ('ftLastAccessTime', _FILETIME),
69 ('ftLastWriteTime', _FILETIME),
76 ('ftLastWriteTime', _FILETIME),
70 ('dwVolumeSerialNumber', _DWORD),
77 ('dwVolumeSerialNumber', _DWORD),
71 ('nFileSizeHigh', _DWORD),
78 ('nFileSizeHigh', _DWORD),
72 ('nFileSizeLow', _DWORD),
79 ('nFileSizeLow', _DWORD),
73 ('nNumberOfLinks', _DWORD),
80 ('nNumberOfLinks', _DWORD),
74 ('nFileIndexHigh', _DWORD),
81 ('nFileIndexHigh', _DWORD),
75 ('nFileIndexLow', _DWORD),
82 ('nFileIndexLow', _DWORD),
76 ]
83 ]
77
84
78
85
79 # CreateFile
86 # CreateFile
80 _FILE_SHARE_READ = 0x00000001
87 _FILE_SHARE_READ = 0x00000001
81 _FILE_SHARE_WRITE = 0x00000002
88 _FILE_SHARE_WRITE = 0x00000002
82 _FILE_SHARE_DELETE = 0x00000004
89 _FILE_SHARE_DELETE = 0x00000004
83
90
84 _OPEN_EXISTING = 3
91 _OPEN_EXISTING = 3
85
92
86 _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
93 _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
87
94
88 # SetFileAttributes
95 # SetFileAttributes
89 _FILE_ATTRIBUTE_NORMAL = 0x80
96 _FILE_ATTRIBUTE_NORMAL = 0x80
90 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
97 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
91
98
92 # Process Security and Access Rights
99 # Process Security and Access Rights
93 _PROCESS_QUERY_INFORMATION = 0x0400
100 _PROCESS_QUERY_INFORMATION = 0x0400
94
101
95 # GetExitCodeProcess
102 # GetExitCodeProcess
96 _STILL_ACTIVE = 259
103 _STILL_ACTIVE = 259
97
104
98
105
99 class _STARTUPINFO(ctypes.Structure):
106 class _STARTUPINFO(ctypes.Structure):
100 _fields_ = [
107 _fields_ = [
101 ('cb', _DWORD),
108 ('cb', _DWORD),
102 ('lpReserved', _LPSTR),
109 ('lpReserved', _LPSTR),
103 ('lpDesktop', _LPSTR),
110 ('lpDesktop', _LPSTR),
104 ('lpTitle', _LPSTR),
111 ('lpTitle', _LPSTR),
105 ('dwX', _DWORD),
112 ('dwX', _DWORD),
106 ('dwY', _DWORD),
113 ('dwY', _DWORD),
107 ('dwXSize', _DWORD),
114 ('dwXSize', _DWORD),
108 ('dwYSize', _DWORD),
115 ('dwYSize', _DWORD),
109 ('dwXCountChars', _DWORD),
116 ('dwXCountChars', _DWORD),
110 ('dwYCountChars', _DWORD),
117 ('dwYCountChars', _DWORD),
111 ('dwFillAttribute', _DWORD),
118 ('dwFillAttribute', _DWORD),
112 ('dwFlags', _DWORD),
119 ('dwFlags', _DWORD),
113 ('wShowWindow', _WORD),
120 ('wShowWindow', _WORD),
114 ('cbReserved2', _WORD),
121 ('cbReserved2', _WORD),
115 ('lpReserved2', ctypes.c_char_p),
122 ('lpReserved2', ctypes.c_char_p),
116 ('hStdInput', _HANDLE),
123 ('hStdInput', _HANDLE),
117 ('hStdOutput', _HANDLE),
124 ('hStdOutput', _HANDLE),
118 ('hStdError', _HANDLE),
125 ('hStdError', _HANDLE),
119 ]
126 ]
120
127
121
128
122 class _PROCESS_INFORMATION(ctypes.Structure):
129 class _PROCESS_INFORMATION(ctypes.Structure):
123 _fields_ = [
130 _fields_ = [
124 ('hProcess', _HANDLE),
131 ('hProcess', _HANDLE),
125 ('hThread', _HANDLE),
132 ('hThread', _HANDLE),
126 ('dwProcessId', _DWORD),
133 ('dwProcessId', _DWORD),
127 ('dwThreadId', _DWORD),
134 ('dwThreadId', _DWORD),
128 ]
135 ]
129
136
130
137
131 _CREATE_NO_WINDOW = 0x08000000
138 _CREATE_NO_WINDOW = 0x08000000
132 _SW_HIDE = 0
139 _SW_HIDE = 0
133
140
134
141
135 class _COORD(ctypes.Structure):
142 class _COORD(ctypes.Structure):
136 _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)]
143 _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)]
137
144
138
145
139 class _SMALL_RECT(ctypes.Structure):
146 class _SMALL_RECT(ctypes.Structure):
140 _fields_ = [
147 _fields_ = [
141 ('Left', ctypes.c_short),
148 ('Left', ctypes.c_short),
142 ('Top', ctypes.c_short),
149 ('Top', ctypes.c_short),
143 ('Right', ctypes.c_short),
150 ('Right', ctypes.c_short),
144 ('Bottom', ctypes.c_short),
151 ('Bottom', ctypes.c_short),
145 ]
152 ]
146
153
147
154
148 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
155 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
149 _fields_ = [
156 _fields_ = [
150 ('dwSize', _COORD),
157 ('dwSize', _COORD),
151 ('dwCursorPosition', _COORD),
158 ('dwCursorPosition', _COORD),
152 ('wAttributes', _WORD),
159 ('wAttributes', _WORD),
153 ('srWindow', _SMALL_RECT),
160 ('srWindow', _SMALL_RECT),
154 ('dwMaximumWindowSize', _COORD),
161 ('dwMaximumWindowSize', _COORD),
155 ]
162 ]
156
163
157
164
158 _STD_OUTPUT_HANDLE = _DWORD(-11).value
165 _STD_OUTPUT_HANDLE = _DWORD(-11).value
159 _STD_ERROR_HANDLE = _DWORD(-12).value
166 _STD_ERROR_HANDLE = _DWORD(-12).value
160
167
161 # CERT_TRUST_STATUS dwErrorStatus
168 # CERT_TRUST_STATUS dwErrorStatus
162 CERT_TRUST_IS_PARTIAL_CHAIN = 0x10000
169 CERT_TRUST_IS_PARTIAL_CHAIN = 0x10000
163
170
164 # CertCreateCertificateContext encodings
171 # CertCreateCertificateContext encodings
165 X509_ASN_ENCODING = 0x00000001
172 X509_ASN_ENCODING = 0x00000001
166 PKCS_7_ASN_ENCODING = 0x00010000
173 PKCS_7_ASN_ENCODING = 0x00010000
167
174
168 # These structs are only complete enough to achieve what we need.
175 # These structs are only complete enough to achieve what we need.
169 class CERT_CHAIN_CONTEXT(ctypes.Structure):
176 class CERT_CHAIN_CONTEXT(ctypes.Structure):
170 _fields_ = (
177 _fields_ = (
171 ("cbSize", _DWORD),
178 ("cbSize", _DWORD),
172 # CERT_TRUST_STATUS struct
179 # CERT_TRUST_STATUS struct
173 ("dwErrorStatus", _DWORD),
180 ("dwErrorStatus", _DWORD),
174 ("dwInfoStatus", _DWORD),
181 ("dwInfoStatus", _DWORD),
175 ("cChain", _DWORD),
182 ("cChain", _DWORD),
176 ("rgpChain", ctypes.c_void_p),
183 ("rgpChain", ctypes.c_void_p),
177 ("cLowerQualityChainContext", _DWORD),
184 ("cLowerQualityChainContext", _DWORD),
178 ("rgpLowerQualityChainContext", ctypes.c_void_p),
185 ("rgpLowerQualityChainContext", ctypes.c_void_p),
179 ("fHasRevocationFreshnessTime", _BOOL),
186 ("fHasRevocationFreshnessTime", _BOOL),
180 ("dwRevocationFreshnessTime", _DWORD),
187 ("dwRevocationFreshnessTime", _DWORD),
181 )
188 )
182
189
183
190
184 class CERT_USAGE_MATCH(ctypes.Structure):
191 class CERT_USAGE_MATCH(ctypes.Structure):
185 _fields_ = (
192 _fields_ = (
186 ("dwType", _DWORD),
193 ("dwType", _DWORD),
187 # CERT_ENHKEY_USAGE struct
194 # CERT_ENHKEY_USAGE struct
188 ("cUsageIdentifier", _DWORD),
195 ("cUsageIdentifier", _DWORD),
189 ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
196 ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
190 )
197 )
191
198
192
199
193 class CERT_CHAIN_PARA(ctypes.Structure):
200 class CERT_CHAIN_PARA(ctypes.Structure):
194 _fields_ = (
201 _fields_ = (
195 ("cbSize", _DWORD),
202 ("cbSize", _DWORD),
196 ("RequestedUsage", CERT_USAGE_MATCH),
203 ("RequestedUsage", CERT_USAGE_MATCH),
197 ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
204 ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
198 ("dwUrlRetrievalTimeout", _DWORD),
205 ("dwUrlRetrievalTimeout", _DWORD),
199 ("fCheckRevocationFreshnessTime", _BOOL),
206 ("fCheckRevocationFreshnessTime", _BOOL),
200 ("dwRevocationFreshnessTime", _DWORD),
207 ("dwRevocationFreshnessTime", _DWORD),
201 ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
208 ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
202 ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
209 ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
203 ("dwStrongSignFlags", _DWORD),
210 ("dwStrongSignFlags", _DWORD),
204 )
211 )
205
212
206
213
207 # types of parameters of C functions used (required by pypy)
214 # types of parameters of C functions used (required by pypy)
208
215
209 _crypt32.CertCreateCertificateContext.argtypes = [
216 _crypt32.CertCreateCertificateContext.argtypes = [
210 _DWORD, # cert encoding
217 _DWORD, # cert encoding
211 ctypes.c_char_p, # cert
218 ctypes.c_char_p, # cert
212 _DWORD,
219 _DWORD,
213 ] # cert size
220 ] # cert size
214 _crypt32.CertCreateCertificateContext.restype = _PCCERT_CONTEXT
221 _crypt32.CertCreateCertificateContext.restype = _PCCERT_CONTEXT
215
222
216 _crypt32.CertGetCertificateChain.argtypes = [
223 _crypt32.CertGetCertificateChain.argtypes = [
217 ctypes.c_void_p, # HCERTCHAINENGINE
224 ctypes.c_void_p, # HCERTCHAINENGINE
218 _PCCERT_CONTEXT,
225 _PCCERT_CONTEXT,
219 ctypes.c_void_p, # LPFILETIME
226 ctypes.c_void_p, # LPFILETIME
220 ctypes.c_void_p, # HCERTSTORE
227 ctypes.c_void_p, # HCERTSTORE
221 ctypes.c_void_p, # PCERT_CHAIN_PARA
228 ctypes.c_void_p, # PCERT_CHAIN_PARA
222 _DWORD,
229 _DWORD,
223 ctypes.c_void_p, # LPVOID
230 ctypes.c_void_p, # LPVOID
224 ctypes.c_void_p, # PCCERT_CHAIN_CONTEXT *
231 ctypes.c_void_p, # PCCERT_CHAIN_CONTEXT *
225 ]
232 ]
226 _crypt32.CertGetCertificateChain.restype = _BOOL
233 _crypt32.CertGetCertificateChain.restype = _BOOL
227
234
228 _crypt32.CertFreeCertificateContext.argtypes = [_PCCERT_CONTEXT]
235 _crypt32.CertFreeCertificateContext.argtypes = [_PCCERT_CONTEXT]
229 _crypt32.CertFreeCertificateContext.restype = _BOOL
236 _crypt32.CertFreeCertificateContext.restype = _BOOL
230
237
231 _kernel32.CreateFileA.argtypes = [
238 _kernel32.CreateFileA.argtypes = [
232 _LPCSTR,
239 _LPCSTR,
233 _DWORD,
240 _DWORD,
234 _DWORD,
241 _DWORD,
235 ctypes.c_void_p,
242 ctypes.c_void_p,
236 _DWORD,
243 _DWORD,
237 _DWORD,
244 _DWORD,
238 _HANDLE,
245 _HANDLE,
239 ]
246 ]
240 _kernel32.CreateFileA.restype = _HANDLE
247 _kernel32.CreateFileA.restype = _HANDLE
241
248
242 _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p]
249 _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p]
243 _kernel32.GetFileInformationByHandle.restype = _BOOL
250 _kernel32.GetFileInformationByHandle.restype = _BOOL
244
251
245 _kernel32.CloseHandle.argtypes = [_HANDLE]
252 _kernel32.CloseHandle.argtypes = [_HANDLE]
246 _kernel32.CloseHandle.restype = _BOOL
253 _kernel32.CloseHandle.restype = _BOOL
247
254
248 try:
255 try:
249 _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p]
256 _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p]
250 _kernel32.CreateHardLinkA.restype = _BOOL
257 _kernel32.CreateHardLinkA.restype = _BOOL
251 except AttributeError:
258 except AttributeError:
252 pass
259 pass
253
260
254 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
261 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
255 _kernel32.SetFileAttributesA.restype = _BOOL
262 _kernel32.SetFileAttributesA.restype = _BOOL
256
263
257 _DRIVE_UNKNOWN = 0
264 _DRIVE_UNKNOWN = 0
258 _DRIVE_NO_ROOT_DIR = 1
265 _DRIVE_NO_ROOT_DIR = 1
259 _DRIVE_REMOVABLE = 2
266 _DRIVE_REMOVABLE = 2
260 _DRIVE_FIXED = 3
267 _DRIVE_FIXED = 3
261 _DRIVE_REMOTE = 4
268 _DRIVE_REMOTE = 4
262 _DRIVE_CDROM = 5
269 _DRIVE_CDROM = 5
263 _DRIVE_RAMDISK = 6
270 _DRIVE_RAMDISK = 6
264
271
265 _kernel32.GetDriveTypeA.argtypes = [_LPCSTR]
272 _kernel32.GetDriveTypeA.argtypes = [_LPCSTR]
266 _kernel32.GetDriveTypeA.restype = _UINT
273 _kernel32.GetDriveTypeA.restype = _UINT
267
274
268 _kernel32.GetVolumeInformationA.argtypes = [
275 _kernel32.GetVolumeInformationA.argtypes = [
269 _LPCSTR,
276 _LPCSTR,
270 ctypes.c_void_p,
277 ctypes.c_void_p,
271 _DWORD,
278 _DWORD,
272 ctypes.c_void_p,
279 ctypes.c_void_p,
273 ctypes.c_void_p,
280 ctypes.c_void_p,
274 ctypes.c_void_p,
281 ctypes.c_void_p,
275 ctypes.c_void_p,
282 ctypes.c_void_p,
276 _DWORD,
283 _DWORD,
277 ]
284 ]
278 _kernel32.GetVolumeInformationA.restype = _BOOL
285 _kernel32.GetVolumeInformationA.restype = _BOOL
279
286
280 _kernel32.GetVolumePathNameA.argtypes = [_LPCSTR, ctypes.c_void_p, _DWORD]
287 _kernel32.GetVolumePathNameA.argtypes = [_LPCSTR, ctypes.c_void_p, _DWORD]
281 _kernel32.GetVolumePathNameA.restype = _BOOL
288 _kernel32.GetVolumePathNameA.restype = _BOOL
282
289
283 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
290 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
284 _kernel32.OpenProcess.restype = _HANDLE
291 _kernel32.OpenProcess.restype = _HANDLE
285
292
286 _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p]
293 _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p]
287 _kernel32.GetExitCodeProcess.restype = _BOOL
294 _kernel32.GetExitCodeProcess.restype = _BOOL
288
295
289 _kernel32.GetLastError.argtypes = []
296 _kernel32.GetLastError.argtypes = []
290 _kernel32.GetLastError.restype = _DWORD
297 _kernel32.GetLastError.restype = _DWORD
291
298
292 _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD]
299 _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD]
293 _kernel32.GetModuleFileNameA.restype = _DWORD
300 _kernel32.GetModuleFileNameA.restype = _DWORD
294
301
295 _kernel32.CreateProcessA.argtypes = [
302 _kernel32.CreateProcessA.argtypes = [
296 _LPCSTR,
303 _LPCSTR,
297 _LPCSTR,
304 _LPCSTR,
298 ctypes.c_void_p,
305 ctypes.c_void_p,
299 ctypes.c_void_p,
306 ctypes.c_void_p,
300 _BOOL,
307 _BOOL,
301 _DWORD,
308 _DWORD,
302 ctypes.c_void_p,
309 ctypes.c_void_p,
303 _LPCSTR,
310 _LPCSTR,
304 ctypes.c_void_p,
311 ctypes.c_void_p,
305 ctypes.c_void_p,
312 ctypes.c_void_p,
306 ]
313 ]
307 _kernel32.CreateProcessA.restype = _BOOL
314 _kernel32.CreateProcessA.restype = _BOOL
308
315
309 _kernel32.ExitProcess.argtypes = [_UINT]
316 _kernel32.ExitProcess.argtypes = [_UINT]
310 _kernel32.ExitProcess.restype = None
317 _kernel32.ExitProcess.restype = None
311
318
312 _kernel32.GetCurrentProcessId.argtypes = []
319 _kernel32.GetCurrentProcessId.argtypes = []
313 _kernel32.GetCurrentProcessId.restype = _DWORD
320 _kernel32.GetCurrentProcessId.restype = _DWORD
314
321
315 # pytype: disable=module-attr
322 # pytype: disable=module-attr
316 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
323 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
317 # pytype: enable=module-attr
324 # pytype: enable=module-attr
318 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
325 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
319 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
326 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
320
327
321 _kernel32.SetConsoleMode.argtypes = [_HANDLE, _DWORD]
328 _kernel32.SetConsoleMode.argtypes = [_HANDLE, _DWORD]
322 _kernel32.SetConsoleMode.restype = _BOOL
329 _kernel32.SetConsoleMode.restype = _BOOL
323
330
324 _kernel32.GetConsoleMode.argtypes = [_HANDLE, ctypes.c_void_p]
331 _kernel32.GetConsoleMode.argtypes = [_HANDLE, ctypes.c_void_p]
325 _kernel32.GetConsoleMode.restype = _BOOL
332 _kernel32.GetConsoleMode.restype = _BOOL
326
333
327 _kernel32.GetStdHandle.argtypes = [_DWORD]
334 _kernel32.GetStdHandle.argtypes = [_DWORD]
328 _kernel32.GetStdHandle.restype = _HANDLE
335 _kernel32.GetStdHandle.restype = _HANDLE
329
336
330 _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p]
337 _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p]
331 _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL
338 _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL
332
339
333 _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
340 _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
334 _advapi32.GetUserNameA.restype = _BOOL
341 _advapi32.GetUserNameA.restype = _BOOL
335
342
336 _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p]
343 _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p]
337 _user32.GetWindowThreadProcessId.restype = _DWORD
344 _user32.GetWindowThreadProcessId.restype = _DWORD
338
345
339 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
346 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
340 _user32.ShowWindow.restype = _BOOL
347 _user32.ShowWindow.restype = _BOOL
341
348
342 # pytype: disable=module-attr
349 # pytype: disable=module-attr
343 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
350 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
344 # pytype: enable=module-attr
351 # pytype: enable=module-attr
345 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
352 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
346 _user32.EnumWindows.restype = _BOOL
353 _user32.EnumWindows.restype = _BOOL
347
354
348 _kernel32.PeekNamedPipe.argtypes = [
355 _kernel32.PeekNamedPipe.argtypes = [
349 _HANDLE,
356 _HANDLE,
350 ctypes.c_void_p,
357 ctypes.c_void_p,
351 _DWORD,
358 _DWORD,
352 ctypes.c_void_p,
359 ctypes.c_void_p,
353 ctypes.c_void_p,
360 ctypes.c_void_p,
354 ctypes.c_void_p,
361 ctypes.c_void_p,
355 ]
362 ]
356 _kernel32.PeekNamedPipe.restype = _BOOL
363 _kernel32.PeekNamedPipe.restype = _BOOL
357
364
358
365
359 def _raiseoserror(name):
366 def _raiseoserror(name: bytes) -> NoReturn:
360 # Force the code to a signed int to avoid an 'int too large' error.
367 # Force the code to a signed int to avoid an 'int too large' error.
361 # See https://bugs.python.org/issue28474
368 # See https://bugs.python.org/issue28474
362 code = _kernel32.GetLastError()
369 code = _kernel32.GetLastError()
363 if code > 0x7FFFFFFF:
370 if code > 0x7FFFFFFF:
364 code -= 2 ** 32
371 code -= 2 ** 32
365 err = ctypes.WinError(code=code) # pytype: disable=module-attr
372 err = ctypes.WinError(code=code) # pytype: disable=module-attr
366 raise OSError(
373 raise OSError(
367 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
374 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
368 )
375 )
369
376
370
377
371 def _getfileinfo(name):
378 def _getfileinfo(name: bytes) -> _BY_HANDLE_FILE_INFORMATION:
372 fh = _kernel32.CreateFileA(
379 fh = _kernel32.CreateFileA(
373 name,
380 name,
374 0,
381 0,
375 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
382 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
376 None,
383 None,
377 _OPEN_EXISTING,
384 _OPEN_EXISTING,
378 _FILE_FLAG_BACKUP_SEMANTICS,
385 _FILE_FLAG_BACKUP_SEMANTICS,
379 None,
386 None,
380 )
387 )
381 if fh == _INVALID_HANDLE_VALUE:
388 if fh == _INVALID_HANDLE_VALUE:
382 _raiseoserror(name)
389 _raiseoserror(name)
383 try:
390 try:
384 fi = _BY_HANDLE_FILE_INFORMATION()
391 fi = _BY_HANDLE_FILE_INFORMATION()
385 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
392 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
386 _raiseoserror(name)
393 _raiseoserror(name)
387 return fi
394 return fi
388 finally:
395 finally:
389 _kernel32.CloseHandle(fh)
396 _kernel32.CloseHandle(fh)
390
397
391
398
392 def checkcertificatechain(cert, build=True):
399 def checkcertificatechain(cert: bytes, build: bool = True) -> bool:
393 """Tests the given certificate to see if there is a complete chain to a
400 """Tests the given certificate to see if there is a complete chain to a
394 trusted root certificate. As a side effect, missing certificates are
401 trusted root certificate. As a side effect, missing certificates are
395 downloaded and installed unless ``build=False``. True is returned if a
402 downloaded and installed unless ``build=False``. True is returned if a
396 chain to a trusted root exists (even if built on the fly), otherwise
403 chain to a trusted root exists (even if built on the fly), otherwise
397 False. NB: A chain to a trusted root does NOT imply that the certificate
404 False. NB: A chain to a trusted root does NOT imply that the certificate
398 is valid.
405 is valid.
399 """
406 """
400
407
401 chainctxptr = ctypes.POINTER(CERT_CHAIN_CONTEXT)
408 chainctxptr = ctypes.POINTER(CERT_CHAIN_CONTEXT)
402
409
403 pchainctx = chainctxptr()
410 pchainctx = chainctxptr()
404 chainpara = CERT_CHAIN_PARA(
411 chainpara = CERT_CHAIN_PARA(
405 cbSize=ctypes.sizeof(CERT_CHAIN_PARA), RequestedUsage=CERT_USAGE_MATCH()
412 cbSize=ctypes.sizeof(CERT_CHAIN_PARA), RequestedUsage=CERT_USAGE_MATCH()
406 )
413 )
407
414
408 certctx = _crypt32.CertCreateCertificateContext(
415 certctx = _crypt32.CertCreateCertificateContext(
409 X509_ASN_ENCODING, cert, len(cert)
416 X509_ASN_ENCODING, cert, len(cert)
410 )
417 )
411 if certctx is None:
418 if certctx is None:
412 _raiseoserror(b'CertCreateCertificateContext')
419 _raiseoserror(b'CertCreateCertificateContext')
413
420
414 flags = 0
421 flags = 0
415
422
416 if not build:
423 if not build:
417 flags |= 0x100 # CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
424 flags |= 0x100 # CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
418
425
419 try:
426 try:
420 # Building the certificate chain will update root certs as necessary.
427 # Building the certificate chain will update root certs as necessary.
421 if not _crypt32.CertGetCertificateChain(
428 if not _crypt32.CertGetCertificateChain(
422 None, # hChainEngine
429 None, # hChainEngine
423 certctx, # pCertContext
430 certctx, # pCertContext
424 None, # pTime
431 None, # pTime
425 None, # hAdditionalStore
432 None, # hAdditionalStore
426 ctypes.byref(chainpara),
433 ctypes.byref(chainpara),
427 flags,
434 flags,
428 None, # pvReserved
435 None, # pvReserved
429 ctypes.byref(pchainctx),
436 ctypes.byref(pchainctx),
430 ):
437 ):
431 _raiseoserror(b'CertGetCertificateChain')
438 _raiseoserror(b'CertGetCertificateChain')
432
439
433 chainctx = pchainctx.contents
440 chainctx = pchainctx.contents
434
441
435 return chainctx.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN == 0
442 return chainctx.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN == 0
436 finally:
443 finally:
437 if pchainctx:
444 if pchainctx:
438 _crypt32.CertFreeCertificateChain(pchainctx)
445 _crypt32.CertFreeCertificateChain(pchainctx)
439 _crypt32.CertFreeCertificateContext(certctx)
446 _crypt32.CertFreeCertificateContext(certctx)
440
447
441
448
442 def oslink(src, dst):
449 def oslink(src: bytes, dst: bytes) -> None:
443 try:
450 try:
444 if not _kernel32.CreateHardLinkA(dst, src, None):
451 if not _kernel32.CreateHardLinkA(dst, src, None):
445 _raiseoserror(src)
452 _raiseoserror(src)
446 except AttributeError: # Wine doesn't support this function
453 except AttributeError: # Wine doesn't support this function
447 _raiseoserror(src)
454 _raiseoserror(src)
448
455
449
456
450 def nlinks(name):
457 def nlinks(name: bytes) -> int:
451 '''return number of hardlinks for the given file'''
458 '''return number of hardlinks for the given file'''
452 return _getfileinfo(name).nNumberOfLinks
459 return _getfileinfo(name).nNumberOfLinks
453
460
454
461
455 def samefile(path1, path2):
462 def samefile(path1: bytes, path2: bytes) -> bool:
456 '''Returns whether path1 and path2 refer to the same file or directory.'''
463 '''Returns whether path1 and path2 refer to the same file or directory.'''
457 res1 = _getfileinfo(path1)
464 res1 = _getfileinfo(path1)
458 res2 = _getfileinfo(path2)
465 res2 = _getfileinfo(path2)
459 return (
466 return (
460 res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
467 res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
461 and res1.nFileIndexHigh == res2.nFileIndexHigh
468 and res1.nFileIndexHigh == res2.nFileIndexHigh
462 and res1.nFileIndexLow == res2.nFileIndexLow
469 and res1.nFileIndexLow == res2.nFileIndexLow
463 )
470 )
464
471
465
472
466 def samedevice(path1, path2):
473 def samedevice(path1: bytes, path2: bytes) -> bool:
467 '''Returns whether path1 and path2 are on the same device.'''
474 '''Returns whether path1 and path2 are on the same device.'''
468 res1 = _getfileinfo(path1)
475 res1 = _getfileinfo(path1)
469 res2 = _getfileinfo(path2)
476 res2 = _getfileinfo(path2)
470 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
477 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
471
478
472
479
473 def peekpipe(pipe):
480 def peekpipe(pipe) -> int:
474 handle = msvcrt.get_osfhandle(pipe.fileno()) # pytype: disable=module-attr
481 handle = msvcrt.get_osfhandle(pipe.fileno()) # pytype: disable=module-attr
475 avail = _DWORD()
482 avail = _DWORD()
476
483
477 if not _kernel32.PeekNamedPipe(
484 if not _kernel32.PeekNamedPipe(
478 handle, None, 0, None, ctypes.byref(avail), None
485 handle, None, 0, None, ctypes.byref(avail), None
479 ):
486 ):
480 err = _kernel32.GetLastError()
487 err = _kernel32.GetLastError()
481 if err == _ERROR_BROKEN_PIPE:
488 if err == _ERROR_BROKEN_PIPE:
482 return 0
489 return 0
483 raise ctypes.WinError(err) # pytype: disable=module-attr
490 raise ctypes.WinError(err) # pytype: disable=module-attr
484
491
485 return avail.value
492 return avail.value
486
493
487
494
488 def lasterrorwaspipeerror(err):
495 def lasterrorwaspipeerror(err) -> bool:
489 if err.errno != errno.EINVAL:
496 if err.errno != errno.EINVAL:
490 return False
497 return False
491 err = _kernel32.GetLastError()
498 err = _kernel32.GetLastError()
492 return err == _ERROR_BROKEN_PIPE or err == _ERROR_NO_DATA
499 return err == _ERROR_BROKEN_PIPE or err == _ERROR_NO_DATA
493
500
494
501
495 def testpid(pid):
502 def testpid(pid: int) -> bool:
496 """return True if pid is still running or unable to
503 """return True if pid is still running or unable to
497 determine, False otherwise"""
504 determine, False otherwise"""
498 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
505 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
499 if h:
506 if h:
500 try:
507 try:
501 status = _DWORD()
508 status = _DWORD()
502 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
509 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
503 return status.value == _STILL_ACTIVE
510 return status.value == _STILL_ACTIVE
504 finally:
511 finally:
505 _kernel32.CloseHandle(h)
512 _kernel32.CloseHandle(h)
506 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
513 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
507
514
508
515
509 def executablepath():
516 def executablepath() -> bytes:
510 '''return full path of hg.exe'''
517 '''return full path of hg.exe'''
511 size = 600
518 size = 600
512 buf = ctypes.create_string_buffer(size + 1)
519 buf = ctypes.create_string_buffer(size + 1)
513 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
520 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
514 # pytype: disable=module-attr
521 # pytype: disable=module-attr
515 if len == 0:
522 if len == 0:
516 raise ctypes.WinError() # Note: WinError is a function
523 raise ctypes.WinError() # Note: WinError is a function
517 elif len == size:
524 elif len == size:
518 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
525 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
519 # pytype: enable=module-attr
526 # pytype: enable=module-attr
520 return buf.value
527 return buf.value
521
528
522
529
523 def getvolumename(path):
530 def getvolumename(path: bytes) -> Optional[bytes]:
524 """Get the mount point of the filesystem from a directory or file
531 """Get the mount point of the filesystem from a directory or file
525 (best-effort)
532 (best-effort)
526
533
527 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
534 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
528 """
535 """
529 # realpath() calls GetFullPathName()
536 # realpath() calls GetFullPathName()
530 realpath = os.path.realpath(path)
537 realpath = os.path.realpath(path)
531
538
532 # allocate at least MAX_PATH long since GetVolumePathName('c:\\', buf, 4)
539 # allocate at least MAX_PATH long since GetVolumePathName('c:\\', buf, 4)
533 # somehow fails on Windows XP
540 # somehow fails on Windows XP
534 size = max(len(realpath), _MAX_PATH) + 1
541 size = max(len(realpath), _MAX_PATH) + 1
535 buf = ctypes.create_string_buffer(size)
542 buf = ctypes.create_string_buffer(size)
536
543
537 if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):
544 if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):
538 # Note: WinError is a function
545 # Note: WinError is a function
539 raise ctypes.WinError() # pytype: disable=module-attr
546 raise ctypes.WinError() # pytype: disable=module-attr
540
547
541 return buf.value
548 return buf.value
542
549
543
550
544 def getfstype(path):
551 def getfstype(path: bytes) -> Optional[bytes]:
545 """Get the filesystem type name from a directory or file (best-effort)
552 """Get the filesystem type name from a directory or file (best-effort)
546
553
547 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
554 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
548 """
555 """
549 volume = getvolumename(path)
556 volume = getvolumename(path)
550
557
551 t = _kernel32.GetDriveTypeA(volume)
558 t = _kernel32.GetDriveTypeA(volume)
552
559
553 if t == _DRIVE_REMOTE:
560 if t == _DRIVE_REMOTE:
554 return b'cifs'
561 return b'cifs'
555 elif t not in (
562 elif t not in (
556 _DRIVE_REMOVABLE,
563 _DRIVE_REMOVABLE,
557 _DRIVE_FIXED,
564 _DRIVE_FIXED,
558 _DRIVE_CDROM,
565 _DRIVE_CDROM,
559 _DRIVE_RAMDISK,
566 _DRIVE_RAMDISK,
560 ):
567 ):
561 return None
568 return None
562
569
563 size = _MAX_PATH + 1
570 size = _MAX_PATH + 1
564 name = ctypes.create_string_buffer(size)
571 name = ctypes.create_string_buffer(size)
565
572
566 if not _kernel32.GetVolumeInformationA(
573 if not _kernel32.GetVolumeInformationA(
567 volume, None, 0, None, None, None, ctypes.byref(name), size
574 volume, None, 0, None, None, None, ctypes.byref(name), size
568 ):
575 ):
569 # Note: WinError is a function
576 # Note: WinError is a function
570 raise ctypes.WinError() # pytype: disable=module-attr
577 raise ctypes.WinError() # pytype: disable=module-attr
571
578
572 return name.value
579 return name.value
573
580
574
581
575 def getuser():
582 def getuser() -> bytes:
576 '''return name of current user'''
583 '''return name of current user'''
577 size = _DWORD(300)
584 size = _DWORD(300)
578 buf = ctypes.create_string_buffer(size.value + 1)
585 buf = ctypes.create_string_buffer(size.value + 1)
579 if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
586 if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
580 raise ctypes.WinError() # pytype: disable=module-attr
587 raise ctypes.WinError() # pytype: disable=module-attr
581 return buf.value
588 return buf.value
582
589
583
590
584 _signalhandler = []
591 _signalhandler: List[_SIGNAL_HANDLER] = []
585
592
586
593
587 def setsignalhandler():
594 def setsignalhandler() -> None:
588 """Register a termination handler for console events including
595 """Register a termination handler for console events including
589 CTRL+C. python signal handlers do not work well with socket
596 CTRL+C. python signal handlers do not work well with socket
590 operations.
597 operations.
591 """
598 """
592
599
593 def handler(event):
600 def handler(event):
594 _kernel32.ExitProcess(1)
601 _kernel32.ExitProcess(1)
595
602
596 if _signalhandler:
603 if _signalhandler:
597 return # already registered
604 return # already registered
598 h = _SIGNAL_HANDLER(handler)
605 h = _SIGNAL_HANDLER(handler)
599 _signalhandler.append(h) # needed to prevent garbage collection
606 _signalhandler.append(h) # needed to prevent garbage collection
600 if not _kernel32.SetConsoleCtrlHandler(h, True):
607 if not _kernel32.SetConsoleCtrlHandler(h, True):
601 raise ctypes.WinError() # pytype: disable=module-attr
608 raise ctypes.WinError() # pytype: disable=module-attr
602
609
603
610
604 def hidewindow():
611 def hidewindow() -> None:
605 def callback(hwnd, pid):
612 def callback(hwnd, pid):
606 wpid = _DWORD()
613 wpid = _DWORD()
607 _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
614 _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
608 if pid == wpid.value:
615 if pid == wpid.value:
609 _user32.ShowWindow(hwnd, _SW_HIDE)
616 _user32.ShowWindow(hwnd, _SW_HIDE)
610 return False # stop enumerating windows
617 return False # stop enumerating windows
611 return True
618 return True
612
619
613 pid = _kernel32.GetCurrentProcessId()
620 pid = _kernel32.GetCurrentProcessId()
614 _user32.EnumWindows(_WNDENUMPROC(callback), pid)
621 _user32.EnumWindows(_WNDENUMPROC(callback), pid)
615
622
616
623
617 def termsize():
624 def termsize() -> Tuple[int, int]:
618 # cmd.exe does not handle CR like a unix console, the CR is
625 # cmd.exe does not handle CR like a unix console, the CR is
619 # counted in the line length. On 80 columns consoles, if 80
626 # counted in the line length. On 80 columns consoles, if 80
620 # characters are written, the following CR won't apply on the
627 # characters are written, the following CR won't apply on the
621 # current line but on the new one. Keep room for it.
628 # current line but on the new one. Keep room for it.
622 width = 80 - 1
629 width = 80 - 1
623 height = 25
630 height = 25
624 # Query stderr to avoid problems with redirections
631 # Query stderr to avoid problems with redirections
625 screenbuf = _kernel32.GetStdHandle(
632 screenbuf = _kernel32.GetStdHandle(
626 _STD_ERROR_HANDLE
633 _STD_ERROR_HANDLE
627 ) # don't close the handle returned
634 ) # don't close the handle returned
628 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
635 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
629 return width, height
636 return width, height
630 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
637 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
631 if not _kernel32.GetConsoleScreenBufferInfo(screenbuf, ctypes.byref(csbi)):
638 if not _kernel32.GetConsoleScreenBufferInfo(screenbuf, ctypes.byref(csbi)):
632 return width, height
639 return width, height
633 width = csbi.srWindow.Right - csbi.srWindow.Left # don't '+ 1'
640 width = csbi.srWindow.Right - csbi.srWindow.Left # don't '+ 1'
634 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1
641 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1
635 return width, height
642 return width, height
636
643
637
644
638 def enablevtmode():
645 def enablevtmode() -> bool:
639 """Enable virtual terminal mode for the associated console. Return True if
646 """Enable virtual terminal mode for the associated console. Return True if
640 enabled, else False."""
647 enabled, else False."""
641
648
642 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
649 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
643
650
644 handle = _kernel32.GetStdHandle(
651 handle = _kernel32.GetStdHandle(
645 _STD_OUTPUT_HANDLE
652 _STD_OUTPUT_HANDLE
646 ) # don't close the handle
653 ) # don't close the handle
647 if handle == _INVALID_HANDLE_VALUE:
654 if handle == _INVALID_HANDLE_VALUE:
648 return False
655 return False
649
656
650 mode = _DWORD(0)
657 mode = _DWORD(0)
651
658
652 if not _kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
659 if not _kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
653 return False
660 return False
654
661
655 if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
662 if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
656 mode.value |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
663 mode.value |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
657
664
658 if not _kernel32.SetConsoleMode(handle, mode):
665 if not _kernel32.SetConsoleMode(handle, mode):
659 return False
666 return False
660
667
661 return True
668 return True
662
669
663
670
664 def spawndetached(args):
671 def spawndetached(args: List[bytes]) -> int:
665 # No standard library function really spawns a fully detached
672 # No standard library function really spawns a fully detached
666 # process under win32 because they allocate pipes or other objects
673 # process under win32 because they allocate pipes or other objects
667 # to handle standard streams communications. Passing these objects
674 # to handle standard streams communications. Passing these objects
668 # to the child process requires handle inheritance to be enabled
675 # to the child process requires handle inheritance to be enabled
669 # which makes really detached processes impossible.
676 # which makes really detached processes impossible.
670 si = _STARTUPINFO()
677 si = _STARTUPINFO()
671 si.cb = ctypes.sizeof(_STARTUPINFO)
678 si.cb = ctypes.sizeof(_STARTUPINFO)
672
679
673 pi = _PROCESS_INFORMATION()
680 pi = _PROCESS_INFORMATION()
674
681
675 env = b''
682 env = b''
676 for k in encoding.environ:
683 for k in encoding.environ:
677 env += b"%s=%s\0" % (k, encoding.environ[k])
684 env += b"%s=%s\0" % (k, encoding.environ[k])
678 if not env:
685 if not env:
679 env = b'\0'
686 env = b'\0'
680 env += b'\0'
687 env += b'\0'
681
688
682 args = subprocess.list2cmdline(pycompat.rapply(encoding.strfromlocal, args))
689 args = subprocess.list2cmdline(pycompat.rapply(encoding.strfromlocal, args))
683
690
684 # TODO: CreateProcessW on py3?
691 # TODO: CreateProcessW on py3?
685 res = _kernel32.CreateProcessA(
692 res = _kernel32.CreateProcessA(
686 None,
693 None,
687 encoding.strtolocal(args),
694 encoding.strtolocal(args),
688 None,
695 None,
689 None,
696 None,
690 False,
697 False,
691 _CREATE_NO_WINDOW,
698 _CREATE_NO_WINDOW,
692 env,
699 env,
693 encoding.getcwd(),
700 encoding.getcwd(),
694 ctypes.byref(si),
701 ctypes.byref(si),
695 ctypes.byref(pi),
702 ctypes.byref(pi),
696 )
703 )
697 if not res:
704 if not res:
698 raise ctypes.WinError() # pytype: disable=module-attr
705 raise ctypes.WinError() # pytype: disable=module-attr
699
706
700 _kernel32.CloseHandle(pi.hProcess)
707 _kernel32.CloseHandle(pi.hProcess)
701 _kernel32.CloseHandle(pi.hThread)
708 _kernel32.CloseHandle(pi.hThread)
702
709
703 return pi.dwProcessId
710 return pi.dwProcessId
704
711
705
712
706 def unlink(f):
713 def unlink(f: bytes) -> None:
707 '''try to implement POSIX' unlink semantics on Windows'''
714 '''try to implement POSIX' unlink semantics on Windows'''
708
715
709 if os.path.isdir(f):
716 if os.path.isdir(f):
710 # use EPERM because it is POSIX prescribed value, even though
717 # use EPERM because it is POSIX prescribed value, even though
711 # unlink(2) on directories returns EISDIR on Linux
718 # unlink(2) on directories returns EISDIR on Linux
712 raise IOError(
719 raise IOError(
713 errno.EPERM,
720 errno.EPERM,
714 r"Unlinking directory not permitted: '%s'"
721 r"Unlinking directory not permitted: '%s'"
715 % encoding.strfromlocal(f),
722 % encoding.strfromlocal(f),
716 )
723 )
717
724
718 # POSIX allows to unlink and rename open files. Windows has serious
725 # POSIX allows to unlink and rename open files. Windows has serious
719 # problems with doing that:
726 # problems with doing that:
720 # - Calling os.unlink (or os.rename) on a file f fails if f or any
727 # - Calling os.unlink (or os.rename) on a file f fails if f or any
721 # hardlinked copy of f has been opened with Python's open(). There is no
728 # hardlinked copy of f has been opened with Python's open(). There is no
722 # way such a file can be deleted or renamed on Windows (other than
729 # way such a file can be deleted or renamed on Windows (other than
723 # scheduling the delete or rename for the next reboot).
730 # scheduling the delete or rename for the next reboot).
724 # - Calling os.unlink on a file that has been opened with Mercurial's
731 # - Calling os.unlink on a file that has been opened with Mercurial's
725 # posixfile (or comparable methods) will delay the actual deletion of
732 # posixfile (or comparable methods) will delay the actual deletion of
726 # the file for as long as the file is held open. The filename is blocked
733 # the file for as long as the file is held open. The filename is blocked
727 # during that time and cannot be used for recreating a new file under
734 # during that time and cannot be used for recreating a new file under
728 # that same name ("zombie file"). Directories containing such zombie files
735 # that same name ("zombie file"). Directories containing such zombie files
729 # cannot be removed or moved.
736 # cannot be removed or moved.
730 # A file that has been opened with posixfile can be renamed, so we rename
737 # A file that has been opened with posixfile can be renamed, so we rename
731 # f to a random temporary name before calling os.unlink on it. This allows
738 # f to a random temporary name before calling os.unlink on it. This allows
732 # callers to recreate f immediately while having other readers do their
739 # callers to recreate f immediately while having other readers do their
733 # implicit zombie filename blocking on a temporary name.
740 # implicit zombie filename blocking on a temporary name.
734
741
735 for tries in range(10):
742 for tries in range(10):
736 temp = b'%s-%08x' % (f, random.randint(0, 0xFFFFFFFF))
743 temp = b'%s-%08x' % (f, random.randint(0, 0xFFFFFFFF))
737 try:
744 try:
738 os.rename(f, temp)
745 os.rename(f, temp)
739 break
746 break
740 except FileExistsError:
747 except FileExistsError:
741 pass
748 pass
742 else:
749 else:
743 raise IOError(errno.EEXIST, "No usable temporary filename found")
750 raise IOError(errno.EEXIST, "No usable temporary filename found")
744
751
745 try:
752 try:
746 os.unlink(temp)
753 os.unlink(temp)
747 except OSError:
754 except OSError:
748 # The unlink might have failed because the READONLY attribute may heave
755 # The unlink might have failed because the READONLY attribute may heave
749 # been set on the original file. Rename works fine with READONLY set,
756 # been set on the original file. Rename works fine with READONLY set,
750 # but not os.unlink. Reset all attributes and try again.
757 # but not os.unlink. Reset all attributes and try again.
751 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
758 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
752 try:
759 try:
753 os.unlink(temp)
760 os.unlink(temp)
754 except OSError:
761 except OSError:
755 # The unlink might have failed due to some very rude AV-Scanners.
762 # The unlink might have failed due to some very rude AV-Scanners.
756 # Leaking a tempfile is the lesser evil than aborting here and
763 # Leaking a tempfile is the lesser evil than aborting here and
757 # leaving some potentially serious inconsistencies.
764 # leaving some potentially serious inconsistencies.
758 pass
765 pass
759
766
760
767
761 def makedir(path, notindexed):
768 def makedir(path: bytes, notindexed: bool) -> None:
762 os.mkdir(path)
769 os.mkdir(path)
763 if notindexed:
770 if notindexed:
764 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
771 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
General Comments 0
You need to be logged in to leave comments. Login now