main.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. #include <library/cpp/archive/yarchive.h>
  2. #include <library/cpp/deprecated/mapped_file/mapped_file.h>
  3. #include <library/cpp/digest/md5/md5.h>
  4. #include <library/cpp/getopt/small/last_getopt.h>
  5. #include <util/folder/dirut.h>
  6. #include <util/folder/filelist.h>
  7. #include <util/folder/path.h>
  8. #include <util/generic/vector.h>
  9. #include <util/generic/yexception.h>
  10. #include <util/memory/blob.h>
  11. #include <util/stream/file.h>
  12. #include <util/string/cast.h>
  13. #include <util/string/escape.h>
  14. #include <util/string/hex.h>
  15. #include <util/string/subst.h>
  16. #include <util/system/filemap.h>
  17. #include <cstring>
  18. namespace {
  19. class TStringArrayOutput: public IOutputStream {
  20. public:
  21. TStringArrayOutput(IOutputStream* slave, size_t stride)
  22. : Slave(*slave)
  23. , Stride(stride)
  24. {
  25. Buf.reserve(stride);
  26. }
  27. void DoFinish() override {
  28. WriteBuf();
  29. Flush();
  30. }
  31. void DoWrite(const void* data, size_t len) override {
  32. for (const char* p = (const char*)data; len > 0; ++p, --len) {
  33. Buf.append(*p);
  34. if (Buf.size() == Stride)
  35. WriteBuf();
  36. }
  37. }
  38. private:
  39. void WriteBuf() {
  40. Slave << '"' << Buf << "\",\n"sv;
  41. Buf.clear();
  42. }
  43. private:
  44. IOutputStream& Slave;
  45. const size_t Stride;
  46. TString Buf;
  47. };
  48. class THexOutput: public IOutputStream {
  49. public:
  50. inline THexOutput(IOutputStream* slave)
  51. : Slave_(slave)
  52. {
  53. }
  54. ~THexOutput() override {
  55. }
  56. inline IOutputStream* Slave() const noexcept {
  57. return Slave_;
  58. }
  59. private:
  60. void DoFinish() override {
  61. Slave_->Write('\n');
  62. Slave_->Flush();
  63. }
  64. void DoWrite(const void* data, size_t len) override {
  65. const char* b = (const char*)data;
  66. while (len) {
  67. const unsigned char c = *b;
  68. char buf[12];
  69. char* tmp = buf;
  70. if (Count_ % Columns == 0) {
  71. *tmp++ = ' ';
  72. *tmp++ = ' ';
  73. *tmp++ = ' ';
  74. *tmp++ = ' ';
  75. }
  76. if (Count_ && Count_ % Columns != 0) {
  77. *tmp++ = ',';
  78. *tmp++ = ' ';
  79. }
  80. *tmp++ = '0';
  81. *tmp++ = 'x';
  82. tmp = HexEncode(&c, 1, tmp);
  83. if ((Count_ % Columns) == (Columns - 1)) {
  84. *tmp++ = ',';
  85. *tmp++ = '\n';
  86. }
  87. Slave_->Write(buf, tmp - buf);
  88. --len;
  89. ++b;
  90. ++Count_;
  91. }
  92. }
  93. private:
  94. // width in source chars
  95. static const size_t Columns = 10;
  96. ui64 Count_ = 0;
  97. IOutputStream* Slave_ = nullptr;
  98. };
  99. struct TYasmOutput: public IOutputStream {
  100. inline TYasmOutput(IOutputStream* out, const TString& base)
  101. : Out_(out)
  102. , Base_(base)
  103. {
  104. *Out_ << "global " << Base_ << "\n";
  105. *Out_ << "global " << Base_ << "Size\n\nSECTION .rodata\n\n";
  106. *Out_ << Base_ << ":\n";
  107. }
  108. ~TYasmOutput() override {
  109. }
  110. void DoFinish() override {
  111. *Out_ << Base_ << "Size:\ndd " << Count_ << '\n';
  112. *Out_ << "%ifidn __OUTPUT_FORMAT__,elf64\n";
  113. *Out_ << "size " << Base_ << " " << Count_ << "\n";
  114. *Out_ << "size " << Base_ << "Size 4\n";
  115. *Out_ << "%endif\n";
  116. }
  117. void DoWrite(const void* data, size_t len) override {
  118. Count_ += len;
  119. const unsigned char* p = (const unsigned char*)data;
  120. while (len) {
  121. const size_t step = Min<size_t>(len, 100);
  122. *Out_ << "db " << (int)*p++;
  123. for (size_t i = 1; i < step; ++i) {
  124. *Out_ << ',' << (int)*p++;
  125. }
  126. *Out_ << '\n';
  127. len -= step;
  128. }
  129. }
  130. IOutputStream* Out_ = nullptr;
  131. const TString Base_;
  132. ui64 Count_ = 0;
  133. };
  134. struct TCOutput: public THexOutput {
  135. inline TCOutput(IOutputStream* out, const TString& base)
  136. : THexOutput(out)
  137. , B(base)
  138. {
  139. *Slave() << "static_assert(sizeof(unsigned int) == 4, \"ups, unsupported platform\");\n\nextern \"C\" {\nextern const unsigned char " << B << "[] = {\n";
  140. }
  141. ~TCOutput() override {
  142. }
  143. void DoFinish() override {
  144. *Slave() << "\n};\nextern const unsigned int " << B << "Size = sizeof(" << B << ") / sizeof(" << B << "[0]);\n}\n";
  145. }
  146. const TString B;
  147. };
  148. struct TCStringOutput: public IOutputStream {
  149. inline TCStringOutput(IOutputStream* out, const TString& base)
  150. : O(out)
  151. , B(base)
  152. {
  153. *O << "static_assert(sizeof(unsigned int) == 4, \"ups, unsupported platform\");\n\nextern \"C\" {\nextern const unsigned char " << B << "[] = \n";
  154. }
  155. ~TCStringOutput() override {
  156. }
  157. void DoWrite(const void* data, size_t len) override {
  158. *O << TString((const char*)data, len).Quote() << '\n';
  159. }
  160. void DoFinish() override {
  161. //*O << ";\nextern const unsigned char* " << B << " = (const unsigned char*)" << B << "Array;\n";
  162. *O << ";\nextern const unsigned int " << B << "Size = sizeof(" << B << ") / sizeof(" << B << "[0]) - 1;\n}\n";
  163. }
  164. IOutputStream* O = nullptr;
  165. const TString B;
  166. };
  167. struct TMyFileComparator {
  168. bool operator()(const TString& fname1, const TString& fname2) const {
  169. if (fname1 == fname2) {
  170. return false;
  171. }
  172. if (const auto* savedResultPtr = SavedResults.FindPtr(std::make_pair(fname1, fname2))) {
  173. return *savedResultPtr < 0;
  174. }
  175. TMemoryMap mmap1(fname1, TMemoryMap::oRdOnly);
  176. TMemoryMap mmap2(fname2, TMemoryMap::oRdOnly);
  177. mmap1.SetSequential();
  178. mmap2.SetSequential();
  179. Y_ASSERT(mmap1.Length() == mmap2.Length());
  180. TMemoryMap::TMapResult mapResult1 = mmap1.Map(0, mmap1.Length());
  181. TMemoryMap::TMapResult mapResult2 = mmap2.Map(0, mmap2.Length());
  182. Y_ASSERT(mapResult1.MappedSize() == mapResult2.MappedSize());
  183. int res = memcmp(mapResult1.MappedData(), mapResult2.MappedData(), mapResult1.MappedSize());
  184. mmap1.Unmap(mapResult1);
  185. mmap2.Unmap(mapResult2);
  186. SavedResults[std::make_pair(fname1, fname2)] = res;
  187. SavedResults[std::make_pair(fname2, fname1)] = -res;
  188. return res < 0;
  189. }
  190. mutable THashMap<std::pair<TString, TString>, int> SavedResults;
  191. };
  192. struct TDuplicatesMap {
  193. void Add(const TString& fname, const TString& rname) {
  194. Y_ENSURE(!InitialFillingDone);
  195. FileNames.push_back(fname);
  196. FileNameToRecordName[fname] = rname;
  197. }
  198. void Finish() {
  199. Y_ENSURE(!InitialFillingDone);
  200. InitialFillingDone = true;
  201. TMap<i64, TVector<TString>> bySize;
  202. for (const TString& fname: FileNames) {
  203. TFile file(fname, OpenExisting | RdOnly);
  204. bySize[file.GetLength()].push_back(fname);
  205. }
  206. for (const auto& bySizeElement: bySize) {
  207. if (bySizeElement.second.size() > 1) {
  208. TMap<TString, TVector<TString>, TMyFileComparator> byContents;
  209. for (const TString& fname: bySizeElement.second) {
  210. byContents[fname].push_back(fname);
  211. }
  212. for (const auto& byContentsElement: byContents) {
  213. if (byContentsElement.second.size() > 1) {
  214. const TString& rootName = byContentsElement.second.front();
  215. const TString& rootRecordName = FileNameToRecordName[rootName];
  216. for (const TString& fname: byContentsElement.second) {
  217. if (fname != rootName) {
  218. Synonyms[FileNameToRecordName[fname]] = rootRecordName;
  219. }
  220. }
  221. }
  222. }
  223. }
  224. }
  225. FileNames.clear();
  226. FileNameToRecordName.clear();
  227. }
  228. bool InitialFillingDone = false;
  229. TVector<TString> FileNames;
  230. THashMap<TString, TString> FileNameToRecordName;
  231. THashMap<TString, TString> Synonyms;
  232. };
  233. struct TDeduplicationArchiveWriter {
  234. TDeduplicationArchiveWriter(const TDuplicatesMap& duplicatesMap, IOutputStream* out, bool compress)
  235. : DuplicatesMap(duplicatesMap)
  236. , Writer(out, compress)
  237. {}
  238. void Finish() {
  239. Writer.Finish();
  240. }
  241. const TDuplicatesMap& DuplicatesMap;
  242. TArchiveWriter Writer;
  243. };
  244. }
  245. static inline TAutoPtr<IOutputStream> OpenOutput(const TString& url) {
  246. if (url.empty()) {
  247. return new TBuffered<TUnbufferedFileOutput>(8192, Duplicate(1));
  248. } else {
  249. return new TBuffered<TUnbufferedFileOutput>(8192, url);
  250. }
  251. }
  252. static inline bool IsDelim(char ch) noexcept {
  253. return ch == '/' || ch == '\\';
  254. }
  255. static inline TString GetFile(const TString& s) {
  256. const char* e = s.end();
  257. const char* b = s.begin();
  258. const char* c = e - 1;
  259. while (c != b && !IsDelim(*c)) {
  260. --c;
  261. }
  262. if (c != e && IsDelim(*c)) {
  263. ++c;
  264. }
  265. return TString(c, e - c);
  266. }
  267. static inline TString Fix(TString f) {
  268. if (!f.empty() && IsDelim(f[f.size() - 1])) {
  269. f.pop_back();
  270. }
  271. return f;
  272. }
  273. static bool Quiet = false;
  274. static inline void Append(IOutputStream& w, const TString& fname, const TString& rname) {
  275. TMappedFileInput in(fname);
  276. if (!Quiet) {
  277. Cerr << "--> " << rname << Endl;
  278. }
  279. TransferData((IInputStream*)&in, &w);
  280. }
  281. static inline void Append(TDuplicatesMap& w, const TString& fname, const TString& rname) {
  282. w.Add(fname, rname);
  283. }
  284. static inline void Append(TDeduplicationArchiveWriter& w, const TString& fname, const TString& rname) {
  285. if (!Quiet) {
  286. Cerr << "--> " << rname << Endl;
  287. }
  288. if (const TString* rootRecordName = w.DuplicatesMap.Synonyms.FindPtr(rname)) {
  289. w.Writer.AddSynonym(*rootRecordName, rname);
  290. } else {
  291. TMappedFileInput in(fname);
  292. w.Writer.Add(rname, &in);
  293. }
  294. }
  295. namespace {
  296. struct TRec {
  297. bool Recursive = false;
  298. TString Key;
  299. TString Path;
  300. TString Prefix;
  301. TRec() = default;
  302. inline void Fix() {
  303. ::Fix(Path);
  304. ::Fix(Prefix);
  305. }
  306. template <typename T>
  307. inline void Recurse(T& w) const {
  308. if (IsDir(Path)) {
  309. DoRecurse(w, "/");
  310. } else {
  311. Append(w, Path, Key.size() ? Key : Prefix + "/" + GetFile(Path));
  312. }
  313. }
  314. template <typename T>
  315. inline void DoRecurse(T& w, const TString& off) const {
  316. {
  317. TFileList fl;
  318. const char* name;
  319. const TString p = Path + off;
  320. fl.Fill(p, true);
  321. while ((name = fl.Next())) {
  322. const TString fname = p + name;
  323. const TString rname = Prefix + off + name;
  324. Append(w, fname, rname);
  325. }
  326. }
  327. if (Recursive) {
  328. TDirsList dl;
  329. const char* name;
  330. const TString p = Path + off;
  331. dl.Fill(p, true);
  332. while ((name = dl.Next())) {
  333. if (strcmp(name, ".") && strcmp(name, "..")) {
  334. DoRecurse(w, off + name + "/");
  335. }
  336. }
  337. }
  338. }
  339. };
  340. }
  341. static TString CutFirstSlash(const TString& fileName) {
  342. if (fileName[0] == '/') {
  343. return fileName.substr(1);
  344. } else {
  345. return fileName;
  346. }
  347. }
  348. struct TMappingReader {
  349. TMemoryMap Map;
  350. TBlob Blob;
  351. TArchiveReader Reader;
  352. TMappingReader(const TString& archive)
  353. : Map(archive)
  354. , Blob(TBlob::FromMemoryMapSingleThreaded(Map, 0, Map.Length()))
  355. , Reader(Blob)
  356. {
  357. }
  358. };
  359. static void UnpackArchive(const TString& archive, const TFsPath& dir = TFsPath()) {
  360. TMappingReader mappingReader(archive);
  361. const TArchiveReader& reader = mappingReader.Reader;
  362. const size_t count = reader.Count();
  363. for (size_t i = 0; i < count; ++i) {
  364. const TString key = reader.KeyByIndex(i);
  365. const TString fileName = CutFirstSlash(key);
  366. if (!Quiet) {
  367. Cerr << archive << " --> " << fileName << Endl;
  368. }
  369. const TFsPath path(dir / fileName);
  370. path.Parent().MkDirs();
  371. TAutoPtr<IInputStream> in = reader.ObjectByKey(key);
  372. TFixedBufferFileOutput out(path);
  373. TransferData(in.Get(), &out);
  374. out.Finish();
  375. }
  376. }
  377. static void ListArchive(const TString& archive, bool cutSlash) {
  378. TMappingReader mappingReader(archive);
  379. const TArchiveReader& reader = mappingReader.Reader;
  380. const size_t count = reader.Count();
  381. for (size_t i = 0; i < count; ++i) {
  382. const TString key = reader.KeyByIndex(i);
  383. TString fileName = key;
  384. if (cutSlash) {
  385. fileName = CutFirstSlash(key);
  386. }
  387. Cout << fileName << Endl;
  388. }
  389. }
  390. static void ListArchiveMd5(const TString& archive, bool cutSlash) {
  391. TMappingReader mappingReader(archive);
  392. const TArchiveReader& reader = mappingReader.Reader;
  393. const size_t count = reader.Count();
  394. for (size_t i = 0; i < count; ++i) {
  395. const TString key = reader.KeyByIndex(i);
  396. TString fileName = key;
  397. if (cutSlash) {
  398. fileName = CutFirstSlash(key);
  399. }
  400. char md5buf[33];
  401. Cout << fileName << '\t' << MD5::Stream(reader.ObjectByKey(key).Get(), md5buf) << Endl;
  402. }
  403. }
  404. int main(int argc, char** argv) {
  405. NLastGetopt::TOpts opts;
  406. opts.AddHelpOption('?');
  407. opts.SetTitle(
  408. "Archiver\n"
  409. "Docs: https://wiki.yandex-team.ru/Development/Poisk/arcadia/tools/archiver"
  410. );
  411. bool hexdump = false;
  412. opts.AddLongOption('x', "hexdump", "Produce hexdump")
  413. .NoArgument()
  414. .Optional()
  415. .StoreValue(&hexdump, true);
  416. size_t stride = 0;
  417. opts.AddLongOption('s', "segments", "Produce segmented C strings array of given size")
  418. .RequiredArgument("<size>")
  419. .Optional()
  420. .DefaultValue("0")
  421. .StoreResult(&stride);
  422. bool cat = false;
  423. opts.AddLongOption('c', "cat", "Do not store keys (file names), just cat uncompressed files")
  424. .NoArgument()
  425. .Optional()
  426. .StoreValue(&cat, true);
  427. bool doNotZip = false;
  428. opts.AddLongOption('p', "plain", "Do not use compression")
  429. .NoArgument()
  430. .Optional()
  431. .StoreValue(&doNotZip, true);
  432. bool deduplicate = false;
  433. opts.AddLongOption("deduplicate", "Turn on file-wise deduplication")
  434. .NoArgument()
  435. .Optional()
  436. .StoreValue(&deduplicate, true);
  437. bool unpack = false;
  438. opts.AddLongOption('u', "unpack", "Unpack archive into current directory")
  439. .NoArgument()
  440. .Optional()
  441. .StoreValue(&unpack, true);
  442. bool list = false;
  443. opts.AddLongOption('l', "list", "List files in archive")
  444. .NoArgument()
  445. .Optional()
  446. .StoreValue(&list, true);
  447. bool cutSlash = true;
  448. opts.AddLongOption("as-is", "somewhy slash is cutted by default in list; with this option key will be shown as-is")
  449. .NoArgument()
  450. .Optional()
  451. .StoreValue(&cutSlash, false);
  452. bool listMd5 = false;
  453. opts.AddLongOption('m', "md5", "List files in archive with MD5 sums")
  454. .NoArgument()
  455. .Optional()
  456. .StoreValue(&listMd5, true);
  457. bool recursive = false;
  458. opts.AddLongOption('r', "recursive", "Read all files under each directory, recursively")
  459. .NoArgument()
  460. .Optional()
  461. .StoreValue(&recursive, true);
  462. Quiet = false;
  463. opts.AddLongOption('q', "quiet", "Do not output progress to stderr")
  464. .NoArgument()
  465. .Optional()
  466. .StoreValue(&Quiet, true);
  467. TString prepend;
  468. opts.AddLongOption('z', "prepend", "Prepend string to output")
  469. .RequiredArgument("<prefix>")
  470. .StoreResult(&prepend);
  471. TString append;
  472. opts.AddLongOption('a', "append", "Append string to output")
  473. .RequiredArgument("<suffix>")
  474. .StoreResult(&append);
  475. TString outputf;
  476. opts.AddLongOption('o', "output", "Output to file instead stdout")
  477. .RequiredArgument("<file>")
  478. .StoreResult(&outputf);
  479. TString unpackDir;
  480. opts.AddLongOption('d', "unpackdir", "Unpack destination directory")
  481. .RequiredArgument("<dir>")
  482. .DefaultValue(".")
  483. .StoreResult(&unpackDir);
  484. TString yasmBase;
  485. opts.AddLongOption('A', "yasm", "Output dump is yasm format")
  486. .RequiredArgument("<base>")
  487. .StoreResult(&yasmBase);
  488. TString cppBase;
  489. opts.AddLongOption('C', "cpp", "Output dump is C/C++ format")
  490. .RequiredArgument("<base>")
  491. .StoreResult(&cppBase);
  492. TString forceKeys;
  493. opts.AddLongOption('k', "keys", "Set explicit list of keys for elements")
  494. .RequiredArgument("<keys>")
  495. .StoreResult(&forceKeys);
  496. opts.SetFreeArgDefaultTitle("<file>");
  497. opts.SetFreeArgsMin(1);
  498. NLastGetopt::TOptsParseResult optsRes(&opts, argc, argv);
  499. SubstGlobal(append, "\\n", "\n");
  500. SubstGlobal(prepend, "\\n", "\n");
  501. TVector<TRec> recs;
  502. const auto& files = optsRes.GetFreeArgs();
  503. TVector<TStringBuf> keys;
  504. if (forceKeys.size())
  505. StringSplitter(forceKeys).Split(':').SkipEmpty().Collect(&keys);
  506. if (keys.size() && keys.size() != files.size()) {
  507. Cerr << "Invalid number of keys=" << keys.size() << " (!= number of files=" << files.size() << ")" << Endl;
  508. return 1;
  509. }
  510. for (size_t i = 0; i < files.size(); ++i) {
  511. const auto& path = files[i];
  512. size_t off = 0;
  513. #ifdef _win_
  514. if (path[0] > 0 && isalpha(path[0]) && path[1] == ':')
  515. off = 2; // skip drive letter ("d:")
  516. #endif // _win_
  517. const size_t pos = path.find(':', off);
  518. TRec cur;
  519. cur.Path = path.substr(0, pos);
  520. if (pos != TString::npos)
  521. cur.Prefix = path.substr(pos + 1);
  522. if (keys.size())
  523. cur.Key = keys[i];
  524. cur.Recursive = recursive;
  525. cur.Fix();
  526. recs.push_back(cur);
  527. }
  528. try {
  529. if (listMd5) {
  530. for (const auto& rec: recs) {
  531. ListArchiveMd5(rec.Path, cutSlash);
  532. }
  533. } else if (list) {
  534. for (const auto& rec: recs) {
  535. ListArchive(rec.Path, cutSlash);
  536. }
  537. } else if (unpack) {
  538. const TFsPath dir(unpackDir);
  539. for (const auto& rec: recs) {
  540. UnpackArchive(rec.Path, dir);
  541. }
  542. } else {
  543. TAutoPtr<IOutputStream> outf(OpenOutput(outputf));
  544. IOutputStream* out = outf.Get();
  545. THolder<IOutputStream> hexout;
  546. if (hexdump) {
  547. hexout.Reset(new THexOutput(out));
  548. out = hexout.Get();
  549. } else if (stride) {
  550. hexout.Reset(new TStringArrayOutput(out, stride));
  551. out = hexout.Get();
  552. } else if (yasmBase) {
  553. hexout.Reset(new TYasmOutput(out, yasmBase));
  554. out = hexout.Get();
  555. } else if (cppBase) {
  556. hexout.Reset(new TCStringOutput(out, cppBase));
  557. out = hexout.Get();
  558. }
  559. outf->Write(prepend.data(), prepend.size());
  560. if (cat) {
  561. for (const auto& rec: recs) {
  562. rec.Recurse(*out);
  563. }
  564. } else {
  565. TDuplicatesMap duplicatesMap;
  566. if (deduplicate) {
  567. for (const auto& rec: recs) {
  568. rec.Recurse(duplicatesMap);
  569. }
  570. }
  571. duplicatesMap.Finish();
  572. TDeduplicationArchiveWriter w(duplicatesMap, out, !doNotZip);
  573. for (const auto& rec: recs) {
  574. rec.Recurse(w);
  575. }
  576. w.Finish();
  577. }
  578. try {
  579. out->Finish();
  580. } catch (...) {
  581. }
  582. outf->Write(append.data(), append.size());
  583. }
  584. } catch (...) {
  585. Cerr << CurrentExceptionMessage() << Endl;
  586. return 1;
  587. }
  588. return 0;
  589. }