1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "uploader/system_profile_cache.h"
18
19#include <base/files/file_util.h>
20#include <base/guid.h>
21#include <base/logging.h>
22#include <base/strings/string_number_conversions.h>
23#include <base/strings/string_util.h>
24#include <brillo/osrelease_reader.h>
25#include <string>
26#include <update_engine/client.h>
27#include <vector>
28
29#include "constants.h"
30#include "persistent_integer.h"
31#include "uploader/metrics_log_base.h"
32#include "uploader/proto/chrome_user_metrics_extension.pb.h"
33
34namespace {
35
36const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
37
38}  // namespace
39
40std::string ChannelToString(
41    const metrics::SystemProfileProto_Channel& channel) {
42  switch (channel) {
43    case metrics::SystemProfileProto::CHANNEL_STABLE:
44    return "STABLE";
45  case metrics::SystemProfileProto::CHANNEL_DEV:
46    return "DEV";
47  case metrics::SystemProfileProto::CHANNEL_BETA:
48    return "BETA";
49  case metrics::SystemProfileProto::CHANNEL_CANARY:
50    return "CANARY";
51  default:
52    return "UNKNOWN";
53  }
54}
55
56SystemProfileCache::SystemProfileCache()
57    : initialized_(false),
58      testing_(false),
59      metrics_directory_(metrics::kMetricsdDirectory),
60      session_id_(new chromeos_metrics::PersistentInteger(
61          kPersistentSessionIdFilename, metrics_directory_)) {}
62
63SystemProfileCache::SystemProfileCache(bool testing,
64                                       const base::FilePath& metrics_directory)
65    : initialized_(false),
66      testing_(testing),
67      metrics_directory_(metrics_directory),
68      session_id_(new chromeos_metrics::PersistentInteger(
69          kPersistentSessionIdFilename, metrics_directory)) {}
70
71bool SystemProfileCache::Initialize() {
72  CHECK(!initialized_)
73      << "this should be called only once in the metrics_daemon lifetime.";
74
75  brillo::OsReleaseReader reader;
76  std::string channel;
77  if (testing_) {
78    reader.LoadTestingOnly(metrics_directory_);
79    channel = "unknown";
80  } else {
81    reader.Load();
82    auto client = update_engine::UpdateEngineClient::CreateInstance();
83    if (!client) {
84      LOG(ERROR) << "failed to create the update engine client";
85      return false;
86    }
87    if (!client->GetChannel(&channel)) {
88      LOG(ERROR) << "failed to read the current channel from update engine.";
89      return false;
90    }
91  }
92
93  if (!reader.GetString(metrics::kProductId, &profile_.product_id)
94      || profile_.product_id.empty()) {
95    LOG(ERROR) << "product_id is not set.";
96    return false;
97  }
98
99  if (!reader.GetString(metrics::kProductVersion, &profile_.version)) {
100    LOG(ERROR) << "failed to read the product version";
101  }
102
103  if (channel.empty() || profile_.version.empty()) {
104    // If the channel or version is missing, the image is not official.
105    // In this case, set the channel to unknown and the version to 0.0.0.0 to
106    // avoid polluting the production data.
107    channel = "";
108    profile_.version = metrics::kDefaultVersion;
109  }
110  std::string guid_path = metrics_directory_.Append(
111      metrics::kMetricsGUIDFileName).value();
112  profile_.client_id = testing_ ?
113      "client_id_test" :
114      GetPersistentGUID(guid_path);
115  profile_.model_manifest_id = "unknown";
116  if (!testing_) {
117    brillo::KeyValueStore weave_config;
118    if (!weave_config.Load(base::FilePath(metrics::kWeaveConfigurationFile))) {
119      LOG(ERROR) << "Failed to load the weave configuration file.";
120    } else if (!weave_config.GetString(metrics::kModelManifestId,
121                                       &profile_.model_manifest_id)) {
122      LOG(ERROR) << "The model manifest id (model_id) is undefined in "
123                 << metrics::kWeaveConfigurationFile;
124    }
125  }
126
127  profile_.channel = ProtoChannelFromString(channel);
128
129  // Increment the session_id everytime we initialize this. If metrics_daemon
130  // does not crash, this should correspond to the number of reboots of the
131  // system.
132  session_id_->Add(1);
133  profile_.session_id = static_cast<int32_t>(session_id_->Get());
134
135  initialized_ = true;
136  return initialized_;
137}
138
139bool SystemProfileCache::InitializeOrCheck() {
140  return initialized_ || Initialize();
141}
142
143bool SystemProfileCache::Populate(
144    metrics::ChromeUserMetricsExtension* metrics_proto) {
145  CHECK(metrics_proto);
146  if (not InitializeOrCheck()) {
147    return false;
148  }
149
150  // The client id is hashed before being sent.
151  metrics_proto->set_client_id(
152      metrics::MetricsLogBase::Hash(profile_.client_id));
153  metrics_proto->set_session_id(profile_.session_id);
154
155  // Sets the product id.
156  metrics_proto->set_product(9);
157
158  metrics::SystemProfileProto* profile_proto =
159      metrics_proto->mutable_system_profile();
160  profile_proto->mutable_hardware()->set_hardware_class(
161      profile_.model_manifest_id);
162  profile_proto->set_app_version(profile_.version);
163  profile_proto->set_channel(profile_.channel);
164  metrics::SystemProfileProto_BrilloDeviceData* device_data =
165      profile_proto->mutable_brillo();
166  device_data->set_product_id(profile_.product_id);
167
168  return true;
169}
170
171std::string SystemProfileCache::GetPersistentGUID(
172    const std::string& filename) {
173  std::string guid;
174  base::FilePath filepath(filename);
175  if (!base::ReadFileToString(filepath, &guid)) {
176    guid = base::GenerateGUID();
177    // If we can't read or write the file, the guid will not be preserved during
178    // the next reboot. Crash.
179    CHECK(base::WriteFile(filepath, guid.c_str(), guid.size()));
180  }
181  return guid;
182}
183
184metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
185    const std::string& channel) {
186  if (channel == "stable-channel") {
187    return metrics::SystemProfileProto::CHANNEL_STABLE;
188  } else if (channel == "dev-channel") {
189    return metrics::SystemProfileProto::CHANNEL_DEV;
190  } else if (channel == "beta-channel") {
191    return metrics::SystemProfileProto::CHANNEL_BETA;
192  } else if (channel == "canary-channel") {
193    return metrics::SystemProfileProto::CHANNEL_CANARY;
194  }
195
196  DLOG(INFO) << "unknown channel: " << channel;
197  return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
198}
199