1 /*----------------------------------------------------------------------------
2 libopt
3 ------------------------------------------------------------------------------
4 This is a program to convert link library filepaths to linker options
5 that select them. E.g. ../lib/libnetpbm.so becomes -L../lib -lnetpbm .
6
7 Each argument is a library filepath. The option string to identify
8 all of those library filepaths goes to Standard Output.
9
10 If there is no slash in the library filepath, we assume it is just a
11 filename to be searched for in the linker's default search path, and
12 generate a -l option, but no -L.
13
14 If an argument doesn't make sense as a library filespec, we copy
15 it verbatim, blank delimited, to the output string.
16
17 The "lib" part of the library name, which we call the prefix, may be
18 other than "lib". The list of recognized values is compiled in as
19 the macro SHLIBPREFIXLIST (see below).
20
21 There is no newline or null character or anything after the output.
22
23 If you specify the option "-runtime", the output includes a -R
24 option in addition, for every library after "-runtime" in the
25 arguments. -R tells the buildtime linker to include the specified
26 library's directory in the search path that the runtime linker uses
27 to find dynamically linked libraries.
28
29 But if you compile this program with the EXPLICIT macro defined, and
30 a library filepath contains a slash, then it skips all that -L/-l
31 nonsense and just outputs the input library filepath verbatim (plus
32 any -R option).
33 ------------------------------------------------------------------------------
34 Why would you want to use this?
35
36 On some systems, the -L/-l output of this program has exactly the
37 same effect as the filepath input when used in the arguments to a
38 link command. A GNU/Linux system, for example. On others (Solaris,
39 for example), if you include /tmp/lib/libnetpbm.so in the link as a
40 link object, the executable gets built in such a way that the system
41 accesses the shared library /tmp/lib/libnetpbm.so at run time. On the
42 other hand, if you instead put the options -L/tmp/lib -lnetpbm on the
43 link command, the executable gets built so that the system accesses
44 libnetpbm.so in its actual installed directory at runtime (that
45 location might be determined by a --rpath linker option or a
46 LD_LIBRARY_PATH environment variable at run time).
47
48 In a make file, it is nice to use the same variable as the
49 dependency of a rule that builds an executable and as the thing that
50 the rule's command uses to identify its input. Here is an example
51 of using libopt for that:
52
53 NETPBMLIB=../lib/libnetpbm.so
54 ...
55 pbmmake: pbmmake.o $(NETPBMLIB)
56 ld -o pbmmake pbmmake.o `libopt $(NETPBMLIB)` --rpath=/lib/netpbm
57
58 Caveat: "-L../lib -lnetpbm" is NOT exactly the same as
59 "../lib/libnetpbm.so" on any system. All of the -l libraries are
60 searched for in all of the -L directories. So you just might get a
61 different library with the -L/-l version than if you specify the
62 library file explicitly.
63
64 That's why you should compile with -DEXPLICIT if your linker can
65 handle explicit file names.
66
67 -----------------------------------------------------------------------------*/
68 #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
69 #define _BSD_SOURCE 1 /* Make sure strdup() is in stdio.h */
70 #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */
71
72 #define MAX_PREFIXES 10
73
74 /* Here's how to use SHLIBPREFIXLIST: Use a -D compile option to pass in
75 a value appropriate for the platform on which you are linking libraries.
76
77 It's a blank-delimited list of prefixes that library names might
78 have. "lib" is traditionally the only prefix, as in libc or
79 libnetpbm. However, on Windows there is a convention of using
80 different prefixes to distinguish different co-existent versions of
81 the same library (kind of like a major number in some unices).
82 E.g. the value "cyg lib" is appropriate for a Cygwin system.
83 */
84 #ifndef SHLIBPREFIXLIST
85 # define SHLIBPREFIXLIST "lib"
86 #endif
87
88 #include <string.h>
89 #include <stdlib.h>
90 #include <stdio.h>
91
92 typedef unsigned char bool;
93 #ifndef TRUE
94 #define TRUE (1)
95 #endif
96 #ifndef FALSE
97 #define FALSE (0)
98 #endif
99
100 #ifdef DLLVERSTR
101 static const char * dllverstr = DLLVERSTR;
102 #else
103 static const char * dllverstr = "";
104 #endif
105
106 bool const explicit =
107 #ifdef EXPLICIT
108 TRUE
109 #else
110 FALSE
111 #endif
112 ;
113
114 static void
strfree(const char * const arg)115 strfree(const char * const arg) {
116 free((void*) arg);
117 }
118
119
120 static void
parse_prefixlist(const char * const prefixlist,char * parsed_prefixes[MAX_PREFIXES+1],bool * const errorP)121 parse_prefixlist(const char * const prefixlist,
122 char * parsed_prefixes[MAX_PREFIXES+1],
123 bool * const errorP) {
124 /*----------------------------------------------------------------------------
125 Given a space-separated list of tokens (suffixes), return an
126 argv-style array of pointers, each to a newly malloc'ed storage
127 area the prefix as a null-terminated string. Return a null string
128 for the unused entries at the end of the array
129
130 We never return more than MAX_PREFIXES prefixes from the input, so
131 there is guaranteed always to be one null string at the end of the
132 array.
133
134 In case of error, return *errorP == TRUE and don't allocate any
135 storage. Otherwise, return *errorP = FALSE.
136 -----------------------------------------------------------------------------*/
137 char * prlist;
138
139 prlist = strdup(prefixlist);
140 if (prlist == NULL)
141 *errorP = TRUE;
142 else {
143 if (strlen(prlist) <= 0)
144 *errorP = TRUE;
145 else {
146 /* NOTE: Mac OS X, at least, does not have strtok_r().
147 2001.09.24
148 */
149 char * token;
150 int num_tokens;
151 int i;
152
153 for (i=0; i < MAX_PREFIXES + 1; i++) {
154 parsed_prefixes[i] = NULL;
155 }
156 num_tokens = 0;
157 token = strtok(prlist, " ");
158 *errorP = FALSE; /* initial value */
159 while (token != NULL && num_tokens < MAX_PREFIXES && !*errorP) {
160 parsed_prefixes[num_tokens] = strdup (token);
161 if (parsed_prefixes[num_tokens] == NULL)
162 *errorP = TRUE;
163 num_tokens++;
164 token = strtok(NULL, " ");
165 }
166 for (i = num_tokens; i < MAX_PREFIXES + 1 && !*errorP; i++) {
167 parsed_prefixes[i] = strdup("");
168 if (parsed_prefixes[i] == NULL)
169 *errorP = TRUE;
170 }
171 }
172 if (*errorP) {
173 /* Deallocate any array entries we successfully did */
174 int i;
175 for (i = 0; i < MAX_PREFIXES + 1; i++)
176 if (parsed_prefixes[i])
177 free(parsed_prefixes[i]);
178 }
179 free(prlist);
180 }
181 }
182
183
184
185 static void
parse_prefix(const char * const filename,bool * const prefix_good_p,unsigned int * const prefix_length_p,bool * const error_p)186 parse_prefix(const char * const filename,
187 bool * const prefix_good_p, unsigned int * const prefix_length_p,
188 bool * const error_p) {
189 /*----------------------------------------------------------------------------
190 Find the library name prefix (e.g. "lib") in the library filename
191 'filename'.
192
193 Return the length of the prefix, in characters, as *prefix_length_p.
194 (The prefix always starts at the beginning of the filename).
195
196 Iff we don't find a valid library name prefix, return *prefix_good_p
197 == FALSE.
198
199 The list of valid prefixes is compiled in as the blank-delimited
200 string which is the value of the SHLIBPREFIXLIST macro.
201 -----------------------------------------------------------------------------*/
202 char * shlibprefixlist[MAX_PREFIXES+1];
203 /* An argv-style array of prefix strings in the first entries,
204 null strings in the later entries. At most MAX_PREFIXES prefixes,
205 so at least one null string.
206 */
207 char * prefix;
208 /* The prefix that the filename actually
209 uses. e.g. if shlibprefixlist = { "lib", "cyg", "", ... } and the
210 filename is "path/to/cyg<something>.<extension>", then
211 prefix = "cyg". String is in the same storage as pointed to by
212 shlibprefixlist (shlibprefixlist[1] in this example).
213 */
214 bool prefix_good;
215 /* The first part of the filename matched one of the prefixes
216 in shlibprefixlist[].
217 */
218 int prefix_length;
219 int i;
220
221 parse_prefixlist(SHLIBPREFIXLIST , shlibprefixlist, error_p);
222 if (!*error_p) {
223 if (strcmp(shlibprefixlist[0], "") == 0) {
224 fprintf(stderr, "libopt was compiled with an invalid value "
225 "of the SHLIBPREFIX macro. It seems to have no "
226 "tokens. SHLIBPREFIX = '%s'",
227 SHLIBPREFIXLIST);
228 exit(100);
229 }
230
231 i = 0; /* start with the first entry in shlibprefixlist[] */
232 prefix_length = 0; /* initial value */
233 prefix = shlibprefixlist[i];
234 prefix_good = FALSE; /* initial value */
235 while ( (*prefix != '\0' ) && !prefix_good ) {
236 /* stop condition: shlibprefixlist has MAX_PREFIXES+1 entries.
237 * we only ever put tokens in the 0..MAX_PREFIXES-1 positions.
238 * Then, we fill DOWN from the MAX_PREFIXES position with '\0'
239 * so we insure that the shlibprefixlist array contains at
240 * least one final '\0' string, but probably many '\0'
241 * strings (depending on how many tokens there were).
242 */
243 prefix_length = strlen(prefix);
244 if (strncmp(filename, prefix, prefix_length) == 0) {
245 prefix_good = TRUE;
246 /* at this point, prefix is pointing to the correct
247 * entry, and prefix_length has the correct value.
248 * When we bail out of the while loop because of the
249 * !prefix_good clause, we can then use these
250 * vars (prefix, prefix_length)
251 */
252 } else {
253 prefix = shlibprefixlist[++i];
254 }
255 }
256 *prefix_length_p = prefix_length;
257 *prefix_good_p = prefix_good;
258 {
259 int i;
260 for (i=0; i < MAX_PREFIXES + 1; i++)
261 free (shlibprefixlist[i]);
262 }
263 }
264 }
265
266
267
268 static void
parse_filename(const char * const filename,const char ** const libname_p,bool * const valid_library_p,bool * const static_p,bool * const error_p)269 parse_filename(const char * const filename,
270 const char ** const libname_p,
271 bool * const valid_library_p,
272 bool * const static_p,
273 bool * const error_p) {
274 /*----------------------------------------------------------------------------
275 Extract the library name root component of the filename 'filename'. This
276 is just a filename, not a whole pathname.
277
278 Return it in newly malloc'ed storage pointed to by '*libname_p'.
279
280 E.g. for "libxyz.so", return "xyz".
281
282 return *valid_library_p == TRUE iff 'filename' validly names a library
283 that can be expressed in a -l linker option.
284
285 return *static_p == TRUE iff 'filename' indicates a static library.
286 (but undefined if *valid_library_p != TRUE).
287
288 return *error_p == TRUE iff some error such as out of memory prevents
289 parsing.
290
291 Do not allocate any memory if *error_p == TRUE or *valid_library_p == FALSE.
292 -----------------------------------------------------------------------------*/
293 char *lastdot;
294 /* Pointer to last period in 'filename'. Null if none */
295
296 /* We accept any period-delimited suffix as a library type suffix.
297 It's probably .so or .a, but is could be .kalamazoo for all we
298 care. (HOWEVER, the double-suffixed import lib used on
299 cygwin (.dll.a) is NOT understood).
300 */
301 char *p;
302
303 lastdot = strrchr(filename, '.');
304 if (lastdot == NULL) {
305 /* This filename doesn't have any suffix, so we don't understand
306 it as a library filename.
307 */
308 *valid_library_p = FALSE;
309 *error_p = FALSE;
310 } else {
311 unsigned int prefix_length;
312 bool prefix_good;
313
314 if (strcmp(lastdot + 1, "a") == 0)
315 *static_p = TRUE;
316 else
317 *static_p = FALSE;
318
319 parse_prefix(filename, &prefix_good, &prefix_length, error_p);
320 if (!*error_p) {
321 if (!prefix_good) {
322 *valid_library_p = FALSE;
323 } else {
324 /* Extract everything between <prefix> and "." as
325 the library name root.
326 */
327 char * libname;
328
329 libname = strdup(filename + prefix_length);
330 if (libname == NULL)
331 *error_p = TRUE;
332 else {
333 libname[lastdot - filename - prefix_length] = '\0';
334 if (strlen(dllverstr) > 0) {
335 p = strstr(libname, dllverstr);
336 if (p) {
337 if (libname + strlen(libname)
338 - strlen(dllverstr) == p) {
339 *p = '\0';
340 }
341 }
342 }
343 if (strlen(libname) == 0) {
344 *valid_library_p = FALSE;
345 strfree(libname);
346 } else
347 *valid_library_p = TRUE;
348 }
349 *libname_p = libname;
350 }
351 }
352 }
353 }
354
355
356
357 static void
parse_filepath(const char * const filepath,const char ** const directory_p,const char ** const filename_p,bool * const error_p)358 parse_filepath(const char * const filepath,
359 const char ** const directory_p,
360 const char ** const filename_p,
361 bool * const error_p) {
362 /*----------------------------------------------------------------------------
363 Extract the directory and filename components of the filepath
364 'filepath' and return them in newly malloc'ed storage pointed to by
365 '*directory_p' and '*filename_p'.
366
367 If there is no directory component, return a null string for it.
368 -----------------------------------------------------------------------------*/
369 char *directory;
370 char *lastslash; /* Pointer to last slash in 'filepath', or null if none */
371
372 lastslash = strrchr(filepath, '/');
373
374 if (lastslash == NULL) {
375 /* There's no directory component; the filename starts at the
376 beginning of the filepath
377 */
378 *filename_p = strdup(filepath);
379 if (*filename_p == NULL)
380 *error_p = TRUE;
381 else {
382 directory = strdup("");
383 if (directory == NULL) {
384 *error_p = TRUE;
385 strfree(*filename_p);
386 } else
387 *error_p = FALSE;
388 }
389 } else {
390 /* Split the string at the slash we just found, into filename and
391 directory
392 */
393 *filename_p = strdup(lastslash+1);
394 if (*filename_p == NULL)
395 *error_p = TRUE;
396 else {
397 directory = strdup(filepath);
398 if (directory == NULL) {
399 *error_p = TRUE;
400 strfree(*filename_p);
401 } else {
402 *error_p = FALSE;
403 directory[lastslash - filepath] = '\0';
404 }
405 }
406 }
407 *directory_p = directory;
408 }
409
410
411
412 static void
doOptions(const char * const filepath,const char * const directory,const char * const libname,bool const runtime,bool const explicit,bool const staticlib,const char ** const optionsP)413 doOptions(const char * const filepath,
414 const char * const directory,
415 const char * const libname,
416 bool const runtime,
417 bool const explicit,
418 bool const staticlib,
419 const char ** const optionsP) {
420
421 char * options;
422 char * linkopt;
423
424 if (strlen(directory) == 0) {
425 linkopt = malloc(strlen(libname) + 10);
426 sprintf(linkopt, "-l%s", libname);
427 } else {
428 if (explicit)
429 linkopt = strdup(filepath);
430 else {
431 linkopt = malloc(strlen(directory) + strlen(libname) + 10);
432 sprintf(linkopt, "-L%s -l%s", directory, libname);
433 }
434 }
435 if (runtime && !staticlib && strlen(directory) > 0) {
436 options = malloc(strlen(linkopt) + strlen(directory) + 10);
437 sprintf(options, "%s -R%s", linkopt, directory);
438 } else
439 options = strdup(linkopt);
440
441 strfree(linkopt);
442
443 *optionsP = options;
444 }
445
446
447
448 static void
processOneLibrary(const char * const filepath,bool const runtime,bool const explicit,const char ** const optionsP,bool * const errorP)449 processOneLibrary(const char * const filepath,
450 bool const runtime,
451 bool const explicit,
452 const char ** const optionsP,
453 bool * const errorP) {
454 /*----------------------------------------------------------------------------
455 Process the library with filepath 'filepath'. Return the resulting
456 linker option string as a newly malloced null-terminated string at
457 *optionsP.
458 -----------------------------------------------------------------------------*/
459 const char * directory;
460 /* Directory component of 'filepath' */
461 const char * filename;
462 /* Filename component of 'filepath' */
463
464 parse_filepath(filepath, &directory, &filename, errorP);
465 if (!*errorP) {
466 const char *libname;
467 /* Library name component of 'filename'. e.g. xyz in
468 libxyz.so
469 */
470 bool valid_library;
471 /* Our argument is a valid library filepath that can be
472 converted to -l/-L notation.
473 */
474 bool staticlib;
475 /* Our argument appears to name a static library. */
476
477 parse_filename(filename,
478 &libname, &valid_library, &staticlib, errorP);
479
480 if (!*errorP) {
481 if (valid_library) {
482 doOptions(filepath, directory, libname,
483 runtime, explicit, staticlib, optionsP);
484
485 strfree(libname);
486 } else
487 *optionsP = strdup(filepath);
488 }
489 strfree(directory);
490 strfree(filename);
491 }
492 }
493
494
495
496 int
main(int argc,char ** argv)497 main(int argc, char **argv) {
498
499 bool error;
500 bool runtime; /* -runtime option has been seen */
501 int retval;
502 unsigned int arg; /* Index into argv[] of argument we're processing */
503 char outputLine[1024];
504
505 strcpy(outputLine, ""); /* initial value */
506 runtime = FALSE; /* initial value */
507 error = FALSE; /* no error yet */
508
509 for (arg = 1; arg < argc && !error; arg++) {
510 if (strcmp(argv[arg], "-runtime") == 0)
511 runtime = TRUE;
512 else if (strcmp(argv[arg], "-quiet") == 0) {
513 /* Doesn't do anything today */
514 } else {
515 const char * options;
516 processOneLibrary(argv[arg], runtime, explicit,
517 &options, &error);
518 if (!error) {
519 if (strlen(outputLine) + strlen(options) + 1 + 1 >
520 sizeof(outputLine))
521 error = TRUE;
522 else {
523 strcat(outputLine, " ");
524 strcat(outputLine, options);
525 }
526 strfree(options);
527 }
528 }
529 }
530 if (error) {
531 fprintf(stderr, "serious libopt error prevented parsing library "
532 "names. Invalid input to libopt is NOT the problem.\n");
533 retval = 10;
534 } else {
535 fputs(outputLine, stdout);
536 retval = 0;
537 }
538 return retval;
539 }
540