From: "堀川 久" Date: 2004-02-08T21:08:24+09:00 Subject: [ruby-list:39102] pstoreのバックアップファイルについて こんにちは。 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/