]>
Commit | Line | Data |
---|---|---|
ee0167c4 | 1 | // Includes /*{{{*/ |
453b82a3 DK |
2 | #include <config.h> |
3 | ||
b9179170 | 4 | #include <apt-pkg/cachefile.h> |
b9179170 | 5 | #include <apt-pkg/cacheset.h> |
b9179170 | 6 | #include <apt-pkg/cmndline.h> |
453b82a3 | 7 | #include <apt-pkg/error.h> |
b9179170 | 8 | #include <apt-pkg/fileutl.h> |
453b82a3 | 9 | #include <apt-pkg/indexfile.h> |
b9179170 | 10 | #include <apt-pkg/pkgrecords.h> |
b9179170 | 11 | #include <apt-pkg/pkgsystem.h> |
453b82a3 DK |
12 | #include <apt-pkg/sourcelist.h> |
13 | #include <apt-pkg/strutl.h> | |
14 | #include <apt-pkg/tagfile.h> | |
15 | #include <apt-pkg/cacheiterators.h> | |
16 | #include <apt-pkg/configuration.h> | |
17 | #include <apt-pkg/depcache.h> | |
18 | #include <apt-pkg/macros.h> | |
19 | #include <apt-pkg/pkgcache.h> | |
9055d5e6 | 20 | #include <apt-pkg/policy.h> |
b9179170 | 21 | |
453b82a3 DK |
22 | #include <apt-private/private-cacheset.h> |
23 | #include <apt-private/private-output.h> | |
24 | #include <apt-private/private-show.h> | |
25 | ||
26 | #include <stdio.h> | |
27 | #include <ostream> | |
28 | #include <string> | |
b9179170 | 29 | |
453b82a3 | 30 | #include <apti18n.h> |
ee0167c4 | 31 | /*}}}*/ |
b9179170 | 32 | |
501cd23e DK |
33 | static bool OpenPackagesFile(pkgCacheFile &CacheFile, pkgCache::VerIterator const &V,/*{{{*/ |
34 | FileFd &PkgF, pkgCache::VerFileIterator &Vf) | |
b9179170 | 35 | { |
501cd23e | 36 | pkgCache const * const Cache = CacheFile.GetPkgCache(); |
b9179170 MV |
37 | if (unlikely(Cache == NULL)) |
38 | return false; | |
39 | ||
40 | // Find an appropriate file | |
501cd23e | 41 | Vf = V.FileList(); |
b9179170 MV |
42 | for (; Vf.end() == false; ++Vf) |
43 | if ((Vf.File()->Flags & pkgCache::Flag::NotSource) == 0) | |
44 | break; | |
45 | if (Vf.end() == true) | |
46 | Vf = V.FileList(); | |
47 | ||
48 | // Check and load the package list file | |
49 | pkgCache::PkgFileIterator I = Vf.File(); | |
50 | if (I.IsOk() == false) | |
51 | return _error->Error(_("Package file %s is out of sync."),I.FileName()); | |
52 | ||
501cd23e DK |
53 | // Read the record |
54 | return PkgF.Open(I.FileName(), FileFd::ReadOnly, FileFd::Extension); | |
55 | } | |
56 | /*}}}*/ | |
57 | static APT_PURE unsigned char const* skipDescriptionFields(unsigned char const * DescP)/*{{{*/ | |
58 | { | |
59 | char const * const TagName = "\nDescription"; | |
60 | size_t const TagLen = strlen(TagName); | |
61 | while ((DescP = (unsigned char*)strchr((char*)DescP, '\n')) != NULL) | |
62 | { | |
63 | if (DescP[1] == ' ') | |
64 | DescP += 2; | |
65 | else if (strncmp((char*)DescP, TagName, TagLen) == 0) | |
66 | DescP += TagLen; | |
67 | else | |
68 | break; | |
69 | } | |
70 | if (DescP != NULL) | |
71 | ++DescP; | |
72 | return DescP; | |
73 | } | |
74 | /*}}}*/ | |
75 | bool DisplayRecordV1(pkgCacheFile &CacheFile, pkgCache::VerIterator const &V,/*{{{*/ | |
76 | std::ostream &out) | |
77 | { | |
78 | FileFd PkgF; | |
79 | pkgCache::VerFileIterator Vf; | |
80 | if (OpenPackagesFile(CacheFile, V, PkgF, Vf) == false) | |
81 | return false; | |
82 | ||
83 | pkgCache * const Cache = CacheFile.GetPkgCache(); | |
84 | if (unlikely(Cache == NULL)) | |
85 | return false; | |
86 | ||
87 | // Read the record (and ensure that it ends with a newline and NUL) | |
88 | unsigned char *Buffer = new unsigned char[Cache->HeaderP->MaxVerFileSize+2]; | |
89 | Buffer[Vf->Size] = '\n'; | |
90 | Buffer[Vf->Size+1] = '\0'; | |
91 | if (PkgF.Seek(Vf->Offset) == false || | |
92 | PkgF.Read(Buffer,Vf->Size) == false) | |
93 | { | |
94 | delete [] Buffer; | |
95 | return false; | |
96 | } | |
97 | ||
98 | // Get a pointer to start of Description field | |
99 | const unsigned char *DescP = (unsigned char*)strstr((char*)Buffer, "\nDescription"); | |
100 | if (DescP != NULL) | |
101 | ++DescP; | |
102 | else | |
103 | DescP = Buffer + Vf->Size; | |
104 | ||
105 | // Write all but Description | |
106 | size_t const length = DescP - Buffer; | |
107 | if (length != 0 && FileFd::Write(STDOUT_FILENO, Buffer, length) == false) | |
108 | { | |
109 | delete [] Buffer; | |
110 | return false; | |
111 | } | |
112 | ||
113 | // Show the right description | |
114 | pkgRecords Recs(*Cache); | |
115 | pkgCache::DescIterator Desc = V.TranslatedDescription(); | |
116 | if (Desc.end() == false) | |
117 | { | |
118 | pkgRecords::Parser &P = Recs.Lookup(Desc.FileList()); | |
119 | out << "Description" << ( (strcmp(Desc.LanguageCode(),"") != 0) ? "-" : "" ) << Desc.LanguageCode() << ": " << P.LongDesc(); | |
120 | out << std::endl << "Description-md5: " << Desc.md5() << std::endl; | |
121 | ||
122 | // Find the first field after the description (if there is any) | |
123 | DescP = skipDescriptionFields(DescP); | |
124 | } | |
125 | // else we have no translation, so we found a lonely Description-md5 -> don't skip it | |
126 | ||
127 | // write the rest of the buffer, but skip mixed in Descriptions* fields | |
128 | while (DescP != NULL) | |
129 | { | |
130 | const unsigned char * const Start = DescP; | |
131 | const unsigned char *End = (unsigned char*)strstr((char*)DescP, "\nDescription"); | |
132 | if (End == NULL) | |
133 | { | |
134 | End = &Buffer[Vf->Size]; | |
135 | DescP = NULL; | |
136 | } | |
137 | else | |
138 | { | |
139 | ++End; // get the newline into the output | |
140 | DescP = skipDescriptionFields(End + strlen("Description")); | |
141 | } | |
142 | size_t const length = End - Start; | |
143 | if (length != 0 && FileFd::Write(STDOUT_FILENO, Start, length) == false) | |
144 | { | |
145 | delete [] Buffer; | |
146 | return false; | |
147 | } | |
148 | } | |
149 | // write a final newline after the last field | |
150 | out << std::endl; | |
151 | ||
152 | delete [] Buffer; | |
153 | return true; | |
154 | } | |
155 | /*}}}*/ | |
156 | static bool DisplayRecordV2(pkgCacheFile &CacheFile, pkgCache::VerIterator const &V,/*{{{*/ | |
157 | std::ostream &out) | |
158 | { | |
159 | FileFd PkgF; | |
160 | pkgCache::VerFileIterator Vf; | |
161 | if (OpenPackagesFile(CacheFile, V, PkgF, Vf) == false) | |
162 | return false; | |
163 | ||
164 | // Check and load the package list file | |
165 | pkgCache::PkgFileIterator I = Vf.File(); | |
166 | if (I.IsOk() == false) | |
167 | return _error->Error(_("Package file %s is out of sync."),I.FileName()); | |
168 | ||
1179953a MV |
169 | // find matching sources.list metaindex |
170 | pkgSourceList *SrcList = CacheFile.GetSourceList(); | |
171 | pkgIndexFile *Index; | |
172 | if (SrcList->FindIndex(I, Index) == false && | |
173 | _system->FindIndex(I, Index) == false) | |
174 | return _error->Error("Can not find indexfile for Package %s (%s)", | |
175 | V.ParentPkg().Name(), V.VerStr()); | |
176 | std::string source_index_file = Index->Describe(true); | |
177 | ||
b9179170 | 178 | // Read the record |
b9179170 MV |
179 | pkgTagSection Tags; |
180 | pkgTagFile TagF(&PkgF); | |
9e51c0b6 MV |
181 | |
182 | if (TagF.Jump(Tags, V.FileList()->Offset) == false) | |
183 | return _error->Error("Internal Error, Unable to parse a package record"); | |
184 | ||
185 | // make size nice | |
186 | std::string installed_size; | |
187 | if (Tags.FindI("Installed-Size") > 0) | |
17622532 | 188 | strprintf(installed_size, "%sB", SizeToStr(Tags.FindI("Installed-Size")*1024).c_str()); |
9e51c0b6 MV |
189 | else |
190 | installed_size = _("unknown"); | |
191 | std::string package_size; | |
192 | if (Tags.FindI("Size") > 0) | |
17622532 | 193 | strprintf(package_size, "%sB", SizeToStr(Tags.FindI("Size")).c_str()); |
9e51c0b6 MV |
194 | else |
195 | package_size = _("unknown"); | |
196 | ||
501cd23e DK |
197 | const char *manual_installed = nullptr; |
198 | if (V.ParentPkg().CurrentVer() == V) | |
199 | { | |
200 | pkgDepCache *depCache = CacheFile.GetDepCache(); | |
201 | if (unlikely(depCache == nullptr)) | |
202 | return false; | |
203 | pkgDepCache::StateCache &state = (*depCache)[V.ParentPkg()]; | |
85d7c0eb | 204 | manual_installed = !(state.Flags & pkgCache::Flag::Auto) ? "yes" : "no"; |
501cd23e | 205 | } |
17622532 MV |
206 | |
207 | // FIXME: add verbose that does not do the removal of the tags? | |
88593886 DK |
208 | std::vector<pkgTagSection::Tag> RW; |
209 | // delete, apt-cache show has this info and most users do not care | |
210 | RW.push_back(pkgTagSection::Tag::Remove("MD5sum")); | |
211 | RW.push_back(pkgTagSection::Tag::Remove("SHA1")); | |
212 | RW.push_back(pkgTagSection::Tag::Remove("SHA256")); | |
213 | RW.push_back(pkgTagSection::Tag::Remove("SHA512")); | |
214 | RW.push_back(pkgTagSection::Tag::Remove("Filename")); | |
215 | RW.push_back(pkgTagSection::Tag::Remove("Multi-Arch")); | |
216 | RW.push_back(pkgTagSection::Tag::Remove("Architecture")); | |
217 | RW.push_back(pkgTagSection::Tag::Remove("Conffiles")); | |
218 | // we use the translated description | |
219 | RW.push_back(pkgTagSection::Tag::Remove("Description")); | |
220 | RW.push_back(pkgTagSection::Tag::Remove("Description-md5")); | |
221 | // improve | |
90139c70 | 222 | RW.push_back(pkgTagSection::Tag::Rewrite("Package", V.ParentPkg().FullName(true))); |
88593886 DK |
223 | RW.push_back(pkgTagSection::Tag::Rewrite("Installed-Size", installed_size)); |
224 | RW.push_back(pkgTagSection::Tag::Remove("Size")); | |
225 | RW.push_back(pkgTagSection::Tag::Rewrite("Download-Size", package_size)); | |
226 | // add | |
501cd23e DK |
227 | if (manual_installed != nullptr) |
228 | RW.push_back(pkgTagSection::Tag::Rewrite("APT-Manual-Installed", manual_installed)); | |
88593886 | 229 | RW.push_back(pkgTagSection::Tag::Rewrite("APT-Sources", source_index_file)); |
85d7c0eb | 230 | |
88593886 DK |
231 | FileFd stdoutfd; |
232 | if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly, false) == false || | |
233 | Tags.Write(stdoutfd, TFRewritePackageOrder, RW) == false || stdoutfd.Close() == false) | |
9e51c0b6 | 234 | return _error->Error("Internal Error, Unable to parse a package record"); |
b9179170 MV |
235 | |
236 | // write the description | |
501cd23e DK |
237 | pkgCache * const Cache = CacheFile.GetPkgCache(); |
238 | if (unlikely(Cache == NULL)) | |
239 | return false; | |
b9179170 | 240 | pkgRecords Recs(*Cache); |
2a79257e | 241 | // FIXME: show (optionally) all available translations(?) |
b9179170 MV |
242 | pkgCache::DescIterator Desc = V.TranslatedDescription(); |
243 | if (Desc.end() == false) | |
244 | { | |
245 | pkgRecords::Parser &P = Recs.Lookup(Desc.FileList()); | |
2a79257e | 246 | out << "Description: " << P.LongDesc(); |
b9179170 MV |
247 | } |
248 | ||
249 | // write a final newline (after the description) | |
250 | out << std::endl << std::endl; | |
251 | ||
252 | return true; | |
253 | } | |
254 | /*}}}*/ | |
ee0167c4 | 255 | bool ShowPackage(CommandLine &CmdL) /*{{{*/ |
b9179170 MV |
256 | { |
257 | pkgCacheFile CacheFile; | |
258 | CacheSetHelperVirtuals helper(true, GlobalError::NOTICE); | |
501cd23e | 259 | APT::CacheSetHelper::VerSelector const select = _config->FindB("APT::Cache::AllVersions", true) ? |
e6f0c9bc | 260 | APT::CacheSetHelper::ALL : APT::CacheSetHelper::CANDIDATE; |
fdf9eef4 DK |
261 | if (select == APT::CacheSetHelper::CANDIDATE && CacheFile.GetDepCache() == nullptr) |
262 | return false; | |
b9179170 | 263 | APT::VersionList const verset = APT::VersionList::FromCommandLine(CacheFile, CmdL.FileList + 1, select, helper); |
501cd23e | 264 | int const ShowVersion = _config->FindI("APT::Cache::Show::Version", 1); |
b9179170 | 265 | for (APT::VersionList::const_iterator Ver = verset.begin(); Ver != verset.end(); ++Ver) |
501cd23e DK |
266 | if (ShowVersion <= 1) |
267 | { | |
268 | if (DisplayRecordV1(CacheFile, Ver, std::cout) == false) | |
269 | return false; | |
270 | } | |
271 | else | |
272 | if (DisplayRecordV2(CacheFile, Ver, c1out) == false) | |
273 | return false; | |
b9179170 | 274 | |
e6f0c9bc | 275 | if (select == APT::CacheSetHelper::CANDIDATE) |
06293aa7 | 276 | { |
e6f0c9bc | 277 | APT::VersionList const verset_all = APT::VersionList::FromCommandLine(CacheFile, CmdL.FileList + 1, APT::CacheSetHelper::ALL, helper); |
6298ff8b DK |
278 | int const records = verset_all.size() - verset.size(); |
279 | if (records > 0) | |
280 | _error->Notice(P_("There is %i additional record. Please use the '-a' switch to see it", "There are %i additional records. Please use the '-a' switch to see them.", records), records); | |
06293aa7 MV |
281 | } |
282 | ||
501cd23e DK |
283 | if (_config->FindB("APT::Cache::ShowVirtuals", false) == true) |
284 | for (APT::PackageSet::const_iterator Pkg = helper.virtualPkgs.begin(); | |
285 | Pkg != helper.virtualPkgs.end(); ++Pkg) | |
286 | { | |
287 | c1out << "Package: " << Pkg.FullName(true) << std::endl; | |
288 | c1out << "State: " << _("not a real package (virtual)") << std::endl; | |
289 | // FIXME: show providers, see private-cacheset.h | |
290 | // CacheSetHelperAPTGet::showVirtualPackageErrors() | |
291 | } | |
b9179170 MV |
292 | |
293 | if (verset.empty() == true) | |
294 | { | |
295 | if (helper.virtualPkgs.empty() == true) | |
296 | return _error->Error(_("No packages found")); | |
297 | else | |
298 | _error->Notice(_("No packages found")); | |
299 | } | |
300 | ||
301 | return true; | |
302 | } | |
303 | /*}}}*/ | |
f19525d5 | 304 | static std::string Sha1FromString(std::string const &input) /*{{{*/ |
b0ce7c65 | 305 | { |
f19525d5 | 306 | // XXX: move to hashes.h: HashString::FromString() ? |
b0ce7c65 MV |
307 | SHA1Summation sha1; |
308 | sha1.Add(input.c_str(), input.length()); | |
309 | return sha1.Result().Value(); | |
310 | } | |
311 | /*}}}*/ | |
9055d5e6 DK |
312 | bool ShowSrcPackage(CommandLine &CmdL) /*{{{*/ |
313 | { | |
314 | pkgCacheFile CacheFile; | |
315 | pkgSourceList *List = CacheFile.GetSourceList(); | |
316 | if (unlikely(List == NULL)) | |
317 | return false; | |
318 | ||
319 | // Create the text record parsers | |
320 | pkgSrcRecords SrcRecs(*List); | |
321 | if (_error->PendingError() == true) | |
322 | return false; | |
323 | ||
324 | bool found = false; | |
b0ce7c65 MV |
325 | // avoid showing identical records |
326 | std::set<std::string> seen; | |
9055d5e6 DK |
327 | for (const char **I = CmdL.FileList + 1; *I != 0; I++) |
328 | { | |
329 | SrcRecs.Restart(); | |
330 | ||
331 | pkgSrcRecords::Parser *Parse; | |
332 | bool found_this = false; | |
333 | while ((Parse = SrcRecs.Find(*I,false)) != 0) { | |
334 | // SrcRecs.Find() will find both binary and source names | |
335 | if (_config->FindB("APT::Cache::Only-Source", false) == true) | |
336 | if (Parse->Package() != *I) | |
337 | continue; | |
b0ce7c65 MV |
338 | std::string sha1str = Sha1FromString(Parse->AsStr()); |
339 | if (std::find(seen.begin(), seen.end(), sha1str) == seen.end()) | |
340 | { | |
341 | std::cout << Parse->AsStr() << std::endl;; | |
342 | found = true; | |
343 | found_this = true; | |
344 | seen.insert(sha1str); | |
345 | } | |
9055d5e6 DK |
346 | } |
347 | if (found_this == false) { | |
348 | _error->Warning(_("Unable to locate package %s"),*I); | |
349 | continue; | |
350 | } | |
351 | } | |
352 | if (found == false) | |
353 | _error->Notice(_("No packages found")); | |
354 | return true; | |
355 | } | |
356 | /*}}}*/ | |
357 | // Policy - Show the results of the preferences file /*{{{*/ | |
358 | bool Policy(CommandLine &CmdL) | |
359 | { | |
360 | pkgCacheFile CacheFile; | |
fdf9eef4 DK |
361 | pkgSourceList const * const SrcList = CacheFile.GetSourceList(); |
362 | if (unlikely(SrcList == nullptr)) | |
363 | return false; | |
364 | pkgCache * const Cache = CacheFile.GetPkgCache(); | |
365 | if (unlikely(Cache == nullptr)) | |
366 | return false; | |
367 | pkgPolicy * const Plcy = CacheFile.GetPolicy(); | |
368 | if (unlikely(Plcy == nullptr)) | |
9055d5e6 DK |
369 | return false; |
370 | ||
9055d5e6 DK |
371 | // Print out all of the package files |
372 | if (CmdL.FileList[1] == 0) | |
373 | { | |
374 | std::cout << _("Package files:") << std::endl; | |
375 | for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F.end() == false; ++F) | |
376 | { | |
377 | if (F.Flagged(pkgCache::Flag::NoPackages)) | |
378 | continue; | |
379 | // Locate the associated index files so we can derive a description | |
380 | pkgIndexFile *Indx; | |
381 | if (SrcList->FindIndex(F,Indx) == false && | |
382 | _system->FindIndex(F,Indx) == false) | |
383 | return _error->Error(_("Cache is out of sync, can't x-ref a package file")); | |
384 | ||
385 | printf("%4i %s\n", | |
386 | Plcy->GetPriority(F),Indx->Describe(true).c_str()); | |
387 | ||
388 | // Print the reference information for the package | |
389 | std::string Str = F.RelStr(); | |
390 | if (Str.empty() == false) | |
391 | printf(" release %s\n",F.RelStr().c_str()); | |
392 | if (F.Site() != 0 && F.Site()[0] != 0) | |
393 | printf(" origin %s\n",F.Site()); | |
394 | } | |
395 | ||
396 | // Show any packages have explicit pins | |
397 | std::cout << _("Pinned packages:") << std::endl; | |
398 | pkgCache::PkgIterator I = Cache->PkgBegin(); | |
399 | for (;I.end() != true; ++I) | |
400 | { | |
258b9e51 | 401 | for (pkgCache::VerIterator V = I.VersionList(); !V.end(); ++V) { |
9055d5e6 DK |
402 | auto Prio = Plcy->GetPriority(V, false); |
403 | if (Prio == 0) | |
404 | continue; | |
405 | ||
406 | std::cout << " "; | |
407 | // Print the package name and the version we are forcing to | |
408 | ioprintf(std::cout, _("%s -> %s with priority %d\n"), I.FullName(true).c_str(), V.VerStr(), Prio); | |
409 | } | |
410 | } | |
411 | return true; | |
412 | } | |
413 | ||
414 | char const * const msgInstalled = _(" Installed: "); | |
415 | char const * const msgCandidate = _(" Candidate: "); | |
416 | short const InstalledLessCandidate = | |
417 | mbstowcs(NULL, msgInstalled, 0) - mbstowcs(NULL, msgCandidate, 0); | |
418 | short const deepInstalled = | |
419 | (InstalledLessCandidate < 0 ? (InstalledLessCandidate*-1) : 0) - 1; | |
420 | short const deepCandidate = | |
421 | (InstalledLessCandidate > 0 ? (InstalledLessCandidate) : 0) - 1; | |
422 | ||
423 | // Print out detailed information for each package | |
424 | APT::CacheSetHelper helper(true, GlobalError::NOTICE); | |
425 | APT::PackageList pkgset = APT::PackageList::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); | |
426 | for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) | |
427 | { | |
428 | std::cout << Pkg.FullName(true) << ":" << std::endl; | |
429 | ||
430 | // Installed version | |
431 | std::cout << msgInstalled << OutputInDepth(deepInstalled, " "); | |
432 | if (Pkg->CurrentVer == 0) | |
433 | std::cout << _("(none)") << std::endl; | |
434 | else | |
435 | std::cout << Pkg.CurrentVer().VerStr() << std::endl; | |
436 | ||
437 | // Candidate Version | |
438 | std::cout << msgCandidate << OutputInDepth(deepCandidate, " "); | |
439 | pkgCache::VerIterator V = Plcy->GetCandidateVer(Pkg); | |
440 | if (V.end() == true) | |
441 | std::cout << _("(none)") << std::endl; | |
442 | else | |
443 | std::cout << V.VerStr() << std::endl; | |
444 | ||
9055d5e6 DK |
445 | // Show the priority tables |
446 | std::cout << _(" Version table:") << std::endl; | |
447 | for (V = Pkg.VersionList(); V.end() == false; ++V) | |
448 | { | |
449 | if (Pkg.CurrentVer() == V) | |
450 | std::cout << " *** " << V.VerStr(); | |
451 | else | |
452 | std::cout << " " << V.VerStr(); | |
03d6de04 JAK |
453 | |
454 | std::cout << " " << Plcy->GetPriority(V) << std::endl; | |
9055d5e6 DK |
455 | for (pkgCache::VerFileIterator VF = V.FileList(); VF.end() == false; ++VF) |
456 | { | |
457 | // Locate the associated index files so we can derive a description | |
458 | pkgIndexFile *Indx; | |
459 | if (SrcList->FindIndex(VF.File(),Indx) == false && | |
460 | _system->FindIndex(VF.File(),Indx) == false) | |
461 | return _error->Error(_("Cache is out of sync, can't x-ref a package file")); | |
462 | printf(" %4i %s\n",Plcy->GetPriority(VF.File()), | |
463 | Indx->Describe(true).c_str()); | |
464 | } | |
465 | } | |
466 | } | |
467 | return true; | |
468 | } | |
469 | /*}}}*/ |