Merge branch 'master' of git@git.opensuse.org:projects/zypp/sat-solver
[platform/upstream/libsolv.git] / ext / repo_repomdxml.c
1 /*
2  * Copyright (c) 2007, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 #define DO_ARRAY 1
9
10 #define _GNU_SOURCE
11 #include <sys/types.h>
12 #include <limits.h>
13 #include <fcntl.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <expat.h>
18
19 #include "pool.h"
20 #include "repo.h"
21 #include "repo_updateinfoxml.h"
22
23 //#define DUMPOUT 0
24
25 /*
26 <repomd>
27
28   <!-- these tags are available in create repo > 0.9.6 -->
29   <revision>timestamp_or_arbitrary_user_supplied_string</revision>
30   <tags>
31     <content>opensuse</content>
32     <content>i386</content>
33     <content>other string</content>
34     <distro cpeid="cpe://o:opensuse_project:opensuse:11">openSUSE 11.0</distro>
35   </tags>
36   <!-- end -->
37
38   <data type="primary">
39     <location href="repodata/primary.xml.gz"/>
40     <checksum type="sha">e9162516fa25fec8d60caaf4682d2e49967786cc</checksum>
41     <timestamp>1215708444</timestamp>
42     <open-checksum type="sha">c796c48184cd5abc260e4ba929bdf01be14778a7</open-checksum>
43   </data>
44   <data type="filelists">
45     <location href="repodata/filelists.xml.gz"/>
46     <checksum type="sha">1c638295c49e9707c22810004ebb0799791fcf45</checksum>
47     <timestamp>1215708445</timestamp>
48     <open-checksum type="sha">54a40d5db3df0813b8acbe58cea616987eb9dc16</open-checksum>
49   </data>
50   <data type="other">
51     <location href="repodata/other.xml.gz"/>
52     <checksum type="sha">a81ef39eaa70e56048f8351055119d8c82af2491</checksum>
53     <timestamp>1215708447</timestamp>
54     <open-checksum type="sha">4d1ee867c8864025575a2fb8fde3b85371d51978</open-checksum>
55   </data>
56   <data type="deltainfo">
57     <location href="repodata/deltainfo.xml.gz"/>
58     <checksum type="sha">5880cfa5187026a24a552d3c0650904a44908c28</checksum>
59     <timestamp>1215708447</timestamp>
60     <open-checksum type="sha">7c964a2c3b17df5bfdd962c3be952c9ca6978d8b</open-checksum>
61   </data>
62   <data type="updateinfo">
63     <location href="repodata/updateinfo.xml.gz"/>
64     <checksum type="sha">4097f7e25c7bb0770ae31b2471a9c8c077ee904b</checksum>
65     <timestamp>1215708447</timestamp>
66     <open-checksum type="sha">24f8252f3dd041e37e7c3feb2d57e02b4422d316</open-checksum>
67   </data>
68   <data type="diskusage">
69     <location href="repodata/diskusage.xml.gz"/>
70     <checksum type="sha">4097f7e25c7bb0770ae31b2471a9c8c077ee904b</checksum>
71     <timestamp>1215708447</timestamp>
72     <open-checksum type="sha">24f8252f3dd041e37e7c3feb2d57e02b4422d316</open-checksum>
73   </data>
74 </repomd>
75
76 support also extension suseinfo format
77 <suseinfo>
78   <expire>timestamp</expire>
79   <products>
80     <id>...</id>
81   </products>
82   <kewwords>
83     <k>...</k>
84   </keywords>
85 </suseinfo>
86
87 */
88
89 enum state {
90   STATE_START,
91   /* extension tags */
92   STATE_SUSEINFO,
93   STATE_EXPIRE,
94   STATE_KEYWORDS,
95   STATE_KEYWORD,
96
97   /* normal repomd.xml */
98   STATE_REPOMD,
99   STATE_REVISION,
100   STATE_TAGS,
101   STATE_CONTENT,
102   STATE_DISTRO,
103   STATE_UPDATES,
104   STATE_DATA,
105   STATE_LOCATION,
106   STATE_CHECKSUM,
107   STATE_TIMESTAMP,
108   STATE_OPENCHECKSUM,
109   NUMSTATES
110 };
111
112 struct stateswitch {
113   enum state from;
114   char *ename;
115   enum state to;
116   int docontent;
117 };
118
119 /* !! must be sorted by first column !! */
120 static struct stateswitch stateswitches[] = {
121   /* suseinfo tags */
122   { STATE_START,       "repomd",          STATE_REPOMD, 0 },
123   { STATE_START,       "suseinfo",        STATE_SUSEINFO, 0 },
124   /* we support the tags element in suseinfo in case
125      createrepo version does not support it yet */
126   { STATE_SUSEINFO,    "tags",            STATE_TAGS, 0 },    
127   { STATE_SUSEINFO,    "expire",          STATE_EXPIRE, 1 },  
128   { STATE_SUSEINFO,    "keywords",        STATE_KEYWORDS, 0 },  
129   /* keywords is the suse extension equivalent of
130      tags/content when this one was not yet available.
131      therefore we parse both */ 
132   { STATE_KEYWORDS,    "k",               STATE_KEYWORD, 1 },  
133   /* standard tags */
134   { STATE_REPOMD,      "revision",        STATE_REVISION, 1 },
135   { STATE_REPOMD,      "tags",            STATE_TAGS,  0 },
136   { STATE_REPOMD,      "data",            STATE_DATA,  0 },
137  
138   { STATE_TAGS,        "content",         STATE_CONTENT,  1 },
139   { STATE_TAGS,        "distro",          STATE_DISTRO,  1 },
140   /* this tag is only valid in suseinfo.xml for now */
141   { STATE_TAGS,        "updates",         STATE_UPDATES,  1 },
142
143   { STATE_DATA,        "location",        STATE_LOCATION, 0 },
144   { STATE_DATA,        "checksum",        STATE_CHECKSUM, 1 },  
145   { STATE_DATA,        "timestamp",       STATE_TIMESTAMP, 1 },
146   { STATE_DATA,        "open-checksum",    STATE_OPENCHECKSUM, 1 },
147   { NUMSTATES }
148 };
149
150
151 struct parsedata {
152   int depth;
153   enum state state;
154   int statedepth;
155   char *content;
156   int lcontent;
157   int acontent;
158   int docontent;
159   Pool *pool;
160   Repo *repo;
161   Repodata *data;
162   
163   XML_Parser *parser;
164   struct stateswitch *swtab[NUMSTATES];
165   enum state sbtab[NUMSTATES];
166   int timestamp;
167   /* handles for collection
168      structures */
169   /* repo updates */
170   Id ruhandle;
171   /* repo products */
172   Id rphandle;
173   /* repo data handle */
174   Id rdhandle;
175
176   const char *tmpattr;
177 };
178
179 /*
180  * find attribute
181  */
182
183 static inline const char *
184 find_attr(const char *txt, const char **atts)
185 {
186   for (; *atts; atts += 2)
187     {
188       if (!strcmp(*atts, txt))
189         return atts[1];
190     }
191   return 0;
192 }
193
194
195 static void XMLCALL
196 startElement(void *userData, const char *name, const char **atts)
197 {
198   struct parsedata *pd = userData;
199   /*Pool *pool = pd->pool;*/
200   struct stateswitch *sw;
201
202 #if 0
203   fprintf(stderr, "start: [%d]%s\n", pd->state, name);
204 #endif
205   if (pd->depth != pd->statedepth)
206     {
207       pd->depth++;
208       return;
209     }
210
211   pd->depth++;
212   if (!pd->swtab[pd->state])
213     return;
214   for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++)  /* find name in statetable */
215     if (!strcmp(sw->ename, name))
216       break;
217   
218   if (sw->from != pd->state)
219     {
220 #if 0
221       fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
222 #endif
223       return;
224     }
225   pd->state = sw->to;
226   pd->docontent = sw->docontent;
227   pd->statedepth = pd->depth;
228   pd->lcontent = 0;
229   *pd->content = 0;
230
231   switch(pd->state)
232     {
233     case STATE_START: break;
234     case STATE_REPOMD:
235       {
236         const char *updstr;
237
238         /* this should be OBSOLETE soon */
239         updstr = find_attr("updates", atts);
240         if (updstr)
241           {
242             char *value = strdup(updstr);
243             char *fvalue = value; /* save the first */
244             while (value)
245               {
246                 char *p = strchr(value, ',');
247                 if (*p)
248                   *p++ = 0;
249                 if (*value)
250                   repo_add_poolstr_array(pd->repo, SOLVID_META, REPOSITORY_UPDATES, value);
251                 value = p;
252               }
253             free(fvalue);
254           }
255           break;
256         }
257     case STATE_SUSEINFO: break;
258     case STATE_EXPIRE: break;
259     case STATE_KEYWORDS: break;
260     case STATE_KEYWORD: break;
261     case STATE_CONTENT: break;
262     case STATE_REVISION: break;
263     case STATE_DISTRO:
264       {
265         /* this is extra metadata about the product this repository
266            was designed for */
267         const char *cpeid = find_attr("cpeid", atts);
268         pd->rphandle = repodata_new_handle(pd->data);
269         /* set the cpeid for the product 
270            the label is set in the content of the tag */
271         if (cpeid)
272           repodata_set_poolstr(pd->data, pd->rphandle, REPOSITORY_PRODUCT_CPEID, cpeid);
273         break;
274       }
275     case STATE_UPDATES:
276       {
277         /* this is extra metadata about the product this repository
278            was designed for */
279         const char *cpeid = find_attr("cpeid", atts);
280         pd->ruhandle = repodata_new_handle(pd->data);
281         /* set the cpeid for the product 
282            the label is set in the content of the tag */
283         if (cpeid)
284           repodata_set_poolstr(pd->data, pd->ruhandle, REPOSITORY_PRODUCT_CPEID, cpeid);
285         break;
286       }
287     case STATE_DATA:
288       {
289         const char *type= find_attr("type", atts);
290         pd->rdhandle = repodata_new_handle(pd->data);
291         if (type)
292           repodata_set_poolstr(pd->data, pd->rdhandle, REPOSITORY_REPOMD_TYPE, type);
293         break;
294       }
295     case STATE_LOCATION:
296       {
297         const char *href = find_attr("href", atts);
298         if (href)
299           repodata_set_str(pd->data, pd->rdhandle, REPOSITORY_REPOMD_LOCATION, href);
300       }
301     case STATE_CHECKSUM:
302     case STATE_OPENCHECKSUM:
303       pd->tmpattr= find_attr("type", atts);
304       break;
305     default:
306       break;
307     }
308   return;
309 }
310
311 static void XMLCALL
312 endElement(void *userData, const char *name)
313 {
314   struct parsedata *pd = userData;
315   /* Pool *pool = pd->pool; */
316
317 #if 0
318   fprintf(stderr, "endElement: %s\n", name);
319 #endif
320   if (pd->depth != pd->statedepth)
321     {
322       pd->depth--;
323 #if 0
324       fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
325 #endif
326       return;
327     }
328
329   pd->depth--;
330   pd->statedepth--;
331   switch (pd->state)
332     {
333     case STATE_START: break;
334     case STATE_REPOMD: 
335       if (pd->timestamp > 0)
336         repodata_set_num(pd->data, SOLVID_META, REPOSITORY_TIMESTAMP, pd->timestamp);
337       break;
338     case STATE_DATA:
339       if (pd->rdhandle)
340         repodata_add_flexarray(pd->data, SOLVID_META, REPOSITORY_REPOMD, pd->rdhandle);
341       pd->rdhandle = 0;
342       break;
343     case STATE_LOCATION: break;
344
345     case STATE_CHECKSUM:
346     case STATE_OPENCHECKSUM:
347       {
348         int l;
349         Id type;
350         if (!strcasecmp(pd->tmpattr, "sha") || !strcasecmp(pd->tmpattr, "sha1"))
351           l = SIZEOF_SHA1 * 2, type = REPOKEY_TYPE_SHA1;
352         else if (!strcasecmp(pd->tmpattr, "sha256"))
353           l = SIZEOF_SHA256 * 2, type = REPOKEY_TYPE_SHA256;
354         else if (!strcasecmp(pd->tmpattr, "md5"))
355           l = SIZEOF_MD5 * 2, type = REPOKEY_TYPE_MD5;
356         else
357           {
358             fprintf(stderr, "Unknown checksum type: %d: %s\n", (unsigned int)XML_GetCurrentLineNumber(*pd->parser), pd->tmpattr);
359             exit(1);
360           }
361         if (strlen(pd->content) != l)
362           {
363             fprintf(stderr, "Invalid checksum length: %d: for %s\n", (unsigned int)XML_GetCurrentLineNumber(*pd->parser), pd->tmpattr);
364             exit(1);
365           }
366         repodata_set_checksum(pd->data, pd->rdhandle, pd->state == STATE_CHECKSUM ? REPOSITORY_REPOMD_CHECKSUM : REPOSITORY_REPOMD_OPENCHECKSUM, type, pd->content);
367         break;
368       }
369
370     case STATE_TIMESTAMP:
371       {
372         /**
373          * we want to look for the newest timestamp
374          * of all resources to save it as the time
375          * the metadata was generated
376          */
377         int timestamp = atoi(pd->content);
378         if (timestamp)
379           repodata_set_num(pd->data, pd->rdhandle, REPOSITORY_REPOMD_TIMESTAMP, timestamp);
380         if (timestamp > pd->timestamp)
381           pd->timestamp = timestamp;
382         break;
383       }
384     case STATE_EXPIRE:
385       {
386         int expire = atoi(pd->content);
387         if (expire > 0)
388           repodata_set_num(pd->data, SOLVID_META, REPOSITORY_EXPIRE, expire);
389         break;
390       }
391       /* repomd.xml content and suseinfo.xml keywords are equivalent */
392     case STATE_CONTENT:
393     case STATE_KEYWORD:
394       if (pd->content)
395         repodata_add_poolstr_array(pd->data, SOLVID_META, REPOSITORY_KEYWORDS, pd->content);
396       break;
397     case STATE_REVISION:
398       if (pd->content)
399         repodata_add_poolstr_array(pd->data, SOLVID_META, REPOSITORY_REVISION, pd->content);
400       break;
401     case STATE_DISTRO:
402       /* distro tag is used in repomd.xml to say the product this repo is
403          made for */
404       if (pd->content)
405         repodata_set_str(pd->data, pd->rphandle, REPOSITORY_PRODUCT_LABEL, pd->content);
406       repodata_add_flexarray(pd->data, SOLVID_META, REPOSITORY_DISTROS, pd->rphandle);
407       break;
408     case STATE_UPDATES:
409       /* distro tag is used in suseinfo.xml to say the repo updates a product
410          however it s not yet a tag standarized for repomd.xml */
411       if (pd->content)
412         repodata_set_str(pd->data, pd->ruhandle, REPOSITORY_PRODUCT_LABEL, pd->content);
413       repodata_add_flexarray(pd->data, SOLVID_META, REPOSITORY_UPDATES, pd->ruhandle);
414       break;
415     case STATE_SUSEINFO: break;
416     case STATE_KEYWORDS: break;
417     case NUMSTATES: break;              
418     default:
419       break;
420     }
421
422   pd->state = pd->sbtab[pd->state];
423   pd->docontent = 0;
424   
425   return;
426 }
427
428
429 static void XMLCALL
430 characterData(void *userData, const XML_Char *s, int len)
431 {
432   struct parsedata *pd = userData;
433   int l;
434   char *c;
435   if (!pd->docontent)
436     return;
437   l = pd->lcontent + len + 1;
438   if (l > pd->acontent)
439     {
440       pd->content = realloc(pd->content, l + 256);
441       pd->acontent = l + 256;
442     }
443   c = pd->content + pd->lcontent;
444   pd->lcontent += len;
445   while (len-- > 0)
446     *c++ = *s++;
447   *c = 0;
448 }
449
450 #define BUFF_SIZE 8192
451
452 void
453 repo_add_repomdxml(Repo *repo, FILE *fp, int flags)
454 {
455   Pool *pool = repo->pool;
456   struct parsedata pd;
457   Repodata *data;
458   char buf[BUFF_SIZE];
459   int i, l;
460   struct stateswitch *sw;
461
462   if (!(flags & REPO_REUSE_REPODATA))
463     data = repo_add_repodata(repo, 0);
464   else
465     data = repo_last_repodata(repo);
466
467   memset(&pd, 0, sizeof(pd));
468   pd.timestamp = 0;
469   for (i = 0, sw = stateswitches; sw->from != NUMSTATES; i++, sw++)
470     {
471       if (!pd.swtab[sw->from])
472         pd.swtab[sw->from] = sw;
473       pd.sbtab[sw->to] = sw->from;
474     }
475   pd.pool = pool;
476   pd.repo = repo;
477   pd.data = data;
478
479   pd.content = malloc(256);
480   pd.acontent = 256;
481   pd.lcontent = 0;
482   XML_Parser parser = XML_ParserCreate(NULL);
483   XML_SetUserData(parser, &pd);
484   pd.parser = &parser;
485   XML_SetElementHandler(parser, startElement, endElement);
486   XML_SetCharacterDataHandler(parser, characterData);
487   for (;;)
488     {
489       l = fread(buf, 1, sizeof(buf), fp);
490       if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
491         {
492           pool_debug(pool, SAT_FATAL, "repo_repomdxml: %s at line %u:%u\n", XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
493           exit(1);
494         }
495       if (l == 0)
496         break;
497     }
498   XML_ParserFree(parser);
499
500   if (!(flags & REPO_NO_INTERNALIZE))
501     repodata_internalize(data);
502
503   free(pd.content);
504 }
505
506 /* EOF */