1 # portage: Lock management code
2 # Copyright 2004 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
7 import errno, os, stat, time, types
8 from portage.exception import InvalidData, DirectoryNotFound, FileNotFound
9 from portage.data import portage_gid
10 from portage.util import writemsg
11 from portage.localization import _
16 return lockfile(mydir,wantnewlockfile=1)
17 def unlockdir(mylock):
18 return unlockfile(mylock)
20 def lockfile(mypath, wantnewlockfile=0, unlinkfile=0, waiting_msg=None):
21 """Creates all dirs upto, the given dir. Creates a lockfile
22 for the given directory as the file: directoryname+'.portage_lockfile'."""
26 raise InvalidData, "Empty path given"
28 if type(mypath) == types.StringType and mypath[-1] == '/':
31 if type(mypath) == types.FileType:
32 mypath = mypath.fileno()
33 if type(mypath) == types.IntType:
38 base, tail = os.path.split(mypath)
39 lockfilename = os.path.join(base, "." + tail + ".portage_lockfile")
45 if type(mypath) == types.StringType:
46 if not os.path.exists(os.path.dirname(mypath)):
47 raise DirectoryNotFound, os.path.dirname(mypath)
48 if not os.path.exists(lockfilename):
49 old_mask=os.umask(000)
50 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR,0660)
52 if os.stat(lockfilename).st_gid != portage_gid:
53 os.chown(lockfilename,os.getuid(),portage_gid)
55 if e[0] == 2: # No such file or directory
56 return lockfile(mypath, wantnewlockfile=wantnewlockfile,
57 unlinkfile=unlinkfile, waiting_msg=waiting_msg)
59 writemsg("Cannot chown a lockfile. This could cause inconvenience later.\n");
62 myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR,0660)
64 elif type(mypath) == types.IntType:
68 raise ValueError, "Unknown type passed in '%s': '%s'" % (type(mypath),mypath)
70 # try for a non-blocking lock, if it's held, throw a message
71 # we're waiting on lockfile and use a blocking attempt.
72 locking_method = fcntl.lockf
74 fcntl.lockf(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
76 if "errno" not in dir(e):
78 if e.errno == errno.EAGAIN:
79 # resource temp unavailable; eg, someone beat us to the lock.
80 if waiting_msg is None:
81 if isinstance(mypath, int):
82 print "waiting for lock on fd %i" % myfd
84 print "waiting for lock on %s" % lockfilename
87 # try for the exclusive lock now.
88 fcntl.lockf(myfd,fcntl.LOCK_EX)
89 elif e.errno == errno.ENOLCK:
90 # We're not allowed to lock on this FS.
93 if lockfilename == str(lockfilename):
96 if os.stat(lockfilename)[stat.ST_NLINK] == 1:
97 os.unlink(lockfilename)
100 link_success = hardlink_lockfile(lockfilename)
103 locking_method = None
109 if type(lockfilename) == types.StringType and \
110 myfd != HARDLINK_FD and os.fstat(myfd).st_nlink == 0:
111 # The file was deleted on us... Keep trying to make one...
113 writemsg("lockfile recurse\n",1)
114 lockfilename, myfd, unlinkfile, locking_method = lockfile(
115 mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
116 waiting_msg=waiting_msg)
118 writemsg(str((lockfilename,myfd,unlinkfile))+"\n",1)
119 return (lockfilename,myfd,unlinkfile,locking_method)
121 def unlockfile(mytuple):
124 #XXX: Compatability hack.
125 if len(mytuple) == 3:
126 lockfilename,myfd,unlinkfile = mytuple
127 locking_method = fcntl.flock
128 elif len(mytuple) == 4:
129 lockfilename,myfd,unlinkfile,locking_method = mytuple
133 if(myfd == HARDLINK_FD):
134 unhardlink_lockfile(lockfilename)
137 # myfd may be None here due to myfd = mypath in lockfile()
138 if type(lockfilename) == types.StringType and not os.path.exists(lockfilename):
139 writemsg("lockfile does not exist '%s'\n" % lockfilename,1)
146 myfd = os.open(lockfilename, os.O_WRONLY,0660)
148 locking_method(myfd,fcntl.LOCK_UN)
150 if type(lockfilename) == types.StringType:
152 raise IOError, "Failed to unlock file '%s'\n" % lockfilename
155 # This sleep call was added to allow other processes that are
156 # waiting for a lock to be able to grab it before it is deleted.
157 # lockfile() already accounts for this situation, however, and
158 # the sleep here adds more time than is saved overall, so am
159 # commenting until it is proved necessary.
162 locking_method(myfd,fcntl.LOCK_EX|fcntl.LOCK_NB)
163 # We won the lock, so there isn't competition for it.
164 # We can safely delete the file.
165 writemsg("Got the lockfile...\n",1)
166 if os.fstat(myfd).st_nlink == 1:
167 os.unlink(lockfilename)
168 writemsg("Unlinked lockfile...\n",1)
169 locking_method(myfd,fcntl.LOCK_UN)
171 writemsg("lockfile does not exist '%s'\n" % lockfilename,1)
175 writemsg("Failed to get lock... someone took it.\n",1)
176 writemsg(str(e)+"\n",1)
178 # why test lockfilename? because we may have been handed an
179 # fd originally, and the caller might not like having their
180 # open fd closed automatically on them.
181 if type(lockfilename) == types.StringType:
189 def hardlock_name(path):
190 return path+".hardlock-"+os.uname()[1]+"-"+str(os.getpid())
192 def hardlink_is_mine(link,lock):
194 return os.stat(link).st_nlink == 2
198 def hardlink_lockfile(lockfilename, max_wait=14400):
199 """Does the NFS, hardlink shuffle to ensure locking on the disk.
200 We create a PRIVATE lockfile, that is just a placeholder on the disk.
201 Then we HARDLINK the real lockfile to that private file.
202 If our file can 2 references, then we have the lock. :)
203 Otherwise we lather, rise, and repeat.
204 We default to a 4 hour timeout.
207 start_time = time.time()
208 myhardlock = hardlock_name(lockfilename)
209 reported_waiting = False
211 while(time.time() < (start_time + max_wait)):
212 # We only need it to exist.
213 myfd = os.open(myhardlock, os.O_CREAT|os.O_RDWR,0660)
216 if not os.path.exists(myhardlock):
217 raise FileNotFound, _("Created lockfile is missing: %(filename)s") % {"filename":myhardlock}
220 res = os.link(myhardlock, lockfilename)
224 if hardlink_is_mine(myhardlock, lockfilename):
233 reported_waiting = True
234 from portage.const import PORTAGE_BIN_PATH
236 print "Waiting on (hardlink) lockfile: (one '.' per 3 seconds)"
237 print "This is a feature to prevent distfiles corruption."
238 print "%s/clean_locks can fix stuck locks." % PORTAGE_BIN_PATH
239 print "Lockfile: " + lockfilename
242 os.unlink(myhardlock)
245 def unhardlink_lockfile(lockfilename):
246 myhardlock = hardlock_name(lockfilename)
247 if hardlink_is_mine(myhardlock, lockfilename):
248 # Make sure not to touch lockfilename unless we really have a lock.
250 os.unlink(lockfilename)
254 os.unlink(myhardlock)
258 def hardlock_cleanup(path, remove_all_locks=False):
259 mypid = str(os.getpid())
260 myhost = os.uname()[1]
261 mydl = os.listdir(path)
268 if os.path.isfile(path+"/"+x):
269 parts = x.split(".hardlock-")
272 hostpid = parts[1].split("-")
273 host = "-".join(hostpid[:-1])
276 if not mylist.has_key(filename):
277 mylist[filename] = {}
278 if not mylist[filename].has_key(host):
279 mylist[filename][host] = []
280 mylist[filename][host].append(pid)
285 results.append("Found %(count)s locks" % {"count":mycount})
287 for x in mylist.keys():
288 if mylist[x].has_key(myhost) or remove_all_locks:
289 mylockname = hardlock_name(path+"/"+x)
290 if hardlink_is_mine(mylockname, path+"/"+x) or \
291 not os.path.exists(path+"/"+x) or \
293 for y in mylist[x].keys():
294 for z in mylist[x][y]:
295 filename = path+"/"+x+".hardlock-"+y+"-"+z
296 if filename == mylockname:
299 # We're sweeping through, unlinking everyone's locks.
301 results.append(_("Unlinked: ") + filename)
305 os.unlink(path+"/"+x)
306 results.append(_("Unlinked: ") + path+"/"+x)
307 os.unlink(mylockname)
308 results.append(_("Unlinked: ") + mylockname)
313 os.unlink(mylockname)
314 results.append(_("Unlinked: ") + mylockname)