From 445c89468c7d361fe891aa09f2c28e943f6fe7c5 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 30 Nov 2021 09:44:35 +0200 Subject: Add support for reopening depdb --- libbuild2/depdb.cxx | 76 ++++++++++++++++++++++++++++++++++++++++++++--------- libbuild2/depdb.hxx | 41 ++++++++++++++++++++++++++--- 2 files changed, 101 insertions(+), 16 deletions(-) (limited to 'libbuild2') 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 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 (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 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()) -- cgit v1.1