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