opkg: add extra data to libopkg progress callbacks
[oweals/opkg-lede.git] / libopkg / opkg.c
1 /* opkg.c - the opkg  package management system
2
3    Thomas Wood <thomas@openedhand.com>
4
5    Copyright (C) 2008 OpenMoko Inc
6
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License as
9    published by the Free Software Foundation; either version 2, or (at
10    your option) any later version.
11
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16  */
17
18 #include <config.h>
19 #include <fnmatch.h>
20
21 #include "opkg.h"
22 #include "opkg_conf.h"
23 #include "args.h"
24
25 #include "opkg_install.h"
26 #include "opkg_configure.h"
27 #include "opkg_download.h"
28 #include "opkg_remove.h"
29 #include "opkg_upgrade.h"
30
31 #include "sprintf_alloc.h"
32 #include "file_util.h"
33
34 #include <libbb/libbb.h>
35
36 struct _opkg_t
37 {
38   args_t *args;
39   opkg_conf_t *conf;
40   opkg_option_t *options;
41 };
42
43 #define opkg_assert(expr) if (!(expr)) { \
44     printf ("opkg: file %s: line %d (%s): Assertation '%s' failed",\
45             __FILE__, __LINE__, __PRETTY_FUNCTION__, # expr); abort (); }
46
47 #define progress(d, p) d.percentage = p; if (progress_callback) progress_callback (opkg, &d, user_data);
48 #define OLD_PKG_TO_NEW(pkg) opkg_package_new_with_values (pkg->name, pkg->version, pkg->architecture, pkg->description, pkg->tags, (pkg->state_status == SS_INSTALLED));
49
50 /** Private Functions ***/
51
52
53 static int
54 opkg_configure_packages(opkg_conf_t *conf, char *pkg_name)
55 {
56   pkg_vec_t *all;
57   int i;
58   pkg_t *pkg;
59   int r, err = 0;
60
61   all = pkg_vec_alloc ();
62   pkg_hash_fetch_available (&conf->pkg_hash, all);
63
64   for (i = 0; i < all->len; i++)
65   {
66     pkg = all->pkgs[i];
67
68     if (pkg_name && fnmatch (pkg_name, pkg->name, 0))
69       continue;
70
71     if (pkg->state_status == SS_UNPACKED)
72     {
73       r = opkg_configure (conf, pkg);
74       if (r == 0)
75       {
76         pkg->state_status = SS_INSTALLED;
77         pkg->parent->state_status = SS_INSTALLED;
78         pkg->state_flag &= ~SF_PREFER;
79       }
80       else
81       {
82         if (!err)
83           err = r;
84       }
85     }
86   }
87
88   pkg_vec_free (all);
89   return err;
90 }
91
92 struct _curl_cb_data
93 {
94   opkg_progress_callback_t cb;
95   opkg_progress_data_t *progress_data;
96   opkg_t *opkg;
97   void *user_data;
98   int start_range;
99   int finish_range;
100 };
101
102 int
103 curl_progress_cb (struct _curl_cb_data *cb_data,
104                   double t,   /* dltotal */
105                   double d,   /* dlnow */
106                   double ultotal,
107                   double ulnow)
108 {
109   int p = (t) ? d * 100 / t : 0;
110   static int prev = -1;
111   int progress = 0;
112
113   /* prevent the same value being sent twice (can occur due to rounding) */
114   if (p == prev)
115     return 0;
116   prev = p;
117
118   if (t < 1)
119     return 0;
120
121   progress = cb_data->start_range + (d / t * ((cb_data->finish_range - cb_data->start_range)));
122   cb_data->progress_data->percentage = progress;
123
124   (cb_data->cb)(cb_data->opkg,
125                 cb_data->progress_data,
126                 cb_data->user_data);
127
128   return 0;
129 }
130
131
132 /*** Public API ***/
133
134 opkg_package_t *
135 opkg_package_new ()
136 {
137
138   opkg_package_t *p;
139
140   p = malloc (sizeof (opkg_package_t));
141   memset (p, 0, sizeof (opkg_package_t));
142
143   return p;
144 }
145
146 opkg_package_t *
147 opkg_package_new_with_values (const char *name, const char *version,
148     const char *arch, const char *desc, const char *tags, int installed)
149 {
150   opkg_package_t *package;
151   package = opkg_package_new ();
152
153 #define sstrdup(x) (x) ? strdup (x) : NULL;
154
155   package->name = sstrdup (name);
156   package->version = sstrdup (version);
157   package->architecture = sstrdup (arch);
158   package->description = sstrdup (desc);
159   package->tags = sstrdup (tags);
160   package->installed = (installed != 0);
161
162   return package;
163 }
164
165 void
166 opkg_package_free (opkg_package_t *p)
167 {
168   free (p->name);
169   free (p->version);
170   free (p->architecture);
171   free (p->description);
172   free (p->tags);
173
174   free (p);
175 }
176
177 opkg_t *
178 opkg_new ()
179 {
180   opkg_t *opkg;
181   opkg = malloc (sizeof (opkg_t));
182
183   opkg->args = malloc (sizeof (args_t));
184   args_init (opkg->args);
185
186   opkg->conf = malloc (sizeof (opkg_conf_t));
187   opkg_conf_init (opkg->conf, opkg->args);
188
189   opkg_init_options_array (opkg->conf, &opkg->options);
190   return opkg;
191 }
192
193 void
194 opkg_free (opkg_t *opkg)
195 {
196   opkg_assert (opkg != NULL);
197
198   opkg_conf_deinit (opkg->conf);
199   args_deinit (opkg->args);
200 }
201
202 int
203 opkg_re_read_config_files (opkg_t *opkg)
204 {
205   args_t *a;
206   opkg_conf_t *c;
207
208   opkg_assert (opkg != NULL);
209
210   a = opkg->args;
211   c = opkg->conf;
212
213   /* Unfortunatly, the easiest way to re-read the config files right now is to
214    * throw away opkg->conf and start again */
215
216   /* copy the settings we need to keep */
217   a->autoremove = c->autoremove;
218   a->force_depends = c->force_depends;
219   a->force_defaults = c->force_defaults;
220   a->force_overwrite = c->force_overwrite;
221   a->force_downgrade = c->force_downgrade;
222   a->force_reinstall = c->force_reinstall;
223   a->force_removal_of_dependent_packages = c->force_removal_of_dependent_packages;
224   a->force_removal_of_essential_packages = c->force_removal_of_essential_packages;
225   a->nodeps = c->nodeps;
226   a->noaction = c->noaction;
227   a->query_all = c->query_all;
228   a->multiple_providers = c->multiple_providers;
229   a->verbosity = c->verbosity;
230
231   if (c->offline_root)
232   {
233     if (a->offline_root) free (a->offline_root);
234     a->offline_root = strdup (c->offline_root);
235   }
236
237   if (c->offline_root_pre_script_cmd)
238   {
239     if (a->offline_root_pre_script_cmd) free (a->offline_root_pre_script_cmd);
240     a->offline_root_pre_script_cmd = strdup (c->offline_root_pre_script_cmd);
241   }
242
243   if (c->offline_root_post_script_cmd)
244   {
245     if (a->offline_root_post_script_cmd) free (a->offline_root_post_script_cmd);
246     a->offline_root_post_script_cmd = strdup (c->offline_root_post_script_cmd);
247   }
248
249   /* throw away old opkg_conf and start again */
250   opkg_conf_deinit (opkg->conf);
251   opkg_conf_init (opkg->conf, opkg->args);
252
253   free (opkg->options);
254   opkg_init_options_array (opkg->conf, &opkg->options);
255
256   return 0;
257 }
258
259 void
260 opkg_get_option (opkg_t *opkg, char *option, void **value)
261 {
262   int i = 0;
263   opkg_option_t *options;
264
265   opkg_assert (opkg != NULL);
266   opkg_assert (option != NULL);
267   opkg_assert (value != NULL);
268
269   options = opkg->options;
270
271   /* look up the option
272    * TODO: this would be much better as a hash table
273    */
274   while (options[i].name)
275   {
276     if (strcmp (options[i].name, option) != 0)
277     {
278       i++;
279       continue;
280     }
281   }
282
283   /* get the option */
284   switch (options[i].type)
285   {
286   case OPKG_OPT_TYPE_BOOL:
287     *((int *) value) = *((int *) options[i].value);
288     return;
289
290   case OPKG_OPT_TYPE_INT:
291     *((int *) value) = *((int *) options[i].value);
292     return;
293
294   case OPKG_OPT_TYPE_STRING:
295     *((char **)value) = strdup (options[i].value);
296     return;
297   }
298
299 }
300
301 void
302 opkg_set_option (opkg_t *opkg, char *option, void *value)
303 {
304   int i = 0, found = 0;
305   opkg_option_t *options;
306
307   opkg_assert (opkg != NULL);
308   opkg_assert (option != NULL);
309   opkg_assert (value != NULL);
310
311   options = opkg->options;
312
313   /* look up the option
314    * TODO: this would be much better as a hash table
315    */
316   while (options[i].name)
317   {
318     if (strcmp (options[i].name, option) == 0)
319     {
320       found = 1;
321       break;
322     }
323     i++;
324   }
325
326   if (!found)
327   {
328     /* XXX: Warning: Option not found */
329     return;
330   }
331
332   /* set the option */
333   switch (options[i].type)
334   {
335   case OPKG_OPT_TYPE_BOOL:
336     if (*((int *) value) == 0)
337       *((int *)options[i].value) = 0;
338     else
339       *((int *)options[i].value) = 1;
340     return;
341
342   case OPKG_OPT_TYPE_INT:
343     *((int *) options[i].value) = *((int *) value);
344     return;
345
346   case OPKG_OPT_TYPE_STRING:
347     *((char **)options[i].value) = strdup (value);
348     return;
349   }
350
351 }
352
353 int
354 opkg_install_package (opkg_t *opkg, const char *package_name, opkg_progress_callback_t progress_callback, void *user_data)
355 {
356   int err;
357   char *package_id = NULL;
358   opkg_progress_data_t pdata;
359   pkg_t *new;
360
361   opkg_assert (opkg != NULL);
362   opkg_assert (package_name != NULL);
363
364   /* ... */
365   pkg_info_preinstall_check (opkg->conf);
366
367   new = pkg_hash_fetch_best_installation_candidate_by_name (opkg->conf, package_name);
368
369   if (!new)
370   {
371     /* XXX: Error: Could not find package to install */
372     return 1;
373   }
374   pdata.action = OPKG_INSTALL;
375   pdata.package = OLD_PKG_TO_NEW (new);
376
377   progress (pdata, 0);
378
379   /* download the package */
380   opkg_prepare_url_for_install (opkg->conf, package_name, &package_id);
381
382   progress (pdata, 50);
383
384   if (!package_id)
385     package_id = strdup (package_name);
386
387   /* unpack the package */
388   if (opkg->conf->multiple_providers)
389   {
390     err = opkg_install_multi_by_name (opkg->conf, package_id);
391   }
392   else
393   {
394     err = opkg_install_by_name (opkg->conf, package_id);
395   }
396
397   if (err)
398     return err;
399
400   progress (pdata, 75);
401
402   /* run configure scripts, etc. */
403   err = opkg_configure_packages (opkg->conf, NULL);
404   if (err)
405     return err;
406
407   /* write out status files and file lists */
408   opkg_conf_write_status_files (opkg->conf);
409   pkg_write_changed_filelists (opkg->conf);
410
411   progress (pdata, 100);
412   opkg_package_free (pdata.package);
413   return 0;
414 }
415
416 int
417 opkg_remove_package (opkg_t *opkg, const char *package_name, opkg_progress_callback_t progress_callback, void *user_data)
418 {
419   pkg_t *pkg = NULL;
420   pkg_t *pkg_to_remove;
421   opkg_progress_data_t pdata;
422
423   opkg_assert (opkg != NULL);
424   opkg_assert (package_name != NULL);
425
426
427
428   pkg_info_preinstall_check (opkg->conf);
429
430
431   pkg = pkg_hash_fetch_installed_by_name (&opkg->conf->pkg_hash, package_name);
432
433   if (pkg == NULL)
434   {
435     /* XXX: Error: Package not installed. */
436     return 1;
437   }
438
439   pdata.action = OPKG_REMOVE;
440   pdata.package = OLD_PKG_TO_NEW (pkg);
441   progress (pdata, 0);
442
443
444   if (pkg->state_status == SS_NOT_INSTALLED)
445   {
446     /* XXX:  Error: Package seems to be not installed (STATUS = NOT_INSTALLED). */
447     return 1;
448   }
449   progress (pdata, 25);
450
451   if (opkg->conf->restrict_to_default_dest)
452   {
453     pkg_to_remove = pkg_hash_fetch_installed_by_name_dest (&opkg->conf->pkg_hash,
454                                                            pkg->name,
455                                                            opkg->conf->default_dest);
456   }
457   else
458   {
459     pkg_to_remove = pkg_hash_fetch_installed_by_name (&opkg->conf->pkg_hash, pkg->name );
460   }
461
462
463   progress (pdata, 75);
464
465   opkg_remove_pkg (opkg->conf, pkg_to_remove, 0);
466
467   /* write out status files and file lists */
468   opkg_conf_write_status_files (opkg->conf);
469   pkg_write_changed_filelists (opkg->conf);
470
471
472   progress (pdata, 100);
473   opkg_package_free (pdata.package);
474   return 0;
475 }
476
477 int
478 opkg_upgrade_package (opkg_t *opkg, const char *package_name, opkg_progress_callback_t progress_callback, void *user_data)
479 {
480   pkg_t *pkg;
481   opkg_progress_data_t pdata;
482
483
484
485   opkg_assert (opkg != NULL);
486   opkg_assert (package_name != NULL);
487
488   pkg_info_preinstall_check (opkg->conf);
489
490   if (opkg->conf->restrict_to_default_dest)
491   {
492     pkg = pkg_hash_fetch_installed_by_name_dest (&opkg->conf->pkg_hash,
493                                                  package_name,
494                                                  opkg->conf->default_dest);
495     if (pkg == NULL)
496     {
497       /* XXX: Error: Package not installed in default_dest */
498       return 1;
499     }
500   }
501   else
502   {
503     pkg = pkg_hash_fetch_installed_by_name (&opkg->conf->pkg_hash,
504                                             package_name);
505   }
506
507   if (!pkg)
508   {
509     /* XXX: Error: Package not installed */
510     return 1;
511   }
512
513   pdata.action = OPKG_INSTALL;
514   pdata.package = OLD_PKG_TO_NEW (pkg);
515   progress (pdata, 0);
516
517   opkg_upgrade_pkg (opkg->conf, pkg);
518   progress (pdata, 75);
519
520   opkg_configure_packages (opkg->conf, NULL);
521   progress (pdata, 100);
522   opkg_package_free (pdata.package);
523   return 0;
524 }
525
526 int
527 opkg_upgrade_all (opkg_t *opkg, opkg_progress_callback_t progress_callback, void *user_data)
528 {
529   pkg_vec_t *installed;
530   int err = 0;
531   int i;
532   pkg_t *pkg;
533   opkg_progress_data_t pdata;
534
535   pdata.action = OPKG_INSTALL;
536   pdata.package = NULL;
537
538   opkg_assert (opkg != NULL);
539   progress (pdata, 0);
540
541   installed = pkg_vec_alloc ();
542   pkg_info_preinstall_check (opkg->conf);
543
544   pkg_hash_fetch_all_installed (&opkg->conf->pkg_hash, installed);
545   for (i = 0; i < installed->len; i++)
546   {
547     pkg = installed->pkgs[i];
548
549     pdata.package = OLD_PKG_TO_NEW (pkg);
550     progress (pdata, 99 * i / installed->len);
551     opkg_package_free (pdata.package);
552
553     err += opkg_upgrade_pkg (opkg->conf, pkg);
554   }
555   pkg_vec_free (installed);
556
557   if (err)
558     return 1;
559
560   err = opkg_configure_packages (opkg->conf, NULL);
561   if (err)
562     return 1;
563
564   pdata.package = NULL;
565   progress (pdata, 100);
566   return 0;
567 }
568
569 int
570 opkg_update_package_lists (opkg_t *opkg, opkg_progress_callback_t progress_callback, void *user_data)
571 {
572   char *tmp;
573   int err;
574   char *lists_dir;
575   pkg_src_list_elt_t *iter;
576   pkg_src_t *src;
577   int sources_list_count, sources_done;
578   opkg_progress_data_t pdata;
579
580   opkg_assert (opkg != NULL);
581
582   pdata.action = OPKG_DOWNLOAD;
583   pdata.package = NULL;
584   progress (pdata, 0);
585
586   sprintf_alloc (&lists_dir, "%s",
587                  (opkg->conf->restrict_to_default_dest)
588                  ? opkg->conf->default_dest->lists_dir
589                  : opkg->conf->lists_dir);
590
591   if (!file_is_dir (lists_dir))
592   {
593     if (file_exists (lists_dir))
594     {
595       /* XXX: Error: file exists but is not a directory */
596       free (lists_dir);
597       return 1;
598     }
599
600     err = file_mkdir_hier (lists_dir, 0755);
601     if (err)
602     {
603       /* XXX: Error: failed to create directory */
604       free (lists_dir);
605       return 1;
606     }
607   }
608
609   tmp = strdup ("/tmp/opkg.XXXXXX");
610
611   if (mkdtemp (tmp) == NULL)
612   {
613     /* XXX: Error: could not create temporary file name */
614     free (lists_dir);
615     free (tmp);
616     return 1;
617   }
618
619   /* cout the number of sources so we can give some progress updates */
620   sources_list_count = 0;
621   sources_done = 0;
622   iter = opkg->conf->pkg_src_list.head;
623   while (iter)
624   {
625     sources_list_count++;
626     iter = iter->next;
627   }
628
629   for (iter = opkg->conf->pkg_src_list.head; iter; iter = iter->next)
630   {
631     char *url, *list_file_name;
632
633     src = iter->data;
634
635     if (src->extra_data)  /* debian style? */
636       sprintf_alloc (&url, "%s/%s/%s", src->value, src->extra_data,
637                      src->gzip ? "Packages.gz" : "Packages");
638     else
639       sprintf_alloc (&url, "%s/%s", src->value, src->gzip ? "Packages.gz" : "Packages");
640
641     sprintf_alloc (&list_file_name, "%s/%s", lists_dir, src->name);
642     if (src->gzip)
643     {
644       char *tmp_file_name;
645       FILE *in, *out;
646       struct _curl_cb_data cb_data;
647
648       sprintf_alloc (&tmp_file_name, "%s/%s.gz", tmp, src->name);
649
650       /* XXX: Note: downloading url */
651
652       cb_data.cb = progress_callback;
653       cb_data.progress_data = &pdata;
654       cb_data.opkg = opkg;
655       cb_data.user_data = user_data;
656       cb_data.start_range = 100 * sources_done / sources_list_count;
657       cb_data.finish_range = 100 * (sources_done + 1) / sources_list_count;
658
659       err = opkg_download (opkg->conf, url, tmp_file_name, (curl_progress_func) curl_progress_cb, &cb_data);
660
661       if (err == 0)
662       {
663         /* XXX: Note: Inflating downloaded file */
664         in = fopen (tmp_file_name, "r");
665         out = fopen (list_file_name, "w");
666         if (in && out)
667           unzip (in, out);
668         else
669           err = 1;
670         if (in)
671           fclose (in);
672         if (out)
673           fclose (out);
674         unlink (tmp_file_name);
675       }
676     }
677     else
678       err = opkg_download (opkg->conf, url, list_file_name, NULL, NULL);
679
680     if (err)
681     {
682       /* XXX: Error: download error */
683     }
684     free (url);
685
686 #ifdef HAVE_GPGME
687     /* download detached signitures to verify the package lists */
688     /* get the url for the sig file */
689     if (src->extra_data)  /* debian style? */
690       sprintf_alloc (&url, "%s/%s/%s", src->value, src->extra_data,
691                      "Packages.sig");
692     else
693       sprintf_alloc (&url, "%s/%s", src->value, "Packages.sig");
694
695     /* create temporary file for it */
696     char *tmp_file_name;
697
698     sprintf_alloc (&tmp_file_name, "%s/%s", tmp, "Packages.sig");
699
700     err = opkg_download (opkg->conf, url, tmp_file_name, NULL, NULL);
701     if (err)
702     {
703       /* XXX: Warning: Download failed */
704     }
705     else
706     {
707       int err;
708       err = opkg_verify_file (opkg->conf, list_file_name, tmp_file_name);
709       if (err == 0)
710       {
711         /* XXX: Notice: Signature check passed */
712       }
713       else
714       {
715         /* XXX: Warning: Signature check failed */
716       }
717     }
718     unlink (tmp_file_name);
719     free (tmp_file_name);
720     free (url);
721 #else
722     /* XXX: Note: Signiture check for %s skipped because GPG support was not
723      * enabled in this build
724      */
725 #endif
726     free (list_file_name);
727
728     sources_done++;
729     progress (pdata, 100 * sources_done / sources_list_count);
730   }
731
732   rmdir (tmp);
733   free (tmp);
734   free (lists_dir);
735
736   return 0;
737 }
738
739
740 int
741 opkg_list_packages (opkg_t *opkg, opkg_package_callback_t callback, void *user_data)
742 {
743   pkg_vec_t *all;
744   int i;
745
746   opkg_assert (opkg);
747   opkg_assert (callback);
748
749   all = pkg_vec_alloc ();
750   pkg_hash_fetch_available (&opkg->conf->pkg_hash, all);
751   for (i = 0; i < all->len; i++)
752   {
753     pkg_t *pkg;
754     opkg_package_t *package;
755
756     pkg = all->pkgs[i];
757
758     package = OLD_PKG_TO_NEW (pkg);
759     callback (opkg, package, user_data);
760   }
761
762   pkg_vec_free (all);
763
764   return 0;
765 }
766
767 int
768 opkg_list_upgradable_packages (opkg_t *opkg, opkg_package_callback_t callback, void *user_data)
769 {
770   pkg_vec_t *all;
771   int i;
772
773   opkg_assert (opkg);
774   opkg_assert (callback);
775
776   all = pkg_vec_alloc ();
777   pkg_hash_fetch_available (&opkg->conf->pkg_hash, all);
778   for (i = 0; i < all->len; i++)
779   {
780     pkg_t *old, *new;
781     int cmp;
782     opkg_package_t *package;
783
784     old = all->pkgs[i];
785     
786     if (old->state_status != SS_INSTALLED)
787       continue;
788
789     new = pkg_hash_fetch_best_installation_candidate_by_name(opkg->conf, old->name);
790     if (new == NULL) {
791       /* XXX: Notice: Assuming locally install package is up to date */
792       continue;
793     }
794           
795     cmp = pkg_compare_versions(old, new);
796
797     if (cmp < 0)
798     {
799       package = OLD_PKG_TO_NEW (new);
800       callback (opkg, package, user_data);
801     }
802   }
803
804   pkg_vec_free (all);
805
806   return 0;
807 }
808