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