-----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"

Reply via email to