- more cleanup and return type fixes
[platform/upstream/libsolv.git] / ext / 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) 2008, 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 <ctype.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <assert.h>
24 #include <dirent.h>
25 #include <expat.h>
26
27 #include "pool.h"
28 #include "repo.h"
29 #include "util.h"
30 #define DISABLE_SPLIT
31 #include "tools_util.h"
32 #include "repo_content.h"
33 #include "repo_zyppdb.h"
34 #include "repo_products.h"
35 #include "repo_releasefile_products.h"
36
37
38 enum state {
39   STATE_START,
40   STATE_PRODUCT,
41   STATE_VENDOR,
42   STATE_NAME,
43   STATE_VERSION,
44   STATE_RELEASE,
45   STATE_ARCH,
46   STATE_SUMMARY,
47   STATE_SHORTSUMMARY,
48   STATE_DESCRIPTION,
49   STATE_UPDATEREPOKEY,
50   STATE_CPEID,
51   STATE_URLS,
52   STATE_URL,
53   STATE_RUNTIMECONFIG,
54   STATE_LINGUAS,
55   STATE_LANG,
56   STATE_REGISTER,
57   STATE_TARGET,
58   STATE_REGRELEASE,
59   STATE_PRODUCTLINE,
60   NUMSTATES
61 };
62
63 struct stateswitch {
64   enum state from;
65   char *ename;
66   enum state to;
67   int docontent;
68 };
69
70 /* !! must be sorted by first column !! */
71 static struct stateswitch stateswitches[] = {
72   { STATE_START,     "product",       STATE_PRODUCT,       0 },
73   { STATE_PRODUCT,   "vendor",        STATE_VENDOR,        1 },
74   { STATE_PRODUCT,   "name",          STATE_NAME,          1 },
75   { STATE_PRODUCT,   "version",       STATE_VERSION,       1 },
76   { STATE_PRODUCT,   "release",       STATE_RELEASE,       1 },
77   { STATE_PRODUCT,   "arch",          STATE_ARCH,          1 },
78   { STATE_PRODUCT,   "productline",   STATE_PRODUCTLINE,   1 },
79   { STATE_PRODUCT,   "summary",       STATE_SUMMARY,       1 },
80   { STATE_PRODUCT,   "shortsummary",  STATE_SHORTSUMMARY,  1 },
81   { STATE_PRODUCT,   "description",   STATE_DESCRIPTION,   1 },
82   { STATE_PRODUCT,   "register",      STATE_REGISTER,      0 },
83   { STATE_PRODUCT,   "urls",          STATE_URLS,          0 },
84   { STATE_PRODUCT,   "runtimeconfig", STATE_RUNTIMECONFIG, 0 },
85   { STATE_PRODUCT,   "linguas",       STATE_LINGUAS,       0 },
86   { STATE_PRODUCT,   "updaterepokey", STATE_UPDATEREPOKEY, 1 },
87   { STATE_PRODUCT,   "cpeid",         STATE_CPEID,         1 },
88   { STATE_URLS,      "url",           STATE_URL,           1 },
89   { STATE_LINGUAS,   "lang",          STATE_LANG,          0 },
90   { STATE_REGISTER,  "target",        STATE_TARGET,        1 },
91   { STATE_REGISTER,  "release",       STATE_REGRELEASE,    1 },
92   { NUMSTATES }
93 };
94
95 struct parsedata {
96   const char *filename;
97   const char *basename;
98   int depth;
99   enum state state;
100   int statedepth;
101   char *content;
102   int lcontent;
103   int acontent;
104   int docontent;
105   Pool *pool;
106   Repo *repo;
107   Repodata *data;
108
109   struct stateswitch *swtab[NUMSTATES];
110   enum state sbtab[NUMSTATES];
111   struct joindata jd;
112
113   const char *tmplang;
114
115   const char *tmpvers;
116   const char *tmprel;
117   Id urltype;
118
119   unsigned int ctime;
120
121   Solvable *solvable;
122   Id handle;
123
124   ino_t baseproduct;
125   ino_t currentproduct;
126   int productscheme;
127 };
128
129
130 /*
131  * find_attr
132  * find value for xml attribute
133  * I: txt, name of attribute
134  * I: atts, list of key/value attributes
135  * O: pointer to value of matching key, or NULL
136  *
137  */
138
139 static inline const char *
140 find_attr(const char *txt, const char **atts)
141 {
142   for (; *atts; atts += 2)
143     {
144       if (!strcmp(*atts, txt))
145         return atts[1];
146     }
147   return 0;
148 }
149
150
151 /*
152  * XML callback: startElement
153  */
154
155 static void XMLCALL
156 startElement(void *userData, const char *name, const char **atts)
157 {
158   struct parsedata *pd = userData;
159   Pool *pool = pd->pool;
160   Solvable *s = pd->solvable;
161   struct stateswitch *sw;
162
163 #if 0
164   fprintf(stderr, "start: [%d]%s\n", pd->state, name);
165 #endif
166   if (pd->depth != pd->statedepth)
167     {
168       pd->depth++;
169       return;
170     }
171
172   pd->depth++;
173   if (!pd->swtab[pd->state])    /* no statetable -> no substates */
174     {
175 #if 0
176       fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
177 #endif
178       return;
179     }
180   for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++)  /* find name in statetable */
181     if (!strcmp(sw->ename, name))
182       break;
183
184   if (sw->from != pd->state)
185     {
186 #if 0
187       fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
188 #endif
189       return;
190     }
191   pd->state = sw->to;
192   pd->docontent = sw->docontent;
193   pd->statedepth = pd->depth;
194   pd->lcontent = 0;
195   *pd->content = 0;
196
197   switch(pd->state)
198     {
199     case STATE_PRODUCT:
200       /* parse 'schemeversion' and store in global variable */
201       {
202         const char * scheme = find_attr("schemeversion", atts);
203         pd->productscheme = (scheme && *scheme) ? atoi(scheme) : -1;
204       }
205       if (!s)
206         {
207           s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
208           pd->handle = s - pool->solvables;
209         }
210       break;
211
212       /* <summary lang="xy">... */
213     case STATE_SUMMARY:
214     case STATE_DESCRIPTION:
215       pd->tmplang = join_dup(&pd->jd, find_attr("lang", atts));
216       break;
217     case STATE_URL:
218       pd->urltype = pool_str2id(pd->pool, find_attr("name", atts), 1);
219       break;
220     default:
221       break;
222     }
223 }
224
225
226 static void XMLCALL
227 endElement(void *userData, const char *name)
228 {
229   struct parsedata *pd = userData;
230   Solvable *s = pd->solvable;
231
232 #if 0
233   fprintf(stderr, "end: [%d]%s\n", pd->state, name);
234 #endif
235   if (pd->depth != pd->statedepth)
236     {
237       pd->depth--;
238 #if 0
239       fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
240 #endif
241       return;
242     }
243
244   pd->depth--;
245   pd->statedepth--;
246
247   switch (pd->state)
248     {
249     case STATE_PRODUCT:
250       /* product done, finish solvable */
251       if (pd->ctime)
252         repodata_set_num(pd->data, pd->handle, SOLVABLE_INSTALLTIME, pd->ctime);
253
254       if (pd->basename)
255         repodata_set_str(pd->data, pd->handle, PRODUCT_REFERENCEFILE, pd->basename);
256
257       /* this is where <productsdir>/baseproduct points to */
258       if (pd->currentproduct == pd->baseproduct)
259         repodata_set_str(pd->data, pd->handle, PRODUCT_TYPE, "base");
260
261       if (pd->tmprel)
262         {
263           if (pd->tmpvers)
264             s->evr = makeevr(pd->pool, join2(&pd->jd, pd->tmpvers, "-", pd->tmprel));
265           else
266             {
267               fprintf(stderr, "Seen <release> but no <version>\n");
268             }
269         }
270       else if (pd->tmpvers)
271         s->evr = makeevr(pd->pool, pd->tmpvers); /* just version, no release */
272       pd->tmpvers = solv_free((void *)pd->tmpvers);
273       pd->tmprel = solv_free((void *)pd->tmprel);
274       if (!s->arch)
275         s->arch = ARCH_NOARCH;
276       if (!s->evr)
277         s->evr = ID_EMPTY;
278       if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
279         s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pd->pool, s->name, s->evr, REL_EQ, 1), 0);
280       pd->solvable = 0;
281       break;
282     case STATE_VENDOR:
283       s->vendor = pool_str2id(pd->pool, pd->content, 1);
284       break;
285     case STATE_NAME:
286       s->name = pool_str2id(pd->pool, join2(&pd->jd, "product", ":", pd->content), 1);
287       break;
288     case STATE_VERSION:
289       pd->tmpvers = solv_strdup(pd->content);
290       break;
291     case STATE_RELEASE:
292       pd->tmprel = solv_strdup(pd->content);
293       break;
294     case STATE_ARCH:
295       s->arch = pool_str2id(pd->pool, pd->content, 1);
296       break;
297     case STATE_PRODUCTLINE:
298       repodata_set_str(pd->data, pd->handle, PRODUCT_PRODUCTLINE, pd->content);
299     break;
300     case STATE_UPDATEREPOKEY:
301       /** obsolete **/
302       break;
303     case STATE_SUMMARY:
304       repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_SUMMARY, pd->tmplang, 1), pd->content);
305       break;
306     case STATE_SHORTSUMMARY:
307       repodata_set_str(pd->data, pd->handle, PRODUCT_SHORTLABEL, pd->content);
308       break;
309     case STATE_DESCRIPTION:
310       repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_DESCRIPTION, pd->tmplang, 1), pd->content);
311       break;
312     case STATE_URL:
313       if (pd->urltype)
314         {
315           repodata_add_poolstr_array(pd->data, pd->handle, PRODUCT_URL, pd->content);
316           repodata_add_idarray(pd->data, pd->handle, PRODUCT_URL_TYPE, pd->urltype);
317         }
318       break;
319     case STATE_TARGET:
320       repodata_set_str(pd->data, pd->handle, PRODUCT_REGISTER_TARGET, pd->content);
321       break;
322     case STATE_REGRELEASE:
323       repodata_set_str(pd->data, pd->handle, PRODUCT_REGISTER_RELEASE, pd->content);
324       break;
325     case STATE_CPEID:
326       if (pd->content)
327         repodata_set_str(pd->data, pd->handle, SOLVABLE_CPEID, pd->content);
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 }
339
340
341 static void XMLCALL
342 characterData(void *userData, const XML_Char *s, int len)
343 {
344   struct parsedata *pd = userData;
345   int l;
346   char *c;
347   if (!pd->docontent)
348     return;
349   l = pd->lcontent + len + 1;
350   if (l > pd->acontent)
351     {
352       pd->content = solv_realloc(pd->content, l + 256);
353       pd->acontent = l + 256;
354     }
355   c = pd->content + pd->lcontent;
356   pd->lcontent += len;
357   while (len-- > 0)
358     *c++ = *s++;
359   *c = 0;
360 }
361
362 #define BUFF_SIZE 8192
363
364
365 /*
366  * add single product to repo
367  *
368  */
369
370 static void
371 add_code11_product(struct parsedata *pd, FILE *fp)
372 {
373   char buf[BUFF_SIZE];
374   int l;
375   struct stat st;
376   XML_Parser parser;
377
378   if (!fstat(fileno(fp), &st))
379     {
380       pd->currentproduct = st.st_ino;
381       pd->ctime = (unsigned int)st.st_ctime;
382     }
383   else
384     {
385       pd->currentproduct = pd->baseproduct + 1; /* make it != baseproduct if stat fails */
386       perror("fstat");
387       pd->ctime = 0;
388     }
389
390   parser = XML_ParserCreate(NULL);
391   XML_SetUserData(parser, pd);
392   XML_SetElementHandler(parser, startElement, endElement);
393   XML_SetCharacterDataHandler(parser, characterData);
394
395   for (;;)
396     {
397       l = fread(buf, 1, sizeof(buf), fp);
398       if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
399         {
400           pool_debug(pd->pool, SOLV_ERROR, "%s: %s at line %u:%u\n", pd->filename, XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
401           pool_debug(pd->pool, SOLV_ERROR, "skipping this product\n");
402           XML_ParserFree(parser);
403           return;
404         }
405       if (l == 0)
406         break;
407     }
408   XML_ParserFree(parser);
409 }
410
411
412 int
413 repo_add_code11_products(Repo *repo, const char *dirpath, int flags)
414 {
415   Repodata *data;
416   struct parsedata pd;
417   struct stateswitch *sw;
418   DIR *dir;
419   int i;
420
421   data = repo_add_repodata(repo, flags);
422
423   memset(&pd, 0, sizeof(pd));
424   pd.repo = repo;
425   pd.pool = repo->pool;
426   pd.data = data;
427
428   pd.content = solv_malloc(256);
429   pd.acontent = 256;
430
431   for (i = 0, sw = stateswitches; sw->from != NUMSTATES; i++, sw++)
432     {
433       if (!pd.swtab[sw->from])
434         pd.swtab[sw->from] = sw;
435       pd.sbtab[sw->to] = sw->from;
436     }
437
438   dir = opendir(dirpath);
439   if (dir)
440     {
441       struct dirent *entry;
442       struct stat st;
443       char *fullpath;
444
445       /* check for <productsdir>/baseproduct on code11 and remember its target inode */
446       if (stat(join2(&pd.jd, dirpath, "/", "baseproduct"), &st) == 0) /* follow symlink */
447         pd.baseproduct = st.st_ino;
448       else
449         pd.baseproduct = 0;
450
451       while ((entry = readdir(dir)))
452         {
453           int len = strlen(entry->d_name);
454           FILE *fp;
455           if (len <= 5 || strcmp(entry->d_name + len - 5, ".prod") != 0)
456             continue;
457           fullpath = join2(&pd.jd, dirpath, "/", entry->d_name);
458           fp = fopen(fullpath, "r");
459           if (!fp)
460             {
461               perror(fullpath);
462               continue;
463             }
464           pd.filename = fullpath;
465           pd.basename = entry->d_name;
466           add_code11_product(&pd, fp);
467           fclose(fp);
468         }
469       closedir(dir);
470     }
471   solv_free(pd.content);
472   join_freemem(&pd.jd);
473
474   if (!(flags & REPO_NO_INTERNALIZE))
475     repodata_internalize(data);
476   return 0;
477 }
478
479
480 /******************************************************************************************/
481
482
483 /*
484  * read all installed products
485  *
486  * try proddir (reading all .xml files from this directory) first
487  * if not available, assume non-code11 layout and parse /etc/xyz-release
488  *
489  * parse each one as a product
490  */
491
492 /* Oh joy! Three parsers for the price of one! */
493
494 int
495 repo_add_products(Repo *repo, const char *proddir, const char *root, int flags)
496 {
497   char *fullpath;
498   DIR *dir;
499   int ret;
500
501   if (proddir)
502     {
503       dir = opendir(proddir);
504       if (dir)
505         {
506           /* assume code11 stype products */
507           closedir(dir);
508           return repo_add_code11_products(repo, proddir, flags);
509         }
510     }
511
512   /* code11 didn't work, try old code10 zyppdb */
513   fullpath = solv_dupjoin(root ? root : 0, "/var/lib/zypp/db/products", 0);
514   dir = opendir(fullpath);
515   if (dir)
516     {
517       closedir(dir);
518       /* assume code10 style products */
519       ret = repo_add_zyppdb_products(repo, fullpath, flags);
520       solv_free(fullpath);
521       return ret;
522     }
523   solv_free(fullpath);
524
525   /* code10/11 didn't work, try -release files parsing */
526   fullpath = solv_dupjoin(root ? root : 0, "/etc", 0);
527   dir = opendir(fullpath);
528   if (dir)
529     {
530       closedir(dir);
531       ret = repo_add_releasefile_products(repo, fullpath, flags);
532       solv_free(fullpath);
533       return ret;
534     }
535
536   /* no luck. print an error message in case the root argument is wrong */
537   perror(fullpath);
538   solv_free(fullpath);
539
540   /* the least we can do... */
541   if (!(flags & REPO_NO_INTERNALIZE) && (flags & REPO_REUSE_REPODATA) != 0)
542     repodata_internalize(repo_last_repodata(repo));
543   return 0;
544 }
545
546 /* EOF */