s_loader.c 16.8 KB
Newer Older
Miller Puckette's avatar
Miller Puckette committed
1 2 3 4
/* Copyright (c) 1997-1999 Miller Puckette.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

5
#if defined(HAVE_LIBDL) || defined(__FreeBSD__)
Miller Puckette's avatar
Miller Puckette committed
6 7
#include <dlfcn.h>
#endif
8
#ifdef HAVE_UNISTD_H
Miller Puckette's avatar
Miller Puckette committed
9 10
#include <stdlib.h>
#include <unistd.h>
Miller Puckette's avatar
Miller Puckette committed
11
#include <sys/types.h>
Miller Puckette's avatar
Miller Puckette committed
12
#endif
13
#ifdef _WIN32
Miller Puckette's avatar
Miller Puckette committed
14 15 16 17
#include <io.h>
#include <windows.h>
#endif
#ifdef __APPLE__
18
#include <mach-o/dyld.h>
Miller Puckette's avatar
Miller Puckette committed
19 20 21 22 23
#endif
#include <string.h>
#include "m_pd.h"
#include "s_stuff.h"
#include <stdio.h>
24
#include <sys/stat.h>
Miller Puckette's avatar
Miller Puckette committed
25 26
#ifdef _MSC_VER  /* This is only for Microsoft's compiler, not cygwin, e.g. */
#define snprintf sprintf_s
27
#define stat _stat
Miller Puckette's avatar
Miller Puckette committed
28
#endif
Miller Puckette's avatar
Miller Puckette committed
29 30 31 32 33 34 35 36 37 38 39

typedef void (*t_xxx)(void);

/* naming convention for externs.  The names are kept distinct for those
who wich to make "fat" externs compiled for many platforms.  Less specific
fallbacks are provided, primarily for back-compatibility; these suffice if
you are building a package which will run with a single set of compiled
objects.  The specific name is the letter b, l, d, or m for  BSD, linux,
darwin, or microsoft, followed by a more specific string, either "fat" for
a fat binary or an indication of the instruction set. */

40
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__GNU__) || defined(__FreeBSD__)
41 42 43 44 45 46 47 48 49 50 51 52
static char sys_dllextent2[] = ".pd_linux";
# ifdef __x86_64__
static char sys_dllextent[] = ".l_ia64"; // this should be .l_x86_64 or .l_amd64
# elif defined(__i386__) || defined(_M_IX86)
static char sys_dllextent[] = ".l_i386";
# elif defined(__arm__)
static char sys_dllextent[] = ".l_arm";
# else
static char sys_dllextent[] = ".so";
# endif
#elif defined(__APPLE__)
# ifndef MACOSX3
Miller Puckette's avatar
Miller Puckette committed
53
static char sys_dllextent[] = ".d_fat", sys_dllextent2[] = ".pd_darwin";
54
# else
Miller Puckette's avatar
Miller Puckette committed
55
static char sys_dllextent[] = ".d_ppc", sys_dllextent2[] = ".pd_darwin";
56 57
# endif
#elif defined(_WIN32) || defined(__CYGWIN__)
Miller Puckette's avatar
Miller Puckette committed
58
static char sys_dllextent[] = ".m_i386", sys_dllextent2[] = ".dll";
59 60
#else
static char sys_dllextent[] = ".so", sys_dllextent2[] = ".so";
Miller Puckette's avatar
Miller Puckette committed
61 62 63 64 65 66 67 68 69 70
#endif

    /* maintain list of loaded modules to avoid repeating loads */
typedef struct _loadedlist
{
    struct _loadedlist *ll_next;
    t_symbol *ll_name;
} t_loadlist;

static t_loadlist *sys_loaded;
71
int sys_onloadlist(const char *classname) /* return true if already loaded */
Miller Puckette's avatar
Miller Puckette committed
72 73 74 75 76 77 78 79 80
{
    t_symbol *s = gensym(classname);
    t_loadlist *ll;
    for (ll = sys_loaded; ll; ll = ll->ll_next)
        if (ll->ll_name == s)
            return (1);
    return (0);
}

81 82
     /* add to list of loaded modules */
