]> git.saurik.com Git - apt.git/blob - apt-pkg/tagfile.cc
revert useless abibreak in sourceslist.h
[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 #if APT_PKG_ABI < 413
285 bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength)
286 {
287 return Scan(Start, MaxLength, true);
288 }
289 #endif
290 bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength, bool const Restart)
291 {
292 Section = Start;
293 const char *End = Start + MaxLength;
294
295 if (Restart == false && Tags.empty() == false)
296 {
297 Stop = Section + Tags.back().StartTag;
298 if (End <= Stop)
299 return false;
300 Stop = (const char *)memchr(Stop,'\n',End - Stop);
301 if (Stop == NULL)
302 return false;
303 ++Stop;
304 }
305 else
306 {
307 Stop = Section;
308 if (Tags.empty() == false)
309 {
310 memset(&LookupTable, 0, sizeof(LookupTable));
311 Tags.clear();
312 }
313 Tags.reserve(0x100);
314 }
315 size_t TagCount = Tags.size();
316
317 if (Stop == 0)
318 return false;
319
320 TagData lastTagData(0);
321 lastTagData.EndTag = 0;
322 unsigned long lastTagHash = 0;
323 while (Stop < End)
324 {
325 TrimRecord(true,End);
326
327 // this can happen when TrimRecord trims away the entire Record
328 // (e.g. because it just contains comments)
329 if(Stop == End)
330 return true;
331
332 // Start a new index and add it to the hash
333 if (isspace(Stop[0]) == 0)
334 {
335 // store the last found tag
336 if (lastTagData.EndTag != 0)
337 {
338 if (LookupTable[lastTagHash] != 0)
339 lastTagData.NextInBucket = LookupTable[lastTagHash];
340 LookupTable[lastTagHash] = TagCount;
341 Tags.push_back(lastTagData);
342 }
343
344 ++TagCount;
345 lastTagData = TagData(Stop - Section);
346 // find the colon separating tag and value
347 char const * Colon = (char const *) memchr(Stop, ':', End - Stop);
348 if (Colon == NULL)
349 return false;
350 // find the end of the tag (which might or might not be the colon)
351 char const * EndTag = Colon;
352 --EndTag;
353 for (; EndTag > Stop && isspace(*EndTag) != 0; --EndTag)
354 ;
355 ++EndTag;
356 lastTagData.EndTag = EndTag - Section;
357 lastTagHash = AlphaHash(Stop, EndTag - Stop);
358 // find the beginning of the value
359 Stop = Colon + 1;
360 for (; isspace(*Stop) != 0; ++Stop);
361 if (Stop >= End)
362 return false;
363 lastTagData.StartValue = Stop - Section;
364 }
365
366 Stop = (const char *)memchr(Stop,'\n',End - Stop);
367
368 if (Stop == 0)
369 return false;
370
371 for (; Stop+1 < End && Stop[1] == '\r'; Stop++)
372 /* nothing */
373 ;
374
375 // Double newline marks the end of the record
376 if (Stop+1 < End && Stop[1] == '\n')
377 {
378 if (lastTagData.EndTag != 0)
379 {
380 if (LookupTable[lastTagHash] != 0)
381 lastTagData.NextInBucket = LookupTable[lastTagHash];
382 LookupTable[lastTagHash] = TagCount;
383 Tags.push_back(lastTagData);
384 }
385
386 TagData const td(Stop - Section);
387 Tags.push_back(td);
388 TrimRecord(false,End);
389 return true;
390 }
391
392 Stop++;
393 }
394
395 return false;
396 }
397 /*}}}*/
398 // TagSection::TrimRecord - Trim off any garbage before/after a record /*{{{*/
399 // ---------------------------------------------------------------------
400 /* There should be exactly 2 newline at the end of the record, no more. */
401 void pkgTagSection::TrimRecord(bool BeforeRecord, const char*& End)
402 {
403 if (BeforeRecord == true)
404 return;
405 for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r'); Stop++);
406 }
407 /*}}}*/
408 // TagSection::Trim - Trim off any trailing garbage /*{{{*/
409 // ---------------------------------------------------------------------
410 /* There should be exactly 1 newline at the end of the buffer, no more. */
411 void pkgTagSection::Trim()
412 {
413 for (; Stop > Section + 2 && (Stop[-2] == '\n' || Stop[-2] == '\r'); Stop--);
414 }
415 /*}}}*/
416 // TagSection::Exists - return True if a tag exists /*{{{*/
417 #if APT_PKG_ABI >= 413
418 bool pkgTagSection::Exists(const char* const Tag) const
419 #else
420 bool pkgTagSection::Exists(const char* const Tag)
421 #endif
422 {
423 unsigned int tmp;
424 return Find(Tag, tmp);
425 }
426 /*}}}*/
427 // TagSection::Find - Locate a tag /*{{{*/
428 // ---------------------------------------------------------------------
429 /* This searches the section for a tag that matches the given string. */
430 bool pkgTagSection::Find(const char *Tag,unsigned int &Pos) const
431 {
432 size_t const Length = strlen(Tag);
433 unsigned int Bucket = LookupTable[AlphaHash(Tag, Length)];
434 if (Bucket == 0)
435 return false;
436
437 for (; Bucket != 0; Bucket = Tags[Bucket - 1].NextInBucket)
438 {
439 if ((Tags[Bucket - 1].EndTag - Tags[Bucket - 1].StartTag) != Length)
440 continue;
441
442 char const * const St = Section + Tags[Bucket - 1].StartTag;
443 if (strncasecmp(Tag,St,Length) != 0)
444 continue;
445
446 Pos = Bucket - 1;
447 return true;
448 }
449
450 Pos = 0;
451 return false;
452 }
453 bool pkgTagSection::Find(const char *Tag,const char *&Start,
454 const char *&End) const
455 {
456 unsigned int Pos;
457 if (Find(Tag, Pos) == false)
458 return false;
459
460 Start = Section + Tags[Pos].StartValue;
461 // Strip off the gunk from the end
462 End = Section + Tags[Pos + 1].StartTag;
463 if (unlikely(Start > End))
464 return _error->Error("Internal parsing error");
465
466 for (; isspace(End[-1]) != 0 && End > Start; --End);
467
468 return true;
469 }
470 /*}}}*/
471 // TagSection::FindS - Find a string /*{{{*/
472 // ---------------------------------------------------------------------
473 /* */
474 string pkgTagSection::FindS(const char *Tag) const
475 {
476 const char *Start;
477 const char *End;
478 if (Find(Tag,Start,End) == false)
479 return string();
480 return string(Start,End);
481 }
482 /*}}}*/
483 // TagSection::FindI - Find an integer /*{{{*/
484 // ---------------------------------------------------------------------
485 /* */
486 signed int pkgTagSection::FindI(const char *Tag,signed long Default) const
487 {
488 const char *Start;
489 const char *Stop;
490 if (Find(Tag,Start,Stop) == false)
491 return Default;
492
493 // Copy it into a temp buffer so we can use strtol
494 char S[300];
495 if ((unsigned)(Stop - Start) >= sizeof(S))
496 return Default;
497 strncpy(S,Start,Stop-Start);
498 S[Stop - Start] = 0;
499
500 char *End;
501 signed long Result = strtol(S,&End,10);
502 if (S == End)
503 return Default;
504 return Result;
505 }
506 /*}}}*/
507 // TagSection::FindULL - Find an unsigned long long integer /*{{{*/
508 // ---------------------------------------------------------------------
509 /* */
510 unsigned long long pkgTagSection::FindULL(const char *Tag, unsigned long long const &Default) const
511 {
512 const char *Start;
513 const char *Stop;
514 if (Find(Tag,Start,Stop) == false)
515 return Default;
516
517 // Copy it into a temp buffer so we can use strtoull
518 char S[100];
519 if ((unsigned)(Stop - Start) >= sizeof(S))
520 return Default;
521 strncpy(S,Start,Stop-Start);
522 S[Stop - Start] = 0;
523
524 char *End;
525 unsigned long long Result = strtoull(S,&End,10);
526 if (S == End)
527 return Default;
528 return Result;
529 }
530 /*}}}*/
531 // TagSection::FindB - Find boolean value /*{{{*/
532 // ---------------------------------------------------------------------
533 /* */
534 bool pkgTagSection::FindB(const char *Tag, bool const &Default) const
535 {
536 const char *Start, *Stop;
537 if (Find(Tag, Start, Stop) == false)
538 return Default;
539 return StringToBool(string(Start, Stop));
540 }
541 /*}}}*/
542 // TagSection::FindFlag - Locate a yes/no type flag /*{{{*/
543 // ---------------------------------------------------------------------
544 /* The bits marked in Flag are masked on/off in Flags */
545 bool pkgTagSection::FindFlag(const char *Tag,unsigned long &Flags,
546 unsigned long Flag) const
547 {
548 const char *Start;
549 const char *Stop;
550 if (Find(Tag,Start,Stop) == false)
551 return true;
552 return FindFlag(Flags, Flag, Start, Stop);
553 }
554 bool pkgTagSection::FindFlag(unsigned long &Flags, unsigned long Flag,
555 char const* Start, char const* Stop)
556 {
557 switch (StringToBool(string(Start, Stop)))
558 {
559 case 0:
560 Flags &= ~Flag;
561 return true;
562
563 case 1:
564 Flags |= Flag;
565 return true;
566
567 default:
568 _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str());
569 return true;
570 }
571 return true;
572 }
573 /*}}}*/
574 APT_PURE unsigned int pkgTagSection::Count() const { /*{{{*/
575 if (Tags.empty() == true)
576 return 0;
577 // the last element is just marking the end and isn't a real one
578 return Tags.size() - 1;
579 }
580 /*}}}*/
581 // TFRewrite - Rewrite a control record /*{{{*/
582 // ---------------------------------------------------------------------
583 /* This writes the control record to stdout rewriting it as necessary. The
584 override map item specificies the rewriting rules to follow. This also
585 takes the time to sort the feild list. */
586
587 /* The order of this list is taken from dpkg source lib/parse.c the fieldinfos
588 array. */
589 static const char *iTFRewritePackageOrder[] = {
590 "Package",
591 "Essential",
592 "Status",
593 "Priority",
594 "Section",
595 "Installed-Size",
596 "Maintainer",
597 "Original-Maintainer",
598 "Architecture",
599 "Source",
600 "Version",
601 "Revision", // Obsolete
602 "Config-Version", // Obsolete
603 "Replaces",
604 "Provides",
605 "Depends",
606 "Pre-Depends",
607 "Recommends",
608 "Suggests",
609 "Conflicts",
610 "Breaks",
611 "Conffiles",
612 "Filename",
613 "Size",
614 "MD5sum",
615 "SHA1",
616 "SHA256",
617 "SHA512",
618 "MSDOS-Filename", // Obsolete
619 "Description",
620 0};
621 static const char *iTFRewriteSourceOrder[] = {"Package",
622 "Source",
623 "Binary",
624 "Version",
625 "Priority",
626 "Section",
627 "Maintainer",
628 "Original-Maintainer",
629 "Build-Depends",
630 "Build-Depends-Indep",
631 "Build-Conflicts",
632 "Build-Conflicts-Indep",
633 "Architecture",
634 "Standards-Version",
635 "Format",
636 "Directory",
637 "Files",
638 0};
639
640 /* Two levels of initialization are used because gcc will set the symbol
641 size of an array to the length of the array, causing dynamic relinking
642 errors. Doing this makes the symbol size constant */
643 const char **TFRewritePackageOrder = iTFRewritePackageOrder;
644 const char **TFRewriteSourceOrder = iTFRewriteSourceOrder;
645
646 bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
647 TFRewriteData *Rewrite)
648 {
649 unsigned char Visited[256]; // Bit 1 is Order, Bit 2 is Rewrite
650 for (unsigned I = 0; I != 256; I++)
651 Visited[I] = 0;
652
653 // Set new tag up as necessary.
654 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
655 {
656 if (Rewrite[J].NewTag == 0)
657 Rewrite[J].NewTag = Rewrite[J].Tag;
658 }
659
660 // Write all all of the tags, in order.
661 if (Order != NULL)
662 {
663 for (unsigned int I = 0; Order[I] != 0; I++)
664 {
665 bool Rewritten = false;
666
667 // See if this is a field that needs to be rewritten
668 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
669 {
670 if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0)
671 {
672 Visited[J] |= 2;
673 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
674 {
675 if (isspace(Rewrite[J].Rewrite[0]))
676 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
677 else
678 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
679 }
680 Rewritten = true;
681 break;
682 }
683 }
684
685 // See if it is in the fragment
686 unsigned Pos;
687 if (Tags.Find(Order[I],Pos) == false)
688 continue;
689 Visited[Pos] |= 1;
690
691 if (Rewritten == true)
692 continue;
693
694 /* Write out this element, taking a moment to rewrite the tag
695 in case of changes of case. */
696 const char *Start;
697 const char *Stop;
698 Tags.Get(Start,Stop,Pos);
699
700 if (fputs(Order[I],Output) < 0)
701 return _error->Errno("fputs","IO Error to output");
702 Start += strlen(Order[I]);
703 if (fwrite(Start,Stop - Start,1,Output) != 1)
704 return _error->Errno("fwrite","IO Error to output");
705 if (Stop[-1] != '\n')
706 fprintf(Output,"\n");
707 }
708 }
709
710 // Now write all the old tags that were missed.
711 for (unsigned int I = 0; I != Tags.Count(); I++)
712 {
713 if ((Visited[I] & 1) == 1)
714 continue;
715
716 const char *Start;
717 const char *Stop;
718 Tags.Get(Start,Stop,I);
719 const char *End = Start;
720 for (; End < Stop && *End != ':'; End++);
721
722 // See if this is a field that needs to be rewritten
723 bool Rewritten = false;
724 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
725 {
726 if (stringcasecmp(Start,End,Rewrite[J].Tag) == 0)
727 {
728 Visited[J] |= 2;
729 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
730 {
731 if (isspace(Rewrite[J].Rewrite[0]))
732 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
733 else
734 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
735 }
736
737 Rewritten = true;
738 break;
739 }
740 }
741
742 if (Rewritten == true)
743 continue;
744
745 // Write out this element
746 if (fwrite(Start,Stop - Start,1,Output) != 1)
747 return _error->Errno("fwrite","IO Error to output");
748 if (Stop[-1] != '\n')
749 fprintf(Output,"\n");
750 }
751
752 // Now write all the rewrites that were missed
753 for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
754 {
755 if ((Visited[J] & 2) == 2)
756 continue;
757
758 if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
759 {
760 if (isspace(Rewrite[J].Rewrite[0]))
761 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
762 else
763 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
764 }
765 }
766
767 return true;
768 }
769 /*}}}*/
770
771 pkgTagSection::~pkgTagSection() {}