]> git.saurik.com Git - apt.git/blame - apt-pkg/aptconfiguration.cc
* methods/bzip2.cc:
[apt.git] / apt-pkg / aptconfiguration.cc
CommitLineData
e878aedb
DK
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3/* ######################################################################
4
5 Provide access methods to various configuration settings,
6 setup defaults and returns validate settings.
7
8 ##################################################################### */
9 /*}}}*/
10// Include Files /*{{{*/
ea542140
DK
11#include <config.h>
12
e878aedb
DK
13#include <apt-pkg/aptconfiguration.h>
14#include <apt-pkg/configuration.h>
9f9717fa 15#include <apt-pkg/error.h>
d7cf5923
DK
16#include <apt-pkg/fileutl.h>
17#include <apt-pkg/macros.h>
18#include <apt-pkg/strutl.h>
e878aedb 19
3f2d77b5
DK
20#include <sys/types.h>
21#include <dirent.h>
8aec002f 22#include <stdio.h>
b9ed63d3 23#include <fcntl.h>
3f2d77b5 24
8bd02d8b 25#include <algorithm>
3f2d77b5
DK
26#include <string>
27#include <vector>
e878aedb
DK
28 /*}}}*/
29namespace APT {
30// getCompressionTypes - Return Vector of usbale compressiontypes /*{{{*/
31// ---------------------------------------------------------------------
32/* return a vector of compression types in the prefered order. */
33std::vector<std::string>
34const Configuration::getCompressionTypes(bool const &Cached) {
35 static std::vector<std::string> types;
36 if (types.empty() == false) {
37 if (Cached == true)
38 return types;
39 else
40 types.clear();
41 }
42
8bd02d8b
DK
43 // setup the defaults for the compressiontypes => method mapping
44 _config->CndSet("Acquire::CompressionTypes::bz2","bzip2");
b2430e6d 45 _config->CndSet("Acquire::CompressionTypes::xz","xz");
8bd02d8b
DK
46 _config->CndSet("Acquire::CompressionTypes::lzma","lzma");
47 _config->CndSet("Acquire::CompressionTypes::gz","gzip");
48
03bef784 49 setDefaultConfigurationForCompressors();
e878aedb 50
8bd02d8b
DK
51 // accept non-list order as override setting for config settings on commandline
52 std::string const overrideOrder = _config->Find("Acquire::CompressionTypes::Order","");
53 if (overrideOrder.empty() == false)
54 types.push_back(overrideOrder);
e878aedb 55
8bd02d8b
DK
56 // load the order setting into our vector
57 std::vector<std::string> const order = _config->FindVector("Acquire::CompressionTypes::Order");
58 for (std::vector<std::string>::const_iterator o = order.begin();
f7f0d6c7 59 o != order.end(); ++o) {
8bd02d8b
DK
60 if ((*o).empty() == true)
61 continue;
62 // ignore types we have no method ready to use
8f3ba4e8 63 if (_config->Exists(std::string("Acquire::CompressionTypes::").append(*o)) == false)
8bd02d8b
DK
64 continue;
65 // ignore types we have no app ready to use
8f3ba4e8 66 std::string const appsetting = std::string("Dir::Bin::").append(*o);
8bd02d8b
DK
67 if (_config->Exists(appsetting) == true) {
68 std::string const app = _config->FindFile(appsetting.c_str(), "");
69 if (app.empty() == false && FileExists(app) == false)
70 continue;
71 }
72 types.push_back(*o);
e878aedb
DK
73 }
74
8bd02d8b 75 // move again over the option tree to add all missing compression types
e878aedb
DK
76 ::Configuration::Item const *Types = _config->Tree("Acquire::CompressionTypes");
77 if (Types != 0)
78 Types = Types->Child;
79
80 for (; Types != 0; Types = Types->Next) {
8bd02d8b
DK
81 if (Types->Tag == "Order" || Types->Tag.empty() == true)
82 continue;
83 // ignore types we already have in the vector
84 if (std::find(types.begin(),types.end(),Types->Tag) != types.end())
85 continue;
86 // ignore types we have no app ready to use
8f3ba4e8 87 std::string const appsetting = std::string("Dir::Bin::").append(Types->Value);
e878aedb
DK
88 if (appsetting.empty() == false && _config->Exists(appsetting) == true) {
89 std::string const app = _config->FindFile(appsetting.c_str(), "");
90 if (app.empty() == false && FileExists(app) == false)
91 continue;
92 }
93 types.push_back(Types->Tag);
94 }
95
5d885723
DK
96 // add the special "uncompressed" type
97 if (std::find(types.begin(), types.end(), "uncompressed") == types.end())
98 {
8f3ba4e8 99 std::string const uncompr = _config->FindFile("Dir::Bin::uncompressed", "");
5d885723
DK
100 if (uncompr.empty() == true || FileExists(uncompr) == true)
101 types.push_back("uncompressed");
102 }
103
e878aedb
DK
104 return types;
105}
106 /*}}}*/
45df0ad2
DK
107// GetLanguages - Return Vector of Language Codes /*{{{*/
108// ---------------------------------------------------------------------
109/* return a vector of language codes in the prefered order.
110 the special word "environment" will be replaced with the long and the short
111 code of the local settings and it will be insured that this will not add
112 duplicates. So in an german local the setting "environment, de_DE, en, de"
113 will result in "de_DE, de, en".
114 The special word "none" is the stopcode for the not-All code vector */
115std::vector<std::string> const Configuration::getLanguages(bool const &All,
d7cf5923 116 bool const &Cached, char const ** const Locale) {
45df0ad2
DK
117 using std::string;
118
119 // The detection is boring and has a lot of cornercases,
120 // so we cache the results to calculated it only once.
121 std::vector<string> static allCodes;
122 std::vector<string> static codes;
123
124 // we have something in the cache
125 if (codes.empty() == false || allCodes.empty() == false) {
126 if (Cached == true) {
127 if(All == true && allCodes.empty() == false)
128 return allCodes;
129 else
130 return codes;
131 } else {
132 allCodes.clear();
133 codes.clear();
134 }
135 }
136
3f2d77b5
DK
137 // Include all Language codes we have a Translation file for in /var/lib/apt/lists
138 // so they will be all included in the Cache.
139 std::vector<string> builtin;
140 DIR *D = opendir(_config->FindDir("Dir::State::lists").c_str());
141 if (D != 0) {
142 builtin.push_back("none");
143 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D)) {
144 string const name = Ent->d_name;
145 size_t const foundDash = name.rfind("-");
7cb28948 146 size_t const foundUnderscore = name.rfind("_", foundDash);
3f2d77b5
DK
147 if (foundDash == string::npos || foundUnderscore == string::npos ||
148 foundDash <= foundUnderscore ||
149 name.substr(foundUnderscore+1, foundDash-(foundUnderscore+1)) != "Translation")
150 continue;
151 string const c = name.substr(foundDash+1);
152 if (unlikely(c.empty() == true) || c == "en")
153 continue;
154 // Skip unusual files, like backups or that alike
155 string::const_iterator s = c.begin();
156 for (;s != c.end(); ++s) {
7cb28948 157 if (isalpha(*s) == 0 && *s != '_')
3f2d77b5
DK
158 break;
159 }
160 if (s != c.end())
161 continue;
162 if (std::find(builtin.begin(), builtin.end(), c) != builtin.end())
163 continue;
164 builtin.push_back(c);
165 }
166 }
cf0e078c 167 closedir(D);
3f2d77b5 168
45df0ad2
DK
169 // FIXME: Remove support for the old APT::Acquire::Translation
170 // it was undocumented and so it should be not very widthly used
171 string const oldAcquire = _config->Find("APT::Acquire::Translation","");
172 if (oldAcquire.empty() == false && oldAcquire != "environment") {
9f9717fa
DK
173 // TRANSLATORS: the two %s are APT configuration options
174 _error->Notice("Option '%s' is deprecated. Please use '%s' instead, see 'man 5 apt.conf' for details.",
175 "APT::Acquire::Translation", "Acquire::Languages");
45df0ad2
DK
176 if (oldAcquire != "none")
177 codes.push_back(oldAcquire);
3f2d77b5 178 codes.push_back("en");
d7cf5923 179 allCodes = codes;
3f2d77b5
DK
180 for (std::vector<string>::const_iterator b = builtin.begin();
181 b != builtin.end(); ++b)
182 if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end())
183 allCodes.push_back(*b);
184 if (All == true)
185 return allCodes;
186 else
187 return codes;
45df0ad2
DK
188 }
189
ab53c018
DK
190 // get the environment language codes: LC_MESSAGES (and later LANGUAGE)
191 // we extract both, a long and a short code and then we will
192 // check if we actually need both (rare) or if the short is enough
193 string const envMsg = string(Locale == 0 ? std::setlocale(LC_MESSAGES, NULL) : *Locale);
194 size_t const lenShort = (envMsg.find('_') != string::npos) ? envMsg.find('_') : 2;
195 size_t const lenLong = (envMsg.find_first_of(".@") != string::npos) ? envMsg.find_first_of(".@") : (lenShort + 3);
196
197 string const envLong = envMsg.substr(0,lenLong);
198 string const envShort = envLong.substr(0,lenShort);
199
200 // It is very likely we will need the environment codes later,
d7cf5923
DK
201 // so let us generate them now from LC_MESSAGES and LANGUAGE
202 std::vector<string> environment;
eb3947c6
DK
203 if (envShort != "C") {
204 // take care of LC_MESSAGES
ab53c018 205 if (envLong != envShort)
eb3947c6
DK
206 environment.push_back(envLong);
207 environment.push_back(envShort);
208 // take care of LANGUAGE
209 const char *language_env = getenv("LANGUAGE") == 0 ? "" : getenv("LANGUAGE");
210 string envLang = Locale == 0 ? language_env : *(Locale+1);
211 if (envLang.empty() == false) {
212 std::vector<string> env = VectorizeString(envLang,':');
213 short addedLangs = 0; // add a maximum of 3 fallbacks from the environment
214 for (std::vector<string>::const_iterator e = env.begin();
215 e != env.end() && addedLangs < 3; ++e) {
216 if (unlikely(e->empty() == true) || *e == "en")
217 continue;
218 if (*e == envLong || *e == envShort)
219 continue;
220 if (std::find(environment.begin(), environment.end(), *e) != environment.end())
d7cf5923 221 continue;
eb3947c6
DK
222 ++addedLangs;
223 environment.push_back(*e);
d7cf5923 224 }
d7cf5923 225 }
eb3947c6
DK
226 } else {
227 environment.push_back("en");
d7cf5923
DK
228 }
229
da833832 230 // Support settings like Acquire::Languages=none on the command line to
45df0ad2
DK
231 // override the configuration settings vector of languages.
232 string const forceLang = _config->Find("Acquire::Languages","");
233 if (forceLang.empty() == false) {
234 if (forceLang == "environment") {
d7cf5923 235 codes = environment;
45df0ad2
DK
236 } else if (forceLang != "none")
237 codes.push_back(forceLang);
7cb28948
DK
238 else //if (forceLang == "none")
239 builtin.clear();
d7cf5923 240 allCodes = codes;
3f2d77b5
DK
241 for (std::vector<string>::const_iterator b = builtin.begin();
242 b != builtin.end(); ++b)
243 if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end())
244 allCodes.push_back(*b);
245 if (All == true)
246 return allCodes;
247 else
248 return codes;
45df0ad2
DK
249 }
250
eb3947c6
DK
251 // cornercase: LANG=C, so we use only "en" Translation
252 if (envShort == "C") {
253 allCodes = codes = environment;
254 allCodes.insert(allCodes.end(), builtin.begin(), builtin.end());
255 if (All == true)
256 return allCodes;
257 else
258 return codes;
259 }
260
45df0ad2
DK
261 std::vector<string> const lang = _config->FindVector("Acquire::Languages");
262 // the default setting -> "environment, en"
263 if (lang.empty() == true) {
d7cf5923 264 codes = environment;
45df0ad2
DK
265 if (envShort != "en")
266 codes.push_back("en");
d7cf5923 267 allCodes = codes;
3f2d77b5
DK
268 for (std::vector<string>::const_iterator b = builtin.begin();
269 b != builtin.end(); ++b)
270 if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end())
271 allCodes.push_back(*b);
272 if (All == true)
273 return allCodes;
274 else
275 return codes;
45df0ad2
DK
276 }
277
278 // the configs define the order, so add the environment
279 // then needed and ensure the codes are not listed twice.
280 bool noneSeen = false;
281 for (std::vector<string>::const_iterator l = lang.begin();
f7f0d6c7 282 l != lang.end(); ++l) {
45df0ad2 283 if (*l == "environment") {
d7cf5923
DK
284 for (std::vector<string>::const_iterator e = environment.begin();
285 e != environment.end(); ++e) {
286 if (std::find(allCodes.begin(), allCodes.end(), *e) != allCodes.end())
287 continue;
45df0ad2 288 if (noneSeen == false)
d7cf5923
DK
289 codes.push_back(*e);
290 allCodes.push_back(*e);
45df0ad2
DK
291 }
292 continue;
293 } else if (*l == "none") {
294 noneSeen = true;
295 continue;
d7cf5923 296 } else if (std::find(allCodes.begin(), allCodes.end(), *l) != allCodes.end())
45df0ad2
DK
297 continue;
298
299 if (noneSeen == false)
300 codes.push_back(*l);
301 allCodes.push_back(*l);
302 }
3f2d77b5
DK
303
304 for (std::vector<string>::const_iterator b = builtin.begin();
305 b != builtin.end(); ++b)
306 if (std::find(allCodes.begin(), allCodes.end(), *b) == allCodes.end())
307 allCodes.push_back(*b);
308
45df0ad2
DK
309 if (All == true)
310 return allCodes;
311 else
312 return codes;
313}
314 /*}}}*/
5dd4c8b8
DK
315// getArchitectures - Return Vector of prefered Architectures /*{{{*/
316std::vector<std::string> const Configuration::getArchitectures(bool const &Cached) {
317 using std::string;
318
319 std::vector<string> static archs;
320 if (likely(Cached == true) && archs.empty() == false)
321 return archs;
322
3152f4aa 323 string const arch = _config->Find("APT::Architecture");
8aec002f
DK
324 archs = _config->FindVector("APT::Architectures");
325
3152f4aa
DK
326 if (unlikely(arch.empty() == true))
327 return archs;
328
8aec002f
DK
329 // FIXME: It is a bit unclean to have debian specific code hereā€¦
330 if (archs.empty() == true) {
331 archs.push_back(arch);
b9ed63d3
DK
332
333 // Generate the base argument list for dpkg
334 std::vector<const char *> Args;
335 string Tmp = _config->Find("Dir::Bin::dpkg","dpkg");
336 {
337 string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/");
338 size_t dpkgChrootLen = dpkgChrootDir.length();
339 if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0) {
340 if (dpkgChrootDir[dpkgChrootLen - 1] == '/')
341 --dpkgChrootLen;
342 Tmp = Tmp.substr(dpkgChrootLen);
343 }
344 }
345 Args.push_back(Tmp.c_str());
346
347 // Stick in any custom dpkg options
348 ::Configuration::Item const *Opts = _config->Tree("DPkg::Options");
349 if (Opts != 0) {
350 Opts = Opts->Child;
351 for (; Opts != 0; Opts = Opts->Next)
352 {
353 if (Opts->Value.empty() == true)
354 continue;
355 Args.push_back(Opts->Value.c_str());
356 }
357 }
358
359 Args.push_back("--print-foreign-architectures");
360 Args.push_back(NULL);
361
362 int external[2] = {-1, -1};
363 if (pipe(external) != 0)
364 {
365 _error->WarningE("getArchitecture", "Can't create IPC pipe for dpkg --print-foreign-architectures");
366 return archs;
367 }
368
369 pid_t dpkgMultiArch = ExecFork();
370 if (dpkgMultiArch == 0) {
371 close(external[0]);
372 std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
373 if (chrootDir != "/" && chroot(chrootDir.c_str()) != 0)
374 _error->WarningE("getArchitecture", "Couldn't chroot into %s for dpkg --print-foreign-architectures", chrootDir.c_str());
375 int const nullfd = open("/dev/null", O_RDONLY);
376 dup2(nullfd, STDIN_FILENO);
377 dup2(external[1], STDOUT_FILENO);
378 dup2(nullfd, STDERR_FILENO);
17019a09 379 execvp(Args[0], (char**) &Args[0]);
b9ed63d3
DK
380 _error->WarningE("getArchitecture", "Can't detect foreign architectures supported by dpkg!");
381 _exit(100);
382 }
383 close(external[1]);
384
385 FILE *dpkg = fdopen(external[0], "r");
8aec002f
DK
386 char buf[1024];
387 if(dpkg != NULL) {
4df62de6 388 while (fgets(buf, sizeof(buf), dpkg) != NULL) {
8aec002f
DK
389 char* arch = strtok(buf, " ");
390 while (arch != NULL) {
391 for (; isspace(*arch) != 0; ++arch);
bdb3d92c 392 if (arch[0] != '\0') {
8aec002f
DK
393 char const* archend = arch;
394 for (; isspace(*archend) == 0 && *archend != '\0'; ++archend);
dd7233af
DK
395 string a(arch, (archend - arch));
396 if (std::find(archs.begin(), archs.end(), a) == archs.end())
397 archs.push_back(a);
8aec002f
DK
398 }
399 arch = strtok(NULL, " ");
400 }
401 }
b9ed63d3 402 fclose(dpkg);
8aec002f 403 }
b9ed63d3 404 ExecWait(dpkgMultiArch, "dpkg --print-foreign-architectures", true);
8aec002f
DK
405 return archs;
406 }
407
5dd4c8b8
DK
408 if (archs.empty() == true ||
409 std::find(archs.begin(), archs.end(), arch) == archs.end())
bd9d81e3 410 archs.insert(archs.begin(), arch);
3152f4aa
DK
411
412 // erase duplicates and empty strings
413 for (std::vector<string>::reverse_iterator a = archs.rbegin();
414 a != archs.rend(); ++a) {
415 if (a->empty() == true || std::find(a + 1, archs.rend(), *a) != archs.rend())
416 archs.erase(a.base()-1);
417 if (a == archs.rend())
418 break;
419 }
420
5dd4c8b8
DK
421 return archs;
422}
423 /*}}}*/
424// checkArchitecture - are we interested in the given Architecture? /*{{{*/
425bool const Configuration::checkArchitecture(std::string const &Arch) {
426 if (Arch == "all")
427 return true;
428 std::vector<std::string> const archs = getArchitectures(true);
429 return (std::find(archs.begin(), archs.end(), Arch) != archs.end());
430}
431 /*}}}*/
03bef784
DK
432// setDefaultConfigurationForCompressors /*{{{*/
433void Configuration::setDefaultConfigurationForCompressors() {
434 // Set default application paths to check for optional compression types
03bef784 435 _config->CndSet("Dir::Bin::bzip2", "/bin/bzip2");
2024154c
DK
436 _config->CndSet("Dir::Bin::xz", "/usr/bin/xz");
437 if (FileExists(_config->FindFile("Dir::Bin::xz")) == true) {
438 _config->CndSet("Dir::Bin::lzma", _config->Find("Dir::Bin::xz"));
439 _config->Set("APT::Compressor::lzma::Binary", "xz");
440 if (_config->Exists("APT::Compressor::lzma::CompressArg") == false) {
441 _config->Set("APT::Compressor::lzma::CompressArg::", "--format=lzma");
442 _config->Set("APT::Compressor::lzma::CompressArg::", "-9");
443 }
444 if (_config->Exists("APT::Compressor::lzma::UncompressArg") == false) {
445 _config->Set("APT::Compressor::lzma::UncompressArg::", "--format=lzma");
446 _config->Set("APT::Compressor::lzma::UncompressArg::", "-d");
447 }
448 } else {
449 _config->CndSet("Dir::Bin::lzma", "/usr/bin/lzma");
450 if (_config->Exists("APT::Compressor::lzma::CompressArg") == false) {
451 _config->Set("APT::Compressor::lzma::CompressArg::", "--suffix=");
452 _config->Set("APT::Compressor::lzma::CompressArg::", "-9");
453 }
454 if (_config->Exists("APT::Compressor::lzma::UncompressArg") == false) {
455 _config->Set("APT::Compressor::lzma::UncompressArg::", "--suffix=");
456 _config->Set("APT::Compressor::lzma::UncompressArg::", "-d");
457 }
458 }
03bef784
DK
459}
460 /*}}}*/
461// getCompressors - Return Vector of usbale compressors /*{{{*/
462// ---------------------------------------------------------------------
463/* return a vector of compressors used by apt-ftparchive in the
464 multicompress functionality or to detect data.tar files */
465std::vector<APT::Configuration::Compressor>
466const Configuration::getCompressors(bool const Cached) {
467 static std::vector<APT::Configuration::Compressor> compressors;
468 if (compressors.empty() == false) {
469 if (Cached == true)
470 return compressors;
471 else
472 compressors.clear();
473 }
474
475 setDefaultConfigurationForCompressors();
476
477 compressors.push_back(Compressor(".", "", "", "", "", 1));
478 if (_config->Exists("Dir::Bin::gzip") == false || FileExists(_config->FindFile("Dir::Bin::gzip")) == true)
479 compressors.push_back(Compressor("gzip",".gz","gzip","-9n","-d",2));
480 if (_config->Exists("Dir::Bin::bzip2") == false || FileExists(_config->FindFile("Dir::Bin::bzip2")) == true)
481 compressors.push_back(Compressor("bzip2",".bz2","bzip2","-9","-d",3));
03bef784 482 if (_config->Exists("Dir::Bin::xz") == false || FileExists(_config->FindFile("Dir::Bin::xz")) == true)
2024154c
DK
483 compressors.push_back(Compressor("xz",".xz","xz","-6","-d",4));
484 if (_config->Exists("Dir::Bin::lzma") == false || FileExists(_config->FindFile("Dir::Bin::lzma")) == true)
485 compressors.push_back(Compressor("lzma",".lzma","lzma","-9","-d",5));
03bef784
DK
486
487 std::vector<std::string> const comp = _config->FindVector("APT::Compressor");
488 for (std::vector<std::string>::const_iterator c = comp.begin();
489 c != comp.end(); ++c) {
490 if (*c == "." || *c == "gzip" || *c == "bzip2" || *c == "lzma" || *c == "xz")
491 continue;
492 compressors.push_back(Compressor(c->c_str(), std::string(".").append(*c).c_str(), c->c_str(), "-9", "-d", 100));
493 }
494
495 return compressors;
496}
497 /*}}}*/
b0e1a43f
DK
498// getCompressorExtensions - supported data.tar extensions /*{{{*/
499// ---------------------------------------------------------------------
500/* */
501std::vector<std::string> const Configuration::getCompressorExtensions() {
502 std::vector<APT::Configuration::Compressor> const compressors = getCompressors();
503 std::vector<std::string> ext;
504 for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressors.begin();
505 c != compressors.end(); ++c)
506 if (c->Extension.empty() == false && c->Extension != ".")
507 ext.push_back(c->Extension);
508 return ext;
509}
510 /*}}}*/
03bef784
DK
511// Compressor constructor /*{{{*/
512// ---------------------------------------------------------------------
513/* */
514Configuration::Compressor::Compressor(char const *name, char const *extension,
515 char const *binary,
516 char const *compressArg, char const *uncompressArg,
517 unsigned short const cost) {
2024154c 518 std::string const config = std::string("APT::Compressor::").append(name).append("::");
03bef784
DK
519 Name = _config->Find(std::string(config).append("Name"), name);
520 Extension = _config->Find(std::string(config).append("Extension"), extension);
521 Binary = _config->Find(std::string(config).append("Binary"), binary);
522 Cost = _config->FindI(std::string(config).append("Cost"), cost);
523 std::string const compConf = std::string(config).append("CompressArg");
524 if (_config->Exists(compConf) == true)
525 CompressArgs = _config->FindVector(compConf);
526 else if (compressArg != NULL)
527 CompressArgs.push_back(compressArg);
528 std::string const uncompConf = std::string(config).append("UncompressArg");
529 if (_config->Exists(uncompConf) == true)
530 UncompressArgs = _config->FindVector(uncompConf);
531 else if (uncompressArg != NULL)
532 UncompressArgs.push_back(uncompressArg);
533}
534 /*}}}*/
e878aedb 535}