]> git.draconx.ca Git - upkg.git/blob - src/package.c
libupkg: Parameterise I/O functions to allow custom streams.
[upkg.git] / src / package.c
1 /*
2  *  upkg: tool for manipulating Unreal Tournament packages.
3  *  Copyright (C) 2009 Nick Bowler
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, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23
24 #include <glib-object.h>
25 #include <ltdl.h>
26
27 #include <uobject/package.h>
28
29 #define U_PKG_GET_PRIV(o) \
30         G_TYPE_INSTANCE_GET_PRIVATE(o, U_PKG_TYPE, struct upkg_priv)
31
32 struct upkg_priv {
33         lt_dlhandle native;
34         FILE *pkg_file;
35 };
36
37 G_DEFINE_TYPE(UPkg, u_pkg, G_TYPE_TYPE_MODULE);
38
39 /* Package search path. */
40 static char  *search_path;
41 static size_t search_path_sz;
42
43 /* List of package file extensions, in descending order of precedence. */
44 static const char u_pkg_exts[][5] = { ".u", ".utx", ".umx", ".uax", ".unr" };
45
46 static char *str_cpy_lower(char *dst, const char *src)
47 {
48         size_t i;
49
50         for (i = 0; src[i]; i++)
51                 dst[i] = tolower(src[i]);
52         dst[i] = 0;
53
54         return dst;
55 }
56
57 static int str_cmp_lower(const char *s1, const char *s2)
58 {
59         size_t i;
60
61         for (i = 0; s1[i] && s2[i]; i++) {
62                 int c1 = tolower(s1[i]), c2 = tolower(s2[i]);
63                 if (c1 < c2)
64                         return -1;
65                 if (c1 > c2)
66                         return 1;
67         }
68
69         if (s1[i])
70                 return 1;
71         if (s2[i])
72                 return -1;
73         return 0;
74 }
75
76 static void dl_print_errors(const char *prefix)
77 {
78         const char *err;
79         while ((err = lt_dlerror())) {
80                 if (prefix) fprintf(stderr, "%s: ", prefix);
81                 fprintf(stderr, "%s\n", err);
82         }
83 }
84
85 /*
86  * Function for use with lt_dlforeachfile.  The user data must point to
87  * a UPkg with the GTypeModule name field initialized.  If a suitable file
88  * is found and could be successfully opened for reading, the pkg_file member
89  * of struct upkg_priv will be filled in.
90  */
91 static int find_package_by_name(const char *filename, void *_pkg)
92 {
93         struct upkg_priv *priv = U_PKG_GET_PRIV(_pkg);
94         GTypeModule *m = _pkg;
95
96         const char *base;
97         size_t len;
98         char *buf;
99
100         base = strrchr(filename, '/');
101         if (base) {
102                 base++;
103         } else {
104                 base = filename;
105         }
106
107         if (str_cmp_lower(base, m->name) != 0)
108                 return 0;
109
110         len = strlen(filename);
111         buf = malloc(len + sizeof **u_pkg_exts);
112         if (!buf)
113                 return 0;
114         strcpy(buf, filename);
115
116         for (unsigned i = 0; i < sizeof u_pkg_exts / sizeof *u_pkg_exts; i++) {
117                 strcpy(buf+len, u_pkg_exts[i]);
118                 priv->pkg_file = fopen(buf, "rb");
119                 if (priv->pkg_file) {
120                         free(buf);
121                         return 1;
122                 }
123         }
124
125         free(buf);
126         return 0;
127 }
128
129 static gboolean u_pkg_load(GTypeModule *m)
130 {
131         struct upkg_priv *priv = U_PKG_GET_PRIV(m);
132         int (*init_func)(GTypeModule *);
133
134         priv->native = lt_dlopenext(m->name);
135         if (!priv->native) {
136                 dl_print_errors(m->name);
137                 return FALSE;
138         }
139
140         init_func = lt_dlsym(priv->native, "init");
141         if (!init_func || init_func(m) != 0) {
142                 dl_print_errors(__func__);
143                 lt_dlclose(priv->native);
144                 return FALSE;
145         }
146
147         lt_dlforeachfile(u_pkg_get_search_path(), find_package_by_name, m);
148
149         return TRUE;
150 }
151
152 static void u_pkg_unload(GTypeModule *m)
153 {
154         struct upkg_priv *priv = U_PKG_GET_PRIV(m);
155         void (*exit_func)(GTypeModule *);
156
157         if (priv->native) {
158                 exit_func = lt_dlsym(priv->native, "exit");
159                 if (exit_func) {
160                         exit_func(m);
161                 }
162
163                 if (lt_dlclose(priv->native) != 0) {
164                         dl_print_errors(__func__);
165                 }
166         }
167
168         if (priv->pkg_file) {
169                 fclose(priv->pkg_file);
170         }
171 }
172
173 static void u_pkg_init(UPkg *pkg)
174 {
175
176 }
177
178 static void u_pkg_class_init(UPkgClass *class)
179 {
180         GTypeModuleClass *modclass = G_TYPE_MODULE_CLASS(class);
181         const char *modpath = getenv("UOBJECT_MODULE_PATH");
182
183         g_type_class_add_private(class, sizeof (struct upkg_priv));
184
185         if (lt_dlinit() != 0) {
186                 dl_print_errors(__func__);
187         }
188
189         if (modpath && lt_dlsetsearchpath(modpath) != 0) {
190                 dl_print_errors(__func__);
191         }
192         if (lt_dladdsearchdir(PKGLIBDIR) != 0) {
193                 dl_print_errors(__func__);
194         }
195
196         modclass->load   = u_pkg_load;
197         modclass->unload = u_pkg_unload;
198 }
199
200 static int expand_search_path(size_t need)
201 {
202         size_t want = search_path_sz;
203         if (want == 0) want = 1;
204
205         while (want < need)
206                 want *= 2;
207
208         if (want > search_path_sz) {
209                 char *new = realloc(search_path, want);
210                 if (!new) {
211                         return -1;
212                 }
213
214                 search_path    = new;
215                 search_path_sz = want;
216         }
217
218         return 0;
219 }
220
221 const char *u_pkg_get_search_path(void)
222 {
223         return search_path ? search_path : "";
224 }
225
226 int u_pkg_set_search_path(const char *path)
227 {
228         if (expand_search_path(strlen(path)+1) != 0)
229                 return -1;
230         strcpy(search_path, path);
231         return 0;
232 }
233
234 int u_pkg_add_search_dir(const char *path)
235 {
236         size_t end = search_path ? strlen(search_path) : 0;
237
238         if (end == 0) {
239                 return u_pkg_set_search_path(path);
240         }
241
242         if (expand_search_path(end + strlen(path) + 2) != 0)
243                 return -1;
244         search_path[end] = LT_PATHSEP_CHAR;
245         strcpy(search_path+end+1, path);
246         return 0;
247 }
248
249 GTypeModule *u_pkg_new(const char *name)
250 {
251         g_return_val_if_fail(name != NULL, NULL);
252
253         char *pkgname = malloc(strlen(name)+1);
254         if (!pkgname) {
255                 return NULL;
256         }
257
258         GTypeModule *mod = g_object_new(U_PKG_TYPE, NULL);
259         if (!mod) {
260                 free(pkgname);
261                 return NULL;
262         }
263
264         mod->name = str_cpy_lower(pkgname, name);
265         return mod;
266 }