こんにちは。

pstoreは,ファイルを書き換えるときにバックアップファイルを作っています
が,タイミングによっては安全ではありません。

ファイルを開くときには、まず"rb+"で開こうとし,失敗したときに"wb+"で開
きなおしていますが,タイミングによってはファイルが切り詰められてしまい
ます。

バックアップファイルが存在するときに,
  ・元ファイルが正しくてバックアップファイルが不正
  ・バックアップファイルが正しくて元ファイルが不正

の両方の可能性があるために,どちらも信頼することができません。というわ
けで,パッチを書きました。

.tmp -> .new -> 元ファイルという順でファイルを変更します。

--- pstore.rb.orig	2004-02-08 20:30:25.200663384 +0900
+++ pstore.rb	2004-02-08 21:01:29.396262576 +0900
@@ -35,7 +35,11 @@
   def in_transaction
     raise PStore::Error, "not in transaction" unless @transaction
   end
-  private :in_transaction
+  def in_transaction_wr()
+    in_transaction()
+    raise PStore::Error, "in read-only transaction" if @rdonly
+  end
+  private :in_transaction, :in_transaction_wr
 
   def [](name)
     in_transaction
@@ -52,11 +56,11 @@
     self[name]
   end
   def []=(name, value)
-    in_transaction
+    in_transaction_wr()
     @table[name] = value
   end
   def delete(name)
-    in_transaction
+    in_transaction_wr()
     @table.delete name
   end
 
@@ -86,27 +90,26 @@
   def transaction(read_only=false)
     raise PStore::Error, "nested transaction" if @transaction
     begin
+      @rdonly = read_only
+      @abort = false
       @transaction = true
       value = nil
-      backup = @filename+"~"
-      begin
-	file = File::open(@filename, read_only ? "rb" : "rb+")
-	orig = true
-      rescue Errno::ENOENT
-	raise if read_only
-	file = File::open(@filename, "wb+")
-      end
-      file.flock(read_only ? File::LOCK_SH : File::LOCK_EX)
-      if read_only
-	@table = Marshal::load(file)
-      elsif orig and (content = file.read) != ""
+      file = File.open(@filename, File::RDWR | File::CREAT)
+      file.flock(File::LOCK_EX)
+      new_file = @filename + ".new"
+      commit_new() if FileTest.exist?(new_file)
+
+      if (content = file.read) != ""
 	@table = Marshal::load(content)
-	size = content.size
-	md5 = Digest::MD5.digest(content)
-	content = nil		# unreference huge data
+        if !read_only
+          size = content.size
+          md5 = Digest::MD5.digest(content)
+        end
       else
 	@table = {}
       end
+      content = nil		# unreference huge data
+
       begin
 	catch(:pstore_abort_transaction) do
 	  value = yield(self)
@@ -116,24 +119,17 @@
 	raise
       ensure
 	if !read_only and !@abort
-	  file.rewind
+          tmp_file = @filename + ".tmp"
 	  content = Marshal::dump(@table)
 	  if !md5 || size != content.size || md5 != Digest::MD5.digest(content)
-	    File::copy @filename, backup
-	    begin
-	      file.write(content)
-	      file.truncate(file.pos)
-	      content = nil		# unreference huge data
-	    rescue
-	      File::rename backup, @filename if File::exist?(backup)
-	      raise
-	    end
-	  end
-	end
-	if @abort and !orig
-	  File.unlink(@filename)
+            File.open(tmp_file, "w") {|t|
+              t.write(content)
+            }
+            File.rename(tmp_file, new_file)
+            commit_new()
+          end
+          content = nil		# unreference huge data
 	end
-	@abort = false
       end
     ensure
       @table = nil
@@ -142,6 +138,15 @@
     end
     value
   end
+
+  private
+  def commit_new()
+    new_file = @filename + ".new"
+    if !File.copy(new_file, @filename)
+      raise IOError
+    end
+    File.unlink(new_file)
+  end
 end
 
 if __FILE__ == $0

-- 
HORIKAWA Hisashi (in Kanji: 堀川 久)
Netsphere Laboratories  http://www.nslabs.jp/