void sys_putonloadlist(const char *classname)
Miller Puckette's avatar
Miller Puckette committed
83 84 85 86 87 88 89 90
{
    t_loadlist *ll = (t_loadlist *)getbytes(sizeof(*ll));
    ll->ll_name = gensym(classname);
    ll->ll_next = sys_loaded;
    sys_loaded = ll;
    /* post("put on list %s", classname); */
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
static char *get_last_file_separator(const char *objectname)
{
    char *c = strrchr(objectname, '/');
    if (c)
    {
        char *ret = c;
            /* if we're the last character before the null terminator,
               OR if the end of the string is "/~", let's interpret the
               slash as part of the class name.
            */
        if (c[1] == '\0' || (c[1] == '~' && c[2] == '\0'))
        {
            *c = '\0';
            ret = strrchr(objectname, '/');
            *c = '/';
        }
        return ret;
    }
    return NULL;
}

Miller Puckette's avatar
Miller Puckette committed
112 113
void class_set_extern_dir(t_symbol *s);

114 115 116 117
static int sys_do_load_abs(t_canvas *canvas, const char *objectname,
    const char *path);
static int sys_do_load_lib(t_canvas *canvas, const char *objectname,
    const char *path)
Miller Puckette's avatar
Miller Puckette committed
118
{
119
    char symname[MAXPDSTRING], filename[MAXPDSTRING], dirbuf[MAXPDSTRING],
120
        *nameptr;
121
    const char *classname, *cnameptr;
Miller Puckette's avatar
Miller Puckette committed
122 123 124
    void *dlobj;
    t_xxx makeout = NULL;
    int i, hexmunge = 0, fd;
125
#ifdef _WIN32
Miller Puckette's avatar
Miller Puckette committed
126 127
    HINSTANCE ntdll;
#endif
128 129 130 131
        /* NULL-path is only used as a last resort,
           but we have already tried all paths */
    if(!path)return (0);

132
    if ((classname = get_last_file_separator(objectname)))
Miller Puckette's avatar
Miller Puckette committed
133 134
        classname++;
    else classname = objectname;
135 136
    for (i = 0, cnameptr = classname; i < MAXPDSTRING-7 && *cnameptr;
        cnameptr++)
Miller Puckette's avatar
Miller Puckette committed
137
    {
138
        char c = *cnameptr;
Miller Puckette's avatar
Miller Puckette committed
139 140 141 142 143 144 145
        if ((c>='0' && c<='9') || (c>='A' && c<='Z')||
           (c>='a' && c<='z' )|| c == '_')
        {
            symname[i] = c;
            i++;
        }
            /* trailing tilde becomes "_tilde" */
146
        else if (c == '~' && cnameptr[1] == 0)
Miller Puckette's avatar
Miller Puckette committed
147 148 149 150 151 152 153 154 155 156 157 158 159 160
        {
            strcpy(symname+i, "_tilde");
            i += strlen(symname+i);
        }
        else /* anything you can't put in a C symbol is sprintf'ed in hex */
        {
            sprintf(symname+i, "0x%02x", c);
            i += strlen(symname+i);
            hexmunge = 1;
        }
    }
    symname[i] = 0;
    if (hexmunge)
    {
Miller Puckette's avatar
Miller Puckette committed
161
        memmove(symname+6, symname, strlen(symname)+1);
162
        memcpy(symname, "setup_", 6);
Miller Puckette's avatar
Miller Puckette committed
163 164
    }
    else strcat(symname, "_setup");
165

Miller Puckette's avatar
Miller Puckette committed
166
#if 0
167
    fprintf(stderr, "lib: %s\n", classname);
Miller Puckette's avatar
Miller Puckette committed
168 169
#endif
        /* try looking in the path for (objectname).(sys_dllextent) ... */
170 171
    if ((fd = sys_trytoopenone(path, objectname, sys_dllextent,
        dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
Miller Puckette's avatar
Miller Puckette committed
172 173
            goto gotone;
        /* same, with the more generic sys_dllextent2 */
174 175
    if ((fd = sys_trytoopenone(path, objectname, sys_dllextent2,
        dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
Miller Puckette's avatar
Miller Puckette committed
176 177
            goto gotone;
        /* next try (objectname)/(classname).(sys_dllextent) ... */
178
    strncpy(filename, objectname, MAXPDSTRING);
Miller Puckette's avatar
Miller Puckette committed
179 180
    filename[MAXPDSTRING-2] = 0;
    strcat(filename, "/");
181 182 183 184
    strncat(filename, classname, MAXPDSTRING-strlen(filename));
    filename[MAXPDSTRING-1] = 0;
    if ((fd = sys_trytoopenone(path, filename, sys_dllextent,
        dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
Miller Puckette's avatar
Miller Puckette committed
185
            goto gotone;
186 187
    if ((fd = sys_trytoopenone(path, filename, sys_dllextent2,
        dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
Miller Puckette's avatar
Miller Puckette committed
188
            goto gotone;
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        /* for hexmunged binary external files, give it a shot
           with the hexmunged name. This is a really ugly system
           but we need it for all the legacy libraries that use
           funky characters. (The only alternative is putting libdir
           classes all in a single file and preloading, which is
           even worse.
           The hexmunger never worked for abstractions without recompiling,
           so we don't and won't support hexmunged abstractions.
        */
    if (hexmunge)
    {
        if ((fd = sys_trytoopenone(path, symname+6, sys_dllextent,
            dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
                goto gotone;
            /* same, with the more generic sys_dllextent2 */
        if ((fd = sys_trytoopenone(path, symname+6, sys_dllextent2,
            dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
                goto gotone;
    }
208 209 210 211 212 213 214 215 216 217 218 219
#ifdef ANDROID
    /* Android libs have a 'lib' prefix, '.so' suffix and don't allow ~ */
    char libname[MAXPDSTRING] = "lib";
    strncat(libname, objectname, MAXPDSTRING - 4);
    int len = strlen(libname);
    if (libname[len-1] == '~' && len < MAXPDSTRING - 6) {
        strcpy(libname+len-1, "_tilde");
    }
    if ((fd = sys_trytoopenone(path, libname, ".so",
        dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
            goto gotone;
#endif
Miller Puckette's avatar
Miller Puckette committed
220 221
    return (0);
gotone:
222
    sys_close(fd);
Miller Puckette's avatar
Miller Puckette committed
223 224 225
    class_set_extern_dir(gensym(dirbuf));

        /* rebuild the absolute pathname */
226 227
    strncpy(filename, dirbuf, MAXPDSTRING);
    filename[MAXPDSTRING-2] = 0;
Miller Puckette's avatar
Miller Puckette committed
228
    strcat(filename, "/");
229 230
    strncat(filename, nameptr, MAXPDSTRING-strlen(filename));
    filename[MAXPDSTRING-1] = 0;
Miller Puckette's avatar
Miller Puckette committed
231

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
#ifdef _WIN32
    {
        char dirname[MAXPDSTRING], *s, *basename;
        sys_bashfilename(filename, filename);
        /* set the dirname as DllDirectory, meaning in the path for
           loading other DLLs so that dependent libraries can be included
           in the same folder as the external. SetDllDirectory() needs a
           minimum supported version of Windows XP SP1 for
           SetDllDirectory, so WINVER must be 0x0502 */
        strncpy(dirname, filename, MAXPDSTRING);
        s = strrchr(dirname, '\\');
        basename = s;
        if (s && *s)
          *s = '\0';
        if (!SetDllDirectory(dirname))
           error("Could not set '%s' as DllDirectory(), '%s' might not load.",
                 dirname, basename);
        /* now load the DLL for the external */
        ntdll = LoadLibrary(filename);
        if (!ntdll)
        {
            verbose(1, "%s: couldn't load", filename);
            class_set_extern_dir(&s_);
            return (0);
        }
        makeout = (t_xxx)GetProcAddress(ntdll, symname);
        if (!makeout)
             makeout = (t_xxx)GetProcAddress(ntdll, "setup");
        SetDllDirectory(NULL); /* reset DLL dir to nothing */
    }
#elif defined(HAVE_LIBDL) || defined(__FreeBSD__)
Miller Puckette's avatar
Miller Puckette committed
263 264 265
    dlobj = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
    if (!dlobj)
    {
266
        verbose(1, "%s: %s", filename, dlerror());
Miller Puckette's avatar
Miller Puckette committed
267 268 269 270
        class_set_extern_dir(&s_);
        return (0);
    }
    makeout = (t_xxx)dlsym(dlobj,  symname);
271 272 273 274 275
    if(!makeout)
        makeout = (t_xxx)dlsym(dlobj,  "setup");
#else
#warning "No dynamic loading mechanism specified, \
    libdl or WIN32 required for loading externals!"
Miller Puckette's avatar
Miller Puckette committed
276 277 278 279
#endif

    if (!makeout)
    {
280
        verbose(1, "load_object: Symbol \"%s\" not found", symname);
Miller Puckette's avatar
Miller Puckette committed
281 282 283 284 285 286 287 288
        class_set_extern_dir(&s_);
        return 0;
    }
    (*makeout)();
    class_set_extern_dir(&s_);
    return (1);
}

289

Miller Puckette's avatar
Miller Puckette committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303
/* linked list of loaders */
typedef struct loader_queue {
    loader_t loader;
    struct loader_queue *next;
} loader_queue_t;

static loader_queue_t loaders = {sys_do_load_lib, NULL};

/* register class loader function */
void sys_register_loader(loader_t loader)
{
    loader_queue_t *q = &loaders;
    while (1)
    {
304 305 306
        if (q->loader == loader)    /* already loaded - nothing to do */
            return;
        else if (q->next)
Miller Puckette's avatar
Miller Puckette committed
307 308 309 310 311 312 313 314
            q = q->next;
        else
        {
            q->next = (loader_queue_t *)getbytes(sizeof(loader_queue_t));
            q->next->loader = loader;
            q->next->next = NULL;
            break;
        }
315
    }
Miller Puckette's avatar
Miller Puckette committed
316 317
}

318 319 320 321 322 323 324 325 326 327 328
#include "g_canvas.h"

/* the data passed to the iter-function */
struct _loadlib_data
{
    t_canvas *canvas;
    const char *classname;
    int ok;
};

int sys_loadlib_iter(const char *path, struct _loadlib_data *data)
Miller Puckette's avatar
Miller Puckette committed
329 330 331 332
{
    int ok = 0;
    loader_queue_t *q;
    for(q = &loaders; q; q = q->next)
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
        if ((ok = q->loader(data->canvas, data->classname, path)))
            break;
    /* if all loaders failed, try to load as abstraction */
    if (!ok)
        ok = sys_do_load_abs(data->canvas, data->classname, path);
    data->ok = ok;
    return (ok == 0);
}

int sys_load_lib(t_canvas *canvas, const char *classname)
{
    int dspstate = canvas_suspend_dsp();
    struct _loadlib_data data;
    data.canvas = canvas;
    data.ok = 0;

    if (sys_onloadlist(classname))
    {
        verbose(1, "%s: already loaded", classname);
        return (1);
    }
        /* if classname is absolute, try this first */
    if (sys_isabsolutepath(classname))
    {
            /* this is just copied from sys_open_absolute()
               LATER avoid code duplication */
        char dirbuf[MAXPDSTRING], *z = strrchr(classname, '/');
        int dirlen;
        if (!z)
            return (0);
        dirlen = z - classname;
        if (dirlen > MAXPDSTRING-1)
            dirlen = MAXPDSTRING-1;
        strncpy(dirbuf, classname, dirlen);
        dirbuf[dirlen] = 0;
        data.classname=classname+(dirlen+1);
        sys_loadlib_iter(dirbuf, &data);
    }
    data.classname = classname;
    if(!data.ok)
        canvas_path_iterate(canvas, (t_canvas_path_iterator)sys_loadlib_iter,
            &data);

    /* if loaders failed so far, we try a last time without a PATH
     * let the loaders search wherever they want */
    if (!data.ok)
        sys_loadlib_iter(0, &data);

    if(data.ok)
      sys_putonloadlist(classname);


Miller Puckette's avatar
Miller Puckette committed
385
    canvas_resume_dsp(dspstate);
386
    return data.ok;
Miller Puckette's avatar
Miller Puckette committed
387 388
}

389 390 391 392 393 394
int sys_run_scheduler(const char *externalschedlibname,
    const char *sys_extraflagsstring)
{
    typedef int (*t_externalschedlibmain)(const char *);
    t_externalschedlibmain externalmainfunc;
    char filename[MAXPDSTRING];
395
    struct stat statbuf;
396
    snprintf(filename, sizeof(filename), "%s%s", externalschedlibname,
397 398
        sys_dllextent);
    sys_bashfilename(filename, filename);
399 400 401 402 403 404 405 406
        /* if first-choice file extent can't 'stat', go for second */
    if (stat(filename, &statbuf) < 0)
    {
        snprintf(filename, sizeof(filename), "%s%s", externalschedlibname,
            sys_dllextent2);
        sys_bashfilename(filename, filename);
    }
#ifdef _WIN32
407 408 409 410
    {
        HINSTANCE ntdll = LoadLibrary(filename);
        if (!ntdll)
        {
411 412
            fprintf(stderr, "%s: couldn't load external scheduler\n", filename);
            error("%s: couldn't load external scheduler", filename);
Miller Puckette's avatar
Miller Puckette committed
413
            return (1);
414 415
        }
        externalmainfunc =
416 417 418 419
            (t_externalschedlibmain)GetProcAddress(ntdll, "pd_extern_sched");
        if (!externalmainfunc)
            externalmainfunc =
                (t_externalschedlibmain)GetProcAddress(ntdll, "main");
420
    }
421
#elif defined HAVE_LIBDL
422
    {
Miller Puckette's avatar
Miller Puckette committed
423 424
        void *dlobj;
        dlobj = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
425 426
        if (!dlobj)
        {
427
            error("%s: %s", filename, dlerror());
Miller Puckette's avatar
Miller Puckette committed
428 429
            fprintf(stderr, "dlopen failed for %s: %s\n", filename, dlerror());
            return (1);
430 431 432 433
        }
        externalmainfunc = (t_externalschedlibmain)dlsym(dlobj,
            "pd_extern_sched");
    }
434 435
#else
    return (0);
436
#endif
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    if (externalmainfunc)
        return((*externalmainfunc)(sys_extraflagsstring));
    else
    {
        fprintf(stderr, "%s: couldn't find pd_extern_sched() or main()\n",
            filename);
        return (0);
    }
}

/* abstraction loading */
void canvas_popabstraction(t_canvas *x);
int pd_setloadingabstraction(t_symbol *sym);
extern t_pd *newest;

static t_pd *do_create_abstraction(t_symbol*s, int argc, t_atom *argv)
{
    /*
     * TODO: check if the there is a binbuf cached for <canvas::symbol>
        and use that instead.  We'll have to invalidate the cache once we
        are done (either with a clock_delay(0) or something else)
     */
    if (!pd_setloadingabstraction(s))
    {
        const char *objectname = s->s_name;
        char dirbuf[MAXPDSTRING], classslashclass[MAXPDSTRING], *nameptr;
        t_glist *glist = (t_glist *)canvas_getcurrent();
464
        t_canvas *canvas = (t_canvas*)canvas_getrootfor(glist);
465 466 467 468 469 470 471 472 473 474 475
        int fd = -1;

        t_pd *was = s__X.s_thing;
        snprintf(classslashclass, MAXPDSTRING, "%s/%s", objectname, objectname);
        if ((fd = canvas_open(canvas, objectname, ".pd",
                  dirbuf, &nameptr, MAXPDSTRING, 0)) >= 0 ||
            (fd = canvas_open(canvas, objectname, ".pat",
                  dirbuf, &nameptr, MAXPDSTRING, 0)) >= 0 ||
            (fd = canvas_open(canvas, classslashclass, ".pd",
                  dirbuf, &nameptr, MAXPDSTRING, 0)) >= 0)
        {
476
            sys_close(fd);
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
            canvas_setargs(argc, argv);

            binbuf_evalfile(gensym(nameptr), gensym(dirbuf));
            if (s__X.s_thing && was != s__X.s_thing)
                canvas_popabstraction((t_canvas *)(s__X.s_thing));
            else s__X.s_thing = was;
            canvas_setargs(0, 0);
            return (newest);
        }
            /* otherwise we couldn't do it; just return 0 */
    }
    else error("%s: can't load abstraction within itself\n", s->s_name);
    newest = 0;
    return (0);
}

/* search for abstraction; register a creator if found */
static int sys_do_load_abs(t_canvas *canvas, const char *objectname,
    const char *path)
{
    int fd;
    static t_gobj*abstraction_classes = 0;
    char dirbuf[MAXPDSTRING], classslashclass[MAXPDSTRING], *nameptr;
        /* NULL-path is only used as a last resort,
           but we have already tried all paths */
    if (!path) return (0);

    snprintf(classslashclass, MAXPDSTRING, "%s/%s", objectname, objectname);
    if ((fd = sys_trytoopenone(path, objectname, ".pd",
              dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0 ||
        (fd = sys_trytoopenone(path, objectname, ".pat",
              dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0 ||
        (fd = sys_trytoopenone(path, classslashclass, ".pd",
              dirbuf, &nameptr, MAXPDSTRING, 1)) >= 0)
    {
512 513
        t_class *c = 0;
        sys_close(fd);
514 515
            /* found an abstraction, now register it as a new pseudo-class */
        class_set_extern_dir(gensym(dirbuf));
516
        if ((c=class_new(gensym(objectname),
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
                        (t_newmethod)do_create_abstraction, 0,
                        0, 0, A_GIMME, 0)))
        {
                /* store away the newly created class, maybe we will need it one day */
            t_gobj*absclass=0;
            absclass=t_getbytes(sizeof(*absclass));
            absclass->g_pd=c;
            absclass->g_next=abstraction_classes;
            abstraction_classes=absclass;
        }
        class_set_extern_dir(&s_);

        return (1);
    }
    return (0);
532
}