-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hi all,
There is dangerous handling of world file updates throughout portage. The attached patch wraps all world file updates in a write_atomic() function which is designed to prevent interprocess interference and prevent corruption when an 'out of space' error occurs. Brian pointed out that portage_util.writedict() already does a similar routine, but it is designed exclusively to write a dictionary object. Zac -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.2 (GNU/Linux) iD8DBQFD1en2/ejvha5XGaMRAiWdAJ9z8qD4NAO5wj7kYnZOa8qSS1YTTACaAlFz uBy7olGkiAlYweTNv1iu0OY= =CJPG -----END PGP SIGNATURE-----
Index: portage-2.1_pre/pym/portage_util.py =================================================================== --- portage-2.1_pre.orig/pym/portage_util.py +++ portage-2.1_pre/pym/portage_util.py @@ -452,3 +452,31 @@ def unique_array(s): u.append(x) return u +def write_atomic(file_path, content): + """Write a file atomically via os.rename(). Atomic replacement prevents + interprocess interference and prevents corruption of the target + file when an 'out of space' error occurs.""" + mystat = os.stat(file_path) + dirname,basename = os.path.split(file_path) + tmp_path=None + fd=None + try: + import tempfile + fd, tmp_path = tempfile.mkstemp(prefix=basename, dir=dirname) + os.write(fd,content) + os.close(fd) + os.chown(tmp_path, mystat.st_uid, mystat.st_gid) + os.chmod(tmp_path, mystat.st_mode) + os.rename(tmp_path,file_path) + finally: + # Make sure we've cleaned up even if an exception is raised. + if fd: + try: + os.close(fd) + except OSError, oe: + pass + if tmp_path: + try: + os.unlink(tmp_path) + except OSError, oe: + pass Index: portage-2.1_pre/pym/portage.py =================================================================== --- portage-2.1_pre.orig/pym/portage.py +++ portage-2.1_pre/pym/portage.py @@ -5850,10 +5850,7 @@ class dblink: os.chown(pdir, 0, portage_gid) os.chmod(pdir, 02770) - myworld=open(self.myroot+WORLD_FILE,"w") - for x in newworldlist: - myworld.write(x+"\n") - myworld.close() + portage_util.write_atomic(os.path.join(self.myroot,WORLD_FILE),"\n".join(newworldlist)) #do original postrm if myebuildpath and os.path.exists(myebuildpath): @@ -6862,10 +6859,7 @@ def do_upgrade(mykey): if processed: #update our internal mtime since we processed all our directives. mtimedb["updates"][mykey]=os.stat(mykey)[stat.ST_MTIME] - myworld=open("/"+WORLD_FILE,"w") - for x in worldlist: - myworld.write(x+"\n") - myworld.close() + portage_util.write_atomic(WORLD_FILE,"\n".join(worldlist)) print "" def commit_mtimedb(): Index: portage-2.1_pre/bin/emaint =================================================================== --- portage-2.1_pre.orig/bin/emaint +++ portage-2.1_pre/bin/emaint @@ -6,7 +6,7 @@ from optparse import OptionParser, Optio import re -import os, portage, portage_const +import os, portage, portage_const, portage_util class WorldHandler(object): def name(): @@ -39,7 +39,7 @@ class WorldHandler(object): def fix(self): errors = [] try: - open(portage_const.WORLD_FILE, "w").write("\n".join(self.okay)) + portage_util.write_atomic(portage_const.WORLD_FILE,"\n".join(self.okay)) except OSError: errors.append(portage_const.WORLD_FILE + " could not be opened for writing") return errors Index: portage-2.1_pre/bin/regenworld =================================================================== --- portage-2.1_pre.orig/bin/regenworld +++ portage-2.1_pre/bin/regenworld @@ -6,7 +6,7 @@ import sys sys.path.insert(0, "/usr/lib/portage/pym") -import portage, string, re +import portage, portage_util, string, re __candidatematcher__ = re.compile("^[0-9]+: \\*\\*\\* emerge ") __noncandidatematcher__ = re.compile(" sync( |$)| clean( |$)| search( |$)|--oneshot| unmerge( |$)") @@ -88,6 +88,4 @@ for mykey in biglist: print "add to world:",myfavkey worldlist.append(myfavkey) -myfile=open(portage.WORLD_FILE, "w") -myfile.write(string.join(worldlist, '\n')+'\n') -myfile.close() +portage_util.write_atomic(portage.WORLD_FILE,"\n".join(worldlist)) Index: portage-2.1_pre/bin/emerge =================================================================== --- portage-2.1_pre.orig/bin/emerge +++ portage-2.1_pre/bin/emerge @@ -1904,7 +1904,7 @@ class depgraph: myfavdict[myfavkey]=myfavkey print ">>> Recording",myfavkey,"in \"world\" favorites file..." if not "--fetchonly" in myopts: - portage.writedict(myfavdict,portage.root+portage.WORLD_FILE,writekey=0) + portage_util.write_atomic(os.path.join(portage.root,portage.WORLD_FILE),"\n".join(myfavdict.values())) portage.mtimedb["resume"]["mergelist"]=mymergelist[:] @@ -2075,7 +2075,7 @@ class depgraph: myfavdict[myfavkey]=myfavkey print ">>> Recording",myfavkey,"in \"world\" favorites file..." emergelog(" === ("+str(mergecount)+" of "+str(len(mymergelist))+") Updating world file ("+x[pkgindex]+")") - portage.writedict(myfavdict,myroot+portage.WORLD_FILE,writekey=0) + portage_util.write_atomic(os.path.join(myroot,portage.WORLD_FILE),"\n".join(myfavdict.values())) if ("noclean" not in portage.features) and (x[0] != "binary"): short_msg = "emerge: ("+str(mergecount)+" of "+str(len(mymergelist))+") "+x[pkgindex]+" Clean Post"