##// END OF EJS Templates
windows: if username(uid=None) is loaded, just use getpass...
Augie Fackler -
r44356:1b26cb04 default
parent child Browse files
Show More
@@ -1,690 +1,693 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 import getpass
11 12 import msvcrt
12 13 import os
13 14 import re
14 15 import stat
15 16 import string
16 17 import sys
17 18
18 19 from .i18n import _
19 20 from .pycompat import getattr
20 21 from . import (
21 22 encoding,
22 23 error,
23 24 policy,
24 25 pycompat,
25 26 win32,
26 27 )
27 28
28 29 try:
29 30 import _winreg as winreg # pytype: disable=import-error
30 31
31 32 winreg.CloseKey
32 33 except ImportError:
33 34 # py2 only
34 35 import winreg # pytype: disable=import-error
35 36
36 37 osutil = policy.importmod('osutil')
37 38
38 39 getfsmountpoint = win32.getvolumename
39 40 getfstype = win32.getfstype
40 41 getuser = win32.getuser
41 42 hidewindow = win32.hidewindow
42 43 makedir = win32.makedir
43 44 nlinks = win32.nlinks
44 45 oslink = win32.oslink
45 46 samedevice = win32.samedevice
46 47 samefile = win32.samefile
47 48 setsignalhandler = win32.setsignalhandler
48 49 spawndetached = win32.spawndetached
49 50 split = os.path.split
50 51 testpid = win32.testpid
51 52 unlink = win32.unlink
52 53
53 54 umask = 0o022
54 55
55 56
56 57 class mixedfilemodewrapper(object):
57 58 """Wraps a file handle when it is opened in read/write mode.
58 59
59 60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
60 61 that files opened with mode r+, w+, or a+ make a call to a file positioning
61 62 function when switching between reads and writes. Without this extra call,
62 63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
63 64
64 65 This class wraps posixfile instances when the file is opened in read/write
65 66 mode and automatically adds checks or inserts appropriate file positioning
66 67 calls when necessary.
67 68 """
68 69
69 70 OPNONE = 0
70 71 OPREAD = 1
71 72 OPWRITE = 2
72 73
73 74 def __init__(self, fp):
74 75 object.__setattr__(self, '_fp', fp)
75 76 object.__setattr__(self, '_lastop', 0)
76 77
77 78 def __enter__(self):
78 79 self._fp.__enter__()
79 80 return self
80 81
81 82 def __exit__(self, exc_type, exc_val, exc_tb):
82 83 self._fp.__exit__(exc_type, exc_val, exc_tb)
83 84
84 85 def __getattr__(self, name):
85 86 return getattr(self._fp, name)
86 87
87 88 def __setattr__(self, name, value):
88 89 return self._fp.__setattr__(name, value)
89 90
90 91 def _noopseek(self):
91 92 self._fp.seek(0, os.SEEK_CUR)
92 93
93 94 def seek(self, *args, **kwargs):
94 95 object.__setattr__(self, '_lastop', self.OPNONE)
95 96 return self._fp.seek(*args, **kwargs)
96 97
97 98 def write(self, d):
98 99 if self._lastop == self.OPREAD:
99 100 self._noopseek()
100 101
101 102 object.__setattr__(self, '_lastop', self.OPWRITE)
102 103 return self._fp.write(d)
103 104
104 105 def writelines(self, *args, **kwargs):
105 106 if self._lastop == self.OPREAD:
106 107 self._noopeseek()
107 108
108 109 object.__setattr__(self, '_lastop', self.OPWRITE)
109 110 return self._fp.writelines(*args, **kwargs)
110 111
111 112 def read(self, *args, **kwargs):
112 113 if self._lastop == self.OPWRITE:
113 114 self._noopseek()
114 115
115 116 object.__setattr__(self, '_lastop', self.OPREAD)
116 117 return self._fp.read(*args, **kwargs)
117 118
118 119 def readline(self, *args, **kwargs):
119 120 if self._lastop == self.OPWRITE:
120 121 self._noopseek()
121 122
122 123 object.__setattr__(self, '_lastop', self.OPREAD)
123 124 return self._fp.readline(*args, **kwargs)
124 125
125 126 def readlines(self, *args, **kwargs):
126 127 if self._lastop == self.OPWRITE:
127 128 self._noopseek()
128 129
129 130 object.__setattr__(self, '_lastop', self.OPREAD)
130 131 return self._fp.readlines(*args, **kwargs)
131 132
132 133
133 134 class fdproxy(object):
134 135 """Wraps osutil.posixfile() to override the name attribute to reflect the
135 136 underlying file name.
136 137 """
137 138
138 139 def __init__(self, name, fp):
139 140 self.name = name
140 141 self._fp = fp
141 142
142 143 def __enter__(self):
143 144 self._fp.__enter__()
144 145 # Return this wrapper for the context manager so that the name is
145 146 # still available.
146 147 return self
147 148
148 149 def __exit__(self, exc_type, exc_value, traceback):
149 150 self._fp.__exit__(exc_type, exc_value, traceback)
150 151
151 152 def __iter__(self):
152 153 return iter(self._fp)
153 154
154 155 def __getattr__(self, name):
155 156 return getattr(self._fp, name)
156 157
157 158
158 159 def posixfile(name, mode=b'r', buffering=-1):
159 160 '''Open a file with even more POSIX-like semantics'''
160 161 try:
161 162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
162 163
163 164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
164 165 # underlying file descriptor.
165 166 if pycompat.ispy3:
166 167 fp = fdproxy(name, fp)
167 168
168 169 # The position when opening in append mode is implementation defined, so
169 170 # make it consistent with other platforms, which position at EOF.
170 171 if b'a' in mode:
171 172 fp.seek(0, os.SEEK_END)
172 173
173 174 if b'+' in mode:
174 175 return mixedfilemodewrapper(fp)
175 176
176 177 return fp
177 178 except WindowsError as err:
178 179 # convert to a friendlier exception
179 180 raise IOError(
180 181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
181 182 )
182 183
183 184
184 185 # may be wrapped by win32mbcs extension
185 186 listdir = osutil.listdir
186 187
187 188
188 189 class winstdout(object):
189 190 '''stdout on windows misbehaves if sent through a pipe'''
190 191
191 192 def __init__(self, fp):
192 193 self.fp = fp
193 194
194 195 def __getattr__(self, key):
195 196 return getattr(self.fp, key)
196 197
197 198 def close(self):
198 199 try:
199 200 self.fp.close()
200 201 except IOError:
201 202 pass
202 203
203 204 def write(self, s):
204 205 try:
205 206 # This is workaround for "Not enough space" error on
206 207 # writing large size of data to console.
207 208 limit = 16000
208 209 l = len(s)
209 210 start = 0
210 211 self.softspace = 0
211 212 while start < l:
212 213 end = start + limit
213 214 self.fp.write(s[start:end])
214 215 start = end
215 216 except IOError as inst:
216 217 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
217 218 raise
218 219 self.close()
219 220 raise IOError(errno.EPIPE, 'Broken pipe')
220 221
221 222 def flush(self):
222 223 try:
223 224 return self.fp.flush()
224 225 except IOError as inst:
225 226 if not win32.lasterrorwaspipeerror(inst):
226 227 raise
227 228 raise IOError(errno.EPIPE, 'Broken pipe')
228 229
229 230
230 231 def _is_win_9x():
231 232 '''return true if run on windows 95, 98 or me.'''
232 233 try:
233 234 return sys.getwindowsversion()[3] == 1
234 235 except AttributeError:
235 236 return b'command' in encoding.environ.get(b'comspec', b'')
236 237
237 238
238 239 def openhardlinks():
239 240 return not _is_win_9x()
240 241
241 242
242 243 def parsepatchoutput(output_line):
243 244 """parses the output produced by patch and returns the filename"""
244 245 pf = output_line[14:]
245 246 if pf[0] == b'`':
246 247 pf = pf[1:-1] # Remove the quotes
247 248 return pf
248 249
249 250
250 251 def sshargs(sshcmd, host, user, port):
251 252 '''Build argument list for ssh or Plink'''
252 253 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
253 254 args = user and (b"%s@%s" % (user, host)) or host
254 255 if args.startswith(b'-') or args.startswith(b'/'):
255 256 raise error.Abort(
256 257 _(b'illegal ssh hostname or username starting with - or /: %s')
257 258 % args
258 259 )
259 260 args = shellquote(args)
260 261 if port:
261 262 args = b'%s %s %s' % (pflag, shellquote(port), args)
262 263 return args
263 264
264 265
265 266 def setflags(f, l, x):
266 267 pass
267 268
268 269
269 270 def copymode(src, dst, mode=None, enforcewritable=False):
270 271 pass
271 272
272 273
273 274 def checkexec(path):
274 275 return False
275 276
276 277
277 278 def checklink(path):
278 279 return False
279 280
280 281
281 282 def setbinary(fd):
282 283 # When run without console, pipes may expose invalid
283 284 # fileno(), usually set to -1.
284 285 fno = getattr(fd, 'fileno', None)
285 286 if fno is not None and fno() >= 0:
286 287 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
287 288
288 289
289 290 def pconvert(path):
290 291 return path.replace(pycompat.ossep, b'/')
291 292
292 293
293 294 def localpath(path):
294 295 return path.replace(b'/', b'\\')
295 296
296 297
297 298 def normpath(path):
298 299 return pconvert(os.path.normpath(path))
299 300
300 301
301 302 def normcase(path):
302 303 return encoding.upper(path) # NTFS compares via upper()
303 304
304 305
305 306 # see posix.py for definitions
306 307 normcasespec = encoding.normcasespecs.upper
307 308 normcasefallback = encoding.upperfallback
308 309
309 310
310 311 def samestat(s1, s2):
311 312 return False
312 313
313 314
314 315 def shelltocmdexe(path, env):
315 316 r"""Convert shell variables in the form $var and ${var} inside ``path``
316 317 to %var% form. Existing Windows style variables are left unchanged.
317 318
318 319 The variables are limited to the given environment. Unknown variables are
319 320 left unchanged.
320 321
321 322 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
322 323 >>> # Only valid values are expanded
323 324 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
324 325 ... e)
325 326 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
326 327 >>> # Single quote prevents expansion, as does \$ escaping
327 328 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
328 329 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
329 330 >>> # $$ is not special. %% is not special either, but can be the end and
330 331 >>> # start of consecutive variables
331 332 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
332 333 'cmd $$ %% %var1%%var2%'
333 334 >>> # No double substitution
334 335 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
335 336 '%var1% %var1%'
336 337 >>> # Tilde expansion
337 338 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
338 339 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
339 340 """
340 341 if not any(c in path for c in b"$'~"):
341 342 return path
342 343
343 344 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
344 345
345 346 res = b''
346 347 index = 0
347 348 pathlen = len(path)
348 349 while index < pathlen:
349 350 c = path[index : index + 1]
350 351 if c == b'\'': # no expansion within single quotes
351 352 path = path[index + 1 :]
352 353 pathlen = len(path)
353 354 try:
354 355 index = path.index(b'\'')
355 356 res += b'"' + path[:index] + b'"'
356 357 except ValueError:
357 358 res += c + path
358 359 index = pathlen - 1
359 360 elif c == b'%': # variable
360 361 path = path[index + 1 :]
361 362 pathlen = len(path)
362 363 try:
363 364 index = path.index(b'%')
364 365 except ValueError:
365 366 res += b'%' + path
366 367 index = pathlen - 1
367 368 else:
368 369 var = path[:index]
369 370 res += b'%' + var + b'%'
370 371 elif c == b'$': # variable
371 372 if path[index + 1 : index + 2] == b'{':
372 373 path = path[index + 2 :]
373 374 pathlen = len(path)
374 375 try:
375 376 index = path.index(b'}')
376 377 var = path[:index]
377 378
378 379 # See below for why empty variables are handled specially
379 380 if env.get(var, b'') != b'':
380 381 res += b'%' + var + b'%'
381 382 else:
382 383 res += b'${' + var + b'}'
383 384 except ValueError:
384 385 res += b'${' + path
385 386 index = pathlen - 1
386 387 else:
387 388 var = b''
388 389 index += 1
389 390 c = path[index : index + 1]
390 391 while c != b'' and c in varchars:
391 392 var += c
392 393 index += 1
393 394 c = path[index : index + 1]
394 395 # Some variables (like HG_OLDNODE) may be defined, but have an
395 396 # empty value. Those need to be skipped because when spawning
396 397 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
397 398 # VAR, and that really confuses things like revset expressions.
398 399 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
399 400 # will substitute to an empty string, and everything is happy.
400 401 if env.get(var, b'') != b'':
401 402 res += b'%' + var + b'%'
402 403 else:
403 404 res += b'$' + var
404 405
405 406 if c != b'':
406 407 index -= 1
407 408 elif (
408 409 c == b'~'
409 410 and index + 1 < pathlen
410 411 and path[index + 1 : index + 2] in (b'\\', b'/')
411 412 ):
412 413 res += b"%USERPROFILE%"
413 414 elif (
414 415 c == b'\\'
415 416 and index + 1 < pathlen
416 417 and path[index + 1 : index + 2] in (b'$', b'~')
417 418 ):
418 419 # Skip '\', but only if it is escaping $ or ~
419 420 res += path[index + 1 : index + 2]
420 421 index += 1
421 422 else:
422 423 res += c
423 424
424 425 index += 1
425 426 return res
426 427
427 428
428 429 # A sequence of backslashes is special iff it precedes a double quote:
429 430 # - if there's an even number of backslashes, the double quote is not
430 431 # quoted (i.e. it ends the quoted region)
431 432 # - if there's an odd number of backslashes, the double quote is quoted
432 433 # - in both cases, every pair of backslashes is unquoted into a single
433 434 # backslash
434 435 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
435 436 # So, to quote a string, we must surround it in double quotes, double
436 437 # the number of backslashes that precede double quotes and add another
437 438 # backslash before every double quote (being careful with the double
438 439 # quote we've appended to the end)
439 440 _quotere = None
440 441 _needsshellquote = None
441 442
442 443
443 444 def shellquote(s):
444 445 r"""
445 446 >>> shellquote(br'C:\Users\xyz')
446 447 '"C:\\Users\\xyz"'
447 448 >>> shellquote(br'C:\Users\xyz/mixed')
448 449 '"C:\\Users\\xyz/mixed"'
449 450 >>> # Would be safe not to quote too, since it is all double backslashes
450 451 >>> shellquote(br'C:\\Users\\xyz')
451 452 '"C:\\\\Users\\\\xyz"'
452 453 >>> # But this must be quoted
453 454 >>> shellquote(br'C:\\Users\\xyz/abc')
454 455 '"C:\\\\Users\\\\xyz/abc"'
455 456 """
456 457 global _quotere
457 458 if _quotere is None:
458 459 _quotere = re.compile(br'(\\*)("|\\$)')
459 460 global _needsshellquote
460 461 if _needsshellquote is None:
461 462 # ":" is also treated as "safe character", because it is used as a part
462 463 # of path name on Windows. "\" is also part of a path name, but isn't
463 464 # safe because shlex.split() (kind of) treats it as an escape char and
464 465 # drops it. It will leave the next character, even if it is another
465 466 # "\".
466 467 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
467 468 if s and not _needsshellquote(s) and not _quotere.search(s):
468 469 # "s" shouldn't have to be quoted
469 470 return s
470 471 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
471 472
472 473
473 474 def _unquote(s):
474 475 if s.startswith(b'"') and s.endswith(b'"'):
475 476 return s[1:-1]
476 477 return s
477 478
478 479
479 480 def shellsplit(s):
480 481 """Parse a command string in cmd.exe way (best-effort)"""
481 482 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
482 483
483 484
484 485 def quotecommand(cmd):
485 486 """Build a command string suitable for os.popen* calls."""
486 487 if sys.version_info < (2, 7, 1):
487 488 # Python versions since 2.7.1 do this extra quoting themselves
488 489 return b'"' + cmd + b'"'
489 490 return cmd
490 491
491 492
492 493 # if you change this stub into a real check, please try to implement the
493 494 # username and groupname functions above, too.
494 495 def isowner(st):
495 496 return True
496 497
497 498
498 499 def findexe(command):
499 500 '''Find executable for command searching like cmd.exe does.
500 501 If command is a basename then PATH is searched for command.
501 502 PATH isn't searched if command is an absolute or relative path.
502 503 An extension from PATHEXT is found and added if not present.
503 504 If command isn't found None is returned.'''
504 505 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
505 506 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
506 507 if os.path.splitext(command)[1].lower() in pathexts:
507 508 pathexts = [b'']
508 509
509 510 def findexisting(pathcommand):
510 511 """Will append extension (if needed) and return existing file"""
511 512 for ext in pathexts:
512 513 executable = pathcommand + ext
513 514 if os.path.exists(executable):
514 515 return executable
515 516 return None
516 517
517 518 if pycompat.ossep in command:
518 519 return findexisting(command)
519 520
520 521 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
521 522 executable = findexisting(os.path.join(path, command))
522 523 if executable is not None:
523 524 return executable
524 525 return findexisting(os.path.expanduser(os.path.expandvars(command)))
525 526
526 527
527 528 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
528 529
529 530
530 531 def statfiles(files):
531 532 '''Stat each file in files. Yield each stat, or None if a file
532 533 does not exist or has a type we don't care about.
533 534
534 535 Cluster and cache stat per directory to minimize number of OS stat calls.'''
535 536 dircache = {} # dirname -> filename -> status | None if file does not exist
536 537 getkind = stat.S_IFMT
537 538 for nf in files:
538 539 nf = normcase(nf)
539 540 dir, base = os.path.split(nf)
540 541 if not dir:
541 542 dir = b'.'
542 543 cache = dircache.get(dir, None)
543 544 if cache is None:
544 545 try:
545 546 dmap = dict(
546 547 [
547 548 (normcase(n), s)
548 549 for n, k, s in listdir(dir, True)
549 550 if getkind(s.st_mode) in _wantedkinds
550 551 ]
551 552 )
552 553 except OSError as err:
553 554 # Python >= 2.5 returns ENOENT and adds winerror field
554 555 # EINVAL is raised if dir is not a directory.
555 556 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
556 557 raise
557 558 dmap = {}
558 559 cache = dircache.setdefault(dir, dmap)
559 560 yield cache.get(base, None)
560 561
561 562
562 563 def username(uid=None):
563 564 """Return the name of the user with the given uid.
564 565
565 566 If uid is None, return the name of the current user."""
567 if not uid:
568 return pycompat.fsencode(getpass.getuser())
566 569 return None
567 570
568 571
569 572 def groupname(gid=None):
570 573 """Return the name of the group with the given gid.
571 574
572 575 If gid is None, return the name of the current group."""
573 576 return None
574 577
575 578
576 579 def readlink(pathname):
577 580 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
578 581
579 582
580 583 def removedirs(name):
581 584 """special version of os.removedirs that does not remove symlinked
582 585 directories or junction points if they actually contain files"""
583 586 if listdir(name):
584 587 return
585 588 os.rmdir(name)
586 589 head, tail = os.path.split(name)
587 590 if not tail:
588 591 head, tail = os.path.split(head)
589 592 while head and tail:
590 593 try:
591 594 if listdir(head):
592 595 return
593 596 os.rmdir(head)
594 597 except (ValueError, OSError):
595 598 break
596 599 head, tail = os.path.split(head)
597 600
598 601
599 602 def rename(src, dst):
600 603 '''atomically rename file src to dst, replacing dst if it exists'''
601 604 try:
602 605 os.rename(src, dst)
603 606 except OSError as e:
604 607 if e.errno != errno.EEXIST:
605 608 raise
606 609 unlink(dst)
607 610 os.rename(src, dst)
608 611
609 612
610 613 def gethgcmd():
611 614 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
612 615
613 616
614 617 def groupmembers(name):
615 618 # Don't support groups on Windows for now
616 619 raise KeyError
617 620
618 621
619 622 def isexec(f):
620 623 return False
621 624
622 625
623 626 class cachestat(object):
624 627 def __init__(self, path):
625 628 pass
626 629
627 630 def cacheable(self):
628 631 return False
629 632
630 633
631 634 def lookupreg(key, valname=None, scope=None):
632 635 ''' Look up a key/value name in the Windows registry.
633 636
634 637 valname: value name. If unspecified, the default value for the key
635 638 is used.
636 639 scope: optionally specify scope for registry lookup, this can be
637 640 a sequence of scopes to look up in order. Default (CURRENT_USER,
638 641 LOCAL_MACHINE).
639 642 '''
640 643 if scope is None:
641 644 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
642 645 elif not isinstance(scope, (list, tuple)):
643 646 scope = (scope,)
644 647 for s in scope:
645 648 try:
646 649 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
647 650 name = valname and encoding.strfromlocal(valname) or valname
648 651 val = winreg.QueryValueEx(hkey, name)[0]
649 652 # never let a Unicode string escape into the wild
650 653 return encoding.unitolocal(val)
651 654 except EnvironmentError:
652 655 pass
653 656
654 657
655 658 expandglobs = True
656 659
657 660
658 661 def statislink(st):
659 662 '''check whether a stat result is a symlink'''
660 663 return False
661 664
662 665
663 666 def statisexec(st):
664 667 '''check whether a stat result is an executable file'''
665 668 return False
666 669
667 670
668 671 def poll(fds):
669 672 # see posix.py for description
670 673 raise NotImplementedError()
671 674
672 675
673 676 def readpipe(pipe):
674 677 """Read all available data from a pipe."""
675 678 chunks = []
676 679 while True:
677 680 size = win32.peekpipe(pipe)
678 681 if not size:
679 682 break
680 683
681 684 s = pipe.read(size)
682 685 if not s:
683 686 break
684 687 chunks.append(s)
685 688
686 689 return b''.join(chunks)
687 690
688 691
689 692 def bindunixsocket(sock, path):
690 693 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now