blob: 444c9806f98bfbae8e52ac60fdb8308953f7d95a [file] [log] [blame]
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +00001//===--------------------- ModuleCache.cpp ----------------------*- C++ -*-===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +00006//
7//===----------------------------------------------------------------------===//
8
Zachary Turner01c32432017-02-14 19:06:07 +00009#include "lldb/Target/ModuleCache.h"
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000010
11#include "lldb/Core/Module.h"
Tamas Berghammer257e13a2015-12-10 17:08:23 +000012#include "lldb/Core/ModuleList.h"
Oleksiy Vyaloveda270e2015-03-12 18:18:03 +000013#include "lldb/Core/ModuleSpec.h"
Oleksiy Vyalov919ef9d2015-05-07 15:28:49 +000014#include "lldb/Host/File.h"
Oleksiy Vyalov919ef9d2015-05-07 15:28:49 +000015#include "lldb/Host/LockFile.h"
Zachary Turner6f9e6902017-03-03 20:56:28 +000016#include "lldb/Utility/Log.h"
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000017#include "llvm/Support/FileSystem.h"
Oleksiy Vyalov280d8dc2015-04-15 14:35:10 +000018#include "llvm/Support/FileUtilities.h"
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000019
20#include <assert.h>
21
22#include <cstdio>
Oleksiy Vyalov919ef9d2015-05-07 15:28:49 +000023
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000024using namespace lldb;
25using namespace lldb_private;
26
27namespace {
28
Kate Stoneb9c1b512016-09-06 20:57:50 +000029const char *kModulesSubdir = ".cache";
30const char *kLockDirName = ".lock";
31const char *kTempFileName = ".temp";
32const char *kTempSymFileName = ".symtemp";
33const char *kSymFileExtension = ".sym";
34const char *kFSIllegalChars = "\\/:*?\"<>|";
Oleksiy Vyalov3154e772016-05-24 18:09:05 +000035
Kate Stoneb9c1b512016-09-06 20:57:50 +000036std::string GetEscapedHostname(const char *hostname) {
Jason Molenda14699cf2016-10-21 02:32:08 +000037 if (hostname == nullptr)
38 hostname = "unknown";
Kate Stoneb9c1b512016-09-06 20:57:50 +000039 std::string result(hostname);
40 size_t size = result.size();
41 for (size_t i = 0; i < size; ++i) {
42 if ((result[i] >= 1 && result[i] <= 31) ||
43 strchr(kFSIllegalChars, result[i]) != nullptr)
44 result[i] = '_';
45 }
46 return result;
Oleksiy Vyalov3154e772016-05-24 18:09:05 +000047}
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000048
Kate Stoneb9c1b512016-09-06 20:57:50 +000049class ModuleLock {
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000050private:
Kate Stoneb9c1b512016-09-06 20:57:50 +000051 File m_file;
52 std::unique_ptr<lldb_private::LockFile> m_lock;
53 FileSpec m_file_spec;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000054
55public:
Zachary Turner97206d52017-05-12 04:51:55 +000056 ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, Status &error);
Kate Stoneb9c1b512016-09-06 20:57:50 +000057 void Delete();
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000058};
59
Zachary Turner7d86ee52017-03-08 17:56:08 +000060static FileSpec JoinPath(const FileSpec &path1, const char *path2) {
Kate Stoneb9c1b512016-09-06 20:57:50 +000061 FileSpec result_spec(path1);
62 result_spec.AppendPathComponent(path2);
63 return result_spec;
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000064}
65
Zachary Turner97206d52017-05-12 04:51:55 +000066static Status MakeDirectory(const FileSpec &dir_path) {
Zachary Turner7d86ee52017-03-08 17:56:08 +000067 namespace fs = llvm::sys::fs;
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000068
Zachary Turner7d86ee52017-03-08 17:56:08 +000069 return fs::create_directories(dir_path.GetPath(), true, fs::perms::owner_all);
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +000070}
71
Kate Stoneb9c1b512016-09-06 20:57:50 +000072FileSpec GetModuleDirectory(const FileSpec &root_dir_spec, const UUID &uuid) {
73 const auto modules_dir_spec = JoinPath(root_dir_spec, kModulesSubdir);
74 return JoinPath(modules_dir_spec, uuid.GetAsString().c_str());
Oleksiy Vyalova9ea0712015-05-08 23:54:34 +000075}
76
Kate Stoneb9c1b512016-09-06 20:57:50 +000077FileSpec GetSymbolFileSpec(const FileSpec &module_file_spec) {
Jonas Devlieghere8f3be7a2018-11-01 21:05:36 +000078 return FileSpec(module_file_spec.GetPath() + kSymFileExtension);
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000079}
80
Kate Stoneb9c1b512016-09-06 20:57:50 +000081void DeleteExistingModule(const FileSpec &root_dir_spec,
82 const FileSpec &sysroot_module_path_spec) {
83 Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_MODULES));
84 UUID module_uuid;
85 {
86 auto module_sp =
87 std::make_shared<Module>(ModuleSpec(sysroot_module_path_spec));
88 module_uuid = module_sp->GetUUID();
89 }
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000090
Kate Stoneb9c1b512016-09-06 20:57:50 +000091 if (!module_uuid.IsValid())
92 return;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +000093
Zachary Turner97206d52017-05-12 04:51:55 +000094 Status error;
Kate Stoneb9c1b512016-09-06 20:57:50 +000095 ModuleLock lock(root_dir_spec, module_uuid, error);
96 if (error.Fail()) {
97 if (log)
98 log->Printf("Failed to lock module %s: %s",
99 module_uuid.GetAsString().c_str(), error.AsCString());
100 }
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000101
Zachary Turner07db3f72017-03-21 05:47:57 +0000102 namespace fs = llvm::sys::fs;
103 fs::file_status st;
104 if (status(sysroot_module_path_spec.GetPath(), st))
Kate Stoneb9c1b512016-09-06 20:57:50 +0000105 return;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000106
Zachary Turner07db3f72017-03-21 05:47:57 +0000107 if (st.getLinkCount() > 2) // module is referred by other hosts.
Kate Stoneb9c1b512016-09-06 20:57:50 +0000108 return;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000109
Kate Stoneb9c1b512016-09-06 20:57:50 +0000110 const auto module_spec_dir = GetModuleDirectory(root_dir_spec, module_uuid);
Zachary Turnerd82067f2017-03-09 05:12:36 +0000111 llvm::sys::fs::remove_directories(module_spec_dir.GetPath());
Kate Stoneb9c1b512016-09-06 20:57:50 +0000112 lock.Delete();
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000113}
114
Kate Stoneb9c1b512016-09-06 20:57:50 +0000115void DecrementRefExistingModule(const FileSpec &root_dir_spec,
116 const FileSpec &sysroot_module_path_spec) {
117 // Remove $platform/.cache/$uuid folder if nobody else references it.
118 DeleteExistingModule(root_dir_spec, sysroot_module_path_spec);
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000119
Kate Stoneb9c1b512016-09-06 20:57:50 +0000120 // Remove sysroot link.
Zachary Turner07db3f72017-03-21 05:47:57 +0000121 llvm::sys::fs::remove(sysroot_module_path_spec.GetPath());
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000122
Kate Stoneb9c1b512016-09-06 20:57:50 +0000123 FileSpec symfile_spec = GetSymbolFileSpec(sysroot_module_path_spec);
Zachary Turner07db3f72017-03-21 05:47:57 +0000124 llvm::sys::fs::remove(symfile_spec.GetPath());
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000125}
126
Zachary Turner97206d52017-05-12 04:51:55 +0000127Status CreateHostSysRootModuleLink(const FileSpec &root_dir_spec,
128 const char *hostname,
129 const FileSpec &platform_module_spec,
130 const FileSpec &local_module_spec,
131 bool delete_existing) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000132 const auto sysroot_module_path_spec =
133 JoinPath(JoinPath(root_dir_spec, hostname),
134 platform_module_spec.GetPath().c_str());
Jonas Devliegheredbd7fab2018-11-01 17:09:25 +0000135 if (FileSystem::Instance().Exists(sysroot_module_path_spec)) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000136 if (!delete_existing)
Zachary Turner97206d52017-05-12 04:51:55 +0000137 return Status();
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000138
Kate Stoneb9c1b512016-09-06 20:57:50 +0000139 DecrementRefExistingModule(root_dir_spec, sysroot_module_path_spec);
140 }
Oleksiy Vyalova9ea0712015-05-08 23:54:34 +0000141
Kate Stoneb9c1b512016-09-06 20:57:50 +0000142 const auto error = MakeDirectory(
Jonas Devlieghere8f3be7a2018-11-01 21:05:36 +0000143 FileSpec(sysroot_module_path_spec.GetDirectory().AsCString()));
Kate Stoneb9c1b512016-09-06 20:57:50 +0000144 if (error.Fail())
145 return error;
Oleksiy Vyalova9ea0712015-05-08 23:54:34 +0000146
Zachary Turner07db3f72017-03-21 05:47:57 +0000147 return llvm::sys::fs::create_hard_link(local_module_spec.GetPath(),
148 sysroot_module_path_spec.GetPath());
Oleksiy Vyalova9ea0712015-05-08 23:54:34 +0000149}
150
Kate Stoneb9c1b512016-09-06 20:57:50 +0000151} // namespace
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000152
Kate Stoneb9c1b512016-09-06 20:57:50 +0000153ModuleLock::ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid,
Zachary Turner97206d52017-05-12 04:51:55 +0000154 Status &error) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000155 const auto lock_dir_spec = JoinPath(root_dir_spec, kLockDirName);
156 error = MakeDirectory(lock_dir_spec);
157 if (error.Fail())
158 return;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000159
Kate Stoneb9c1b512016-09-06 20:57:50 +0000160 m_file_spec = JoinPath(lock_dir_spec, uuid.GetAsString().c_str());
Jonas Devlieghere50bc1ed2018-11-02 22:34:51 +0000161 FileSystem::Instance().Open(m_file, m_file_spec,
162 File::eOpenOptionWrite |
163 File::eOpenOptionCanCreate |
164 File::eOpenOptionCloseOnExec);
Kate Stoneb9c1b512016-09-06 20:57:50 +0000165 if (!m_file) {
166 error.SetErrorToErrno();
167 return;
168 }
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000169
Kate Stoneb9c1b512016-09-06 20:57:50 +0000170 m_lock.reset(new lldb_private::LockFile(m_file.GetDescriptor()));
171 error = m_lock->WriteLock(0, 1);
172 if (error.Fail())
173 error.SetErrorStringWithFormat("Failed to lock file: %s",
174 error.AsCString());
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000175}
176
Kate Stoneb9c1b512016-09-06 20:57:50 +0000177void ModuleLock::Delete() {
178 if (!m_file)
179 return;
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000180
Kate Stoneb9c1b512016-09-06 20:57:50 +0000181 m_file.Close();
Zachary Turner07db3f72017-03-21 05:47:57 +0000182 llvm::sys::fs::remove(m_file_spec.GetPath());
Oleksiy Vyalov2b38f332015-09-18 18:12:39 +0000183}
184
185/////////////////////////////////////////////////////////////////////////
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000186
Zachary Turner97206d52017-05-12 04:51:55 +0000187Status ModuleCache::Put(const FileSpec &root_dir_spec, const char *hostname,
188 const ModuleSpec &module_spec, const FileSpec &tmp_file,
189 const FileSpec &target_file) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000190 const auto module_spec_dir =
191 GetModuleDirectory(root_dir_spec, module_spec.GetUUID());
192 const auto module_file_path =
193 JoinPath(module_spec_dir, target_file.GetFilename().AsCString());
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000194
Kate Stoneb9c1b512016-09-06 20:57:50 +0000195 const auto tmp_file_path = tmp_file.GetPath();
Malcolm Parsons771ef6d2016-11-02 20:34:10 +0000196 const auto err_code =
197 llvm::sys::fs::rename(tmp_file_path, module_file_path.GetPath());
Kate Stoneb9c1b512016-09-06 20:57:50 +0000198 if (err_code)
Zachary Turner97206d52017-05-12 04:51:55 +0000199 return Status("Failed to rename file %s to %s: %s", tmp_file_path.c_str(),
200 module_file_path.GetPath().c_str(),
201 err_code.message().c_str());
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000202
Kate Stoneb9c1b512016-09-06 20:57:50 +0000203 const auto error = CreateHostSysRootModuleLink(
204 root_dir_spec, hostname, target_file, module_file_path, true);
205 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000206 return Status("Failed to create link to %s: %s",
207 module_file_path.GetPath().c_str(), error.AsCString());
208 return Status();
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000209}
210
Zachary Turner97206d52017-05-12 04:51:55 +0000211Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname,
212 const ModuleSpec &module_spec,
213 ModuleSP &cached_module_sp, bool *did_create_ptr) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000214 const auto find_it =
215 m_loaded_modules.find(module_spec.GetUUID().GetAsString());
216 if (find_it != m_loaded_modules.end()) {
217 cached_module_sp = (*find_it).second.lock();
218 if (cached_module_sp)
Zachary Turner97206d52017-05-12 04:51:55 +0000219 return Status();
Kate Stoneb9c1b512016-09-06 20:57:50 +0000220 m_loaded_modules.erase(find_it);
221 }
Oleksiy Vyaloveda270e2015-03-12 18:18:03 +0000222
Kate Stoneb9c1b512016-09-06 20:57:50 +0000223 const auto module_spec_dir =
224 GetModuleDirectory(root_dir_spec, module_spec.GetUUID());
225 const auto module_file_path = JoinPath(
226 module_spec_dir, module_spec.GetFileSpec().GetFilename().AsCString());
Oleksiy Vyaloveda270e2015-03-12 18:18:03 +0000227
Jonas Devliegheredbd7fab2018-11-01 17:09:25 +0000228 if (!FileSystem::Instance().Exists(module_file_path))
Zachary Turner97206d52017-05-12 04:51:55 +0000229 return Status("Module %s not found", module_file_path.GetPath().c_str());
Jonas Devlieghere59b78bc2018-11-01 04:45:28 +0000230 if (FileSystem::Instance().GetByteSize(module_file_path) !=
231 module_spec.GetObjectSize())
Zachary Turner97206d52017-05-12 04:51:55 +0000232 return Status("Module %s has invalid file size",
233 module_file_path.GetPath().c_str());
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000234
Kate Stoneb9c1b512016-09-06 20:57:50 +0000235 // We may have already cached module but downloaded from an another host - in
236 // this case let's create a link to it.
237 auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname,
238 module_spec.GetFileSpec(),
239 module_file_path, false);
240 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000241 return Status("Failed to create link to %s: %s",
242 module_file_path.GetPath().c_str(), error.AsCString());
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000243
Kate Stoneb9c1b512016-09-06 20:57:50 +0000244 auto cached_module_spec(module_spec);
245 cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5
246 // content hash instead of real UUID.
247 cached_module_spec.GetFileSpec() = module_file_path;
248 cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec();
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000249
Kate Stoneb9c1b512016-09-06 20:57:50 +0000250 error = ModuleList::GetSharedModule(cached_module_spec, cached_module_sp,
251 nullptr, nullptr, did_create_ptr, false);
252 if (error.Fail())
253 return error;
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000254
Kate Stoneb9c1b512016-09-06 20:57:50 +0000255 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec());
Jonas Devliegheredbd7fab2018-11-01 17:09:25 +0000256 if (FileSystem::Instance().Exists(symfile_spec))
Kate Stoneb9c1b512016-09-06 20:57:50 +0000257 cached_module_sp->SetSymbolFileFileSpec(symfile_spec);
Oleksiy Vyaloveda270e2015-03-12 18:18:03 +0000258
Kate Stoneb9c1b512016-09-06 20:57:50 +0000259 m_loaded_modules.insert(
260 std::make_pair(module_spec.GetUUID().GetAsString(), cached_module_sp));
261
Zachary Turner97206d52017-05-12 04:51:55 +0000262 return Status();
Oleksiy Vyalov63acdfd2015-03-10 01:15:28 +0000263}
264
Zachary Turner97206d52017-05-12 04:51:55 +0000265Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec,
266 const char *hostname,
267 const ModuleSpec &module_spec,
268 const ModuleDownloader &module_downloader,
269 const SymfileDownloader &symfile_downloader,
270 lldb::ModuleSP &cached_module_sp,
271 bool *did_create_ptr) {
Kate Stoneb9c1b512016-09-06 20:57:50 +0000272 const auto module_spec_dir =
273 GetModuleDirectory(root_dir_spec, module_spec.GetUUID());
274 auto error = MakeDirectory(module_spec_dir);
275 if (error.Fail())
276 return error;
Oleksiy Vyalov919ef9d2015-05-07 15:28:49 +0000277
Kate Stoneb9c1b512016-09-06 20:57:50 +0000278 ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error);
279 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000280 return Status("Failed to lock module %s: %s",
281 module_spec.GetUUID().GetAsString().c_str(),
282 error.AsCString());
Oleksiy Vyalov919ef9d2015-05-07 15:28:49 +0000283
Kate Stoneb9c1b512016-09-06 20:57:50 +0000284 const auto escaped_hostname(GetEscapedHostname(hostname));
285 // Check local cache for a module.
286 error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec,
287 cached_module_sp, did_create_ptr);
288 if (error.Success())
289 return error;
Oleksiy Vyalov280d8dc2015-04-15 14:35:10 +0000290
Kate Stoneb9c1b512016-09-06 20:57:50 +0000291 const auto tmp_download_file_spec = JoinPath(module_spec_dir, kTempFileName);
292 error = module_downloader(module_spec, tmp_download_file_spec);
Malcolm Parsons771ef6d2016-11-02 20:34:10 +0000293 llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath());
Kate Stoneb9c1b512016-09-06 20:57:50 +0000294 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000295 return Status("Failed to download module: %s", error.AsCString());
Oleksiy Vyalov280d8dc2015-04-15 14:35:10 +0000296
Kate Stoneb9c1b512016-09-06 20:57:50 +0000297 // Put downloaded file into local module cache.
298 error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec,
299 tmp_download_file_spec, module_spec.GetFileSpec());
300 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000301 return Status("Failed to put module into cache: %s", error.AsCString());
Oleksiy Vyalov280d8dc2015-04-15 14:35:10 +0000302
Kate Stoneb9c1b512016-09-06 20:57:50 +0000303 tmp_file_remover.releaseFile();
304 error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec,
305 cached_module_sp, did_create_ptr);
306 if (error.Fail())
307 return error;
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000308
Kate Stoneb9c1b512016-09-06 20:57:50 +0000309 // Fetching a symbol file for the module
310 const auto tmp_download_sym_file_spec =
311 JoinPath(module_spec_dir, kTempSymFileName);
312 error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec);
Malcolm Parsons771ef6d2016-11-02 20:34:10 +0000313 llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath());
Kate Stoneb9c1b512016-09-06 20:57:50 +0000314 if (error.Fail())
315 // Failed to download a symfile but fetching the module was successful. The
Adrian Prantl05097242018-04-30 16:49:04 +0000316 // module might contain the necessary symbols and the debugging is also
317 // possible without a symfile.
Zachary Turner97206d52017-05-12 04:51:55 +0000318 return Status();
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000319
Kate Stoneb9c1b512016-09-06 20:57:50 +0000320 error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec,
321 tmp_download_sym_file_spec,
322 GetSymbolFileSpec(module_spec.GetFileSpec()));
323 if (error.Fail())
Zachary Turner97206d52017-05-12 04:51:55 +0000324 return Status("Failed to put symbol file into cache: %s",
325 error.AsCString());
Tamas Berghammerec3f92a2015-08-12 11:10:25 +0000326
Kate Stoneb9c1b512016-09-06 20:57:50 +0000327 tmp_symfile_remover.releaseFile();
328
329 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec());
330 cached_module_sp->SetSymbolFileFileSpec(symfile_spec);
Zachary Turner97206d52017-05-12 04:51:55 +0000331 return Status();
Oleksiy Vyalov280d8dc2015-04-15 14:35:10 +0000332}