diff --git a/sys/boot/common/boot.c b/sys/boot/common/boot.c index a0cd6d666df4..571c6c9e7112 100644 --- a/sys/boot/common/boot.c +++ b/sys/boot/common/boot.c @@ -65,7 +65,7 @@ command_boot(int argc, char *argv[]) } /* find/load the kernel module */ - if (mod_load(argv[1], argc - 2, argv + 2) != 0) + if (mod_loadkld(argv[1], argc - 2, argv + 2) != 0) return(CMD_ERROR); /* we have consumed all arguments */ argc = 1; @@ -343,11 +343,10 @@ loadakernel(int try, int argc, char* argv[]) char *cp; for (try = 0; (cp = getbootfile(try)) != NULL; try++) - if (mod_load(cp, argc - 1, argv + 1) != 0) + if (mod_loadkld(cp, argc - 1, argv + 1) != 0) printf("can't load '%s'\n", cp); else return 1; - return 0; } diff --git a/sys/boot/common/bootstrap.h b/sys/boot/common/bootstrap.h index 11085eb7c504..1248fb0824c9 100644 --- a/sys/boot/common/bootstrap.h +++ b/sys/boot/common/bootstrap.h @@ -173,10 +173,12 @@ struct file_metadata }; struct preloaded_file; +struct mod_depend; struct kernel_module { char *m_name; /* module name */ + int m_version; /* module version */ /* char *m_args;*/ /* arguments for the module */ struct preloaded_file *m_fp; struct kernel_module *m_next; @@ -215,15 +217,16 @@ struct file_format extern struct file_format *file_formats[]; /* supplied by consumer */ extern struct preloaded_file *preloaded_files; -int mod_load(char *name, int argc, char *argv[]); +int mod_load(char *name, struct mod_depend *verinfo, int argc, char *argv[]); int mod_loadobj(char *type, char *name); +int mod_loadkld(const char *name, int argc, char *argv[]); struct preloaded_file *file_alloc(void); struct preloaded_file *file_findfile(char *name, char *type); struct file_metadata *file_findmetadata(struct preloaded_file *fp, int type); void file_discard(struct preloaded_file *fp); void file_addmetadata(struct preloaded_file *fp, int type, size_t size, void *p); -int file_addmodule(struct preloaded_file *fp, char *modname, +int file_addmodule(struct preloaded_file *fp, char *modname, int version, struct kernel_module **newmp); diff --git a/sys/boot/common/load_aout.c b/sys/boot/common/load_aout.c index 88678154b9e0..e2a964368cdd 100644 --- a/sys/boot/common/load_aout.c +++ b/sys/boot/common/load_aout.c @@ -64,7 +64,6 @@ aout_loadfile(char *filename, vm_offset_t dest, struct preloaded_file **result) vm_offset_t addr; int err, kernel; u_int pad; - char *s; fp = NULL; @@ -134,11 +133,7 @@ aout_loadfile(char *filename, vm_offset_t dest, struct preloaded_file **result) fp = file_alloc(); if (kernel) setenv("kernelname", filename, 1); - s = strrchr(filename, '/'); - if (s) - fp->f_name = strdup(s + 1); - else - fp->f_name = strdup(filename); + fp->f_name = strdup(filename); fp->f_type = strdup(kernel ? aout_kerneltype : aout_moduletype); /* Page-align the load address */ diff --git a/sys/boot/common/load_elf.c b/sys/boot/common/load_elf.c index 0eddbb4b94ed..22f7ff784fef 100644 --- a/sys/boot/common/load_elf.c +++ b/sys/boot/common/load_elf.c @@ -83,7 +83,6 @@ elf_loadfile(char *filename, vm_offset_t dest, struct preloaded_file **result) Elf_Ehdr *ehdr; int err; u_int pad; - char *s; ssize_t bytes_read; fp = NULL; @@ -183,11 +182,7 @@ elf_loadfile(char *filename, vm_offset_t dest, struct preloaded_file **result) } if (ef.kernel) setenv("kernelname", filename, 1); - s = strrchr(filename, '/'); - if (s) - fp->f_name = strdup(s + 1); - else - fp->f_name = strdup(filename); + fp->f_name = strdup(filename); fp->f_type = strdup(ef.kernel ? elf_kerneltype : elf_moduletype); #ifdef ELF_VERBOSE @@ -242,7 +237,6 @@ elf_loadimage(struct preloaded_file *fp, elf_file_t ef, vm_offset_t off) vm_offset_t ssym, esym; Elf_Dyn *dp; int ndp; - char *s; int symstrindex; int symtabindex; long size; @@ -515,19 +509,6 @@ elf_loadimage(struct preloaded_file *fp, elf_file_t ef, vm_offset_t off) if (ef->kernel) /* kernel must not depend on anything */ goto out; - for (i = 0; i < ndp; i++) { - if (dp[i].d_tag == NULL) - break; - if (dp[i].d_tag != DT_NEEDED) - continue; - j = dp[i].d_un.d_ptr; - if (j < 1 || j > ef->strsz - 2) - continue; - s = strdupout((vm_offset_t)&ef->strtab[j]); - file_addmetadata(fp, MODINFOMD_DEPLIST, strlen(s) + 1, s); - free(s); - } - out: if (dp) free(dp); @@ -537,6 +518,7 @@ elf_loadimage(struct preloaded_file *fp, elf_file_t ef, vm_offset_t off) } static char invalid_name[] = "bad"; + char * fake_modname(const char *name) { @@ -570,9 +552,11 @@ int elf_parse_modmetadata(struct preloaded_file *fp, elf_file_t ef) { struct mod_metadata md; + struct mod_depend *mdepend; + struct mod_version mver; Elf_Sym sym; char *s, *v, **p, **p_stop; - int modcnt; + int modcnt, minfolen; if (elf_lookup_symbol(fp, ef, "__start_set_modmetadata_set", &sym) != 0) return ENOENT; @@ -590,12 +574,20 @@ elf_parse_modmetadata(struct preloaded_file *fp, elf_file_t ef) if (ef->kernel) /* kernel must not depend on anything */ break; s = strdupout((vm_offset_t)(md.md_cval + ef->off)); - file_addmetadata(fp, MODINFOMD_DEPLIST, strlen(s) + 1, s); + minfolen = sizeof(*mdepend) + strlen(s) + 1; + mdepend = malloc(minfolen); + if (mdepend == NULL) + return ENOMEM; + COPYOUT((vm_offset_t)(md.md_data + ef->off), mdepend, sizeof(*mdepend)); + strcpy((char*)(mdepend + 1), s); free(s); + file_addmetadata(fp, MODINFOMD_DEPLIST, minfolen, mdepend); + free(mdepend); break; case MDT_VERSION: s = strdupout((vm_offset_t)(md.md_cval + ef->off)); - file_addmodule(fp, s, NULL); + COPYOUT((vm_offset_t)(md.md_data + ef->off), &mver, sizeof(mver)); + file_addmodule(fp, s, mver.mv_version, NULL); free(s); modcnt++; break; @@ -603,7 +595,7 @@ elf_parse_modmetadata(struct preloaded_file *fp, elf_file_t ef) } if (modcnt == 0) { s = fake_modname(fp->f_name); - file_addmodule(fp, s, NULL); + file_addmodule(fp, s, 1, NULL); free(s); } return 0; diff --git a/sys/boot/common/module.c b/sys/boot/common/module.c index 6fffd9db4419..f8e448e48b84 100644 --- a/sys/boot/common/module.c +++ b/sys/boot/common/module.c @@ -34,25 +34,50 @@ #include #include #include +#include +#include #include "bootstrap.h" +#define MDIR_REMOVED 0x0001 +#define MDIR_NOHINTS 0x0002 + +struct moduledir { + char *d_path; /* path of modules directory */ + u_char *d_hints; /* content of linker.hints file */ + int d_hintsz; /* size of hints data */ + int d_flags; + STAILQ_ENTRY(moduledir) d_link; +}; + static int file_load(char *filename, vm_offset_t dest, struct preloaded_file **result); static int file_loadraw(char *type, char *name); static int file_load_dependancies(struct preloaded_file *base_mod); -static char * file_search(char *name); -struct kernel_module * file_findmodule(struct preloaded_file *fp, char *modname); -static char *mod_searchmodule(char *name); +static char * file_search(const char *name, char **extlist); +static struct kernel_module * file_findmodule(struct preloaded_file *fp, char *modname, struct mod_depend *verinfo); +static int file_havepath(const char *name); +static char *mod_searchmodule(char *name, struct mod_depend *verinfo); static void file_insert_tail(struct preloaded_file *mp); struct file_metadata* metadata_next(struct file_metadata *base_mp, int type); +static void moduledir_readhints(struct moduledir *mdp); +static void moduledir_rebuild(void); /* load address should be tweaked by first module loaded (kernel) */ static vm_offset_t loadaddr = 0; static const char *default_searchpath ="/boot/kernel;/boot/modules;/modules"; +static STAILQ_HEAD(, moduledir) moduledir_list = STAILQ_HEAD_INITIALIZER(moduledir_list); + struct preloaded_file *preloaded_files = NULL; +static char *kld_ext_list[] = { + ".ko", + "", + NULL +}; + + /* * load an object, either a disk file or code module. * @@ -71,9 +96,9 @@ static int command_load(int argc, char *argv[]) { char *typestr; - int dofile, ch, error; + int dofile, dokld, ch, error; - dofile = 0; + dokld = dofile = 0; optind = 1; optreset = 1; typestr = NULL; @@ -81,8 +106,11 @@ command_load(int argc, char *argv[]) command_errmsg = "no filename specified"; return(CMD_ERROR); } - while ((ch = getopt(argc, argv, "t:")) != -1) { + while ((ch = getopt(argc, argv, "kt:")) != -1) { switch(ch) { + case 'k': + dokld = 1; + break; case 't': typestr = optarg; dofile = 1; @@ -106,11 +134,19 @@ command_load(int argc, char *argv[]) } return(file_loadraw(typestr, argv[1])); } - + /* + * Do we have explicit KLD load ? + */ + if (dokld || file_havepath(argv[1])) { + error = mod_loadkld(argv[1], argc - 2, argv + 2); + if (error == EEXIST) + sprintf(command_errbuf, "warning: KLD '%s' already loaded", argv[1]); + return (error == 0 ? CMD_OK : CMD_ERROR); + } /* * Looks like a request for a module. */ - error = mod_load(argv[1], argc - 2, argv + 2); + error = mod_load(argv[1], NULL, argc - 2, argv + 2); if (error == EEXIST) sprintf(command_errbuf, "warning: module '%s' already loaded", argv[1]); return (error == 0 ? CMD_OK : CMD_ERROR); @@ -172,7 +208,7 @@ command_lsmod(int argc, char *argv[]) if (fp->f_modules) { pager_output(" modules: "); for (mp = fp->f_modules; mp; mp = mp->m_next) { - sprintf(lbuf, "%s ", mp->m_name); + sprintf(lbuf, "%s.%d ", mp->m_name, mp->m_version); pager_output(lbuf); } pager_output("\n"); @@ -222,6 +258,8 @@ static int file_load_dependancies(struct preloaded_file *base_file) { struct file_metadata *md; struct preloaded_file *fp; + struct mod_depend *verinfo; + struct kernel_module *mp; char *dmodname; int error; @@ -230,12 +268,25 @@ file_load_dependancies(struct preloaded_file *base_file) { return (0); error = 0; do { - dmodname = (char *)md->md_data; - if (file_findmodule(NULL, dmodname) == NULL) { + verinfo = (struct mod_depend*)md->md_data; + dmodname = (char *)(verinfo + 1); + if (file_findmodule(NULL, dmodname, verinfo) == NULL) { printf("loading required module '%s'\n", dmodname); - error = mod_load(dmodname, 0, NULL); + error = mod_load(dmodname, verinfo, 0, NULL); if (error) break; + /* + * If module loaded via kld name which isn't listed + * in the linker.hints file, we should check if it have + * required version. + */ + mp = file_findmodule(NULL, dmodname, verinfo); + if (mp == NULL) { + sprintf(command_errbuf, "module '%s' exists but with wrong version", + dmodname); + error = ENOENT; + break; + } } md = metadata_next(md, MODINFOMD_DEPLIST); } while (md); @@ -268,7 +319,7 @@ file_loadraw(char *type, char *name) } /* locate the file on the load path */ - cp = file_search(name); + cp = file_search(name, NULL); if (cp == NULL) { sprintf(command_errbuf, "can't find '%s'", name); return(CMD_ERROR); @@ -321,15 +372,18 @@ file_loadraw(char *type, char *name) * If module is already loaded just assign new argc/argv. */ int -mod_load(char *modname, int argc, char *argv[]) +mod_load(char *modname, struct mod_depend *verinfo, int argc, char *argv[]) { - struct preloaded_file *fp, *last_file; struct kernel_module *mp; int err; char *filename; + if (file_havepath(modname)) { + printf("Warning: mod_load() called instead of mod_loadkld() for module '%s'\n", modname); + return (mod_loadkld(modname, argc, argv)); + } /* see if module is already loaded */ - mp = file_findmodule(NULL, modname); + mp = file_findmodule(NULL, modname, verinfo); if (mp) { #ifdef moduleargs if (mp->m_args) @@ -340,33 +394,53 @@ mod_load(char *modname, int argc, char *argv[]) return (0); } /* locate file with the module on the search path */ - filename = mod_searchmodule(modname); + filename = mod_searchmodule(modname, verinfo); if (filename == NULL) { sprintf(command_errbuf, "can't find '%s'", modname); return (ENOENT); } + err = mod_loadkld(filename, argc, argv); + return (err); +} + +/* + * Load specified KLD. If path is omitted, then try to locate it via + * search path. + */ +int +mod_loadkld(const char *kldname, int argc, char *argv[]) +{ + struct preloaded_file *fp, *last_file; + int err; + char *filename; + + /* + * Get fully qualified KLD name + */ + filename = file_search(kldname, kld_ext_list); + if (filename == NULL) { + sprintf(command_errbuf, "can't find '%s'", kldname); + return (ENOENT); + } + /* + * Check if KLD already loaded + */ + fp = file_findfile(filename, NULL); + if (fp) { + sprintf(command_errbuf, "warning: KLD '%s' already loaded", filename); + free(filename); + return (0); + } for (last_file = preloaded_files; last_file != NULL && last_file->f_next != NULL; last_file = last_file->f_next) ; - fp = NULL; do { err = file_load(filename, loadaddr, &fp); if (err) break; -#ifdef moduleargs - mp = file_findmodule(fp, modname); - if (mp == NULL) { - sprintf(command_errbuf, "module '%s' not found in the file '%s': %s", - modname, filename, strerror(err)); - err = ENOENT; - break; - } - mp->m_args = unargv(argc, argv); -#else fp->f_args = unargv(argc, argv); -#endif loadaddr = fp->f_addr + fp->f_size; file_insert_tail(fp); /* Add to the list of loaded files */ if (file_load_dependancies(fp) != 0) { @@ -407,24 +481,38 @@ file_findfile(char *name, char *type) * NULL may be passed as a wildcard. */ struct kernel_module * -file_findmodule(struct preloaded_file *fp, char *modname) +file_findmodule(struct preloaded_file *fp, char *modname, + struct mod_depend *verinfo) { - struct kernel_module *mp; + struct kernel_module *mp, *best; + int bestver, mver; if (fp == NULL) { for (fp = preloaded_files; fp; fp = fp->f_next) { - for (mp = fp->f_modules; mp; mp = mp->m_next) { - if (strcmp(modname, mp->m_name) == 0) - return (mp); - } + mp = file_findmodule(fp, modname, verinfo); + if (mp) + return (mp); } return (NULL); } + best = NULL; + bestver = 0; for (mp = fp->f_modules; mp; mp = mp->m_next) { - if (strcmp(modname, mp->m_name) == 0) - return (mp); + if (strcmp(modname, mp->m_name) == 0) { + if (verinfo == NULL) + return (mp); + mver = mp->m_version; + if (mver == verinfo->md_ver_preferred) + return (mp); + if (mver >= verinfo->md_ver_minimum && + mver <= verinfo->md_ver_maximum && + mver > bestver) { + best = mp; + bestver = mver; + } + } } - return (NULL); + return (best); } /* * Make a copy of (size) bytes of data from (p), and associate them as @@ -468,6 +556,57 @@ metadata_next(struct file_metadata *md, int type) return (md); } +static char *emptyextlist[] = { "", NULL }; + +/* + * Check if the given file is in place and return full path to it. + */ +static char * +file_lookup(const char *path, const char *name, int namelen, char **extlist) +{ + struct stat st; + char *result, *cp, **cpp; + int pathlen, extlen, len; + + pathlen = strlen(path); + extlen = 0; + if (extlist == NULL) + extlist = emptyextlist; + for (cpp = extlist; *cpp; cpp++) { + len = strlen(*cpp); + if (len > extlen) + extlen = len; + } + result = malloc(pathlen + namelen + extlen + 2); + if (result == NULL) + return (NULL); + bcopy(path, result, pathlen); + if (pathlen > 0 && result[pathlen - 1] != '/') + result[pathlen++] = '/'; + cp = result + pathlen; + bcopy(name, cp, namelen); + cp += namelen; + for (cpp = extlist; *cpp; cpp++) { + strcpy(cp, *cpp); + if (stat(result, &st) == 0 && S_ISREG(st.st_mode)) + return result; + } + free(result); + return NULL; +} + +/* + * Check if file name have any qualifiers + */ +static int +file_havepath(const char *name) +{ + const char *cp; + + archsw.arch_getdev(NULL, name, &cp); + return (cp != name || strchr(name, '/') != NULL); +} + /* * Attempt to find the file (name) on the module searchpath. * If (name) is qualified in any way, we simply check it and @@ -479,86 +618,141 @@ metadata_next(struct file_metadata *md, int type) * it internally. */ static char * -file_search(char *name) +file_search(const char *name, char **extlist) { - char *result; - char *path, *sp; - const char *cp; + struct moduledir *mdp; struct stat sb; + char *result; + int namelen; /* Don't look for nothing */ if (name == NULL) - return(name); + return(NULL); if (*name == 0) return(strdup(name)); - /* - * See if there's a device on the front, or a directory name. - */ - archsw.arch_getdev(NULL, name, &cp); - if ((cp != name) || (strchr(name, '/') != NULL)) { + if (file_havepath(name)) { /* Qualified, so just see if it exists */ if (stat(name, &sb) == 0) return(strdup(name)); return(NULL); } - - /* - * Get the module path - */ - if ((cp = getenv("module_path")) == NULL) - cp = default_searchpath; - sp = path = strdup(cp); - - /* - * Traverse the path, splitting off ';'-delimited components. - */ + moduledir_rebuild(); result = NULL; - while((cp = strsep(&path, ";")) != NULL) { - result = malloc(strlen(cp) + strlen(name) + 5); - strcpy(result, cp); - if (cp[strlen(cp) - 1] != '/') - strcat(result, "/"); - strcat(result, name); - if ((stat(result, &sb) == 0) && - S_ISREG(sb.st_mode)) + namelen = strlen(name); + STAILQ_FOREACH(mdp, &moduledir_list, d_link) { + result = file_lookup(mdp->d_path, name, namelen, extlist); + if (result) break; - free(result); - result = NULL; } - free(sp); return(result); } +#define INT_ALIGN(base, ptr) ptr = \ + (base) + (((ptr) - (base) + sizeof(int) - 1) & ~(sizeof(int) - 1)) + +static char * +mod_search_hints(struct moduledir *mdp, const char *modname, + struct mod_depend *verinfo) +{ + u_char *cp, *recptr, *bufend, *best; + char *result; + int *intp, bestver, blen, clen, found, ival, modnamelen, reclen; + + moduledir_readhints(mdp); + modnamelen = strlen(modname); + found = 0; + result = NULL; + bestver = 0; + if (mdp->d_hints == NULL) + goto bad; + recptr = mdp->d_hints; + bufend = recptr + mdp->d_hintsz; + clen = blen = 0; + best = cp = NULL; + while (recptr < bufend && !found) { + intp = (int*)recptr; + reclen = *intp++; + ival = *intp++; + cp = (char*)intp; + switch (ival) { + case MDT_VERSION: + clen = *cp++; + if (clen != modnamelen || bcmp(cp, modname, clen) != 0) + break; + cp += clen; + INT_ALIGN(mdp->d_hints, cp); + ival = *(int*)cp; + cp += sizeof(int); + clen = *cp++; + if (verinfo == NULL || ival == verinfo->md_ver_preferred) { + found = 1; + break; + } + if (ival >= verinfo->md_ver_minimum && + ival <= verinfo->md_ver_maximum && + ival > bestver) { + bestver = ival; + best = cp; + blen = clen; + } + break; + default: + break; + } + recptr += reclen + sizeof(int); + } + /* + * Finally check if KLD is in the place + */ + if (found) + result = file_lookup(mdp->d_path, cp, clen, NULL); + else if (best) + result = file_lookup(mdp->d_path, best, blen, NULL); +bad: + /* + * If nothing found or hints is absent - fallback to the old way + * by using "kldname[.ko]" as module name. + */ + if (!found && !bestver && result == NULL) + result = file_lookup(mdp->d_path, modname, modnamelen, kld_ext_list); + return result; +} + /* * Attempt to locate the file containing the module (name) */ static char * -mod_searchmodule(char *name) +mod_searchmodule(char *name, struct mod_depend *verinfo) { - char *tn, *result; - - /* Look for (name).ko */ - tn = malloc(strlen(name) + 3 + 1); - strcpy(tn, name); - strcat(tn, ".ko"); - result = file_search(tn); - free(tn); - /* Look for just (name) (useful for finding kernels) */ - if (result == NULL) - result = file_search(name); + struct moduledir *mdp; + char *result; + + moduledir_rebuild(); + /* + * Now we ready to lookup module in the given directories + */ + result = NULL; + STAILQ_FOREACH(mdp, &moduledir_list, d_link) { + result = mod_search_hints(mdp, name, verinfo); + if (result) + break; + } return(result); } int -file_addmodule(struct preloaded_file *fp, char *modname, +file_addmodule(struct preloaded_file *fp, char *modname, int version, struct kernel_module **newmp) { struct kernel_module *mp; + struct mod_depend mdepend; - mp = file_findmodule(fp, modname); + bzero(&mdepend, sizeof(mdepend)); + mdepend.md_ver_preferred = version; + mp = file_findmodule(fp, modname, &mdepend); if (mp) return (EEXIST); mp = malloc(sizeof(struct kernel_module)); @@ -566,6 +760,7 @@ file_addmodule(struct preloaded_file *fp, char *modname, return (ENOMEM); bzero(mp, sizeof(struct kernel_module)); mp->m_name = strdup(modname); + mp->m_version = version; mp->m_fp = fp; mp->m_next = fp->f_modules; fp->f_modules = mp; @@ -641,3 +836,123 @@ file_insert_tail(struct preloaded_file *fp) } } +static char * +moduledir_fullpath(struct moduledir *mdp, const char *fname) +{ + char *cp; + + cp = malloc(strlen(mdp->d_path) + strlen(fname) + 2); + if (cp == NULL) + return NULL; + strcpy(cp, mdp->d_path); + strcat(cp, "/"); + strcat(cp, fname); + return (cp); +} + +/* + * Read linker.hints file into memory performing some sanity checks. + */ +static void +moduledir_readhints(struct moduledir *mdp) +{ + struct stat st; + char *path; + int fd, size, version; + + if (mdp->d_hints != NULL || (mdp->d_flags & MDIR_NOHINTS)) + return; + path = moduledir_fullpath(mdp, "linker.hints"); + if (stat(path, &st) != 0 || st.st_size < (sizeof(version) + sizeof(int)) || + st.st_size > 100 * 1024 || (fd = open(path, O_RDONLY)) < 0) { + free(path); + mdp->d_flags |= MDIR_NOHINTS; + return; + } + free(path); + size = read(fd, &version, sizeof(version)); + if (size != sizeof(version) || version != LINKER_HINTS_VERSION) + goto bad; + size = st.st_size - size; + mdp->d_hints = malloc(size); + if (mdp->d_hints == NULL) + goto bad; + if (read(fd, mdp->d_hints, size) != size) + goto bad; + mdp->d_hintsz = size; + close(fd); + return; +bad: + close(fd); + if (mdp->d_hints) { + free(mdp->d_hints); + mdp->d_hints = NULL; + } + mdp->d_flags |= MDIR_NOHINTS; + return; +} + +/* + * Extract directories from the ';' separated list, remove duplicates. + */ +static void +moduledir_rebuild(void) +{ + struct moduledir *mdp, *mtmp; + const char *path, *cp, *ep; + int cplen; + + path = getenv("module_path"); + if (path == NULL) + path = default_searchpath; + /* + * Rebuild list of module directories if it changed + */ + STAILQ_FOREACH(mdp, &moduledir_list, d_link) + mdp->d_flags |= MDIR_REMOVED; + + for (ep = path; *ep != 0; ep++) { + cp = ep; + for (; *ep != 0 && *ep != ';'; ep++) + ; + /* + * Ignore trailing slashes + */ + for (cplen = ep - cp; cplen > 1 && cp[cplen - 1] == '/'; cplen--) + ; + STAILQ_FOREACH(mdp, &moduledir_list, d_link) { + if (strlen(mdp->d_path) != cplen || bcmp(cp, mdp->d_path, cplen) != 0) + continue; + mdp->d_flags &= ~MDIR_REMOVED; + break; + } + if (mdp == NULL) { + mdp = malloc(sizeof(*mdp) + cplen + 1); + if (mdp == NULL) + return; + mdp->d_path = (char*)(mdp + 1); + bcopy(cp, mdp->d_path, cplen); + mdp->d_path[cplen] = 0; + mdp->d_hints = NULL; + mdp->d_flags = 0; + STAILQ_INSERT_TAIL(&moduledir_list, mdp, d_link); + } + } + /* + * Delete unused directories if any + */ + mdp = STAILQ_FIRST(&moduledir_list); + while (mdp) { + if ((mdp->d_flags & MDIR_REMOVED) == 0) { + mdp = STAILQ_NEXT(mdp, d_link); + } else { + if (mdp->d_hints) + free(mdp->d_hints); + mtmp = mdp; + mdp = STAILQ_NEXT(mdp, d_link); + STAILQ_REMOVE(&moduledir_list, mtmp, moduledir, d_link); + free(mtmp); + } + } + return; +} diff --git a/sys/boot/i386/libi386/i386_module.c b/sys/boot/i386/libi386/i386_module.c index cafd22b97e06..5c29aee00d34 100644 --- a/sys/boot/i386/libi386/i386_module.c +++ b/sys/boot/i386/libi386/i386_module.c @@ -50,7 +50,7 @@ i386_autoload(void) /* autoload ACPI support */ /* XXX should be in 4th keyed off acpi_load */ if ((getenv("acpi_load") && !getenv("hint.acpi.0.disable"))) { - error = mod_load("acpi", 0, NULL); + error = mod_load("acpi", NULL, 0, NULL); if (error != 0) printf("ACPI autoload failed - %s\n", strerror(error)); } diff --git a/sys/conf/kmod.mk b/sys/conf/kmod.mk index b2109a534b55..ddf3abcd5d8c 100644 --- a/sys/conf/kmod.mk +++ b/sys/conf/kmod.mk @@ -237,6 +237,9 @@ realinstall: _SUBDIR ln -fs $$l $$t; \ done; true .endif +.if !defined(NO_XREF) + kldxref ${DESTDIR}${KMODDIR} +.endif install: afterinstall _SUBDIR .if !defined(NOMAN) diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c index 7b5753bd51c1..97d8a9ac09c2 100644 --- a/sys/kern/kern_linker.c +++ b/sys/kern/kern_linker.c @@ -52,8 +52,11 @@ int kld_debug = 0; #endif -static char *linker_search_path(const char *name); +/*static char *linker_search_path(const char *name, struct mod_depend *verinfo);*/ static const char *linker_basename(const char* path); +static int linker_load_module(const char *kldname, const char *modname, + struct linker_file *parent, struct mod_depend *verinfo, + struct linker_file **lfpp); /* Metadata from the static kernel */ SET_DECLARE(modmetadata_set, struct mod_metadata); @@ -332,29 +335,12 @@ linker_load_file(const char* filename, linker_file_t* result) return error; } +/* XXX: function parameters are incomplete */ int linker_reference_module(const char *modname, linker_file_t *result) { - char *pathname; - int res; - /* - * There will be a system to look up or guess a file name from - * a module name. - * For now we just try to load a file with the same name. - */ - if ((pathname = linker_search_path(modname)) == NULL) - return (ENOENT); - - /* - * If the module is already loaded or built into the kernel, - * linker_load_file() simply bumps it's refcount. - */ - res = linker_load_file(pathname, result); - - free(pathname, M_LINKER); - - return (res); + return linker_load_module(NULL, modname, NULL, NULL, result); } linker_file_t @@ -699,9 +685,8 @@ linker_ddb_symbol_values(c_linker_sym_t sym, linker_symval_t *symval) int kldload(struct proc* p, struct kldload_args* uap) { + char *kldname, *modname; char *pathname = NULL; - char *realpath = NULL; - const char *filename; linker_file_t lf; int error = 0; @@ -719,19 +704,19 @@ kldload(struct proc* p, struct kldload_args* uap) if ((error = copyinstr(SCARG(uap, file), pathname, MAXPATHLEN, NULL)) != 0) goto out; - realpath = linker_search_path(pathname); - if (realpath == NULL) { - error = ENOENT; - goto out; + /* + * If path do not contain qualified name or any dot in it (kldname.ko, or + * kldname.ver.ko) treat it as interface name. + */ + if (index(pathname, '/') || index(pathname, '.')) { + kldname = pathname; + modname = NULL; + } else { + kldname = NULL; + modname = pathname; } - /* Can't load more than one file with the same name */ - filename = linker_basename(realpath); - if (linker_find_file_by_name(filename)) { - error = EEXIST; - goto out; - } - - if ((error = linker_load_file(realpath, &lf)) != 0) + error = linker_load_module(kldname, modname, NULL, NULL, &lf); + if (error) goto out; lf->userrefs++; @@ -740,8 +725,6 @@ kldload(struct proc* p, struct kldload_args* uap) out: if (pathname) free(pathname, M_TEMP); - if (realpath) - free(realpath, M_LINKER); mtx_unlock(&Giant); return (error); } @@ -1001,6 +984,29 @@ modlist_lookup(const char *name, int ver) return NULL; } +static modlist_t +modlist_lookup2(const char *name, struct mod_depend *verinfo) +{ + modlist_t mod, bestmod; + int ver; + + if (verinfo == NULL) + return modlist_lookup(name, 0); + bestmod = NULL; + for (mod = TAILQ_FIRST(&found_modules); mod; mod = TAILQ_NEXT(mod, link)) { + if (strcmp(mod->name, name) != 0) + continue; + ver = mod->version; + if (ver == verinfo->md_ver_preferred) + return mod; + if (ver >= verinfo->md_ver_minimum && + ver <= verinfo->md_ver_maximum && + ver > bestmod->version) + bestmod = mod; + } + return bestmod; +} + static modlist_t modlist_newmodule(const char *modname, int version, linker_file_t container) { @@ -1170,7 +1176,7 @@ linker_preload(void* arg) } if (nmdp < stop) /* it's a self reference */ continue; - if (modlist_lookup(modname, 0) == NULL) { + if (modlist_lookup2(modname, verinfo) == NULL) { /* ok, the module isn't here yet, we are not finished */ resolves = 0; } @@ -1234,7 +1240,7 @@ linker_preload(void* arg) if (mp->md_type != MDT_DEPEND) continue; linker_mdt_depend(lf, mp, &modname, &verinfo); - mod = modlist_lookup(modname, 0); + mod = modlist_lookup2(modname, verinfo); mod->container->refs++; error = linker_file_add_dependancy(lf, mod->container); if (error) @@ -1279,6 +1285,7 @@ SYSINIT(preload, SI_SUB_KLD, SI_ORDER_MIDDLE, linker_preload, 0); * character as a separator to be consistent with the bootloader. */ +static char linker_hintfile[] = "linker.hints"; static char linker_path[MAXPATHLEN] = "/boot/kernel;/boot/modules/;/modules/"; SYSCTL_STRING(_kern, OID_AUTO, module_path, CTLFLAG_RW, linker_path, @@ -1287,20 +1294,226 @@ SYSCTL_STRING(_kern, OID_AUTO, module_path, CTLFLAG_RW, linker_path, TUNABLE_STR("module_path", linker_path, sizeof(linker_path)); static char *linker_ext_list[] = { - ".ko", "", + ".ko", NULL }; +/* + * Check if file actually exists either with or without extension listed + * in the linker_ext_list. + * (probably should be generic for the rest of the kernel) + */ static char * -linker_search_path(const char *name) +linker_lookup_file(const char *path, int pathlen, + const char *name, int namelen, struct vattr *vap) { struct nameidata nd; struct proc *p = curproc; /* XXX */ - char *cp, *ep, *result, **cpp; - int error, extlen, len, flags; + char *result, **cpp; + int error, len, extlen, flags; enum vtype type; + extlen = 0; + for (cpp = linker_ext_list; *cpp; cpp++) { + len = strlen(*cpp); + if (len > extlen) + extlen = len; + } + extlen++; /* trailing '\0' */ + + result = malloc((pathlen + namelen + extlen), M_LINKER, M_WAITOK); + for (cpp = linker_ext_list; *cpp; cpp++) { + bcopy(path, result, pathlen); + bcopy(name, result + pathlen, namelen); + strcpy(result + namelen + pathlen, *cpp); + /* + * Attempt to open the file, and return the path if we succeed + * and it's a regular file. + */ + NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, result, p); + flags = FREAD; + error = vn_open(&nd, &flags, 0); + if (error == 0) { + NDFREE(&nd, NDF_ONLY_PNBUF); + type = nd.ni_vp->v_type; + if (vap) + VOP_GETATTR(nd.ni_vp, vap, p->p_ucred, p); + VOP_UNLOCK(nd.ni_vp, 0, p); + vn_close(nd.ni_vp, FREAD, p->p_ucred, p); + if (type == VREG) + return(result); + } + } + free(result, M_LINKER); + return(NULL); +} + +#define INT_ALIGN(base, ptr) ptr = \ + (base) + (((ptr) - (base) + sizeof(int) - 1) & ~(sizeof(int) - 1)) + +/* + * Lookup KLD which contains requested module in the "linker.hints" file. + * If version specification is available, then try to find the best KLD. + * Otherwise just find the latest one. + */ +static char * +linker_hints_lookup(const char *path, int pathlen, + const char *modname, int modnamelen, + struct mod_depend *verinfo) +{ + struct proc *p = curproc; + struct ucred *cred = p ? p->p_ucred : NULL; + struct nameidata nd; + struct vattr vattr, mattr; + u_char *hints = NULL; + u_char *cp, *recptr, *bufend, *result, *best, *pathbuf; + int error, ival, bestver, *intp, reclen, found, flags, clen, blen; + + result = NULL; + bestver = found = 0; + + reclen = imax(modnamelen, strlen(linker_hintfile)) + pathlen; + pathbuf = malloc(reclen, M_LINKER, M_WAITOK); + bcopy(path, pathbuf, pathlen); + strcpy(pathbuf + pathlen, linker_hintfile); + + NDINIT(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, pathbuf, p); + flags = FREAD; + error = vn_open(&nd, &flags, 0); + if (error) + goto bad; + NDFREE(&nd, NDF_ONLY_PNBUF); + VOP_UNLOCK(nd.ni_vp, 0, p); + if (nd.ni_vp->v_type != VREG) + goto bad; + best = cp = NULL; + error = VOP_GETATTR(nd.ni_vp, &vattr, cred, p); + if (error) + goto bad; + /* + * XXX: we need to limit this number to some reasonable value + */ + if (vattr.va_size > 100 * 1024) { + printf("hints file too large %ld\n", (long)vattr.va_size); + goto bad; + } + hints = malloc(vattr.va_size, M_TEMP, M_WAITOK); + if (hints == NULL) + goto bad; + error = vn_rdwr(UIO_READ, nd.ni_vp, (caddr_t)hints, vattr.va_size, 0, + UIO_SYSSPACE, IO_NODELOCKED, cred, &reclen, p); + if (error) + goto bad; + vn_close(nd.ni_vp, FREAD, cred, p); + nd.ni_vp = NULL; + if (reclen != 0) { + printf("can't read %d\n", reclen); + goto bad; + } + intp = (int*)hints; + ival = *intp++; + if (ival != LINKER_HINTS_VERSION) { + printf("hints file version mismatch %d\n", ival); + goto bad; + } + bufend = hints + vattr.va_size; + recptr = (u_char*)intp; + clen = blen = 0; + while (recptr < bufend && !found) { + intp = (int*)recptr; + reclen = *intp++; + ival = *intp++; + cp = (char*)intp; + switch (ival) { + case MDT_VERSION: + clen = *cp++; + if (clen != modnamelen || bcmp(cp, modname, clen) != 0) + break; + cp += clen; + INT_ALIGN(hints, cp); + ival = *(int*)cp; + cp += sizeof(int); + clen = *cp++; + if (verinfo == NULL || ival == verinfo->md_ver_preferred) { + found = 1; + break; + } + if (ival >= verinfo->md_ver_minimum && + ival <= verinfo->md_ver_maximum && + ival > bestver) { + bestver = ival; + best = cp; + blen = clen; + } + break; + default: + break; + } + recptr += reclen + sizeof(int); + } + /* + * Finally check if KLD is in the place + */ + if (found) + result = linker_lookup_file(path, pathlen, cp, clen, &mattr); + else if (best) + result = linker_lookup_file(path, pathlen, best, blen, &mattr); + if (result && timespeccmp(&mattr.va_mtime, &vattr.va_mtime, >)) { + /* + * KLD is newer than hints file. What we should do now ? + */ + printf("warning: KLD '%s' is newer than the linker.hints file\n", result); + } +bad: + if (hints) + free(hints, M_TEMP); + if (nd.ni_vp != NULL) + vn_close(nd.ni_vp, FREAD, cred, p); + /* + * If nothing found or hints is absent - fallback to the old way + * by using "kldname[.ko]" as module name. + */ + if (!found && !bestver && result == NULL) + result = linker_lookup_file(path, pathlen, modname, modnamelen, NULL); + return result; +} + +/* + * Lookup KLD which contains requested module in the all directories. + */ +static char * +linker_search_module(const char *modname, int modnamelen, + struct mod_depend *verinfo) +{ + char *cp, *ep, *result; + + /* + * traverse the linker path + */ + for (cp = linker_path; *cp; cp = ep + 1) { + + /* find the end of this component */ + for (ep = cp; (*ep != 0) && (*ep != ';'); ep++) + ; + result = linker_hints_lookup(cp, ep - cp, modname, modnamelen, verinfo); + if (result != NULL) + return(result); + if (*ep == 0) + break; + } + return (NULL); +} + +/* + * Search for module in all directories listed in the linker_path. + */ +static char * +linker_search_kld(const char *name) +{ + char *cp, *ep, *result, **cpp; + int extlen, len; + /* qualified at all? */ if (index(name, '/')) return(linker_strdup(name)); @@ -1314,42 +1527,15 @@ linker_search_path(const char *name) extlen++; /* trailing '\0' */ /* traverse the linker path */ - cp = linker_path; len = strlen(name); - for (;;) { - + for (ep = linker_path; *ep; ep++) { + cp = ep; /* find the end of this component */ - for (ep = cp; (*ep != 0) && (*ep != ';'); ep++) + for (; *ep != 0 && *ep != ';'; ep++) ; - result = malloc((len + (ep - cp) + extlen + 1), M_LINKER, M_WAITOK); - if (result == NULL) /* actually ENOMEM */ - return(NULL); - for (cpp = linker_ext_list; *cpp; cpp++) { - strncpy(result, cp, ep - cp); - strcpy(result + (ep - cp), "/"); - strcat(result, name); - strcat(result, *cpp); - /* - * Attempt to open the file, and return the path if we succeed - * and it's a regular file. - */ - NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, result, p); - flags = FREAD; - error = vn_open(&nd, &flags, 0); - if (error == 0) { - NDFREE(&nd, NDF_ONLY_PNBUF); - type = nd.ni_vp->v_type; - VOP_UNLOCK(nd.ni_vp, 0, p); - vn_close(nd.ni_vp, FREAD, p->p_ucred, p); - if (type == VREG) - return(result); - } - } - free(result, M_LINKER); - - if (*ep == 0) - break; - cp = ep + 1; + result = linker_lookup_file(cp, ep - cp, name, len, NULL); + if (result != NULL) + return(result); } return(NULL); } @@ -1372,23 +1558,41 @@ linker_basename(const char* path) * if "parent" is not NULL, register a reference to it. */ static int -linker_load_module(const char *modname, struct linker_file *parent) +linker_load_module(const char *kldname, const char *modname, + struct linker_file *parent, struct mod_depend *verinfo, + struct linker_file **lfpp) { linker_file_t lfdep; const char *filename; char *pathname; int error; - /* - * There will be a system to look up or guess a file name from - * a module name. - * For now we just try to load a file with the same name. - */ - pathname = linker_search_path(modname); + if (modname == NULL) { + /* + * We have to load KLD + */ + KASSERT(verinfo == NULL, ("linker_load_module: verinfo is not NULL")); + pathname = linker_search_kld(kldname); + } else { + if (modlist_lookup2(modname, verinfo) != NULL) + return (EEXIST); + if (kldname == NULL) { + /* + * Need to find a KLD with required module + */ + pathname = linker_search_module(modname, strlen(modname), verinfo); + } else + pathname = linker_strdup(kldname); + } if (pathname == NULL) - return ENOENT; + return (ENOENT); - /* Can't load more than one file with the same basename */ + /* + * Can't load more than one file with the same basename + * XXX: Actually it should be possible to have multiple KLDs + * with the same basename but different path because they can provide + * different versions of the same modules. + */ filename = linker_basename(pathname); if (linker_find_file_by_name(filename)) { error = EEXIST; @@ -1399,11 +1603,18 @@ linker_load_module(const char *modname, struct linker_file *parent) error = linker_load_file(pathname, &lfdep); if (error) break; + if (modname && verinfo && modlist_lookup2(modname, verinfo) == NULL) { + linker_file_unload(lfdep); + error = ENOENT; + break; + } if (parent) { error = linker_file_add_dependancy(parent, lfdep); if (error) break; } + if (lfpp) + *lfpp = lfdep; } while(0); out: if (pathname) @@ -1421,6 +1632,7 @@ linker_load_dependancies(linker_file_t lf) linker_file_t lfdep; struct mod_metadata **start, **stop, **mdp, **nmdp; struct mod_metadata *mp, *nmp; + struct mod_depend *verinfo; modlist_t mod; const char *modname, *nmodname; int ver, error = 0, count; @@ -1454,7 +1666,7 @@ linker_load_dependancies(linker_file_t lf) mp = linker_reloc_ptr(lf, *mdp); if (mp->md_type != MDT_DEPEND) continue; - modname = linker_reloc_ptr(lf, mp->md_cval); + linker_mdt_depend(lf, mp, &modname, &verinfo); nmodname = NULL; for (nmdp = start; nmdp < stop; nmdp++) { nmp = linker_reloc_ptr(lf, *nmdp); @@ -1466,7 +1678,7 @@ linker_load_dependancies(linker_file_t lf) } if (nmdp < stop) /* early exit, it's a self reference */ continue; - mod = modlist_lookup(modname, 0); + mod = modlist_lookup2(modname, verinfo); if (mod) { /* woohoo, it's loaded already */ lfdep = mod->container; lfdep->refs++; @@ -1475,7 +1687,7 @@ linker_load_dependancies(linker_file_t lf) break; continue; } - error = linker_load_module(modname, lf); + error = linker_load_module(NULL, modname, lf, verinfo, NULL); if (error) { printf("KLD %s: depends on %s - not available\n", lf->filename, modname); diff --git a/sys/sys/linker.h b/sys/sys/linker.h index 20ab83ec1206..687b86531395 100644 --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -189,6 +189,8 @@ int linker_ddb_symbol_values(c_linker_sym_t _sym, linker_symval_t *_symval); #define MODINFOMD_DEPLIST (0x4001 | MODINFOMD_NOCOPY) /* depends on */ +#define LINKER_HINTS_VERSION 1 /* linker.hints file version */ + #ifdef _KERNEL /*