chromium/chrome/browser/ash/chromebox_for_meetings/artemis/log_source.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/chromebox_for_meetings/artemis/log_source.h"

#include <sys/stat.h>

#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "chrome/browser/ash/chromebox_for_meetings/artemis/specialized_log_sources.h"

namespace ash::cfm {

LogSource::LogSource(const std::string& filepath,
                     base::TimeDelta poll_rate,
                     size_t batch_size)
    : LocalDataSource(poll_rate,
                      /*data_needs_redacting=*/false,
                      /*is_incremental=*/true),
      log_file_(filepath),
      batch_size_(batch_size) {
  recovery_offset_ = GetLastKnownOffsetFromStorage();

  // No point in proceeding here if the file can't be opened
  if (!log_file_.OpenAtOffset(recovery_offset_)) {
    file_is_accessible_ = false;
    return;
  }

  // Store this now so we can detect rotations later.
  last_known_inode_ = GetCurrentFileInode();
}

LogSource::~LogSource() = default;

std::unique_ptr<LogSource> LogSource::Create(const std::string& filename,
                                             base::TimeDelta poll_rate,
                                             size_t batch_size) {
  if (filename == kCfmAuditLogFile) {
    return std::make_unique<AuditLogSource>(poll_rate, batch_size);
  } else if (filename == kCfmBiosInfoLogFile) {
    return std::make_unique<BiosInfoLogSource>(poll_rate, batch_size);
  } else if (filename == kCfmEventlogLogFile) {
    return std::make_unique<EventlogLogSource>(poll_rate, batch_size);
  } else if (filename == kCfmLacrosLogFile) {
    return std::make_unique<LacrosLogSource>(poll_rate, batch_size);
  } else if (filename == kCfmVariationsListLogFile) {
    return std::make_unique<VariationsListLogSource>(poll_rate, batch_size);
  }

  return std::make_unique<LogSource>(filename, poll_rate, batch_size);
}

void LogSource::Fetch(FetchCallback callback) {
  // Cache the current offset to use as a recovery offset in the
  // event of a crash. Note that this will NOT be flushed to disk
  // until we get a call to Flush(), so if we crash before then,
  // the last flushed offset will be used.
  //
  // Since the data buffer will continue filling up between this
  // call to Fetch() and the next call to Flush(), we MUST cache
  // this value here, or we risk dropping those logs.
  recovery_offset_ = log_file_.GetCurrentOffset();
  LocalDataSource::Fetch(std::move(callback));
}

void LogSource::Flush() {
  if (!file_is_accessible_) {
    return;
  }
  // The upload succeeded, so update our recovery offset.
  PersistCurrentOffsetToStorage();
  LocalDataSource::Flush();
}

const std::string& LogSource::GetDisplayName() {
  return log_file_.GetFilePath();
}

std::vector<std::string> LogSource::GetNextData() {
  if (!file_is_accessible_) {
    return {};
  }

  if (log_file_.IsInFailState()) {
    LOG(ERROR) << "Attempted to fetch logs for '" << log_file_.GetFilePath()
               << "', but the stream is dead";
    return {};
  }

  // If the file rotated from under us, reset it to start following the
  // new file. TODO(b/320996557): this might drop newest logs from old
  // rotated file.
  if (DidFileRotate()) {
    VLOG(4) << "Detected rotation in file '" << log_file_.GetFilePath() << "'";
    log_file_.CloseStream();
    log_file_.OpenAtOffset(0);
  }

  // ifstreams for files that have reached an EOF will not yield
  // newly-written lines unless the file is explicitly reset.
  // If we've hit an EOF, refresh the stream (close & re-open).
  //
  // NB: if the last read didn't cause an EOF, new lines will be
  // available immediately without the need to Refresh() first.
  if (log_file_.IsAtEOF()) {
    VLOG(4) << "Refreshing log file '" << log_file_.GetFilePath() << "'";
    log_file_.Refresh();
  }

  return log_file_.RetrieveNextLogs(batch_size_);
}

int LogSource::GetCurrentFileInode() {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  struct stat file_info;
  const std::string& filepath = log_file_.GetFilePath();

  if (stat(filepath.c_str(), &file_info) != 0) {
    LOG(ERROR) << "Unable to get inode of " << filepath;
    return kInvalidFileInode;
  }

  return file_info.st_ino;
}

bool LogSource::DidFileRotate() {
  int curr_inode = GetCurrentFileInode();

  if (curr_inode != kInvalidFileInode && last_known_inode_ != curr_inode) {
    if (PersistentDb::IsInitialized()) {
      PersistentDb::Get()->DeleteKeyIfExists(last_known_inode_);
    }
    last_known_inode_ = curr_inode;
    return true;
  }

  return false;
}

std::streampos LogSource::GetLastKnownOffsetFromStorage() {
  int default_value = 0;

  if (!PersistentDb::IsInitialized()) {
    return default_value;
  }

  int inode = GetCurrentFileInode();
  return PersistentDb::Get()->GetValueFromKey(inode, default_value);
}

void LogSource::PersistCurrentOffsetToStorage() {
  if (!PersistentDb::IsInitialized()) {
    LOG(WARNING) << "PersistentDb is inactive; recovery feature is disabled";
    return;
  }
  int inode = GetCurrentFileInode();
  PersistentDb::Get()->SaveValueToKey(inode, recovery_offset_);
}

}  // namespace ash::cfm