parse diskusage.xml
[platform/upstream/libsolv.git] / tools / repo_diskusagexml.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_diskusagexml.h"
22
23 //#define DUMPOUT 0
24
25 /*
26  * <diskusage>
27  *   <duinfo name="3ddiag" ver="0.742" rel="45" arch="i586">
28  *     <dirs>
29  *       <dir name="/" size="56" count="11"/>
30  *       <dir name="usr/" size="56" count="11"/>
31  *       <dir name="usr/bin/" size="38" count="10"/>
32  *       <dir name="usr/share/" size="18" count="1"/>
33  *       <dir name="usr/share/doc/" size="18" count="1"/>
34  *     </dirs>
35  *   </duinfo>
36  *   <duinfo name="915resolution" ver="0.5.3" rel="74" arch="i586">
37  *     <dirs>
38  *       <dir name="/" size="27" count="7"/>
39  * ...
40  *     </dirs>
41  *   </duinfo>
42  * </diskusage>
43  */
44
45 enum state {
46   STATE_START,
47   STATE_DISKUSAGE, /* 1 */
48   STATE_DUINFO,    /* 2 */
49   STATE_DIRS,      /* 3 */
50   STATE_DIR,       /* 4 */
51   NUMSTATES
52 };
53
54 struct stateswitch {
55   enum state from;
56   char *ename;
57   enum state to;
58   int docontent;
59 };
60
61 /* !! must be sorted by first column !! */
62 static struct stateswitch stateswitches[] = {
63   { STATE_START,       "diskusage",      STATE_DISKUSAGE,    0 },
64   { STATE_DISKUSAGE,   "duinfo",         STATE_DUINFO,       0 },
65   { STATE_DUINFO,      "dirs",           STATE_DIRS,         0 },
66   { STATE_DIRS,        "dir",            STATE_DIR,          0 },
67   { NUMSTATES }
68 };
69
70 struct parsedata {
71   int depth;
72   enum state state;
73   int statedepth;
74   char *content;
75   int lcontent;
76   int acontent;
77   int docontent;
78   Pool *pool;
79   Repo *repo;
80   Repodata *data;
81   int datanum;
82   
83   struct stateswitch *swtab[NUMSTATES];
84   enum state sbtab[NUMSTATES];
85   char *tempstr;
86   int ltemp;
87   int atemp;
88   Solvable *s;
89   Id handle;
90
91   Id (*dirs)[3]; // dirid, size, nfiles
92   int ndirs;
93 };
94
95 /*
96  * id3_cmp
97  * compare
98  *
99  */
100
101 static int
102 id3_cmp (const void *v1, const void *v2)
103 {
104   Id *i1 = (Id*)v1;
105   Id *i2 = (Id*)v2;
106   return i1[0] - i2[0];
107 }
108
109
110 /*
111  * commit_diskusage
112  *
113  */
114
115 static void
116 commit_diskusage (struct parsedata *pd, unsigned handle)
117 {
118   unsigned i;
119   Dirpool *dp = &pd->data->dirpool;
120   /* Now sort in dirid order.  This ensures that parents come before
121      their children.  */
122   if (pd->ndirs > 1)
123     qsort(pd->dirs, pd->ndirs, sizeof (pd->dirs[0]), id3_cmp);
124   /* Substract leaf numbers from all parents to make the numbers
125      non-cumulative.  This must be done post-order (i.e. all leafs
126      adjusted before parents).  We ensure this by starting at the end of
127      the array moving to the start, hence seeing leafs before parents.  */
128   for (i = pd->ndirs; i--;)
129     {
130       unsigned p = dirpool_parent(dp, pd->dirs[i][0]);
131       unsigned j = i;
132       for (; p; p = dirpool_parent(dp, p))
133         {
134           for (; j--;)
135             if (pd->dirs[j][0] == p)
136               break;
137           if (j < pd->ndirs)
138             {
139               if (pd->dirs[j][1] < pd->dirs[i][1])
140                 pd->dirs[j][1] = 0;
141               else
142                 pd->dirs[j][1] -= pd->dirs[i][1];
143               if (pd->dirs[j][2] < pd->dirs[i][2])
144                 pd->dirs[j][2] = 0;
145               else
146                 pd->dirs[j][2] -= pd->dirs[i][2];
147             }
148           else
149             /* Haven't found this parent in the list, look further if
150                we maybe find the parents parent.  */
151             j = i;
152         }
153     }
154 #if 0
155   char sbuf[1024];
156   char *buf = sbuf;
157   unsigned slen = sizeof (sbuf);
158   for (i = 0; i < pd->ndirs; i++)
159     {
160       dir2str (attr, pd->dirs[i][0], &buf, &slen);
161       fprintf (stderr, "have dir %d %d %d %s\n", pd->dirs[i][0], pd->dirs[i][1], pd->dirs[i][2], buf);
162     }
163   if (buf != sbuf)
164     free (buf);
165 #endif
166   for (i = 0; i < pd->ndirs; i++)
167     if (pd->dirs[i][1] || pd->dirs[i][2])
168       {
169         repodata_add_dirnumnum(pd->data, handle, SOLVABLE_DISKUSAGE, pd->dirs[i][0], pd->dirs[i][1], pd->dirs[i][2]);
170       }
171   pd->ndirs = 0;
172 }
173
174
175 /*
176  * find attribute
177  */
178
179 static const char *
180 find_attr(const char *txt, const char **atts)
181 {
182   for (; *atts; atts += 2)
183     {
184       if (!strcmp(*atts, txt))
185         return atts[1];
186     }
187   return 0;
188 }
189
190
191 /*
192  * create evr (as Id) from 'epoch', 'version' and 'release' attributes
193  */
194
195 static Id
196 makeevr_atts(Pool *pool, struct parsedata *pd, const char **atts)
197 {
198   const char *e, *v, *r, *v2;
199   char *c;
200   int l;
201
202   e = v = r = 0;
203   for (; *atts; atts += 2)
204     {
205       if (!strcmp(*atts, "epoch"))
206         e = atts[1];
207       else if (!strcmp(*atts, "version"))
208         v = atts[1];
209       else if (!strcmp(*atts, "release"))
210         r = atts[1];
211     }
212   if (e && !strcmp(e, "0"))
213     e = 0;
214   if (v && !e)
215     {
216       for (v2 = v; *v2 >= '0' && *v2 <= '9'; v2++)
217         ;
218       if (v2 > v && *v2 == ':')
219         e = "0";
220     }
221   l = 1;
222   if (e)
223     l += strlen(e) + 1;
224   if (v)
225     l += strlen(v);
226   if (r)
227     l += strlen(r) + 1;
228   if (l > pd->acontent)
229     {
230       pd->content = realloc(pd->content, l + 256);
231       pd->acontent = l + 256;
232     }
233   c = pd->content;
234   if (e)
235     {
236       strcpy(c, e);
237       c += strlen(c);
238       *c++ = ':';
239     }
240   if (v)
241     {
242       strcpy(c, v);
243       c += strlen(c);
244     }
245   if (r)
246     {
247       *c++ = '-';
248       strcpy(c, r);
249       c += strlen(c);
250     }
251   *c = 0;
252   if (!*pd->content)
253     return 0;
254 #if 0
255   fprintf(stderr, "evr: %s\n", pd->content);
256 #endif
257   return str2id(pool, pd->content, 1);
258 }
259
260
261 /*
262  * XML callback: startElement
263  */
264
265 static void XMLCALL
266 startElement(void *userData, const char *name, const char **atts)
267 {
268   struct parsedata *pd = userData;
269   Pool *pool = pd->pool;
270   struct stateswitch *sw;
271   const char *str;
272
273 #if 0
274       fprintf(stderr, "start: [%d]%s\n", pd->state, name);
275 #endif
276   if (pd->depth != pd->statedepth)
277     {
278       pd->depth++;
279       return;
280     }
281
282   pd->depth++;
283   for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++)  /* find name in statetable */
284     if (!strcmp(sw->ename, name))
285       break;
286   
287   if (sw->from != pd->state)
288     {
289 #if 1
290       fprintf(stderr, "into unknown: [%d]%s (from: %d)\n", sw->to, name, sw->from);
291       exit( 1 );
292 #endif
293       return;
294     }
295   pd->state = sw->to;
296   pd->docontent = sw->docontent;
297   pd->statedepth = pd->depth;
298   pd->lcontent = 0;
299   *pd->content = 0;
300
301   switch(pd->state)
302     {
303       case STATE_START:
304           break;
305       case STATE_DUINFO:
306           pd->s = pool_id2solvable(pd->pool, repo_add_solvable(pd->repo));
307           repodata_extend(pd->data, pd->s - pd->pool->solvables);
308           pd->handle = repodata_get_handle(pd->data, pd->s - pd->pool->solvables - pd->repo->start);
309           if ( (str = find_attr("name", atts)) )
310             {
311               pd->s->name = str2id(pool, str, 1);
312             }
313           pd->s->evr = makeevr_atts(pool, pd, atts);
314           if ( (str = find_attr("arch", atts)) )
315             {
316               pd->s->arch = str2id(pool, str, 1);
317             }
318           break;
319
320       case STATE_DIR:
321         {
322           long filesz = 0, filenum = 0;
323           unsigned dirid;
324           if ( (str = find_attr("name", atts)) )
325             {
326               dirid = repodata_str2dir(pd->data, str, 1);
327             }
328           else
329             {         
330               fprintf( stderr, "<dir .../> tag without 'name' attribute, atts = %p, *atts = %p\n", atts, *atts);
331               break;
332             }
333           if ( (str = find_attr("size", atts)) )
334             {
335               filesz = strtol (str, 0, 0);
336             }
337           if ( (str = find_attr("count", atts)) )
338             {
339               filenum = strtol (str, 0, 0);
340             }
341           pd->dirs = sat_extend(pd->dirs, pd->ndirs, 1, sizeof(pd->dirs[0]), 31);
342           pd->dirs[pd->ndirs][0] = dirid;
343           pd->dirs[pd->ndirs][1] = filesz;
344           pd->dirs[pd->ndirs][2] = filenum;
345           pd->ndirs++;
346         }
347         break;
348       default:
349       break;
350     }
351   return;
352 }
353
354
355 static void XMLCALL
356 endElement(void *userData, const char *name)
357 {
358   struct parsedata *pd = userData;
359
360 #if 0
361       fprintf(stderr, "end: %s\n", name);
362 #endif
363   if (pd->depth != pd->statedepth)
364     {
365       pd->depth--;
366 #if 1
367       fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
368 #endif
369       return;
370     }
371
372   pd->depth--;
373   pd->statedepth--;
374
375   switch (pd->state)
376     {
377       case STATE_DUINFO:
378         if (pd->ndirs)
379           commit_diskusage (pd, pd->handle);
380       break;
381       default:
382       break;
383     }
384   
385   pd->state = pd->sbtab[pd->state];
386   pd->docontent = 0;
387   
388   return;
389 }
390
391
392 static void XMLCALL
393 characterData(void *userData, const XML_Char *s, int len)
394 {
395   struct parsedata *pd = userData;
396   int l;
397   char *c;
398   if (!pd->docontent) {
399 #if 0
400     char *dup = strndup( s, len );
401   fprintf(stderr, "Content: [%d]'%s'\n", pd->state, dup );
402   free( dup );
403 #endif
404     return;
405   }
406   l = pd->lcontent + len + 1;
407   if (l > pd->acontent)
408     {
409       pd->content = realloc(pd->content, l + 256);
410       pd->acontent = l + 256;
411     }
412   c = pd->content + pd->lcontent;
413   pd->lcontent += len;
414   while (len-- > 0)
415     *c++ = *s++;
416   *c = 0;
417 }
418
419 #define BUFF_SIZE 8192
420
421 void
422 repo_add_diskusagexml(Repo *repo, FILE *fp, int flags)
423 {
424   Pool *pool = repo->pool;
425   struct parsedata pd;
426   char buf[BUFF_SIZE];
427   int i, l;
428   struct stateswitch *sw;
429
430   memset(&pd, 0, sizeof(pd));
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   pd.pool = pool;
438   pd.repo = repo;
439   pd.data = repo_add_repodata(pd.repo, 0);
440
441   pd.content = malloc(256);
442   pd.acontent = 256;
443   pd.lcontent = 0;
444   pd.tempstr = malloc(256);
445   pd.atemp = 256;
446   pd.ltemp = 0;
447   pd.datanum = 0;
448   XML_Parser parser = XML_ParserCreate(NULL);
449   XML_SetUserData(parser, &pd);
450   XML_SetElementHandler(parser, startElement, endElement);
451   XML_SetCharacterDataHandler(parser, characterData);
452   for (;;)
453     {
454       l = fread(buf, 1, sizeof(buf), fp);
455       if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
456         {
457           fprintf(stderr, "repo_diskusagexml: %s at line %u:%u\n", XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
458           exit(1);
459         }
460       if (l == 0)
461         break;
462     }
463   XML_ParserFree(parser);
464
465   if (pd.data)
466     repodata_internalize(pd.data);
467
468   free(pd.content);
469 }
470
471 /* EOF */