| /* Loading dynamic objects for GNU Make. | |
| Copyright (C) 2012-2025 Free Software Foundation, Inc. | |
| This file is part of GNU Make. | |
| GNU Make is free software; you can redistribute it and/or modify it under the | |
| terms of the GNU General Public License as published by the Free Software | |
| Foundation; either version 3 of the License, or (at your option) any later | |
| version. | |
| GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY | |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License along with | |
| this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include "makeint.h" | |
| #if MAKE_LOAD | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <stdlib.h> | |
| #include <dlfcn.h> | |
| #include <errno.h> | |
| #include "debug.h" | |
| #include "filedef.h" | |
| #include "variable.h" | |
| /* Tru64 V4.0 does not have this flag */ | |
| #ifndef RTLD_GLOBAL | |
| # define RTLD_GLOBAL 0 | |
| #endif | |
| #define GMK_SETUP "_gmk_setup" | |
| #define GMK_UNLOAD "_gmk_unload" | |
| typedef int (*setup_func_t)(unsigned int abi, const floc *flocp); | |
| typedef void (*unload_func_t)(void); | |
| struct load_list | |
| { | |
| struct load_list *next; | |
| const char *name; | |
| void *dlp; | |
| unload_func_t unload; | |
| }; | |
| static struct load_list *loaded_syms = NULL; | |
| static setup_func_t | |
| load_object (const floc *flocp, int noerror, const char *ldname, | |
| const char *setupnm) | |
| { | |
| static void *global_dl = NULL; | |
| char *buf; | |
| const char *fp; | |
| char *endp; | |
| void *dlp; | |
| struct load_list *new; | |
| setup_func_t symp; | |
| if (! global_dl) | |
| { | |
| global_dl = dlopen (NULL, RTLD_NOW|RTLD_GLOBAL); | |
| if (! global_dl) | |
| { | |
| const char *err = dlerror (); | |
| OS (fatal, flocp, _("failed to open global symbol table: %s"), err); | |
| } | |
| } | |
| /* Find the prefix of the ldname. */ | |
| fp = strrchr (ldname, '/'); | |
| #ifdef HAVE_DOS_PATHS | |
| if (fp) | |
| { | |
| const char *fp2 = strchr (fp, '\\'); | |
| if (fp2 > fp) | |
| fp = fp2; | |
| } | |
| else | |
| fp = strrchr (ldname, '\\'); | |
| /* The (improbable) case of d:foo. */ | |
| if (fp && *fp && fp[1] == ':') | |
| fp++; | |
| #endif | |
| if (!fp) | |
| fp = ldname; | |
| else | |
| ++fp; | |
| endp = buf = alloca (strlen (fp) + CSTRLEN (GMK_UNLOAD) + 1); | |
| while (isalnum ((unsigned char) *fp) || *fp == '_') | |
| *(endp++) = *(fp++); | |
| /* If we didn't find a symbol name yet, construct it from the prefix. */ | |
| if (! setupnm) | |
| { | |
| memcpy (endp, GMK_SETUP, CSTRLEN (GMK_SETUP) + 1); | |
| setupnm = buf; | |
| } | |
| DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), setupnm, ldname)); | |
| symp = (setup_func_t) dlsym (global_dl, setupnm); | |
| if (symp) | |
| return symp; | |
| /* If the path has no "/", try the current directory first. */ | |
| dlp = NULL; | |
| if (! strchr (ldname, '/') | |
| #ifdef HAVE_DOS_PATHS | |
| && ! strchr (ldname, '\\') | |
| #endif | |
| ) | |
| dlp = dlopen (concat (2, "./", ldname), RTLD_LAZY|RTLD_GLOBAL); | |
| /* If we haven't opened it yet, try the default search path. */ | |
| if (! dlp) | |
| dlp = dlopen (ldname, RTLD_LAZY|RTLD_GLOBAL); | |
| /* Still no? Then fail. */ | |
| if (! dlp) | |
| { | |
| const char *err = dlerror (); | |
| if (noerror) | |
| DB (DB_BASIC, ("%s\n", err)); | |
| else | |
| OS (error, flocp, "%s", err); | |
| return NULL; | |
| } | |
| DB (DB_VERBOSE, (_("Loaded shared object %s\n"), ldname)); | |
| /* Assert that the GPL license symbol is defined. */ | |
| symp = (setup_func_t) dlsym (dlp, "plugin_is_GPL_compatible"); | |
| if (! symp) | |
| OS (fatal, flocp, | |
| _("loaded object %s is not declared to be GPL compatible"), ldname); | |
| symp = (setup_func_t) dlsym (dlp, setupnm); | |
| if (! symp) | |
| { | |
| const char *err = dlerror (); | |
| OSSS (fatal, flocp, _("failed to load symbol %s from %s: %s"), | |
| setupnm, ldname, err); | |
| } | |
| new = xcalloc (sizeof (struct load_list)); | |
| new->next = loaded_syms; | |
| loaded_syms = new; | |
| new->name = ldname; | |
| new->dlp = dlp; | |
| /* Compute the name of the unload function and look it up. */ | |
| memcpy (endp, GMK_UNLOAD, CSTRLEN (GMK_UNLOAD) + 1); | |
| new->unload = (unload_func_t) dlsym (dlp, buf); | |
| if (new->unload) | |
| DB (DB_VERBOSE, (_("Detected symbol %s in %s\n"), buf, ldname)); | |
| return symp; | |
| } | |
| int | |
| load_file (const floc *flocp, struct file *file, int noerror) | |
| { | |
| const char *ldname = file->name; | |
| char *buf; | |
| char *setupnm = NULL; | |
| const char *fp; | |
| int r; | |
| setup_func_t symp; | |
| /* Break the input into an object file name and a symbol name. If no symbol | |
| name was provided, compute one from the object file name. */ | |
| fp = strchr (ldname, '('); | |
| if (fp) | |
| { | |
| const char *ep; | |
| /* There's an open paren, so see if there's a close paren: if so use | |
| that as the symbol name. We can't have whitespace: it would have | |
| been chopped up before this function is called. */ | |
| ep = strchr (fp+1, ')'); | |
| if (ep && ep[1] == '\0') | |
| { | |
| size_t l = fp - ldname; | |
| ++fp; | |
| if (fp == ep) | |
| OS (fatal, flocp, _("empty symbol name for load: %s"), ldname); | |
| /* Make a copy of the ldname part. */ | |
| buf = alloca (strlen (ldname) + 1); | |
| memcpy (buf, ldname, l); | |
| buf[l] = '\0'; | |
| ldname = buf; | |
| /* Make a copy of the symbol name part. */ | |
| setupnm = buf + l + 1; | |
| memcpy (setupnm, fp, ep - fp); | |
| setupnm[ep - fp] = '\0'; | |
| } | |
| } | |
| /* Make sure this name is in the string cache. */ | |
| ldname = file->name = strcache_add (ldname); | |
| /* If this object has been loaded, we're done: return -1 to ensure make does | |
| not rebuild again. If a rebuild is allowed it was set up when this | |
| object was initially loaded. */ | |
| file = lookup_file (ldname); | |
| if (file && file->loaded) | |
| return -1; | |
| /* Load it! */ | |
| symp = load_object (flocp, noerror, ldname, setupnm); | |
| if (! symp) | |
| return 0; | |
| /* Invoke the setup function. */ | |
| { | |
| unsigned int abi = GMK_ABI_VERSION; | |
| r = (*symp) (abi, flocp); | |
| } | |
| /* If the load didn't fail, add the file to the .LOADED variable. */ | |
| if (r) | |
| do_variable_definition(flocp, ".LOADED", ldname, o_file, f_append_value, 0, | |
| s_global); | |
| return r; | |
| } | |
| int | |
| unload_file (const char *name) | |
| { | |
| struct load_list **dp = &loaded_syms; | |
| /* Unload and remove the entry for this file. */ | |
| while (*dp != NULL) | |
| { | |
| struct load_list *d = *dp; | |
| if (streq (d->name, name)) | |
| { | |
| int rc; | |
| DB (DB_VERBOSE, (_("Unloading shared object %s\n"), name)); | |
| if (d->unload) | |
| (*d->unload) (); | |
| rc = dlclose (d->dlp); | |
| if (rc) | |
| perror_with_name ("dlclose: ", d->name); | |
| *dp = d->next; | |
| free (d); | |
| return rc; | |
| } | |
| dp = &d->next; | |
| } | |
| return 0; | |
| } | |
| void | |
| unload_all () | |
| { | |
| while (loaded_syms) | |
| { | |
| struct load_list *d = loaded_syms; | |
| loaded_syms = loaded_syms->next; | |
| if (d->unload) | |
| (*d->unload) (); | |
| if (dlclose (d->dlp)) | |
| perror_with_name ("dlclose: ", d->name); | |
| free (d); | |
| } | |
| } | |
| #else | |
| int | |
| load_file (const floc *flocp, struct file *file UNUSED, int noerror) | |
| { | |
| if (! noerror) | |
| O (fatal, flocp, | |
| _("'load' is not supported on this platform")); | |
| return 0; | |
| } | |
| int | |
| unload_file (const char *name UNUSED) | |
| { | |
| O (fatal, NILF, "INTERNAL: cannot unload when load is not supported"); | |
| } | |
| void | |
| unload_all () | |
| { | |
| } | |
| #endif /* MAKE_LOAD */ |