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