rlz_value_store_chromeos.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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. #include "rlz/chromeos/lib/rlz_value_store_chromeos.h"
  5. #include <tuple>
  6. #include "base/base_paths.h"
  7. #include "base/bind.h"
  8. #include "base/containers/contains.h"
  9. #include "base/containers/cxx20_erase.h"
  10. #include "base/cxx17_backports.h"
  11. #include "base/files/file_util.h"
  12. #include "base/files/important_file_writer.h"
  13. #include "base/json/json_file_value_serializer.h"
  14. #include "base/json/json_string_value_serializer.h"
  15. #include "base/lazy_instance.h"
  16. #include "base/logging.h"
  17. #include "base/memory/ptr_util.h"
  18. #include "base/metrics/histogram_macros.h"
  19. #include "base/path_service.h"
  20. #include "base/strings/string_number_conversions.h"
  21. #include "base/strings/string_piece.h"
  22. #include "base/task/sequenced_task_runner.h"
  23. #include "base/values.h"
  24. #include "chromeos/ash/components/dbus/dbus_thread_manager.h"
  25. #include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
  26. #include "chromeos/system/factory_ping_embargo_check.h"
  27. #include "chromeos/system/statistics_provider.h"
  28. #include "dbus/bus.h"
  29. #include "rlz/lib/lib_values.h"
  30. #include "rlz/lib/recursive_cross_process_lock_posix.h"
  31. #include "rlz/lib/supplementary_branding.h"
  32. #include "rlz/lib/time_util.h"
  33. namespace rlz_lib {
  34. namespace {
  35. // Key names.
  36. const char kPingTimeKey[] = "ping_time";
  37. const char kAccessPointKey[] = "access_points";
  38. const char kProductEventKey[] = "product_events";
  39. const char kStatefulEventKey[] = "stateful_events";
  40. // Brand name used when there is no supplementary brand name.
  41. const char kNoSupplementaryBrand[] = "_";
  42. // RLZ store filename.
  43. const base::FilePath::CharType kRLZDataFileName[] =
  44. FILE_PATH_LITERAL("RLZ Data");
  45. // RLZ store lock filename
  46. const base::FilePath::CharType kRLZLockFileName[] =
  47. FILE_PATH_LITERAL("RLZ Data.lock");
  48. // RLZ store path for testing.
  49. base::LazyInstance<base::FilePath>::Leaky g_testing_rlz_store_path =
  50. LAZY_INSTANCE_INITIALIZER;
  51. base::FilePath GetRlzStorePathCommon() {
  52. base::FilePath homedir;
  53. base::PathService::Get(base::DIR_HOME, &homedir);
  54. return g_testing_rlz_store_path.Get().empty()
  55. ? homedir
  56. : g_testing_rlz_store_path.Get();
  57. }
  58. // Returns file path of the RLZ storage.
  59. base::FilePath GetRlzStorePath() {
  60. return GetRlzStorePathCommon().Append(kRLZDataFileName);
  61. }
  62. // Returns file path of the RLZ storage lock file.
  63. base::FilePath GetRlzStoreLockPath() {
  64. return GetRlzStorePathCommon().Append(kRLZLockFileName);
  65. }
  66. // Returns the dictionary key for storing access point-related prefs.
  67. std::string GetKeyName(const std::string& key, AccessPoint access_point) {
  68. std::string brand = SupplementaryBranding::GetBrand();
  69. if (brand.empty())
  70. brand = kNoSupplementaryBrand;
  71. return key + "." + GetAccessPointName(access_point) + "." + brand;
  72. }
  73. // Returns the dictionary key for storing product-related prefs.
  74. std::string GetKeyName(const std::string& key, Product product) {
  75. std::string brand = SupplementaryBranding::GetBrand();
  76. if (brand.empty())
  77. brand = kNoSupplementaryBrand;
  78. return key + "." + GetProductName(product) + "." + brand;
  79. }
  80. // Uses |brand| to replace the brand code contained in |rlz|. No-op if |rlz| is
  81. // in incorrect format or already contains |brand|. Returns whether the
  82. // replacement took place.
  83. bool ConvertToDynamicRlz(const std::string& brand,
  84. std::string* rlz,
  85. AccessPoint access_point) {
  86. if (brand.size() != 4) {
  87. LOG(ERROR) << "Invalid brand code format: " + brand;
  88. return false;
  89. }
  90. // Do a sanity check for the rlz string format. It must start with a
  91. // single-digit rlz encoding version, followed by a two-alphanum access point
  92. // name, and a four-letter brand code.
  93. if (rlz->size() < 7 ||
  94. rlz->substr(1, 2) != GetAccessPointName(access_point)) {
  95. LOG(ERROR) << "Invalid rlz string format: " + *rlz;
  96. return false;
  97. }
  98. if (rlz->substr(3, 4) == brand)
  99. return false;
  100. rlz->replace(3, 4, brand);
  101. return true;
  102. }
  103. // Forward declare so that it could be referred in SetRlzPingSent.
  104. void OnSetRlzPingSent(int retry_count, bool success);
  105. // Calls debug daemon client to set |should_send_rlz_ping| to 0 in RW_VPD.
  106. // Re-post the work on DBus's original thread if it is not called from there
  107. // because DBus code is not thread safe.
  108. void SetRlzPingSent(int retry_count) {
  109. // GetSystemBus() could return null in tests.
  110. base::SequencedTaskRunner* const origin_task_runner =
  111. chromeos::DBusThreadManager::Get()->GetSystemBus()
  112. ? chromeos::DBusThreadManager::Get()
  113. ->GetSystemBus()
  114. ->GetOriginTaskRunner()
  115. : nullptr;
  116. if (origin_task_runner && !origin_task_runner->RunsTasksInCurrentSequence()) {
  117. origin_task_runner->PostTask(FROM_HERE,
  118. base::BindOnce(&SetRlzPingSent, retry_count));
  119. return;
  120. }
  121. ash::DebugDaemonClient::Get()->SetRlzPingSent(
  122. base::BindOnce(&OnSetRlzPingSent, retry_count + 1));
  123. }
  124. // Callback invoked for DebugDaemonClient::SetRlzPingSent.
  125. void OnSetRlzPingSent(int retry_count, bool success) {
  126. if (success) {
  127. UMA_HISTOGRAM_BOOLEAN("Rlz.SetRlzPingSent", true);
  128. return;
  129. }
  130. if (retry_count >= RlzValueStoreChromeOS::kMaxRetryCount) {
  131. UMA_HISTOGRAM_BOOLEAN("Rlz.SetRlzPingSent", false);
  132. LOG(ERROR) << "Setting " << chromeos::system::kShouldSendRlzPingKey
  133. << " failed after " << RlzValueStoreChromeOS::kMaxRetryCount
  134. << " attempts.";
  135. return;
  136. }
  137. SetRlzPingSent(retry_count);
  138. }
  139. // Copy |value| without empty children.
  140. absl::optional<base::Value> CopyWithoutEmptyChildren(const base::Value& value) {
  141. switch (value.type()) {
  142. case base::Value::Type::DICT: {
  143. base::Value::Dict dict;
  144. const base::Value::Dict& dict_in = value.GetDict();
  145. for (auto it = dict_in.begin(); it != dict_in.end(); ++it) {
  146. absl::optional<base::Value> item_copy =
  147. CopyWithoutEmptyChildren(it->second);
  148. if (item_copy)
  149. dict.Set(it->first, std::move(*item_copy));
  150. }
  151. if (dict.empty())
  152. return absl::nullopt;
  153. return base::Value(std::move(dict));
  154. }
  155. case base::Value::Type::LIST: {
  156. base::Value::List list;
  157. list.reserve(value.GetList().size());
  158. for (const base::Value& item : value.GetList()) {
  159. absl::optional<base::Value> item_copy = CopyWithoutEmptyChildren(item);
  160. if (item_copy)
  161. list.Append(std::move(*item_copy));
  162. }
  163. if (list.empty())
  164. return absl::nullopt;
  165. return base::Value(std::move(list));
  166. }
  167. default:
  168. return value.Clone();
  169. }
  170. }
  171. } // namespace
  172. const int RlzValueStoreChromeOS::kMaxRetryCount = 3;
  173. RlzValueStoreChromeOS::RlzValueStoreChromeOS(const base::FilePath& store_path)
  174. : rlz_store_(base::Value::Type::DICTIONARY),
  175. store_path_(store_path),
  176. read_only_(true) {
  177. ReadStore();
  178. }
  179. RlzValueStoreChromeOS::~RlzValueStoreChromeOS() {
  180. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  181. WriteStore();
  182. }
  183. bool RlzValueStoreChromeOS::HasAccess(AccessType type) {
  184. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  185. return type == kReadAccess || !read_only_;
  186. }
  187. bool RlzValueStoreChromeOS::WritePingTime(Product product, int64_t time) {
  188. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  189. rlz_store_.SetStringPath(GetKeyName(kPingTimeKey, product),
  190. base::NumberToString(time));
  191. return true;
  192. }
  193. bool RlzValueStoreChromeOS::ReadPingTime(Product product, int64_t* time) {
  194. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  195. // TODO(wzang): Make sure time is correct (check that npupdate has updated
  196. // successfully). See AutoEnrollmentController::SystemClockSyncWaiter for
  197. // potential refactor in the ping embargo class.
  198. if (!HasRlzEmbargoEndDatePassed()) {
  199. *time = GetSystemTimeAsInt64();
  200. return true;
  201. }
  202. const std::string* ping_time =
  203. rlz_store_.FindStringPath(GetKeyName(kPingTimeKey, product));
  204. return ping_time ? base::StringToInt64(*ping_time, time) : false;
  205. }
  206. bool RlzValueStoreChromeOS::ClearPingTime(Product product) {
  207. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  208. rlz_store_.RemovePath(GetKeyName(kPingTimeKey, product));
  209. return true;
  210. }
  211. bool RlzValueStoreChromeOS::WriteAccessPointRlz(AccessPoint access_point,
  212. const char* new_rlz) {
  213. // If an access point already exists, don't overwrite it. This is to prevent
  214. // writing cohort data for first search which is not needed in Chrome OS.
  215. //
  216. // There are two possible cases: either the user performs a search before the
  217. // first ping is sent on first run, or they do not. If they do, then
  218. // |new_rlz| contain cohorts for install and first search, but they will be
  219. // the same. If they don't, the first time WriteAccessPointRlz() is called
  220. // |new_rlz| will contain only install cohort. The second time it will
  221. // contain both install and first search cohorts. Ignoring the second
  222. // means the first search cohort will never be stored.
  223. if (HasAccessPointRlz(access_point))
  224. return true;
  225. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  226. rlz_store_.SetStringPath(GetKeyName(kAccessPointKey, access_point), new_rlz);
  227. return true;
  228. }
  229. bool RlzValueStoreChromeOS::ReadAccessPointRlz(AccessPoint access_point,
  230. char* rlz,
  231. size_t rlz_size) {
  232. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  233. const std::string* rlz_value =
  234. rlz_store_.FindStringPath(GetKeyName(kAccessPointKey, access_point));
  235. if (rlz_value && rlz_value->size() < rlz_size) {
  236. strncpy(rlz, rlz_value->c_str(), rlz_size);
  237. return true;
  238. }
  239. if (rlz_size > 0)
  240. *rlz = '\0';
  241. return rlz_value == nullptr;
  242. }
  243. bool RlzValueStoreChromeOS::ClearAccessPointRlz(AccessPoint access_point) {
  244. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  245. rlz_store_.RemovePath(GetKeyName(kAccessPointKey, access_point));
  246. return true;
  247. }
  248. bool RlzValueStoreChromeOS::UpdateExistingAccessPointRlz(
  249. const std::string& brand) {
  250. DCHECK(SupplementaryBranding::GetBrand().empty());
  251. bool updated = false;
  252. for (int i = NO_ACCESS_POINT + 1; i < LAST_ACCESS_POINT; ++i) {
  253. AccessPoint access_point = static_cast<AccessPoint>(i);
  254. const std::string access_point_key =
  255. GetKeyName(kAccessPointKey, access_point);
  256. const std::string* rlz = rlz_store_.FindStringPath(access_point_key);
  257. if (rlz) {
  258. std::string rlz_copy = *rlz;
  259. if (ConvertToDynamicRlz(brand, &rlz_copy, access_point)) {
  260. rlz_store_.SetStringPath(access_point_key, rlz_copy);
  261. updated = true;
  262. }
  263. }
  264. }
  265. return updated;
  266. }
  267. bool RlzValueStoreChromeOS::AddProductEvent(Product product,
  268. const char* event_rlz) {
  269. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  270. return AddValueToList(GetKeyName(kProductEventKey, product),
  271. base::Value(event_rlz));
  272. }
  273. bool RlzValueStoreChromeOS::ReadProductEvents(
  274. Product product,
  275. std::vector<std::string>* events) {
  276. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  277. const base::Value* events_list =
  278. rlz_store_.FindListPath(GetKeyName(kProductEventKey, product));
  279. if (!events_list)
  280. return false;
  281. events->clear();
  282. bool remove_caf = false;
  283. for (const base::Value& item : events_list->GetList()) {
  284. const std::string* event = item.GetIfString();
  285. if (!event)
  286. continue;
  287. if (*event == "CAF" && IsStatefulEvent(product, "CAF"))
  288. remove_caf = true;
  289. events->push_back(*event);
  290. }
  291. if (remove_caf)
  292. ClearProductEvent(product, "CAF");
  293. return true;
  294. }
  295. bool RlzValueStoreChromeOS::ClearProductEvent(Product product,
  296. const char* event_rlz) {
  297. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  298. return RemoveValueFromList(GetKeyName(kProductEventKey, product),
  299. base::Value(event_rlz));
  300. }
  301. bool RlzValueStoreChromeOS::ClearAllProductEvents(Product product) {
  302. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  303. rlz_store_.RemovePath(GetKeyName(kProductEventKey, product));
  304. return true;
  305. }
  306. bool RlzValueStoreChromeOS::AddStatefulEvent(Product product,
  307. const char* event_rlz) {
  308. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  309. if (strcmp(event_rlz, "CAF") == 0)
  310. SetRlzPingSent(/*retry_count=*/0);
  311. return AddValueToList(GetKeyName(kStatefulEventKey, product),
  312. base::Value(event_rlz));
  313. }
  314. bool RlzValueStoreChromeOS::IsStatefulEvent(Product product,
  315. const char* event_rlz) {
  316. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  317. const bool event_exists = ListContainsValue(
  318. GetKeyName(kStatefulEventKey, product), base::Value(event_rlz));
  319. if (strcmp(event_rlz, "CAF") == 0) {
  320. chromeos::system::StatisticsProvider* stats =
  321. chromeos::system::StatisticsProvider::GetInstance();
  322. std::string should_send_rlz_ping_value;
  323. if (stats->GetMachineStatistic(chromeos::system::kShouldSendRlzPingKey,
  324. &should_send_rlz_ping_value)) {
  325. if (should_send_rlz_ping_value ==
  326. chromeos::system::kShouldSendRlzPingValueFalse) {
  327. return true;
  328. } else if (should_send_rlz_ping_value !=
  329. chromeos::system::kShouldSendRlzPingValueTrue) {
  330. LOG(WARNING) << chromeos::system::kShouldSendRlzPingKey
  331. << " has an unexpected value: "
  332. << should_send_rlz_ping_value << ". Treat it as "
  333. << chromeos::system::kShouldSendRlzPingValueFalse
  334. << " to avoid sending duplicate rlz ping.";
  335. return true;
  336. }
  337. if (!HasRlzEmbargoEndDatePassed())
  338. return true;
  339. } else {
  340. // If |kShouldSendRlzPingKey| doesn't exist in RW_VPD, treat it in the
  341. // same way with the case of |kShouldSendRlzPingValueFalse|.
  342. return true;
  343. }
  344. }
  345. return event_exists;
  346. }
  347. bool RlzValueStoreChromeOS::ClearAllStatefulEvents(Product product) {
  348. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  349. rlz_store_.RemovePath(GetKeyName(kStatefulEventKey, product));
  350. return true;
  351. }
  352. void RlzValueStoreChromeOS::CollectGarbage() {
  353. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  354. NOTIMPLEMENTED();
  355. }
  356. // static
  357. bool RlzValueStoreChromeOS::HasRlzEmbargoEndDatePassed() {
  358. chromeos::system::StatisticsProvider* statistics_provider =
  359. chromeos::system::StatisticsProvider::GetInstance();
  360. return chromeos::system::GetRlzPingEmbargoState(statistics_provider) !=
  361. chromeos::system::FactoryPingEmbargoState::kNotPassed;
  362. }
  363. void RlzValueStoreChromeOS::ReadStore() {
  364. int error_code = 0;
  365. std::string error_msg;
  366. JSONFileValueDeserializer deserializer(store_path_);
  367. std::unique_ptr<base::Value> value =
  368. deserializer.Deserialize(&error_code, &error_msg);
  369. switch (error_code) {
  370. case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
  371. read_only_ = false;
  372. break;
  373. case JSONFileValueDeserializer::JSON_NO_ERROR:
  374. read_only_ = false;
  375. rlz_store_ = std::move(*value);
  376. break;
  377. default:
  378. LOG(ERROR) << "Error reading RLZ store: " << error_msg;
  379. }
  380. }
  381. void RlzValueStoreChromeOS::WriteStore() {
  382. std::string json_data;
  383. JSONStringValueSerializer serializer(&json_data);
  384. serializer.set_pretty_print(true);
  385. base::Value copy = CopyWithoutEmptyChildren(rlz_store_)
  386. .value_or(base::Value(base::Value::Type::DICTIONARY));
  387. if (!serializer.Serialize(copy)) {
  388. LOG(ERROR) << "Failed to serialize RLZ data";
  389. NOTREACHED();
  390. return;
  391. }
  392. if (!base::ImportantFileWriter::WriteFileAtomically(store_path_, json_data))
  393. LOG(ERROR) << "Error writing RLZ store";
  394. }
  395. bool RlzValueStoreChromeOS::AddValueToList(const std::string& list_name,
  396. base::Value value) {
  397. base::Value::List* list =
  398. rlz_store_.GetDict().FindListByDottedPath(list_name);
  399. if (!list) {
  400. list = &rlz_store_.SetPath(list_name, base::Value(base::Value::Type::LIST))
  401. ->GetList();
  402. }
  403. if (!base::Contains(*list, value)) {
  404. list->Append(std::move(value));
  405. }
  406. return true;
  407. }
  408. bool RlzValueStoreChromeOS::RemoveValueFromList(const std::string& list_name,
  409. const base::Value& to_remove) {
  410. base::Value::List* list =
  411. rlz_store_.GetDict().FindListByDottedPath(list_name);
  412. if (!list)
  413. return false;
  414. list->EraseIf(
  415. [&to_remove](const base::Value& value) { return value == to_remove; });
  416. return true;
  417. }
  418. bool RlzValueStoreChromeOS::ListContainsValue(const std::string& list_name,
  419. const base::Value& value) const {
  420. const base::Value::List* list =
  421. rlz_store_.GetDict().FindListByDottedPath(list_name);
  422. if (!list)
  423. return false;
  424. return base::Contains(*list, value);
  425. }
  426. bool RlzValueStoreChromeOS::HasAccessPointRlz(AccessPoint access_point) const {
  427. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  428. const std::string* value =
  429. rlz_store_.FindStringPath(GetKeyName(kAccessPointKey, access_point));
  430. return value && !value->empty();
  431. }
  432. namespace {
  433. // RlzValueStoreChromeOS keeps its data in memory and only writes it to disk
  434. // when ScopedRlzValueStoreLock goes out of scope. Hence, if several
  435. // ScopedRlzValueStoreLocks are nested, they all need to use the same store
  436. // object.
  437. RecursiveCrossProcessLock g_recursive_lock =
  438. RECURSIVE_CROSS_PROCESS_LOCK_INITIALIZER;
  439. // This counts the nesting depth of |ScopedRlzValueStoreLock|.
  440. int g_lock_depth = 0;
  441. // This is the shared store object. Non-|NULL| only when |g_lock_depth > 0|.
  442. RlzValueStoreChromeOS* g_store = NULL;
  443. } // namespace
  444. ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() {
  445. bool got_cross_process_lock =
  446. g_recursive_lock.TryGetCrossProcessLock(GetRlzStoreLockPath());
  447. // At this point, we hold the in-process lock, no matter the value of
  448. // |got_cross_process_lock|.
  449. ++g_lock_depth;
  450. if (!got_cross_process_lock) {
  451. // Acquiring cross-process lock failed, so simply return here.
  452. // In-process lock will be released in dtor.
  453. DCHECK(!g_store);
  454. return;
  455. }
  456. if (g_lock_depth > 1) {
  457. // Reuse the already existing store object.
  458. DCHECK(g_store);
  459. store_.reset(g_store);
  460. return;
  461. }
  462. // This is the topmost lock, create a new store object.
  463. DCHECK(!g_store);
  464. g_store = new RlzValueStoreChromeOS(GetRlzStorePath());
  465. store_.reset(g_store);
  466. }
  467. ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() {
  468. --g_lock_depth;
  469. DCHECK_GE(g_lock_depth, 0);
  470. if (g_lock_depth > 0) {
  471. // Other locks are still using store_, so don't free it yet.
  472. std::ignore = store_.release();
  473. return;
  474. }
  475. g_store = NULL;
  476. g_recursive_lock.ReleaseLock();
  477. }
  478. RlzValueStore* ScopedRlzValueStoreLock::GetStore() {
  479. return store_.get();
  480. }
  481. namespace testing {
  482. void SetRlzStoreDirectory(const base::FilePath& directory) {
  483. g_testing_rlz_store_path.Get() = directory;
  484. }
  485. std::string RlzStoreFilenameStr() {
  486. return GetRlzStorePath().value();
  487. }
  488. } // namespace testing
  489. } // namespace rlz_lib