]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/fileutl.cc
fix insecure use of /tmp in EDSP solver 'dump'
[apt.git] / apt-pkg / contrib / fileutl.cc
CommitLineData
578bfd0a
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
578bfd0a
AL
3/* ######################################################################
4
5 File Utilities
6
7 CopyFile - Buffered copy of a single file
8 GetLock - dpkg compatible lock file manipulation (fcntl)
9
614adaa0
MV
10 Most of this source is placed in the Public Domain, do with it what
11 you will
7da2b375 12 It was originally written by Jason Gunthorpe <jgg@debian.org>.
a3a03f5d 13 FileFd gzip support added by Martin Pitt <martin.pitt@canonical.com>
578bfd0a 14
614adaa0
MV
15 The exception is RunScripts() it is under the GPLv2
16
578bfd0a
AL
17 ##################################################################### */
18 /*}}}*/
19// Include Files /*{{{*/
ea542140
DK
20#include <config.h>
21
094a497d 22#include <apt-pkg/fileutl.h>
1cd1c398 23#include <apt-pkg/strutl.h>
094a497d 24#include <apt-pkg/error.h>
b2e465d6 25#include <apt-pkg/sptr.h>
468720c5 26#include <apt-pkg/aptconfiguration.h>
75ef8f14 27#include <apt-pkg/configuration.h>
453b82a3 28#include <apt-pkg/macros.h>
b2e465d6 29
453b82a3
DK
30#include <ctype.h>
31#include <stdarg.h>
32#include <stddef.h>
33#include <sys/select.h>
34#include <time.h>
35#include <string>
36#include <vector>
152ab79e 37#include <cstdlib>
4f333a8b 38#include <cstring>
3010fb0e 39#include <cstdio>
4d055c05 40#include <iostream>
578bfd0a 41#include <unistd.h>
2c206aa4 42#include <fcntl.h>
578bfd0a 43#include <sys/stat.h>
cc2313b7 44#include <sys/time.h>
1ae93c94 45#include <sys/wait.h>
46e39c8e 46#include <dirent.h>
54676e1a 47#include <signal.h>
65a1e968 48#include <errno.h>
8d01b9d6 49#include <glob.h>
fc1a78d8 50#include <pwd.h>
3927c6da 51#include <grp.h>
8d01b9d6 52
75ef8f14 53#include <set>
46e39c8e 54#include <algorithm>
98cc7fd2 55#include <memory>
2cae0ccb 56
7efb8c8e
DK
57#ifdef HAVE_ZLIB
58 #include <zlib.h>
699b209e 59#endif
c4997486
DK
60#ifdef HAVE_BZ2
61 #include <bzlib.h>
62#endif
7f350a37
DK
63#ifdef HAVE_LZMA
64 #include <lzma.h>
2cae0ccb 65#endif
05eab8af
AC
66#include <endian.h>
67#include <stdint.h>
ea542140 68
3927c6da
MV
69#if __gnu_linux__
70#include <sys/prctl.h>
71#endif
72
ea542140 73#include <apti18n.h>
578bfd0a
AL
74 /*}}}*/
75
4d055c05
AL
76using namespace std;
77
614adaa0
MV
78// RunScripts - Run a set of scripts from a configuration subtree /*{{{*/
79// ---------------------------------------------------------------------
80/* */
81bool RunScripts(const char *Cnf)
82{
83 Configuration::Item const *Opts = _config->Tree(Cnf);
84 if (Opts == 0 || Opts->Child == 0)
85 return true;
86 Opts = Opts->Child;
87
88 // Fork for running the system calls
89 pid_t Child = ExecFork();
90
91 // This is the child
92 if (Child == 0)
93 {
cfba4f69
MV
94 if (_config->FindDir("DPkg::Chroot-Directory","/") != "/")
95 {
96 std::cerr << "Chrooting into "
97 << _config->FindDir("DPkg::Chroot-Directory")
98 << std::endl;
99 if (chroot(_config->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0)
100 _exit(100);
101 }
102
614adaa0
MV
103 if (chdir("/tmp/") != 0)
104 _exit(100);
105
106 unsigned int Count = 1;
107 for (; Opts != 0; Opts = Opts->Next, Count++)
108 {
109 if (Opts->Value.empty() == true)
110 continue;
e5b7e019
MV
111
112 if(_config->FindB("Debug::RunScripts", false) == true)
113 std::clog << "Running external script: '"
114 << Opts->Value << "'" << std::endl;
115
614adaa0
MV
116 if (system(Opts->Value.c_str()) != 0)
117 _exit(100+Count);
118 }
119 _exit(0);
120 }
121
122 // Wait for the child
123 int Status = 0;
124 while (waitpid(Child,&Status,0) != Child)
125 {
126 if (errno == EINTR)
127 continue;
128 return _error->Errno("waitpid","Couldn't wait for subprocess");
129 }
130
131 // Restore sig int/quit
132 signal(SIGQUIT,SIG_DFL);
133 signal(SIGINT,SIG_DFL);
134
135 // Check for an error code.
136 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
137 {
138 unsigned int Count = WEXITSTATUS(Status);
139 if (Count > 100)
140 {
141 Count -= 100;
142 for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--);
143 _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str());
144 }
145
146 return _error->Error("Sub-process returned an error code");
147 }
148
149 return true;
150}
151 /*}}}*/
152
578bfd0a
AL
153// CopyFile - Buffered copy of a file /*{{{*/
154// ---------------------------------------------------------------------
155/* The caller is expected to set things so that failure causes erasure */
8b89e57f 156bool CopyFile(FileFd &From,FileFd &To)
578bfd0a 157{
2128d3fc
DK
158 if (From.IsOpen() == false || To.IsOpen() == false ||
159 From.Failed() == true || To.Failed() == true)
578bfd0a 160 return false;
e977b8b9 161
578bfd0a 162 // Buffered copy between fds
98cc7fd2 163 std::unique_ptr<unsigned char[]> Buf(new unsigned char[64000]);
e977b8b9
DK
164 constexpr unsigned long long BufSize = sizeof(Buf.get())/sizeof(Buf.get()[0]);
165 unsigned long long ToRead = 0;
166 do {
167 if (From.Read(Buf.get(),BufSize, &ToRead) == false ||
98cc7fd2 168 To.Write(Buf.get(),ToRead) == false)
578bfd0a 169 return false;
e977b8b9 170 } while (ToRead != 0);
578bfd0a 171
e977b8b9 172 return true;
578bfd0a
AL
173}
174 /*}}}*/
175// GetLock - Gets a lock file /*{{{*/
176// ---------------------------------------------------------------------
177/* This will create an empty file of the given name and lock it. Once this
178 is done all other calls to GetLock in any other process will fail with
179 -1. The return result is the fd of the file, the call should call
180 close at some time. */
181int GetLock(string File,bool Errors)
182{
f659b39a
OS
183 // GetLock() is used in aptitude on directories with public-write access
184 // Use O_NOFOLLOW here to prevent symlink traversal attacks
185 int FD = open(File.c_str(),O_RDWR | O_CREAT | O_NOFOLLOW,0640);
578bfd0a
AL
186 if (FD < 0)
187 {
1e3f4083 188 // Read only .. can't have locking problems there.
b2e465d6
AL
189 if (errno == EROFS)
190 {
191 _error->Warning(_("Not using locking for read only lock file %s"),File.c_str());
192 return dup(0); // Need something for the caller to close
193 }
194
578bfd0a 195 if (Errors == true)
b2e465d6
AL
196 _error->Errno("open",_("Could not open lock file %s"),File.c_str());
197
198 // Feh.. We do this to distinguish the lock vs open case..
199 errno = EPERM;
578bfd0a
AL
200 return -1;
201 }
b2e465d6
AL
202 SetCloseExec(FD,true);
203
1e3f4083 204 // Acquire a write lock
578bfd0a 205 struct flock fl;
c71bc556
AL
206 fl.l_type = F_WRLCK;
207 fl.l_whence = SEEK_SET;
208 fl.l_start = 0;
209 fl.l_len = 0;
578bfd0a
AL
210 if (fcntl(FD,F_SETLK,&fl) == -1)
211 {
3d165906
MV
212 // always close to not leak resources
213 int Tmp = errno;
214 close(FD);
215 errno = Tmp;
216
d89df07a
AL
217 if (errno == ENOLCK)
218 {
b2e465d6
AL
219 _error->Warning(_("Not using locking for nfs mounted lock file %s"),File.c_str());
220 return dup(0); // Need something for the caller to close
3d165906
MV
221 }
222
578bfd0a 223 if (Errors == true)
b2e465d6
AL
224 _error->Errno("open",_("Could not get lock %s"),File.c_str());
225
578bfd0a
AL
226 return -1;
227 }
228
229 return FD;
230}
231 /*}}}*/
232// FileExists - Check if a file exists /*{{{*/
233// ---------------------------------------------------------------------
36f1098a 234/* Beware: Directories are also files! */
578bfd0a
AL
235bool FileExists(string File)
236{
237 struct stat Buf;
238 if (stat(File.c_str(),&Buf) != 0)
239 return false;
240 return true;
241}
242 /*}}}*/
36f1098a
DK
243// RealFileExists - Check if a file exists and if it is really a file /*{{{*/
244// ---------------------------------------------------------------------
245/* */
246bool RealFileExists(string File)
247{
248 struct stat Buf;
249 if (stat(File.c_str(),&Buf) != 0)
250 return false;
251 return ((Buf.st_mode & S_IFREG) != 0);
252}
253 /*}}}*/
1cd1c398
DK
254// DirectoryExists - Check if a directory exists and is really one /*{{{*/
255// ---------------------------------------------------------------------
256/* */
257bool DirectoryExists(string const &Path)
258{
259 struct stat Buf;
260 if (stat(Path.c_str(),&Buf) != 0)
261 return false;
262 return ((Buf.st_mode & S_IFDIR) != 0);
263}
264 /*}}}*/
265// CreateDirectory - poor man's mkdir -p guarded by a parent directory /*{{{*/
266// ---------------------------------------------------------------------
267/* This method will create all directories needed for path in good old
268 mkdir -p style but refuses to do this if Parent is not a prefix of
269 this Path. Example: /var/cache/ and /var/cache/apt/archives are given,
270 so it will create apt/archives if /var/cache exists - on the other
271 hand if the parent is /var/lib the creation will fail as this path
272 is not a parent of the path to be generated. */
273bool CreateDirectory(string const &Parent, string const &Path)
274{
275 if (Parent.empty() == true || Path.empty() == true)
276 return false;
277
278 if (DirectoryExists(Path) == true)
279 return true;
280
281 if (DirectoryExists(Parent) == false)
282 return false;
283
284 // we are not going to create directories "into the blue"
9ce3cfc9 285 if (Path.compare(0, Parent.length(), Parent) != 0)
1cd1c398
DK
286 return false;
287
288 vector<string> const dirs = VectorizeString(Path.substr(Parent.size()), '/');
289 string progress = Parent;
290 for (vector<string>::const_iterator d = dirs.begin(); d != dirs.end(); ++d)
291 {
292 if (d->empty() == true)
293 continue;
294
295 progress.append("/").append(*d);
296 if (DirectoryExists(progress) == true)
297 continue;
298
299 if (mkdir(progress.c_str(), 0755) != 0)
300 return false;
301 }
302 return true;
303}
304 /*}}}*/
7753e468 305// CreateAPTDirectoryIfNeeded - ensure that the given directory exists /*{{{*/
b29c3712
DK
306// ---------------------------------------------------------------------
307/* a small wrapper around CreateDirectory to check if it exists and to
308 remove the trailing "/apt/" from the parent directory if needed */
7753e468 309bool CreateAPTDirectoryIfNeeded(string const &Parent, string const &Path)
b29c3712
DK
310{
311 if (DirectoryExists(Path) == true)
312 return true;
313
314 size_t const len = Parent.size();
315 if (len > 5 && Parent.find("/apt/", len - 6, 5) == len - 5)
316 {
317 if (CreateDirectory(Parent.substr(0,len-5), Path) == true)
318 return true;
319 }
320 else if (CreateDirectory(Parent, Path) == true)
321 return true;
322
323 return false;
324}
325 /*}}}*/
46e39c8e
MV
326// GetListOfFilesInDir - returns a vector of files in the given dir /*{{{*/
327// ---------------------------------------------------------------------
328/* If an extension is given only files with this extension are included
329 in the returned vector, otherwise every "normal" file is included. */
b39c1859
MV
330std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext,
331 bool const &SortList, bool const &AllowNoExt)
332{
333 std::vector<string> ext;
334 ext.reserve(2);
335 if (Ext.empty() == false)
336 ext.push_back(Ext);
337 if (AllowNoExt == true && ext.empty() == false)
338 ext.push_back("");
339 return GetListOfFilesInDir(Dir, ext, SortList);
340}
341std::vector<string> GetListOfFilesInDir(string const &Dir, std::vector<string> const &Ext,
342 bool const &SortList)
343{
344 // Attention debuggers: need to be set with the environment config file!
345 bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
346 if (Debug == true)
347 {
348 std::clog << "Accept in " << Dir << " only files with the following " << Ext.size() << " extensions:" << std::endl;
349 if (Ext.empty() == true)
350 std::clog << "\tNO extension" << std::endl;
351 else
352 for (std::vector<string>::const_iterator e = Ext.begin();
353 e != Ext.end(); ++e)
354 std::clog << '\t' << (e->empty() == true ? "NO" : *e) << " extension" << std::endl;
355 }
356
46e39c8e 357 std::vector<string> List;
36f1098a 358
69c2ecbd 359 if (DirectoryExists(Dir) == false)
36f1098a
DK
360 {
361 _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str());
362 return List;
363 }
364
1408e219 365 Configuration::MatchAgainstConfig SilentIgnore("Dir::Ignore-Files-Silently");
46e39c8e
MV
366 DIR *D = opendir(Dir.c_str());
367 if (D == 0)
368 {
369 _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
370 return List;
371 }
372
373 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
374 {
b39c1859 375 // skip "hidden" files
46e39c8e
MV
376 if (Ent->d_name[0] == '.')
377 continue;
378
491058e3
DK
379 // Make sure it is a file and not something else
380 string const File = flCombine(Dir,Ent->d_name);
381#ifdef _DIRENT_HAVE_D_TYPE
382 if (Ent->d_type != DT_REG)
383#endif
384 {
69c2ecbd 385 if (RealFileExists(File) == false)
491058e3 386 {
84e254d6
DK
387 // do not show ignoration warnings for directories
388 if (
389#ifdef _DIRENT_HAVE_D_TYPE
390 Ent->d_type == DT_DIR ||
391#endif
69c2ecbd 392 DirectoryExists(File) == true)
84e254d6 393 continue;
491058e3
DK
394 if (SilentIgnore.Match(Ent->d_name) == false)
395 _error->Notice(_("Ignoring '%s' in directory '%s' as it is not a regular file"), Ent->d_name, Dir.c_str());
396 continue;
397 }
398 }
399
b39c1859
MV
400 // check for accepted extension:
401 // no extension given -> periods are bad as hell!
402 // extensions given -> "" extension allows no extension
403 if (Ext.empty() == false)
404 {
405 string d_ext = flExtension(Ent->d_name);
406 if (d_ext == Ent->d_name) // no extension
407 {
408 if (std::find(Ext.begin(), Ext.end(), "") == Ext.end())
409 {
410 if (Debug == true)
411 std::clog << "Bad file: " << Ent->d_name << " → no extension" << std::endl;
5edc3966 412 if (SilentIgnore.Match(Ent->d_name) == false)
491058e3 413 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has no filename extension"), Ent->d_name, Dir.c_str());
b39c1859
MV
414 continue;
415 }
416 }
417 else if (std::find(Ext.begin(), Ext.end(), d_ext) == Ext.end())
418 {
419 if (Debug == true)
420 std::clog << "Bad file: " << Ent->d_name << " → bad extension »" << flExtension(Ent->d_name) << "«" << std::endl;
1408e219 421 if (SilentIgnore.Match(Ent->d_name) == false)
491058e3 422 _error->Notice(_("Ignoring file '%s' in directory '%s' as it has an invalid filename extension"), Ent->d_name, Dir.c_str());
b39c1859
MV
423 continue;
424 }
425 }
46e39c8e 426
b39c1859 427 // Skip bad filenames ala run-parts
46e39c8e
MV
428 const char *C = Ent->d_name;
429 for (; *C != 0; ++C)
430 if (isalpha(*C) == 0 && isdigit(*C) == 0
9d39208a 431 && *C != '_' && *C != '-' && *C != ':') {
b39c1859
MV
432 // no required extension -> dot is a bad character
433 if (*C == '.' && Ext.empty() == false)
434 continue;
46e39c8e 435 break;
b39c1859 436 }
46e39c8e 437
b39c1859 438 // we don't reach the end of the name -> bad character included
46e39c8e 439 if (*C != 0)
b39c1859
MV
440 {
441 if (Debug == true)
442 std::clog << "Bad file: " << Ent->d_name << " → bad character »"
443 << *C << "« in filename (period allowed: " << (Ext.empty() ? "no" : "yes") << ")" << std::endl;
444 continue;
445 }
446
fbb2c7e0
DK
447 // skip filenames which end with a period. These are never valid
448 if (*(C - 1) == '.')
449 {
450 if (Debug == true)
451 std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
452 continue;
453 }
454
455 if (Debug == true)
456 std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
457 List.push_back(File);
458 }
459 closedir(D);
460
461 if (SortList == true)
462 std::sort(List.begin(),List.end());
463 return List;
464}
465std::vector<string> GetListOfFilesInDir(string const &Dir, bool SortList)
466{
467 bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
468 if (Debug == true)
469 std::clog << "Accept in " << Dir << " all regular files" << std::endl;
470
471 std::vector<string> List;
472
69c2ecbd 473 if (DirectoryExists(Dir) == false)
fbb2c7e0
DK
474 {
475 _error->Error(_("List of files can't be created as '%s' is not a directory"), Dir.c_str());
476 return List;
477 }
478
479 DIR *D = opendir(Dir.c_str());
480 if (D == 0)
481 {
482 _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
483 return List;
484 }
485
486 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
487 {
488 // skip "hidden" files
489 if (Ent->d_name[0] == '.')
490 continue;
491
492 // Make sure it is a file and not something else
493 string const File = flCombine(Dir,Ent->d_name);
494#ifdef _DIRENT_HAVE_D_TYPE
495 if (Ent->d_type != DT_REG)
496#endif
497 {
69c2ecbd 498 if (RealFileExists(File) == false)
fbb2c7e0
DK
499 {
500 if (Debug == true)
501 std::clog << "Bad file: " << Ent->d_name << " → it is not a real file" << std::endl;
502 continue;
503 }
504 }
505
506 // Skip bad filenames ala run-parts
507 const char *C = Ent->d_name;
508 for (; *C != 0; ++C)
509 if (isalpha(*C) == 0 && isdigit(*C) == 0
510 && *C != '_' && *C != '-' && *C != '.')
511 break;
512
513 // we don't reach the end of the name -> bad character included
514 if (*C != 0)
515 {
516 if (Debug == true)
517 std::clog << "Bad file: " << Ent->d_name << " → bad character »" << *C << "« in filename" << std::endl;
518 continue;
519 }
520
b39c1859
MV
521 // skip filenames which end with a period. These are never valid
522 if (*(C - 1) == '.')
523 {
524 if (Debug == true)
525 std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
46e39c8e 526 continue;
b39c1859 527 }
46e39c8e 528
b39c1859
MV
529 if (Debug == true)
530 std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
46e39c8e
MV
531 List.push_back(File);
532 }
533 closedir(D);
534
535 if (SortList == true)
536 std::sort(List.begin(),List.end());
537 return List;
538}
539 /*}}}*/
578bfd0a
AL
540// SafeGetCWD - This is a safer getcwd that returns a dynamic string /*{{{*/
541// ---------------------------------------------------------------------
542/* We return / on failure. */
543string SafeGetCWD()
544{
545 // Stash the current dir.
546 char S[300];
547 S[0] = 0;
7f25bdff 548 if (getcwd(S,sizeof(S)-2) == 0)
578bfd0a 549 return "/";
7f25bdff
AL
550 unsigned int Len = strlen(S);
551 S[Len] = '/';
552 S[Len+1] = 0;
578bfd0a
AL
553 return S;
554}
555 /*}}}*/
2ec858bc
MV
556// GetModificationTime - Get the mtime of the given file or -1 on error /*{{{*/
557// ---------------------------------------------------------------------
558/* We return / on failure. */
559time_t GetModificationTime(string const &Path)
560{
561 struct stat St;
562 if (stat(Path.c_str(), &St) < 0)
563 return -1;
564 return St.st_mtime;
565}
566 /*}}}*/
8ce4327b
AL
567// flNotDir - Strip the directory from the filename /*{{{*/
568// ---------------------------------------------------------------------
569/* */
570string flNotDir(string File)
571{
572 string::size_type Res = File.rfind('/');
573 if (Res == string::npos)
574 return File;
575 Res++;
576 return string(File,Res,Res - File.length());
577}
578 /*}}}*/
d38b7b3d
AL
579// flNotFile - Strip the file from the directory name /*{{{*/
580// ---------------------------------------------------------------------
171c45bc 581/* Result ends in a / */
d38b7b3d
AL
582string flNotFile(string File)
583{
584 string::size_type Res = File.rfind('/');
585 if (Res == string::npos)
171c45bc 586 return "./";
d38b7b3d
AL
587 Res++;
588 return string(File,0,Res);
589}
590 /*}}}*/
b2e465d6
AL
591// flExtension - Return the extension for the file /*{{{*/
592// ---------------------------------------------------------------------
593/* */
594string flExtension(string File)
595{
596 string::size_type Res = File.rfind('.');
597 if (Res == string::npos)
598 return File;
599 Res++;
600 return string(File,Res,Res - File.length());
601}
602 /*}}}*/
421c8d10
AL
603// flNoLink - If file is a symlink then deref it /*{{{*/
604// ---------------------------------------------------------------------
605/* If the name is not a link then the returned path is the input. */
606string flNoLink(string File)
607{
608 struct stat St;
609 if (lstat(File.c_str(),&St) != 0 || S_ISLNK(St.st_mode) == 0)
610 return File;
611 if (stat(File.c_str(),&St) != 0)
612 return File;
613
614 /* Loop resolving the link. There is no need to limit the number of
615 loops because the stat call above ensures that the symlink is not
616 circular */
617 char Buffer[1024];
618 string NFile = File;
619 while (1)
620 {
621 // Read the link
3286ad13 622 ssize_t Res;
421c8d10 623 if ((Res = readlink(NFile.c_str(),Buffer,sizeof(Buffer))) <= 0 ||
3286ad13 624 (size_t)Res >= sizeof(Buffer))
421c8d10
AL
625 return File;
626
627 // Append or replace the previous path
628 Buffer[Res] = 0;
629 if (Buffer[0] == '/')
630 NFile = Buffer;
631 else
632 NFile = flNotFile(NFile) + Buffer;
633
634 // See if we are done
635 if (lstat(NFile.c_str(),&St) != 0)
636 return File;
637 if (S_ISLNK(St.st_mode) == 0)
638 return NFile;
639 }
640}
641 /*}}}*/
b2e465d6
AL
642// flCombine - Combine a file and a directory /*{{{*/
643// ---------------------------------------------------------------------
644/* If the file is an absolute path then it is just returned, otherwise
645 the directory is pre-pended to it. */
646string flCombine(string Dir,string File)
647{
648 if (File.empty() == true)
649 return string();
650
651 if (File[0] == '/' || Dir.empty() == true)
652 return File;
653 if (File.length() >= 2 && File[0] == '.' && File[1] == '/')
654 return File;
655 if (Dir[Dir.length()-1] == '/')
656 return Dir + File;
657 return Dir + '/' + File;
658}
659 /*}}}*/
53ac87ac
MV
660// flAbsPath - Return the absolute path of the filename /*{{{*/
661// ---------------------------------------------------------------------
662/* */
663string flAbsPath(string File)
664{
665 char *p = realpath(File.c_str(), NULL);
666 if (p == NULL)
667 {
668 _error->Errno("realpath", "flAbsPath failed");
669 return "";
670 }
671 std::string AbsPath(p);
672 free(p);
673 return AbsPath;
674}
675 /*}}}*/
3b5421b4
AL
676// SetCloseExec - Set the close on exec flag /*{{{*/
677// ---------------------------------------------------------------------
678/* */
679void SetCloseExec(int Fd,bool Close)
680{
681 if (fcntl(Fd,F_SETFD,(Close == false)?0:FD_CLOEXEC) != 0)
682 {
683 cerr << "FATAL -> Could not set close on exec " << strerror(errno) << endl;
684 exit(100);
685 }
686}
687 /*}}}*/
688// SetNonBlock - Set the nonblocking flag /*{{{*/
689// ---------------------------------------------------------------------
690/* */
691void SetNonBlock(int Fd,bool Block)
692{
0a8a80e5
AL
693 int Flags = fcntl(Fd,F_GETFL) & (~O_NONBLOCK);
694 if (fcntl(Fd,F_SETFL,Flags | ((Block == false)?0:O_NONBLOCK)) != 0)
3b5421b4
AL
695 {
696 cerr << "FATAL -> Could not set non-blocking flag " << strerror(errno) << endl;
697 exit(100);
698 }
699}
700 /*}}}*/
701// WaitFd - Wait for a FD to become readable /*{{{*/
702// ---------------------------------------------------------------------
b2e465d6 703/* This waits for a FD to become readable using select. It is useful for
6d5dd02a
AL
704 applications making use of non-blocking sockets. The timeout is
705 in seconds. */
1084d58a 706bool WaitFd(int Fd,bool write,unsigned long timeout)
3b5421b4
AL
707{
708 fd_set Set;
cc2313b7 709 struct timeval tv;
3b5421b4
AL
710 FD_ZERO(&Set);
711 FD_SET(Fd,&Set);
6d5dd02a
AL
712 tv.tv_sec = timeout;
713 tv.tv_usec = 0;
1084d58a 714 if (write == true)
b0db36b1
AL
715 {
716 int Res;
717 do
718 {
719 Res = select(Fd+1,0,&Set,0,(timeout != 0?&tv:0));
720 }
721 while (Res < 0 && errno == EINTR);
722
723 if (Res <= 0)
724 return false;
1084d58a
AL
725 }
726 else
727 {
b0db36b1
AL
728 int Res;
729 do
730 {
731 Res = select(Fd+1,&Set,0,0,(timeout != 0?&tv:0));
732 }
733 while (Res < 0 && errno == EINTR);
734
735 if (Res <= 0)
736 return false;
cc2313b7 737 }
1084d58a 738
3b5421b4
AL
739 return true;
740}
741 /*}}}*/
96ae6de5 742// MergeKeepFdsFromConfiguration - Merge APT::Keep-Fds configuration /*{{{*/
54676e1a 743// ---------------------------------------------------------------------
96ae6de5
MV
744/* This is used to merge the APT::Keep-Fds with the provided KeepFDs
745 * set.
746 */
747void MergeKeepFdsFromConfiguration(std::set<int> &KeepFDs)
e45c4617 748{
e45c4617
MV
749 Configuration::Item const *Opts = _config->Tree("APT::Keep-Fds");
750 if (Opts != 0 && Opts->Child != 0)
751 {
752 Opts = Opts->Child;
753 for (; Opts != 0; Opts = Opts->Next)
754 {
755 if (Opts->Value.empty() == true)
756 continue;
757 int fd = atoi(Opts->Value.c_str());
758 KeepFDs.insert(fd);
759 }
760 }
96ae6de5
MV
761}
762 /*}}}*/
54676e1a
AL
763// ExecFork - Magical fork that sanitizes the context before execing /*{{{*/
764// ---------------------------------------------------------------------
765/* This is used if you want to cleanse the environment for the forked
766 child, it fixes up the important signals and nukes all of the fds,
767 otherwise acts like normal fork. */
75ef8f14 768pid_t ExecFork()
96ae6de5
MV
769{
770 set<int> KeepFDs;
771 // we need to merge the Keep-Fds as external tools like
772 // debconf-apt-progress use it
773 MergeKeepFdsFromConfiguration(KeepFDs);
e45c4617
MV
774 return ExecFork(KeepFDs);
775}
776
777pid_t ExecFork(std::set<int> KeepFDs)
54676e1a
AL
778{
779 // Fork off the process
780 pid_t Process = fork();
781 if (Process < 0)
782 {
783 cerr << "FATAL -> Failed to fork." << endl;
784 exit(100);
785 }
786
787 // Spawn the subprocess
788 if (Process == 0)
789 {
790 // Setup the signals
791 signal(SIGPIPE,SIG_DFL);
792 signal(SIGQUIT,SIG_DFL);
793 signal(SIGINT,SIG_DFL);
794 signal(SIGWINCH,SIG_DFL);
795 signal(SIGCONT,SIG_DFL);
796 signal(SIGTSTP,SIG_DFL);
75ef8f14 797
be4d908f
JAK
798 DIR *dir = opendir("/proc/self/fd");
799 if (dir != NULL)
75ef8f14 800 {
be4d908f
JAK
801 struct dirent *ent;
802 while ((ent = readdir(dir)))
803 {
804 int fd = atoi(ent->d_name);
805 // If fd > 0, it was a fd number and not . or ..
806 if (fd >= 3 && KeepFDs.find(fd) == KeepFDs.end())
807 fcntl(fd,F_SETFD,FD_CLOEXEC);
808 }
809 closedir(dir);
810 } else {
811 long ScOpenMax = sysconf(_SC_OPEN_MAX);
812 // Close all of our FDs - just in case
813 for (int K = 3; K != ScOpenMax; K++)
814 {
815 if(KeepFDs.find(K) == KeepFDs.end())
816 fcntl(K,F_SETFD,FD_CLOEXEC);
817 }
75ef8f14 818 }
54676e1a
AL
819 }
820
821 return Process;
822}
823 /*}}}*/
ddc1d8d0
AL
824// ExecWait - Fancy waitpid /*{{{*/
825// ---------------------------------------------------------------------
2c9a72d1 826/* Waits for the given sub process. If Reap is set then no errors are
ddc1d8d0
AL
827 generated. Otherwise a failed subprocess will generate a proper descriptive
828 message */
3826564e 829bool ExecWait(pid_t Pid,const char *Name,bool Reap)
ddc1d8d0
AL
830{
831 if (Pid <= 1)
832 return true;
833
834 // Wait and collect the error code
835 int Status;
836 while (waitpid(Pid,&Status,0) != Pid)
837 {
838 if (errno == EINTR)
839 continue;
840
841 if (Reap == true)
842 return false;
843
db0db9fe 844 return _error->Error(_("Waited for %s but it wasn't there"),Name);
ddc1d8d0
AL
845 }
846
847
848 // Check for an error code.
849 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
850 {
851 if (Reap == true)
852 return false;
ab7f4d7c 853 if (WIFSIGNALED(Status) != 0)
40e7fe0e 854 {
ab7f4d7c
MV
855 if( WTERMSIG(Status) == SIGSEGV)
856 return _error->Error(_("Sub-process %s received a segmentation fault."),Name);
857 else
858 return _error->Error(_("Sub-process %s received signal %u."),Name, WTERMSIG(Status));
40e7fe0e 859 }
ddc1d8d0
AL
860
861 if (WIFEXITED(Status) != 0)
b2e465d6 862 return _error->Error(_("Sub-process %s returned an error code (%u)"),Name,WEXITSTATUS(Status));
ddc1d8d0 863
b2e465d6 864 return _error->Error(_("Sub-process %s exited unexpectedly"),Name);
ddc1d8d0
AL
865 }
866
867 return true;
868}
869 /*}}}*/
f8aba23f 870// StartsWithGPGClearTextSignature - Check if a file is Pgp/GPG clearsigned /*{{{*/
fe5804fc 871bool StartsWithGPGClearTextSignature(string const &FileName)
0854ad8b
MV
872{
873 static const char* SIGMSG = "-----BEGIN PGP SIGNED MESSAGE-----\n";
1c89c98a 874 char buffer[strlen(SIGMSG)+1];
0854ad8b
MV
875 FILE* gpg = fopen(FileName.c_str(), "r");
876 if (gpg == NULL)
877 return false;
878
879 char const * const test = fgets(buffer, sizeof(buffer), gpg);
880 fclose(gpg);
881 if (test == NULL || strcmp(buffer, SIGMSG) != 0)
882 return false;
883
884 return true;
885}
f8aba23f 886 /*}}}*/
d84da499
DK
887// ChangeOwnerAndPermissionOfFile - set file attributes to requested values /*{{{*/
888bool ChangeOwnerAndPermissionOfFile(char const * const requester, char const * const file, char const * const user, char const * const group, mode_t const mode)
889{
890 if (strcmp(file, "/dev/null") == 0)
891 return true;
892 bool Res = true;
893 if (getuid() == 0 && strlen(user) != 0 && strlen(group) != 0) // if we aren't root, we can't chown, so don't try it
894 {
895 // ensure the file is owned by root and has good permissions
896 struct passwd const * const pw = getpwnam(user);
897 struct group const * const gr = getgrnam(group);
898 if (pw != NULL && gr != NULL && chown(file, pw->pw_uid, gr->gr_gid) != 0)
899 Res &= _error->WarningE(requester, "chown to %s:%s of file %s failed", user, group, file);
900 }
901 if (chmod(file, mode) != 0)
902 Res &= _error->WarningE(requester, "chmod 0%o of file %s failed", mode, file);
903 return Res;
904}
905 /*}}}*/
0854ad8b 906
4239dbca
DK
907class FileFdPrivate { /*{{{*/
908 public:
909#ifdef HAVE_ZLIB
910 gzFile gz;
911#endif
912#ifdef HAVE_BZ2
913 BZFILE* bz2;
914#endif
915#ifdef HAVE_LZMA
916 struct LZMAFILE {
917 FILE* file;
918 uint8_t buffer[4096];
919 lzma_stream stream;
920 lzma_ret err;
921 bool eof;
922 bool compressing;
923
25613a61 924 LZMAFILE() : file(NULL), eof(false), compressing(false) { buffer[0] = '\0'; }
4239dbca
DK
925 ~LZMAFILE() {
926 if (compressing == true)
927 {
928 for (;;) {
929 stream.avail_out = sizeof(buffer)/sizeof(buffer[0]);
930 stream.next_out = buffer;
931 err = lzma_code(&stream, LZMA_FINISH);
932 if (err != LZMA_OK && err != LZMA_STREAM_END)
933 {
934 _error->Error("~LZMAFILE: Compress finalisation failed");
935 break;
936 }
937 size_t const n = sizeof(buffer)/sizeof(buffer[0]) - stream.avail_out;
938 if (n && fwrite(buffer, 1, n, file) != n)
939 {
940 _error->Errno("~LZMAFILE",_("Write error"));
941 break;
942 }
943 if (err == LZMA_STREAM_END)
944 break;
945 }
946 }
947 lzma_end(&stream);
948 fclose(file);
949 }
950 };
951 LZMAFILE* lzma;
952#endif
953 int compressed_fd;
954 pid_t compressor_pid;
955 bool pipe;
956 APT::Configuration::Compressor compressor;
957 unsigned int openmode;
958 unsigned long long seekpos;
959 FileFdPrivate() :
960#ifdef HAVE_ZLIB
961 gz(NULL),
962#endif
963#ifdef HAVE_BZ2
964 bz2(NULL),
965#endif
966#ifdef HAVE_LZMA
967 lzma(NULL),
968#endif
969 compressed_fd(-1), compressor_pid(-1), pipe(false),
970 openmode(0), seekpos(0) {};
971 bool InternalClose(std::string const &FileName)
972 {
973 if (false)
974 /* dummy so that the rest can be 'else if's */;
975#ifdef HAVE_ZLIB
976 else if (gz != NULL) {
977 int const e = gzclose(gz);
978 gz = NULL;
979 // gzdclose() on empty files always fails with "buffer error" here, ignore that
980 if (e != 0 && e != Z_BUF_ERROR)
981 return _error->Errno("close",_("Problem closing the gzip file %s"), FileName.c_str());
982 }
983#endif
984#ifdef HAVE_BZ2
985 else if (bz2 != NULL) {
986 BZ2_bzclose(bz2);
987 bz2 = NULL;
988 }
989#endif
990#ifdef HAVE_LZMA
991 else if (lzma != NULL) {
992 delete lzma;
993 lzma = NULL;
994 }
995#endif
996 return true;
997 }
998 bool CloseDown(std::string const &FileName)
999 {
1000 bool const Res = InternalClose(FileName);
1001
1002 if (compressor_pid > 0)
1003 ExecWait(compressor_pid, "FileFdCompressor", true);
1004 compressor_pid = -1;
1005
1006 return Res;
1007 }
1008 bool InternalStream() const {
1009 return false
1010#ifdef HAVE_BZ2
1011 || bz2 != NULL
1012#endif
1013#ifdef HAVE_LZMA
1014 || lzma != NULL
1015#endif
1016 ;
1017 }
1018
1019
1020 ~FileFdPrivate() { CloseDown(""); }
1021};
1022 /*}}}*/
6c55f07a
DK
1023// FileFd Constructors /*{{{*/
1024FileFd::FileFd(std::string FileName,unsigned int const Mode,unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL)
1025{
1026 Open(FileName,Mode, None, AccessMode);
1027}
1028FileFd::FileFd(std::string FileName,unsigned int const Mode, CompressMode Compress, unsigned long AccessMode) : iFd(-1), Flags(0), d(NULL)
1029{
1030 Open(FileName,Mode, Compress, AccessMode);
1031}
1032FileFd::FileFd() : iFd(-1), Flags(AutoClose), d(NULL) {}
1033FileFd::FileFd(int const Fd, unsigned int const Mode, CompressMode Compress) : iFd(-1), Flags(0), d(NULL)
1034{
1035 OpenDescriptor(Fd, Mode, Compress);
1036}
1037FileFd::FileFd(int const Fd, bool const AutoClose) : iFd(-1), Flags(0), d(NULL)
1038{
1039 OpenDescriptor(Fd, ReadWrite, None, AutoClose);
1040}
1041 /*}}}*/
13d87e2e 1042// FileFd::Open - Open a file /*{{{*/
578bfd0a
AL
1043// ---------------------------------------------------------------------
1044/* The most commonly used open mode combinations are given with Mode */
e5f3f8c1 1045bool FileFd::Open(string FileName,unsigned int const Mode,CompressMode Compress, unsigned long const AccessMode)
578bfd0a 1046{
257e8d66 1047 if (Mode == ReadOnlyGzip)
e5f3f8c1 1048 return Open(FileName, ReadOnly, Gzip, AccessMode);
257e8d66 1049
468720c5 1050 if (Compress == Auto && (Mode & WriteOnly) == WriteOnly)
ae635e3c 1051 return FileFdError("Autodetection on %s only works in ReadOnly openmode!", FileName.c_str());
257e8d66 1052
468720c5
DK
1053 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
1054 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
1055 if (Compress == Auto)
1056 {
468720c5
DK
1057 for (; compressor != compressors.end(); ++compressor)
1058 {
e788a834 1059 std::string file = FileName + compressor->Extension;
468720c5
DK
1060 if (FileExists(file) == false)
1061 continue;
1062 FileName = file;
468720c5
DK
1063 break;
1064 }
1065 }
1066 else if (Compress == Extension)
1067 {
52b47296
DK
1068 std::string::size_type const found = FileName.find_last_of('.');
1069 std::string ext;
1070 if (found != std::string::npos)
1071 {
1072 ext = FileName.substr(found);
1073 if (ext == ".new" || ext == ".bak")
1074 {
1075 std::string::size_type const found2 = FileName.find_last_of('.', found - 1);
1076 if (found2 != std::string::npos)
1077 ext = FileName.substr(found2, found - found2);
1078 else
1079 ext.clear();
1080 }
1081 }
aee1aac6
DK
1082 for (; compressor != compressors.end(); ++compressor)
1083 if (ext == compressor->Extension)
1084 break;
1085 // no matching extension - assume uncompressed (imagine files like 'example.org_Packages')
1086 if (compressor == compressors.end())
1087 for (compressor = compressors.begin(); compressor != compressors.end(); ++compressor)
1088 if (compressor->Name == ".")
468720c5 1089 break;
468720c5 1090 }
aee1aac6 1091 else
468720c5
DK
1092 {
1093 std::string name;
1094 switch (Compress)
1095 {
aee1aac6 1096 case None: name = "."; break;
468720c5
DK
1097 case Gzip: name = "gzip"; break;
1098 case Bzip2: name = "bzip2"; break;
1099 case Lzma: name = "lzma"; break;
1100 case Xz: name = "xz"; break;
aee1aac6
DK
1101 case Auto:
1102 case Extension:
52b47296 1103 // Unreachable
ae635e3c 1104 return FileFdError("Opening File %s in None, Auto or Extension should be already handled?!?", FileName.c_str());
468720c5
DK
1105 }
1106 for (; compressor != compressors.end(); ++compressor)
1107 if (compressor->Name == name)
1108 break;
aee1aac6 1109 if (compressor == compressors.end())
ae635e3c 1110 return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
468720c5
DK
1111 }
1112
aee1aac6 1113 if (compressor == compressors.end())
ae635e3c 1114 return FileFdError("Can't find a match for specified compressor mode for file %s", FileName.c_str());
e5f3f8c1 1115 return Open(FileName, Mode, *compressor, AccessMode);
aee1aac6 1116}
e5f3f8c1 1117bool FileFd::Open(string FileName,unsigned int const Mode,APT::Configuration::Compressor const &compressor, unsigned long const AccessMode)
aee1aac6
DK
1118{
1119 Close();
aee1aac6
DK
1120 Flags = AutoClose;
1121
1122 if ((Mode & WriteOnly) != WriteOnly && (Mode & (Atomic | Create | Empty | Exclusive)) != 0)
ae635e3c 1123 return FileFdError("ReadOnly mode for %s doesn't accept additional flags!", FileName.c_str());
aee1aac6 1124 if ((Mode & ReadWrite) == 0)
ae635e3c 1125 return FileFdError("No openmode provided in FileFd::Open for %s", FileName.c_str());
468720c5 1126
257e8d66
DK
1127 if ((Mode & Atomic) == Atomic)
1128 {
1129 Flags |= Replace;
257e8d66
DK
1130 }
1131 else if ((Mode & (Exclusive | Create)) == (Exclusive | Create))
1132 {
1133 // for atomic, this will be done by rename in Close()
1134 unlink(FileName.c_str());
1135 }
1136 if ((Mode & Empty) == Empty)
578bfd0a 1137 {
257e8d66
DK
1138 struct stat Buf;
1139 if (lstat(FileName.c_str(),&Buf) == 0 && S_ISLNK(Buf.st_mode))
1140 unlink(FileName.c_str());
1141 }
c4fc2fd7 1142
561f860a
DK
1143 int fileflags = 0;
1144 #define if_FLAGGED_SET(FLAG, MODE) if ((Mode & FLAG) == FLAG) fileflags |= MODE
1145 if_FLAGGED_SET(ReadWrite, O_RDWR);
1146 else if_FLAGGED_SET(ReadOnly, O_RDONLY);
1147 else if_FLAGGED_SET(WriteOnly, O_WRONLY);
4a9db827 1148
561f860a
DK
1149 if_FLAGGED_SET(Create, O_CREAT);
1150 if_FLAGGED_SET(Empty, O_TRUNC);
1151 if_FLAGGED_SET(Exclusive, O_EXCL);
561f860a 1152 #undef if_FLAGGED_SET
52b47296 1153
7335eebe
AGM
1154 if ((Mode & Atomic) == Atomic)
1155 {
1156 char *name = strdup((FileName + ".XXXXXX").c_str());
1157
dc545c0b 1158 if((iFd = mkstemp(name)) == -1)
7335eebe
AGM
1159 {
1160 free(name);
98b69f9d 1161 return FileFdErrno("mkstemp", "Could not create temporary file for %s", FileName.c_str());
7335eebe
AGM
1162 }
1163
1164 TemporaryFileName = string(name);
7335eebe 1165 free(name);
dc545c0b 1166
230e69d7
DK
1167 // umask() will always set the umask and return the previous value, so
1168 // we first set the umask and then reset it to the old value
1169 mode_t const CurrentUmask = umask(0);
1170 umask(CurrentUmask);
1171 // calculate the actual file permissions (just like open/creat)
1172 mode_t const FilePermissions = (AccessMode & ~CurrentUmask);
1173
1174 if(fchmod(iFd, FilePermissions) == -1)
dc545c0b 1175 return FileFdErrno("fchmod", "Could not change permissions for temporary file %s", TemporaryFileName.c_str());
7335eebe 1176 }
468720c5 1177 else
230e69d7 1178 iFd = open(FileName.c_str(), fileflags, AccessMode);
468720c5 1179
b711c01e 1180 this->FileName = FileName;
561f860a
DK
1181 if (iFd == -1 || OpenInternDescriptor(Mode, compressor) == false)
1182 {
468720c5 1183 if (iFd != -1)
fc81e8f2 1184 {
561f860a
DK
1185 close (iFd);
1186 iFd = -1;
fc81e8f2 1187 }
ae635e3c 1188 return FileFdErrno("open",_("Could not open file %s"), FileName.c_str());
257e8d66 1189 }
578bfd0a 1190
13d87e2e
AL
1191 SetCloseExec(iFd,true);
1192 return true;
578bfd0a 1193}
257e8d66
DK
1194 /*}}}*/
1195// FileFd::OpenDescriptor - Open a filedescriptor /*{{{*/
1196// ---------------------------------------------------------------------
1197/* */
52b47296 1198bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, CompressMode Compress, bool AutoClose)
aee1aac6
DK
1199{
1200 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
1201 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
1202 std::string name;
bce778a3
MV
1203
1204 // compat with the old API
1205 if (Mode == ReadOnlyGzip && Compress == None)
1206 Compress = Gzip;
1207
aee1aac6
DK
1208 switch (Compress)
1209 {
1210 case None: name = "."; break;
1211 case Gzip: name = "gzip"; break;
1212 case Bzip2: name = "bzip2"; break;
1213 case Lzma: name = "lzma"; break;
1214 case Xz: name = "xz"; break;
1215 case Auto:
1216 case Extension:
f97bb523
DK
1217 if (AutoClose == true && Fd != -1)
1218 close(Fd);
ae635e3c 1219 return FileFdError("Opening Fd %d in Auto or Extension compression mode is not supported", Fd);
aee1aac6
DK
1220 }
1221 for (; compressor != compressors.end(); ++compressor)
1222 if (compressor->Name == name)
1223 break;
1224 if (compressor == compressors.end())
f97bb523
DK
1225 {
1226 if (AutoClose == true && Fd != -1)
1227 close(Fd);
ae635e3c 1228 return FileFdError("Can't find a configured compressor %s for file %s", name.c_str(), FileName.c_str());
f97bb523 1229 }
aee1aac6
DK
1230 return OpenDescriptor(Fd, Mode, *compressor, AutoClose);
1231}
52b47296 1232bool FileFd::OpenDescriptor(int Fd, unsigned int const Mode, APT::Configuration::Compressor const &compressor, bool AutoClose)
144c0969
JAK
1233{
1234 Close();
1235 Flags = (AutoClose) ? FileFd::AutoClose : 0;
84baaae9 1236 iFd = Fd;
b711c01e 1237 this->FileName = "";
84baaae9 1238 if (OpenInternDescriptor(Mode, compressor) == false)
468720c5 1239 {
f97bb523 1240 if (iFd != -1 && (
84baaae9 1241 (Flags & Compressed) == Compressed ||
f97bb523
DK
1242 AutoClose == true))
1243 {
468720c5 1244 close (iFd);
f97bb523
DK
1245 iFd = -1;
1246 }
1247 return FileFdError(_("Could not open file descriptor %d"), Fd);
144c0969 1248 }
144c0969 1249 return true;
468720c5 1250}
52b47296 1251bool FileFd::OpenInternDescriptor(unsigned int const Mode, APT::Configuration::Compressor const &compressor)
468720c5 1252{
84baaae9
DK
1253 if (iFd == -1)
1254 return false;
ff477ee1
DK
1255 if (compressor.Name == "." || compressor.Binary.empty() == true)
1256 return true;
1257
7f350a37 1258#if defined HAVE_ZLIB || defined HAVE_BZ2 || defined HAVE_LZMA
69d6988a
DK
1259 // the API to open files is similar, so setup to avoid code duplicates later
1260 // and while at it ensure that we close before opening (if its a reopen)
1261 void* (*compress_open)(int, const char *) = NULL;
7f350a37
DK
1262 if (false)
1263 /* dummy so that the rest can be 'else if's */;
4239dbca 1264#define APT_COMPRESS_INIT(NAME,OPEN) \
7f350a37 1265 else if (compressor.Name == NAME) \
69d6988a
DK
1266 { \
1267 compress_open = (void*(*)(int, const char *)) OPEN; \
4239dbca 1268 if (d != NULL) d->InternalClose(FileName); \
69d6988a
DK
1269 }
1270#ifdef HAVE_ZLIB
4239dbca 1271 APT_COMPRESS_INIT("gzip", gzdopen)
69d6988a
DK
1272#endif
1273#ifdef HAVE_BZ2
4239dbca 1274 APT_COMPRESS_INIT("bzip2", BZ2_bzdopen)
69d6988a 1275#endif
7f350a37 1276#ifdef HAVE_LZMA
4239dbca
DK
1277 APT_COMPRESS_INIT("xz", fdopen)
1278 APT_COMPRESS_INIT("lzma", fdopen)
7f350a37 1279#endif
69d6988a
DK
1280#undef APT_COMPRESS_INIT
1281#endif
1282
ba667cf7
DK
1283 if (d == NULL)
1284 {
1285 d = new FileFdPrivate();
1286 d->openmode = Mode;
1287 d->compressor = compressor;
7f350a37 1288#if defined HAVE_ZLIB || defined HAVE_BZ2 || defined HAVE_LZMA
f6ffe501 1289 if ((Flags & AutoClose) != AutoClose && compress_open != NULL)
84baaae9
DK
1290 {
1291 // Need to duplicate fd here or gz/bz2 close for cleanup will close the fd as well
1292 int const internFd = dup(iFd);
1293 if (internFd == -1)
1294 return FileFdErrno("OpenInternDescriptor", _("Could not open file descriptor %d"), iFd);
1295 iFd = internFd;
1296 }
69d6988a 1297#endif
ba667cf7 1298 }
ff477ee1 1299
7f350a37 1300#if defined HAVE_ZLIB || defined HAVE_BZ2 || defined HAVE_LZMA
69d6988a 1301 if (compress_open != NULL)
468720c5 1302 {
69d6988a 1303 void* compress_struct = NULL;
468720c5 1304 if ((Mode & ReadWrite) == ReadWrite)
69d6988a 1305 compress_struct = compress_open(iFd, "r+");
468720c5 1306 else if ((Mode & WriteOnly) == WriteOnly)
69d6988a 1307 compress_struct = compress_open(iFd, "w");
468720c5 1308 else
69d6988a
DK
1309 compress_struct = compress_open(iFd, "r");
1310 if (compress_struct == NULL)
468720c5 1311 return false;
69d6988a 1312
7f350a37
DK
1313 if (false)
1314 /* dummy so that the rest can be 'else if's */;
69d6988a 1315#ifdef HAVE_ZLIB
7f350a37 1316 else if (compressor.Name == "gzip")
69d6988a 1317 d->gz = (gzFile) compress_struct;
699b209e 1318#endif
c4997486 1319#ifdef HAVE_BZ2
7f350a37 1320 else if (compressor.Name == "bzip2")
69d6988a 1321 d->bz2 = (BZFILE*) compress_struct;
7f350a37
DK
1322#endif
1323#ifdef HAVE_LZMA
1324 else if (compressor.Name == "xz" || compressor.Name == "lzma")
adbd7eb4 1325 {
7f350a37
DK
1326 uint32_t const xzlevel = 6;
1327 uint64_t const memlimit = UINT64_MAX;
1328 if (d->lzma == NULL)
1329 d->lzma = new FileFdPrivate::LZMAFILE;
1330 d->lzma->file = (FILE*) compress_struct;
21ea1dbb
MV
1331 lzma_stream tmp_stream = LZMA_STREAM_INIT;
1332 d->lzma->stream = tmp_stream;
7f350a37
DK
1333
1334 if ((Mode & ReadWrite) == ReadWrite)
1335 return FileFdError("ReadWrite mode is not supported for file %s", FileName.c_str());
1336
1337 if ((Mode & WriteOnly) == WriteOnly)
1338 {
1339 if (compressor.Name == "xz")
1340 {
1341 if (lzma_easy_encoder(&d->lzma->stream, xzlevel, LZMA_CHECK_CRC32) != LZMA_OK)
1342 return false;
1343 }
1344 else
1345 {
1346 lzma_options_lzma options;
1347 lzma_lzma_preset(&options, xzlevel);
1348 if (lzma_alone_encoder(&d->lzma->stream, &options) != LZMA_OK)
1349 return false;
1350 }
1351 d->lzma->compressing = true;
1352 }
1353 else
1354 {
1355 if (compressor.Name == "xz")
1356 {
1357 if (lzma_auto_decoder(&d->lzma->stream, memlimit, 0) != LZMA_OK)
1358 return false;
1359 }
1360 else
1361 {
1362 if (lzma_alone_decoder(&d->lzma->stream, memlimit) != LZMA_OK)
1363 return false;
1364 }
1365 d->lzma->compressing = false;
1366 }
adbd7eb4 1367 }
69d6988a 1368#endif
c4997486
DK
1369 Flags |= Compressed;
1370 return true;
1371 }
1372#endif
1373
adbd7eb4
DK
1374 // collect zombies here in case we reopen
1375 if (d->compressor_pid > 0)
1376 ExecWait(d->compressor_pid, "FileFdCompressor", true);
561f860a
DK
1377
1378 if ((Mode & ReadWrite) == ReadWrite)
ae635e3c 1379 return FileFdError("ReadWrite mode is not supported for file %s", FileName.c_str());
561f860a
DK
1380
1381 bool const Comp = (Mode & WriteOnly) == WriteOnly;
561f860a
DK
1382 if (Comp == false)
1383 {
adbd7eb4 1384 // Handle 'decompression' of empty files
561f860a
DK
1385 struct stat Buf;
1386 fstat(iFd, &Buf);
1387 if (Buf.st_size == 0 && S_ISFIFO(Buf.st_mode) == false)
1388 return true;
1389
1390 // We don't need the file open - instead let the compressor open it
1391 // as he properly knows better how to efficiently read from 'his' file
1392 if (FileName.empty() == false)
afb093cd 1393 {
561f860a 1394 close(iFd);
afb093cd
DK
1395 iFd = -1;
1396 }
561f860a
DK
1397 }
1398
1399 // Create a data pipe
1400 int Pipe[2] = {-1,-1};
1401 if (pipe(Pipe) != 0)
ae635e3c 1402 return FileFdErrno("pipe",_("Failed to create subprocess IPC"));
561f860a
DK
1403 for (int J = 0; J != 2; J++)
1404 SetCloseExec(Pipe[J],true);
1405
1406 d->compressed_fd = iFd;
1407 d->pipe = true;
1408
1409 if (Comp == true)
1410 iFd = Pipe[1];
1411 else
1412 iFd = Pipe[0];
1413
1414 // The child..
1415 d->compressor_pid = ExecFork();
1416 if (d->compressor_pid == 0)
1417 {
1418 if (Comp == true)
1419 {
1420 dup2(d->compressed_fd,STDOUT_FILENO);
1421 dup2(Pipe[0],STDIN_FILENO);
1422 }
1423 else
1424 {
7f350a37 1425 if (d->compressed_fd != -1)
561f860a
DK
1426 dup2(d->compressed_fd,STDIN_FILENO);
1427 dup2(Pipe[1],STDOUT_FILENO);
1428 }
0b4895d3
DK
1429 int const nullfd = open("/dev/null", O_WRONLY);
1430 if (nullfd != -1)
1431 {
1432 dup2(nullfd,STDERR_FILENO);
1433 close(nullfd);
1434 }
561f860a
DK
1435
1436 SetCloseExec(STDOUT_FILENO,false);
1437 SetCloseExec(STDIN_FILENO,false);
1438
1439 std::vector<char const*> Args;
1440 Args.push_back(compressor.Binary.c_str());
1441 std::vector<std::string> const * const addArgs =
1442 (Comp == true) ? &(compressor.CompressArgs) : &(compressor.UncompressArgs);
1443 for (std::vector<std::string>::const_iterator a = addArgs->begin();
1444 a != addArgs->end(); ++a)
1445 Args.push_back(a->c_str());
1446 if (Comp == false && FileName.empty() == false)
1447 {
bb93178b
DK
1448 // commands not needing arguments, do not need to be told about using standard output
1449 // in reality, only testcases with tools like cat, rev, rot13, … are able to trigger this
1450 if (compressor.CompressArgs.empty() == false && compressor.UncompressArgs.empty() == false)
1451 Args.push_back("--stdout");
561f860a
DK
1452 if (TemporaryFileName.empty() == false)
1453 Args.push_back(TemporaryFileName.c_str());
1454 else
1455 Args.push_back(FileName.c_str());
1456 }
1457 Args.push_back(NULL);
1458
1459 execvp(Args[0],(char **)&Args[0]);
1460 cerr << _("Failed to exec compressor ") << Args[0] << endl;
1461 _exit(100);
1462 }
1463 if (Comp == true)
1464 close(Pipe[0]);
468720c5 1465 else
561f860a 1466 close(Pipe[1]);
561f860a 1467
468720c5 1468 return true;
144c0969 1469}
578bfd0a 1470 /*}}}*/
8e06abb2 1471// FileFd::~File - Closes the file /*{{{*/
578bfd0a
AL
1472// ---------------------------------------------------------------------
1473/* If the proper modes are selected then we close the Fd and possibly
1474 unlink the file on error. */
8e06abb2 1475FileFd::~FileFd()
578bfd0a
AL
1476{
1477 Close();
500400fe 1478 if (d != NULL)
500400fe 1479 d->CloseDown(FileName);
96ab3c6f
MV
1480 delete d;
1481 d = NULL;
578bfd0a
AL
1482}
1483 /*}}}*/
8e06abb2 1484// FileFd::Read - Read a bit of the file /*{{{*/
578bfd0a 1485// ---------------------------------------------------------------------
1e3f4083 1486/* We are careful to handle interruption by a signal while reading
b0db36b1 1487 gracefully. */
650faab0 1488bool FileFd::Read(void *To,unsigned long long Size,unsigned long long *Actual)
578bfd0a 1489{
3286ad13 1490 ssize_t Res;
b0db36b1 1491 errno = 0;
f604cf55
AL
1492 if (Actual != 0)
1493 *Actual = 0;
699b209e 1494 *((char *)To) = '\0';
b0db36b1 1495 do
578bfd0a 1496 {
7f350a37
DK
1497 if (false)
1498 /* dummy so that the rest can be 'else if's */;
7efb8c8e 1499#ifdef HAVE_ZLIB
7f350a37 1500 else if (d != NULL && d->gz != NULL)
c4997486 1501 Res = gzread(d->gz,To,Size);
c4997486
DK
1502#endif
1503#ifdef HAVE_BZ2
7f350a37 1504 else if (d != NULL && d->bz2 != NULL)
c4997486 1505 Res = BZ2_bzread(d->bz2,To,Size);
699b209e 1506#endif
7f350a37
DK
1507#ifdef HAVE_LZMA
1508 else if (d != NULL && d->lzma != NULL)
1509 {
1510 if (d->lzma->eof == true)
1511 break;
1512
1513 d->lzma->stream.next_out = (uint8_t *) To;
1514 d->lzma->stream.avail_out = Size;
1515 if (d->lzma->stream.avail_in == 0)
1516 {
1517 d->lzma->stream.next_in = d->lzma->buffer;
1518 d->lzma->stream.avail_in = fread(d->lzma->buffer, 1, sizeof(d->lzma->buffer)/sizeof(d->lzma->buffer[0]), d->lzma->file);
1519 }
1520 d->lzma->err = lzma_code(&d->lzma->stream, LZMA_RUN);
1521 if (d->lzma->err == LZMA_STREAM_END)
1522 {
1523 d->lzma->eof = true;
1524 Res = Size - d->lzma->stream.avail_out;
1525 }
1526 else if (d->lzma->err != LZMA_OK)
1527 {
1528 Res = -1;
1529 errno = 0;
1530 }
1531 else
c4b113e6 1532 {
7f350a37 1533 Res = Size - d->lzma->stream.avail_out;
c4b113e6
DK
1534 if (Res == 0)
1535 {
1536 // lzma run was okay, but produced no output…
1537 Res = -1;
1538 errno = EINTR;
1539 }
1540 }
7f350a37
DK
1541 }
1542#endif
1543 else
a3a03f5d 1544 Res = read(iFd,To,Size);
b711c01e 1545
b0db36b1
AL
1546 if (Res < 0)
1547 {
b711c01e 1548 if (errno == EINTR)
c4b113e6
DK
1549 {
1550 // trick the while-loop into running again
1551 Res = 1;
1552 errno = 0;
b711c01e 1553 continue;
c4b113e6 1554 }
7f350a37
DK
1555 if (false)
1556 /* dummy so that the rest can be 'else if's */;
7efb8c8e 1557#ifdef HAVE_ZLIB
7f350a37 1558 else if (d != NULL && d->gz != NULL)
b711c01e
DK
1559 {
1560 int err;
1561 char const * const errmsg = gzerror(d->gz, &err);
1562 if (err != Z_ERRNO)
ae635e3c 1563 return FileFdError("gzread: %s (%d: %s)", _("Read error"), err, errmsg);
b711c01e 1564 }
c4997486
DK
1565#endif
1566#ifdef HAVE_BZ2
7f350a37 1567 else if (d != NULL && d->bz2 != NULL)
c4997486
DK
1568 {
1569 int err;
1570 char const * const errmsg = BZ2_bzerror(d->bz2, &err);
1571 if (err != BZ_IO_ERROR)
c36db2b5 1572 return FileFdError("BZ2_bzread: %s %s (%d: %s)", FileName.c_str(), _("Read error"), err, errmsg);
c4997486 1573 }
7f350a37
DK
1574#endif
1575#ifdef HAVE_LZMA
1576 else if (d != NULL && d->lzma != NULL)
1577 return FileFdError("lzma_read: %s (%d)", _("Read error"), d->lzma->err);
b711c01e 1578#endif
ae635e3c 1579 return FileFdErrno("read",_("Read error"));
b0db36b1 1580 }
578bfd0a 1581
b0db36b1
AL
1582 To = (char *)To + Res;
1583 Size -= Res;
ff477ee1
DK
1584 if (d != NULL)
1585 d->seekpos += Res;
f604cf55
AL
1586 if (Actual != 0)
1587 *Actual += Res;
b0db36b1
AL
1588 }
1589 while (Res > 0 && Size > 0);
1590
1591 if (Size == 0)
1592 return true;
1593
ddc1d8d0 1594 // Eof handling
f604cf55 1595 if (Actual != 0)
ddc1d8d0
AL
1596 {
1597 Flags |= HitEof;
1598 return true;
1599 }
ae635e3c
DK
1600
1601 return FileFdError(_("read, still have %llu to read but none left"), Size);
578bfd0a
AL
1602}
1603 /*}}}*/
032bd56f
DK
1604// FileFd::ReadLine - Read a complete line from the file /*{{{*/
1605// ---------------------------------------------------------------------
1606/* Beware: This method can be quiet slow for big buffers on UNcompressed
1607 files because of the naive implementation! */
1608char* FileFd::ReadLine(char *To, unsigned long long const Size)
1609{
699b209e 1610 *To = '\0';
7efb8c8e 1611#ifdef HAVE_ZLIB
ff477ee1 1612 if (d != NULL && d->gz != NULL)
032bd56f 1613 return gzgets(d->gz, To, Size);
699b209e 1614#endif
032bd56f
DK
1615
1616 unsigned long long read = 0;
40468850
DK
1617 while ((Size - 1) != read)
1618 {
1619 unsigned long long done = 0;
1620 if (Read(To + read, 1, &done) == false)
1621 return NULL;
1622 if (done == 0)
1623 break;
1624 if (To[read++] == '\n')
1625 break;
1626 }
1627 if (read == 0)
032bd56f 1628 return NULL;
40468850 1629 To[read] = '\0';
032bd56f
DK
1630 return To;
1631}
1632 /*}}}*/
8e06abb2 1633// FileFd::Write - Write to the file /*{{{*/
578bfd0a
AL
1634// ---------------------------------------------------------------------
1635/* */
650faab0 1636bool FileFd::Write(const void *From,unsigned long long Size)
578bfd0a 1637{
3286ad13 1638 ssize_t Res;
b0db36b1
AL
1639 errno = 0;
1640 do
578bfd0a 1641 {
7f350a37
DK
1642 if (false)
1643 /* dummy so that the rest can be 'else if's */;
7efb8c8e 1644#ifdef HAVE_ZLIB
7f350a37
DK
1645 else if (d != NULL && d->gz != NULL)
1646 Res = gzwrite(d->gz,From,Size);
c4997486
DK
1647#endif
1648#ifdef HAVE_BZ2
7f350a37
DK
1649 else if (d != NULL && d->bz2 != NULL)
1650 Res = BZ2_bzwrite(d->bz2,(void*)From,Size);
699b209e 1651#endif
7f350a37
DK
1652#ifdef HAVE_LZMA
1653 else if (d != NULL && d->lzma != NULL)
1654 {
1655 d->lzma->stream.next_in = (uint8_t *)From;
1656 d->lzma->stream.avail_in = Size;
1657 d->lzma->stream.next_out = d->lzma->buffer;
1658 d->lzma->stream.avail_out = sizeof(d->lzma->buffer)/sizeof(d->lzma->buffer[0]);
1659 d->lzma->err = lzma_code(&d->lzma->stream, LZMA_RUN);
1660 if (d->lzma->err != LZMA_OK)
1661 return false;
1662 size_t const n = sizeof(d->lzma->buffer)/sizeof(d->lzma->buffer[0]) - d->lzma->stream.avail_out;
1663 size_t const m = (n == 0) ? 0 : fwrite(d->lzma->buffer, 1, n, d->lzma->file);
1664 if (m != n)
1665 Res = -1;
1666 else
1667 Res = Size - d->lzma->stream.avail_in;
1668 }
699b209e 1669#endif
7f350a37
DK
1670 else
1671 Res = write(iFd,From,Size);
1672
b0db36b1
AL
1673 if (Res < 0 && errno == EINTR)
1674 continue;
1675 if (Res < 0)
1676 {
7f350a37
DK
1677 if (false)
1678 /* dummy so that the rest can be 'else if's */;
c4997486 1679#ifdef HAVE_ZLIB
7f350a37 1680 else if (d != NULL && d->gz != NULL)
c4997486
DK
1681 {
1682 int err;
1683 char const * const errmsg = gzerror(d->gz, &err);
1684 if (err != Z_ERRNO)
ae635e3c 1685 return FileFdError("gzwrite: %s (%d: %s)", _("Write error"), err, errmsg);
c4997486
DK
1686 }
1687#endif
1688#ifdef HAVE_BZ2
7f350a37 1689 else if (d != NULL && d->bz2 != NULL)
c4997486
DK
1690 {
1691 int err;
1692 char const * const errmsg = BZ2_bzerror(d->bz2, &err);
1693 if (err != BZ_IO_ERROR)
ae635e3c 1694 return FileFdError("BZ2_bzwrite: %s (%d: %s)", _("Write error"), err, errmsg);
c4997486 1695 }
7f350a37
DK
1696#endif
1697#ifdef HAVE_LZMA
1698 else if (d != NULL && d->lzma != NULL)
1699 return FileFdErrno("lzma_fwrite", _("Write error"));
c4997486 1700#endif
ae635e3c 1701 return FileFdErrno("write",_("Write error"));
b0db36b1
AL
1702 }
1703
cf4ff3b7 1704 From = (char const *)From + Res;
b0db36b1 1705 Size -= Res;
ff477ee1
DK
1706 if (d != NULL)
1707 d->seekpos += Res;
578bfd0a 1708 }
b0db36b1 1709 while (Res > 0 && Size > 0);
578bfd0a 1710
b0db36b1
AL
1711 if (Size == 0)
1712 return true;
ae635e3c
DK
1713
1714 return FileFdError(_("write, still have %llu to write but couldn't"), Size);
d68d65ad
DK
1715}
1716bool FileFd::Write(int Fd, const void *From, unsigned long long Size)
1717{
3286ad13 1718 ssize_t Res;
d68d65ad
DK
1719 errno = 0;
1720 do
1721 {
1722 Res = write(Fd,From,Size);
1723 if (Res < 0 && errno == EINTR)
1724 continue;
1725 if (Res < 0)
1726 return _error->Errno("write",_("Write error"));
1727
cf4ff3b7 1728 From = (char const *)From + Res;
d68d65ad
DK
1729 Size -= Res;
1730 }
1731 while (Res > 0 && Size > 0);
1732
1733 if (Size == 0)
1734 return true;
1735
1736 return _error->Error(_("write, still have %llu to write but couldn't"), Size);
578bfd0a
AL
1737}
1738 /*}}}*/
8e06abb2 1739// FileFd::Seek - Seek in the file /*{{{*/
578bfd0a
AL
1740// ---------------------------------------------------------------------
1741/* */
650faab0 1742bool FileFd::Seek(unsigned long long To)
578bfd0a 1743{
bb93178b
DK
1744 Flags &= ~HitEof;
1745
4239dbca 1746 if (d != NULL && (d->pipe == true || d->InternalStream() == true))
699b209e 1747 {
1abbc47c
DK
1748 // Our poor man seeking in pipes is costly, so try to avoid it
1749 unsigned long long seekpos = Tell();
1750 if (seekpos == To)
1751 return true;
1752 else if (seekpos < To)
1753 return Skip(To - seekpos);
1754
561f860a 1755 if ((d->openmode & ReadOnly) != ReadOnly)
ae635e3c 1756 return FileFdError("Reopen is only implemented for read-only files!");
4239dbca 1757 d->InternalClose(FileName);
afb093cd
DK
1758 if (iFd != -1)
1759 close(iFd);
1760 iFd = -1;
561f860a
DK
1761 if (TemporaryFileName.empty() == false)
1762 iFd = open(TemporaryFileName.c_str(), O_RDONLY);
1763 else if (FileName.empty() == false)
1764 iFd = open(FileName.c_str(), O_RDONLY);
1765 else
6fd947bd
DK
1766 {
1767 if (d->compressed_fd > 0)
1768 if (lseek(d->compressed_fd, 0, SEEK_SET) != 0)
1769 iFd = d->compressed_fd;
500400fe 1770 if (iFd < 0)
ae635e3c 1771 return FileFdError("Reopen is not implemented for pipes opened with FileFd::OpenDescriptor()!");
6fd947bd 1772 }
561f860a
DK
1773
1774 if (OpenInternDescriptor(d->openmode, d->compressor) == false)
ae635e3c 1775 return FileFdError("Seek on file %s because it couldn't be reopened", FileName.c_str());
561f860a
DK
1776
1777 if (To != 0)
1778 return Skip(To);
1abbc47c
DK
1779
1780 d->seekpos = To;
561f860a 1781 return true;
699b209e 1782 }
3286ad13 1783 off_t res;
7efb8c8e 1784#ifdef HAVE_ZLIB
ff477ee1 1785 if (d != NULL && d->gz)
032bd56f 1786 res = gzseek(d->gz,To,SEEK_SET);
a3a03f5d 1787 else
699b209e 1788#endif
a3a03f5d 1789 res = lseek(iFd,To,SEEK_SET);
3286ad13 1790 if (res != (off_t)To)
ae635e3c 1791 return FileFdError("Unable to seek to %llu", To);
1abbc47c 1792
ff477ee1
DK
1793 if (d != NULL)
1794 d->seekpos = To;
727f18af
AL
1795 return true;
1796}
1797 /*}}}*/
1798// FileFd::Skip - Seek in the file /*{{{*/
1799// ---------------------------------------------------------------------
1800/* */
650faab0 1801bool FileFd::Skip(unsigned long long Over)
727f18af 1802{
4239dbca 1803 if (d != NULL && (d->pipe == true || d->InternalStream() == true))
40468850 1804 {
40468850
DK
1805 char buffer[1024];
1806 while (Over != 0)
1807 {
1808 unsigned long long toread = std::min((unsigned long long) sizeof(buffer), Over);
1809 if (Read(buffer, toread) == false)
ae635e3c 1810 return FileFdError("Unable to seek ahead %llu",Over);
40468850
DK
1811 Over -= toread;
1812 }
1813 return true;
1814 }
1815
3286ad13 1816 off_t res;
7efb8c8e 1817#ifdef HAVE_ZLIB
ff477ee1 1818 if (d != NULL && d->gz != NULL)
032bd56f 1819 res = gzseek(d->gz,Over,SEEK_CUR);
a3a03f5d 1820 else
699b209e 1821#endif
a3a03f5d 1822 res = lseek(iFd,Over,SEEK_CUR);
1823 if (res < 0)
ae635e3c 1824 return FileFdError("Unable to seek ahead %llu",Over);
ff477ee1
DK
1825 if (d != NULL)
1826 d->seekpos = res;
1abbc47c 1827
6d5dd02a
AL
1828 return true;
1829}
1830 /*}}}*/
1831// FileFd::Truncate - Truncate the file /*{{{*/
1832// ---------------------------------------------------------------------
1833/* */
650faab0 1834bool FileFd::Truncate(unsigned long long To)
6d5dd02a 1835{
ad5051ef
DK
1836 // truncating /dev/null is always successful - as we get an error otherwise
1837 if (To == 0 && FileName == "/dev/null")
1838 return true;
7f350a37 1839#if defined HAVE_ZLIB || defined HAVE_BZ2 || defined HAVE_LZMA
4239dbca 1840 if (d != NULL && (d->InternalStream() == true
7f350a37 1841#ifdef HAVE_ZLIB
4239dbca 1842 || d->gz != NULL
7f350a37 1843#endif
4239dbca 1844 ))
ae635e3c 1845 return FileFdError("Truncating compressed files is not implemented (%s)", FileName.c_str());
c4997486 1846#endif
6d5dd02a 1847 if (ftruncate(iFd,To) != 0)
ae635e3c
DK
1848 return FileFdError("Unable to truncate to %llu",To);
1849
578bfd0a
AL
1850 return true;
1851}
1852 /*}}}*/
7f25bdff
AL
1853// FileFd::Tell - Current seek position /*{{{*/
1854// ---------------------------------------------------------------------
1855/* */
650faab0 1856unsigned long long FileFd::Tell()
7f25bdff 1857{
1abbc47c
DK
1858 // In theory, we could just return seekpos here always instead of
1859 // seeking around, but not all users of FileFd use always Seek() and co
1860 // so d->seekpos isn't always true and we can just use it as a hint if
1861 // we have nothing else, but not always as an authority…
4239dbca 1862 if (d != NULL && (d->pipe == true || d->InternalStream() == true))
1abbc47c
DK
1863 return d->seekpos;
1864
a3a03f5d 1865 off_t Res;
7efb8c8e 1866#ifdef HAVE_ZLIB
ff477ee1 1867 if (d != NULL && d->gz != NULL)
032bd56f 1868 Res = gztell(d->gz);
a3a03f5d 1869 else
699b209e 1870#endif
a3a03f5d 1871 Res = lseek(iFd,0,SEEK_CUR);
7f25bdff 1872 if (Res == (off_t)-1)
ae635e3c 1873 FileFdErrno("lseek","Failed to determine the current file position");
ff477ee1
DK
1874 if (d != NULL)
1875 d->seekpos = Res;
7f25bdff
AL
1876 return Res;
1877}
1878 /*}}}*/
8190b07a 1879static bool StatFileFd(char const * const msg, int const iFd, std::string const &FileName, struct stat &Buf, FileFdPrivate * const d) /*{{{*/
578bfd0a 1880{
6008b79a
DK
1881 bool ispipe = (d != NULL && d->pipe == true);
1882 if (ispipe == false)
1883 {
1884 if (fstat(iFd,&Buf) != 0)
8190b07a
DK
1885 // higher-level code will generate more meaningful messages,
1886 // even translated this would be meaningless for users
1887 return _error->Errno("fstat", "Unable to determine %s for fd %i", msg, iFd);
003c40d3
DK
1888 if (FileName.empty() == false)
1889 ispipe = S_ISFIFO(Buf.st_mode);
6008b79a 1890 }
699b209e
DK
1891
1892 // for compressor pipes st_size is undefined and at 'best' zero
6008b79a 1893 if (ispipe == true)
699b209e
DK
1894 {
1895 // we set it here, too, as we get the info here for free
1896 // in theory the Open-methods should take care of it already
ff477ee1
DK
1897 if (d != NULL)
1898 d->pipe = true;
699b209e 1899 if (stat(FileName.c_str(), &Buf) != 0)
8190b07a
DK
1900 return _error->Errno("fstat", "Unable to determine %s for file %s", msg, FileName.c_str());
1901 }
1902 return true;
1903}
1904 /*}}}*/
1905// FileFd::FileSize - Return the size of the file /*{{{*/
1906unsigned long long FileFd::FileSize()
1907{
1908 struct stat Buf;
1909 if (StatFileFd("file size", iFd, FileName, Buf, d) == false)
1910 {
1911 Flags |= Fail;
1912 return 0;
699b209e 1913 }
4260fd39
DK
1914 return Buf.st_size;
1915}
1916 /*}}}*/
8190b07a
DK
1917// FileFd::ModificationTime - Return the time of last touch /*{{{*/
1918time_t FileFd::ModificationTime()
1919{
1920 struct stat Buf;
1921 if (StatFileFd("modification time", iFd, FileName, Buf, d) == false)
1922 {
1923 Flags |= Fail;
1924 return 0;
1925 }
1926 return Buf.st_mtime;
1927}
1928 /*}}}*/
4260fd39
DK
1929// FileFd::Size - Return the size of the content in the file /*{{{*/
1930// ---------------------------------------------------------------------
1931/* */
650faab0 1932unsigned long long FileFd::Size()
4260fd39 1933{
650faab0 1934 unsigned long long size = FileSize();
44dc669e 1935
699b209e
DK
1936 // for compressor pipes st_size is undefined and at 'best' zero,
1937 // so we 'read' the content and 'seek' back - see there
4239dbca 1938 if (d != NULL && (d->pipe == true || (d->InternalStream() == true && size > 0)))
699b209e 1939 {
1abbc47c 1940 unsigned long long const oldSeek = Tell();
699b209e
DK
1941 char ignore[1000];
1942 unsigned long long read = 0;
1943 do {
638593bd
DK
1944 if (Read(ignore, sizeof(ignore), &read) == false)
1945 {
1946 Seek(oldSeek);
1947 return 0;
1948 }
699b209e 1949 } while(read != 0);
1abbc47c
DK
1950 size = Tell();
1951 Seek(oldSeek);
699b209e 1952 }
7efb8c8e 1953#ifdef HAVE_ZLIB
44dc669e 1954 // only check gzsize if we are actually a gzip file, just checking for
032bd56f 1955 // "gz" is not sufficient as uncompressed files could be opened with
44dc669e 1956 // gzopen in "direct" mode as well
ff477ee1 1957 else if (d != NULL && d->gz && !gzdirect(d->gz) && size > 0)
9c182afa 1958 {
65c72a4b 1959 off_t const oldPos = lseek(iFd,0,SEEK_CUR);
9c182afa
MP
1960 /* unfortunately zlib.h doesn't provide a gzsize(), so we have to do
1961 * this ourselves; the original (uncompressed) file size is the last 32
1962 * bits of the file */
650faab0 1963 // FIXME: Size for gz-files is limited by 32bit… no largefile support
9c182afa 1964 if (lseek(iFd, -4, SEEK_END) < 0)
fbb89d94 1965 {
638593bd
DK
1966 FileFdErrno("lseek","Unable to seek to end of gzipped file");
1967 return 0;
fbb89d94 1968 }
05eab8af 1969 uint32_t size = 0;
9c182afa 1970 if (read(iFd, &size, 4) != 4)
fbb89d94 1971 {
638593bd
DK
1972 FileFdErrno("read","Unable to read original size of gzipped file");
1973 return 0;
fbb89d94 1974 }
05eab8af 1975 size = le32toh(size);
9c182afa 1976
65c72a4b 1977 if (lseek(iFd, oldPos, SEEK_SET) < 0)
fbb89d94 1978 {
638593bd
DK
1979 FileFdErrno("lseek","Unable to seek in gzipped file");
1980 return 0;
fbb89d94 1981 }
65c72a4b 1982
9c182afa
MP
1983 return size;
1984 }
699b209e 1985#endif
9c182afa 1986
44dc669e 1987 return size;
578bfd0a
AL
1988}
1989 /*}}}*/
8e06abb2 1990// FileFd::Close - Close the file if the close flag is set /*{{{*/
578bfd0a
AL
1991// ---------------------------------------------------------------------
1992/* */
8e06abb2 1993bool FileFd::Close()
578bfd0a 1994{
032bd56f
DK
1995 if (iFd == -1)
1996 return true;
1997
578bfd0a
AL
1998 bool Res = true;
1999 if ((Flags & AutoClose) == AutoClose)
d13c2d3f 2000 {
500400fe
DK
2001 if ((Flags & Compressed) != Compressed && iFd > 0 && close(iFd) != 0)
2002 Res &= _error->Errno("close",_("Problem closing the file %s"), FileName.c_str());
2da8aae5
JAK
2003 }
2004
2005 if (d != NULL)
2006 {
2007 Res &= d->CloseDown(FileName);
2008 delete d;
2009 d = NULL;
d13c2d3f 2010 }
3010fb0e 2011
d3aac32e 2012 if ((Flags & Replace) == Replace) {
3010fb0e 2013 if (rename(TemporaryFileName.c_str(), FileName.c_str()) != 0)
62d073d9
DK
2014 Res &= _error->Errno("rename",_("Problem renaming the file %s to %s"), TemporaryFileName.c_str(), FileName.c_str());
2015
fd3b761e 2016 FileName = TemporaryFileName; // for the unlink() below.
257e8d66 2017 TemporaryFileName.clear();
3010fb0e 2018 }
62d073d9
DK
2019
2020 iFd = -1;
2021
578bfd0a
AL
2022 if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail &&
2023 FileName.empty() == false)
2024 if (unlink(FileName.c_str()) != 0)
62d073d9 2025 Res &= _error->WarningE("unlnk",_("Problem unlinking the file %s"), FileName.c_str());
3010fb0e 2026
fbb89d94
DK
2027 if (Res == false)
2028 Flags |= Fail;
578bfd0a
AL
2029 return Res;
2030}
2031 /*}}}*/
b2e465d6
AL
2032// FileFd::Sync - Sync the file /*{{{*/
2033// ---------------------------------------------------------------------
2034/* */
2035bool FileFd::Sync()
2036{
b2e465d6 2037 if (fsync(iFd) != 0)
ae635e3c
DK
2038 return FileFdErrno("sync",_("Problem syncing the file"));
2039 return true;
2040}
2041 /*}}}*/
2042// FileFd::FileFdErrno - set Fail and call _error->Errno *{{{*/
2043bool FileFd::FileFdErrno(const char *Function, const char *Description,...)
2044{
2045 Flags |= Fail;
2046 va_list args;
2047 size_t msgSize = 400;
2048 int const errsv = errno;
2049 while (true)
fbb89d94 2050 {
ae635e3c
DK
2051 va_start(args,Description);
2052 if (_error->InsertErrno(GlobalError::ERROR, Function, Description, args, errsv, msgSize) == false)
2053 break;
2054 va_end(args);
fbb89d94 2055 }
ae635e3c
DK
2056 return false;
2057}
2058 /*}}}*/
2059// FileFd::FileFdError - set Fail and call _error->Error *{{{*/
2060bool FileFd::FileFdError(const char *Description,...) {
2061 Flags |= Fail;
2062 va_list args;
2063 size_t msgSize = 400;
2064 while (true)
2065 {
2066 va_start(args,Description);
2067 if (_error->Insert(GlobalError::ERROR, Description, args, msgSize) == false)
2068 break;
2069 va_end(args);
2070 }
2071 return false;
b2e465d6
AL
2072}
2073 /*}}}*/
699b209e 2074
7f350a37
DK
2075APT_DEPRECATED gzFile FileFd::gzFd() {
2076#ifdef HAVE_ZLIB
2077 return d->gz;
2078#else
2079 return NULL;
2080#endif
2081}
8d01b9d6 2082
f8aba23f 2083// Glob - wrapper around "glob()" /*{{{*/
8d01b9d6
MV
2084std::vector<std::string> Glob(std::string const &pattern, int flags)
2085{
2086 std::vector<std::string> result;
2087 glob_t globbuf;
ec4835a1
ÁGM
2088 int glob_res;
2089 unsigned int i;
8d01b9d6
MV
2090
2091 glob_res = glob(pattern.c_str(), flags, NULL, &globbuf);
2092
2093 if (glob_res != 0)
2094 {
2095 if(glob_res != GLOB_NOMATCH) {
2096 _error->Errno("glob", "Problem with glob");
2097 return result;
2098 }
2099 }
2100
2101 // append results
2102 for(i=0;i<globbuf.gl_pathc;i++)
2103 result.push_back(string(globbuf.gl_pathv[i]));
2104
2105 globfree(&globbuf);
2106 return result;
2107}
2108 /*}}}*/
f8aba23f 2109std::string GetTempDir() /*{{{*/
68e01721
MV
2110{
2111 const char *tmpdir = getenv("TMPDIR");
2112
2113#ifdef P_tmpdir
2114 if (!tmpdir)
2115 tmpdir = P_tmpdir;
2116#endif
2117
68e01721 2118 struct stat st;
0d303f17 2119 if (!tmpdir || strlen(tmpdir) == 0 || // tmpdir is set
dd6da7d2
DK
2120 stat(tmpdir, &st) != 0 || (st.st_mode & S_IFDIR) == 0) // exists and is directory
2121 tmpdir = "/tmp";
2122 else if (geteuid() != 0 && // root can do everything anyway
2123 faccessat(-1, tmpdir, R_OK | W_OK | X_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW) != 0) // current user has rwx access to directory
68e01721
MV
2124 tmpdir = "/tmp";
2125
2126 return string(tmpdir);
dd6da7d2
DK
2127}
2128std::string GetTempDir(std::string const &User)
2129{
2130 // no need/possibility to drop privs
2131 if(getuid() != 0 || User.empty() || User == "root")
2132 return GetTempDir();
2133
2134 struct passwd const * const pw = getpwnam(User.c_str());
2135 if (pw == NULL)
2136 return GetTempDir();
2137
226c0f64
DK
2138 gid_t const old_euid = geteuid();
2139 gid_t const old_egid = getegid();
dd6da7d2
DK
2140 if (setegid(pw->pw_gid) != 0)
2141 _error->Errno("setegid", "setegid %u failed", pw->pw_gid);
2142 if (seteuid(pw->pw_uid) != 0)
2143 _error->Errno("seteuid", "seteuid %u failed", pw->pw_uid);
2144
2145 std::string const tmp = GetTempDir();
2146
226c0f64
DK
2147 if (seteuid(old_euid) != 0)
2148 _error->Errno("seteuid", "seteuid %u failed", old_euid);
2149 if (setegid(old_egid) != 0)
2150 _error->Errno("setegid", "setegid %u failed", old_egid);
dd6da7d2
DK
2151
2152 return tmp;
68e01721 2153}
f8aba23f 2154 /*}}}*/
c9443c01 2155FileFd* GetTempFile(std::string const &Prefix, bool ImmediateUnlink, FileFd * const TmpFd) /*{{{*/
0d29b9d4
MV
2156{
2157 char fn[512];
c9443c01 2158 FileFd * const Fd = TmpFd == NULL ? new FileFd() : TmpFd;
0d29b9d4 2159
c9443c01
DK
2160 std::string const tempdir = GetTempDir();
2161 snprintf(fn, sizeof(fn), "%s/%s.XXXXXX",
0d29b9d4 2162 tempdir.c_str(), Prefix.c_str());
c9443c01 2163 int const fd = mkstemp(fn);
0d29b9d4
MV
2164 if(ImmediateUnlink)
2165 unlink(fn);
c9443c01 2166 if (fd < 0)
0d29b9d4
MV
2167 {
2168 _error->Errno("GetTempFile",_("Unable to mkstemp %s"), fn);
2169 return NULL;
2170 }
c9443c01 2171 if (!Fd->OpenDescriptor(fd, FileFd::ReadWrite, FileFd::None, true))
0d29b9d4
MV
2172 {
2173 _error->Errno("GetTempFile",_("Unable to write to %s"),fn);
2174 return NULL;
2175 }
0d29b9d4
MV
2176 return Fd;
2177}
f8aba23f
DK
2178 /*}}}*/
2179bool Rename(std::string From, std::string To) /*{{{*/
c1409d1b
MV
2180{
2181 if (rename(From.c_str(),To.c_str()) != 0)
2182 {
2183 _error->Error(_("rename failed, %s (%s -> %s)."),strerror(errno),
2184 From.c_str(),To.c_str());
2185 return false;
f8aba23f 2186 }
c1409d1b
MV
2187 return true;
2188}
f8aba23f
DK
2189 /*}}}*/
2190bool Popen(const char* Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode)/*{{{*/
7ad2a347
MV
2191{
2192 int fd;
2193 if (Mode != FileFd::ReadOnly && Mode != FileFd::WriteOnly)
2194 return _error->Error("Popen supports ReadOnly (x)or WriteOnly mode only");
2195
2196 int Pipe[2] = {-1, -1};
2197 if(pipe(Pipe) != 0)
7ad2a347 2198 return _error->Errno("pipe", _("Failed to create subprocess IPC"));
5e49cbb7 2199
7ad2a347
MV
2200 std::set<int> keep_fds;
2201 keep_fds.insert(Pipe[0]);
2202 keep_fds.insert(Pipe[1]);
2203 Child = ExecFork(keep_fds);
2204 if(Child < 0)
2205 return _error->Errno("fork", "Failed to fork");
2206 if(Child == 0)
2207 {
2208 if(Mode == FileFd::ReadOnly)
2209 {
2210 close(Pipe[0]);
2211 fd = Pipe[1];
2212 }
2213 else if(Mode == FileFd::WriteOnly)
2214 {
2215 close(Pipe[1]);
2216 fd = Pipe[0];
2217 }
2218
2219 if(Mode == FileFd::ReadOnly)
2220 {
2221 dup2(fd, 1);
2222 dup2(fd, 2);
2223 } else if(Mode == FileFd::WriteOnly)
2224 dup2(fd, 0);
2225
2226 execv(Args[0], (char**)Args);
2227 _exit(100);
2228 }
2229 if(Mode == FileFd::ReadOnly)
2230 {
2231 close(Pipe[1]);
2232 fd = Pipe[0];
2233 } else if(Mode == FileFd::WriteOnly)
2234 {
2235 close(Pipe[0]);
2236 fd = Pipe[1];
2237 }
2238 Fd.OpenDescriptor(fd, Mode, FileFd::None, true);
2239
2240 return true;
2241}
f8aba23f
DK
2242 /*}}}*/
2243bool DropPrivileges() /*{{{*/
fc1a78d8 2244{
8f45798d
DK
2245 if(_config->FindB("Debug::NoDropPrivs", false) == true)
2246 return true;
2247
2248#if __gnu_linux__
2249#if defined(PR_SET_NO_NEW_PRIVS) && ( PR_SET_NO_NEW_PRIVS != 38 )
2250#error "PR_SET_NO_NEW_PRIVS is defined, but with a different value than expected!"
2251#endif
2252 // see prctl(2), needs linux3.5 at runtime - magic constant to avoid it at buildtime
2253 int ret = prctl(38, 1, 0, 0, 0);
2254 // ignore EINVAL - kernel is too old to understand the option
2255 if(ret < 0 && errno != EINVAL)
2256 _error->Warning("PR_SET_NO_NEW_PRIVS failed with %i", ret);
2257#endif
2258
990dd78a
DK
2259 // empty setting disables privilege dropping - this also ensures
2260 // backward compatibility, see bug #764506
2261 const std::string toUser = _config->Find("APT::Sandbox::User");
2262 if (toUser.empty())
2263 return true;
2264
f1e3c8f0 2265 // uid will be 0 in the end, but gid might be different anyway
8f45798d
DK
2266 uid_t const old_uid = getuid();
2267 gid_t const old_gid = getgid();
fc1a78d8 2268
5f2047ec
JAK
2269 if (old_uid != 0)
2270 return true;
3927c6da 2271
b8dae9a1 2272 struct passwd *pw = getpwnam(toUser.c_str());
fc1a78d8 2273 if (pw == NULL)
b8dae9a1 2274 return _error->Error("No user %s, can not drop rights", toUser.c_str());
3927c6da 2275
f1e3c8f0 2276 // Do not change the order here, it might break things
5a326439 2277 // Get rid of all our supplementary groups first
3b084f06 2278 if (setgroups(1, &pw->pw_gid))
3927c6da
MV
2279 return _error->Errno("setgroups", "Failed to setgroups");
2280
5a326439
JAK
2281 // Now change the group ids to the new user
2282#ifdef HAVE_SETRESGID
2283 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0)
2284 return _error->Errno("setresgid", "Failed to set new group ids");
2285#else
3927c6da 2286 if (setegid(pw->pw_gid) != 0)
5f2047ec
JAK
2287 return _error->Errno("setegid", "Failed to setegid");
2288
fc1a78d8
MV
2289 if (setgid(pw->pw_gid) != 0)
2290 return _error->Errno("setgid", "Failed to setgid");
5a326439 2291#endif
5f2047ec 2292
5a326439
JAK
2293 // Change the user ids to the new user
2294#ifdef HAVE_SETRESUID
2295 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
2296 return _error->Errno("setresuid", "Failed to set new user ids");
2297#else
fc1a78d8
MV
2298 if (setuid(pw->pw_uid) != 0)
2299 return _error->Errno("setuid", "Failed to setuid");
5f2047ec
JAK
2300 if (seteuid(pw->pw_uid) != 0)
2301 return _error->Errno("seteuid", "Failed to seteuid");
5a326439 2302#endif
5f2047ec 2303
f1e3c8f0 2304 // Verify that the user has only a single group, and the correct one
5f2047ec
JAK
2305 gid_t groups[1];
2306 if (getgroups(1, groups) != 1)
2307 return _error->Errno("getgroups", "Could not get new groups");
2308 if (groups[0] != pw->pw_gid)
2309 return _error->Error("Could not switch group");
f1e3c8f0
JAK
2310
2311 // Verify that gid, egid, uid, and euid changed
5f2047ec
JAK
2312 if (getgid() != pw->pw_gid)
2313 return _error->Error("Could not switch group");
2314 if (getegid() != pw->pw_gid)
2315 return _error->Error("Could not switch effective group");
2316 if (getuid() != pw->pw_uid)
2317 return _error->Error("Could not switch user");
2318 if (geteuid() != pw->pw_uid)
2319 return _error->Error("Could not switch effective user");
2320
550ab420 2321#ifdef HAVE_GETRESUID
48ed0977 2322 // verify that the saved set-user-id was changed as well
550ab420
JAK
2323 uid_t ruid = 0;
2324 uid_t euid = 0;
2325 uid_t suid = 0;
2326 if (getresuid(&ruid, &euid, &suid))
2327 return _error->Errno("getresuid", "Could not get saved set-user-ID");
2328 if (suid != pw->pw_uid)
2329 return _error->Error("Could not switch saved set-user-ID");
2330#endif
2331
2332#ifdef HAVE_GETRESGID
48ed0977 2333 // verify that the saved set-group-id was changed as well
550ab420
JAK
2334 gid_t rgid = 0;
2335 gid_t egid = 0;
2336 gid_t sgid = 0;
2337 if (getresgid(&rgid, &egid, &sgid))
2338 return _error->Errno("getresuid", "Could not get saved set-group-ID");
2339 if (sgid != pw->pw_gid)
2340 return _error->Error("Could not switch saved set-group-ID");
2341#endif
2342
bdc00df5
JAK
2343 // Check that uid and gid changes do not work anymore
2344 if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1))
2345 return _error->Error("Could restore a gid to root, privilege dropping did not work");
2346
2347 if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1))
2348 return _error->Error("Could restore a uid to root, privilege dropping did not work");
2349
fc1a78d8
MV
2350 return true;
2351}
f8aba23f 2352 /*}}}*/