diff options
Diffstat (limited to 'libbuild2/depdb.cxx')
-rw-r--r-- | libbuild2/depdb.cxx | 232 |
1 files changed, 167 insertions, 65 deletions
diff --git a/libbuild2/depdb.cxx b/libbuild2/depdb.cxx index 86fbece..0dabeca 100644 --- a/libbuild2/depdb.cxx +++ b/libbuild2/depdb.cxx @@ -15,22 +15,39 @@ using namespace butl; namespace build2 { + // Note that state::write with absent pos is interpreted as non-existent. + // depdb_base:: - depdb_base (const path& p, timestamp mt) + depdb_base (const path& p, bool ro, state s, optional<uint64_t> 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 (mt == timestamp_nonexistent) + if (s == state::write) { - state_ = state::write; - om |= fdopen_mode::create | fdopen_mode::exclusive; + om |= fdopen_mode::out; + + if (!pos) + om |= fdopen_mode::create | fdopen_mode::exclusive; + em |= ifdstream::failbit; } else { - state_ = state::read; om |= fdopen_mode::in; + + // Both in & out so can switch from read to write. + // + if (!ro) + om |= fdopen_mode::out; } auto_fd fd; @@ -40,10 +57,10 @@ namespace build2 } catch (const io_error&) { - bool c (state_ == state::write); + bool c (s == state::write && !pos); diag_record dr (fail); - dr << "unable to " << (c ? "create" : "open") << ' ' << p; + dr << "unable to " << (c ? "create " : "open ") << p; if (c) dr << info << "did you forget to add fsdir{} prerequisite for " @@ -52,6 +69,16 @@ namespace build2 dr << endf; } + if (pos) + try + { + fdseek (fd.get (), *pos, fdseek_mode::set); + } + catch (const io_error& e) + { + fail << "unable to rewind " << p << ": " << e; + } + // Open the corresponding stream. Note that if we throw after that, the // corresponding member will not be destroyed. This is the reason for the // depdb/base split. @@ -59,37 +86,50 @@ namespace build2 if (state_ == state::read) { new (&is_) ifdstream (move (fd), em); - buf_ = static_cast<fdbuf*> (is_.rdbuf ()); + buf_ = static_cast<fdstreambuf*> (is_.rdbuf ()); } else { - new (&os_) ofdstream (move (fd), em); - buf_ = static_cast<fdbuf*> (os_.rdbuf ()); + new (&os_) ofdstream (move (fd), em, pos ? *pos : 0); + buf_ = static_cast<fdstreambuf*> (os_.rdbuf ()); } } depdb:: - depdb (path_type&& p, timestamp mt) - : depdb_base (p, 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), - touch (false) + mtime (mt != timestamp_nonexistent ? mt : timestamp_unknown) { // Read/write the database format version. // 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, bool ro) + : depdb (move (p), ro, build2::mtime (p)) + { } depdb:: - depdb (path_type p) - : depdb (move (p), build2::mtime (p)) + depdb (reopen_state rs) + : depdb_base (rs.path, false, state::write, rs.pos), + path (move (rs.path)), + mtime (timestamp_unknown), + touch (rs.mtime) { } @@ -98,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<fdbuf*> (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<fdstreambuf*> (os_.rdbuf ()); + } state_ = state::write; mtime = timestamp_unknown; @@ -282,14 +329,24 @@ namespace build2 } void depdb:: - close () + 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 // the rest, and then add the "end marker" (we cannot have anything in the // write mode since we truncate in change()). // + // Note that we handle touch with timestamp_unknown specially by making a + // modification to the file (which happens naturally in the write mode) + // and letting the filesystem update its mtime. + // if (state_ == state::read_eof) { if (!touch) @@ -314,8 +371,11 @@ namespace build2 // Note also that utime() on Windows is a bad idea (see touch_file() for // details). // - pos_ = buf_->tellg (); // The last line is accepted. - change (false /* truncate */); // Write end marker below. + if (*touch == timestamp_unknown) + { + pos_ = buf_->tellg (); // The last line is accepted. + change (false /* truncate */); // Write end marker below. + } } else if (state_ != state::write) { @@ -323,9 +383,10 @@ namespace build2 change (true /* truncate */); } - if (mtime_check ()) + if (mc && mtime_check ()) start_ = system_clock::now (); + if (state_ == state::write) try { os_.put ('\0'); // The "end marker". @@ -333,7 +394,17 @@ namespace build2 } catch (const io_error& e) { - fail << "unable to flush " << path << ": " << e; + fail << "unable to flush file " << path << ": " << e; + } + + if (touch && *touch != timestamp_unknown) + try + { + file_mtime (path, *touch); + } + catch (const system_error& e) + { + fail << "unable to touch file " << path << ": " << e; } // On some platforms (currently confirmed on FreeBSD running as VMs) one @@ -353,6 +424,37 @@ namespace build2 #endif } + depdb::reopen_state depdb:: + close_to_reopen () + { + assert (!touch); + + if (state_ != state::write) + { + pos_ = buf_->tellg (); // The last line is accepted. + change (state_ != state::read_eof /* truncate */); + } + + pos_ = buf_->tellp (); + + try + { + os_.put ('\0'); // The "end marker". + os_.close (); + } + catch (const io_error& e) + { + fail << "unable to flush file " << path << ": " << e; + } + + // Note: must still be done for FreeBSD if changing anything here (see + // close() for details). + // + mtime = build2::mtime (path); + + return reopen_state {move (path), pos_, mtime}; + } + void depdb:: check_mtime_ (const path_type& t, timestamp e) { |