##// END OF EJS Templates
cygwin: define normcase spec and fallback...
Siddharth Agarwal -
r24596:75ea27f1 default
parent child Browse files
Show More
@@ -1,615 +1,618
1 1 # posix.py - Posix 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 i18n import _
9 9 import encoding
10 10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
11 11 import fcntl, re
12 12
13 13 posixfile = open
14 14 normpath = os.path.normpath
15 15 samestat = os.path.samestat
16 16 oslink = os.link
17 17 unlink = os.unlink
18 18 rename = os.rename
19 19 expandglobs = False
20 20
21 21 umask = os.umask(0)
22 22 os.umask(umask)
23 23
24 24 def split(p):
25 25 '''Same as posixpath.split, but faster
26 26
27 27 >>> import posixpath
28 28 >>> for f in ['/absolute/path/to/file',
29 29 ... 'relative/path/to/file',
30 30 ... 'file_alone',
31 31 ... 'path/to/directory/',
32 32 ... '/multiple/path//separators',
33 33 ... '/file_at_root',
34 34 ... '///multiple_leading_separators_at_root',
35 35 ... '']:
36 36 ... assert split(f) == posixpath.split(f), f
37 37 '''
38 38 ht = p.rsplit('/', 1)
39 39 if len(ht) == 1:
40 40 return '', p
41 41 nh = ht[0].rstrip('/')
42 42 if nh:
43 43 return nh, ht[1]
44 44 return ht[0] + '/', ht[1]
45 45
46 46 def openhardlinks():
47 47 '''return true if it is safe to hold open file handles to hardlinks'''
48 48 return True
49 49
50 50 def nlinks(name):
51 51 '''return number of hardlinks for the given file'''
52 52 return os.lstat(name).st_nlink
53 53
54 54 def parsepatchoutput(output_line):
55 55 """parses the output produced by patch and returns the filename"""
56 56 pf = output_line[14:]
57 57 if os.sys.platform == 'OpenVMS':
58 58 if pf[0] == '`':
59 59 pf = pf[1:-1] # Remove the quotes
60 60 else:
61 61 if pf.startswith("'") and pf.endswith("'") and " " in pf:
62 62 pf = pf[1:-1] # Remove the quotes
63 63 return pf
64 64
65 65 def sshargs(sshcmd, host, user, port):
66 66 '''Build argument list for ssh'''
67 67 args = user and ("%s@%s" % (user, host)) or host
68 68 return port and ("%s -p %s" % (args, port)) or args
69 69
70 70 def isexec(f):
71 71 """check whether a file is executable"""
72 72 return (os.lstat(f).st_mode & 0100 != 0)
73 73
74 74 def setflags(f, l, x):
75 75 s = os.lstat(f).st_mode
76 76 if l:
77 77 if not stat.S_ISLNK(s):
78 78 # switch file to link
79 79 fp = open(f)
80 80 data = fp.read()
81 81 fp.close()
82 82 os.unlink(f)
83 83 try:
84 84 os.symlink(data, f)
85 85 except OSError:
86 86 # failed to make a link, rewrite file
87 87 fp = open(f, "w")
88 88 fp.write(data)
89 89 fp.close()
90 90 # no chmod needed at this point
91 91 return
92 92 if stat.S_ISLNK(s):
93 93 # switch link to file
94 94 data = os.readlink(f)
95 95 os.unlink(f)
96 96 fp = open(f, "w")
97 97 fp.write(data)
98 98 fp.close()
99 99 s = 0666 & ~umask # avoid restatting for chmod
100 100
101 101 sx = s & 0100
102 102 if x and not sx:
103 103 # Turn on +x for every +r bit when making a file executable
104 104 # and obey umask.
105 105 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
106 106 elif not x and sx:
107 107 # Turn off all +x bits
108 108 os.chmod(f, s & 0666)
109 109
110 110 def copymode(src, dst, mode=None):
111 111 '''Copy the file mode from the file at path src to dst.
112 112 If src doesn't exist, we're using mode instead. If mode is None, we're
113 113 using umask.'''
114 114 try:
115 115 st_mode = os.lstat(src).st_mode & 0777
116 116 except OSError, inst:
117 117 if inst.errno != errno.ENOENT:
118 118 raise
119 119 st_mode = mode
120 120 if st_mode is None:
121 121 st_mode = ~umask
122 122 st_mode &= 0666
123 123 os.chmod(dst, st_mode)
124 124
125 125 def checkexec(path):
126 126 """
127 127 Check whether the given path is on a filesystem with UNIX-like exec flags
128 128
129 129 Requires a directory (like /foo/.hg)
130 130 """
131 131
132 132 # VFAT on some Linux versions can flip mode but it doesn't persist
133 133 # a FS remount. Frequently we can detect it if files are created
134 134 # with exec bit on.
135 135
136 136 try:
137 137 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
138 138 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
139 139 try:
140 140 os.close(fh)
141 141 m = os.stat(fn).st_mode & 0777
142 142 new_file_has_exec = m & EXECFLAGS
143 143 os.chmod(fn, m ^ EXECFLAGS)
144 144 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
145 145 finally:
146 146 os.unlink(fn)
147 147 except (IOError, OSError):
148 148 # we don't care, the user probably won't be able to commit anyway
149 149 return False
150 150 return not (new_file_has_exec or exec_flags_cannot_flip)
151 151
152 152 def checklink(path):
153 153 """check whether the given path is on a symlink-capable filesystem"""
154 154 # mktemp is not racy because symlink creation will fail if the
155 155 # file already exists
156 156 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
157 157 try:
158 158 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
159 159 try:
160 160 os.symlink(os.path.basename(fd.name), name)
161 161 os.unlink(name)
162 162 return True
163 163 finally:
164 164 fd.close()
165 165 except AttributeError:
166 166 return False
167 167 except OSError, inst:
168 168 # sshfs might report failure while successfully creating the link
169 169 if inst[0] == errno.EIO and os.path.exists(name):
170 170 os.unlink(name)
171 171 return False
172 172
173 173 def checkosfilename(path):
174 174 '''Check that the base-relative path is a valid filename on this platform.
175 175 Returns None if the path is ok, or a UI string describing the problem.'''
176 176 pass # on posix platforms, every path is ok
177 177
178 178 def setbinary(fd):
179 179 pass
180 180
181 181 def pconvert(path):
182 182 return path
183 183
184 184 def localpath(path):
185 185 return path
186 186
187 187 def samefile(fpath1, fpath2):
188 188 """Returns whether path1 and path2 refer to the same file. This is only
189 189 guaranteed to work for files, not directories."""
190 190 return os.path.samefile(fpath1, fpath2)
191 191
192 192 def samedevice(fpath1, fpath2):
193 193 """Returns whether fpath1 and fpath2 are on the same device. This is only
194 194 guaranteed to work for files, not directories."""
195 195 st1 = os.lstat(fpath1)
196 196 st2 = os.lstat(fpath2)
197 197 return st1.st_dev == st2.st_dev
198 198
199 199 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
200 200 def normcase(path):
201 201 return path.lower()
202 202
203 203 # what normcase does to ASCII strings
204 204 normcasespec = encoding.normcasespecs.lower
205 205 # fallback normcase function for non-ASCII strings
206 206 normcasefallback = normcase
207 207
208 208 if sys.platform == 'darwin':
209 209
210 210 def normcase(path):
211 211 '''
212 212 Normalize a filename for OS X-compatible comparison:
213 213 - escape-encode invalid characters
214 214 - decompose to NFD
215 215 - lowercase
216 216 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
217 217
218 218 >>> normcase('UPPER')
219 219 'upper'
220 220 >>> normcase('Caf\xc3\xa9')
221 221 'cafe\\xcc\\x81'
222 222 >>> normcase('\xc3\x89')
223 223 'e\\xcc\\x81'
224 224 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
225 225 '%b8%ca%c3\\xca\\xbe%c8.jpg'
226 226 '''
227 227
228 228 try:
229 229 return encoding.asciilower(path) # exception for non-ASCII
230 230 except UnicodeDecodeError:
231 231 return normcasefallback(path)
232 232
233 233 normcasespec = encoding.normcasespecs.lower
234 234
235 235 def normcasefallback(path):
236 236 try:
237 237 u = path.decode('utf-8')
238 238 except UnicodeDecodeError:
239 239 # OS X percent-encodes any bytes that aren't valid utf-8
240 240 s = ''
241 241 g = ''
242 242 l = 0
243 243 for c in path:
244 244 o = ord(c)
245 245 if l and o < 128 or o >= 192:
246 246 # we want a continuation byte, but didn't get one
247 247 s += ''.join(["%%%02X" % ord(x) for x in g])
248 248 g = ''
249 249 l = 0
250 250 if l == 0 and o < 128:
251 251 # ascii
252 252 s += c
253 253 elif l == 0 and 194 <= o < 245:
254 254 # valid leading bytes
255 255 if o < 224:
256 256 l = 1
257 257 elif o < 240:
258 258 l = 2
259 259 else:
260 260 l = 3
261 261 g = c
262 262 elif l > 0 and 128 <= o < 192:
263 263 # valid continuations
264 264 g += c
265 265 l -= 1
266 266 if not l:
267 267 s += g
268 268 g = ''
269 269 else:
270 270 # invalid
271 271 s += "%%%02X" % o
272 272
273 273 # any remaining partial characters
274 274 s += ''.join(["%%%02X" % ord(x) for x in g])
275 275 u = s.decode('utf-8')
276 276
277 277 # Decompose then lowercase (HFS+ technote specifies lower)
278 278 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
279 279 # drop HFS+ ignored characters
280 280 return encoding.hfsignoreclean(enc)
281 281
282 282 if sys.platform == 'cygwin':
283 283 # workaround for cygwin, in which mount point part of path is
284 284 # treated as case sensitive, even though underlying NTFS is case
285 285 # insensitive.
286 286
287 287 # default mount points
288 288 cygwinmountpoints = sorted([
289 289 "/usr/bin",
290 290 "/usr/lib",
291 291 "/cygdrive",
292 292 ], reverse=True)
293 293
294 294 # use upper-ing as normcase as same as NTFS workaround
295 295 def normcase(path):
296 296 pathlen = len(path)
297 297 if (pathlen == 0) or (path[0] != os.sep):
298 298 # treat as relative
299 299 return encoding.upper(path)
300 300
301 301 # to preserve case of mountpoint part
302 302 for mp in cygwinmountpoints:
303 303 if not path.startswith(mp):
304 304 continue
305 305
306 306 mplen = len(mp)
307 307 if mplen == pathlen: # mount point itself
308 308 return mp
309 309 if path[mplen] == os.sep:
310 310 return mp + encoding.upper(path[mplen:])
311 311
312 312 return encoding.upper(path)
313 313
314 normcasespec = encoding.normcasespecs.other
315 normcasefallback = normcase
316
314 317 # Cygwin translates native ACLs to POSIX permissions,
315 318 # but these translations are not supported by native
316 319 # tools, so the exec bit tends to be set erroneously.
317 320 # Therefore, disable executable bit access on Cygwin.
318 321 def checkexec(path):
319 322 return False
320 323
321 324 # Similarly, Cygwin's symlink emulation is likely to create
322 325 # problems when Mercurial is used from both Cygwin and native
323 326 # Windows, with other native tools, or on shared volumes
324 327 def checklink(path):
325 328 return False
326 329
327 330 _needsshellquote = None
328 331 def shellquote(s):
329 332 if os.sys.platform == 'OpenVMS':
330 333 return '"%s"' % s
331 334 global _needsshellquote
332 335 if _needsshellquote is None:
333 336 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
334 337 if s and not _needsshellquote(s):
335 338 # "s" shouldn't have to be quoted
336 339 return s
337 340 else:
338 341 return "'%s'" % s.replace("'", "'\\''")
339 342
340 343 def quotecommand(cmd):
341 344 return cmd
342 345
343 346 def popen(command, mode='r'):
344 347 return os.popen(command, mode)
345 348
346 349 def testpid(pid):
347 350 '''return False if pid dead, True if running or not sure'''
348 351 if os.sys.platform == 'OpenVMS':
349 352 return True
350 353 try:
351 354 os.kill(pid, 0)
352 355 return True
353 356 except OSError, inst:
354 357 return inst.errno != errno.ESRCH
355 358
356 359 def explainexit(code):
357 360 """return a 2-tuple (desc, code) describing a subprocess status
358 361 (codes from kill are negative - not os.system/wait encoding)"""
359 362 if code >= 0:
360 363 return _("exited with status %d") % code, code
361 364 return _("killed by signal %d") % -code, -code
362 365
363 366 def isowner(st):
364 367 """Return True if the stat object st is from the current user."""
365 368 return st.st_uid == os.getuid()
366 369
367 370 def findexe(command):
368 371 '''Find executable for command searching like which does.
369 372 If command is a basename then PATH is searched for command.
370 373 PATH isn't searched if command is an absolute or relative path.
371 374 If command isn't found None is returned.'''
372 375 if sys.platform == 'OpenVMS':
373 376 return command
374 377
375 378 def findexisting(executable):
376 379 'Will return executable if existing file'
377 380 if os.path.isfile(executable) and os.access(executable, os.X_OK):
378 381 return executable
379 382 return None
380 383
381 384 if os.sep in command:
382 385 return findexisting(command)
383 386
384 387 if sys.platform == 'plan9':
385 388 return findexisting(os.path.join('/bin', command))
386 389
387 390 for path in os.environ.get('PATH', '').split(os.pathsep):
388 391 executable = findexisting(os.path.join(path, command))
389 392 if executable is not None:
390 393 return executable
391 394 return None
392 395
393 396 def setsignalhandler():
394 397 pass
395 398
396 399 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
397 400
398 401 def statfiles(files):
399 402 '''Stat each file in files. Yield each stat, or None if a file does not
400 403 exist or has a type we don't care about.'''
401 404 lstat = os.lstat
402 405 getkind = stat.S_IFMT
403 406 for nf in files:
404 407 try:
405 408 st = lstat(nf)
406 409 if getkind(st.st_mode) not in _wantedkinds:
407 410 st = None
408 411 except OSError, err:
409 412 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
410 413 raise
411 414 st = None
412 415 yield st
413 416
414 417 def getuser():
415 418 '''return name of current user'''
416 419 return getpass.getuser()
417 420
418 421 def username(uid=None):
419 422 """Return the name of the user with the given uid.
420 423
421 424 If uid is None, return the name of the current user."""
422 425
423 426 if uid is None:
424 427 uid = os.getuid()
425 428 try:
426 429 return pwd.getpwuid(uid)[0]
427 430 except KeyError:
428 431 return str(uid)
429 432
430 433 def groupname(gid=None):
431 434 """Return the name of the group with the given gid.
432 435
433 436 If gid is None, return the name of the current group."""
434 437
435 438 if gid is None:
436 439 gid = os.getgid()
437 440 try:
438 441 return grp.getgrgid(gid)[0]
439 442 except KeyError:
440 443 return str(gid)
441 444
442 445 def groupmembers(name):
443 446 """Return the list of members of the group with the given
444 447 name, KeyError if the group does not exist.
445 448 """
446 449 return list(grp.getgrnam(name).gr_mem)
447 450
448 451 def spawndetached(args):
449 452 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
450 453 args[0], args)
451 454
452 455 def gethgcmd():
453 456 return sys.argv[:1]
454 457
455 458 def termwidth():
456 459 try:
457 460 import termios, array
458 461 for dev in (sys.stderr, sys.stdout, sys.stdin):
459 462 try:
460 463 try:
461 464 fd = dev.fileno()
462 465 except AttributeError:
463 466 continue
464 467 if not os.isatty(fd):
465 468 continue
466 469 try:
467 470 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
468 471 width = array.array('h', arri)[1]
469 472 if width > 0:
470 473 return width
471 474 except AttributeError:
472 475 pass
473 476 except ValueError:
474 477 pass
475 478 except IOError, e:
476 479 if e[0] == errno.EINVAL:
477 480 pass
478 481 else:
479 482 raise
480 483 except ImportError:
481 484 pass
482 485 return 80
483 486
484 487 def makedir(path, notindexed):
485 488 os.mkdir(path)
486 489
487 490 def unlinkpath(f, ignoremissing=False):
488 491 """unlink and remove the directory if it is empty"""
489 492 try:
490 493 os.unlink(f)
491 494 except OSError, e:
492 495 if not (ignoremissing and e.errno == errno.ENOENT):
493 496 raise
494 497 # try removing directories that might now be empty
495 498 try:
496 499 os.removedirs(os.path.dirname(f))
497 500 except OSError:
498 501 pass
499 502
500 503 def lookupreg(key, name=None, scope=None):
501 504 return None
502 505
503 506 def hidewindow():
504 507 """Hide current shell window.
505 508
506 509 Used to hide the window opened when starting asynchronous
507 510 child process under Windows, unneeded on other systems.
508 511 """
509 512 pass
510 513
511 514 class cachestat(object):
512 515 def __init__(self, path):
513 516 self.stat = os.stat(path)
514 517
515 518 def cacheable(self):
516 519 return bool(self.stat.st_ino)
517 520
518 521 __hash__ = object.__hash__
519 522
520 523 def __eq__(self, other):
521 524 try:
522 525 # Only dev, ino, size, mtime and atime are likely to change. Out
523 526 # of these, we shouldn't compare atime but should compare the
524 527 # rest. However, one of the other fields changing indicates
525 528 # something fishy going on, so return False if anything but atime
526 529 # changes.
527 530 return (self.stat.st_mode == other.stat.st_mode and
528 531 self.stat.st_ino == other.stat.st_ino and
529 532 self.stat.st_dev == other.stat.st_dev and
530 533 self.stat.st_nlink == other.stat.st_nlink and
531 534 self.stat.st_uid == other.stat.st_uid and
532 535 self.stat.st_gid == other.stat.st_gid and
533 536 self.stat.st_size == other.stat.st_size and
534 537 self.stat.st_mtime == other.stat.st_mtime and
535 538 self.stat.st_ctime == other.stat.st_ctime)
536 539 except AttributeError:
537 540 return False
538 541
539 542 def __ne__(self, other):
540 543 return not self == other
541 544
542 545 def executablepath():
543 546 return None # available on Windows only
544 547
545 548 class unixdomainserver(socket.socket):
546 549 def __init__(self, join, subsystem):
547 550 '''Create a unix domain socket with the given prefix.'''
548 551 super(unixdomainserver, self).__init__(socket.AF_UNIX)
549 552 sockname = subsystem + '.sock'
550 553 self.realpath = self.path = join(sockname)
551 554 if os.path.islink(self.path):
552 555 if os.path.exists(self.path):
553 556 self.realpath = os.readlink(self.path)
554 557 else:
555 558 os.unlink(self.path)
556 559 try:
557 560 self.bind(self.realpath)
558 561 except socket.error, err:
559 562 if err.args[0] == 'AF_UNIX path too long':
560 563 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
561 564 self.realpath = os.path.join(tmpdir, sockname)
562 565 try:
563 566 self.bind(self.realpath)
564 567 os.symlink(self.realpath, self.path)
565 568 except (OSError, socket.error):
566 569 self.cleanup()
567 570 raise
568 571 else:
569 572 raise
570 573 self.listen(5)
571 574
572 575 def cleanup(self):
573 576 def okayifmissing(f, path):
574 577 try:
575 578 f(path)
576 579 except OSError, err:
577 580 if err.errno != errno.ENOENT:
578 581 raise
579 582
580 583 okayifmissing(os.unlink, self.path)
581 584 if self.realpath != self.path:
582 585 okayifmissing(os.unlink, self.realpath)
583 586 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
584 587
585 588 def statislink(st):
586 589 '''check whether a stat result is a symlink'''
587 590 return st and stat.S_ISLNK(st.st_mode)
588 591
589 592 def statisexec(st):
590 593 '''check whether a stat result is an executable file'''
591 594 return st and (st.st_mode & 0100 != 0)
592 595
593 596 def readpipe(pipe):
594 597 """Read all available data from a pipe."""
595 598 # We can't fstat() a pipe because Linux will always report 0.
596 599 # So, we set the pipe to non-blocking mode and read everything
597 600 # that's available.
598 601 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
599 602 flags |= os.O_NONBLOCK
600 603 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
601 604
602 605 try:
603 606 chunks = []
604 607 while True:
605 608 try:
606 609 s = pipe.read()
607 610 if not s:
608 611 break
609 612 chunks.append(s)
610 613 except IOError:
611 614 break
612 615
613 616 return ''.join(chunks)
614 617 finally:
615 618 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
General Comments 0
You need to be logged in to leave comments. Login now