]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/fileutl.cc
fix regression that APT::Keep-Fds is not honored (closes: #730490)
[apt.git] / apt-pkg / contrib / fileutl.cc
CommitLineData
578bfd0a
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
7da2b375 3// $Id: fileutl.cc,v 1.42 2002/09/14 05:29:22 jgg Exp $
578bfd0a
AL
4/* ######################################################################
5
6 File Utilities
7
8 CopyFile - Buffered copy of a single file
9 GetLock - dpkg compatible lock file manipulation (fcntl)
10
614adaa0
MV
11 Most of this source is placed in the Public Domain, do with it what
12 you will
7da2b375 13 It was originally written by Jason Gunthorpe <jgg@debian.org>.
a3a03f5d 14 FileFd gzip support added by Martin Pitt <martin.pitt@canonical.com>
578bfd0a 15
614adaa0
MV
16 The exception is RunScripts() it is under the GPLv2
17
578bfd0a
AL
18 ##################################################################### */
19 /*}}}*/
20// Include Files /*{{{*/
ea542140
DK
21#include <config.h>
22
094a497d 23#include <apt-pkg/fileutl.h>
1cd1c398 24#include <apt-pkg/strutl.h>
094a497d 25#include <apt-pkg/error.h>
b2e465d6 26#include <apt-pkg/sptr.h>
468720c5 27#include <apt-pkg/aptconfiguration.h>
75ef8f14 28#include <apt-pkg/configuration.h>
b2e465d6 29
152ab79e 30#include <cstdlib>
4f333a8b 31#include <cstring>
3010fb0e 32#include <cstdio>
4f333a8b 33
4d055c05 34#include <iostream>
578bfd0a 35#include <unistd.h>
2c206aa4 36#include <fcntl.h>
578bfd0a 37#include <sys/stat.h>
578bfd0a 38#include <sys/types.h>
cc2313b7 39#include <sys/time.h>
1ae93c94 40#include <sys/wait.h>
46e39c8e 41#include <dirent.h>
54676e1a 42#include <signal.h>
65a1e968 43#include <errno.h>
488011fa
MV
44#include <glob.h>
45
75ef8f14 46#include <set>
46e39c8e 47#include <algorithm>
2cae0ccb 48
7efb8c8e
DK
49#ifdef HAVE_ZLIB
50 #include <zlib.h>
699b209e 51#endif
c4997486
DK
52#ifdef HAVE_BZ2
53 #include <bzlib.h>
54#endif
032bd56f 55
2a79d5b5 56#ifdef WORDS_BIGENDIAN
2cae0ccb
DK
57#include <inttypes.h>
58#endif
ea542140
DK
59
60#include <apti18n.h>
578bfd0a
AL
61 /*}}}*/
62
4d055c05
AL
63using namespace std;
64
032bd56f
DK
65class FileFdPrivate {
66 public:
7efb8c8e 67#ifdef HAVE_ZLIB
032bd56f 68 gzFile gz;
699b209e
DK
69#else
70 void* gz;
c4997486
DK
71#endif
72#ifdef HAVE_BZ2
73 BZFILE* bz2;
74#else
75 void* bz2;
699b209e 76#endif
561f860a 77 int compressed_fd;
699b209e
DK
78 pid_t compressor_pid;
79 bool pipe;
80 APT::Configuration::Compressor compressor;
52b47296 81 unsigned int openmode;
1abbc47c 82 unsigned long long seekpos;
c4997486
DK
83 FileFdPrivate() : gz(NULL), bz2(NULL),
84 compressed_fd(-1), compressor_pid(-1), pipe(false),
1abbc47c 85 openmode(0), seekpos(0) {};
500400fe
DK
86 bool CloseDown(std::string const &FileName)
87 {
88 bool Res = true;
89#ifdef HAVE_ZLIB
90 if (gz != NULL) {
91 int const e = gzclose(gz);
92 gz = NULL;
93 // gzdclose() on empty files always fails with "buffer error" here, ignore that
94 if (e != 0 && e != Z_BUF_ERROR)
95 Res &= _error->Errno("close",_("Problem closing the gzip file %s"), FileName.c_str());
96 }
97#endif
98#ifdef HAVE_BZ2
99 if (bz2 != NULL) {
100 BZ2_bzclose(bz2);
101 bz2 = NULL;
102 }
103#endif
104 if (compressor_pid > 0)
105 ExecWait(compressor_pid, "FileFdCompressor", true);
106 compressor_pid = -1;
107
108 return Res;
109 }
110 ~FileFdPrivate() { CloseDown(""); }
032bd56f
DK
111};
112
614adaa0
MV
113// RunScripts - Run a set of scripts from a configuration subtree /*{{{*/
114// ---------------------------------------------------------------------
115/* */
116bool RunScripts(const char *Cnf)
117{
118 Configuration::Item const *Opts = _config->Tree(Cnf);
119 if (Opts == 0 || Opts->Child == 0)
120 return true;
121 Opts = Opts->Child;
122
123 // Fork for running the system calls
124 pid_t Child = ExecFork();
125
126 // This is the child
127 if (Child == 0)
128 {
cfba4f69
MV
129 if (_config->FindDir("DPkg::Chroot-Directory","/") != "/")
130 {
131 std::cerr << "Chrooting into "
132 << _config->FindDir("DPkg::Chroot-Directory")
133 << std::endl;
134 if (chroot(_config->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0)
135 _exit(100);
136 }
137
614adaa0
MV
138 if (chdir("/tmp/") != 0)
139 _exit(100);
140
141 unsigned int Count = 1;
142 for (; Opts != 0; Opts = Opts->Next, Count++)
143 {
144 if (Opts->Value.empty() == true)
145 continue;
146
147 if (system(Opts->Value.c_str()) != 0)
148 _exit(100+Count);
149 }
150 _exit(0);
151 }
152
153 // Wait for the child
154 int Status = 0;
155 while (waitpid(Child,&Status,0) != Child)
156 {
157 if (errno == EINTR)
158 continue;
159 return _error->Errno("waitpid","Couldn't wait for subprocess");
160 }
161
162 // Restore sig int/quit
163 signal(SIGQUIT,SIG_DFL);
164 signal(SIGINT,SIG_DFL);
165
166 // Check for an error code.
167 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
168 {
169 unsigned int Count = WEXITSTATUS(Status);
170 if (Count > 100)
171 {
172 Count -= 100;
173 for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--);
174 _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str());
175 }
176
177 return _error->Error("Sub-process returned an error code");
178 }
179
180 return true;
181}
182 /*}}}*/
183
578bfd0a
AL
184// CopyFile - Buffered copy of a file /*{{{*/
185// ---------------------------------------------------------------------
186/* The caller is expected to set things so that failure causes erasure */
8b89e57f 187bool CopyFile(FileFd &From,FileFd &To)
578bfd0a 188{
2128d3fc
DK
189 if (From.IsOpen() == false || To.IsOpen() == false ||
190 From.Failed() == true || To.Failed() == true)
578bfd0a
AL
191 return false;
192
193 // Buffered copy between fds
b2e465d6 194 SPtrArray<unsigned char> Buf = new unsigned char[64000];
650faab0 195 unsigned long long Size = From.Size();
b0db36b1 196 while (Size != 0)
578bfd0a 197 {
650faab0 198 unsigned long long ToRead = Size;
b0db36b1
AL
199 if (Size > 64000)
200 ToRead = 64000;
201
4a6d5862 202 if (From.Read(Buf,ToRead) == false ||
b0db36b1 203 To.Write(Buf,ToRead) == false)
578bfd0a 204 return false;
b0db36b1
AL
205
206 Size -= ToRead;
578bfd0a
AL
207 }
208
578bfd0a
AL
209 return true;
210}
211 /*}}}*/
212// GetLock - Gets a lock file /*{{{*/
213// ---------------------------------------------------------------------
214/* This will create an empty file of the given name and lock it. Once this
215 is done all other calls to GetLock in any other process will fail with
216 -1. The return result is the fd of the file, the call should call
217 close at some time. */
218int GetLock(string File,bool Errors)
219{
f659b39a
OS
220 // GetLock() is used in aptitude on directories with public-write access
221 // Use O_NOFOLLOW here to prevent symlink traversal attacks
222 int FD = open(File.c_str(),O_RDWR | O_CREAT | O_NOFOLLOW,0640);
578bfd0a
AL
223 if (FD < 0)
224 {
b2e465d6
AL
225 // Read only .. cant have locking problems there.
226 if (errno == EROFS)
227 {
228 _error->Warning(_("Not using locking for read only lock file %s"),File.c_str());
229 return dup(0); // Need something for the caller to close
230 }
231
578bfd0a 232 if (Errors == true)
b2e465d6
AL
233 _error->Errno("open",_("Could not open lock file %s"),File.c_str());
234
235 // Feh.. We do this to distinguish the lock vs open case..
236 errno = EPERM;
578bfd0a
AL
237 return -1;
238 }
b2e465d6
AL
239 SetCloseExec(FD,true);
240
578bfd0a
AL
241 // Aquire a write lock
242 struct flock fl;
c71bc556
AL
243 fl.l_type = F_WRLCK;
244 fl.l_whence = SEEK_SET;
245 fl.l_start = 0;
246 fl.l_len = 0;
578bfd0a
AL
247 if (fcntl(FD,F_SETLK,&fl) == -1)
248 {
3d165906
MV
249 // always close to not leak resources
250 int Tmp = errno;
251 close(FD);
252 errno = Tmp;
253
d89df07a
AL
254 if (errno == ENOLCK)
255 {
b2e465d6
AL
256 _error->Warning(_("Not using locking for nfs mounted lock file %s"),File.c_str());
257 return dup(0); // Need something for the caller to close
3d165906
MV
258 }
259
578bfd0a 260 if (Errors == true)
b2e465d6
AL
261 _error->Errno("open",_("Could not get lock %s"),File.c_str());
262
578bfd0a
AL
263 return -1;
264 }
265
266 return FD;
267}
268 /*}}}*/
269// FileExists - Check if a file exists /*{{{*/
270// ---------------------------------------------------------------------
36f1098a 271/* Beware: Directories are also files! */
578bfd0a
AL
272bool FileExists(string File)
273{
274 struct stat Buf;
275 if (stat(File.c_str(),&Buf) != 0)
276 return false;
277 return true;
278}
279 /*}}}*/
36f1098a
DK
280// RealFileExists - Check if a file exists and if it is really a file /*{{{*/
281// ---------------------------------------------------------------------
282/* */
283bool RealFileExists(string File)
284{
285 struct stat Buf;
286 if (stat(File.c_str(),&Buf) != 0)
287 return false;
288 return ((Buf.st_mode & S_IFREG) != 0);
289}
290 /*}}}*/
1cd1c398
DK
291// DirectoryExists - Check if a directory exists and is really one /*{{{*/
292// ---------------------------------------------------------------------
293/* */
294bool DirectoryExists(string const &Path)
295{
296 struct stat Buf;
297 if (stat(Path.c_str(),&Buf) != 0)
298 return false;
299 return ((Buf.st_mode & S_IFDIR) != 0);
300}
301 /*}}}*/
302// CreateDirectory - poor man's mkdir -p guarded by a parent directory /*{{{*/
303// ---------------------------------------------------------------------
304/* This method will create all directories needed for path in good old
305 mkdir -p style but refuses to do this if Parent is not a prefix of
306 this Path. Example: /var/cache/ and /var/cache/apt/archives are given,
307 so it will create apt/archives if /var/cache exists - on the other
308 hand if the parent is /var/lib the creation will fail as this path
309 is not a parent of the path to be generated. */
310bool CreateDirectory(string const &Parent, string const &Path)
311{
312 if (Parent.empty() == true || Path.empty() == true)
313 return false;
314
315 if (DirectoryExists(Path) == true)
316 return true;
317
318 if (DirectoryExists(Parent) == false)
319 return false;
320
321 // we are not going to create directories "into the blue"
322 if (Path.find(Parent, 0) != 0)
323 return false;
324
325 vector<string> const dirs = VectorizeString(Path.substr(Parent.size()), '/');
326 string progress = Parent;
327 for (vector<string>::const_iterator d = dirs.begin(); d != dirs.end(); ++d)
328 {
329 if (d->empty() == true)
330 continue;
331
332 progress.append("/").append(*d);
333 if (DirectoryExists(progress) == true)
334 continue;
335
336 if (mkdir(progress.c_str(), 0755) != 0)
337 return false;
338 }
339 return true;
340}
341 /*}}}*/
7753e468 342// CreateAPTDirectoryIfNeeded - ensure that the given directory exists /*{{{*/
b29c3712
DK
343// ---------------------------------------------------------------------
344/* a small wrapper around CreateDirectory to check if it exists and to
345 remove the trailing "/apt/" from the parent directory if needed */
7753e468 346bool CreateAPTDirectoryIfNeeded(string const &Parent, string const &Path)
b29c3712
DK
347{
348 if (DirectoryExists(Path) == true)
349 return true;
350
351 size_t const len = Parent.size();
352 if (len > 5 && Parent.find("/apt/", len - 6, 5) == len - 5)
353 {
354 if (CreateDirectory(Parent.substr(0,len-5), Path) == true)
355 return true;
356 }
357 else if (CreateDirectory(Parent, Path) == true)
358 return true;
359
360 return false;
361}
362 /*}}}*/
46e39c8e
MV
363// GetListOfFilesInDir - returns a vector of files in the given dir /*{{{*/
364// ---------------------------------------------------------------------
365/* If an extension is given only files with this extension are included
366 in the returned vector, otherwise every "normal" file is included. */
b39c1859
MV
367std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext,
368 bool const &SortList, bool const &AllowNoExt)
369{
370 std::vector<string> ext;
371 ext.reserve(2);
372 if (Ext.empty() == false)
373 ext.push_back(Ext);
374 if (AllowNoExt == true && ext.empty() == false)
375 ext.push_back("");
376 return GetListOfFilesInDir(Dir, ext, SortList);
377}
378std::vector<string> GetListOfFilesInDir(string const &Dir, std::vector<string> const &Ext,
379 bool const &SortList)
380{
381 // Attention debuggers: need to be set with the environment config file!
382 bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
383 if (Debug == true)
384 {
385 std::clog << "Accept in " << Dir << " only files with the following " << Ext.size() << " extensions:" << std::endl;
386 if (Ext.empty() == true)
387 std::clog << "\tNO extension" << std::endl;
388 else
389 for (std::vector<string>::const_iterator e = Ext.begin();
390 e != Ext.end(); ++e)
391 std::clog << '\t' << (e->empty() == true ? "NO" : *e) << " extension" << std::endl;
392 }
393
46e39c8e 394 std::vector<string> List;
36f1098a 395
69c2ecbd 396 if (DirectoryExists(Dir) == false)
36f1098a
DK
397 {
398 _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str());
399 return List;
400 }
401
1408e219 402 Configuration::MatchAgainstConfig SilentIgnore("Dir::Ignore-Files-Silently");
46e39c8e
MV
403 DIR *D = opendir(Dir.c_str());
404 if (D == 0)
405 {
406 _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
407 return List;
408 }
409
410 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
411 {
b39c1859 412 // skip "hidden" files
46e39c8e
MV
413 if (Ent->d_name[0] == '.')
414 continue;
415
491058e3
DK
416 // Make sure it is a file and not something else
417 string const File = flCombine(Dir,Ent->d_name);
418#ifdef _DIRENT_HAVE_D_TYPE
419 if (Ent->d_type != DT_REG)
420#endif
421 {
69c2ecbd 422 if (RealFileExists(File) == false)
491058e3 423 {
84e254d6
DK
424 // do not show ignoration warnings for directories
425 if (
426#ifdef _DIRENT_HAVE_D_TYPE
427 Ent->d_type == DT_DIR ||
428#endif
69c2ecbd 429 DirectoryExists(File) == true)
84e254d6 430 continue;
491058e3
DK
431 if (SilentIgnore.Match(Ent->d_name) == false)
432 _error->Notice(_("Ignoring '%s' in directory '%s' as it is not a regular file"), Ent->d_name, Dir.c_str());
433 continue;
434 }
435 }
436
b39c1859
MV
437 // check for accepted extension:
438 // no extension given -> periods are bad as hell!
439 // extensions given -> "" extension allows no extension
440 if (Ext.empty() == false)
441 {
442 string d_ext = flExtension(Ent->d_name);
443 if (d_ext == Ent->d_name) // no extension
444 {
445 if (std::find(Ext.begin(), Ext.end(), "") == Ext.end())
446 {
447 if (Debug == true)
448 std::clog << "Bad file: " << Ent->d_name << " → no extension" << std::endl;
5edc3966 449 if (SilentIgnore.Match(Ent->d_name) == false)
491058e3 450 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has no filename extension"), Ent->d_name, Dir.c_str());
b39c1859
MV
451 continue;
452 }
453 }
454 else if (std::find(Ext.begin(), Ext.end(), d_ext) == Ext.end())
455 {
456 if (Debug == true)
457 std::clog << "Bad file: " << Ent->d_name << " → bad extension »" << flExtension(Ent->d_name) << "«" << std::endl;
1408e219 458 if (SilentIgnore.Match(Ent->d_name) == false)
491058e3 459 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has an invalid filename extension"), Ent->d_name, Dir.c_str());
b39c1859
MV
460 continue;
461 }
462 }
46e39c8e 463
b39c1859 464 // Skip bad filenames ala run-parts
46e39c8e
MV
465 const char *C = Ent->d_name;
466 for (; *C != 0; ++C)
467 if (isalpha(*C) == 0 && isdigit(*C) == 0
b39c1859
MV
468 && *C != '_' && *C != '-') {
469 // no required extension -> dot is a bad character
470 if (*C == '.' && Ext.empty() == false)
471 continue;
46e39c8e 472 break;
b39c1859 473 }
46e39c8e 474
b39c1859 475 // we don't reach the end of the name -> bad character included
46e39c8e 476 if (*C != 0)
b39c1859
MV
477 {
478 if (Debug == true)
479 std::clog << "Bad file: " << Ent->d_name << " → bad character »"
480 << *C << "« in filename (period allowed: " << (Ext.empty() ? "no" : "yes") << ")" << std::endl;
481 continue;
482 }
483
fbb2c7e0
DK
484 // skip filenames which end with a period. These are never valid
485 if (*(C - 1) == '.')
486 {
487 if (Debug == true)
488 std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
489 continue;
490 }
491
492 if (Debug == true)
493 std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
494 List.push_back(File);
495 }
496 closedir(D);
497
498 if (SortList == true)
499 std::sort(List.begin(),List.end());
500 return List;
501}
502std::vector<string> GetListOfFilesInDir(string const &Dir, bool SortList)
503{
504 bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
505 if (Debug == true)
506 std::clog << "Accept in " << Dir << " all regular files" << std::endl;
507
508 std::vector<string> List;
509
69c2ecbd 510 if (DirectoryExists(Dir) == false)
fbb2c7e0
DK
511 {
512 _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str());
513 return List;
514 }
515
516 DIR *D = opendir(Dir.c_str());
517 if (D == 0)
518 {
519 _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
520 return List;
521 }
522
523 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
524 {
525 // skip "hidden" files
526 if (Ent->d_name[0] == '.')
527 continue;
528
529 // Make sure it is a file and not something else
530 string const File = flCombine(Dir,Ent->d_name);
531#ifdef _DIRENT_HAVE_D_TYPE
532 if (Ent->d_type != DT_REG)
533#endif
534 {
69c2ecbd 535 if (RealFileExists(File) == false)
fbb2c7e0
DK
536 {
537 if (Debug == true)
538 std::clog << "Bad file: " << Ent->d_name << " → it is not a real file" << std::endl;
539 continue;
540 }
541 }
542
543 // Skip bad filenames ala run-parts
544 const char *C = Ent->d_name;
545 for (; *C != 0; ++C)
546 if (isalpha(*C) == 0 && isdigit(*C) == 0
547 && *C != '_' && *C != '-' && *C != '.')
548 break;
549
550 // we don't reach the end of the name -> bad character included
551 if (*C != 0)
552 {
553 if (Debug == true)
554 std::clog << "Bad file: " << Ent->d_name << " → bad character »" << *C << "« in filename" << std::endl;
555 continue;
556 }
557
b39c1859
MV
558 // skip filenames which end with a period. These are never valid
559 if (*(C - 1) == '.')
560 {
561 if (Debug == true)
562 std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
46e39c8e 563 continue;
b39c1859 564 }
46e39c8e 565
b39c1859
MV
566 if (Debug == true)
567 std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
46e39c8e
MV
568 List.push_back(File);
569 }
570 closedir(D);
571
572 if (SortList == true)
573 std::sort(List.begin(),List.end());
574 return List;
575}
576 /*}}}*/
578bfd0a
AL
577// SafeGetCWD - This is a safer getcwd that returns a dynamic string /*{{{*/
578// ---------------------------------------------------------------------
579/* We return / on failure. */
580string SafeGetCWD()
581{
582 // Stash the current dir.
583 char S[300];
584 S[0] = 0;
7f25bdff 585 if (getcwd(S,sizeof(S)-2) == 0)
578bfd0a 586 return "/";
7f25bdff
AL
587 unsigned int Len = strlen(S);
588 S[Len] = '/';
589 S[Len+1] = 0;
578bfd0a
AL
590 return S;
591}
592 /*}}}*/
2ec858bc
MV
593// GetModificationTime - Get the mtime of the given file or -1 on error /*{{{*/
594// ---------------------------------------------------------------------
595/* We return / on failure. */
596time_t GetModificationTime(string const &Path)
597{
598 struct stat St;
599 if (stat(Path.c_str(), &St) < 0)
600 return -1;
601 return St.st_mtime;
602}
603 /*}}}*/
8ce4327b
AL
604// flNotDir - Strip the directory from the filename /*{{{*/
605// ---------------------------------------------------------------------
606/* */
607string flNotDir(string File)
608{
609 string::size_type Res = File.rfind('/');
610 if (Res == string::npos)
611 return File;
612 Res++;
613 return string(File,Res,Res - File.length());
614}
615 /*}}}*/
d38b7b3d
AL
616// flNotFile - Strip the file from the directory name /*{{{*/
617// ---------------------------------------------------------------------
171c45bc 618/* Result ends in a / */
d38b7b3d
AL
619string flNotFile(string File)
620{
621 string::size_type Res = File.rfind('/');
622 if (Res == string::npos)
171c45bc 623 return "./";
d38b7b3d
AL
624 Res++;
625 return string(File,0,Res);
626}
627 /*}}}*/
b2e465d6
AL
628// flExtension - Return the extension for the file /*{{{*/
629// ---------------------------------------------------------------------
630/* */
631string flExtension(string File)
632{
633 string::size_type Res = File.rfind('.');
634 if (Res == string::npos)
635 return File;
636 Res++;
637 return string(File,Res,Res - File.length());
638}
639 /*}}}*/
421c8d10
AL
640// flNoLink - If file is a symlink then deref it /*{{{*/
641// ---------------------------------------------------------------------
642/* If the name is not a link then the returned path is the input. */
643string flNoLink(string File)
644{
645 struct stat St;
646 if (lstat(File.c_str(),&St) != 0 || S_ISLNK(St.st_mode) == 0)
647 return File;
648 if (stat(File.c_str(),&St) != 0)
649 return File;
650
651 /* Loop resolving the link. There is no need to limit the number of
652 loops because the stat call above ensures that the symlink is not
653 circular */
654 char Buffer[1024];
655 string NFile = File;
656 while (1)
657 {
658 // Read the link
3286ad13 659 ssize_t Res;
421c8d10 660 if ((Res = readlink(NFile.c_str(),Buffer,sizeof(Buffer))) <= 0 ||
3286ad13 661 (size_t)Res >= sizeof(Buffer))
421c8d10
AL
662 return File;
663
664 // Append or replace the previous path
665 Buffer[Res] = 0;
666 if (Buffer[0] == '/')
667 NFile = Buffer;
668 else
669 NFile = flNotFile(NFile) + Buffer;
670
671 // See if we are done
672 if (lstat(NFile.c_str(),&St) != 0)
673 return File;
674 if (S_ISLNK(St.st_mode) == 0)
675 return NFile;
676 }
677}
678 /*}}}*/
b2e465d6
AL
679// flCombine - Combine a file and a directory /*{{{*/
680// ---------------------------------------------------------------------
681/* If the file is an absolute path then it is just returned, otherwise
682 the directory is pre-pended to it. */
683string flCombine(string Dir,string File)
684{
685 if (File.empty() == true)
686 return string();
687
688 if (File[0] == '/' || Dir.empty() == true)
689 return File;
690 if (File.length() >= 2 && File[0] == '.' && File[1] == '/')
691 return File;
692 if (Dir[Dir.length()-1] == '/')
693 return Dir + File;
694 return Dir + '/' + File;
695}
696 /*}}}*/
3b5421b4
AL
697// SetCloseExec - Set the close on exec flag /*{{{*/
698// ---------------------------------------------------------------------
699/* */
700void SetCloseExec(int Fd,bool Close)
701{
702 if (fcntl(Fd,F_SETFD,(Close == false)?0:FD_CLOEXEC) != 0)
703 {
704 cerr << "FATAL -> Could not set close on exec " << strerror(errno) << endl;
705 exit(100);
706 }
707}
708 /*}}}*/
709// SetNonBlock - Set the nonblocking flag /*{{{*/
710// ---------------------------------------------------------------------
711/* */
712void SetNonBlock(int Fd,bool Block)
713{
0a8a80e5
AL
714 int Flags = fcntl(Fd,F_GETFL) & (~O_NONBLOCK);
715 if (fcntl(Fd,F_SETFL,Flags | ((Block == false)?0:O_NONBLOCK)) != 0)
3b5421b4
AL
716 {
717 cerr << "FATAL -> Could not set non-blocking flag " << strerror(errno) << endl;
718 exit(100);
719 }
720}
721 /*}}}*/
722// WaitFd - Wait for a FD to become readable /*{{{*/
723// ---------------------------------------------------------------------
b2e465d6 724/* This waits for a FD to become readable using select. It is useful for
6d5dd02a
AL
725 applications making use of non-blocking sockets. The timeout is
726 in seconds. */
1084d58a 727bool WaitFd(int Fd,bool write,unsigned long timeout)
3b5421b4
AL
728{
729 fd_set Set;
cc2313b7 730 struct timeval tv;
3b5421b4
AL
731 FD_ZERO(&Set);
732 FD_SET(Fd,&Set);
6d5dd02a
AL
733 tv.tv_sec = timeout;
734 tv.tv_usec = 0;
1084d58a 735 if (write == true)
b0db36b1
AL
736 {
737 int Res;
738 do
739 {
740 Res = select(Fd+1,0,&Set,0,(timeout != 0?&tv:0));
741 }
742 while (Res < 0 && errno == EINTR);
743
744 if (Res <= 0)
745 return false;
1084d58a
AL
746 }
747 else
748 {
b0db36b1
AL
749 int Res;
750 do
751 {
752 Res = select(Fd+1,&Set,0,0,(timeout != 0?&tv:0));
753 }
754 while (Res < 0 && errno == EINTR);
755
756 if (Res <= 0)
757 return false;
cc2313b7 758 }
1084d58a 759
3b5421b4
AL
760 return true;
761}
762 /*}}}*/
96ae6de5 763// MergeKeepFdsFromConfiguration - Merge APT::Keep-Fds configuration /*{{{*/
54676e1a 764// ---------------------------------------------------------------------
96ae6de5
MV
765/* This is used to merge the APT::Keep-Fds with the provided KeepFDs
766 * set.
767 */
768void MergeKeepFdsFromConfiguration(std::set<int> &KeepFDs)
e45c4617 769{
e45c4617
MV
770 Configuration::Item const *Opts = _config->Tree("APT::Keep-Fds");
771 if (Opts != 0 && Opts->Child != 0)
772 {
773 Opts = Opts->Child;
774 for (; Opts != 0; Opts = Opts->Next)
775 {
776 if (Opts->Value.empty() == true)
777 continue;
778 int fd = atoi(Opts->Value.c_str());
779 KeepFDs.insert(fd);
780 }
781 }
96ae6de5
MV
782}
783 /*}}}*/
784// ExecFork - Magical fork that sanitizes the context before execing /*{{{*/
785// ---------------------------------------------------------------------
786/* This is used if you want to cleanse the environment for the forked
787 child, it fixes up the important signals and nukes all of the fds,
788 otherwise acts like normal fork. */
789pid_t ExecFork()
790{
791 set<int> KeepFDs;
792 // we need to merge the Keep-Fds as external tools like
793 // debconf-apt-progress use it
794 MergeKeepFdsFromConfiguration(KeepFDs);
e45c4617
MV
795 return ExecFork(KeepFDs);
796}
797
798pid_t ExecFork(std::set<int> KeepFDs)
54676e1a
AL
799{
800 // Fork off the process
801 pid_t Process = fork();
802 if (Process < 0)
803 {
804 cerr << "FATAL -> Failed to fork." << endl;
805 exit(100);
806 }
807
808 // Spawn the subprocess
809 if (Process == 0)
810 {
811 // Setup the signals
812 signal(SIGPIPE,SIG_DFL);
813 signal(SIGQUIT,SIG_DFL);
814 signal(SIGINT,SIG_DFL);
815 signal(SIGWINCH,SIG_DFL);
816 signal(SIGCONT,SIG_DFL);
817 signal(SIGTSTP,SIG_DFL);
75ef8f14 818
54676e1a 819 // Close all of our FDs - just in case
61f954bf 820 for (int K = 3; K != sysconf(_SC_OPEN_MAX); K++)
75ef8f14
MV
821 {
822 if(KeepFDs.find(K) == KeepFDs.end())
007dc9e0 823 fcntl(K,F_SETFD,FD_CLOEXEC);
75ef8f14 824 }
54676e1a
AL
825 }
826
827 return Process;
828}
829 /*}}}*/
ddc1d8d0
AL
830// ExecWait - Fancy waitpid /*{{{*/
831// ---------------------------------------------------------------------
2c9a72d1 832/* Waits for the given sub process. If Reap is set then no errors are
ddc1d8d0
AL
833 generated. Otherwise a failed subprocess will generate a proper descriptive
834 message */
3826564e 835bool ExecWait(pid_t Pid,const char *Name,bool Reap)
ddc1d8d0
AL
836{
837 if (Pid <= 1)
838 return true;
839
840 // Wait and collect the error code
841 int Status;
842 while (waitpid(Pid,&Status,0) != Pid)
843 {
844 if (errno == EINTR)
845 continue;
846
847 if (Reap == true)
848 return false;
849
db0db9fe 850 return _error->Error(_("Waited for %s but it wasn't there"),Name);
ddc1d8d0
AL
851 }
852
853
854 // Check for an error code.
855 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
856 {
857 if (Reap == true)
858 return false;
ab7f4d7c 859 if (WIFSIGNALED(Status) != 0)
40e7fe0e 860 {
ab7f4d7c
MV
861 if( WTERMSIG(Status) == SIGSEGV)
862 return _error->Error(_("Sub-process %s received a segmentation fault."),Name);
863 else
864 return _error->Error(_("Sub-process %s received signal %u."),Name, WTERMSIG(Status));
40e7fe0e 865 }
ddc1d8d0
AL
866
867 if (WIFEXITED(Status) != 0)
b2e465d6 868 return _error->Error(_("Sub-process %s returned an error code (%u)"),Name,WEXITSTATUS(Status));
ddc1d8d0 869
b2e465d6 870 return _error->Error(_("Sub-process %s exited unexpectedly"),Name);
ddc1d8d0
AL
871 }
872
873 return true;
874}
875 /*}}}*/
578bfd0a 876
13d87e2e 877// FileFd::Open - Open a file /*{{{*/
578bfd0a
AL
878// ---------------------------------------------------------------------
879/* The most commonly used open mode combinations are given with Mode */
52b47296 880bool FileFd::Open(string FileName,unsigned int const Mode,CompressMode Compress, unsigned long const Perms)
578bfd0a 881{
257e8d66
DK
882 if (Mode == ReadOnlyGzip)
883 return Open(FileName, ReadOnly, Gzip, Perms);
257e8d66 884
468720c5 885 if (Compress == Auto && (Mode & WriteOnly) == WriteOnly)
ae635e3c 886 return FileFdError("Autodetection on %s only works in ReadOnly openmode!", FileName.c_str());
257e8d66 887
468720c5
DK
888 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
889 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
890 if (Compress == Auto)
891 {
468720c5
DK
892 for (; compressor != compressors.end(); ++compressor)
893 {
894 std::string file = std::string(FileName).append(compressor->Extension);
895 if (FileExists(file) == false)
896 continue;
897 FileName = file;
468720c5
DK
898 break;
899 }
900 }
901 else if (Compress == Extension)
902 {
52b47296
DK
903 std::string::size_type const found = FileName.find_last_of('.');
904 std::string ext;
905 if (found != std::string::npos)
906 {
907 ext = FileName.substr(found);
908 if (ext == ".new" || ext == ".bak")
909 {
910 std::string::size_type const found2 = FileName.find_last_of('.', found - 1);
911 if (found2 != std::string::npos)
912 ext = FileName.substr(found2, found - found2);
913 else
914 ext.clear();
915 }
916 }
aee1aac6
DK
917 for (; compressor != compressors.end(); ++compressor)
918 if (ext == compressor->Extension)
919 break;
920 // no matching extension - assume uncompressed (imagine files like 'example.org_Packages')
921 if (compressor == compressors.end())
922 for (compressor = compressors.begin(); compressor != compressors.end(); ++compressor)
923 if (compressor->Name == ".")
468720c5 924 break;
468720c5 925 }
aee1aac6 926 else
468720c5
DK
927 {
928 std::string name;
929 switch (Compress)
930 {
aee1aac6 931 case None: name = "."; break;
468720c5
DK
932 case Gzip: name = "gzip"; break;
933 case Bzip2: name = "bzip2"; break;
934 case Lzma: name = "lzma"; break;
935 case Xz: name = "xz"; break;
aee1aac6
DK
936 case Auto:
937 case Extension:
52b47296 938 // Unreachable
ae635e3c 939 return FileFdError("Opening File %s in None, Auto or Extension should be already handled?!?", FileName.c_str());
468720c5
DK
940 }
941 for (; compressor != compressors.end(); ++compressor)
942 if (compressor->Name == name)
943 break;
aee1aac6 944 if (compressor == compressors.end())
ae635e3c 945 return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
468720c5
DK
946 }
947
aee1aac6 948 if (compressor == compressors.end())
ae635e3c 949 return FileFdError("Can't find a match for specified compressor mode for file %s", FileName.c_str());
aee1aac6
DK
950 return Open(FileName, Mode, *compressor, Perms);
951}
52b47296 952bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Compressor const &compressor, unsigned long const Perms)
aee1aac6
DK
953{
954 Close();
aee1aac6
DK
955 Flags = AutoClose;
956
957 if ((Mode & WriteOnly) != WriteOnly && (Mode & (Atomic | Create | Empty | Exclusive)) != 0)
ae635e3c 958 return FileFdError("ReadOnly mode for %s doesn't accept additional flags!", FileName.c_str());
aee1aac6 959 if ((Mode & ReadWrite) == 0)
ae635e3c 960 return FileFdError("No openmode provided in FileFd::Open for %s", FileName.c_str());
468720c5 961
257e8d66
DK
962 if ((Mode & Atomic) == Atomic)
963 {
964 Flags |= Replace;
257e8d66
DK
965 }
966 else if ((Mode & (Exclusive | Create)) == (Exclusive | Create))
967 {
968 // for atomic, this will be done by rename in Close()
969 unlink(FileName.c_str());
970 }
971 if ((Mode & Empty) == Empty)
578bfd0a 972 {
257e8d66
DK
973 struct stat Buf;
974 if (lstat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode))
975 unlink(FileName.c_str());
976 }
c4fc2fd7 977
561f860a
DK
978 int fileflags = 0;
979 #define if_FLAGGED_SET(FLAG, MODE) if ((Mode & FLAG) == FLAG) fileflags |= MODE
980 if_FLAGGED_SET(ReadWrite, O_RDWR);
981 else if_FLAGGED_SET(ReadOnly, O_RDONLY);
982 else if_FLAGGED_SET(WriteOnly, O_WRONLY);
4a9db827 983
561f860a
DK
984 if_FLAGGED_SET(Create, O_CREAT);
985 if_FLAGGED_SET(Empty, O_TRUNC);
986 if_FLAGGED_SET(Exclusive, O_EXCL);
561f860a 987 #undef if_FLAGGED_SET
52b47296 988
7335eebe
AGM
989 if ((Mode & Atomic) == Atomic)
990 {
991 char *name = strdup((FileName + ".XXXXXX").c_str());
992
dc545c0b 993 if((iFd = mkstemp(name)) == -1)
7335eebe
AGM
994 {
995 free(name);
98b69f9d 996 return FileFdErrno("mkstemp", "Could not create temporary file for %s", FileName.c_str());
7335eebe
AGM
997 }
998
999 TemporaryFileName = string(name);
7335eebe 1000 free(name);
dc545c0b
DK
1001
1002 if(Perms != 600 && fchmod(iFd, Perms) == -1)
1003 return FileFdErrno("fchmod", "Could not change permissions for temporary file %s", TemporaryFileName.c_str());
7335eebe 1004 }
468720c5 1005 else
561f860a 1006 iFd = open(FileName.c_str(), fileflags, Perms);
468720c5 1007
b711c01e 1008 this->FileName = FileName;
561f860a
DK
1009 if (iFd == -1 || OpenInternDescriptor(Mode, compressor) == false)
1010 {
468720c5 1011 if (iFd != -1)
fc81e8f2 1012 {
561f860a
DK
1013 close (iFd);
1014 iFd = -1;
fc81e8f2 1015 }
ae635e3c 1016 return FileFdErrno("open",_("Could not open file %s"), FileName.c_str());
257e8d66 1017 }
578bfd0a 1018
13d87e2e
AL
1019 SetCloseExec(iFd,true);
1020 return true;
578bfd0a 1021}
257e8d66
DK
1022 /*}}}*/
1023// FileFd::OpenDescriptor - Open a filedescriptor /*{{{*/
1024// ---------------------------------------------------------------------
1025/* */
52b47296 1026bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compress, bool AutoClose)
aee1aac6
DK
1027{
1028 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
1029 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
1030 std::string name;
bce778a3
MV
1031
1032 // compat with the old API
1033 if (Mode == ReadOnlyGzip && Compress == None)
1034 Compress = Gzip;
1035
aee1aac6
DK
1036 switch (Compress)
1037 {
1038 case None: name = "."; break;
1039 case Gzip: name = "gzip"; break;
1040 case Bzip2: name = "bzip2"; break;
1041 case Lzma: name = "lzma"; break;
1042 case Xz: name = "xz"; break;
1043 case Auto:
1044 case Extension:
f97bb523
DK
1045 if (AutoClose == true && Fd != -1)
1046 close(Fd);
ae635e3c 1047 return FileFdError("Opening Fd %d in Auto or Extension compression mode is not supported", Fd);
aee1aac6
DK
1048 }
1049 for (; compressor != compressors.end(); ++compressor)
1050 if (compressor->Name == name)
1051 break;
1052 if (compressor == compressors.end())
f97bb523
DK
1053 {
1054 if (AutoClose == true && Fd != -1)
1055 close(Fd);
ae635e3c 1056 return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
f97bb523 1057 }
aee1aac6
DK
1058 return OpenDescriptor(Fd, Mode, *compressor, AutoClose);
1059}
52b47296 1060bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, APT::Configuration::Compressor const &compressor, bool AutoClose)
144c0969
JAK
1061{
1062 Close();
1063 Flags = (AutoClose) ? FileFd::AutoClose : 0;
d3aac32e
DK
1064 if (AutoClose == false && (
1065#ifdef HAVE_ZLIB
1066 compressor.Name == "gzip" ||
1067#endif
1068#ifdef HAVE_BZ2
1069 compressor.Name == "bzip2" ||
1070#endif
1071 false))
1072 {
1073 // Need to duplicate fd here or gzclose for cleanup will close the fd as well
1074 iFd = dup(Fd);
1075 }
1076 else
1077 iFd = Fd;
b711c01e 1078 this->FileName = "";
f97bb523 1079 if (Fd == -1 || OpenInternDescriptor(Mode, compressor) == false)
468720c5 1080 {
f97bb523
DK
1081 if (iFd != -1 && (
1082#ifdef HAVE_ZLIB
1083 compressor.Name == "gzip" ||
1084#endif
1085#ifdef HAVE_BZ2
1086 compressor.Name == "bzip2" ||
1087#endif
1088 AutoClose == true))
1089 {
468720c5 1090 close (iFd);
f97bb523
DK
1091 iFd = -1;
1092 }
1093 return FileFdError(_("Could not open file descriptor %d"), Fd);
144c0969 1094 }
144c0969 1095 return true;
468720c5 1096}
52b47296 1097bool FileFd::OpenInternDescriptor(unsigned int const Mode, APT::Configuration::Compressor const &compressor)
468720c5 1098{
ff477ee1
DK
1099 if (compressor.Name == "." || compressor.Binary.empty() == true)
1100 return true;
1101
ba667cf7
DK
1102 if (d == NULL)
1103 {
1104 d = new FileFdPrivate();
1105 d->openmode = Mode;
1106 d->compressor = compressor;
1107 }
ff477ee1 1108
7efb8c8e 1109#ifdef HAVE_ZLIB
ff477ee1 1110 if (compressor.Name == "gzip")
468720c5 1111 {
adbd7eb4
DK
1112 if (d->gz != NULL)
1113 {
1114 gzclose(d->gz);
1115 d->gz = NULL;
1116 }
468720c5 1117 if ((Mode & ReadWrite) == ReadWrite)
032bd56f 1118 d->gz = gzdopen(iFd, "r+");
468720c5 1119 else if ((Mode & WriteOnly) == WriteOnly)
032bd56f 1120 d->gz = gzdopen(iFd, "w");
468720c5 1121 else
c4997486 1122 d->gz = gzdopen(iFd, "r");
032bd56f 1123 if (d->gz == NULL)
468720c5 1124 return false;
032bd56f 1125 Flags |= Compressed;
561f860a 1126 return true;
468720c5 1127 }
699b209e 1128#endif
c4997486 1129#ifdef HAVE_BZ2
ff477ee1 1130 if (compressor.Name == "bzip2")
c4997486 1131 {
adbd7eb4
DK
1132 if (d->bz2 != NULL)
1133 {
1134 BZ2_bzclose(d->bz2);
1135 d->bz2 = NULL;
1136 }
c4997486
DK
1137 if ((Mode & ReadWrite) == ReadWrite)
1138 d->bz2 = BZ2_bzdopen(iFd, "r+");
1139 else if ((Mode & WriteOnly) == WriteOnly)
1140 d->bz2 = BZ2_bzdopen(iFd, "w");
1141 else
1142 d->bz2 = BZ2_bzdopen(iFd, "r");
1143 if (d->bz2 == NULL)
1144 return false;
1145 Flags |= Compressed;
1146 return true;
1147 }
1148#endif
1149
adbd7eb4
DK
1150 // collect zombies here in case we reopen
1151 if (d->compressor_pid > 0)
1152 ExecWait(d->compressor_pid, "FileFdCompressor", true);
561f860a
DK
1153
1154 if ((Mode & ReadWrite) == ReadWrite)
ae635e3c 1155 return FileFdError("ReadWrite mode is not supported for file %s", FileName.c_str());
561f860a
DK
1156
1157 bool const Comp = (Mode & WriteOnly) == WriteOnly;
561f860a
DK
1158 if (Comp == false)
1159 {
adbd7eb4 1160 // Handle 'decompression' of empty files
561f860a
DK
1161 struct stat Buf;
1162 fstat(iFd, &Buf);
1163 if (Buf.st_size == 0 && S_ISFIFO(Buf.st_mode) == false)
1164 return true;
1165
1166 // We don't need the file open - instead let the compressor open it
1167 // as he properly knows better how to efficiently read from 'his' file
1168 if (FileName.empty() == false)
afb093cd 1169 {
561f860a 1170 close(iFd);
afb093cd
DK
1171 iFd = -1;
1172 }
561f860a
DK
1173 }
1174
1175 // Create a data pipe
1176 int Pipe[2] = {-1,-1};
1177 if (pipe(Pipe) != 0)
ae635e3c 1178 return FileFdErrno("pipe",_("Failed to create subprocess IPC"));
561f860a
DK
1179 for (int J = 0; J != 2; J++)
1180 SetCloseExec(Pipe[J],true);
1181
1182 d->compressed_fd = iFd;
1183 d->pipe = true;
1184
1185 if (Comp == true)
1186 iFd = Pipe[1];
1187 else
1188 iFd = Pipe[0];
1189
1190 // The child..
1191 d->compressor_pid = ExecFork();
1192 if (d->compressor_pid == 0)
1193 {
1194 if (Comp == true)
1195 {
1196 dup2(d->compressed_fd,STDOUT_FILENO);
1197 dup2(Pipe[0],STDIN_FILENO);
1198 }
1199 else
1200 {
1201 if (FileName.empty() == true)
1202 dup2(d->compressed_fd,STDIN_FILENO);
1203 dup2(Pipe[1],STDOUT_FILENO);
1204 }
0b4895d3
DK
1205 int const nullfd = open("/dev/null", O_WRONLY);
1206 if (nullfd != -1)
1207 {
1208 dup2(nullfd,STDERR_FILENO);
1209 close(nullfd);
1210 }
561f860a
DK
1211
1212 SetCloseExec(STDOUT_FILENO,false);
1213 SetCloseExec(STDIN_FILENO,false);
1214
1215 std::vector<char const*> Args;
1216 Args.push_back(compressor.Binary.c_str());
1217 std::vector<std::string> const * const addArgs =
1218 (Comp == true) ? &(compressor.CompressArgs) : &(compressor.UncompressArgs);
1219 for (std::vector<std::string>::const_iterator a = addArgs->begin();
1220 a != addArgs->end(); ++a)
1221 Args.push_back(a->c_str());
1222 if (Comp == false && FileName.empty() == false)
1223 {
1224 Args.push_back("--stdout");
1225 if (TemporaryFileName.empty() == false)
1226 Args.push_back(TemporaryFileName.c_str());
1227 else
1228 Args.push_back(FileName.c_str());
1229 }
1230 Args.push_back(NULL);
1231
1232 execvp(Args[0],(char **)&Args[0]);
1233 cerr << _("Failed to exec compressor ") << Args[0] << endl;
1234 _exit(100);
1235 }
1236 if (Comp == true)
1237 close(Pipe[0]);
468720c5 1238 else
561f860a 1239 close(Pipe[1]);
561f860a 1240
468720c5 1241 return true;
144c0969 1242}
578bfd0a 1243 /*}}}*/
8e06abb2 1244// FileFd::~File - Closes the file /*{{{*/
578bfd0a
AL
1245// ---------------------------------------------------------------------
1246/* If the proper modes are selected then we close the Fd and possibly
1247 unlink the file on error. */
8e06abb2 1248FileFd::~FileFd()
578bfd0a
AL
1249{
1250 Close();
500400fe 1251 if (d != NULL)
500400fe 1252 d->CloseDown(FileName);
96ab3c6f
MV
1253 delete d;
1254 d = NULL;
578bfd0a
AL
1255}
1256 /*}}}*/
8e06abb2 1257// FileFd::Read - Read a bit of the file /*{{{*/
578bfd0a 1258// ---------------------------------------------------------------------
b0db36b1
AL
1259/* We are carefull to handle interruption by a signal while reading
1260 gracefully. */
650faab0 1261bool FileFd::Read(void *To,unsigned long long Size,unsigned long long *Actual)
578bfd0a 1262{
3286ad13 1263 ssize_t Res;
b0db36b1 1264 errno = 0;
f604cf55
AL
1265 if (Actual != 0)
1266 *Actual = 0;
699b209e 1267 *((char *)To) = '\0';
b0db36b1 1268 do
578bfd0a 1269 {
7efb8c8e 1270#ifdef HAVE_ZLIB
ff477ee1 1271 if (d != NULL && d->gz != NULL)
c4997486
DK
1272 Res = gzread(d->gz,To,Size);
1273 else
1274#endif
1275#ifdef HAVE_BZ2
ff477ee1 1276 if (d != NULL && d->bz2 != NULL)
c4997486 1277 Res = BZ2_bzread(d->bz2,To,Size);
a3a03f5d 1278 else
699b209e 1279#endif
a3a03f5d 1280 Res = read(iFd,To,Size);
b711c01e 1281
b0db36b1
AL
1282 if (Res < 0)
1283 {
b711c01e
DK
1284 if (errno == EINTR)
1285 continue;
7efb8c8e 1286#ifdef HAVE_ZLIB
ff477ee1 1287 if (d != NULL && d->gz != NULL)
b711c01e
DK
1288 {
1289 int err;
1290 char const * const errmsg = gzerror(d->gz, &err);
1291 if (err != Z_ERRNO)
ae635e3c 1292 return FileFdError("gzread: %s (%d: %s)", _("Read error"), err, errmsg);
b711c01e 1293 }
c4997486
DK
1294#endif
1295#ifdef HAVE_BZ2
ff477ee1 1296 if (d != NULL && d->bz2 != NULL)
c4997486
DK
1297 {
1298 int err;
1299 char const * const errmsg = BZ2_bzerror(d->bz2, &err);
1300 if (err != BZ_IO_ERROR)
ae635e3c 1301 return FileFdError("BZ2_bzread: %s (%d: %s)", _("Read error"), err, errmsg);
c4997486 1302 }
b711c01e 1303#endif
ae635e3c 1304 return FileFdErrno("read",_("Read error"));
b0db36b1 1305 }
578bfd0a 1306
b0db36b1
AL
1307 To = (char *)To + Res;
1308 Size -= Res;
ff477ee1
DK
1309 if (d != NULL)
1310 d->seekpos += Res;
f604cf55
AL
1311 if (Actual != 0)
1312 *Actual += Res;
b0db36b1
AL
1313 }
1314 while (Res > 0 && Size > 0);
1315
1316 if (Size == 0)
1317 return true;
1318
ddc1d8d0 1319 // Eof handling
f604cf55 1320 if (Actual != 0)
ddc1d8d0
AL
1321 {
1322 Flags |= HitEof;
1323 return true;
1324 }
ae635e3c
DK
1325
1326 return FileFdError(_("read, still have %llu to read but none left"), Size);
578bfd0a
AL
1327}
1328 /*}}}*/
032bd56f
DK
1329// FileFd::ReadLine - Read a complete line from the file /*{{{*/
1330// ---------------------------------------------------------------------
1331/* Beware: This method can be quiet slow for big buffers on UNcompressed
1332 files because of the naive implementation! */
1333char* FileFd::ReadLine(char *To, unsigned long long const Size)
1334{
699b209e 1335 *To = '\0';
7efb8c8e 1336#ifdef HAVE_ZLIB
ff477ee1 1337 if (d != NULL && d->gz != NULL)
032bd56f 1338 return gzgets(d->gz, To, Size);
699b209e 1339#endif
032bd56f
DK
1340
1341 unsigned long long read = 0;
40468850
DK
1342 while ((Size - 1) != read)
1343 {
1344 unsigned long long done = 0;
1345 if (Read(To + read, 1, &done) == false)
1346 return NULL;
1347 if (done == 0)
1348 break;
1349 if (To[read++] == '\n')
1350 break;
1351 }
1352 if (read == 0)
032bd56f 1353 return NULL;
40468850 1354 To[read] = '\0';
032bd56f
DK
1355 return To;
1356}
1357 /*}}}*/
8e06abb2 1358// FileFd::Write - Write to the file /*{{{*/
578bfd0a
AL
1359// ---------------------------------------------------------------------
1360/* */
650faab0 1361bool FileFd::Write(const void *From,unsigned long long Size)
578bfd0a 1362{
3286ad13 1363 ssize_t Res;
b0db36b1
AL
1364 errno = 0;
1365 do
578bfd0a 1366 {
7efb8c8e 1367#ifdef HAVE_ZLIB
ff477ee1 1368 if (d != NULL && d->gz != NULL)
032bd56f 1369 Res = gzwrite(d->gz,From,Size);
a3a03f5d 1370 else
c4997486
DK
1371#endif
1372#ifdef HAVE_BZ2
ff477ee1 1373 if (d != NULL && d->bz2 != NULL)
c4997486
DK
1374 Res = BZ2_bzwrite(d->bz2,(void*)From,Size);
1375 else
699b209e 1376#endif
a3a03f5d 1377 Res = write(iFd,From,Size);
b0db36b1
AL
1378 if (Res < 0 && errno == EINTR)
1379 continue;
1380 if (Res < 0)
1381 {
c4997486 1382#ifdef HAVE_ZLIB
ff477ee1 1383 if (d != NULL && d->gz != NULL)
c4997486
DK
1384 {
1385 int err;
1386 char const * const errmsg = gzerror(d->gz, &err);
1387 if (err != Z_ERRNO)
ae635e3c 1388 return FileFdError("gzwrite: %s (%d: %s)", _("Write error"), err, errmsg);
c4997486
DK
1389 }
1390#endif
1391#ifdef HAVE_BZ2
ff477ee1 1392 if (d != NULL && d->bz2 != NULL)
c4997486
DK
1393 {
1394 int err;
1395 char const * const errmsg = BZ2_bzerror(d->bz2, &err);
1396 if (err != BZ_IO_ERROR)
ae635e3c 1397 return FileFdError("BZ2_bzwrite: %s (%d: %s)", _("Write error"), err, errmsg);
c4997486
DK
1398 }
1399#endif
ae635e3c 1400 return FileFdErrno("write",_("Write error"));
b0db36b1
AL
1401 }
1402
1403 From = (char *)From + Res;
1404 Size -= Res;
ff477ee1
DK
1405 if (d != NULL)
1406 d->seekpos += Res;
578bfd0a 1407 }
b0db36b1 1408 while (Res > 0 && Size > 0);
578bfd0a 1409
b0db36b1
AL
1410 if (Size == 0)
1411 return true;
ae635e3c
DK
1412
1413 return FileFdError(_("write, still have %llu to write but couldn't"), Size);
d68d65ad
DK
1414}
1415bool FileFd::Write(int Fd, const void *From, unsigned long long Size)
1416{
3286ad13 1417 ssize_t Res;
d68d65ad
DK
1418 errno = 0;
1419 do
1420 {
1421 Res = write(Fd,From,Size);
1422 if (Res < 0 && errno == EINTR)
1423 continue;
1424 if (Res < 0)
1425 return _error->Errno("write",_("Write error"));
1426
1427 From = (char *)From + Res;
1428 Size -= Res;
1429 }
1430 while (Res > 0 && Size > 0);
1431
1432 if (Size == 0)
1433 return true;
1434
1435 return _error->Error(_("write, still have %llu to write but couldn't"), Size);
578bfd0a
AL
1436}
1437 /*}}}*/
8e06abb2 1438// FileFd::Seek - Seek in the file /*{{{*/
578bfd0a
AL
1439// ---------------------------------------------------------------------
1440/* */
650faab0 1441bool FileFd::Seek(unsigned long long To)
578bfd0a 1442{
ff477ee1 1443 if (d != NULL && (d->pipe == true
c4997486 1444#ifdef HAVE_BZ2
ff477ee1 1445 || d->bz2 != NULL
c4997486 1446#endif
ff477ee1 1447 ))
699b209e 1448 {
1abbc47c
DK
1449 // Our poor man seeking in pipes is costly, so try to avoid it
1450 unsigned long long seekpos = Tell();
1451 if (seekpos == To)
1452 return true;
1453 else if (seekpos < To)
1454 return Skip(To - seekpos);
1455
561f860a 1456 if ((d->openmode & ReadOnly) != ReadOnly)
ae635e3c 1457 return FileFdError("Reopen is only implemented for read-only files!");
c4997486 1458#ifdef HAVE_BZ2
e3b402f4
MV
1459 if (d->bz2 != NULL)
1460 {
1461 BZ2_bzclose(d->bz2);
1462 d->bz2 = NULL;
1463 }
c4997486 1464#endif
afb093cd
DK
1465 if (iFd != -1)
1466 close(iFd);
1467 iFd = -1;
561f860a
DK
1468 if (TemporaryFileName.empty() == false)
1469 iFd = open(TemporaryFileName.c_str(), O_RDONLY);
1470 else if (FileName.empty() == false)
1471 iFd = open(FileName.c_str(), O_RDONLY);
1472 else
6fd947bd
DK
1473 {
1474 if (d->compressed_fd > 0)
1475 if (lseek(d->compressed_fd, 0, SEEK_SET) != 0)
1476 iFd = d->compressed_fd;
500400fe 1477 if (iFd < 0)
ae635e3c 1478 return FileFdError("Reopen is not implemented for pipes opened with FileFd::OpenDescriptor()!");
6fd947bd 1479 }
561f860a
DK
1480
1481 if (OpenInternDescriptor(d->openmode, d->compressor) == false)
ae635e3c 1482 return FileFdError("Seek on file %s because it couldn't be reopened", FileName.c_str());
561f860a
DK
1483
1484 if (To != 0)
1485 return Skip(To);
1abbc47c
DK
1486
1487 d->seekpos = To;
561f860a 1488 return true;
699b209e 1489 }
3286ad13 1490 off_t res;
7efb8c8e 1491#ifdef HAVE_ZLIB
ff477ee1 1492 if (d != NULL && d->gz)
032bd56f 1493 res = gzseek(d->gz,To,SEEK_SET);
a3a03f5d 1494 else
699b209e 1495#endif
a3a03f5d 1496 res = lseek(iFd,To,SEEK_SET);
3286ad13 1497 if (res != (off_t)To)
ae635e3c 1498 return FileFdError("Unable to seek to %llu", To);
1abbc47c 1499
ff477ee1
DK
1500 if (d != NULL)
1501 d->seekpos = To;
727f18af
AL
1502 return true;
1503}
1504 /*}}}*/
1505// FileFd::Skip - Seek in the file /*{{{*/
1506// ---------------------------------------------------------------------
1507/* */
650faab0 1508bool FileFd::Skip(unsigned long long Over)
727f18af 1509{
ff477ee1 1510 if (d != NULL && (d->pipe == true
c4997486 1511#ifdef HAVE_BZ2
ff477ee1 1512 || d->bz2 != NULL
c4997486 1513#endif
ff477ee1 1514 ))
40468850
DK
1515 {
1516 d->seekpos += Over;
1517 char buffer[1024];
1518 while (Over != 0)
1519 {
1520 unsigned long long toread = std::min((unsigned long long) sizeof(buffer), Over);
1521 if (Read(buffer, toread) == false)
ae635e3c 1522 return FileFdError("Unable to seek ahead %llu",Over);
40468850
DK
1523 Over -= toread;
1524 }
1525 return true;
1526 }
1527
3286ad13 1528 off_t res;
7efb8c8e 1529#ifdef HAVE_ZLIB
ff477ee1 1530 if (d != NULL && d->gz != NULL)
032bd56f 1531 res = gzseek(d->gz,Over,SEEK_CUR);
a3a03f5d 1532 else
699b209e 1533#endif
a3a03f5d 1534 res = lseek(iFd,Over,SEEK_CUR);
1535 if (res < 0)
ae635e3c 1536 return FileFdError("Unable to seek ahead %llu",Over);
ff477ee1
DK
1537 if (d != NULL)
1538 d->seekpos = res;
1abbc47c 1539
6d5dd02a
AL
1540 return true;
1541}
1542 /*}}}*/
1543// FileFd::Truncate - Truncate the file /*{{{*/
1544// ---------------------------------------------------------------------
1545/* */
650faab0 1546bool FileFd::Truncate(unsigned long long To)
6d5dd02a 1547{
c4997486 1548#if defined HAVE_ZLIB || defined HAVE_BZ2
ff477ee1 1549 if (d != NULL && (d->gz != NULL || d->bz2 != NULL))
ae635e3c 1550 return FileFdError("Truncating compressed files is not implemented (%s)", FileName.c_str());
c4997486 1551#endif
6d5dd02a 1552 if (ftruncate(iFd,To) != 0)
ae635e3c
DK
1553 return FileFdError("Unable to truncate to %llu",To);
1554
578bfd0a
AL
1555 return true;
1556}
1557 /*}}}*/
7f25bdff
AL
1558// FileFd::Tell - Current seek position /*{{{*/
1559// ---------------------------------------------------------------------
1560/* */
650faab0 1561unsigned long long FileFd::Tell()
7f25bdff 1562{
1abbc47c
DK
1563 // In theory, we could just return seekpos here always instead of
1564 // seeking around, but not all users of FileFd use always Seek() and co
1565 // so d->seekpos isn't always true and we can just use it as a hint if
1566 // we have nothing else, but not always as an authority…
ff477ee1 1567 if (d != NULL && (d->pipe == true
c4997486 1568#ifdef HAVE_BZ2
ff477ee1 1569 || d->bz2 != NULL
c4997486 1570#endif
ff477ee1 1571 ))
1abbc47c
DK
1572 return d->seekpos;
1573
a3a03f5d 1574 off_t Res;
7efb8c8e 1575#ifdef HAVE_ZLIB
ff477ee1 1576 if (d != NULL && d->gz != NULL)
032bd56f 1577 Res = gztell(d->gz);
a3a03f5d 1578 else
699b209e 1579#endif
a3a03f5d 1580 Res = lseek(iFd,0,SEEK_CUR);
7f25bdff 1581 if (Res == (off_t)-1)
ae635e3c 1582 FileFdErrno("lseek","Failed to determine the current file position");
ff477ee1
DK
1583 if (d != NULL)
1584 d->seekpos = Res;
7f25bdff
AL
1585 return Res;
1586}
1587 /*}}}*/
4260fd39 1588// FileFd::FileSize - Return the size of the file /*{{{*/
578bfd0a
AL
1589// ---------------------------------------------------------------------
1590/* */
650faab0 1591unsigned long long FileFd::FileSize()
578bfd0a
AL
1592{
1593 struct stat Buf;
ff477ee1 1594 if ((d == NULL || d->pipe == false) && fstat(iFd,&Buf) != 0)
ae635e3c 1595 return FileFdErrno("fstat","Unable to determine the file size");
699b209e
DK
1596
1597 // for compressor pipes st_size is undefined and at 'best' zero
ff477ee1 1598 if ((d != NULL && d->pipe == true) || S_ISFIFO(Buf.st_mode))
699b209e
DK
1599 {
1600 // we set it here, too, as we get the info here for free
1601 // in theory the Open-methods should take care of it already
ff477ee1
DK
1602 if (d != NULL)
1603 d->pipe = true;
699b209e 1604 if (stat(FileName.c_str(), &Buf) != 0)
ae635e3c 1605 return FileFdErrno("stat","Unable to determine the file size");
699b209e
DK
1606 }
1607
4260fd39
DK
1608 return Buf.st_size;
1609}
1610 /*}}}*/
1611// FileFd::Size - Return the size of the content in the file /*{{{*/
1612// ---------------------------------------------------------------------
1613/* */
650faab0 1614unsigned long long FileFd::Size()
4260fd39 1615{
650faab0 1616 unsigned long long size = FileSize();
44dc669e 1617
699b209e
DK
1618 // for compressor pipes st_size is undefined and at 'best' zero,
1619 // so we 'read' the content and 'seek' back - see there
ff477ee1 1620 if (d != NULL && (d->pipe == true
c4997486 1621#ifdef HAVE_BZ2
ff477ee1 1622 || (d->bz2 && size > 0)
c4997486 1623#endif
ff477ee1 1624 ))
699b209e 1625 {
1abbc47c 1626 unsigned long long const oldSeek = Tell();
699b209e
DK
1627 char ignore[1000];
1628 unsigned long long read = 0;
1629 do {
638593bd
DK
1630 if (Read(ignore, sizeof(ignore), &read) == false)
1631 {
1632 Seek(oldSeek);
1633 return 0;
1634 }
699b209e 1635 } while(read != 0);
1abbc47c
DK
1636 size = Tell();
1637 Seek(oldSeek);
699b209e 1638 }
7efb8c8e 1639#ifdef HAVE_ZLIB
44dc669e 1640 // only check gzsize if we are actually a gzip file, just checking for
032bd56f 1641 // "gz" is not sufficient as uncompressed files could be opened with
44dc669e 1642 // gzopen in "direct" mode as well
ff477ee1 1643 else if (d != NULL && d->gz && !gzdirect(d->gz) && size > 0)
9c182afa 1644 {
65c72a4b 1645 off_t const oldPos = lseek(iFd,0,SEEK_CUR);
9c182afa
MP
1646 /* unfortunately zlib.h doesn't provide a gzsize(), so we have to do
1647 * this ourselves; the original (uncompressed) file size is the last 32
1648 * bits of the file */
650faab0 1649 // FIXME: Size for gz-files is limited by 32bit… no largefile support
9c182afa 1650 if (lseek(iFd, -4, SEEK_END) < 0)
638593bd
DK
1651 {
1652 FileFdErrno("lseek","Unable to seek to end of gzipped file");
1653 return 0;
1654 }
1655 size = 0;
9c182afa 1656 if (read(iFd, &size, 4) != 4)
638593bd
DK
1657 {
1658 FileFdErrno("read","Unable to read original size of gzipped file");
1659 return 0;
1660 }
f330c0f3
DK
1661
1662#ifdef WORDS_BIGENDIAN
2cae0ccb
DK
1663 uint32_t tmp_size = size;
1664 uint8_t const * const p = (uint8_t const * const) &tmp_size;
1665 tmp_size = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
1666 size = tmp_size;
f330c0f3 1667#endif
9c182afa 1668
65c72a4b 1669 if (lseek(iFd, oldPos, SEEK_SET) < 0)
638593bd
DK
1670 {
1671 FileFdErrno("lseek","Unable to seek in gzipped file");
1672 return 0;
1673 }
65c72a4b 1674
9c182afa
MP
1675 return size;
1676 }
699b209e 1677#endif
9c182afa 1678
44dc669e 1679 return size;
578bfd0a
AL
1680}
1681 /*}}}*/
76a763e1
DK
1682// FileFd::ModificationTime - Return the time of last touch /*{{{*/
1683// ---------------------------------------------------------------------
1684/* */
1685time_t FileFd::ModificationTime()
1686{
1687 struct stat Buf;
ff477ee1 1688 if ((d == NULL || d->pipe == false) && fstat(iFd,&Buf) != 0)
76a763e1 1689 {
ae635e3c 1690 FileFdErrno("fstat","Unable to determine the modification time of file %s", FileName.c_str());
76a763e1
DK
1691 return 0;
1692 }
699b209e
DK
1693
1694 // for compressor pipes st_size is undefined and at 'best' zero
ff477ee1 1695 if ((d != NULL && d->pipe == true) || S_ISFIFO(Buf.st_mode))
699b209e
DK
1696 {
1697 // we set it here, too, as we get the info here for free
1698 // in theory the Open-methods should take care of it already
ff477ee1
DK
1699 if (d != NULL)
1700 d->pipe = true;
699b209e
DK
1701 if (stat(FileName.c_str(), &Buf) != 0)
1702 {
ae635e3c 1703 FileFdErrno("fstat","Unable to determine the modification time of file %s", FileName.c_str());
699b209e
DK
1704 return 0;
1705 }
1706 }
1707
76a763e1
DK
1708 return Buf.st_mtime;
1709}
1710 /*}}}*/
8e06abb2 1711// FileFd::Close - Close the file if the close flag is set /*{{{*/
578bfd0a
AL
1712// ---------------------------------------------------------------------
1713/* */
8e06abb2 1714bool FileFd::Close()
578bfd0a 1715{
032bd56f
DK
1716 if (iFd == -1)
1717 return true;
1718
578bfd0a
AL
1719 bool Res = true;
1720 if ((Flags & AutoClose) == AutoClose)
d13c2d3f 1721 {
500400fe
DK
1722 if ((Flags & Compressed) != Compressed && iFd > 0 && close(iFd) != 0)
1723 Res &= _error->Errno("close",_("Problem closing the file %s"), FileName.c_str());
1724
1725 if (d != NULL)
1726 {
1727 Res &= d->CloseDown(FileName);
1728 delete d;
1729 d = NULL;
1730 }
d13c2d3f 1731 }
3010fb0e 1732
d3aac32e 1733 if ((Flags & Replace) == Replace) {
3010fb0e 1734 if (rename(TemporaryFileName.c_str(), FileName.c_str()) != 0)
62d073d9
DK
1735 Res &= _error->Errno("rename",_("Problem renaming the file %s to %s"), TemporaryFileName.c_str(), FileName.c_str());
1736
fd3b761e 1737 FileName = TemporaryFileName; // for the unlink() below.
257e8d66 1738 TemporaryFileName.clear();
3010fb0e 1739 }
62d073d9
DK
1740
1741 iFd = -1;
1742
578bfd0a
AL
1743 if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail &&
1744 FileName.empty() == false)
1745 if (unlink(FileName.c_str()) != 0)
62d073d9 1746 Res &= _error->WarningE("unlnk",_("Problem unlinking the file %s"), FileName.c_str());
3010fb0e 1747
fbb89d94
DK
1748 if (Res == false)
1749 Flags |= Fail;
578bfd0a
AL
1750 return Res;
1751}
1752 /*}}}*/
b2e465d6
AL
1753// FileFd::Sync - Sync the file /*{{{*/
1754// ---------------------------------------------------------------------
1755/* */
1756bool FileFd::Sync()
1757{
b2e465d6 1758 if (fsync(iFd) != 0)
ae635e3c
DK
1759 return FileFdErrno("sync",_("Problem syncing the file"));
1760 return true;
1761}
1762 /*}}}*/
1763// FileFd::FileFdErrno - set Fail and call _error->Errno *{{{*/
1764bool FileFd::FileFdErrno(const char *Function, const char *Description,...)
1765{
1766 Flags |= Fail;
1767 va_list args;
1768 size_t msgSize = 400;
1769 int const errsv = errno;
1770 while (true)
fbb89d94 1771 {
ae635e3c
DK
1772 va_start(args,Description);
1773 if (_error->InsertErrno(GlobalError::ERROR, Function, Description, args, errsv, msgSize) == false)
1774 break;
1775 va_end(args);
fbb89d94 1776 }
ae635e3c
DK
1777 return false;
1778}
1779 /*}}}*/
1780// FileFd::FileFdError - set Fail and call _error->Error *{{{*/
1781bool FileFd::FileFdError(const char *Description,...) {
1782 Flags |= Fail;
1783 va_list args;
1784 size_t msgSize = 400;
1785 while (true)
1786 {
1787 va_start(args,Description);
1788 if (_error->Insert(GlobalError::ERROR, Description, args, msgSize) == false)
1789 break;
1790 va_end(args);
1791 }
1792 return false;
b2e465d6
AL
1793}
1794 /*}}}*/
699b209e
DK
1795
1796gzFile FileFd::gzFd() { return (gzFile) d->gz; }
488011fa
MV
1797
1798
1799// Glob - wrapper around "glob()" /*{{{*/
1800// ---------------------------------------------------------------------
1801/* */
1802std::vector<std::string> Glob(std::string const &pattern, int flags)
1803{
1804 std::vector<std::string> result;
1805 glob_t globbuf;
ec4835a1
ÁGM
1806 int glob_res;
1807 unsigned int i;
488011fa
MV
1808
1809 glob_res = glob(pattern.c_str(), flags, NULL, &globbuf);
1810
1811 if (glob_res != 0)
1812 {
1813 if(glob_res != GLOB_NOMATCH) {
1814 _error->Errno("glob", "Problem with glob");
1815 return result;
1816 }
1817 }
1818
1819 // append results
1820 for(i=0;i<globbuf.gl_pathc;i++)
1821 result.push_back(string(globbuf.gl_pathv[i]));
1822
1823 globfree(&globbuf);
1824 return result;
1825}
1826 /*}}}*/