]> git.saurik.com Git - apt.git/blob - apt-pkg/tagfile.cc
e667c495fd0fc68caade79dbb5ac957790fab914
[apt.git] / apt-pkg / tagfile.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: tagfile.cc,v 1.37.2.2 2003/12/31 16:02:30 mdz Exp $
4 /* ######################################################################
5
6 Fast scanner for RFC-822 type header information
7
8 This uses a rotating buffer to load the package information into.
9 The scanner runs over it and isolates and indexes a single section.
10
11 ##################################################################### */
12 /*}}}*/
13 // Include Files /*{{{*/
14 #include<config.h>
15
16 #include <apt-pkg/tagfile.h>
17 #include <apt-pkg/error.h>
18 #include <apt-pkg/strutl.h>
19 #include <apt-pkg/fileutl.h>
20
21 #include <string>
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <apti18n.h>
28 /*}}}*/
29
30 using std::string;
31
32 class pkgTagFilePrivate
33 {
34 public:
35 pkgTagFilePrivate(FileFd *pFd, unsigned long long Size) : Fd(*pFd), Buffer(NULL),
36 Start(NULL), End(NULL),
37 Done(false), iOffset(0),
38 Size(Size)
39 {
40 }
41 FileFd &Fd;
42 char *Buffer;
43 char *Start;
44 char *End;
45 bool Done;
46 unsigned long long iOffset;
47 unsigned long long Size;
48 };
49
50 static unsigned long AlphaHash(const char *Text, size_t Length) /*{{{*/
51 {
52 /* This very simple hash function for the last 8 letters gives
53 very good performance on the debian package files */
54 if (Length > 8)
55 {
56 Text += (Length - 8);
57 Length = 8;
58 }
59 unsigned long Res = 0;
60 for (size_t i = 0; i < Length; ++i)
61 Res = ((unsigned long)(Text[i]) & 0xDF) ^ (Res << 1);
62 return Res & 0xFF;
63 }
64 /*}}}*/
65
66 // TagFile::pkgTagFile - Constructor /*{{{*/
67 // ---------------------------------------------------------------------
68 /* */
69 pkgTagFile::pkgTagFile(FileFd *pFd,unsigned long long Size)
70 : d(NULL)
71 {
72 Init(pFd, Size);
73 }
74
75 void pkgTagFile::Init(FileFd *pFd,unsigned long long Size)
76 {
77 /* The size is increased by 4 because if we start with the Size of the
78 filename we need to try to read 1 char more to see an EOF faster, 1
79 char the end-pointer can be on and maybe 2 newlines need to be added
80 to the end of the file -> 4 extra chars */
81 Size += 4;
82 if(d != NULL)
83 {
84 free(d->Buffer);
85 delete d;
86 }
87 d = new pkgTagFilePrivate(pFd, Size);
88
89 if (d->Fd.IsOpen() == false)
90 d->Start = d->End = d->Buffer = 0;
91 else
92 d->Buffer = (char*)malloc(sizeof(char) * Size);
93
94 if (d->Buffer == NULL)
95 d->Done = true;
96 else
97 d->Done = false;
98
99 d->Start = d->End = d->Buffer;
100 d->iOffset = 0;
101 if (d->Done == false)
102 Fill();
103 }
104 /*}}}*/
105 // TagFile::~pkgTagFile - Destructor /*{{{*/
106 // ---------------------------------------------------------------------
107 /* */
108 pkgTagFile::~pkgTagFile()
109 {
110 free(d->Buffer);
111 delete d;
112 }
113 /*}}}*/
114 // TagFile::Offset - Return the current offset in the buffer /*{{{*/
115 APT_PURE unsigned long pkgTagFile::Offset()
116 {
117 return d->iOffset;
118 }
119 /*}}}*/
120 // TagFile::Resize - Resize the internal buffer /*{{{*/
121 // ---------------------------------------------------------------------
122 /* Resize the internal buffer (double it in size). Fail if a maximum size
123 * size is reached.
124 */
125 bool pkgTagFile::Resize()
126 {
127 // fail is the buffer grows too big
128 if(d->Size > 1024*1024+1)
129 return false;
130
131 return Resize(d->Size * 2);
132 }
133 bool pkgTagFile::Resize(unsigned long long const newSize)
134 {
135 unsigned long long const EndSize = d->End - d->Start;
136
137 // get new buffer and use it
138 char* newBuffer = (char*)realloc(d->Buffer, sizeof(char) * newSize);
139 if (newBuffer == NULL)
140 return false;
141 d->Buffer = newBuffer;
142 d->Size = newSize;
143
144 // update the start/end pointers to the new buffer
145 d->Start = d->Buffer;
146 d->End = d->Start + EndSize;
147 return true;
148 }
149 /*}}}*/
150 // TagFile::Step - Advance to the next section /*{{{*/
151 // ---------------------------------------------------------------------
152 /* If the Section Scanner fails we refill the buffer and try again.
153 * If that fails too, double the buffer size and try again until a
154 * maximum buffer is reached.
155 */
156 bool pkgTagFile::Step(pkgTagSection &Tag)
157 {
158 if(Tag.Scan(d->Start,d->End - d->Start) == false)
159 {
160 do
161 {
162 if (Fill() == false)
163 return false;
164
165 if(Tag.Scan(d->Start,d->End - d->Start, false))
166 break;
167
168 if (Resize() == false)
169 return _error->Error(_("Unable to parse package file %s (1)"),
170 d->Fd.Name().c_str());
171
172 } while (Tag.Scan(d->Start,d->End - d->Start, false) == false);
173 }
174
175 d->Start += Tag.size();
176 d->iOffset += Tag.size();
177
178 Tag.Trim();
179 return true;
180 }
181 /*}}}*/
182 // TagFile::Fill - Top up the buffer /*{{{*/
183 // ---------------------------------------------------------------------
184 /* This takes the bit at the end of the buffer and puts it at the start
185 then fills the rest from the file */
186 bool pkgTagFile::Fill()
187 {
188 unsigned long long EndSize = d->End - d->Start;
189 unsigned long long Actual = 0;
190
191 memmove(d->Buffer,d->Start,EndSize);
192 d->Start = d->Buffer;
193 d->End = d->Buffer + EndSize;
194
195 if (d->Done == false)
196 {
197 // See if only a bit of the file is left
198 unsigned long long const dataSize = d->Size - ((d->End - d->Buffer) + 1);
199 if (d->Fd.Read(d->End, dataSize, &Actual) == false)
200 return false;
201 if (Actual != dataSize)
202 d->Done = true;
203 d->End += Actual;
204 }
205
206 if (d->Done == true)
207 {
208 if (EndSize <= 3 && Actual == 0)
209 return false;
210 if (d->Size - (d->End - d->Buffer) < 4)
211 return true;
212
213 // Append a double new line if one does not exist
214 unsigned int LineCount = 0;
215 for (const char *E = d->End - 1; E - d->End < 6 && (*E == '\n' || *E == '\r'); E--)
216 if (*E == '\n')
217 LineCount++;
218 if (LineCount < 2)
219 {
220 if ((unsigned)(d->End - d->Buffer) >= d->Size)
221 Resize(d->Size + 3);
222 for (; LineCount < 2; LineCount++)
223 *d->End++ = '\n';
224 }
225
226 return true;
227 }
228
229 return true;
230 }
231 /*}}}*/
232 // TagFile::Jump - Jump to a pre-recorded location in the file /*{{{*/
233 // ---------------------------------------------------------------------
234 /* This jumps to a pre-recorded file location and reads the record
235 that is there */
236 bool pkgTagFile::Jump(pkgTagSection &Tag,unsigned long long Offset)
237 {
238 // We are within a buffer space of the next hit..
239 if (Offset >= d->iOffset && d->iOffset + (d->End - d->Start) > Offset)
240 {
241 unsigned long long Dist = Offset - d->iOffset;
242 d->Start += Dist;
243 d->iOffset += Dist;
244 // if we have seen the end, don't ask for more
245 if (d->Done == true)
246 return Tag.Scan(d->Start, d->End - d->Start);
247 else
248 return Step(Tag);
249 }
250
251 // Reposition and reload..
252 d->iOffset = Offset;
253 d->Done = false;
254 if (d->Fd.Seek(Offset) == false)
255 return false;
256 d->End = d->Start = d->Buffer;
257
258 if (Fill() == false)
259 return false;
260
261 if (Tag.Scan(d->Start, d->End - d->Start) == true)
262 return true;
263
264 // This appends a double new line (for the real eof handling)
265 if (Fill() == false)
266 return false;
267
268 if (Tag.Scan(d->Start, d->End - d->Start, false) == false)
269 return _error->Error(_("Unable to parse package file %s (2)"),d->Fd.Name().c_str());
270
271 return true;
272 }
273 /*}}}*/
274 // pkgTagSection::pkgTagSection - Constructor /*{{{*/
275 // ---------------------------------------------------------------------
276 /* */
277 pkgTagSection::pkgTagSection()
278 : Section(0), d(NULL), Stop(0)
279 {
280 memset(&LookupTable, 0, sizeof(LookupTable));
281 }
282 /*}}}*/
283 // TagSection::Scan - Scan for the end of the header information /*{{{*/
284 bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength, bool const Restart)
285 {
286 Section = Start;
287 const char *End = Start + MaxLength;
288
289 if (Restart == false && Tags.empty() == false)
290 {
291 Stop = Section + Tags.back().StartTag;
292 if (End <= Stop)
293 return false;
294 Stop = (const char *)memchr(Stop,'\n',End - Stop);
295 if (Stop == NULL)
296 return false;
297 ++Stop;
298 }
299 else
300 {
301 Stop = Section;
302 if (Tags.empty() == false)
303 {
304 memset(&LookupTable, 0, sizeof(LookupTable));
305 Tags.clear();
306 }
307 Tags.reserve(0x100);
308 }
309 size_t TagCount = Tags.size();
310
311 if (Stop == 0)
312 return false;
313
314 TagData lastTagData(0);
315 lastTagData.EndTag = 0;
316 unsigned long lastTagHash = 0;
317 while (Stop < End)
318 {
319 TrimRecord(true,End);
320
321 // this can happen when TrimRecord trims away the entire Record
322 // (e.g. because it just contains comments)
323 if(Stop == End)
324 return true;
325
326 // Start a new index and add it to the hash
327 if (isspace(Stop[0]) == 0)
328 {
329 // store the last found tag
330 if (lastTagData.EndTag != 0)
331 {
332 if (LookupTable[lastTagHash] != 0)
333 lastTagData.NextInBucket = LookupTable[lastTagHash];
334 LookupTable[lastTagHash] = TagCount;
335 Tags.push_back(lastTagData);
336 }
337
338 ++TagCount;
339 lastTagData = TagData(Stop - Section);
340 // find the colon separating tag and value
341 char const * Colon = (char const *) memchr(Stop, ':', End - Stop);
342 if (Colon == NULL)
343 return false;
344 // find the end of the tag (which might or might not be the colon)
345 char const * EndTag = Colon;
346 --EndTag;
347 for (; EndTag > Stop && isspace(*EndTag) != 0; --EndTag)
348 ;
349 ++EndTag;
350 lastTagData.EndTag = EndTag - Section;
351 lastTagHash = AlphaHash(Stop, EndTag - Stop);
352 // find the beginning of the value
353 Stop = Colon + 1;
354 for (; isspace(*Stop) != 0; ++Stop);
355 if (Stop >= End)
356 return false;
357 lastTagData.StartValue = Stop - Section;
358 }
359
360 Stop = (const char *)memchr(Stop,'\n',End - Stop);
361
362 if (Stop == 0)
363 return false;
364
365 for (; Stop+1 < End && Stop[1] == '\r'; Stop++)
366 /* nothing */
367 ;
368
369 // Double newline marks the end of the record
370 if (Stop+1 < End && Stop[1] == '\n')
371 {
372 if (lastTagData.EndTag != 0)
373 {
374 if (LookupTable[lastTagHash] != 0)
375 lastTagData.NextInBucket = LookupTable[lastTagHash];
376 LookupTable[lastTagHash] = TagCount;
377 Tags.push_back(lastTagData);
378 }
379
380 TagData const td(Stop - Section);
381 Tags.push_back(td);
382 TrimRecord(false,End);
383 return true;
384 }
385
386 Stop++;
387 }
388
389 return false;
390 }
391 /*}}}*/
392 // TagSection::TrimRecord - Trim off any garbage before/after a record /*{{{*/
393 // ---------------------------------------------------------------------
394 /* There should be exactly 2 newline at the end of the record, no more. */
395 void pkgTagSection::TrimRecord(bool BeforeRecord, const char*& End)
396 {
397 if (BeforeRecord == true)
398 return;
399 for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r'); Stop++);
400 }
401 /*}}}*/
402 // TagSection::Trim - Trim off any trailing garbage /*{{{*/
403 // ---------------------------------------------------------------------
404 /* There should be exactly 1 newline at the end of the buffer, no more. */
405 void pkgTagSection::Trim()
406 {
407 for (; Stop > Section + 2 && (Stop[-2] == '\n' || Stop[-2] == '\r'); Stop--);
408 }
409 /*}}}*/
410 // TagSection::Exists - return True if a tag exists /*{{{*/
411 #if APT_PKG_ABI >= 413
412 bool pkgTagSection::Exists(const char* const Tag) const
413 #else
414 bool pkgTagSection::Exists(const char* const Tag)
415 #endif
416 {
417 unsigned int tmp;
418 return Find(Tag, tmp);
419 }
420 /*}}}*/
421 // TagSection::Find - Locate a tag /*{{{*/
422 // ---------------------------------------------------------------------
423 /* This searches the section for a tag that matches the given string. */
424 bool pkgTagSection::Find(const char *Tag,unsigned int &Pos) const
425 {
426 size_t const Length = strlen(Tag);
427 unsigned int Bucket = LookupTable[AlphaHash(Tag, Length)];
428 if (Bucket == 0)
429 return false;
430
431 for (; Bucket != 0; Bucket = Tags[Bucket - 1].NextInBucket)
432 {
433 if ((Tags[Bucket - 1].EndTag - Tags[Bucket - 1].StartTag) != Length)
434 continue;
435
436 char const * const St = Section + Tags[Bucket - 1].StartTag;
437 if (strncasecmp(Tag,St,Length) != 0)
438 continue;
439
440 Pos = Bucket - 1;
441 return true;
442 }
443
444 Pos = 0;
445 return false;
446 }
447 bool pkgTagSection::Find(const char *Tag,const char *&Start,
448 const char *&End) const
449 {
450 unsigned int Pos;
451 if (Find(Tag, Pos) == false)
452 return false;
453
454 Start = Section + Tags[Pos].StartValue;
455 // Strip off the gunk from the end
456 End = Section + Tags[Pos + 1].StartTag;
457 if (unlikely(Start > End))
458 return _error->Error("Internal parsing error");
459
460 for (; isspace(End[-1]) != 0 && End > Start; --End);
461
462 return true;
463 }
464 /*}}}*/
465 // TagSection::FindS - Find a string /*{{{*/
466 // ---------------------------------------------------------------------
467 /* */
468 string pkgTagSection::FindS(const char *Tag) const
469 {
470 const char *Start;
471 const char *End;
472 if (Find(Tag,Start,End) == false)
473 return string();
474 return string(Start,End);
475 }
476 /*}}}*/
477 // TagSection::FindI - Find an integer /*{{{*/
478 // ---------------------------------------------------------------------
479 /* */
480 signed int pkgTagSection::FindI(const char *Tag,signed long Default) const
481 {
482 const char *Start;
483 const char *Stop;
484 if (Find(Tag,Start,Stop) == false)
485 return Default;
486
487 // Copy it into a temp buffer so we can use strtol
488 char S[300];
489 if ((unsigned)(Stop - Start) >= sizeof(S))
490 return Default;
491 strncpy(S,Start,Stop-Start);
492 S[Stop - Start] = 0;
493
494 char *End;
495 signed long Result = strtol(S,&End,10);
496 if (S == End)
497 return Default;
498 return Result;
499 }
500 /*}}}*/
501 // TagSection::FindULL - Find an unsigned long long integer /*{{{*/
502 // ---------------------------------------------------------------------
503 /* */
504 unsigned long long pkgTagSection::FindULL(const char *Tag, unsigned long long const &Default) const
505 {
506 const char *Start;
507 const char *Stop;
508 if (Find(Tag,Start,Stop) == false)
509 return Default;
510
511 // Copy it into a temp buffer so we can use strtoull
512 char S[100];
513 if ((unsigned)(Stop - Start) >= sizeof(S))
514 return Default;
515 strncpy(S,Start,Stop-Start);
516 S[Stop - Start] = 0;
517
518 char *End;
519 unsigned long long Result = strtoull(S,&End,10);
520 if (S == End)
521 return Default;
522 return Result;
523 }
524 /*}}}*/
525 // TagSection::FindB - Find boolean value /*{{{*/
526 // ---------------------------------------------------------------------
527 /* */
528 bool pkgTagSection::FindB(const char *Tag, bool const &Default) const
529 {
530 const char *Start, *Stop;
531 if (Find(Tag, Start, Stop) == false)
532 return Default;
533 return StringToBool(string(Start, Stop));
534 }
535 /*}}}*/
536 // TagSection::FindFlag - Locate a yes/no type flag /*{{{*/
537 // ---------------------------------------------------------------------
538 /* The bits marked in Flag are masked on/off in Flags */
539 bool pkgTagSection::FindFlag(const char *Tag,unsigned long &Flags,
540 unsigned long Flag) const
541 {
542 const char *Start;
543 const char *Stop;
544 if (Find(Tag,Start,Stop) == false)
545 return true;
546 return FindFlag(Flags, Flag, Start, Stop);
547 }
548 bool pkgTagSection::FindFlag(unsigned long &Flags, unsigned long Flag,
549 char const* Start, char const* Stop)
550 {
551 switch (StringToBool(string(Start, Stop)))
552 {
553 case 0:
554 Flags &= ~Flag;
555 return true;
556
557 case 1:
558 Flags |= Flag;
559 return true;
560
561 default:
562 _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str());
563 return true;
564 }
565 return true;
566 }
567 /*}}}*/
568 APT_PURE unsigned int pkgTagSection::Count() const { /*{{{*/
569 if (Tags.empty() == true)
570 return 0;
571 // the last element is just marking the end and isn't a real one
572 return Tags.size() - 1;
573 }
574 /*}}}*/
575 // TFRewrite - Rewrite a control record /*{{{*/
576 // ---------------------------------------------------------------------
577 /* This writes the control record to stdout rewriting it as necessary. The
578 override map item specificies the rewriting rules to follow. This also
579 takes the time to sort the feild list. */
580
581 /* The order of this list is taken from dpkg source lib/parse.c the fieldinfos
582 array. */
583 static const char *iTFRewritePackageOrder[] = {
584 "Package",
585 "Essential",
586 "Status",
587 "Priority",
588 "Section",
589 "Installed-Size",
590 "Maintainer",
591 "Original-Maintainer",
592 "Architecture",
593 "Source",
594 "Version",
595 "Revision", // Obsolete
596 "Config-Version", // Obsolete
597 "Replaces",
598 "Provides",
599 "Depends",
600 "Pre-Depends",
601 "Recommends",
602 "Suggests",
603 "Conflicts",
604 "Breaks",
605 "Conffiles",
606 "Filename",
607 "Size",
608 "MD5sum",
609 "SHA1",
610 "SHA256",
611 "SHA512",
612 "MSDOS-Filename", // Obsolete
613 "Description",
614 0};
615 static const char *iTFRewriteSourceOrder[] = {"Package",
616 "Source",
617 "Binary",
618 "Version",
619 "Priority",
620 "Section",
621 "Maintainer",
622 "Original-Maintainer",
623 "Build-Depends",
624 "Build-Depends-Indep",
625 "Build-Conflicts",
626 "Build-Conflicts-Indep",
627 "Architecture",
628 "Standards-Version",
629 "Format",
630 "Directory",
631 "Files",
632 0};
633
634 /* Two levels of initialization are used because gcc will set the symbol
635 size of an array to the length of the array, causing dynamic relinking
636 errors. Doing this makes the symbol size constant */
637 const char **TFRewritePackageOrder = iTFRewritePackageOrder;
638 const char **TFRewriteSourceOrder = iTFRewriteSourceOrder;
639
640 bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
641 TFRewriteData *Rewrite)
642 {
643 unsigned char Visited[256]; // Bit 1 is Order, Bit 2 is Rewrite
644 for (unsigned I = 0; I != 256; I++)
645 Visited[I] = 0;
646
647 // Set new tag up as necessary.
648 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
649 {
650 if (Rewrite[J].NewTag == 0)
651 Rewrite[J].NewTag = Rewrite[J].Tag;
652 }
653
654 // Write all all of the tags, in order.
655 if (Order != NULL)
656 {
657 for (unsigned int I = 0; Order[I] != 0; I++)
658 {
659 bool Rewritten = false;
660
661 // See if this is a field that needs to be rewritten
662 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
663 {
664 if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0)
665 {
666 Visited[J] |= 2;
667 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
668 {
669 if (isspace(Rewrite[J].Rewrite[0]))
670 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
671 else
672 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
673 }
674 Rewritten = true;
675 break;
676 }
677 }
678
679 // See if it is in the fragment
680 unsigned Pos;
681 if (Tags.Find(Order[I],Pos) == false)
682 continue;
683 Visited[Pos] |= 1;
684
685 if (Rewritten == true)
686 continue;
687
688 /* Write out this element, taking a moment to rewrite the tag
689 in case of changes of case. */
690 const char *Start;
691 const char *Stop;
692 Tags.Get(Start,Stop,Pos);
693
694 if (fputs(Order[I],Output) < 0)
695 return _error->Errno("fputs","IO Error to output");
696 Start += strlen(Order[I]);
697 if (fwrite(Start,Stop - Start,1,Output) != 1)
698 return _error->Errno("fwrite","IO Error to output");
699 if (Stop[-1] != '\n')
700 fprintf(Output,"\n");
701 }
702 }
703
704 // Now write all the old tags that were missed.
705 for (unsigned int I = 0; I != Tags.Count(); I++)
706 {
707 if ((Visited[I] & 1) == 1)
708 continue;
709
710 const char *Start;
711 const char *Stop;
712 Tags.Get(Start,Stop,I);
713 const char *End = Start;
714 for (; End < Stop && *End != ':'; End++);
715
716 // See if this is a field that needs to be rewritten
717 bool Rewritten = false;
718 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
719 {
720 if (stringcasecmp(Start,End,Rewrite[J].Tag) == 0)
721 {
722 Visited[J] |= 2;
723 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
724 {
725 if (isspace(Rewrite[J].Rewrite[0]))
726 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
727 else
728 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
729 }
730
731 Rewritten = true;
732 break;
733 }
734 }
735
736 if (Rewritten == true)
737 continue;
738
739 // Write out this element
740 if (fwrite(Start,Stop - Start,1,Output) != 1)
741 return _error->Errno("fwrite","IO Error to output");
742 if (Stop[-1] != '\n')
743 fprintf(Output,"\n");
744 }
745
746 // Now write all the rewrites that were missed
747 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
748 {
749 if ((Visited[J] & 2) == 2)
750 continue;
751
752 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
753 {
754 if (isspace(Rewrite[J].Rewrite[0]))
755 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
756 else
757 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
758 }
759 }
760
761 return true;
762 }
763 /*}}}*/
764
765 pkgTagSection::~pkgTagSection() {}