]> git.saurik.com Git - apt.git/blob - apt-pkg/contrib/fileutl.cc
a98c2cb855e4271257032a15dc6571d61bc7a029
[apt.git] / apt-pkg / contrib / fileutl.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: fileutl.cc,v 1.42 2002/09/14 05:29:22 jgg Exp $
4 /* ######################################################################
5
6 File Utilities
7
8 CopyFile - Buffered copy of a single file
9 GetLock - dpkg compatible lock file manipulation (fcntl)
10
11 Most of this source is placed in the Public Domain, do with it what
12 you will
13 It was originally written by Jason Gunthorpe <jgg@debian.org>.
14 FileFd gzip support added by Martin Pitt <martin.pitt@canonical.com>
15
16 The exception is RunScripts() it is under the GPLv2
17
18 ##################################################################### */
19 /*}}}*/
20 // Include Files /*{{{*/
21 #include <config.h>
22
23 #include <apt-pkg/fileutl.h>
24 #include <apt-pkg/strutl.h>
25 #include <apt-pkg/error.h>
26 #include <apt-pkg/sptr.h>
27 #include <apt-pkg/aptconfiguration.h>
28 #include <apt-pkg/configuration.h>
29
30 #include <cstdlib>
31 #include <cstring>
32 #include <cstdio>
33
34 #include <iostream>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <sys/time.h>
40 #include <sys/wait.h>
41 #include <dirent.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <set>
45 #include <algorithm>
46
47 // FIXME: Compressor Fds have some speed disadvantages and are a bit buggy currently,
48 // so while the current implementation satisfies the testcases it is not a real option
49 // to disable it for now
50 #define APT_USE_ZLIB 1
51 #if APT_USE_ZLIB
52 #include <zlib.h>
53 #else
54 #pragma message "Usage of zlib is DISABLED!"
55 #endif
56
57 #ifdef WORDS_BIGENDIAN
58 #include <inttypes.h>
59 #endif
60
61 #include <apti18n.h>
62 /*}}}*/
63
64 using namespace std;
65
66 class FileFdPrivate {
67 public:
68 #if APT_USE_ZLIB
69 gzFile gz;
70 #else
71 void* gz;
72 #endif
73 int compressed_fd;
74 pid_t compressor_pid;
75 bool pipe;
76 APT::Configuration::Compressor compressor;
77 unsigned int openmode;
78 FileFdPrivate() : gz(NULL), compressed_fd(-1), compressor_pid(-1), pipe(false) {};
79 };
80
81 // RunScripts - Run a set of scripts from a configuration subtree /*{{{*/
82 // ---------------------------------------------------------------------
83 /* */
84 bool RunScripts(const char *Cnf)
85 {
86 Configuration::Item const *Opts = _config->Tree(Cnf);
87 if (Opts == 0 || Opts->Child == 0)
88 return true;
89 Opts = Opts->Child;
90
91 // Fork for running the system calls
92 pid_t Child = ExecFork();
93
94 // This is the child
95 if (Child == 0)
96 {
97 if (_config->FindDir("DPkg::Chroot-Directory","/") != "/")
98 {
99 std::cerr << "Chrooting into "
100 << _config->FindDir("DPkg::Chroot-Directory")
101 << std::endl;
102 if (chroot(_config->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0)
103 _exit(100);
104 }
105
106 if (chdir("/tmp/") != 0)
107 _exit(100);
108
109 unsigned int Count = 1;
110 for (; Opts != 0; Opts = Opts->Next, Count++)
111 {
112 if (Opts->Value.empty() == true)
113 continue;
114
115 if (system(Opts->Value.c_str()) != 0)
116 _exit(100+Count);
117 }
118 _exit(0);
119 }
120
121 // Wait for the child
122 int Status = 0;
123 while (waitpid(Child,&Status,0) != Child)
124 {
125 if (errno == EINTR)
126 continue;
127 return _error->Errno("waitpid","Couldn't wait for subprocess");
128 }
129
130 // Restore sig int/quit
131 signal(SIGQUIT,SIG_DFL);
132 signal(SIGINT,SIG_DFL);
133
134 // Check for an error code.
135 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
136 {
137 unsigned int Count = WEXITSTATUS(Status);
138 if (Count > 100)
139 {
140 Count -= 100;
141 for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--);
142 _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str());
143 }
144
145 return _error->Error("Sub-process returned an error code");
146 }
147
148 return true;
149 }
150 /*}}}*/
151
152 // CopyFile - Buffered copy of a file /*{{{*/
153 // ---------------------------------------------------------------------
154 /* The caller is expected to set things so that failure causes erasure */
155 bool CopyFile(FileFd &From,FileFd &To)
156 {
157 if (From.IsOpen() == false || To.IsOpen() == false)
158 return false;
159
160 // Buffered copy between fds
161 SPtrArray<unsigned char> Buf = new unsigned char[64000];
162 unsigned long long Size = From.Size();
163 while (Size != 0)
164 {
165 unsigned long long ToRead = Size;
166 if (Size > 64000)
167 ToRead = 64000;
168
169 if (From.Read(Buf,ToRead) == false ||
170 To.Write(Buf,ToRead) == false)
171 return false;
172
173 Size -= ToRead;
174 }
175
176 return true;
177 }
178 /*}}}*/
179 // GetLock - Gets a lock file /*{{{*/
180 // ---------------------------------------------------------------------
181 /* This will create an empty file of the given name and lock it. Once this
182 is done all other calls to GetLock in any other process will fail with
183 -1. The return result is the fd of the file, the call should call
184 close at some time. */
185 int GetLock(string File,bool Errors)
186 {
187 // GetLock() is used in aptitude on directories with public-write access
188 // Use O_NOFOLLOW here to prevent symlink traversal attacks
189 int FD = open(File.c_str(),O_RDWR | O_CREAT | O_NOFOLLOW,0640);
190 if (FD < 0)
191 {
192 // Read only .. cant have locking problems there.
193 if (errno == EROFS)
194 {
195 _error->Warning(_("Not using locking for read only lock file %s"),File.c_str());
196 return dup(0); // Need something for the caller to close
197 }
198
199 if (Errors == true)
200 _error->Errno("open",_("Could not open lock file %s"),File.c_str());
201
202 // Feh.. We do this to distinguish the lock vs open case..
203 errno = EPERM;
204 return -1;
205 }
206 SetCloseExec(FD,true);
207
208 // Aquire a write lock
209 struct flock fl;
210 fl.l_type = F_WRLCK;
211 fl.l_whence = SEEK_SET;
212 fl.l_start = 0;
213 fl.l_len = 0;
214 if (fcntl(FD,F_SETLK,&fl) == -1)
215 {
216 if (errno == ENOLCK)
217 {
218 _error->Warning(_("Not using locking for nfs mounted lock file %s"),File.c_str());
219 return dup(0); // Need something for the caller to close
220 }
221 if (Errors == true)
222 _error->Errno("open",_("Could not get lock %s"),File.c_str());
223
224 int Tmp = errno;
225 close(FD);
226 errno = Tmp;
227 return -1;
228 }
229
230 return FD;
231 }
232 /*}}}*/
233 // FileExists - Check if a file exists /*{{{*/
234 // ---------------------------------------------------------------------
235 /* Beware: Directories are also files! */
236 bool FileExists(string File)
237 {
238 struct stat Buf;
239 if (stat(File.c_str(),&Buf) != 0)
240 return false;
241 return true;
242 }
243 /*}}}*/
244 // RealFileExists - Check if a file exists and if it is really a file /*{{{*/
245 // ---------------------------------------------------------------------
246 /* */
247 bool RealFileExists(string File)
248 {
249 struct stat Buf;
250 if (stat(File.c_str(),&Buf) != 0)
251 return false;
252 return ((Buf.st_mode & S_IFREG) != 0);
253 }
254 /*}}}*/
255 // DirectoryExists - Check if a directory exists and is really one /*{{{*/
256 // ---------------------------------------------------------------------
257 /* */
258 bool DirectoryExists(string const &Path)
259 {
260 struct stat Buf;
261 if (stat(Path.c_str(),&Buf) != 0)
262 return false;
263 return ((Buf.st_mode & S_IFDIR) != 0);
264 }
265 /*}}}*/
266 // CreateDirectory - poor man's mkdir -p guarded by a parent directory /*{{{*/
267 // ---------------------------------------------------------------------
268 /* This method will create all directories needed for path in good old
269 mkdir -p style but refuses to do this if Parent is not a prefix of
270 this Path. Example: /var/cache/ and /var/cache/apt/archives are given,
271 so it will create apt/archives if /var/cache exists - on the other
272 hand if the parent is /var/lib the creation will fail as this path
273 is not a parent of the path to be generated. */
274 bool CreateDirectory(string const &Parent, string const &Path)
275 {
276 if (Parent.empty() == true || Path.empty() == true)
277 return false;
278
279 if (DirectoryExists(Path) == true)
280 return true;
281
282 if (DirectoryExists(Parent) == false)
283 return false;
284
285 // we are not going to create directories "into the blue"
286 if (Path.find(Parent, 0) != 0)
287 return false;
288
289 vector<string> const dirs = VectorizeString(Path.substr(Parent.size()), '/');
290 string progress = Parent;
291 for (vector<string>::const_iterator d = dirs.begin(); d != dirs.end(); ++d)
292 {
293 if (d->empty() == true)
294 continue;
295
296 progress.append("/").append(*d);
297 if (DirectoryExists(progress) == true)
298 continue;
299
300 if (mkdir(progress.c_str(), 0755) != 0)
301 return false;
302 }
303 return true;
304 }
305 /*}}}*/
306 // CreateAPTDirectoryIfNeeded - ensure that the given directory exists /*{{{*/
307 // ---------------------------------------------------------------------
308 /* a small wrapper around CreateDirectory to check if it exists and to
309 remove the trailing "/apt/" from the parent directory if needed */
310 bool CreateAPTDirectoryIfNeeded(string const &Parent, string const &Path)
311 {
312 if (DirectoryExists(Path) == true)
313 return true;
314
315 size_t const len = Parent.size();
316 if (len > 5 && Parent.find("/apt/", len - 6, 5) == len - 5)
317 {
318 if (CreateDirectory(Parent.substr(0,len-5), Path) == true)
319 return true;
320 }
321 else if (CreateDirectory(Parent, Path) == true)
322 return true;
323
324 return false;
325 }
326 /*}}}*/
327 // GetListOfFilesInDir - returns a vector of files in the given dir /*{{{*/
328 // ---------------------------------------------------------------------
329 /* If an extension is given only files with this extension are included
330 in the returned vector, otherwise every "normal" file is included. */
331 std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext,
332 bool const &SortList, bool const &AllowNoExt)
333 {
334 std::vector<string> ext;
335 ext.reserve(2);
336 if (Ext.empty() == false)
337 ext.push_back(Ext);
338 if (AllowNoExt == true && ext.empty() == false)
339 ext.push_back("");
340 return GetListOfFilesInDir(Dir, ext, SortList);
341 }
342 std::vector<string> GetListOfFilesInDir(string const &Dir, std::vector<string> const &Ext,
343 bool const &SortList)
344 {
345 // Attention debuggers: need to be set with the environment config file!
346 bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
347 if (Debug == true)
348 {
349 std::clog << "Accept in " << Dir << " only files with the following " << Ext.size() << " extensions:" << std::endl;
350 if (Ext.empty() == true)
351 std::clog << "\tNO extension" << std::endl;
352 else
353 for (std::vector<string>::const_iterator e = Ext.begin();
354 e != Ext.end(); ++e)
355 std::clog << '\t' << (e->empty() == true ? "NO" : *e) << " extension" << std::endl;
356 }
357
358 std::vector<string> List;
359
360 if (DirectoryExists(Dir.c_str()) == false)
361 {
362 _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str());
363 return List;
364 }
365
366 Configuration::MatchAgainstConfig SilentIgnore("Dir::Ignore-Files-Silently");
367 DIR *D = opendir(Dir.c_str());
368 if (D == 0)
369 {
370 _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
371 return List;
372 }
373
374 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
375 {
376 // skip "hidden" files
377 if (Ent->d_name[0] == '.')
378 continue;
379
380 // Make sure it is a file and not something else
381 string const File = flCombine(Dir,Ent->d_name);
382 #ifdef _DIRENT_HAVE_D_TYPE
383 if (Ent->d_type != DT_REG)
384 #endif
385 {
386 if (RealFileExists(File.c_str()) == false)
387 {
388 if (SilentIgnore.Match(Ent->d_name) == false)
389 _error->Notice(_("Ignoring '%s' in directory '%s' as it is not a regular file"), Ent->d_name, Dir.c_str());
390 continue;
391 }
392 }
393
394 // check for accepted extension:
395 // no extension given -> periods are bad as hell!
396 // extensions given -> "" extension allows no extension
397 if (Ext.empty() == false)
398 {
399 string d_ext = flExtension(Ent->d_name);
400 if (d_ext == Ent->d_name) // no extension
401 {
402 if (std::find(Ext.begin(), Ext.end(), "") == Ext.end())
403 {
404 if (Debug == true)
405 std::clog << "Bad file: " << Ent->d_name << " → no extension" << std::endl;
406 if (SilentIgnore.Match(Ent->d_name) == false)
407 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has no filename extension"), Ent->d_name, Dir.c_str());
408 continue;
409 }
410 }
411 else if (std::find(Ext.begin(), Ext.end(), d_ext) == Ext.end())
412 {
413 if (Debug == true)
414 std::clog << "Bad file: " << Ent->d_name << " → bad extension »" << flExtension(Ent->d_name) << "«" << std::endl;
415 if (SilentIgnore.Match(Ent->d_name) == false)
416 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has an invalid filename extension"), Ent->d_name, Dir.c_str());
417 continue;
418 }
419 }
420
421 // Skip bad filenames ala run-parts
422 const char *C = Ent->d_name;
423 for (; *C != 0; ++C)
424 if (isalpha(*C) == 0 && isdigit(*C) == 0
425 && *C != '_' && *C != '-') {
426 // no required extension -> dot is a bad character
427 if (*C == '.' && Ext.empty() == false)
428 continue;
429 break;
430 }
431
432 // we don't reach the end of the name -> bad character included
433 if (*C != 0)
434 {
435 if (Debug == true)
436 std::clog << "Bad file: " << Ent->d_name << " → bad character »"
437 << *C << "« in filename (period allowed: " << (Ext.empty() ? "no" : "yes") << ")" << std::endl;
438 continue;
439 }
440
441 // skip filenames which end with a period. These are never valid
442 if (*(C - 1) == '.')
443 {
444 if (Debug == true)
445 std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
446 continue;
447 }
448
449 if (Debug == true)
450 std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
451 List.push_back(File);
452 }
453 closedir(D);
454
455 if (SortList == true)
456 std::sort(List.begin(),List.end());
457 return List;
458 }
459 /*}}}*/
460 // SafeGetCWD - This is a safer getcwd that returns a dynamic string /*{{{*/
461 // ---------------------------------------------------------------------
462 /* We return / on failure. */
463 string SafeGetCWD()
464 {
465 // Stash the current dir.
466 char S[300];
467 S[0] = 0;
468 if (getcwd(S,sizeof(S)-2) == 0)
469 return "/";
470 unsigned int Len = strlen(S);
471 S[Len] = '/';
472 S[Len+1] = 0;
473 return S;
474 }
475 /*}}}*/
476 // GetModificationTime - Get the mtime of the given file or -1 on error /*{{{*/
477 // ---------------------------------------------------------------------
478 /* We return / on failure. */
479 time_t GetModificationTime(string const &Path)
480 {
481 struct stat St;
482 if (stat(Path.c_str(), &St) < 0)
483 return -1;
484 return St.st_mtime;
485 }
486 /*}}}*/
487 // flNotDir - Strip the directory from the filename /*{{{*/
488 // ---------------------------------------------------------------------
489 /* */
490 string flNotDir(string File)
491 {
492 string::size_type Res = File.rfind('/');
493 if (Res == string::npos)
494 return File;
495 Res++;
496 return string(File,Res,Res - File.length());
497 }
498 /*}}}*/
499 // flNotFile - Strip the file from the directory name /*{{{*/
500 // ---------------------------------------------------------------------
501 /* Result ends in a / */
502 string flNotFile(string File)
503 {
504 string::size_type Res = File.rfind('/');
505 if (Res == string::npos)
506 return "./";
507 Res++;
508 return string(File,0,Res);
509 }
510 /*}}}*/
511 // flExtension - Return the extension for the file /*{{{*/
512 // ---------------------------------------------------------------------
513 /* */
514 string flExtension(string File)
515 {
516 string::size_type Res = File.rfind('.');
517 if (Res == string::npos)
518 return File;
519 Res++;
520 return string(File,Res,Res - File.length());
521 }
522 /*}}}*/
523 // flNoLink - If file is a symlink then deref it /*{{{*/
524 // ---------------------------------------------------------------------
525 /* If the name is not a link then the returned path is the input. */
526 string flNoLink(string File)
527 {
528 struct stat St;
529 if (lstat(File.c_str(),&St) != 0 || S_ISLNK(St.st_mode) == 0)
530 return File;
531 if (stat(File.c_str(),&St) != 0)
532 return File;
533
534 /* Loop resolving the link. There is no need to limit the number of
535 loops because the stat call above ensures that the symlink is not
536 circular */
537 char Buffer[1024];
538 string NFile = File;
539 while (1)
540 {
541 // Read the link
542 int Res;
543 if ((Res = readlink(NFile.c_str(),Buffer,sizeof(Buffer))) <= 0 ||
544 (unsigned)Res >= sizeof(Buffer))
545 return File;
546
547 // Append or replace the previous path
548 Buffer[Res] = 0;
549 if (Buffer[0] == '/')
550 NFile = Buffer;
551 else
552 NFile = flNotFile(NFile) + Buffer;
553
554 // See if we are done
555 if (lstat(NFile.c_str(),&St) != 0)
556 return File;
557 if (S_ISLNK(St.st_mode) == 0)
558 return NFile;
559 }
560 }
561 /*}}}*/
562 // flCombine - Combine a file and a directory /*{{{*/
563 // ---------------------------------------------------------------------
564 /* If the file is an absolute path then it is just returned, otherwise
565 the directory is pre-pended to it. */
566 string flCombine(string Dir,string File)
567 {
568 if (File.empty() == true)
569 return string();
570
571 if (File[0] == '/' || Dir.empty() == true)
572 return File;
573 if (File.length() >= 2 && File[0] == '.' && File[1] == '/')
574 return File;
575 if (Dir[Dir.length()-1] == '/')
576 return Dir + File;
577 return Dir + '/' + File;
578 }
579 /*}}}*/
580 // SetCloseExec - Set the close on exec flag /*{{{*/
581 // ---------------------------------------------------------------------
582 /* */
583 void SetCloseExec(int Fd,bool Close)
584 {
585 if (fcntl(Fd,F_SETFD,(Close == false)?0:FD_CLOEXEC) != 0)
586 {
587 cerr << "FATAL -> Could not set close on exec " << strerror(errno) << endl;
588 exit(100);
589 }
590 }
591 /*}}}*/
592 // SetNonBlock - Set the nonblocking flag /*{{{*/
593 // ---------------------------------------------------------------------
594 /* */
595 void SetNonBlock(int Fd,bool Block)
596 {
597 int Flags = fcntl(Fd,F_GETFL) & (~O_NONBLOCK);
598 if (fcntl(Fd,F_SETFL,Flags | ((Block == false)?0:O_NONBLOCK)) != 0)
599 {
600 cerr << "FATAL -> Could not set non-blocking flag " << strerror(errno) << endl;
601 exit(100);
602 }
603 }
604 /*}}}*/
605 // WaitFd - Wait for a FD to become readable /*{{{*/
606 // ---------------------------------------------------------------------
607 /* This waits for a FD to become readable using select. It is useful for
608 applications making use of non-blocking sockets. The timeout is
609 in seconds. */
610 bool WaitFd(int Fd,bool write,unsigned long timeout)
611 {
612 fd_set Set;
613 struct timeval tv;
614 FD_ZERO(&Set);
615 FD_SET(Fd,&Set);
616 tv.tv_sec = timeout;
617 tv.tv_usec = 0;
618 if (write == true)
619 {
620 int Res;
621 do
622 {
623 Res = select(Fd+1,0,&Set,0,(timeout != 0?&tv:0));
624 }
625 while (Res < 0 && errno == EINTR);
626
627 if (Res <= 0)
628 return false;
629 }
630 else
631 {
632 int Res;
633 do
634 {
635 Res = select(Fd+1,&Set,0,0,(timeout != 0?&tv:0));
636 }
637 while (Res < 0 && errno == EINTR);
638
639 if (Res <= 0)
640 return false;
641 }
642
643 return true;
644 }
645 /*}}}*/
646 // ExecFork - Magical fork that sanitizes the context before execing /*{{{*/
647 // ---------------------------------------------------------------------
648 /* This is used if you want to cleanse the environment for the forked
649 child, it fixes up the important signals and nukes all of the fds,
650 otherwise acts like normal fork. */
651 pid_t ExecFork()
652 {
653 // Fork off the process
654 pid_t Process = fork();
655 if (Process < 0)
656 {
657 cerr << "FATAL -> Failed to fork." << endl;
658 exit(100);
659 }
660
661 // Spawn the subprocess
662 if (Process == 0)
663 {
664 // Setup the signals
665 signal(SIGPIPE,SIG_DFL);
666 signal(SIGQUIT,SIG_DFL);
667 signal(SIGINT,SIG_DFL);
668 signal(SIGWINCH,SIG_DFL);
669 signal(SIGCONT,SIG_DFL);
670 signal(SIGTSTP,SIG_DFL);
671
672 set<int> KeepFDs;
673 Configuration::Item const *Opts = _config->Tree("APT::Keep-Fds");
674 if (Opts != 0 && Opts->Child != 0)
675 {
676 Opts = Opts->Child;
677 for (; Opts != 0; Opts = Opts->Next)
678 {
679 if (Opts->Value.empty() == true)
680 continue;
681 int fd = atoi(Opts->Value.c_str());
682 KeepFDs.insert(fd);
683 }
684 }
685
686 // Close all of our FDs - just in case
687 for (int K = 3; K != 40; K++)
688 {
689 if(KeepFDs.find(K) == KeepFDs.end())
690 fcntl(K,F_SETFD,FD_CLOEXEC);
691 }
692 }
693
694 return Process;
695 }
696 /*}}}*/
697 // ExecWait - Fancy waitpid /*{{{*/
698 // ---------------------------------------------------------------------
699 /* Waits for the given sub process. If Reap is set then no errors are
700 generated. Otherwise a failed subprocess will generate a proper descriptive
701 message */
702 bool ExecWait(pid_t Pid,const char *Name,bool Reap)
703 {
704 if (Pid <= 1)
705 return true;
706
707 // Wait and collect the error code
708 int Status;
709 while (waitpid(Pid,&Status,0) != Pid)
710 {
711 if (errno == EINTR)
712 continue;
713
714 if (Reap == true)
715 return false;
716
717 return _error->Error(_("Waited for %s but it wasn't there"),Name);
718 }
719
720
721 // Check for an error code.
722 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
723 {
724 if (Reap == true)
725 return false;
726 if (WIFSIGNALED(Status) != 0)
727 {
728 if( WTERMSIG(Status) == SIGSEGV)
729 return _error->Error(_("Sub-process %s received a segmentation fault."),Name);
730 else
731 return _error->Error(_("Sub-process %s received signal %u."),Name, WTERMSIG(Status));
732 }
733
734 if (WIFEXITED(Status) != 0)
735 return _error->Error(_("Sub-process %s returned an error code (%u)"),Name,WEXITSTATUS(Status));
736
737 return _error->Error(_("Sub-process %s exited unexpectedly"),Name);
738 }
739
740 return true;
741 }
742 /*}}}*/
743
744 // FileFd::Open - Open a file /*{{{*/
745 // ---------------------------------------------------------------------
746 /* The most commonly used open mode combinations are given with Mode */
747 bool FileFd::Open(string FileName,unsigned int const Mode,CompressMode Compress, unsigned long const Perms)
748 {
749 if (Mode == ReadOnlyGzip)
750 return Open(FileName, ReadOnly, Gzip, Perms);
751
752 if (Compress == Auto && (Mode & WriteOnly) == WriteOnly)
753 return _error->Error("Autodetection on %s only works in ReadOnly openmode!", FileName.c_str());
754
755 // FIXME: Denote inbuilt compressors somehow - as we don't need to have the binaries for them
756 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
757 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
758 if (Compress == Auto)
759 {
760 for (; compressor != compressors.end(); ++compressor)
761 {
762 std::string file = std::string(FileName).append(compressor->Extension);
763 if (FileExists(file) == false)
764 continue;
765 FileName = file;
766 break;
767 }
768 }
769 else if (Compress == Extension)
770 {
771 std::string::size_type const found = FileName.find_last_of('.');
772 std::string ext;
773 if (found != std::string::npos)
774 {
775 ext = FileName.substr(found);
776 if (ext == ".new" || ext == ".bak")
777 {
778 std::string::size_type const found2 = FileName.find_last_of('.', found - 1);
779 if (found2 != std::string::npos)
780 ext = FileName.substr(found2, found - found2);
781 else
782 ext.clear();
783 }
784 }
785 for (; compressor != compressors.end(); ++compressor)
786 if (ext == compressor->Extension)
787 break;
788 // no matching extension - assume uncompressed (imagine files like 'example.org_Packages')
789 if (compressor == compressors.end())
790 for (compressor = compressors.begin(); compressor != compressors.end(); ++compressor)
791 if (compressor->Name == ".")
792 break;
793 }
794 else
795 {
796 std::string name;
797 switch (Compress)
798 {
799 case None: name = "."; break;
800 case Gzip: name = "gzip"; break;
801 case Bzip2: name = "bzip2"; break;
802 case Lzma: name = "lzma"; break;
803 case Xz: name = "xz"; break;
804 case Auto:
805 case Extension:
806 // Unreachable
807 return _error->Error("Opening File %s in None, Auto or Extension should be already handled?!?", FileName.c_str());
808 }
809 for (; compressor != compressors.end(); ++compressor)
810 if (compressor->Name == name)
811 break;
812 if (compressor == compressors.end())
813 return _error->Error("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
814 }
815
816 if (compressor == compressors.end())
817 return _error->Error("Can't find a match for specified compressor mode for file %s", FileName.c_str());
818 return Open(FileName, Mode, *compressor, Perms);
819 }
820 bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Compressor const &compressor, unsigned long const Perms)
821 {
822 Close();
823 d = new FileFdPrivate;
824 d->openmode = Mode;
825 Flags = AutoClose;
826
827 if ((Mode & WriteOnly) != WriteOnly && (Mode & (Atomic | Create | Empty | Exclusive)) != 0)
828 return _error->Error("ReadOnly mode for %s doesn't accept additional flags!", FileName.c_str());
829 if ((Mode & ReadWrite) == 0)
830 return _error->Error("No openmode provided in FileFd::Open for %s", FileName.c_str());
831
832 if ((Mode & Atomic) == Atomic)
833 {
834 Flags |= Replace;
835 char *name = strdup((FileName + ".XXXXXX").c_str());
836 TemporaryFileName = string(mktemp(name));
837 free(name);
838 }
839 else if ((Mode & (Exclusive | Create)) == (Exclusive | Create))
840 {
841 // for atomic, this will be done by rename in Close()
842 unlink(FileName.c_str());
843 }
844 if ((Mode & Empty) == Empty)
845 {
846 struct stat Buf;
847 if (lstat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode))
848 unlink(FileName.c_str());
849 }
850
851 int fileflags = 0;
852 #define if_FLAGGED_SET(FLAG, MODE) if ((Mode & FLAG) == FLAG) fileflags |= MODE
853 if_FLAGGED_SET(ReadWrite, O_RDWR);
854 else if_FLAGGED_SET(ReadOnly, O_RDONLY);
855 else if_FLAGGED_SET(WriteOnly, O_WRONLY);
856
857 if_FLAGGED_SET(Create, O_CREAT);
858 if_FLAGGED_SET(Empty, O_TRUNC);
859 if_FLAGGED_SET(Exclusive, O_EXCL);
860 else if_FLAGGED_SET(Atomic, O_EXCL);
861 #undef if_FLAGGED_SET
862
863 if (TemporaryFileName.empty() == false)
864 iFd = open(TemporaryFileName.c_str(), fileflags, Perms);
865 else
866 iFd = open(FileName.c_str(), fileflags, Perms);
867
868 if (iFd == -1 || OpenInternDescriptor(Mode, compressor) == false)
869 {
870 if (iFd != -1)
871 {
872 close (iFd);
873 iFd = -1;
874 }
875 return _error->Errno("open",_("Could not open file %s"), FileName.c_str());
876 }
877
878 this->FileName = FileName;
879 SetCloseExec(iFd,true);
880 return true;
881 }
882 /*}}}*/
883 // FileFd::OpenDescriptor - Open a filedescriptor /*{{{*/
884 // ---------------------------------------------------------------------
885 /* */
886 bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compress, bool AutoClose)
887 {
888 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
889 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
890 std::string name;
891 switch (Compress)
892 {
893 case None: name = "."; break;
894 case Gzip: name = "gzip"; break;
895 case Bzip2: name = "bzip2"; break;
896 case Lzma: name = "lzma"; break;
897 case Xz: name = "xz"; break;
898 case Auto:
899 case Extension:
900 return _error->Error("Opening Fd %d in Auto or Extension compression mode is not supported", Fd);
901 }
902 for (; compressor != compressors.end(); ++compressor)
903 if (compressor->Name == name)
904 break;
905 if (compressor == compressors.end())
906 return _error->Error("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
907
908 return OpenDescriptor(Fd, Mode, *compressor, AutoClose);
909 }
910 bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, APT::Configuration::Compressor const &compressor, bool AutoClose)
911 {
912 Close();
913 d = new FileFdPrivate;
914 d->openmode = Mode;
915 Flags = (AutoClose) ? FileFd::AutoClose : 0;
916 iFd = Fd;
917 if (OpenInternDescriptor(Mode, compressor) == false)
918 {
919 if (AutoClose)
920 close (iFd);
921 return _error->Errno("gzdopen",_("Could not open file descriptor %d"), Fd);
922 }
923 this->FileName = "";
924 return true;
925 }
926 bool FileFd::OpenInternDescriptor(unsigned int const Mode, APT::Configuration::Compressor const &compressor)
927 {
928 d->compressor = compressor;
929 if (compressor.Name == "." || compressor.Binary.empty() == true)
930 return true;
931 #if APT_USE_ZLIB
932 else if (compressor.Name == "gzip")
933 {
934 if ((Mode & ReadWrite) == ReadWrite)
935 d->gz = gzdopen(iFd, "r+");
936 else if ((Mode & WriteOnly) == WriteOnly)
937 d->gz = gzdopen(iFd, "w");
938 else
939 d->gz = gzdopen (iFd, "r");
940 if (d->gz == NULL)
941 return false;
942 Flags |= Compressed;
943 return true;
944 }
945 #endif
946
947 if ((Mode & ReadWrite) == ReadWrite)
948 return _error->Error("ReadWrite mode is not supported for file %s", FileName.c_str());
949
950 bool const Comp = (Mode & WriteOnly) == WriteOnly;
951 // Handle 'decompression' of empty files
952 if (Comp == false)
953 {
954 struct stat Buf;
955 fstat(iFd, &Buf);
956 if (Buf.st_size == 0 && S_ISFIFO(Buf.st_mode) == false)
957 return true;
958
959 // We don't need the file open - instead let the compressor open it
960 // as he properly knows better how to efficiently read from 'his' file
961 if (FileName.empty() == false)
962 close(iFd);
963 }
964
965 // Create a data pipe
966 int Pipe[2] = {-1,-1};
967 if (pipe(Pipe) != 0)
968 return _error->Errno("pipe",_("Failed to create subprocess IPC"));
969 for (int J = 0; J != 2; J++)
970 SetCloseExec(Pipe[J],true);
971
972 d->compressed_fd = iFd;
973 d->pipe = true;
974
975 if (Comp == true)
976 iFd = Pipe[1];
977 else
978 iFd = Pipe[0];
979
980 // The child..
981 d->compressor_pid = ExecFork();
982 if (d->compressor_pid == 0)
983 {
984 if (Comp == true)
985 {
986 dup2(d->compressed_fd,STDOUT_FILENO);
987 dup2(Pipe[0],STDIN_FILENO);
988 }
989 else
990 {
991 if (FileName.empty() == true)
992 dup2(d->compressed_fd,STDIN_FILENO);
993 dup2(Pipe[1],STDOUT_FILENO);
994 }
995
996 SetCloseExec(STDOUT_FILENO,false);
997 SetCloseExec(STDIN_FILENO,false);
998
999 std::vector<char const*> Args;
1000 Args.push_back(compressor.Binary.c_str());
1001 std::vector<std::string> const * const addArgs =
1002 (Comp == true) ? &(compressor.CompressArgs) : &(compressor.UncompressArgs);
1003 for (std::vector<std::string>::const_iterator a = addArgs->begin();
1004 a != addArgs->end(); ++a)
1005 Args.push_back(a->c_str());
1006 if (Comp == false && FileName.empty() == false)
1007 {
1008 Args.push_back("--stdout");
1009 if (TemporaryFileName.empty() == false)
1010 Args.push_back(TemporaryFileName.c_str());
1011 else
1012 Args.push_back(FileName.c_str());
1013 }
1014 Args.push_back(NULL);
1015
1016 execvp(Args[0],(char **)&Args[0]);
1017 cerr << _("Failed to exec compressor ") << Args[0] << endl;
1018 _exit(100);
1019 }
1020 if (Comp == true)
1021 close(Pipe[0]);
1022 else
1023 close(Pipe[1]);
1024 if (Comp == true || FileName.empty() == true)
1025 close(d->compressed_fd);
1026
1027 return true;
1028 }
1029 /*}}}*/
1030 // FileFd::~File - Closes the file /*{{{*/
1031 // ---------------------------------------------------------------------
1032 /* If the proper modes are selected then we close the Fd and possibly
1033 unlink the file on error. */
1034 FileFd::~FileFd()
1035 {
1036 Close();
1037 }
1038 /*}}}*/
1039 // FileFd::Read - Read a bit of the file /*{{{*/
1040 // ---------------------------------------------------------------------
1041 /* We are carefull to handle interruption by a signal while reading
1042 gracefully. */
1043 bool FileFd::Read(void *To,unsigned long long Size,unsigned long long *Actual)
1044 {
1045 int Res;
1046 errno = 0;
1047 if (Actual != 0)
1048 *Actual = 0;
1049 *((char *)To) = '\0';
1050 do
1051 {
1052 #if APT_USE_ZLIB
1053 if (d->gz != NULL)
1054 Res = gzread(d->gz,To,Size);
1055 else
1056 #endif
1057 Res = read(iFd,To,Size);
1058 if (Res < 0 && errno == EINTR)
1059 continue;
1060 if (Res < 0)
1061 {
1062 Flags |= Fail;
1063 return _error->Errno("read",_("Read error"));
1064 }
1065
1066 To = (char *)To + Res;
1067 Size -= Res;
1068 if (Actual != 0)
1069 *Actual += Res;
1070 }
1071 while (Res > 0 && Size > 0);
1072
1073 if (Size == 0)
1074 return true;
1075
1076 // Eof handling
1077 if (Actual != 0)
1078 {
1079 Flags |= HitEof;
1080 return true;
1081 }
1082
1083 Flags |= Fail;
1084 return _error->Error(_("read, still have %llu to read but none left"), Size);
1085 }
1086 /*}}}*/
1087 // FileFd::ReadLine - Read a complete line from the file /*{{{*/
1088 // ---------------------------------------------------------------------
1089 /* Beware: This method can be quiet slow for big buffers on UNcompressed
1090 files because of the naive implementation! */
1091 char* FileFd::ReadLine(char *To, unsigned long long const Size)
1092 {
1093 *To = '\0';
1094 #if APT_USE_ZLIB
1095 if (d->gz != NULL)
1096 return gzgets(d->gz, To, Size);
1097 #endif
1098
1099 unsigned long long read = 0;
1100 if (Read(To, Size, &read) == false)
1101 return NULL;
1102 char* c = To;
1103 for (; *c != '\n' && *c != '\0' && read != 0; --read, ++c)
1104 ; // find the end of the line
1105 if (*c != '\0')
1106 *c = '\0';
1107 if (read != 0)
1108 Seek(Tell() - read);
1109 return To;
1110 }
1111 /*}}}*/
1112 // FileFd::Write - Write to the file /*{{{*/
1113 // ---------------------------------------------------------------------
1114 /* */
1115 bool FileFd::Write(const void *From,unsigned long long Size)
1116 {
1117 int Res;
1118 errno = 0;
1119 do
1120 {
1121 #if APT_USE_ZLIB
1122 if (d->gz != NULL)
1123 Res = gzwrite(d->gz,From,Size);
1124 else
1125 #endif
1126 Res = write(iFd,From,Size);
1127 if (Res < 0 && errno == EINTR)
1128 continue;
1129 if (Res < 0)
1130 {
1131 Flags |= Fail;
1132 return _error->Errno("write",_("Write error"));
1133 }
1134
1135 From = (char *)From + Res;
1136 Size -= Res;
1137 }
1138 while (Res > 0 && Size > 0);
1139
1140 if (Size == 0)
1141 return true;
1142
1143 Flags |= Fail;
1144 return _error->Error(_("write, still have %llu to write but couldn't"), Size);
1145 }
1146 /*}}}*/
1147 // FileFd::Seek - Seek in the file /*{{{*/
1148 // ---------------------------------------------------------------------
1149 /* */
1150 bool FileFd::Seek(unsigned long long To)
1151 {
1152 if (d->pipe == true)
1153 {
1154 if ((d->openmode & ReadOnly) != ReadOnly)
1155 return _error->Error("Reopen is only implemented for read-only files!");
1156 close(iFd);
1157 iFd = 0;
1158 if (TemporaryFileName.empty() == false)
1159 iFd = open(TemporaryFileName.c_str(), O_RDONLY);
1160 else if (FileName.empty() == false)
1161 iFd = open(FileName.c_str(), O_RDONLY);
1162 else
1163 {
1164 if (d->compressed_fd > 0)
1165 if (lseek(d->compressed_fd, 0, SEEK_SET) != 0)
1166 iFd = d->compressed_fd;
1167 if (iFd <= 0)
1168 return _error->Error("Reopen is not implemented for pipes opened with FileFd::OpenDescriptor()!");
1169 }
1170
1171 if (OpenInternDescriptor(d->openmode, d->compressor) == false)
1172 return _error->Error("Seek on file %s because it couldn't be reopened", FileName.c_str());
1173
1174 if (To != 0)
1175 return Skip(To);
1176 return true;
1177 }
1178 int res;
1179 #if APT_USE_ZLIB
1180 if (d->gz)
1181 res = gzseek(d->gz,To,SEEK_SET);
1182 else
1183 #endif
1184 res = lseek(iFd,To,SEEK_SET);
1185 if (res != (signed)To)
1186 {
1187 Flags |= Fail;
1188 return _error->Error("Unable to seek to %llu", To);
1189 }
1190
1191 return true;
1192 }
1193 /*}}}*/
1194 // FileFd::Skip - Seek in the file /*{{{*/
1195 // ---------------------------------------------------------------------
1196 /* */
1197 bool FileFd::Skip(unsigned long long Over)
1198 {
1199 int res;
1200 #if APT_USE_ZLIB
1201 if (d->gz != NULL)
1202 res = gzseek(d->gz,Over,SEEK_CUR);
1203 else
1204 #endif
1205 res = lseek(iFd,Over,SEEK_CUR);
1206 if (res < 0)
1207 {
1208 Flags |= Fail;
1209 return _error->Error("Unable to seek ahead %llu",Over);
1210 }
1211
1212 return true;
1213 }
1214 /*}}}*/
1215 // FileFd::Truncate - Truncate the file /*{{{*/
1216 // ---------------------------------------------------------------------
1217 /* */
1218 bool FileFd::Truncate(unsigned long long To)
1219 {
1220 if (d->gz != NULL)
1221 {
1222 Flags |= Fail;
1223 return _error->Error("Truncating gzipped files is not implemented (%s)", FileName.c_str());
1224 }
1225 if (ftruncate(iFd,To) != 0)
1226 {
1227 Flags |= Fail;
1228 return _error->Error("Unable to truncate to %llu",To);
1229 }
1230
1231 return true;
1232 }
1233 /*}}}*/
1234 // FileFd::Tell - Current seek position /*{{{*/
1235 // ---------------------------------------------------------------------
1236 /* */
1237 unsigned long long FileFd::Tell()
1238 {
1239 off_t Res;
1240 #if APT_USE_ZLIB
1241 if (d->gz != NULL)
1242 Res = gztell(d->gz);
1243 else
1244 #endif
1245 Res = lseek(iFd,0,SEEK_CUR);
1246 if (Res == (off_t)-1)
1247 _error->Errno("lseek","Failed to determine the current file position");
1248 return Res;
1249 }
1250 /*}}}*/
1251 // FileFd::FileSize - Return the size of the file /*{{{*/
1252 // ---------------------------------------------------------------------
1253 /* */
1254 unsigned long long FileFd::FileSize()
1255 {
1256 struct stat Buf;
1257 if (d->pipe == false && fstat(iFd,&Buf) != 0)
1258 return _error->Errno("fstat","Unable to determine the file size");
1259
1260 // for compressor pipes st_size is undefined and at 'best' zero
1261 if (d->pipe == true || S_ISFIFO(Buf.st_mode))
1262 {
1263 // we set it here, too, as we get the info here for free
1264 // in theory the Open-methods should take care of it already
1265 d->pipe = true;
1266 if (stat(FileName.c_str(), &Buf) != 0)
1267 return _error->Errno("stat","Unable to determine the file size");
1268 }
1269
1270 return Buf.st_size;
1271 }
1272 /*}}}*/
1273 // FileFd::Size - Return the size of the content in the file /*{{{*/
1274 // ---------------------------------------------------------------------
1275 /* */
1276 unsigned long long FileFd::Size()
1277 {
1278 unsigned long long size = FileSize();
1279
1280 // for compressor pipes st_size is undefined and at 'best' zero,
1281 // so we 'read' the content and 'seek' back - see there
1282 if (d->pipe == true)
1283 {
1284 // FIXME: If we have read first and then FileSize() the report is wrong
1285 size = 0;
1286 char ignore[1000];
1287 unsigned long long read = 0;
1288 do {
1289 Read(ignore, sizeof(ignore), &read);
1290 size += read;
1291 } while(read != 0);
1292 Seek(0);
1293 }
1294 #if APT_USE_ZLIB
1295 // only check gzsize if we are actually a gzip file, just checking for
1296 // "gz" is not sufficient as uncompressed files could be opened with
1297 // gzopen in "direct" mode as well
1298 else if (d->gz && !gzdirect(d->gz) && size > 0)
1299 {
1300 /* unfortunately zlib.h doesn't provide a gzsize(), so we have to do
1301 * this ourselves; the original (uncompressed) file size is the last 32
1302 * bits of the file */
1303 // FIXME: Size for gz-files is limited by 32bit… no largefile support
1304 off_t orig_pos = lseek(iFd, 0, SEEK_CUR);
1305 if (lseek(iFd, -4, SEEK_END) < 0)
1306 return _error->Errno("lseek","Unable to seek to end of gzipped file");
1307 size = 0L;
1308 if (read(iFd, &size, 4) != 4)
1309 return _error->Errno("read","Unable to read original size of gzipped file");
1310
1311 #ifdef WORDS_BIGENDIAN
1312 uint32_t tmp_size = size;
1313 uint8_t const * const p = (uint8_t const * const) &tmp_size;
1314 tmp_size = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
1315 size = tmp_size;
1316 #endif
1317
1318 if (lseek(iFd, orig_pos, SEEK_SET) < 0)
1319 return _error->Errno("lseek","Unable to seek in gzipped file");
1320 return size;
1321 }
1322 #endif
1323
1324 return size;
1325 }
1326 /*}}}*/
1327 // FileFd::ModificationTime - Return the time of last touch /*{{{*/
1328 // ---------------------------------------------------------------------
1329 /* */
1330 time_t FileFd::ModificationTime()
1331 {
1332 struct stat Buf;
1333 if (d->pipe == false && fstat(iFd,&Buf) != 0)
1334 {
1335 _error->Errno("fstat","Unable to determine the modification time of file %s", FileName.c_str());
1336 return 0;
1337 }
1338
1339 // for compressor pipes st_size is undefined and at 'best' zero
1340 if (d->pipe == true || S_ISFIFO(Buf.st_mode))
1341 {
1342 // we set it here, too, as we get the info here for free
1343 // in theory the Open-methods should take care of it already
1344 d->pipe = true;
1345 if (stat(FileName.c_str(), &Buf) != 0)
1346 {
1347 _error->Errno("fstat","Unable to determine the modification time of file %s", FileName.c_str());
1348 return 0;
1349 }
1350 }
1351
1352 return Buf.st_mtime;
1353 }
1354 /*}}}*/
1355 // FileFd::Close - Close the file if the close flag is set /*{{{*/
1356 // ---------------------------------------------------------------------
1357 /* */
1358 bool FileFd::Close()
1359 {
1360 if (iFd == -1)
1361 return true;
1362
1363 bool Res = true;
1364 if ((Flags & AutoClose) == AutoClose)
1365 {
1366 #if APT_USE_ZLIB
1367 if (d != NULL && d->gz != NULL) {
1368 int const e = gzclose(d->gz);
1369 // gzdopen() on empty files always fails with "buffer error" here, ignore that
1370 if (e != 0 && e != Z_BUF_ERROR)
1371 Res &= _error->Errno("close",_("Problem closing the gzip file %s"), FileName.c_str());
1372 } else
1373 #endif
1374 if (iFd > 0 && close(iFd) != 0)
1375 Res &= _error->Errno("close",_("Problem closing the file %s"), FileName.c_str());
1376 }
1377
1378 if ((Flags & Replace) == Replace && iFd >= 0) {
1379 if (rename(TemporaryFileName.c_str(), FileName.c_str()) != 0)
1380 Res &= _error->Errno("rename",_("Problem renaming the file %s to %s"), TemporaryFileName.c_str(), FileName.c_str());
1381
1382 FileName = TemporaryFileName; // for the unlink() below.
1383 TemporaryFileName.clear();
1384 }
1385
1386 iFd = -1;
1387
1388 if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail &&
1389 FileName.empty() == false)
1390 if (unlink(FileName.c_str()) != 0)
1391 Res &= _error->WarningE("unlnk",_("Problem unlinking the file %s"), FileName.c_str());
1392
1393 if (d != NULL)
1394 {
1395 if (d->compressor_pid > 0)
1396 ExecWait(d->compressor_pid, "FileFdCompressor", true);
1397 delete d;
1398 d = NULL;
1399 }
1400
1401 return Res;
1402 }
1403 /*}}}*/
1404 // FileFd::Sync - Sync the file /*{{{*/
1405 // ---------------------------------------------------------------------
1406 /* */
1407 bool FileFd::Sync()
1408 {
1409 #ifdef _POSIX_SYNCHRONIZED_IO
1410 if (fsync(iFd) != 0)
1411 return _error->Errno("sync",_("Problem syncing the file"));
1412 #endif
1413 return true;
1414 }
1415 /*}}}*/
1416
1417 gzFile FileFd::gzFd() { return (gzFile) d->gz; }