backup checkin, still not fully functional
[platform/upstream/libsolv.git] / tools / repo_products.c
1 /*
2  * repo_products.c
3  * 
4  * Parses all files below 'proddir'
5  * See http://en.opensuse.org/Product_Management/Code11
6  * 
7  * 
8  * Copyright (c) 2007, Novell Inc.
9  *
10  * This program is licensed under the BSD license, read LICENSE.BSD
11  * for further information
12  */
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <limits.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <dirent.h>
24 #include <expat.h>
25
26 #include "pool.h"
27 #include "repo.h"
28 #include "util.h"
29 #define DISABLE_SPLIT
30 #include "tools_util.h"
31 #include "repo_content.h"
32
33
34 static ino_t baseproduct = 0;
35 static ino_t currentproduct = 0;
36
37 //#define DUMPOUT 0
38
39 enum state {
40   STATE_START,           // 0
41   STATE_PRODUCT,         // 1
42   STATE_GENERAL,         // 2
43   STATE_VENDOR,          // 3
44   STATE_NAME,            // 4
45   STATE_VERSION,         // 5
46   STATE_RELEASE,         // 6
47   STATE_SUMMARY,         // 7
48   STATE_DESCRIPTION,     // 8
49   STATE_DISTRIBUTION,    // 9
50   STATE_FLAVOR,          // 10
51   STATE_URLS,            // 11
52   STATE_URL,             // 12
53   STATE_UPDATEREPOKEY,   // 13
54   STATE_BUILDCONFIG,     // 14
55   STATE_INSTALLCONFIG,   // 15
56   STATE_RUNTIMECONFIG,   // 16
57   STATE_LINGUAS,         // 17
58   STATE_LANG,            // 18
59   NUMSTATES              // 0
60 };
61
62 struct stateswitch {
63   enum state from;
64   char *ename;
65   enum state to;
66   int docontent;
67 };
68
69 /* !! must be sorted by first column !! */
70 static struct stateswitch stateswitches[] = {
71   { STATE_START,     "product",       STATE_PRODUCT,       0 },
72   { STATE_PRODUCT,   "general",       STATE_GENERAL,       0 },
73   { STATE_GENERAL,   "vendor",        STATE_VENDOR,        1 },
74   { STATE_GENERAL,   "name",          STATE_NAME,          1 },
75   { STATE_GENERAL,   "version",       STATE_VERSION,       1 },
76   { STATE_GENERAL,   "release",       STATE_RELEASE,       1 },
77   { STATE_GENERAL,   "summary",       STATE_SUMMARY,       1 },
78   { STATE_GENERAL,   "description",   STATE_DESCRIPTION,   1 },
79   { STATE_GENERAL,   "distribution",  STATE_DISTRIBUTION,  0 },
80   { STATE_GENERAL,   "urls",          STATE_URLS,          0 },
81   { STATE_GENERAL,   "runtimeconfig", STATE_RUNTIMECONFIG, 0 },
82   { STATE_GENERAL,   "installconfig", STATE_INSTALLCONFIG, 0 },
83   { STATE_GENERAL,   "buildconfig",   STATE_BUILDCONFIG,   0 },
84   { STATE_GENERAL,   "linguas",       STATE_LINGUAS,       0 },
85   { STATE_GENERAL,   "update_repo_key", STATE_UPDATEREPOKEY,   0 },
86   { STATE_URLS,      "url",           STATE_URL,           0 },
87 /*  { STATE_BUILDCONFIG,"linguas",      STATE_LINGUAS,       0 }, */
88   { STATE_LINGUAS,   "lang",          STATE_LANG,          0 },
89   { NUMSTATES }
90 };
91
92 struct parsedata {
93   int depth;
94   enum state state;
95   int statedepth;
96   char *content;
97   int lcontent;
98   int acontent;
99   int docontent;
100   Pool *pool;
101   Repo *repo;
102   Repodata *data;
103   int datanum;
104   
105   struct stateswitch *swtab[NUMSTATES];
106   enum state sbtab[NUMSTATES];
107
108   const char *attribute; /* only print this attribute, if currentproduct == baseproduct */
109
110   const char *tmplang;
111   const char *tmpvers;
112   const char *tmprel;
113
114   Solvable *s;
115   Id handle;
116
117   Id langcache[ID_NUM_INTERNAL];
118 };
119
120
121 /*
122  * find_attr
123  * find value for xml attribute
124  * I: txt, name of attribute
125  * I: atts, list of key/value attributes
126  * I: dup, strdup it
127  * O: pointer to value of matching key, or NULL
128  * 
129  */
130
131 static inline const char *
132 find_attr(const char *txt, const char **atts, int dup)
133 {
134   for (; *atts; atts += 2)
135     {
136       if (!strcmp(*atts, txt))
137         return dup ? strdup(atts[1]) : atts[1];
138     }
139   return 0;
140 }
141
142
143 /*
144  * create localized tag
145  */
146
147 static Id
148 langtag(struct parsedata *pd, Id tag, const char *language)
149 {
150   if (language && !language[0])
151     language = 0;
152   if (!language || tag >= ID_NUM_INTERNAL)
153     return pool_id2langid(pd->repo->pool, tag, language, 1);
154   if (!pd->langcache[tag])
155     pd->langcache[tag] = pool_id2langid(pd->repo->pool, tag, language, 1);
156   return pd->langcache[tag];
157 }
158
159
160 /*
161  * XML callback: startElement
162  */
163
164 static void XMLCALL
165 startElement(void *userData, const char *name, const char **atts)
166 {
167   struct parsedata *pd = userData;
168   Pool *pool = pd->pool;
169   struct stateswitch *sw;
170
171 #if 0
172       fprintf(stderr, "start: [%d]%s\n", pd->state, name);
173 #endif
174   if (pd->depth != pd->statedepth)
175     {
176       pd->depth++;
177       return;
178     }
179
180   pd->depth++;
181   for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++)  /* find name in statetable */
182     if (!strcmp(sw->ename, name))
183       break;
184   
185   if (sw->from != pd->state)
186     {
187 #if 1
188       fprintf(stderr, "into unknown: [%d]%s (from: %d, state %d)\n", sw->to, name, sw->from, pd->state);
189       exit( 1 );
190 #endif
191       return;
192     }
193   pd->state = sw->to;
194   pd->docontent = sw->docontent;
195   pd->statedepth = pd->depth;
196   pd->lcontent = 0;
197   *pd->content = 0;
198
199   switch(pd->state)
200     {
201       case STATE_START:
202           break;
203     case STATE_PRODUCT:
204       if (!pd->s)
205         {
206           
207           pd->s = pool_id2solvable(pool, repo_add_solvable(pd->repo));
208           repodata_extend(pd->data, pd->s - pool->solvables);
209           pd->handle = repodata_get_handle(pd->data, pd->s - pool->solvables - pd->repo->start);
210         }
211      break;
212
213       /* <summary lang="xy">... */
214     case STATE_SUMMARY:
215       pd->tmplang = find_attr("lang", atts, 1);
216       break;
217     case STATE_DESCRIPTION:
218       pd->tmplang = find_attr("lang", atts, 1);
219       break;
220     case STATE_DISTRIBUTION:
221         {
222           const char *str;
223           if ((str = find_attr("flavor", atts, 0)))
224             repo_set_str(pd->repo, pd->s - pool->solvables, PRODUCT_FLAVOR, str);
225           if ((str = find_attr("target", atts, 0)))
226             {
227               if (currentproduct == baseproduct
228                   && pd->attribute
229                   && !strcmp(pd->attribute, "distribution.target"))
230                 printf("%s\n", str);
231               else
232                 repo_set_str(pd->repo, pd->s - pool->solvables, SOLVABLE_DISTRIBUTION, str);
233             }
234         }
235       break;
236     case STATE_URLS:
237     case STATE_URL:
238     case STATE_RUNTIMECONFIG:
239       default:
240       break;
241     }
242   return;
243 }
244
245
246 static void XMLCALL
247 endElement(void *userData, const char *name)
248 {
249   struct parsedata *pd = userData;
250
251 #if 0
252       fprintf(stderr, "end: [%d]%s\n", pd->state, name);
253 #endif
254   if (pd->depth != pd->statedepth)
255     {
256       pd->depth--;
257 #if 1
258       fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
259 #endif
260       return;
261     }
262
263   pd->depth--;
264   pd->statedepth--;
265
266   switch (pd->state)
267     {
268     case STATE_VENDOR:
269       pd->s->vendor = str2id(pd->pool, pd->content, 1);
270       break;
271     case STATE_NAME:
272       pd->s->name = str2id(pd->pool, join2("product", ":", pd->content), 1);
273       break;
274     case STATE_VERSION:
275       pd->tmpvers = strdup(pd->content);
276       break;
277     case STATE_RELEASE:
278       pd->tmprel = strdup(pd->content);
279       break;
280     case STATE_SUMMARY:
281       repodata_set_str(pd->data, pd->handle, langtag(pd, SOLVABLE_SUMMARY, pd->tmplang), pd->content);
282       if (pd->tmplang) 
283       {
284         free( (char *)pd->tmplang );
285         pd->tmplang = 0;
286       }
287       break;
288     case STATE_DESCRIPTION:
289       repodata_set_str(pd->data, pd->handle, langtag(pd, SOLVABLE_DESCRIPTION, pd->tmplang), pd->content );
290       if (pd->tmplang) 
291       {
292         free( (char *)pd->tmplang );
293         pd->tmplang = 0;
294       }
295       break;
296     case STATE_DISTRIBUTION:
297       break;
298     case STATE_URL:
299       break;
300     case STATE_RUNTIMECONFIG:
301       break;
302     default:
303       break;
304     }
305   
306   pd->state = pd->sbtab[pd->state];
307   pd->docontent = 0;
308   
309 #if 0
310       fprintf(stderr, "end: [%s] -> %d\n", name, pd->state);
311 #endif
312   return;
313 }
314
315
316 static void XMLCALL
317 characterData(void *userData, const XML_Char *s, int len)
318 {
319   struct parsedata *pd = userData;
320   int l;
321   char *c;
322   if (!pd->docontent) {
323 #if 0
324     char *dup = strndup( s, len );
325   fprintf(stderr, "Content: [%d]'%s'\n", pd->state, dup );
326   free( dup );
327 #endif
328     return;
329   }
330   l = pd->lcontent + len + 1;
331   if (l > pd->acontent)
332     {
333       pd->content = realloc(pd->content, l + 256);
334       pd->acontent = l + 256;
335     }
336   c = pd->content + pd->lcontent;
337   pd->lcontent += len;
338   while (len-- > 0)
339     *c++ = *s++;
340   *c = 0;
341 }
342
343 #define BUFF_SIZE 8192
344
345
346 /*
347  * add single product to repo
348  *
349  */
350
351 static void
352 repo_add_product(struct parsedata *pd, Repodata *data, FILE *fp, int code11)
353 {
354   Pool *pool = pd->pool;
355   char buf[BUFF_SIZE];
356   int i, l;
357   struct stateswitch *sw;
358   struct stat st;
359
360   if (!fstat(fileno(fp), &st))
361     currentproduct = st.st_ino;
362   else 
363     {
364       currentproduct = baseproduct+1; /* make it != baseproduct if stat fails */
365       st.st_ctime = 0;
366       perror("Can't stat()");
367     }
368   
369   for (i = 0, sw = stateswitches; sw->from != NUMSTATES; i++, sw++)
370     {
371       if (!pd->swtab[sw->from])
372         pd->swtab[sw->from] = sw;
373       pd->sbtab[sw->to] = sw->from;
374     }
375   XML_Parser parser = XML_ParserCreate(NULL);
376   XML_SetUserData(parser, pd);
377   XML_SetElementHandler(parser, startElement, endElement);
378   XML_SetCharacterDataHandler(parser, characterData);
379   
380   for (;;)
381     {
382       l = fread(buf, 1, sizeof(buf), fp);
383       if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
384         {
385           fprintf(stderr, "repo_products: %s at line %u:%u\n", XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
386           exit(1);
387         }
388       if (l == 0)
389         break;
390     }
391   XML_ParserFree(parser);
392   
393   if (pd->s)
394     {
395       Solvable *s = pd->s;
396
397       if (st.st_ctime)
398         repodata_set_num(pd->data, pd->handle, SOLVABLE_INSTALLTIME, st.st_ctime);
399       /* this is where <productsdir>/baseproduct points to */
400       if (currentproduct == baseproduct)
401         repodata_set_str(pd->data, pd->handle, PRODUCT_TYPE, "base");
402       
403       if (pd->tmprel)
404         {
405           if (pd->tmpvers)
406             {
407               s->evr = makeevr(pool, join2(pd->tmpvers, "-", pd->tmprel));
408               free((char *)pd->tmpvers);
409               pd->tmpvers = 0;
410             }
411           else
412             {
413               fprintf(stderr, "Seen <release> but no <version>\n");
414             }
415           free((char *)pd->tmprel);
416           pd->tmprel = 0;
417         }
418       else if (pd->tmpvers)
419         {
420           s->evr = makeevr(pool, pd->tmpvers); /* just version, no release */
421           free((char *)pd->tmpvers);
422           pd->tmpvers = 0;
423         }
424       if (!s->arch)
425         s->arch = ARCH_NOARCH;
426       if (s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
427         {
428           s->provides = repo_addid_dep(pd->repo, s->provides, rel2id(pool, s->name, s->evr, REL_EQ, 1), 0);
429         }
430     } /* if pd->s */
431   return;
432 }
433
434
435 /*
436  * parse dir looking for files ending in suffix
437  */
438
439 static void
440 parse_dir(DIR *dir, const char *path, struct parsedata *pd, Repodata *repodata, int code11)
441 {
442   struct dirent *entry;
443   char *suffix = code11 ? ".prod" : "-release";
444   int slen = code11 ? 5 : 8;  /* strlen(".prod") : strlen("-release") */
445   struct stat st;
446   
447   /* check for <productsdir>/baseproduct on code11 and remember its target inode */
448   if (code11
449       && stat(join2(path, "/", "baseproduct"), &st) == 0) /* follow symlink */
450     {
451       baseproduct = st.st_ino;
452     }
453   else
454     baseproduct = 0;
455
456   while ((entry = readdir(dir)))
457     {
458       int len;
459       len = strlen(entry->d_name);
460
461       /* skip /etc/lsb-release, thats not a product per-se */
462       if (!code11
463           && strcmp(entry->d_name, "lsb-release") == 0)
464         {
465           continue;
466         }
467       
468       if (len > slen
469           && strcmp(entry->d_name+len-slen, suffix) == 0)
470         {
471           char *fullpath = join2(path, "/", entry->d_name);
472           FILE *fp = fopen(fullpath, "r");
473           if (!fp)
474             {
475               perror(fullpath);
476               break;
477             }
478           repo_add_product(pd, repodata, fp, code11);
479           fclose(fp);
480         }
481     }
482 }
483
484
485 /*
486  * read all installed products
487  * 
488  * try proddir (reading all .xml files from this directory) first
489  * if not available, assume non-code11 layout and parse /etc/xyz-release
490  *
491  * parse each one as a product
492  */
493
494 void
495 repo_add_products(Repo *repo, Repodata *repodata, const char *proddir, const char *root, const char *attribute)
496 {
497   const char *fullpath = proddir;
498   int code11 = 1;
499   DIR *dir = opendir(fullpath);
500   struct parsedata pd;
501   
502   memset(&pd, 0, sizeof(pd));
503   pd.repo = repo;
504   pd.pool = repo->pool;
505   pd.data = repo_add_repodata(pd.repo, 0);
506
507   pd.content = malloc(256);
508   pd.acontent = 256;
509
510   pd.attribute = attribute;
511
512   if (!dir)
513     {
514       fullpath = root ? join2(root, "", "/etc") : "/etc";
515       dir = opendir(fullpath);
516       code11 = 0;
517     }
518   if (!dir)
519     {
520       perror(fullpath);
521     }
522   else
523     {
524       parse_dir(dir, fullpath, &pd, repodata, code11);
525     }
526   
527   if (pd.data)
528     repodata_internalize(pd.data);
529
530   free(pd.content);
531   join_freemem();
532   closedir(dir);
533 }
534
535 /* EOF */