- initial import of revision 374 from cnc
[apt.git] / tools / genpkglist.cc
1 /*
2  * $Id: genpkglist.cc,v 1.7 2003/01/30 17:18:21 niemeyer Exp $
3  */
4 #include <alloca.h>
5 #include <ctype.h>
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <rpm/rpmlib.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <assert.h>
16
17 #include <map>
18 #include <iostream>
19
20 #include <apt-pkg/error.h>
21 #include <apt-pkg/tagfile.h>
22 #include <apt-pkg/configuration.h>
23 #include <apt-pkg/rpmhandler.h>
24
25 #include "cached_md5.h"
26
27 #if RPM_VERSION >= 0x040100
28 #include <rpm/rpmts.h>
29 #endif
30
31 #define CRPMTAG_TIMESTAMP   1012345
32
33 int tags[] =  {
34        RPMTAG_NAME, 
35        RPMTAG_EPOCH,
36        RPMTAG_VERSION,
37        RPMTAG_RELEASE,
38        RPMTAG_GROUP,
39        RPMTAG_ARCH,
40        RPMTAG_PACKAGER,
41        RPMTAG_SOURCERPM,
42        RPMTAG_SIZE,
43        RPMTAG_VENDOR,
44        RPMTAG_OS,
45        
46        RPMTAG_DESCRIPTION, 
47        RPMTAG_SUMMARY, 
48        /*RPMTAG_HEADERI18NTABLE*/ HEADER_I18NTABLE,
49        
50        RPMTAG_REQUIREFLAGS, 
51        RPMTAG_REQUIRENAME,
52        RPMTAG_REQUIREVERSION,
53        
54        RPMTAG_CONFLICTFLAGS,
55        RPMTAG_CONFLICTNAME,
56        RPMTAG_CONFLICTVERSION,
57        
58        RPMTAG_PROVIDENAME,
59        RPMTAG_PROVIDEFLAGS,
60        RPMTAG_PROVIDEVERSION,
61        
62        RPMTAG_OBSOLETENAME,
63        RPMTAG_OBSOLETEFLAGS,
64        RPMTAG_OBSOLETEVERSION,
65
66        RPMTAG_FILEFLAGS
67 };
68 int numTags = sizeof(tags) / sizeof(int);
69
70
71
72 typedef struct {
73    string importance;
74    string date;
75    string summary;
76    string url;
77 } UpdateInfo;
78
79
80 static inline int usefullFile(char *a)
81 {
82    int l = strlen(a);
83    
84    if (strstr(a, "bin") || strstr(a, "/etc") || strncmp(a, "/lib", 4) == 0)
85        return 1;
86    
87    if (l < 3)
88        return 0;
89    
90    if (strcmp(a + l - 3, ".so") == 0
91        || strstr(a, ".so."))
92        return 1;
93    return 0;
94 }
95
96
97 static void copyStrippedFileList(Header header, Header newHeader)
98 {
99    int i;
100    int i1, i2;
101    
102    int type1, type2, type3;
103    int count1, count2, count3;
104    char **dirnames = NULL, **basenames = NULL;
105    int_32 *dirindexes = NULL;
106    char **dnames, **bnames;
107    int_32 *dindexes;
108    int res1, res2, res3;
109    
110 #define FREE(a) if (a) free(a);
111    
112    res1 = headerGetEntry(header, RPMTAG_DIRNAMES, &type1, 
113                          (void**)&dirnames, &count1);
114    res2 = headerGetEntry(header, RPMTAG_BASENAMES, &type2, 
115                          (void**)&basenames, &count2);
116    res3 = headerGetEntry(header, RPMTAG_DIRINDEXES, &type3, 
117                          (void**)&dirindexes, &count3);
118    
119    if (res1 != 1 || res2 != 1 || res3 != 1) {
120       FREE(dirnames);
121       FREE(basenames);
122       return;
123    }
124
125    dnames = dirnames;
126    bnames = basenames;
127    dindexes = (int_32*)malloc(sizeof(int_32)*count3);
128    
129    i1 = 0;
130    i2 = 0;
131    for (i = 0; i < count2 ; i++) 
132    {
133       int ok = 0;
134       
135       ok = usefullFile(basenames[i]);
136       if (!ok) 
137           ok = usefullFile(dirnames[dirindexes[i]]);
138       
139       if (!ok) {
140          int k = i;
141          while (dirindexes[i] == dirindexes[k] && i < count2)
142              i++;
143          i--;
144          continue;
145       }
146       
147       
148       if (ok)
149       {
150          int j;
151          
152          bnames[i1] = basenames[i];
153          for (j = 0; j < i2; j++)
154          {
155             if (dnames[j] == dirnames[dirindexes[i]])
156             {
157                dindexes[i1] = j;
158                break;
159             }
160          }
161          if (j == i2) 
162          {
163             dnames[i2] = dirnames[dirindexes[i]];
164             dindexes[i1] = i2;
165             i2++;
166          }
167          assert(i2 <= count1);
168          i1++;
169       } 
170    }
171    
172    if (i1 == 0) {
173       FREE(dirnames);
174       FREE(basenames);
175       FREE(dindexes);
176       return;
177    }
178    
179    headerAddEntry(newHeader, RPMTAG_DIRNAMES, type1, dnames, i2);
180    
181    headerAddEntry(newHeader, RPMTAG_BASENAMES, type2, bnames, i1);
182    
183    headerAddEntry(newHeader, RPMTAG_DIRINDEXES, type3, dindexes, i1);
184    
185    FREE(dirnames);
186    FREE(basenames);
187    FREE(dindexes);
188 }
189
190
191
192
193
194 bool loadUpdateInfo(char *path, map<string,UpdateInfo> &map)
195 {
196    FileFd F(path, FileFd::ReadOnly);
197    if (_error->PendingError()) 
198    {
199       return false;
200    }
201    
202    pkgTagFile Tags(&F);
203    pkgTagSection Section;
204    
205    while (Tags.Step(Section)) 
206    {
207       string file = Section.FindS("File");
208       UpdateInfo info;
209
210       info.importance = Section.FindS("Importance");
211       info.date = Section.FindS("Date");
212       info.summary = Section.FindS("Summary");
213       info.url = Section.FindS("URL");
214
215       map[file] = info;
216    }
217    return true;
218 }
219
220 #if RPM_VERSION >= 0x040000
221 // No prototype from rpm after 4.0.
222 extern "C" {
223 int headerGetRawEntry(Header h, int_32 tag, int_32 * type,
224                       void *p, int_32 *c);
225 }
226 #endif
227
228 bool copyFields(Header h, Header newHeader,
229                 FILE *idxfile, const char *directory, char *filename,
230                 unsigned filesize, map<string,UpdateInfo> &updateInfo,
231                 bool fullFileList)
232 {
233    int i;
234    int_32 size[1];
235
236    size[0] = filesize;
237    
238    // the std tags
239    for (i = 0; i < numTags; i++) {
240       int_32 type, count;
241       void *data;
242       int res;
243       
244       // Copy raw entry, so that internationalized strings
245       // will get copied correctly.
246       res = headerGetRawEntry(h, tags[i], &type, &data, &count);
247       if (res != 1)
248          continue;
249       headerAddEntry(newHeader, tags[i], type, data, count);
250    }
251  
252    if (fullFileList) {
253       int type1, type2, type3;
254       int count1, count2, count3;
255       char **dnames, **bnames, **dindexes;
256       int res;
257    
258       res = headerGetEntry(h, RPMTAG_DIRNAMES, &type1, 
259                            (void**)&dnames, &count1);
260       res = headerGetEntry(h, RPMTAG_BASENAMES, &type2, 
261                            (void**)&bnames, &count2);
262       res = headerGetEntry(h, RPMTAG_DIRINDEXES, &type3, 
263                            (void**)&dindexes, &count3);
264
265       if (res == 1) {
266          headerAddEntry(newHeader, RPMTAG_DIRNAMES, type1, dnames, count1);
267          headerAddEntry(newHeader, RPMTAG_BASENAMES, type2, bnames, count2);
268          headerAddEntry(newHeader, RPMTAG_DIRINDEXES, type3, dindexes, count3);
269       }
270    } else {
271        copyStrippedFileList(h, newHeader);
272    }
273    
274    // update index of srpms
275    if (idxfile) {
276       int_32 type, count;
277       char *srpm;
278       char *name;
279       int res;
280       
281       res = headerGetEntry(h, RPMTAG_NAME, &type, 
282                            (void**)&name, &count);
283       res = headerGetEntry(h, RPMTAG_SOURCERPM, &type, 
284                            (void**)&srpm, &count);
285       if (res == 1) {
286          fprintf(idxfile, "%s %s\n", srpm, name);
287       }
288    }
289    // our additional tags
290    headerAddEntry(newHeader, CRPMTAG_DIRECTORY, RPM_STRING_TYPE,
291                   directory, 1);
292    headerAddEntry(newHeader, CRPMTAG_FILENAME, RPM_STRING_TYPE, 
293                   filename, 1);
294    headerAddEntry(newHeader, CRPMTAG_FILESIZE, RPM_INT32_TYPE,
295                   size, 1);
296       
297    // update description tags
298    if (updateInfo.find(string(filename)) != updateInfo.end()) {
299       const char *tmp;
300       string name = string(filename);
301       
302       tmp = updateInfo[name].summary.c_str();
303       headerAddEntry(newHeader, CRPMTAG_UPDATE_SUMMARY,
304                      RPM_STRING_TYPE,
305                      tmp, 1);
306       tmp = updateInfo[name].url.c_str();
307       headerAddEntry(newHeader, CRPMTAG_UPDATE_URL,
308                      RPM_STRING_TYPE,
309                      tmp, 1);
310       tmp = updateInfo[name].date.c_str();
311       headerAddEntry(newHeader, CRPMTAG_UPDATE_DATE,
312                      RPM_STRING_TYPE,
313                      tmp, 1);
314       tmp = updateInfo[name].importance.c_str();
315       headerAddEntry(newHeader, CRPMTAG_UPDATE_IMPORTANCE,
316                      RPM_STRING_TYPE,
317                      tmp, 1);
318    }
319    
320    return true;
321 }
322
323
324 int selectDirent(const struct dirent *ent)
325 {
326    int state = 0;
327    const char *p = ent->d_name;
328    
329    while (1) {
330       if (*p == '.') {
331           state = 1;
332       } else if (state == 1 && *p == 'r')
333           state++;
334       else if (state == 2 && *p == 'p')
335           state++;
336       else if (state == 3 && *p == 'm')
337           state++;
338       else if (state == 4 && *p == '\0')
339           return 1;
340       else if (*p == '\0')
341           return 0;
342       else
343           state = 0;
344       p++;
345    }
346 }
347
348
349 void usage()
350 {
351    cerr << "genpkglist " << VERSION << endl;
352    cerr << "usage: genpkglist [<options>] <dir> <suffix>" << endl;
353    cerr << "options:" << endl;
354    cerr << " --index <file>  file to write srpm index data to" << endl;
355    cerr << " --info <file>   file to read update info from" << endl;
356    cerr << " --meta <suffix> create package file list with given suffix" << endl;
357    cerr << " --bloat         do not strip the package file list. Needed for some" << endl;
358    cerr << "                 distributions that use non-automatically generated" << endl;
359    cerr << "                 file dependencies" << endl;
360    cerr << " --append        append to the package file list, don't overwrite" << endl;
361    cerr << " --progress      show a progress bar" << endl;
362    cerr << " --cachedir=DIR  use a custom directory for package md5sum cache"<<endl;
363 }
364
365
366
367 #ifndef HAVE_SCANDIR
368 // from glibc 1.09.1  mod'd by jmik, ins'd by asm, fix'd by sbi
369 int alphasort(const void * a, const void * b)
370 {
371   return strcmp ((*(struct dirent **) a)->d_name,
372                  (*(struct dirent **) b)->d_name);
373 }
374
375 int scandir(const char * dir, struct dirent *** namelist, 
376         int (* select)(struct dirent *), 
377         int (* cmp)(const void *, const void *))
378
379 {
380   DIR *dp = opendir (dir);
381   struct dirent **v = NULL;
382   size_t vsize = 0, i;
383   struct dirent *d;
384   int save;
385
386   if (dp == NULL)
387     return -1;
388
389   save = errno;
390   errno = 0;
391
392   i = 0;
393   while ((d = readdir (dp)) != NULL)
394     {
395     if (select == NULL || (*select) (d))
396       {
397         if (i == vsize)
398           {
399             struct dirent **newv;
400             if (vsize == 0)
401               vsize = 10;
402             else
403               vsize *= 2;
404             newv = (struct dirent **) realloc (v, vsize * sizeof (*v));
405             if (newv == NULL)
406               {
407               lose:
408                 errno = ENOMEM;
409                 break;
410               }
411             v = newv;
412           }
413
414         v[i] = (struct dirent *) malloc (d->d_reclen);
415         if (v[i] == NULL)
416           goto lose;
417
418         // *v[i++] = *d;
419         memcpy(v[i], d, d->d_reclen);
420         i++;
421       }
422     }
423
424   v[i] = NULL;
425
426   if (errno != 0)
427     {
428       save = errno;
429       (void) closedir (dp);
430       while (i > 0)
431         free (v[--i]);
432       free (v);
433       errno = save;
434       return -1;
435     }
436
437   (void) closedir (dp);
438   errno = save;
439
440   /* Sort the list if we have a comparison function to sort with.  */
441   if (cmp != NULL)
442     qsort (v, i, sizeof (struct dirent *), cmp);
443
444   *namelist = v;
445   return i;
446 }
447 // end of new stuff from glibc
448 #endif /* !HAVE_SCANDIR */
449
450
451 int main(int argc, char ** argv) 
452 {
453    string rpmsdir;
454    string pkglist_path;
455    FD_t outfd, fd;
456    struct dirent **dirEntries;
457    int entry_no, entry_cur;
458    map<string,UpdateInfo> updateInfo;
459    CachedMD5 *md5cache;
460    char *op_dir;
461    char *op_suf;
462    char *op_index = NULL;
463    char *op_update = NULL;
464    FILE *idxfile;
465    int i;
466    bool fullFileList = false;
467    bool progressBar = false;
468    const char *pkgListSuffix = NULL;
469    bool pkgListAppend = false;
470    
471    putenv("LC_ALL="); // Is this necessary yet (after i18n was supported)?
472    for (i = 1; i < argc; i++) {
473       if (strcmp(argv[i], "--index") == 0) {
474          i++;
475          if (i < argc) {
476             op_index = argv[i];
477          } else {
478             cout << "genpkglist: filename missing for option --index"<<endl;
479             exit(1);
480          }
481       } else if (strcmp(argv[i], "--info") == 0) {
482          i++;
483          if (i < argc) {
484             op_update = argv[i];
485          } else {
486             cout << "genpkglist: filename missing for option --info"<<endl;
487             exit(1);
488          }
489       } else if (strcmp(argv[i], "--bloat") == 0) {
490          fullFileList = true;
491       } else if (strcmp(argv[i], "--progress") == 0) {
492          progressBar = true;
493       } else if (strcmp(argv[i], "--append") == 0) {
494          pkgListAppend = true;
495       } else if (strcmp(argv[i], "--meta") == 0) {
496          i++;
497          if (i < argc) {
498             pkgListSuffix = argv[i];
499          } else {
500             cout << "genpkglist: argument missing for option --meta"<<endl;
501             exit(1);
502          }
503       } else if (strcmp(argv[i], "--cachedir") == 0) {
504          i++;
505          if (i < argc) {
506             _config->Set("Dir::Cache", argv[i]);
507          } else {
508             cout << "genpkglist: argument missing for option --cachedir"<<endl;
509             exit(1);
510          }
511       } else {
512          break;
513       }
514    }
515    if (argc - i > 0)
516        op_dir = argv[i++];
517    else {
518       usage();
519       exit(1);
520    }
521    if (argc - i > 0)
522        op_suf = argv[i++];
523    else {
524       usage();
525       exit(1);
526    }
527    if (argc != i) {
528       usage();
529    }
530    
531    if (op_update) {
532       if (!loadUpdateInfo(op_update, updateInfo)) {
533          cerr << "genpkglist: error reading update info from file " << op_update << endl;
534          _error->DumpErrors();
535          exit(1);
536       }
537    }
538    if (op_index) {
539       idxfile = fopen(op_index, "w+");
540       if (!idxfile) {
541          cerr << "genpkglist: could not open " << op_index << " for writing";
542          perror("");
543          exit(1);
544       }
545    } else {
546       idxfile = NULL;
547    }
548    
549    {
550       char cwd[200];
551       
552       getcwd(cwd, 200);
553       if (*op_dir != '/') {
554          rpmsdir = string(cwd) + "/" + string(op_dir);
555       } else {
556          rpmsdir = string(op_dir);
557       }
558    }
559    pkglist_path = string(rpmsdir);
560    rpmsdir = rpmsdir + "/RPMS." + string(op_suf);
561
562    string dirtag = "RPMS." + string(op_suf);
563
564    entry_no = scandir(rpmsdir.c_str(), &dirEntries, selectDirent, alphasort);
565    if (entry_no < 0) {
566       cerr << "genpkglist: error opening directory " << rpmsdir << ":"
567           << strerror(errno);
568       return 1;
569    }
570    
571    chdir(rpmsdir.c_str());
572    
573    if (pkgListSuffix != NULL)
574            pkglist_path = pkglist_path + "/base/pkglist." + pkgListSuffix;
575    else
576            pkglist_path = pkglist_path + "/base/pkglist." + op_suf;
577    
578    
579    if (pkgListAppend == true && FileExists(pkglist_path)) {
580       outfd = fdOpen(pkglist_path.c_str(), O_WRONLY|O_APPEND, 0644);
581    } else {
582       unlink(pkglist_path.c_str());
583       outfd = fdOpen(pkglist_path.c_str(), O_WRONLY|O_TRUNC|O_CREAT, 0644);
584    }
585    if (!outfd) {
586       cerr << "genpkglist: error creating file" << pkglist_path << ":"
587           << strerror(errno);
588       return 1;
589    }
590
591    md5cache = new CachedMD5(string(op_dir) + string(op_suf), "genpkglist");
592
593 #if RPM_VERSION >= 0x040100
594    rpmReadConfigFiles(NULL, NULL);
595    rpmts ts = rpmtsCreate();
596    rpmtsSetVSFlags(ts, (rpmVSFlags_e)-1);
597 #else
598    int isSource;
599 #endif   
600
601    for (entry_cur = 0; entry_cur < entry_no; entry_cur++) {
602       struct stat sb;
603
604       if (progressBar) {
605          if (entry_cur)
606             printf("\b\b\b\b\b\b\b\b\b\b");
607          printf(" %04i/%04i", entry_cur + 1, entry_no);
608          fflush(stdout);
609       }
610
611       if (stat(dirEntries[entry_cur]->d_name, &sb) < 0) {
612             cerr << "\nWarning: " << strerror(errno) << ": " << 
613                     dirEntries[entry_cur]->d_name << endl;
614             continue;
615       }
616
617       {
618          Header h;
619          int rc;
620          
621          fd = fdOpen(dirEntries[entry_cur]->d_name, O_RDONLY, 0666);
622
623          if (!fd) {
624             cerr << "\nWarning: " << strerror(errno) << ": " << 
625                     dirEntries[entry_cur]->d_name << endl;
626             continue;
627          }
628          
629 #if RPM_VERSION >= 0x040100
630          rc = rpmReadPackageFile(ts, fd, dirEntries[entry_cur]->d_name, &h);
631          if (rc == RPMRC_OK || rc == RPMRC_NOTTRUSTED || rc == RPMRC_NOKEY) {
632 #else
633          rc = rpmReadPackageHeader(fd, &h, &isSource, NULL, NULL);
634          if (rc == 0) {
635 #endif
636             Header newHeader;
637             char md5[34];
638             
639             newHeader = headerNew();
640             
641             copyFields(h, newHeader, idxfile, dirtag.c_str(),
642                        dirEntries[entry_cur]->d_name,
643                        sb.st_size, updateInfo, fullFileList);
644
645             md5cache->MD5ForFile(string(dirEntries[entry_cur]->d_name), 
646                                  sb.st_mtime, md5);
647             headerAddEntry(newHeader, CRPMTAG_MD5, RPM_STRING_TYPE, md5, 1);
648
649             headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
650             
651             headerFree(newHeader);
652             headerFree(h);
653          } else {
654             cerr << "\nWarning: Skipping malformed RPM: " << 
655                     dirEntries[entry_cur]->d_name << endl;
656          }
657          Fclose(fd);
658       }
659    }
660
661    Fclose(outfd);
662
663 #if RPM_VERSION >= 0x040100
664    ts = rpmtsFree(ts);
665 #endif
666    
667    delete md5cache;
668
669    return 0;
670 }