Imported Upstream version 0.6.27
[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 #define _GNU_SOURCE
15 #define _XOPEN_SOURCE
16 #include <time.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <assert.h>
25 #include <dirent.h>
26
27 #include "pool.h"
28 #include "repo.h"
29 #include "util.h"
30 #include "solv_xmlparser.h"
31 #define DISABLE_SPLIT
32 #include "tools_util.h"
33 #include "repo_content.h"
34 #include "repo_zyppdb.h"
35 #include "repo_products.h"
36 #include "repo_releasefile_products.h"
37
38
39 enum state {
40   STATE_START,
41   STATE_PRODUCT,
42   STATE_VENDOR,
43   STATE_NAME,
44   STATE_VERSION,
45   STATE_RELEASE,
46   STATE_ARCH,
47   STATE_SUMMARY,
48   STATE_SHORTSUMMARY,
49   STATE_DESCRIPTION,
50   STATE_UPDATEREPOKEY,
51   STATE_CPEID,
52   STATE_URLS,
53   STATE_URL,
54   STATE_RUNTIMECONFIG,
55   STATE_LINGUAS,
56   STATE_LANG,
57   STATE_REGISTER,
58   STATE_TARGET,
59   STATE_REGRELEASE,
60   STATE_REGFLAVOR,
61   STATE_PRODUCTLINE,
62   STATE_REGUPDATES,
63   STATE_REGUPDREPO,
64   STATE_ENDOFLIFE,
65   NUMSTATES
66 };
67
68 static struct solv_xmlparser_element stateswitches[] = {
69   { STATE_START,     "product",       STATE_PRODUCT,       0 },
70   { STATE_PRODUCT,   "vendor",        STATE_VENDOR,        1 },
71   { STATE_PRODUCT,   "name",          STATE_NAME,          1 },
72   { STATE_PRODUCT,   "version",       STATE_VERSION,       1 },
73   { STATE_PRODUCT,   "release",       STATE_RELEASE,       1 },
74   { STATE_PRODUCT,   "arch",          STATE_ARCH,          1 },
75   { STATE_PRODUCT,   "productline",   STATE_PRODUCTLINE,   1 },
76   { STATE_PRODUCT,   "summary",       STATE_SUMMARY,       1 },
77   { STATE_PRODUCT,   "shortsummary",  STATE_SHORTSUMMARY,  1 },
78   { STATE_PRODUCT,   "description",   STATE_DESCRIPTION,   1 },
79   { STATE_PRODUCT,   "register",      STATE_REGISTER,      0 },
80   { STATE_PRODUCT,   "urls",          STATE_URLS,          0 },
81   { STATE_PRODUCT,   "runtimeconfig", STATE_RUNTIMECONFIG, 0 },
82   { STATE_PRODUCT,   "linguas",       STATE_LINGUAS,       0 },
83   { STATE_PRODUCT,   "updaterepokey", STATE_UPDATEREPOKEY, 1 },
84   { STATE_PRODUCT,   "cpeid",         STATE_CPEID,         1 },
85   { STATE_PRODUCT,   "endoflife",     STATE_ENDOFLIFE,     1 },
86   { STATE_URLS,      "url",           STATE_URL,           1 },
87   { STATE_LINGUAS,   "lang",          STATE_LANG,          0 },
88   { STATE_REGISTER,  "target",        STATE_TARGET,        1 },
89   { STATE_REGISTER,  "release",       STATE_REGRELEASE,    1 },
90   { STATE_REGISTER,  "flavor",        STATE_REGFLAVOR,     1 },
91   { STATE_REGISTER,  "updates",       STATE_REGUPDATES,    0 },
92   { STATE_REGUPDATES, "repository",   STATE_REGUPDREPO,    0 },
93   { NUMSTATES }
94 };
95
96 struct parsedata {
97   const char *filename;
98   const char *basename;
99   Pool *pool;
100   Repo *repo;
101   Repodata *data;
102
103   struct solv_xmlparser xmlp;
104   struct joindata jd;
105
106   const char *tmplang;
107
108   const char *tmpvers;
109   const char *tmprel;
110   Id urltype;
111
112   unsigned int ctime;
113
114   Solvable *solvable;
115   Id handle;
116
117   ino_t baseproduct;
118   ino_t currentproduct;
119   int productscheme;
120 };
121
122
123 static time_t
124 datestr2timestamp(const char *date)
125 {
126   const char *p; 
127   struct tm tm; 
128
129   if (!date || !*date)
130     return 0;
131   for (p = date; *p >= '0' && *p <= '9'; p++)
132     ;   
133   if (!*p)
134     return atoi(date);
135   memset(&tm, 0, sizeof(tm));
136   p = strptime(date, "%F%T", &tm);
137   if (!p)
138     {
139       memset(&tm, 0, sizeof(tm));
140       p = strptime(date, "%F", &tm);
141       if (!p || *p)
142         return 0;
143     }
144   return timegm(&tm);
145 }
146
147 static void
148 startElement(struct solv_xmlparser *xmlp, int state, const char *name, const char **atts)
149 {
150   struct parsedata *pd = xmlp->userdata;
151   Pool *pool = pd->pool;
152   Solvable *s = pd->solvable;
153
154   switch(state)
155     {
156     case STATE_PRODUCT:
157       /* parse 'schemeversion' and store in global variable */
158       {
159         const char * scheme = solv_xmlparser_find_attr("schemeversion", atts);
160         pd->productscheme = (scheme && *scheme) ? atoi(scheme) : -1;
161       }
162       if (!s)
163         {
164           s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
165           pd->handle = s - pool->solvables;
166         }
167       break;
168
169       /* <summary lang="xy">... */
170     case STATE_SUMMARY:
171     case STATE_DESCRIPTION:
172       pd->tmplang = join_dup(&pd->jd, solv_xmlparser_find_attr("lang", atts));
173       break;
174     case STATE_URL:
175       pd->urltype = pool_str2id(pd->pool, solv_xmlparser_find_attr("name", atts), 1);
176       break;
177     case STATE_REGUPDREPO:
178       {
179         const char *repoid = solv_xmlparser_find_attr("repoid", atts);
180         if (repoid && *repoid)
181           {
182             Id h = repodata_new_handle(pd->data);
183             repodata_set_str(pd->data, h, PRODUCT_UPDATES_REPOID, repoid);
184             repodata_add_flexarray(pd->data, pd->handle, PRODUCT_UPDATES, h);
185           }
186         break;
187       }
188     default:
189       break;
190     }
191 }
192
193
194 static void
195 endElement(struct solv_xmlparser *xmlp, int state, char *content)
196 {
197   struct parsedata *pd = xmlp->userdata;
198   Solvable *s = pd->solvable;
199
200   switch (state)
201     {
202     case STATE_PRODUCT:
203       /* product done, finish solvable */
204       if (pd->ctime)
205         repodata_set_num(pd->data, pd->handle, SOLVABLE_INSTALLTIME, pd->ctime);
206
207       if (pd->basename)
208         repodata_set_str(pd->data, pd->handle, PRODUCT_REFERENCEFILE, pd->basename);
209
210       /* this is where <productsdir>/baseproduct points to */
211       if (pd->currentproduct == pd->baseproduct)
212         repodata_set_str(pd->data, pd->handle, PRODUCT_TYPE, "base");
213
214       if (pd->tmprel)
215         {
216           if (pd->tmpvers)
217             s->evr = makeevr(pd->pool, join2(&pd->jd, pd->tmpvers, "-", pd->tmprel));
218           else
219             {
220               fprintf(stderr, "Seen <release> but no <version>\n");
221             }
222         }
223       else if (pd->tmpvers)
224         s->evr = makeevr(pd->pool, pd->tmpvers); /* just version, no release */
225       pd->tmpvers = solv_free((void *)pd->tmpvers);
226       pd->tmprel = solv_free((void *)pd->tmprel);
227       if (!s->arch)
228         s->arch = ARCH_NOARCH;
229       if (!s->evr)
230         s->evr = ID_EMPTY;
231       if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
232         s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pd->pool, s->name, s->evr, REL_EQ, 1), 0);
233       pd->solvable = 0;
234       break;
235     case STATE_VENDOR:
236       s->vendor = pool_str2id(pd->pool, content, 1);
237       break;
238     case STATE_NAME:
239       s->name = pool_str2id(pd->pool, join2(&pd->jd, "product", ":", content), 1);
240       break;
241     case STATE_VERSION:
242       pd->tmpvers = solv_strdup(content);
243       break;
244     case STATE_RELEASE:
245       pd->tmprel = solv_strdup(content);
246       break;
247     case STATE_ARCH:
248       s->arch = pool_str2id(pd->pool, content, 1);
249       break;
250     case STATE_PRODUCTLINE:
251       repodata_set_str(pd->data, pd->handle, PRODUCT_PRODUCTLINE, content);
252     break;
253     case STATE_UPDATEREPOKEY:
254       /** obsolete **/
255       break;
256     case STATE_SUMMARY:
257       repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_SUMMARY, pd->tmplang, 1), content);
258       break;
259     case STATE_SHORTSUMMARY:
260       repodata_set_str(pd->data, pd->handle, PRODUCT_SHORTLABEL, content);
261       break;
262     case STATE_DESCRIPTION:
263       repodata_set_str(pd->data, pd->handle, pool_id2langid(pd->pool, SOLVABLE_DESCRIPTION, pd->tmplang, 1), content);
264       break;
265     case STATE_URL:
266       if (pd->urltype)
267         {
268           repodata_add_poolstr_array(pd->data, pd->handle, PRODUCT_URL, content);
269           repodata_add_idarray(pd->data, pd->handle, PRODUCT_URL_TYPE, pd->urltype);
270         }
271       break;
272     case STATE_TARGET:
273       repodata_set_str(pd->data, pd->handle, PRODUCT_REGISTER_TARGET, content);
274       break;
275     case STATE_REGRELEASE:
276       repodata_set_str(pd->data, pd->handle, PRODUCT_REGISTER_RELEASE, content);
277       break;
278     case STATE_REGFLAVOR:
279       repodata_set_str(pd->data, pd->handle, PRODUCT_REGISTER_FLAVOR, content);
280       break;
281     case STATE_CPEID:
282       if (*content)
283         repodata_set_str(pd->data, pd->handle, SOLVABLE_CPEID, content);
284       break;
285     case STATE_ENDOFLIFE:
286       /* FATE#320699: Support tri-state product-endoflife (tag absent, present but nodate(0), present + date) */
287       repodata_set_num(pd->data, pd->handle, PRODUCT_ENDOFLIFE, (*content ? datestr2timestamp(content) : 0));
288       break;
289     default:
290       break;
291     }
292 }
293
294 static void
295 errorCallback(struct solv_xmlparser *xmlp, const char *errstr, unsigned int line, unsigned int column)
296 {
297   struct parsedata *pd = xmlp->userdata;
298   pool_debug(pd->pool, SOLV_ERROR, "%s: %s at line %u:%u\n", pd->filename, errstr, line, column);
299   if (pd->solvable)
300     {   
301       repo_free_solvable(pd->repo, pd->solvable - pd->pool->solvables, 1); 
302       pd->solvable = 0;
303     }   
304 }
305
306
307 int
308 repo_add_code11_products(Repo *repo, const char *dirpath, int flags)
309 {
310   Repodata *data;
311   struct parsedata pd;
312   DIR *dir;
313
314   data = repo_add_repodata(repo, flags);
315
316   memset(&pd, 0, sizeof(pd));
317   pd.repo = repo;
318   pd.pool = repo->pool;
319   pd.data = data;
320
321   solv_xmlparser_init(&pd.xmlp, stateswitches, &pd, startElement, endElement, errorCallback);
322
323   if (flags & REPO_USE_ROOTDIR)
324     dirpath = pool_prepend_rootdir(repo->pool, dirpath);
325   dir = opendir(dirpath);
326   if (dir)
327     {
328       struct dirent *entry;
329       struct stat st;
330       char *fullpath;
331
332       /* check for <productsdir>/baseproduct on code11 and remember its target inode */
333       if (stat(join2(&pd.jd, dirpath, "/", "baseproduct"), &st) == 0) /* follow symlink */
334         pd.baseproduct = st.st_ino;
335       else
336         pd.baseproduct = 0;
337
338       while ((entry = readdir(dir)))
339         {
340           int len = strlen(entry->d_name);
341           FILE *fp;
342           if (len <= 5 || strcmp(entry->d_name + len - 5, ".prod") != 0)
343             continue;
344           fullpath = join2(&pd.jd, dirpath, "/", entry->d_name);
345           fp = fopen(fullpath, "r");
346           if (!fp)
347             {
348               pool_error(repo->pool, 0, "%s: %s", fullpath, strerror(errno));
349               continue;
350             }
351           if (fstat(fileno(fp), &st))
352             {
353               pool_error(repo->pool, 0, "%s: %s", fullpath, strerror(errno));
354               fclose(fp);
355               continue;
356             }
357           pd.currentproduct = st.st_ino;
358           pd.ctime = (unsigned int)st.st_ctime;
359           pd.filename = fullpath;
360           pd.basename = entry->d_name;
361           solv_xmlparser_parse(&pd.xmlp, fp);
362           fclose(fp);
363         }
364       closedir(dir);
365     }
366   solv_xmlparser_free(&pd.xmlp);
367   join_freemem(&pd.jd);
368   if (flags & REPO_USE_ROOTDIR)
369     solv_free((char *)dirpath);
370
371   if (!(flags & REPO_NO_INTERNALIZE))
372     repodata_internalize(data);
373   return 0;
374 }
375
376
377 /******************************************************************************************/
378
379
380 /*
381  * read all installed products
382  *
383  * try proddir (reading all .xml files from this directory) first
384  * if not available, assume non-code11 layout and parse /etc/xyz-release
385  *
386  * parse each one as a product
387  */
388
389 /* Oh joy! Three parsers for the price of one! */
390
391 int
392 repo_add_products(Repo *repo, const char *proddir, int flags)
393 {
394   const char *fullpath;
395   DIR *dir;
396
397   if (proddir)
398     {
399       dir = opendir(flags & REPO_USE_ROOTDIR ? pool_prepend_rootdir_tmp(repo->pool, proddir) : proddir);
400       if (dir)
401         {
402           /* assume code11 stype products */
403           closedir(dir);
404           return repo_add_code11_products(repo, proddir, flags);
405         }
406     }
407
408   /* code11 didn't work, try old code10 zyppdb */
409   fullpath = "/var/lib/zypp/db/products";
410   if (flags & REPO_USE_ROOTDIR)
411     fullpath = pool_prepend_rootdir_tmp(repo->pool, fullpath);
412   dir = opendir(fullpath);
413   if (dir)
414     {
415       closedir(dir);
416       /* assume code10 style products */
417       return repo_add_zyppdb_products(repo, "/var/lib/zypp/db/products", flags);
418     }
419
420   /* code10/11 didn't work, try -release files parsing */
421   fullpath = "/etc";
422   if (flags & REPO_USE_ROOTDIR)
423     fullpath = pool_prepend_rootdir_tmp(repo->pool, fullpath);
424   dir = opendir(fullpath);
425   if (dir)
426     {
427       closedir(dir);
428       return repo_add_releasefile_products(repo, "/etc", flags);
429     }
430
431   /* no luck. check if the rootdir exists */
432   fullpath = pool_get_rootdir(repo->pool);
433   if (fullpath && *fullpath)
434     {
435       dir = opendir(fullpath);
436       if (!dir)
437         return pool_error(repo->pool, -1, "%s: %s", fullpath, strerror(errno));
438       closedir(dir);
439     }
440
441   /* the least we can do... */
442   if (!(flags & REPO_NO_INTERNALIZE) && (flags & REPO_REUSE_REPODATA) != 0)
443     repodata_internalize(repo_last_repodata(repo));
444   return 0;
445 }
446
447 /* EOF */