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