]> git.saurik.com Git - apt.git/blame - apt-pkg/tagfile.cc
fileutl: empty file support: Avoid fstat() on -1 fd and check result
[apt.git] / apt-pkg / tagfile.cc
CommitLineData
578bfd0a
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
b3d44315 3// $Id: tagfile.cc,v 1.37.2.2 2003/12/31 16:02:30 mdz Exp $
578bfd0a
AL
4/* ######################################################################
5
6 Fast scanner for RFC-822 type header information
7
ad00ae81 8 This uses a rotating buffer to load the package information into.
578bfd0a
AL
9 The scanner runs over it and isolates and indexes a single section.
10
11 ##################################################################### */
12 /*}}}*/
13// Include Files /*{{{*/
ea542140
DK
14#include<config.h>
15
094a497d
AL
16#include <apt-pkg/tagfile.h>
17#include <apt-pkg/error.h>
cdcc6d34 18#include <apt-pkg/strutl.h>
472ff00e 19#include <apt-pkg/fileutl.h>
eff0c22e 20#include <apt-pkg/string_view.h>
578bfd0a 21
55153bf9
DK
22#include <list>
23
578bfd0a
AL
24#include <string>
25#include <stdio.h>
851a45a8 26#include <ctype.h>
453b82a3
DK
27#include <stdlib.h>
28#include <string.h>
ea542140
DK
29
30#include <apti18n.h>
578bfd0a
AL
31 /*}}}*/
32
851a45a8 33using std::string;
eff0c22e 34using APT::StringView;
851a45a8 35
55153bf9 36class APT_HIDDEN pkgTagFilePrivate /*{{{*/
1abbce9e
MV
37{
38public:
55153bf9 39 void Reset(FileFd * const pFd, unsigned long long const pSize, pkgTagFile::Flags const pFlags)
1abbce9e 40 {
3d8232bf
DK
41 if (Buffer != NULL)
42 free(Buffer);
6c55f07a 43 Buffer = NULL;
3d8232bf 44 Fd = pFd;
55153bf9 45 Flags = pFlags;
6c55f07a
DK
46 Start = NULL;
47 End = NULL;
48 Done = false;
49 iOffset = 0;
50 Size = pSize;
55153bf9
DK
51 isCommentedLine = false;
52 chunks.clear();
1abbce9e 53 }
6c55f07a 54
55153bf9 55 pkgTagFilePrivate(FileFd * const pFd, unsigned long long const Size, pkgTagFile::Flags const pFlags) : Buffer(NULL)
6c55f07a 56 {
55153bf9 57 Reset(pFd, Size, pFlags);
6c55f07a
DK
58 }
59 FileFd * Fd;
55153bf9 60 pkgTagFile::Flags Flags;
1abbce9e
MV
61 char *Buffer;
62 char *Start;
63 char *End;
64 bool Done;
650faab0
DK
65 unsigned long long iOffset;
66 unsigned long long Size;
55153bf9
DK
67 bool isCommentedLine;
68 struct FileChunk
69 {
70 bool const good;
71 size_t length;
72 FileChunk(bool const pgood, size_t const plength) : good(pgood), length(plength) {}
73 };
74 std::list<FileChunk> chunks;
3d8232bf
DK
75
76 ~pkgTagFilePrivate()
77 {
78 if (Buffer != NULL)
79 free(Buffer);
80 }
1abbce9e 81};
55153bf9
DK
82 /*}}}*/
83class APT_HIDDEN pkgTagSectionPrivate /*{{{*/
bc4ccfeb
DK
84{
85public:
86 pkgTagSectionPrivate()
87 {
88 }
89 struct TagData {
90 unsigned int StartTag;
91 unsigned int EndTag;
92 unsigned int StartValue;
93 unsigned int NextInBucket;
94
e8afd168 95 explicit TagData(unsigned int const StartTag) : StartTag(StartTag), EndTag(0), StartValue(0), NextInBucket(0) {}
bc4ccfeb
DK
96 };
97 std::vector<TagData> Tags;
98};
55153bf9 99 /*}}}*/
bc4ccfeb 100
8710a36a
DK
101static unsigned long AlphaHash(const char *Text, size_t Length) /*{{{*/
102{
103 /* This very simple hash function for the last 8 letters gives
104 very good performance on the debian package files */
105 if (Length > 8)
106 {
107 Text += (Length - 8);
108 Length = 8;
109 }
110 unsigned long Res = 0;
111 for (size_t i = 0; i < Length; ++i)
112 Res = ((unsigned long)(Text[i]) & 0xDF) ^ (Res << 1);
113 return Res & 0xFF;
114}
115 /*}}}*/
116
578bfd0a 117// TagFile::pkgTagFile - Constructor /*{{{*/
55153bf9
DK
118pkgTagFile::pkgTagFile(FileFd * const pFd,pkgTagFile::Flags const pFlags, unsigned long long const Size)
119 : d(new pkgTagFilePrivate(pFd, Size + 4, pFlags))
120{
121 Init(pFd, pFlags, Size);
122}
6c55f07a 123pkgTagFile::pkgTagFile(FileFd * const pFd,unsigned long long const Size)
55153bf9 124 : pkgTagFile(pFd, pkgTagFile::STRICT, Size)
feab34c5 125{
feab34c5 126}
55153bf9 127void pkgTagFile::Init(FileFd * const pFd, pkgTagFile::Flags const pFlags, unsigned long long Size)
578bfd0a 128{
0aae6d14
DK
129 /* The size is increased by 4 because if we start with the Size of the
130 filename we need to try to read 1 char more to see an EOF faster, 1
131 char the end-pointer can be on and maybe 2 newlines need to be added
132 to the end of the file -> 4 extra chars */
133 Size += 4;
55153bf9 134 d->Reset(pFd, Size, pFlags);
1abbce9e 135
6c55f07a 136 if (d->Fd->IsOpen() == false)
1abbce9e 137 d->Start = d->End = d->Buffer = 0;
4b2803b8
DK
138 else
139 d->Buffer = (char*)malloc(sizeof(char) * Size);
140
141 if (d->Buffer == NULL)
1abbce9e 142 d->Done = true;
4b2803b8
DK
143 else
144 d->Done = false;
145
1abbce9e 146 d->Start = d->End = d->Buffer;
1abbce9e 147 d->iOffset = 0;
4b2803b8
DK
148 if (d->Done == false)
149 Fill();
55153bf9
DK
150}
151void pkgTagFile::Init(FileFd * const pFd,unsigned long long Size)
152{
153 Init(pFd, pkgTagFile::STRICT, Size);
578bfd0a
AL
154}
155 /*}}}*/
b2e465d6 156// TagFile::~pkgTagFile - Destructor /*{{{*/
29f7b36c
AL
157pkgTagFile::~pkgTagFile()
158{
1abbce9e
MV
159 delete d;
160}
161 /*}}}*/
4b2803b8 162// TagFile::Offset - Return the current offset in the buffer /*{{{*/
a02db58f 163APT_PURE unsigned long pkgTagFile::Offset()
1abbce9e
MV
164{
165 return d->iOffset;
29f7b36c
AL
166}
167 /*}}}*/
75c541fd 168// TagFile::Resize - Resize the internal buffer /*{{{*/
578bfd0a 169// ---------------------------------------------------------------------
75c541fd
MV
170/* Resize the internal buffer (double it in size). Fail if a maximum size
171 * size is reached.
172 */
173bool pkgTagFile::Resize()
578bfd0a 174{
75c541fd 175 // fail is the buffer grows too big
1abbce9e 176 if(d->Size > 1024*1024+1)
432b168c
MV
177 return false;
178
0aae6d14
DK
179 return Resize(d->Size * 2);
180}
181bool pkgTagFile::Resize(unsigned long long const newSize)
182{
183 unsigned long long const EndSize = d->End - d->Start;
0aae6d14 184
75c541fd 185 // get new buffer and use it
55153bf9 186 char* const newBuffer = static_cast<char*>(realloc(d->Buffer, sizeof(char) * newSize));
4b2803b8
DK
187 if (newBuffer == NULL)
188 return false;
189 d->Buffer = newBuffer;
0aae6d14 190 d->Size = newSize;
99c2e5ac 191
75c541fd 192 // update the start/end pointers to the new buffer
1abbce9e
MV
193 d->Start = d->Buffer;
194 d->End = d->Start + EndSize;
75c541fd
MV
195 return true;
196}
81e9789b 197 /*}}}*/
578bfd0a
AL
198// TagFile::Step - Advance to the next section /*{{{*/
199// ---------------------------------------------------------------------
75c541fd
MV
200/* If the Section Scanner fails we refill the buffer and try again.
201 * If that fails too, double the buffer size and try again until a
202 * maximum buffer is reached.
203 */
578bfd0a
AL
204bool pkgTagFile::Step(pkgTagSection &Tag)
205{
8710a36a 206 if(Tag.Scan(d->Start,d->End - d->Start) == false)
0852eaef 207 {
8710a36a
DK
208 do
209 {
210 if (Fill() == false)
211 return false;
212
213 if(Tag.Scan(d->Start,d->End - d->Start, false))
214 break;
75c541fd 215
8710a36a 216 if (Resize() == false)
2f6a2fbb 217 return _error->Error(_("Unable to parse package file %s (%d)"),
6c55f07a 218 d->Fd->Name().c_str(), 1);
8710a36a
DK
219
220 } while (Tag.Scan(d->Start,d->End - d->Start, false) == false);
613f9499 221 }
8710a36a 222
55153bf9
DK
223 size_t tagSize = Tag.size();
224 d->Start += tagSize;
225
226 if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0)
227 d->iOffset += tagSize;
228 else
229 {
230 auto first = d->chunks.begin();
231 for (; first != d->chunks.end(); ++first)
232 {
233 if (first->good == false)
234 d->iOffset += first->length;
235 else
236 {
237 if (tagSize < first->length)
238 {
239 first->length -= tagSize;
240 d->iOffset += tagSize;
241 break;
242 }
243 else
244 {
245 tagSize -= first->length;
246 d->iOffset += first->length;
247 }
248 }
249 }
250 d->chunks.erase(d->chunks.begin(), first);
251 }
b2e465d6
AL
252
253 Tag.Trim();
99c2e5ac
MV
254 return true;
255}
256 /*}}}*/
257// TagFile::Fill - Top up the buffer /*{{{*/
258// ---------------------------------------------------------------------
259/* This takes the bit at the end of the buffer and puts it at the start
260 then fills the rest from the file */
55153bf9
DK
261static bool FillBuffer(pkgTagFilePrivate * const d)
262{
263 unsigned long long Actual = 0;
264 // See if only a bit of the file is left
265 unsigned long long const dataSize = d->Size - ((d->End - d->Buffer) + 1);
266 if (d->Fd->Read(d->End, dataSize, &Actual) == false)
267 return false;
268 if (Actual != dataSize)
269 d->Done = true;
270 d->End += Actual;
271 return true;
272}
273static void RemoveCommentsFromBuffer(pkgTagFilePrivate * const d)
274{
275 // look for valid comments in the buffer
276 char * good_start = nullptr, * bad_start = nullptr;
277 char * current = d->Start;
278 if (d->isCommentedLine == false)
279 {
280 if (d->Start == d->Buffer)
281 {
282 // the start of the buffer is a newline as a record can't start
283 // in the middle of a line by definition.
284 if (*d->Start == '#')
285 {
286 d->isCommentedLine = true;
287 ++current;
288 if (current > d->End)
289 d->chunks.emplace_back(false, 1);
290 }
291 }
292 if (d->isCommentedLine == false)
293 good_start = d->Start;
294 else
295 bad_start = d->Start;
296 }
297 else
298 bad_start = d->Start;
299
300 std::vector<std::pair<char*, size_t>> good_parts;
301 while (current <= d->End)
302 {
303 size_t const restLength = (d->End - current) + 1;
304 if (d->isCommentedLine == false)
305 {
306 current = static_cast<char*>(memchr(current, '#', restLength));
307 if (current == nullptr)
308 {
309 size_t const goodLength = d->End - good_start;
310 d->chunks.emplace_back(true, goodLength);
311 if (good_start != d->Start)
312 good_parts.push_back(std::make_pair(good_start, goodLength));
313 break;
314 }
315 bad_start = current;
316 --current;
317 // ensure that this is really a comment and not a '#' in the middle of a line
318 if (*current == '\n')
319 {
320 size_t const goodLength = (current - good_start) + 1;
321 d->chunks.emplace_back(true, goodLength);
322 good_parts.push_back(std::make_pair(good_start, goodLength));
323 good_start = nullptr;
324 d->isCommentedLine = true;
325 }
326 current += 2;
327 }
328 else // the current line is a comment
329 {
330 current = static_cast<char*>(memchr(current, '\n', restLength));
331 if (current == nullptr)
332 {
333 d->chunks.emplace_back(false, (d->End - bad_start));
334 break;
335 }
336 ++current;
337 // is the next line a comment, too?
338 if (current > d->End || *current != '#')
339 {
340 d->chunks.emplace_back(false, (current - bad_start));
341 good_start = current;
342 bad_start = nullptr;
343 d->isCommentedLine = false;
344 }
345 ++current;
346 }
347 }
348
349 if (good_parts.empty() == false)
350 {
351 // we found comments, so move later parts over them
352 current = d->Start;
353 for (auto const &good: good_parts)
354 {
355 memmove(current, good.first, good.second);
356 current += good.second;
357 }
358 d->End = current;
359 }
360
361 if (d->isCommentedLine == true)
362 {
363 // deal with a buffer containing only comments
364 // or an (unfinished) comment at the end
365 if (good_parts.empty() == true)
366 d->End = d->Start;
367 else
368 d->Start = d->End;
369 }
370 else
371 {
372 // the buffer was all comment, but ended with the buffer
373 if (good_parts.empty() == true && good_start >= d->End)
374 d->End = d->Start;
375 else
376 d->Start = d->End;
377 }
378}
99c2e5ac
MV
379bool pkgTagFile::Fill()
380{
55153bf9
DK
381 unsigned long long const EndSize = d->End - d->Start;
382 if (EndSize != 0)
383 {
384 memmove(d->Buffer,d->Start,EndSize);
385 d->Start = d->End = d->Buffer + EndSize;
386 }
387 else
388 d->Start = d->End = d->Buffer;
389
650faab0 390 unsigned long long Actual = 0;
55153bf9 391 while (d->Done == false && d->Size > (Actual + 1))
99c2e5ac 392 {
55153bf9 393 if (FillBuffer(d) == false)
99c2e5ac 394 return false;
55153bf9
DK
395 if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) != 0)
396 RemoveCommentsFromBuffer(d);
397 Actual = d->End - d->Buffer;
99c2e5ac 398 }
55153bf9
DK
399 d->Start = d->Buffer;
400
1abbce9e 401 if (d->Done == true)
99c2e5ac
MV
402 {
403 if (EndSize <= 3 && Actual == 0)
404 return false;
1abbce9e 405 if (d->Size - (d->End - d->Buffer) < 4)
99c2e5ac 406 return true;
55153bf9 407
99c2e5ac
MV
408 // Append a double new line if one does not exist
409 unsigned int LineCount = 0;
1abbce9e 410 for (const char *E = d->End - 1; E - d->End < 6 && (*E == '\n' || *E == '\r'); E--)
99c2e5ac 411 if (*E == '\n')
55153bf9 412 ++LineCount;
0aae6d14
DK
413 if (LineCount < 2)
414 {
55153bf9 415 if (static_cast<unsigned long long>(d->End - d->Buffer) >= d->Size)
0aae6d14 416 Resize(d->Size + 3);
55153bf9 417 for (; LineCount < 2; ++LineCount)
0aae6d14
DK
418 *d->End++ = '\n';
419 }
99c2e5ac 420 }
578bfd0a
AL
421 return true;
422}
423 /*}}}*/
ad00ae81
AL
424// TagFile::Jump - Jump to a pre-recorded location in the file /*{{{*/
425// ---------------------------------------------------------------------
03e39e59
AL
426/* This jumps to a pre-recorded file location and reads the record
427 that is there */
650faab0 428bool pkgTagFile::Jump(pkgTagSection &Tag,unsigned long long Offset)
ad00ae81 429{
55153bf9 430 if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0 &&
b2e465d6 431 // We are within a buffer space of the next hit..
55153bf9 432 Offset >= d->iOffset && d->iOffset + (d->End - d->Start) > Offset)
b2e465d6 433 {
650faab0 434 unsigned long long Dist = Offset - d->iOffset;
1abbce9e
MV
435 d->Start += Dist;
436 d->iOffset += Dist;
e62aa1dd
DK
437 // if we have seen the end, don't ask for more
438 if (d->Done == true)
439 return Tag.Scan(d->Start, d->End - d->Start);
440 else
441 return Step(Tag);
b2e465d6
AL
442 }
443
2ca99a0d 444 // Reposition and reload..
1abbce9e
MV
445 d->iOffset = Offset;
446 d->Done = false;
6c55f07a 447 if (d->Fd->Seek(Offset) == false)
2ca99a0d 448 return false;
1abbce9e 449 d->End = d->Start = d->Buffer;
55153bf9
DK
450 d->isCommentedLine = false;
451 d->chunks.clear();
452
2ca99a0d
MV
453 if (Fill() == false)
454 return false;
99c2e5ac 455
1abbce9e 456 if (Tag.Scan(d->Start, d->End - d->Start) == true)
2ca99a0d
MV
457 return true;
458
459 // This appends a double new line (for the real eof handling)
460 if (Fill() == false)
461 return false;
0852eaef 462
8710a36a 463 if (Tag.Scan(d->Start, d->End - d->Start, false) == false)
6c55f07a 464 return _error->Error(_("Unable to parse package file %s (%d)"),d->Fd->Name().c_str(), 2);
06bba740 465
ad00ae81
AL
466 return true;
467}
468 /*}}}*/
b40394c0
MV
469// pkgTagSection::pkgTagSection - Constructor /*{{{*/
470// ---------------------------------------------------------------------
471/* */
bc4ccfeb 472APT_IGNORE_DEPRECATED_PUSH
b40394c0 473pkgTagSection::pkgTagSection()
6c55f07a 474 : Section(0), d(new pkgTagSectionPrivate()), Stop(0)
b40394c0 475{
bc4ccfeb 476 memset(&AlphaIndexes, 0, sizeof(AlphaIndexes));
b40394c0 477}
bc4ccfeb 478APT_IGNORE_DEPRECATED_POP
b40394c0 479 /*}}}*/
578bfd0a 480// TagSection::Scan - Scan for the end of the header information /*{{{*/
8710a36a 481bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength, bool const Restart)
578bfd0a 482{
8710a36a 483 Section = Start;
578bfd0a 484 const char *End = Start + MaxLength;
8710a36a 485
bc4ccfeb 486 if (Restart == false && d->Tags.empty() == false)
8710a36a 487 {
bc4ccfeb 488 Stop = Section + d->Tags.back().StartTag;
8710a36a
DK
489 if (End <= Stop)
490 return false;
491 Stop = (const char *)memchr(Stop,'\n',End - Stop);
492 if (Stop == NULL)
493 return false;
494 ++Stop;
495 }
496 else
497 {
498 Stop = Section;
bc4ccfeb 499 if (d->Tags.empty() == false)
8710a36a 500 {
bc4ccfeb
DK
501 memset(&AlphaIndexes, 0, sizeof(AlphaIndexes));
502 d->Tags.clear();
8710a36a 503 }
bc4ccfeb 504 d->Tags.reserve(0x100);
8710a36a 505 }
bc4ccfeb 506 unsigned int TagCount = d->Tags.size();
c7b5ce1c 507
2ca99a0d 508 if (Stop == 0)
0852eaef 509 return false;
81e9789b 510
bc4ccfeb 511 pkgTagSectionPrivate::TagData lastTagData(0);
8710a36a
DK
512 lastTagData.EndTag = 0;
513 unsigned long lastTagHash = 0;
514 while (Stop < End)
578bfd0a 515 {
75ab11ae
MV
516 TrimRecord(true,End);
517
518 // this can happen when TrimRecord trims away the entire Record
519 // (e.g. because it just contains comments)
520 if(Stop == End)
521 return true;
81e9789b 522
90d64280 523 // Start a new index and add it to the hash
74dedb4a 524 if (isspace_ascii(Stop[0]) == 0)
c1a22377 525 {
8710a36a
DK
526 // store the last found tag
527 if (lastTagData.EndTag != 0)
528 {
bc4ccfeb
DK
529 if (AlphaIndexes[lastTagHash] != 0)
530 lastTagData.NextInBucket = AlphaIndexes[lastTagHash];
531 APT_IGNORE_DEPRECATED_PUSH
532 AlphaIndexes[lastTagHash] = TagCount;
bc4ccfeb
DK
533 APT_IGNORE_DEPRECATED_POP
534 d->Tags.push_back(lastTagData);
8710a36a
DK
535 }
536
bc4ccfeb
DK
537 APT_IGNORE_DEPRECATED(++TagCount;)
538 lastTagData = pkgTagSectionPrivate::TagData(Stop - Section);
8710a36a
DK
539 // find the colon separating tag and value
540 char const * Colon = (char const *) memchr(Stop, ':', End - Stop);
541 if (Colon == NULL)
542 return false;
543 // find the end of the tag (which might or might not be the colon)
544 char const * EndTag = Colon;
545 --EndTag;
74dedb4a 546 for (; EndTag > Stop && isspace_ascii(*EndTag) != 0; --EndTag)
8710a36a
DK
547 ;
548 ++EndTag;
549 lastTagData.EndTag = EndTag - Section;
550 lastTagHash = AlphaHash(Stop, EndTag - Stop);
551 // find the beginning of the value
552 Stop = Colon + 1;
67c90775 553 for (; Stop < End && isspace_ascii(*Stop) != 0; ++Stop)
c72f5c4f
DK
554 if (*Stop == '\n' && Stop[1] != ' ')
555 break;
8710a36a
DK
556 if (Stop >= End)
557 return false;
558 lastTagData.StartValue = Stop - Section;
c1a22377 559 }
0a8e3465 560
c1a22377 561 Stop = (const char *)memchr(Stop,'\n',End - Stop);
8710a36a 562
c1a22377 563 if (Stop == 0)
0852eaef 564 return false;
81e9789b 565
75ab11ae
MV
566 for (; Stop+1 < End && Stop[1] == '\r'; Stop++)
567 /* nothing */
568 ;
c1a22377 569
f3bcc383
AL
570 // Double newline marks the end of the record
571 if (Stop+1 < End && Stop[1] == '\n')
578bfd0a 572 {
8710a36a
DK
573 if (lastTagData.EndTag != 0)
574 {
bc4ccfeb
DK
575 if (AlphaIndexes[lastTagHash] != 0)
576 lastTagData.NextInBucket = AlphaIndexes[lastTagHash];
577 APT_IGNORE_DEPRECATED(AlphaIndexes[lastTagHash] = TagCount;)
bc4ccfeb 578 d->Tags.push_back(lastTagData);
8710a36a
DK
579 }
580
bc4ccfeb 581 pkgTagSectionPrivate::TagData const td(Stop - Section);
bc4ccfeb 582 d->Tags.push_back(td);
81e9789b 583 TrimRecord(false,End);
0852eaef 584 return true;
578bfd0a
AL
585 }
586
c1a22377
AL
587 Stop++;
588 }
138d4b3d 589
0852eaef 590 return false;
578bfd0a
AL
591}
592 /*}}}*/
81e9789b
MV
593// TagSection::TrimRecord - Trim off any garbage before/after a record /*{{{*/
594// ---------------------------------------------------------------------
595/* There should be exactly 2 newline at the end of the record, no more. */
596void pkgTagSection::TrimRecord(bool BeforeRecord, const char*& End)
597{
598 if (BeforeRecord == true)
599 return;
600 for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r'); Stop++);
601}
602 /*}}}*/
b2e465d6
AL
603// TagSection::Trim - Trim off any trailing garbage /*{{{*/
604// ---------------------------------------------------------------------
605/* There should be exactly 1 newline at the end of the buffer, no more. */
606void pkgTagSection::Trim()
607{
608 for (; Stop > Section + 2 && (Stop[-2] == '\n' || Stop[-2] == '\r'); Stop--);
609}
610 /*}}}*/
8710a36a 611// TagSection::Exists - return True if a tag exists /*{{{*/
eff0c22e 612bool pkgTagSection::Exists(StringView Tag) const
c8b860fb
MV
613{
614 unsigned int tmp;
615 return Find(Tag, tmp);
616}
617 /*}}}*/
578bfd0a
AL
618// TagSection::Find - Locate a tag /*{{{*/
619// ---------------------------------------------------------------------
620/* This searches the section for a tag that matches the given string. */
eff0c22e 621bool pkgTagSection::Find(StringView TagView,unsigned int &Pos) const
578bfd0a 622{
eff0c22e
JAK
623 const char * const Tag = TagView.data();
624 size_t const Length = TagView.length();
bc4ccfeb 625 unsigned int Bucket = AlphaIndexes[AlphaHash(Tag, Length)];
8710a36a 626 if (Bucket == 0)
c1a22377 627 return false;
8710a36a 628
bc4ccfeb 629 for (; Bucket != 0; Bucket = d->Tags[Bucket - 1].NextInBucket)
578bfd0a 630 {
bc4ccfeb 631 if ((d->Tags[Bucket - 1].EndTag - d->Tags[Bucket - 1].StartTag) != Length)
578bfd0a
AL
632 continue;
633
bc4ccfeb 634 char const * const St = Section + d->Tags[Bucket - 1].StartTag;
8710a36a 635 if (strncasecmp(Tag,St,Length) != 0)
b2e465d6 636 continue;
8710a36a
DK
637
638 Pos = Bucket - 1;
b2e465d6
AL
639 return true;
640 }
641
642 Pos = 0;
643 return false;
644}
eff0c22e
JAK
645
646bool pkgTagSection::Find(StringView Tag,const char *&Start,
b2e465d6
AL
647 const char *&End) const
648{
8710a36a
DK
649 unsigned int Pos;
650 if (Find(Tag, Pos) == false)
b2e465d6 651 return false;
578bfd0a 652
bc4ccfeb 653 Start = Section + d->Tags[Pos].StartValue;
8710a36a 654 // Strip off the gunk from the end
bc4ccfeb 655 End = Section + d->Tags[Pos + 1].StartTag;
8710a36a
DK
656 if (unlikely(Start > End))
657 return _error->Error("Internal parsing error");
658
74dedb4a 659 for (; isspace_ascii(End[-1]) != 0 && End > Start; --End);
8710a36a
DK
660
661 return true;
578bfd0a
AL
662}
663 /*}}}*/
0e66b144 664// TagSection::FindS - Find a string /*{{{*/
eff0c22e 665StringView pkgTagSection::Find(StringView Tag) const
a05599f1
AL
666{
667 const char *Start;
668 const char *End;
669 if (Find(Tag,Start,End) == false)
eff0c22e
JAK
670 return StringView();
671 return StringView(Start, End - Start);
a05599f1
AL
672}
673 /*}}}*/
8d058ea5 674// TagSection::FindRawS - Find a string /*{{{*/
eff0c22e 675StringView pkgTagSection::FindRaw(StringView Tag) const
8d058ea5
DK
676{
677 unsigned int Pos;
678 if (Find(Tag, Pos) == false)
679 return "";
680
681 char const *Start = (char const *) memchr(Section + d->Tags[Pos].EndTag, ':', d->Tags[Pos].StartValue - d->Tags[Pos].EndTag);
682 ++Start;
683 char const *End = Section + d->Tags[Pos + 1].StartTag;
684 if (unlikely(Start > End))
685 return "";
686
74dedb4a 687 for (; isspace_ascii(End[-1]) != 0 && End > Start; --End);
8d058ea5 688
eff0c22e 689 return StringView(Start, End - Start);
8d058ea5
DK
690}
691 /*}}}*/
a05599f1
AL
692// TagSection::FindI - Find an integer /*{{{*/
693// ---------------------------------------------------------------------
694/* */
eff0c22e 695signed int pkgTagSection::FindI(StringView Tag,signed long Default) const
a05599f1
AL
696{
697 const char *Start;
b0b4efb9
AL
698 const char *Stop;
699 if (Find(Tag,Start,Stop) == false)
700 return Default;
701
702 // Copy it into a temp buffer so we can use strtol
703 char S[300];
704 if ((unsigned)(Stop - Start) >= sizeof(S))
705 return Default;
706 strncpy(S,Start,Stop-Start);
707 S[Stop - Start] = 0;
809aa216
JAK
708
709 errno = 0;
b0b4efb9
AL
710 char *End;
711 signed long Result = strtol(S,&End,10);
137e8ad4
JAK
712 if (errno == ERANGE ||
713 Result < std::numeric_limits<int>::min() || Result > std::numeric_limits<int>::max()) {
809aa216 714 errno = ERANGE;
137e8ad4 715 _error->Error(_("Cannot convert %s to integer: out of range"), S);
809aa216 716 }
b0b4efb9
AL
717 if (S == End)
718 return Default;
719 return Result;
720}
721 /*}}}*/
e2c66de5
DK
722// TagSection::FindULL - Find an unsigned long long integer /*{{{*/
723// ---------------------------------------------------------------------
724/* */
eff0c22e 725unsigned long long pkgTagSection::FindULL(StringView Tag, unsigned long long const &Default) const
e2c66de5
DK
726{
727 const char *Start;
728 const char *Stop;
729 if (Find(Tag,Start,Stop) == false)
730 return Default;
731
732 // Copy it into a temp buffer so we can use strtoull
733 char S[100];
734 if ((unsigned)(Stop - Start) >= sizeof(S))
735 return Default;
736 strncpy(S,Start,Stop-Start);
737 S[Stop - Start] = 0;
738
739 char *End;
740 unsigned long long Result = strtoull(S,&End,10);
741 if (S == End)
742 return Default;
743 return Result;
744}
745 /*}}}*/
a2fdb57f
MV
746// TagSection::FindB - Find boolean value /*{{{*/
747// ---------------------------------------------------------------------
748/* */
eff0c22e 749bool pkgTagSection::FindB(StringView Tag, bool Default) const
a2fdb57f
MV
750{
751 const char *Start, *Stop;
752 if (Find(Tag, Start, Stop) == false)
753 return Default;
754 return StringToBool(string(Start, Stop));
755}
756 /*}}}*/
b0b4efb9
AL
757// TagSection::FindFlag - Locate a yes/no type flag /*{{{*/
758// ---------------------------------------------------------------------
759/* The bits marked in Flag are masked on/off in Flags */
eff0c22e 760bool pkgTagSection::FindFlag(StringView Tag, uint8_t &Flags,
dfe66c72
DK
761 uint8_t const Flag) const
762{
763 const char *Start;
764 const char *Stop;
765 if (Find(Tag,Start,Stop) == false)
766 return true;
767 return FindFlag(Flags, Flag, Start, Stop);
768}
769bool pkgTagSection::FindFlag(uint8_t &Flags, uint8_t const Flag,
770 char const* const Start, char const* const Stop)
771{
772 switch (StringToBool(string(Start, Stop)))
773 {
774 case 0:
775 Flags &= ~Flag;
776 return true;
777
778 case 1:
779 Flags |= Flag;
780 return true;
781
782 default:
783 _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str());
784 return true;
785 }
786 return true;
787}
eff0c22e 788bool pkgTagSection::FindFlag(StringView Tag,unsigned long &Flags,
b2e465d6 789 unsigned long Flag) const
b0b4efb9
AL
790{
791 const char *Start;
792 const char *Stop;
793 if (Find(Tag,Start,Stop) == false)
794 return true;
fe0f7911
DK
795 return FindFlag(Flags, Flag, Start, Stop);
796}
d64e130a 797bool pkgTagSection::FindFlag(unsigned long &Flags, unsigned long Flag,
fe0f7911
DK
798 char const* Start, char const* Stop)
799{
800 switch (StringToBool(string(Start, Stop)))
801 {
b0b4efb9
AL
802 case 0:
803 Flags &= ~Flag;
804 return true;
805
806 case 1:
807 Flags |= Flag;
808 return true;
809
810 default:
b2e465d6 811 _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str());
b0b4efb9
AL
812 return true;
813 }
814 return true;
a05599f1
AL
815}
816 /*}}}*/
55153bf9 817void pkgTagSection::Get(const char *&Start,const char *&Stop,unsigned int I) const/*{{{*/
bc4ccfeb
DK
818{
819 Start = Section + d->Tags[I].StartTag;
820 Stop = Section + d->Tags[I+1].StartTag;
821}
55153bf9 822 /*}}}*/
8710a36a 823APT_PURE unsigned int pkgTagSection::Count() const { /*{{{*/
bc4ccfeb 824 if (d->Tags.empty() == true)
8710a36a
DK
825 return 0;
826 // the last element is just marking the end and isn't a real one
bc4ccfeb 827 return d->Tags.size() - 1;
8710a36a
DK
828}
829 /*}}}*/
8d058ea5
DK
830// TagSection::Write - Ordered (re)writing of fields /*{{{*/
831pkgTagSection::Tag pkgTagSection::Tag::Remove(std::string const &Name)
832{
833 return Tag(REMOVE, Name, "");
834}
835pkgTagSection::Tag pkgTagSection::Tag::Rename(std::string const &OldName, std::string const &NewName)
836{
837 return Tag(RENAME, OldName, NewName);
838}
839pkgTagSection::Tag pkgTagSection::Tag::Rewrite(std::string const &Name, std::string const &Data)
840{
841 if (Data.empty() == true)
842 return Tag(REMOVE, Name, "");
843 else
844 return Tag(REWRITE, Name, Data);
845}
eff0c22e 846static bool WriteTag(FileFd &File, std::string Tag, StringView Value)
8d058ea5 847{
74dedb4a 848 if (Value.empty() || isspace_ascii(Value[0]) != 0)
8d058ea5
DK
849 Tag.append(":");
850 else
851 Tag.append(": ");
eff0c22e 852 Tag.append(Value.data(), Value.length());
8d058ea5
DK
853 Tag.append("\n");
854 return File.Write(Tag.c_str(), Tag.length());
855}
856static bool RewriteTags(FileFd &File, pkgTagSection const * const This, char const * const Tag,
857 std::vector<pkgTagSection::Tag>::const_iterator &R,
858 std::vector<pkgTagSection::Tag>::const_iterator const &REnd)
859{
860 size_t const TagLen = strlen(Tag);
861 for (; R != REnd; ++R)
862 {
863 std::string data;
864 if (R->Name.length() == TagLen && strncasecmp(R->Name.c_str(), Tag, R->Name.length()) == 0)
865 {
866 if (R->Action != pkgTagSection::Tag::REWRITE)
867 break;
868 data = R->Data;
869 }
870 else if(R->Action == pkgTagSection::Tag::RENAME && R->Data.length() == TagLen &&
871 strncasecmp(R->Data.c_str(), Tag, R->Data.length()) == 0)
eff0c22e 872 data = This->FindRaw(R->Name.c_str()).to_string();
8d058ea5
DK
873 else
874 continue;
875
876 return WriteTag(File, Tag, data);
877 }
878 return true;
879}
880bool pkgTagSection::Write(FileFd &File, char const * const * const Order, std::vector<Tag> const &Rewrite) const
881{
882 // first pass: Write everything we have an order for
883 if (Order != NULL)
884 {
885 for (unsigned int I = 0; Order[I] != 0; ++I)
886 {
887 std::vector<Tag>::const_iterator R = Rewrite.begin();
888 if (RewriteTags(File, this, Order[I], R, Rewrite.end()) == false)
889 return false;
890 if (R != Rewrite.end())
891 continue;
892
893 if (Exists(Order[I]) == false)
894 continue;
895
eff0c22e 896 if (WriteTag(File, Order[I], FindRaw(Order[I])) == false)
8d058ea5
DK
897 return false;
898 }
899 }
900 // second pass: See if we have tags which aren't ordered
901 if (d->Tags.empty() == false)
902 {
903 for (std::vector<pkgTagSectionPrivate::TagData>::const_iterator T = d->Tags.begin(); T != d->Tags.end() - 1; ++T)
904 {
905 char const * const fieldname = Section + T->StartTag;
906 size_t fieldnamelen = T->EndTag - T->StartTag;
907 if (Order != NULL)
908 {
909 unsigned int I = 0;
910 for (; Order[I] != 0; ++I)
911 {
912 if (fieldnamelen == strlen(Order[I]) && strncasecmp(fieldname, Order[I], fieldnamelen) == 0)
913 break;
914 }
915 if (Order[I] != 0)
916 continue;
917 }
918
919 std::string const name(fieldname, fieldnamelen);
920 std::vector<Tag>::const_iterator R = Rewrite.begin();
921 if (RewriteTags(File, this, name.c_str(), R, Rewrite.end()) == false)
922 return false;
923 if (R != Rewrite.end())
924 continue;
925
eff0c22e 926 if (WriteTag(File, name, FindRaw(name)) == false)
8d058ea5
DK
927 return false;
928 }
929 }
930 // last pass: see if there are any rewrites remaining we haven't done yet
931 for (std::vector<Tag>::const_iterator R = Rewrite.begin(); R != Rewrite.end(); ++R)
932 {
933 if (R->Action == Tag::REMOVE)
934 continue;
935 std::string const name = ((R->Action == Tag::RENAME) ? R->Data : R->Name);
936 if (Exists(name.c_str()))
937 continue;
938 if (Order != NULL)
939 {
940 unsigned int I = 0;
941 for (; Order[I] != 0; ++I)
942 {
943 if (strncasecmp(name.c_str(), Order[I], name.length()) == 0 && name.length() == strlen(Order[I]))
944 break;
945 }
946 if (Order[I] != 0)
947 continue;
948 }
949
eff0c22e 950 if (WriteTag(File, name, ((R->Action == Tag::RENAME) ? FindRaw(R->Name) : R->Data)) == false)
8d058ea5
DK
951 return false;
952 }
953 return true;
954}
955 /*}}}*/
e8fb1cdf 956
81460e32
DK
957void pkgUserTagSection::TrimRecord(bool /*BeforeRecord*/, const char* &End)/*{{{*/
958{
959 for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r' || Stop[0] == '#'); Stop++)
960 if (Stop[0] == '#')
961 Stop = (const char*) memchr(Stop,'\n',End-Stop);
962}
963 /*}}}*/
964
e8fb1cdf
DK
965#include "tagfile-order.c"
966
b2e465d6
AL
967// TFRewrite - Rewrite a control record /*{{{*/
968// ---------------------------------------------------------------------
969/* This writes the control record to stdout rewriting it as necessary. The
970 override map item specificies the rewriting rules to follow. This also
971 takes the time to sort the feild list. */
8d058ea5 972APT_IGNORE_DEPRECATED_PUSH
b2e465d6
AL
973bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
974 TFRewriteData *Rewrite)
975{
976 unsigned char Visited[256]; // Bit 1 is Order, Bit 2 is Rewrite
977 for (unsigned I = 0; I != 256; I++)
978 Visited[I] = 0;
979
980 // Set new tag up as necessary.
981 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
982 {
983 if (Rewrite[J].NewTag == 0)
984 Rewrite[J].NewTag = Rewrite[J].Tag;
985 }
986
987 // Write all all of the tags, in order.
9e51c0b6 988 if (Order != NULL)
b2e465d6 989 {
9e51c0b6 990 for (unsigned int I = 0; Order[I] != 0; I++)
b2e465d6 991 {
9e51c0b6
MV
992 bool Rewritten = false;
993
994 // See if this is a field that needs to be rewritten
995 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
996 {
997 if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0)
998 {
999 Visited[J] |= 2;
1000 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
1001 {
74dedb4a 1002 if (isspace_ascii(Rewrite[J].Rewrite[0]))
9e51c0b6
MV
1003 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1004 else
1005 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1006 }
1007 Rewritten = true;
1008 break;
1009 }
1010 }
b2e465d6 1011
9e51c0b6
MV
1012 // See if it is in the fragment
1013 unsigned Pos;
eff0c22e 1014 if (Tags.Find(StringView(Order[I]),Pos) == false)
9e51c0b6
MV
1015 continue;
1016 Visited[Pos] |= 1;
1017
1018 if (Rewritten == true)
1019 continue;
b2e465d6 1020
9e51c0b6
MV
1021 /* Write out this element, taking a moment to rewrite the tag
1022 in case of changes of case. */
1023 const char *Start;
1024 const char *Stop;
1025 Tags.Get(Start,Stop,Pos);
b2e465d6 1026
9e51c0b6
MV
1027 if (fputs(Order[I],Output) < 0)
1028 return _error->Errno("fputs","IO Error to output");
1029 Start += strlen(Order[I]);
1030 if (fwrite(Start,Stop - Start,1,Output) != 1)
1031 return _error->Errno("fwrite","IO Error to output");
1032 if (Stop[-1] != '\n')
1033 fprintf(Output,"\n");
1034 }
1035 }
b2e465d6
AL
1036
1037 // Now write all the old tags that were missed.
1038 for (unsigned int I = 0; I != Tags.Count(); I++)
1039 {
1040 if ((Visited[I] & 1) == 1)
1041 continue;
1042
1043 const char *Start;
1044 const char *Stop;
1045 Tags.Get(Start,Stop,I);
1046 const char *End = Start;
1047 for (; End < Stop && *End != ':'; End++);
1048
1049 // See if this is a field that needs to be rewritten
1050 bool Rewritten = false;
1051 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
1052 {
1053 if (stringcasecmp(Start,End,Rewrite[J].Tag) == 0)
1054 {
1055 Visited[J] |= 2;
1056 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
1057 {
74dedb4a 1058 if (isspace_ascii(Rewrite[J].Rewrite[0]))
b2e465d6
AL
1059 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1060 else
1061 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1062 }
1063
1064 Rewritten = true;
1065 break;
1066 }
1067 }
1068
1069 if (Rewritten == true)
1070 continue;
1071
1072 // Write out this element
1073 if (fwrite(Start,Stop - Start,1,Output) != 1)
1074 return _error->Errno("fwrite","IO Error to output");
1075 if (Stop[-1] != '\n')
1076 fprintf(Output,"\n");
1077 }
1078
1079 // Now write all the rewrites that were missed
1080 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
1081 {
1082 if ((Visited[J] & 2) == 2)
1083 continue;
1084
1085 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
1086 {
74dedb4a 1087 if (isspace_ascii(Rewrite[J].Rewrite[0]))
b2e465d6
AL
1088 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1089 else
1090 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
1091 }
1092 }
1093
1094 return true;
1095}
8d058ea5 1096APT_IGNORE_DEPRECATED_POP
b2e465d6 1097 /*}}}*/
862bafea 1098
bc4ccfeb 1099pkgTagSection::~pkgTagSection() { delete d; }