rlz_value_store_chromeos.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "rlz/chromeos/lib/rlz_value_store_chromeos.h"
6
7#include "base/file_util.h"
8#include "base/files/important_file_writer.h"
9#include "base/json/json_file_value_serializer.h"
10#include "base/json/json_string_value_serializer.h"
11#include "base/logging.h"
12#include "base/sequenced_task_runner.h"
13#include "base/string_number_conversions.h"
14#include "base/values.h"
15#include "rlz/lib/lib_values.h"
16#include "rlz/lib/recursive_cross_process_lock_posix.h"
17#include "rlz/lib/rlz_lib.h"
18
19namespace rlz_lib {
20
21namespace {
22
23// Product names.
24const char kProductChrome[] = "chrome";
25const char kProductOther[] = "other";
26
27// Key names.
28const char kPingTimeKey[] = "ping_time";
29const char kAccessPointKey[] = "access_points";
30const char kProductEventKey[] = "product_events";
31const char kStatefulEventKey[] = "stateful_events";
32
33// Brand name used when there is no supplementary brand name.
34const char kNoSupplementaryBrand[] = "_";
35
36// RLZ store filename.
37const base::FilePath::CharType kRLZDataFileName[] =
38    FILE_PATH_LITERAL("RLZ Data");
39
40// RLZ store lock filename
41const base::FilePath::CharType kRLZLockFileName[] =
42    FILE_PATH_LITERAL("RLZ Data.lock");
43
44// RLZ store path for testing.
45base::FilePath g_testing_rlz_store_path_;
46
47// Returns file path of the RLZ storage.
48base::FilePath GetRlzStorePath() {
49  return g_testing_rlz_store_path_.empty() ?
50      file_util::GetHomeDir().Append(kRLZDataFileName) :
51      g_testing_rlz_store_path_.Append(kRLZDataFileName);
52}
53
54// Returns file path of the RLZ storage lock file.
55base::FilePath GetRlzStoreLockPath() {
56  return g_testing_rlz_store_path_.empty() ?
57      file_util::GetHomeDir().Append(kRLZLockFileName) :
58      g_testing_rlz_store_path_.Append(kRLZLockFileName);
59}
60
61// Returns the dictionary key for storing access point-related prefs.
62std::string GetKeyName(std::string key, AccessPoint access_point) {
63  std::string brand = SupplementaryBranding::GetBrand();
64  if (brand.empty())
65    brand = kNoSupplementaryBrand;
66  return key + "." + GetAccessPointName(access_point) + "." + brand;
67}
68
69// Returns the dictionary key for storing product-related prefs.
70std::string GetKeyName(std::string key, Product product) {
71  std::string brand = SupplementaryBranding::GetBrand();
72  if (brand.empty())
73    brand = kNoSupplementaryBrand;
74  return key + "." + GetProductName(product) + "." + brand;
75}
76
77}  // namespace
78
79RlzValueStoreChromeOS::RlzValueStoreChromeOS(const base::FilePath& store_path)
80    : rlz_store_(new base::DictionaryValue),
81      store_path_(store_path),
82      read_only_(true) {
83  ReadStore();
84}
85
86RlzValueStoreChromeOS::~RlzValueStoreChromeOS() {
87  WriteStore();
88}
89
90bool RlzValueStoreChromeOS::HasAccess(AccessType type) {
91  DCHECK(CalledOnValidThread());
92  return type == kReadAccess || !read_only_;
93}
94
95bool RlzValueStoreChromeOS::WritePingTime(Product product, int64 time) {
96  DCHECK(CalledOnValidThread());
97  rlz_store_->SetString(GetKeyName(kPingTimeKey, product),
98                        base::Int64ToString(time));
99  return true;
100}
101
102bool RlzValueStoreChromeOS::ReadPingTime(Product product, int64* time) {
103  DCHECK(CalledOnValidThread());
104  std::string ping_time;
105  return rlz_store_->GetString(GetKeyName(kPingTimeKey, product), &ping_time) &&
106      base::StringToInt64(ping_time, time);
107}
108
109bool RlzValueStoreChromeOS::ClearPingTime(Product product) {
110  DCHECK(CalledOnValidThread());
111  rlz_store_->Remove(GetKeyName(kPingTimeKey, product), NULL);
112  return true;
113}
114
115bool RlzValueStoreChromeOS::WriteAccessPointRlz(AccessPoint access_point,
116                                                const char* new_rlz) {
117  DCHECK(CalledOnValidThread());
118  rlz_store_->SetString(
119      GetKeyName(kAccessPointKey, access_point), new_rlz);
120  return true;
121}
122
123bool RlzValueStoreChromeOS::ReadAccessPointRlz(AccessPoint access_point,
124                                               char* rlz,
125                                               size_t rlz_size) {
126  DCHECK(CalledOnValidThread());
127  std::string rlz_value;
128  rlz_store_->GetString(GetKeyName(kAccessPointKey, access_point), &rlz_value);
129  if (rlz_value.size() < rlz_size) {
130    strncpy(rlz, rlz_value.c_str(), rlz_size);
131    return true;
132  }
133  if (rlz_size > 0)
134    *rlz = '\0';
135  return false;
136}
137
138bool RlzValueStoreChromeOS::ClearAccessPointRlz(AccessPoint access_point) {
139  DCHECK(CalledOnValidThread());
140  rlz_store_->Remove(GetKeyName(kAccessPointKey, access_point), NULL);
141  return true;
142}
143
144bool RlzValueStoreChromeOS::AddProductEvent(Product product,
145                                            const char* event_rlz) {
146  DCHECK(CalledOnValidThread());
147  return AddValueToList(GetKeyName(kProductEventKey, product),
148                        base::Value::CreateStringValue(event_rlz));
149}
150
151bool RlzValueStoreChromeOS::ReadProductEvents(
152    Product product,
153    std::vector<std::string>* events) {
154  DCHECK(CalledOnValidThread());
155  base::ListValue* events_list = NULL; ;
156  if (!rlz_store_->GetList(GetKeyName(kProductEventKey, product), &events_list))
157    return false;
158  events->clear();
159  for (size_t i = 0; i < events_list->GetSize(); ++i) {
160    std::string event;
161    if (events_list->GetString(i, &event))
162      events->push_back(event);
163  }
164  return true;
165}
166
167bool RlzValueStoreChromeOS::ClearProductEvent(Product product,
168                                              const char* event_rlz) {
169  DCHECK(CalledOnValidThread());
170  base::StringValue event_value(event_rlz);
171  return RemoveValueFromList(GetKeyName(kProductEventKey, product),
172                             event_value);
173}
174
175bool RlzValueStoreChromeOS::ClearAllProductEvents(Product product) {
176  DCHECK(CalledOnValidThread());
177  rlz_store_->Remove(GetKeyName(kProductEventKey, product), NULL);
178  return true;
179}
180
181bool RlzValueStoreChromeOS::AddStatefulEvent(Product product,
182                                             const char* event_rlz) {
183  DCHECK(CalledOnValidThread());
184  return AddValueToList(GetKeyName(kStatefulEventKey, product),
185                        base::Value::CreateStringValue(event_rlz));
186}
187
188bool RlzValueStoreChromeOS::IsStatefulEvent(Product product,
189                                            const char* event_rlz) {
190  DCHECK(CalledOnValidThread());
191  base::StringValue event_value(event_rlz);
192  base::ListValue* events_list = NULL;
193  return rlz_store_->GetList(GetKeyName(kStatefulEventKey, product),
194                             &events_list) &&
195      events_list->Find(event_value) != events_list->end();
196}
197
198bool RlzValueStoreChromeOS::ClearAllStatefulEvents(Product product) {
199  DCHECK(CalledOnValidThread());
200  rlz_store_->Remove(GetKeyName(kStatefulEventKey, product), NULL);
201  return true;
202}
203
204void RlzValueStoreChromeOS::CollectGarbage() {
205  DCHECK(CalledOnValidThread());
206  NOTIMPLEMENTED();
207}
208
209void RlzValueStoreChromeOS::ReadStore() {
210  int error_code = 0;
211  std::string error_msg;
212  JSONFileValueSerializer serializer(store_path_);
213  scoped_ptr<base::Value> value(
214      serializer.Deserialize(&error_code, &error_msg));
215  switch (error_code) {
216    case JSONFileValueSerializer::JSON_NO_SUCH_FILE:
217      read_only_ = false;
218      break;
219    case JSONFileValueSerializer::JSON_NO_ERROR:
220      read_only_ = false;
221      rlz_store_.reset(static_cast<base::DictionaryValue*>(value.release()));
222      break;
223    default:
224      LOG(ERROR) << "Error reading RLZ store: " << error_msg;
225  }
226}
227
228void RlzValueStoreChromeOS::WriteStore() {
229  std::string json_data;
230  JSONStringValueSerializer serializer(&json_data);
231  serializer.set_pretty_print(true);
232  scoped_ptr<DictionaryValue> copy(rlz_store_->DeepCopyWithoutEmptyChildren());
233  if (!serializer.Serialize(*copy.get())) {
234    LOG(ERROR) << "Failed to serialize RLZ data";
235    NOTREACHED();
236    return;
237  }
238  if (!base::ImportantFileWriter::WriteFileAtomically(store_path_, json_data))
239    LOG(ERROR) << "Error writing RLZ store";
240}
241
242bool RlzValueStoreChromeOS::AddValueToList(std::string list_name,
243                                           base::Value* value) {
244  base::ListValue* list_value = NULL;
245  if (!rlz_store_->GetList(list_name, &list_value)) {
246    list_value = new base::ListValue;
247    rlz_store_->Set(list_name, list_value);
248  }
249  list_value->AppendIfNotPresent(value);
250  return true;
251}
252
253bool RlzValueStoreChromeOS::RemoveValueFromList(std::string list_name,
254                                                const base::Value& value) {
255  base::ListValue* list_value = NULL;
256  if (!rlz_store_->GetList(list_name, &list_value))
257    return false;
258  size_t index;
259  list_value->Remove(value, &index);
260  return true;
261}
262
263namespace {
264
265// RlzValueStoreChromeOS keeps its data in memory and only writes it to disk
266// when ScopedRlzValueStoreLock goes out of scope. Hence, if several
267// ScopedRlzValueStoreLocks are nested, they all need to use the same store
268// object.
269
270RecursiveCrossProcessLock g_recursive_lock =
271    RECURSIVE_CROSS_PROCESS_LOCK_INITIALIZER;
272
273// This counts the nesting depth of |ScopedRlzValueStoreLock|.
274int g_lock_depth = 0;
275
276// This is the shared store object. Non-|NULL| only when |g_lock_depth > 0|.
277RlzValueStoreChromeOS* g_store = NULL;
278
279}  // namespace
280
281ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() {
282  bool got_cross_process_lock =
283      g_recursive_lock.TryGetCrossProcessLock(GetRlzStoreLockPath());
284  // At this point, we hold the in-process lock, no matter the value of
285  // |got_cross_process_lock|.
286
287  ++g_lock_depth;
288  if (!got_cross_process_lock) {
289    // Acquiring cross-process lock failed, so simply return here.
290    // In-process lock will be released in dtor.
291    DCHECK(!g_store);
292    return;
293  }
294
295  if (g_lock_depth > 1) {
296    // Reuse the already existing store object.
297    DCHECK(g_store);
298    store_.reset(g_store);
299    return;
300  }
301
302  // This is the topmost lock, create a new store object.
303  DCHECK(!g_store);
304  g_store = new RlzValueStoreChromeOS(GetRlzStorePath());
305  store_.reset(g_store);
306}
307
308ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() {
309  --g_lock_depth;
310  DCHECK(g_lock_depth >= 0);
311
312  if (g_lock_depth > 0) {
313    // Other locks are still using store_, so don't free it yet.
314    ignore_result(store_.release());
315    return;
316  }
317
318  g_store = NULL;
319
320  g_recursive_lock.ReleaseLock();
321}
322
323RlzValueStore* ScopedRlzValueStoreLock::GetStore() {
324  return store_.get();
325}
326
327namespace testing {
328
329void SetRlzStoreDirectory(const base::FilePath& directory) {
330  g_testing_rlz_store_path_ = directory;
331}
332
333std::string RlzStoreFilenameStr() {
334  return GetRlzStorePath().value();
335}
336
337}  // namespace testing
338
339}  // namespace rlz_lib
340