aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-11-30 09:44:35 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-11-30 09:44:35 +0200
commit445c89468c7d361fe891aa09f2c28e943f6fe7c5 (patch)
tree2499cf6845e01f01db08518a1e3f56e6bafbcf84
parentc06a49cb25b5a1088f645232e87629fa7830f028 (diff)
Add support for reopening depdb
-rw-r--r--libbuild2/depdb.cxx76
-rw-r--r--libbuild2/depdb.hxx41
2 files changed, 101 insertions, 16 deletions
diff --git a/libbuild2/depdb.cxx b/libbuild2/depdb.cxx
index e50870c..c229f23 100644
--- a/libbuild2/depdb.cxx
+++ b/libbuild2/depdb.cxx
@@ -15,23 +15,24 @@ 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, state s, optional<uint64_t> pos)
+ : state_ (s)
{
fdopen_mode om (fdopen_mode::out | 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;
+ if (!pos)
+ om |= fdopen_mode::create | fdopen_mode::exclusive;
+
em |= ifdstream::failbit;
}
else
- {
- state_ = state::read;
- om |= fdopen_mode::in;
- }
+ om |= fdopen_mode::in; // Both in & out so can switch from read to write.
auto_fd fd;
try
@@ -40,10 +41,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 +53,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.
@@ -63,14 +74,15 @@ namespace build2
}
else
{
- new (&os_) ofdstream (move (fd), em);
+ 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_base (p,
+ mt != timestamp_nonexistent ? state::read : state::write),
path (move (p)),
mtime (mt != timestamp_nonexistent ? mt : timestamp_unknown)
{
@@ -92,6 +104,15 @@ namespace build2
{
}
+ depdb::
+ depdb (reopen_state rs)
+ : depdb_base (rs.path, state::write, rs.pos),
+ path (move (rs.path)),
+ mtime (timestamp_unknown),
+ touch (rs.mtime)
+ {
+ }
+
void depdb::
change (bool trunc)
{
@@ -370,6 +391,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)
{
diff --git a/libbuild2/depdb.hxx b/libbuild2/depdb.hxx
index 5b5052d..55413bf 100644
--- a/libbuild2/depdb.hxx
+++ b/libbuild2/depdb.hxx
@@ -62,12 +62,14 @@ namespace build2
//
struct LIBBUILD2_SYMEXPORT depdb_base
{
- explicit
- depdb_base (const path&, timestamp);
+ // Implementation details.
+ //
+ enum class state {read, read_eof, write};
+ depdb_base (const path&, state, optional<uint64_t> pos = nullopt);
~depdb_base ();
- enum class state {read, read_eof, write} state_;
+ state state_;
union
{
@@ -113,6 +115,27 @@ namespace build2
explicit
depdb (path_type);
+ struct reopen_state
+ {
+ path_type path;
+ uint64_t pos;
+ timestamp mtime;
+ };
+
+ // Reopen the database for writing. The reopen state must have been
+ // obtained by calling close_to_reopen() below. Besides opening the file
+ // and adjusting its write position, this constructor also sets touch to
+ // the timestamp returned by close_to_reopen() to help maintain the
+ // "database mtime is before target mtime" invariant.
+ //
+ // This functionality is primarily useful to handle dynamic dependency
+ // information that is produced as a byproduct of compilation. In this
+ // case the "static" part of the database is written in match and the
+ // "dynamic" part -- in execute.
+ //
+ explicit
+ depdb (reopen_state);
+
// Close the database. If this function is not called, then the database
// may be left in the old/currupt state. Note that in the read mode this
// function will "chop off" lines that haven't been read.
@@ -125,6 +148,16 @@ namespace build2
void
close (bool mtime_check = true);
+ // Temporarily close the database to be reopened for writing later.
+ // Besides the file path and write position also return the database file
+ // modification time after closing.
+ //
+ // Note that after this call the resulting database file is valid and if
+ // it's not reopened later, the result is equivalent to calling close().
+ //
+ reopen_state
+ close_to_reopen ();
+
// Flush any unwritten data to disk. This is primarily useful when reusing
// a (partially written) database as an input to external programs (e.g.,
// as a module map).
@@ -158,7 +191,7 @@ namespace build2
// the next line in the database (which you are free to move from). If you
// then call write(), this line will be overwritten.
//
- // If the result is NULL, then it means no next line is unavailable. This
+ // If the result is NULL, then it means no next line is available. This
// can be due to several reasons:
//
// - eof reached (you can detect this by calling more() before read())