aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/depdb.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/depdb.cxx')
-rw-r--r--libbuild2/depdb.cxx232
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)
{