blob: 2c0df0035da229395713ef5ac7b8b5e8541f7856 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "machine-id-regen/machine_id_regen.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/rand_util.h>
#include <base/strings/string_split.h>
#include <base/system/sys_info.h>
#include <brillo/dbus/dbus_method_invoker.h>
#include <brillo/dbus/dbus_signal.h>
#include <brillo/errors/error.h>
#include <brillo/syslog_logging.h>
#include <dbus/error.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "machine-id-regen/file_auto_lock.h"
#include "machine-id-regen/timestamp.h"
#include "upstart/dbus-proxies.h"
namespace {
constexpr char kTimestampFileName[] = "timestamp-machine-id";
constexpr char kUpstartServiceName[] = "com.ubuntu.Upstart";
constexpr char kRegenEventName[] = "cros-machine-id-regenerated";
constexpr char kAvahiServiceName[] = "org.freedesktop.Avahi";
constexpr char kAvahiInterfaceName[] = "org.freedesktop.Avahi.Server";
constexpr char kAvahiMethodName[] = "SetHostName";
struct MetricsDescription {
const std::string reason;
const std::string pretty;
const int value;
MetricsDescription() : reason("unknown"), pretty("Unknown"), value(0) {}
MetricsDescription(const std::string& reason,
const std::string& pretty,
int value)
: reason(reason), pretty(pretty), value(value) {}
};
bool emit_metrics(const std::string& reason,
std::shared_ptr<MetricsLibrary> metrics_lib,
base::TimeDelta last_update_s) {
std::vector<std::shared_ptr<MetricsDescription>> reasons = {
std::make_shared<MetricsDescription>("network", "Network", 1),
std::make_shared<MetricsDescription>("periodic", "Periodic", 2)};
std::vector<std::shared_ptr<MetricsDescription>>::iterator it = std::find_if(
reasons.begin(), reasons.end(),
[&reason](const std::shared_ptr<MetricsDescription>& metrics) {
return metrics->reason.compare(reason);
});
std::shared_ptr<MetricsDescription> metrics = nullptr;
if (it == reasons.end()) {
metrics = std::make_shared<MetricsDescription>();
} else {
metrics = *it;
}
if (!metrics_lib->SendSparseToUMA("ChromeOS.MachineIdRegen.Reason",
metrics->value)) {
return false;
}
if (last_update_s.is_zero()) {
return false;
}
return metrics_lib->SendToUMA("ChromeOS.MachineIdRegen.AgeSeconds",
last_update_s.InSeconds(), 0, 86400, 50) &&
metrics_lib->SendToUMA(
"ChromeOS.MachineIdRegen.AgeSeconds_" + metrics->pretty,
last_update_s.InSeconds(), 0, 86400, 50);
}
std::string generate_machine_id() {
uint8_t machine_id[16];
base::RandBytes(machine_id, sizeof(machine_id));
return base::HexEncode(machine_id, sizeof(machine_id));
}
} // namespace
namespace machineidregen {
bool send_machine_id_to_avahi(scoped_refptr<dbus::Bus> bus,
const std::string& machine_id) {
dbus::ObjectProxy* proxy =
bus->GetObjectProxy(kAvahiServiceName, dbus::ObjectPath("/"));
if (!proxy) {
LOG(WARNING) << "Unable to get dbus proxy for " << kAvahiServiceName;
return false;
}
dbus::MethodCall method_call(kAvahiInterfaceName, kAvahiMethodName);
dbus::MessageWriter writer(&method_call);
std::vector<std::string> args_keyvals;
args_keyvals.emplace_back(machine_id);
writer.AppendArrayOfStrings(args_keyvals);
auto result = proxy->CallMethodAndBlock(&method_call, 10000);
if (!result.has_value()) {
dbus::Error error = std::move(result.error());
std::string error_name = error.name();
LOG(WARNING) << kAvahiMethodName << " finished with " << error_name
<< " error.";
return false;
}
return true;
}
bool emit_machine_id_regen(scoped_refptr<dbus::Bus> bus) {
brillo::ErrorPtr error;
com::ubuntu::Upstart0_6Proxy upstart_proxy =
com::ubuntu::Upstart0_6Proxy(bus, kUpstartServiceName);
if (!upstart_proxy.EmitEvent(kRegenEventName, {}, false, &error)) {
LOG(ERROR) << "Could not emit upstart event: " << error->GetMessage();
return false;
}
return true;
}
bool regen_machine_id(const base::FilePath& state_dir,
const base::FilePath& machine_id_file,
const std::string& reason,
std::shared_ptr<MetricsLibrary> metrics_lib,
base::TimeDelta minimum_age_seconds) {
FileAutoLock lock(base::FilePath(state_dir.value() + "/lock"));
if (!lock.lock()) {
LOG(ERROR) << "Could not aquire lock";
return false;
}
base::TimeDelta uptime = base::SysInfo::Uptime();
Timestamp timestamp(state_dir.Append(kTimestampFileName));
base::TimeDelta last_update;
std::optional<base::TimeDelta> ret = timestamp.get_last_update();
if (ret.has_value()) {
last_update = ret.value();
} else {
LOG(WARNING) << "Could not read last regeneration time from "
<< timestamp.GetPath();
LOG(WARNING) << "Reset last update time to 0";
last_update = base::Seconds(0);
}
base::TimeDelta seconds_since_last_update = uptime - last_update;
if (!minimum_age_seconds.is_zero() && !seconds_since_last_update.is_zero() &&
seconds_since_last_update < minimum_age_seconds) {
LOG(INFO) << "Not regenerating since we did so "
<< seconds_since_last_update.InSeconds() << " seconds ago";
return true;
}
const std::string new_machine_id = generate_machine_id() + "\n";
if (!brillo::WriteToFileAtomic(machine_id_file, new_machine_id.c_str(),
new_machine_id.length(), 0644)) {
PLOG(ERROR) << "Failed to save to machine id to " << machine_id_file;
return false;
}
if (!timestamp.update(uptime)) {
LOG(ERROR) << "Could not update regeneration timestamp in "
<< timestamp.GetPath();
return false;
}
dbus::Bus::Options opts;
opts.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(opts)));
if (!bus->Connect()) {
LOG(ERROR) << "Failed to connect to system dbus";
return false;
}
if (!send_machine_id_to_avahi(bus, new_machine_id)) {
LOG(WARNING) << "Skip Avahi update assuming avahi deamon is offline.";
}
if (!emit_machine_id_regen(bus)) {
LOG(ERROR) << "emit failed";
}
LOG(INFO) << "Regenerated " << machine_id_file
<< " (reason: " + reason + ") ";
bool emit_result =
emit_metrics(reason, metrics_lib, seconds_since_last_update);
if (!emit_result) {
LOG(ERROR) << "Error while emitting metrics";
return false;
}
if (!lock.unlock()) {
LOG(ERROR) << "Could not release lock";
return false;
}
return true;
}
} // namespace machineidregen