##// END OF EJS Templates
fix file handling bugs on windows....
Vadim Gelfer -
r2176:9b42304d default
parent child Browse files
Show More
@@ -6,7 +6,7 b''
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import *
8 from demandload import *
9 demandload(globals(), "cStringIO changelog errno manifest os tempfile")
9 demandload(globals(), "cStringIO changelog errno manifest os tempfile util")
10
10
11 # writes to metadata files are ordered. reads: changelog, manifest,
11 # writes to metadata files are ordered. reads: changelog, manifest,
12 # normal files. writes: normal files, manifest, changelog.
12 # normal files. writes: normal files, manifest, changelog.
@@ -36,19 +36,21 b' class appendfile(object):'
36 def __init__(self, fp, tmpname):
36 def __init__(self, fp, tmpname):
37 if tmpname:
37 if tmpname:
38 self.tmpname = tmpname
38 self.tmpname = tmpname
39 self.tmpfp = open(self.tmpname, 'ab+')
39 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
40 else:
40 else:
41 fd, self.tmpname = tempfile.mkstemp()
41 fd, self.tmpname = tempfile.mkstemp()
42 self.tmpfp = os.fdopen(fd, 'ab+')
42 os.close(fd)
43 self.tmpfp = util.posixfile(self.tmpname, 'ab+')
43 self.realfp = fp
44 self.realfp = fp
44 self.offset = fp.tell()
45 self.offset = fp.tell()
45 # real file is not written by anyone else. cache its size so
46 # real file is not written by anyone else. cache its size so
46 # seek and read can be fast.
47 # seek and read can be fast.
47 self.realsize = os.fstat(fp.fileno()).st_size
48 self.realsize = util.fstat(fp).st_size
49 self.name = fp.name
48
50
49 def end(self):
51 def end(self):
50 self.tmpfp.flush() # make sure the stat is correct
52 self.tmpfp.flush() # make sure the stat is correct
51 return self.realsize + os.fstat(self.tmpfp.fileno()).st_size
53 return self.realsize + util.fstat(self.tmpfp).st_size
52
54
53 def tell(self):
55 def tell(self):
54 return self.offset
56 return self.offset
@@ -160,7 +160,7 b' class bundlerepository(localrepo.localre'
160 def __init__(self, ui, path, bundlename):
160 def __init__(self, ui, path, bundlename):
161 localrepo.localrepository.__init__(self, ui, path)
161 localrepo.localrepository.__init__(self, ui, path)
162 f = open(bundlename, "rb")
162 f = open(bundlename, "rb")
163 s = os.fstat(f.fileno())
163 s = util.fstat(f)
164 self.bundlefile = f
164 self.bundlefile = f
165 header = self.bundlefile.read(6)
165 header = self.bundlefile.read(6)
166 if not header.startswith("HG"):
166 if not header.startswith("HG"):
@@ -14,7 +14,7 b' from node import *'
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import demandload
15 from demandload import demandload
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
17 demandload(globals(), "sha struct zlib")
17 demandload(globals(), "sha struct util zlib")
18
18
19 # revlog version strings
19 # revlog version strings
20 REVLOGV0 = 0
20 REVLOGV0 = 0
@@ -322,7 +322,7 b' class revlog(object):'
322 i = ""
322 i = ""
323 else:
323 else:
324 try:
324 try:
325 st = os.fstat(f.fileno())
325 st = util.fstat(f)
326 except AttributeError, inst:
326 except AttributeError, inst:
327 st = None
327 st = None
328 else:
328 else:
@@ -57,7 +57,7 b' class sshrepository(remoterepository):'
57
57
58 def readerr(self):
58 def readerr(self):
59 while 1:
59 while 1:
60 size = os.fstat(self.pipee.fileno())[stat.ST_SIZE]
60 size = util.fstat(self.pipee).st_size
61 if size == 0: break
61 if size == 0: break
62 l = self.pipee.readline()
62 l = self.pipee.readline()
63 if not l: break
63 if not l: break
@@ -406,8 +406,18 b' def rename(src, dst):'
406 """forcibly rename a file"""
406 """forcibly rename a file"""
407 try:
407 try:
408 os.rename(src, dst)
408 os.rename(src, dst)
409 except:
409 except OSError, err:
410 os.unlink(dst)
410 # on windows, rename to existing file is not allowed, so we
411 # must delete destination first. but if file is open, unlink
412 # schedules it for delete but does not delete it. rename
413 # happens immediately even for open files, so we create
414 # temporary file, delete it, rename destination to that name,
415 # then delete that. then rename is safe to do.
416 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
417 os.close(fd)
418 os.unlink(temp)
419 os.rename(dst, temp)
420 os.unlink(temp)
411 os.rename(src, dst)
421 os.rename(src, dst)
412
422
413 def unlink(f):
423 def unlink(f):
@@ -449,90 +459,13 b' def audit_path(path):'
449 or os.pardir in parts):
459 or os.pardir in parts):
450 raise Abort(_("path contains illegal component: %s\n") % path)
460 raise Abort(_("path contains illegal component: %s\n") % path)
451
461
452 def opener(base, audit=True):
453 """
454 return a function that opens files relative to base
455
456 this function is used to hide the details of COW semantics and
457 remote file access from higher level code.
458 """
459 p = base
460 audit_p = audit
461
462 def mktempcopy(name):
463 d, fn = os.path.split(name)
464 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
465 fp = os.fdopen(fd, "wb")
466 try:
467 fp.write(file(name, "rb").read())
468 except:
469 try: os.unlink(temp)
470 except: pass
471 raise
472 fp.close()
473 st = os.lstat(name)
474 os.chmod(temp, st.st_mode)
475 return temp
476
477 class atomictempfile(file):
478 """the file will only be copied when rename is called"""
479 def __init__(self, name, mode):
480 self.__name = name
481 self.temp = mktempcopy(name)
482 file.__init__(self, self.temp, mode)
483 def rename(self):
484 if not self.closed:
485 file.close(self)
486 rename(self.temp, self.__name)
487 def __del__(self):
488 if not self.closed:
489 try:
490 os.unlink(self.temp)
491 except: pass
492 file.close(self)
493
494 class atomicfile(atomictempfile):
495 """the file will only be copied on close"""
496 def __init__(self, name, mode):
497 atomictempfile.__init__(self, name, mode)
498 def close(self):
499 self.rename()
500 def __del__(self):
501 self.rename()
502
503 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
504 if audit_p:
505 audit_path(path)
506 f = os.path.join(p, path)
507
508 if not text:
509 mode += "b" # for that other OS
510
511 if mode[0] != "r":
512 try:
513 nlink = nlinks(f)
514 except OSError:
515 d = os.path.dirname(f)
516 if not os.path.isdir(d):
517 os.makedirs(d)
518 else:
519 if atomic:
520 return atomicfile(f, mode)
521 elif atomictemp:
522 return atomictempfile(f, mode)
523 if nlink > 1:
524 rename(mktempcopy(f), f)
525 return file(f, mode)
526
527 return o
528
529 def _makelock_file(info, pathname):
462 def _makelock_file(info, pathname):
530 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
463 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
531 os.write(ld, info)
464 os.write(ld, info)
532 os.close(ld)
465 os.close(ld)
533
466
534 def _readlock_file(pathname):
467 def _readlock_file(pathname):
535 return file(pathname).read()
468 return posixfile(pathname).read()
536
469
537 def nlinks(pathname):
470 def nlinks(pathname):
538 """Return number of hardlinks for the given file."""
471 """Return number of hardlinks for the given file."""
@@ -544,6 +477,15 b' else:'
544 def os_link(src, dst):
477 def os_link(src, dst):
545 raise OSError(0, _("Hardlinks not supported"))
478 raise OSError(0, _("Hardlinks not supported"))
546
479
480 def fstat(fp):
481 '''stat file object that may not have fileno method.'''
482 try:
483 return os.fstat(fp.fileno())
484 except AttributeError:
485 return os.stat(fp.name)
486
487 posixfile = file
488
547 # Platform specific variants
489 # Platform specific variants
548 if os.name == 'nt':
490 if os.name == 'nt':
549 demandload(globals(), "msvcrt")
491 demandload(globals(), "msvcrt")
@@ -722,6 +664,84 b' else:'
722 return _("stopped by signal %d") % val, val
664 return _("stopped by signal %d") % val, val
723 raise ValueError(_("invalid exit code"))
665 raise ValueError(_("invalid exit code"))
724
666
667 def opener(base, audit=True):
668 """
669 return a function that opens files relative to base
670
671 this function is used to hide the details of COW semantics and
672 remote file access from higher level code.
673 """
674 p = base
675 audit_p = audit
676
677 def mktempcopy(name):
678 d, fn = os.path.split(name)
679 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
680 os.close(fd)
681 fp = posixfile(temp, "wb")
682 try:
683 fp.write(posixfile(name, "rb").read())
684 except:
685 try: os.unlink(temp)
686 except: pass
687 raise
688 fp.close()
689 st = os.lstat(name)
690 os.chmod(temp, st.st_mode)
691 return temp
692
693 class atomictempfile(posixfile):
694 """the file will only be copied when rename is called"""
695 def __init__(self, name, mode):
696 self.__name = name
697 self.temp = mktempcopy(name)
698 posixfile.__init__(self, self.temp, mode)
699 def rename(self):
700 if not self.closed:
701 posixfile.close(self)
702 rename(self.temp, self.__name)
703 def __del__(self):
704 if not self.closed:
705 try:
706 os.unlink(self.temp)
707 except: pass
708 posixfile.close(self)
709
710 class atomicfile(atomictempfile):
711 """the file will only be copied on close"""
712 def __init__(self, name, mode):
713 atomictempfile.__init__(self, name, mode)
714 def close(self):
715 self.rename()
716 def __del__(self):
717 self.rename()
718
719 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
720 if audit_p:
721 audit_path(path)
722 f = os.path.join(p, path)
723
724 if not text:
725 mode += "b" # for that other OS
726
727 if mode[0] != "r":
728 try:
729 nlink = nlinks(f)
730 except OSError:
731 d = os.path.dirname(f)
732 if not os.path.isdir(d):
733 os.makedirs(d)
734 else:
735 if atomic:
736 return atomicfile(f, mode)
737 elif atomictemp:
738 return atomictempfile(f, mode)
739 if nlink > 1:
740 rename(mktempcopy(f), f)
741 return posixfile(f, mode)
742
743 return o
744
725 class chunkbuffer(object):
745 class chunkbuffer(object):
726 """Allow arbitrary sized chunks of data to be efficiently read from an
746 """Allow arbitrary sized chunks of data to be efficiently read from an
727 iterator over chunks of arbitrary size."""
747 iterator over chunks of arbitrary size."""
@@ -16,9 +16,9 b' import win32api'
16 from demandload import *
16 from demandload import *
17 from i18n import gettext as _
17 from i18n import gettext as _
18 demandload(globals(), 'errno os pywintypes win32con win32file win32process')
18 demandload(globals(), 'errno os pywintypes win32con win32file win32process')
19 demandload(globals(), 'winerror')
19 demandload(globals(), 'cStringIO winerror')
20
20
21 class WinError(OSError):
21 class WinError:
22 winerror_map = {
22 winerror_map = {
23 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
23 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
24 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
24 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
@@ -105,7 +105,7 b' class WinError(OSError):'
105 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
105 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
106 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
106 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
107 winerror.ERROR_PATH_BUSY: errno.EBUSY,
107 winerror.ERROR_PATH_BUSY: errno.EBUSY,
108 winerror.ERROR_PATH_NOT_FOUND: errno.ENOTDIR,
108 winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
109 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
109 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
110 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
110 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
111 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
111 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
@@ -129,6 +129,19 b' class WinError(OSError):'
129
129
130 def __init__(self, err):
130 def __init__(self, err):
131 self.win_errno, self.win_function, self.win_strerror = err
131 self.win_errno, self.win_function, self.win_strerror = err
132 if self.win_strerror.endswith('.'):
133 self.win_strerror = self.win_strerror[:-1]
134
135 class WinIOError(WinError, IOError):
136 def __init__(self, err, filename=None):
137 WinError.__init__(self, err)
138 IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
139 self.win_strerror)
140 self.filename = filename
141
142 class WinOSError(WinError, OSError):
143 def __init__(self, err):
144 WinError.__init__(self, err)
132 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
145 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
133 self.win_strerror)
146 self.win_strerror)
134
147
@@ -137,7 +150,7 b' def os_link(src, dst):'
137 try:
150 try:
138 win32file.CreateHardLink(dst, src)
151 win32file.CreateHardLink(dst, src)
139 except pywintypes.error, details:
152 except pywintypes.error, details:
140 raise WinError(details)
153 raise WinOSError(details)
141
154
142 def nlinks(pathname):
155 def nlinks(pathname):
143 """Return number of hardlinks for the given file."""
156 """Return number of hardlinks for the given file."""
@@ -169,3 +182,99 b' def system_rcpath_win32():'
169 proc = win32api.GetCurrentProcess()
182 proc = win32api.GetCurrentProcess()
170 filename = win32process.GetModuleFileNameEx(proc, 0)
183 filename = win32process.GetModuleFileNameEx(proc, 0)
171 return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
184 return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
185
186 class posixfile(object):
187 '''file object with posix-like semantics. on windows, normal
188 files can not be deleted or renamed if they are open. must open
189 with win32file.FILE_SHARE_DELETE. this flag does not exist on
190 windows <= nt.'''
191
192 # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
193 # but does not work at all. wrap win32 file api instead.
194
195 def __init__(self, name, mode='rb'):
196 access = 0
197 if 'r' in mode or '+' in mode:
198 access |= win32file.GENERIC_READ
199 if 'w' in mode or 'a' in mode:
200 access |= win32file.GENERIC_WRITE
201 if 'r' in mode:
202 creation = win32file.OPEN_EXISTING
203 elif 'a' in mode:
204 creation = win32file.OPEN_ALWAYS
205 else:
206 creation = win32file.CREATE_ALWAYS
207 try:
208 self.handle = win32file.CreateFile(name,
209 access,
210 win32file.FILE_SHARE_READ |
211 win32file.FILE_SHARE_WRITE |
212 win32file.FILE_SHARE_DELETE,
213 None,
214 creation,
215 win32file.FILE_ATTRIBUTE_NORMAL,
216 0)
217 except pywintypes.error, err:
218 raise WinIOError(err, name)
219 self.closed = False
220 self.name = name
221 self.mode = mode
222
223 def read(self, count=-1):
224 try:
225 cs = cStringIO.StringIO()
226 while count:
227 wincount = int(count)
228 if wincount == -1:
229 wincount = 1048576
230 val, data = win32file.ReadFile(self.handle, wincount)
231 if not data: break
232 cs.write(data)
233 if count != -1:
234 count -= len(data)
235 return cs.getvalue()
236 except pywintypes.error, err:
237 raise WinIOError(err)
238
239 def write(self, data):
240 try:
241 if 'a' in self.mode:
242 win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
243 nwrit = 0
244 while nwrit < len(data):
245 val, nwrit = win32file.WriteFile(self.handle, data)
246 data = data[nwrit:]
247 except pywintypes.error, err:
248 raise WinIOError(err)
249
250 def seek(self, pos, whence=0):
251 try:
252 win32file.SetFilePointer(self.handle, int(pos), whence)
253 except pywintypes.error, err:
254 raise WinIOError(err)
255
256 def tell(self):
257 try:
258 return win32file.SetFilePointer(self.handle, 0,
259 win32file.FILE_CURRENT)
260 except pywintypes.error, err:
261 raise WinIOError(err)
262
263 def close(self):
264 if not self.closed:
265 self.handle = None
266 self.closed = True
267
268 def flush(self):
269 try:
270 win32file.FlushFileBuffers(self.handle)
271 except pywintypes.error, err:
272 raise WinIOError(err)
273
274 def truncate(self, pos=0):
275 try:
276 win32file.SetFilePointer(self.handle, int(pos),
277 win32file.FILE_BEGIN)
278 win32file.SetEndOfFile(self.handle)
279 except pywintypes.error, err:
280 raise WinIOError(err)
General Comments 0
You need to be logged in to leave comments. Login now