From 382c3d20ab779fe57719bf56cd88a64259c1a3ca Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 8 Feb 2022 13:55:07 +0200 Subject: Add read-only mode to depdb --- libbuild2/depdb.cxx | 135 ++++++++++++++++++++++++++++++++-------------------- libbuild2/depdb.hxx | 15 ++++-- libbuild2/depdb.ixx | 6 +-- 3 files changed, 98 insertions(+), 58 deletions(-) diff --git a/libbuild2/depdb.cxx b/libbuild2/depdb.cxx index c229f23..0dabeca 100644 --- a/libbuild2/depdb.cxx +++ b/libbuild2/depdb.cxx @@ -18,21 +18,37 @@ namespace build2 // Note that state::write with absent pos is interpreted as non-existent. // depdb_base:: - depdb_base (const path& p, state s, optional pos) - : state_ (s) + depdb_base (const path& p, bool ro, state s, optional pos) + : state_ (s), ro_ (ro) { - fdopen_mode om (fdopen_mode::out | fdopen_mode::binary); + if (s == state::write && ro) + { + new (&is_) ifdstream (); + buf_ = nullptr; // Shouldn't be needed. + return; + } + + fdopen_mode om (fdopen_mode::binary); ifdstream::iostate em (ifdstream::badbit); if (s == state::write) { + om |= fdopen_mode::out; + if (!pos) om |= fdopen_mode::create | fdopen_mode::exclusive; em |= ifdstream::failbit; } else - om |= fdopen_mode::in; // Both in & out so can switch from read to write. + { + om |= fdopen_mode::in; + + // Both in & out so can switch from read to write. + // + if (!ro) + om |= fdopen_mode::out; + } auto_fd fd; try @@ -80,8 +96,9 @@ namespace build2 } depdb:: - depdb (path_type&& p, timestamp mt) + depdb (path_type&& p, bool ro, timestamp mt) : depdb_base (p, + ro, mt != timestamp_nonexistent ? state::read : state::write), path (move (p)), mtime (mt != timestamp_nonexistent ? mt : timestamp_unknown) @@ -91,22 +108,25 @@ namespace build2 if (state_ == state::read) { string* l (read ()); - if (l == nullptr || *l != "1") - write ('1'); + if (l != nullptr && *l == "1") + return; } - else + + if (!ro) write ('1'); + else if (reading ()) + change (); } depdb:: - depdb (path_type p) - : depdb (move (p), build2::mtime (p)) + depdb (path_type p, bool ro) + : depdb (move (p), ro, build2::mtime (p)) { } depdb:: depdb (reopen_state rs) - : depdb_base (rs.path, state::write, rs.pos), + : depdb_base (rs.path, false, state::write, rs.pos), path (move (rs.path)), mtime (timestamp_unknown), touch (rs.mtime) @@ -118,51 +138,58 @@ namespace build2 { assert (state_ != state::write); - // Transfer the file descriptor from ifdstream to ofdstream. Note that the - // steps in this dance must be carefully ordered to make sure we don't - // call any destructors twice in the face of exceptions. - // - auto_fd fd (is_.release ()); - - // Consider this scenario: we are overwriting an old line (so it ends with - // a newline and the "end marker") but the operation failed half way - // through. Now we have the prefix from the new line, the suffix from the - // old, and everything looks valid. So what we need is to somehow - // invalidate the old content so that it can never combine with (partial) - // new content to form a valid line. One way to do that would be to - // truncate the file. - // - if (trunc) - try + if (ro_) { - fdtruncate (fd.get (), pos_); + buf_ = nullptr; } - catch (const io_error& e) + else { - fail << "unable to truncate " << path << ": " << e; - } + // Transfer the file descriptor from ifdstream to ofdstream. Note that + // the steps in this dance must be carefully ordered to make sure we + // don't call any destructors twice in the face of exceptions. + // + auto_fd fd (is_.release ()); + + // Consider this scenario: we are overwriting an old line (so it ends + // with a newline and the "end marker") but the operation failed half + // way through. Now we have the prefix from the new line, the suffix + // from the old, and everything looks valid. So what we need is to + // somehow invalidate the old content so that it can never combine with + // (partial) new content to form a valid line. One way to do that would + // be to truncate the file. + // + if (trunc) + try + { + fdtruncate (fd.get (), pos_); + } + catch (const io_error& e) + { + fail << "unable to truncate " << path << ": " << e; + } - // Note: the file descriptor position can be beyond the pos_ value due to - // the ifdstream buffering. That's why we need to seek to switch from - // reading to writing. - // - try - { - fdseek (fd.get (), pos_, fdseek_mode::set); - } - catch (const io_error& e) - { - fail << "unable to rewind " << path << ": " << e; - } + // Note: the file descriptor position can be beyond the pos_ value due + // to the ifdstream buffering. That's why we need to seek to switch from + // reading to writing. + // + try + { + fdseek (fd.get (), pos_, fdseek_mode::set); + } + catch (const io_error& e) + { + fail << "unable to rewind " << path << ": " << e; + } - // @@ Strictly speaking, ofdstream can throw which will leave us in a - // non-destructible state. Unlikely but possible. - // - is_.~ifdstream (); - new (&os_) ofdstream (move (fd), - ofdstream::badbit | ofdstream::failbit, - pos_); - buf_ = static_cast (os_.rdbuf ()); + // @@ Strictly speaking, ofdstream can throw which will leave us in a + // non-destructible state. Unlikely but possible. + // + is_.~ifdstream (); + new (&os_) ofdstream (move (fd), + ofdstream::badbit | ofdstream::failbit, + pos_); + buf_ = static_cast (os_.rdbuf ()); + } state_ = state::write; mtime = timestamp_unknown; @@ -304,6 +331,12 @@ namespace build2 void depdb:: close (bool mc) { + if (ro_) + { + is_.close (); + return; + } + // If we are at eof, then it means all lines are good, there is the "end // marker" at the end, and we don't need to do anything, except, maybe // touch the file. Otherwise, if we are still in the read mode, truncate diff --git a/libbuild2/depdb.hxx b/libbuild2/depdb.hxx index 55413bf..5855c3f 100644 --- a/libbuild2/depdb.hxx +++ b/libbuild2/depdb.hxx @@ -66,14 +66,15 @@ namespace build2 // enum class state {read, read_eof, write}; - depdb_base (const path&, state, optional pos = nullopt); + depdb_base (const path&, bool ro, state, optional pos = nullopt); ~depdb_base (); state state_; + bool ro_; union { - ifdstream is_; // read, read_eof + ifdstream is_; // read, read_eof, (ro && write) ofdstream os_; // write }; @@ -107,13 +108,19 @@ namespace build2 // has wrong format version, or is corrupt, then the database will be // immediately switched to writing. // + // If read_only is true, then don't actually make any modifications to the + // database file. In other words, the database is still nominally switched + // to writing but without any filesystem changes. Note that calling any + // write-only functions (write(), touch, etc) on such a database is + // illegal. + // // The failure commonly happens when the user tries to stash the target in // a non-existent subdirectory but forgets to add the corresponding fsdir{} // prerequisite. That's why the issued diagnostics may provide the // corresponding hint. // explicit - depdb (path_type); + depdb (path_type, bool read_only = false); struct reopen_state { @@ -304,7 +311,7 @@ namespace build2 depdb& operator= (const depdb&) = delete; private: - depdb (path_type&&, timestamp); + depdb (path_type&&, bool, timestamp); void change (bool truncate = true); diff --git a/libbuild2/depdb.ixx b/libbuild2/depdb.ixx index 819fadd..18b4351 100644 --- a/libbuild2/depdb.ixx +++ b/libbuild2/depdb.ixx @@ -8,7 +8,7 @@ namespace build2 inline depdb_base:: ~depdb_base () { - if (state_ != state::write) + if (state_ != state::write || ro_) is_.~ifdstream (); else os_.~ofdstream (); @@ -17,7 +17,7 @@ namespace build2 inline void depdb:: flush () { - if (state_ == state::write) + if (state_ == state::write && !ro_) try { os_.flush (); @@ -37,7 +37,7 @@ namespace build2 inline void depdb:: check_mtime (const path_type& t, timestamp e) { - if (state_ == state::write && mtime_check ()) + if (state_ == state::write && !ro_ && mtime_check ()) check_mtime_ (t, e); } -- cgit v1.1