1 # portage: Lock management code
2 # Copyright 2004 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 __all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
7 "hardlock_name", "hardlink_is_mine", "hardlink_lockfile", \
8 "unhardlink_lockfile", "hardlock_cleanup"]
13 from portage import os
14 from portage.exception import DirectoryNotFound, FileNotFound, \
15 InvalidData, TryAgain, OperationNotPermitted, PermissionDenied
16 from portage.data import portage_gid
17 from portage.output import EOutput
18 from portage.util import writemsg
19 from portage.localization import _
23 # Used by emerge in order to disable the "waiting for lock" message
24 # so that it doesn't interfere with the status display.
28 return lockfile(mydir,wantnewlockfile=1)
29 def unlockdir(mylock):
30 return unlockfile(mylock)
32 def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
33 waiting_msg=None, flags=0):
35 If wantnewlockfile is True then this creates a lockfile in the parent
36 directory as the file: '.' + basename + '.portage_lockfile'.
41 raise InvalidData(_("Empty path given"))
43 if isinstance(mypath, basestring) and mypath[-1] == '/':
46 if hasattr(mypath, 'fileno'):
47 mypath = mypath.fileno()
48 if isinstance(mypath, int):
53 base, tail = os.path.split(mypath)
54 lockfilename = os.path.join(base, "." + tail + ".portage_lockfile")
60 if isinstance(mypath, basestring):
61 if not os.path.exists(os.path.dirname(mypath)):
62 raise DirectoryNotFound(os.path.dirname(mypath))
63 preexisting = os.path.exists(lockfilename)
64 old_mask = os.umask(000)
67 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0660)
69 func_call = "open('%s')" % lockfilename
70 if e.errno == OperationNotPermitted.errno:
71 raise OperationNotPermitted(func_call)
72 elif e.errno == PermissionDenied.errno:
73 raise PermissionDenied(func_call)
79 if os.stat(lockfilename).st_gid != portage_gid:
80 os.chown(lockfilename, -1, portage_gid)
82 if e.errno in (errno.ENOENT, errno.ESTALE):
83 return lockfile(mypath,
84 wantnewlockfile=wantnewlockfile,
85 unlinkfile=unlinkfile, waiting_msg=waiting_msg,
88 writemsg(_("Cannot chown a lockfile. This could "
89 "cause inconvenience later.\n"))
94 elif isinstance(mypath, int):
98 raise ValueError(_("Unknown type passed in '%s': '%s'") % \
99 (type(mypath), mypath))
101 # try for a non-blocking lock, if it's held, throw a message
102 # we're waiting on lockfile and use a blocking attempt.
103 locking_method = fcntl.lockf
105 fcntl.lockf(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
107 if "errno" not in dir(e):
109 if e.errno in (errno.EACCES, errno.EAGAIN):
110 # resource temp unavailable; eg, someone beat us to the lock.
111 if flags & os.O_NONBLOCK:
112 raise TryAgain(mypath)
117 if waiting_msg is None:
118 if isinstance(mypath, int):
119 waiting_msg = _("waiting for lock on fd %i") % myfd
121 waiting_msg = _("waiting for lock on %s\n") % lockfilename
122 out.ebegin(waiting_msg)
123 # try for the exclusive lock now.
125 fcntl.lockf(myfd, fcntl.LOCK_EX)
126 except EnvironmentError as e:
130 elif e.errno == errno.ENOLCK:
131 # We're not allowed to lock on this FS.
134 if lockfilename == str(lockfilename):
137 if os.stat(lockfilename)[stat.ST_NLINK] == 1:
138 os.unlink(lockfilename)
141 link_success = hardlink_lockfile(lockfilename)
144 locking_method = None
150 if isinstance(lockfilename, basestring) and \
151 myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
152 # The file was deleted on us... Keep trying to make one...
154 writemsg(_("lockfile recurse\n"), 1)
155 lockfilename, myfd, unlinkfile, locking_method = lockfile(
156 mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
157 waiting_msg=waiting_msg, flags=flags)
159 writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
160 return (lockfilename,myfd,unlinkfile,locking_method)
162 def _fstat_nlink(fd):
164 @param fd: an open file descriptor
167 @return: the current number of hardlinks to the file
170 return os.fstat(fd).st_nlink
171 except EnvironmentError as e:
172 if e.errno in (errno.ENOENT, errno.ESTALE):
173 # Some filesystems such as CIFS return
174 # ENOENT which means st_nlink == 0.
178 def unlockfile(mytuple):
181 #XXX: Compatability hack.
182 if len(mytuple) == 3:
183 lockfilename,myfd,unlinkfile = mytuple
184 locking_method = fcntl.flock
185 elif len(mytuple) == 4:
186 lockfilename,myfd,unlinkfile,locking_method = mytuple
190 if(myfd == HARDLINK_FD):
191 unhardlink_lockfile(lockfilename)
194 # myfd may be None here due to myfd = mypath in lockfile()
195 if isinstance(lockfilename, basestring) and \
196 not os.path.exists(lockfilename):
197 writemsg(_("lockfile does not exist '%s'\n") % lockfilename,1)
204 myfd = os.open(lockfilename, os.O_WRONLY,0660)
206 locking_method(myfd,fcntl.LOCK_UN)
208 if isinstance(lockfilename, basestring):
210 raise IOError(_("Failed to unlock file '%s'\n") % lockfilename)
213 # This sleep call was added to allow other processes that are
214 # waiting for a lock to be able to grab it before it is deleted.
215 # lockfile() already accounts for this situation, however, and
216 # the sleep here adds more time than is saved overall, so am
217 # commenting until it is proved necessary.
220 locking_method(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
221 # We won the lock, so there isn't competition for it.
222 # We can safely delete the file.
223 writemsg(_("Got the lockfile...\n"), 1)
224 if _fstat_nlink(myfd) == 1:
225 os.unlink(lockfilename)
226 writemsg(_("Unlinked lockfile...\n"), 1)
227 locking_method(myfd,fcntl.LOCK_UN)
229 writemsg(_("lockfile does not exist '%s'\n") % lockfilename, 1)
232 except Exception as e:
233 writemsg(_("Failed to get lock... someone took it.\n"), 1)
234 writemsg(str(e)+"\n",1)
236 # why test lockfilename? because we may have been handed an
237 # fd originally, and the caller might not like having their
238 # open fd closed automatically on them.
239 if isinstance(lockfilename, basestring):
247 def hardlock_name(path):
248 return path+".hardlock-"+os.uname()[1]+"-"+str(os.getpid())
250 def hardlink_is_mine(link,lock):
252 return os.stat(link).st_nlink == 2
256 def hardlink_lockfile(lockfilename, max_wait=14400):
257 """Does the NFS, hardlink shuffle to ensure locking on the disk.
258 We create a PRIVATE lockfile, that is just a placeholder on the disk.
259 Then we HARDLINK the real lockfile to that private file.
260 If our file can 2 references, then we have the lock. :)
261 Otherwise we lather, rise, and repeat.
262 We default to a 4 hour timeout.
265 start_time = time.time()
266 myhardlock = hardlock_name(lockfilename)
267 reported_waiting = False
269 while(time.time() < (start_time + max_wait)):
270 # We only need it to exist.
271 myfd = os.open(myhardlock, os.O_CREAT|os.O_RDWR,0660)
274 if not os.path.exists(myhardlock):
276 _("Created lockfile is missing: %(filename)s") % \
277 {"filename" : myhardlock})
280 res = os.link(myhardlock, lockfilename)
284 if hardlink_is_mine(myhardlock, lockfilename):
287 writemsg("\n", noiselevel=-1)
291 writemsg(".", noiselevel=-1)
293 reported_waiting = True
294 from portage.const import PORTAGE_BIN_PATH
295 msg = _("\nWaiting on (hardlink) lockfile: (one '.' per 3 seconds)\n"
296 "%(bin_path)s/clean_locks can fix stuck locks.\n"
297 "Lockfile: %(lockfilename)s\n") % \
298 {"bin_path": PORTAGE_BIN_PATH, "lockfilename": lockfilename}
299 writemsg(msg, noiselevel=-1)
302 os.unlink(myhardlock)
305 def unhardlink_lockfile(lockfilename):
306 myhardlock = hardlock_name(lockfilename)
307 if hardlink_is_mine(myhardlock, lockfilename):
308 # Make sure not to touch lockfilename unless we really have a lock.
310 os.unlink(lockfilename)
314 os.unlink(myhardlock)
318 def hardlock_cleanup(path, remove_all_locks=False):
319 mypid = str(os.getpid())
320 myhost = os.uname()[1]
321 mydl = os.listdir(path)
328 if os.path.isfile(path+"/"+x):
329 parts = x.split(".hardlock-")
332 hostpid = parts[1].split("-")
333 host = "-".join(hostpid[:-1])
336 if filename not in mylist:
337 mylist[filename] = {}
338 if host not in mylist[filename]:
339 mylist[filename][host] = []
340 mylist[filename][host].append(pid)
345 results.append(_("Found %(count)s locks") % {"count":mycount})
348 if myhost in mylist[x] or remove_all_locks:
349 mylockname = hardlock_name(path+"/"+x)
350 if hardlink_is_mine(mylockname, path+"/"+x) or \
351 not os.path.exists(path+"/"+x) or \
354 for z in mylist[x][y]:
355 filename = path+"/"+x+".hardlock-"+y+"-"+z
356 if filename == mylockname:
359 # We're sweeping through, unlinking everyone's locks.
361 results.append(_("Unlinked: ") + filename)
365 os.unlink(path+"/"+x)
366 results.append(_("Unlinked: ") + path+"/"+x)
367 os.unlink(mylockname)
368 results.append(_("Unlinked: ") + mylockname)
373 os.unlink(mylockname)
374 results.append(_("Unlinked: ") + mylockname)