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