Initial original import from: fuse-2.4.2-2.fc4
[captive.git] / src / install / acquire / captivemodid.c
1 /* $Id$
2  * W32 disk modules identifier for acquiration installation utility
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "captivemodid.h"       /* self */
23 #include <glib/gmessages.h>
24 #include <libxml/xmlreader.h>
25 #include <glib/ghash.h>
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <libgnomevfs/gnome-vfs-file-size.h>
29 #include <openssl/md5.h>
30 #include <openssl/bn.h>
31 #include <openssl/crypto.h>
32 #include <glib/gstrfuncs.h>
33 #include <ctype.h>
34 #include "../../libcaptive/include/captive/libxml.h"
35
36 #include <captive/macros.h>
37
38
39 gchar *calc_md5(gconstpointer base,size_t length)
40 {
41 unsigned char md5_bin[1+128/8]; /* 128 bits==16 bytes; '1+' for leading stub to prevent shorter output of BN_bn2hex() */
42 BIGNUM *bignum;
43 char *hex;
44 gchar *r,*gs;
45
46         /* already done above */
47         /* Calculate MD5 sum and convert it to hex string: */
48         MD5(base,length,md5_bin+1);
49         md5_bin[0]=0xFF;  /* stub to prevent shorter output of BN_bn2hex() */
50         bignum=BN_bin2bn(md5_bin,1+128/8,NULL);
51         hex=BN_bn2hex(bignum);
52         g_assert(strlen(hex)==2*(1+128/8));
53         r=g_strdup(hex+2);
54         OPENSSL_free(hex);
55         BN_free(bignum);
56
57         g_assert(strlen(r)==32);
58         for (gs=r;*gs;gs++) {
59                 g_assert(isxdigit(*gs));
60                 *gs=tolower(*gs);
61                 g_assert(isxdigit(*gs));
62                 }
63         return r;
64 }
65
66
67 /* map: GINT_TO_POINTER(captivemodid_module.length) -> !=NULL */
68 static GHashTable *module_valid_length_hash;
69
70 static void module_valid_length_hash_init(void)
71 {
72         if (module_valid_length_hash)
73                 return;
74         module_valid_length_hash=g_hash_table_new(g_direct_hash,g_direct_equal);
75 }
76
77 /* map: (const xmlChar *)md5 -> (struct captivemodid_module *) */
78 static GHashTable *module_md5_hash;
79
80 static void module_md5_hash_init(void)
81 {
82         if (module_md5_hash)
83                 return;
84         module_md5_hash=g_hash_table_new(g_str_hash,g_str_equal);
85 }
86
87 /* map: (const xmlChar *)type -> (gpointer)GINT_TO_POINTER(priority) */
88 /* We remove entry for module with already the best priority found,
89  * therefore captivemodid_module_type_best_priority_lookup() will return
90  * 'G_MININT' afterwards.
91  */
92 static GHashTable *module_type_best_priority_hash;
93
94 static void module_type_best_priority_hash_init(void)
95 {
96         if (module_type_best_priority_hash)
97                 return;
98         module_type_best_priority_hash=g_hash_table_new(g_str_hash,g_str_equal);
99 }
100
101 static void captivemodid_load_module(struct captivemodid_module *module)
102 {
103 struct captivemodid_module *module_md5_conflict;
104 gpointer valid_length_value_gpointer;
105
106         module_md5_hash_init();
107         if ((module_md5_conflict=g_hash_table_lookup(module_md5_hash,module->md5))) {
108                 g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"),
109                                 module->id,module_md5_conflict->id);
110                 return;
111                 }
112         g_hash_table_insert(module_md5_hash,(/* de-const */ xmlChar *)module->md5,module);
113
114         module_valid_length_hash_init();
115         if (!g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(module->length),
116                         NULL,   /* orig_key */
117                         &valid_length_value_gpointer))  /* value */
118                 g_hash_table_insert(module_valid_length_hash,GINT_TO_POINTER(module->length),GINT_TO_POINTER(module->cabinet_used));
119         else {
120                 /* Conflicting 'cabinet_used' values for single 'cabinet size'? */
121                 if (valid_length_value_gpointer && GPOINTER_TO_INT(valid_length_value_gpointer)!=module->cabinet_used)
122                         g_hash_table_insert(module_valid_length_hash,GINT_TO_POINTER(module->length),NULL);
123                 }
124
125         if (strcmp((const char *)module->type,"cabinet")) {
126                 if (module->priority>captivemodid_module_type_best_priority_lookup(module->type)) {
127                         module_type_best_priority_hash_init();
128                         g_hash_table_insert(module_type_best_priority_hash,
129                                         (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority));
130                         }
131                 }
132 }
133
134 gboolean captivemodid_module_length_is_valid(GnomeVFSFileSize file_size)
135 {
136 gint file_size_gint;
137
138         if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size)    /* Size too big to be valid. */
139                 return FALSE;
140         module_valid_length_hash_init();
141         return g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(file_size_gint),
142                         NULL,   /* orig_key */
143                         NULL);  /* value */
144 }
145
146 gint captivemodid_cabinet_length_to_used(gint cabinet_length)
147 {
148 gpointer valid_length_value_gpointer;
149
150         if (!g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(cabinet_length),
151                         NULL,   /* orig_key */
152                         &valid_length_value_gpointer))  /* value */
153                 return 0;
154         return GPOINTER_TO_INT(valid_length_value_gpointer);
155 }
156
157 struct captivemodid_module *captivemodid_module_md5_lookup(const gchar *file_md5)
158 {
159         g_return_val_if_fail(file_md5!=NULL,NULL);
160
161         module_md5_hash_init();
162         return g_hash_table_lookup(module_md5_hash,file_md5);
163 }
164
165 gint captivemodid_module_type_best_priority_lookup(const xmlChar *module_type)
166 {
167 gpointer r_gpointer;
168 gboolean errbool;
169
170         g_return_val_if_fail(module_type!=NULL,G_MININT);
171
172         module_type_best_priority_hash_init();
173         errbool=g_hash_table_lookup_extended(module_type_best_priority_hash,
174                         module_type,    /* lookup_key */
175                         NULL,   /* orig_key */
176                         &r_gpointer);   /* value */
177         if (!errbool)
178                 return G_MININT;
179
180         return GPOINTER_TO_INT(r_gpointer);
181 }
182
183 /* Returns: TRUE if all modules were found. */
184 gboolean captivemodid_module_type_best_priority_found(const xmlChar *module_type)
185 {
186 gboolean errbool;
187
188         g_return_val_if_fail(module_type!=NULL,FALSE);
189
190         module_type_best_priority_hash_init();
191         errbool=g_hash_table_remove(module_type_best_priority_hash,module_type);
192         g_assert(errbool==TRUE);
193
194         return !g_hash_table_size(module_type_best_priority_hash);
195 }
196
197 static xmlChar *captivemodid_load_module_xml_get_attr
198                 (const gchar *captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name)
199 {
200 xmlChar *r;
201
202         if (!(r=xmlTextReaderGetAttribute(xml_reader,BAD_CAST attr_name))) {
203                 /* FIXME: File line identification? */
204                 g_warning(_("%s: Undefined attributes: %s"),captivemodid_pathname,attr_name);
205                 return NULL;
206                 }
207         return r;
208 }
209
210 static long captivemodid_load_module_xml_get_attr_l
211                 (const gchar *captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max)
212 {
213 xmlChar *string;
214 long r;
215 char *ends;
216
217         g_return_val_if_fail(num_min-1<num_min,-1);
218         g_return_val_if_fail(num_min<=num_max,num_min-1);
219         g_return_val_if_fail(LONG_MIN<num_min,LONG_MIN);
220         g_return_val_if_fail(num_max<LONG_MAX,num_min-1);
221
222         if (!(string=captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,attr_name)))
223                 return num_min-1;
224         r=strtol((const char *)string,&ends,0);
225         xmlFree(string);
226         if (r<num_min || r>num_max) {
227                 g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captivemodid_pathname,num_min,num_max,r);
228                 return num_min-1;
229                 }
230         return r;
231 }
232
233 static void captivemodid_load_module_xml(const gchar *captivemodid_pathname,xmlTextReader *xml_reader)
234 {
235 struct captivemodid_module *module;
236 xmlChar *cabinet_used_string;
237
238         captive_new0(module);
239         if (!(module->type=captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"type")))
240                 goto fail_free_module;
241         if (!(module->md5 =captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"md5")))
242                 goto fail_free_module;
243         if (strlen((const char *)module->md5)!=strspn((const char *)module->md5,"0123456789abcdef")) {
244                 g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captivemodid_pathname,module->md5);
245                 goto fail_free_module;
246                 }
247         if (strlen((const char *)module->md5)!=32) {
248                 g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captivemodid_pathname,module->md5);
249                 goto fail_free_module;
250                 }
251         if (!(module->id  =captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"id")))
252                 goto fail_free_module;
253         if (0>=(module->length=captivemodid_load_module_xml_get_attr_l(
254                         captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1)))
255                 goto fail_free_module;
256         if (!(cabinet_used_string=xmlTextReaderGetAttribute(xml_reader,BAD_CAST "cabinet_used")))
257                 module->cabinet_used=0;
258         else {
259                 xmlFree(cabinet_used_string);
260                 if (0>=(module->cabinet_used=captivemodid_load_module_xml_get_attr_l(
261                                 captivemodid_pathname,xml_reader,"cabinet_used",1,G_MAXINT-1)))
262                         goto fail_free_module;
263                 }
264         if (G_MININT>=(module->priority=captivemodid_load_module_xml_get_attr_l(captivemodid_pathname,xml_reader,"priority",
265                         G_MININT+1,G_MAXINT-1)))
266                 goto fail_free_module;
267         captivemodid_load_module(module);
268         return;
269
270 fail_free_module:
271         xmlFree((xmlChar *)module->type);
272         xmlFree((xmlChar *)module->md5);
273         xmlFree((xmlChar *)module->id);
274         g_free(module);
275 }
276
277 static void captivemodid_load_foreach
278                 (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */)
279 {
280         g_return_if_fail(type!=NULL);
281
282         g_return_if_fail(captivemodid_module_best_priority_notify!=NULL);
283
284         (*captivemodid_module_best_priority_notify)((const gchar *)type);
285 }
286
287 void (*captivemodid_module_best_priority_notify)(const gchar *module_type);
288
289 gboolean captivemodid_load(const gchar *captivemodid_pathname)
290 {
291 xmlTextReader *xml_reader;
292
293         if (!(xml_reader=xmlNewTextReaderFilename(captivemodid_pathname)))
294                 return FALSE;
295         while (1==xmlTextReaderRead(xml_reader)) {
296                 switch (xmlTextReaderNodeType(xml_reader)) {
297
298                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
299                                 break;
300
301                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE:
302                                 break;
303
304                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT:    /* Even empty nodes have some '#text'. */
305                                 break;
306
307                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END:     /* We do not track tag ends. */
308                                 break;
309
310                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
311 const xmlChar *xml_name;
312
313                                 xml_name=xmlTextReaderName(xml_reader);
314                                 /**/ if (!xmlStrcmp(xml_name,BAD_CAST "modid")) {       /* root tag */
315                                         }
316                                 else if (!xmlStrcmp(xml_name,BAD_CAST "module"))
317                                         captivemodid_load_module_xml(captivemodid_pathname,xml_reader);
318                                 else g_warning(_("%s: Unknown ELEMENT node: %s"),captivemodid_pathname,xml_name);
319                                 xmlFree((xmlChar *)xml_name);
320                                 } break;
321
322                         default: g_assert_not_reached();
323                         }
324                 }
325         xmlFreeTextReader(xml_reader);
326
327         if (captivemodid_module_best_priority_notify) {
328                 g_hash_table_foreach(module_type_best_priority_hash,
329                                                 (GHFunc)captivemodid_load_foreach,NULL);
330                 }
331         return TRUE;
332 }