in go/storage/certdatabase.go [581:697]
func (db *CertDatabase) Commit(aProofOfLock string) error {
// Commit() moves serials from cache to storage, removes expired serial
// numbers from storage, and updates the coverage metadata file. This is
// done in four steps:
// 1) coverage metadata is retrieved from cache and written to a
// temporary file,
// 2) cached serials are moved to persistent storage,
// 3) the coverage metadata file is atomically overwritten with the
// temporary file from step 1,
// 4) expired serial numbers are removed from storage.
// This sequence of operations ensures that the coverage metadata file
// describes a subset of the stored serials at the end of step 3. (It
// will typically be a strict subset, as the commit process is intended
// to run in parallel with ct-fetch).
//
// The caller must hold the commit lock (i.e. the caller must store a
// random value under the key `lock::commit` in the cache and then
// provide that value here as `aProofOfLock`).
//
// The epoch value in storage must be one less than the epoch value in
// cache (unless this is the first time that Commit() has been called,
// in which case both epochs will be equal to 0).
if db.readOnlyStorage {
return fmt.Errorf("Cannot commit serials to read-only storage")
}
hasLock, err := db.cache.HasCommitLock(aProofOfLock)
if err != nil {
return err
}
if !hasLock {
return errors.New("Caller must hold commit lock")
}
storageEpoch, err := db.getStorageEpoch()
if err != nil {
return err
}
cacheEpoch, err := db.cache.GetEpoch()
if err != nil {
return err
}
if (cacheEpoch != storageEpoch+1) && !(cacheEpoch == 0 && storageEpoch == 0) {
return errors.New("Inconsistent cache and storage epochs. Restart ct-fetch.")
}
logList, err := db.cache.LoadAllLogStates()
if err != nil {
return err
}
ctLogFD, err := renameio.TempFile("", db.coverageFile())
if err != nil {
return err
}
defer ctLogFD.Cleanup()
enc := json.NewEncoder(ctLogFD)
if err = enc.Encode(logList); err != nil {
return err
}
err = db.moveCachedSerialsToStorage()
if err != nil {
return err
}
err = ctLogFD.CloseAtomicallyReplace()
if err != nil {
return err
}
err = db.removeExpiredSerialsFromStorage(time.Now())
if err != nil {
return err
}
err = db.moveAliasedSerials()
if err != nil {
return err
}
// The data on disk is in a good state and we just have to increment
// the cache and storage epochs. We can ignore some errors here as long
// as the end result is that the cache is one epoch ahead of storage.
epochFD, err := renameio.TempFile("", db.epochFile())
if err != nil {
glog.Warningf("Failed to increment epochs: %s", err)
return nil
}
defer epochFD.Cleanup()
writer := bufio.NewWriter(epochFD)
_, err = writer.WriteString(fmt.Sprintf("%v\n", cacheEpoch))
if err != nil {
glog.Warningf("Failed to increment epochs: %s", err)
return nil
}
writer.Flush()
err = db.cache.NextEpoch()
if err != nil {
glog.Warningf("Failed to increment epochs: %s", err)
return nil
}
err = epochFD.CloseAtomicallyReplace()
if err != nil {
// This is the one case where we get inconsistent epochs.
return err
}
return nil
}