Index: head/sys/kern/kern_environment.c =================================================================== --- head/sys/kern/kern_environment.c (revision 335994) +++ head/sys/kern/kern_environment.c (revision 335995) @@ -1,850 +1,920 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998 Michael Smith * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The unified bootloader passes us a pointer to a preserved copy of * bootstrap/kernel environment variables. We convert them to a * dynamic array of strings later when the VM subsystem is up. * * We make these available through the kenv(2) syscall for userland * and through kern_getenv()/freeenv() kern_setenv() kern_unsetenv() testenv() for * the kernel. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +static char *_getenv_dynamic_locked(const char *name, int *idx); +static char *_getenv_dynamic(const char *name, int *idx); + static MALLOC_DEFINE(M_KENV, "kenv", "kernel environment"); #define KENV_SIZE 512 /* Maximum number of environment strings */ -/* pointer to the static environment */ +/* pointer to the config-generated static environment */ char *kern_envp; -static int env_len; -static int env_pos; + +/* pointer to the md-static environment */ +char *md_envp; +static int md_env_len; +static int md_env_pos; + static char *kernenv_next(char *); /* dynamic environment variables */ char **kenvp; struct mtx kenv_lock; /* * No need to protect this with a mutex since SYSINITS are single threaded. */ int dynamic_kenv = 0; #define KENV_CHECK if (!dynamic_kenv) \ panic("%s: called before SI_SUB_KMEM", __func__) int sys_kenv(td, uap) struct thread *td; struct kenv_args /* { int what; const char *name; char *value; int len; } */ *uap; { char *name, *value, *buffer = NULL; size_t len, done, needed, buflen; int error, i; KASSERT(dynamic_kenv, ("kenv: dynamic_kenv = 0")); error = 0; if (uap->what == KENV_DUMP) { #ifdef MAC error = mac_kenv_check_dump(td->td_ucred); if (error) return (error); #endif done = needed = 0; buflen = uap->len; if (buflen > KENV_SIZE * (KENV_MNAMELEN + KENV_MVALLEN + 2)) buflen = KENV_SIZE * (KENV_MNAMELEN + KENV_MVALLEN + 2); if (uap->len > 0 && uap->value != NULL) buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); mtx_lock(&kenv_lock); for (i = 0; kenvp[i] != NULL; i++) { len = strlen(kenvp[i]) + 1; needed += len; len = min(len, buflen - done); /* * If called with a NULL or insufficiently large * buffer, just keep computing the required size. */ if (uap->value != NULL && buffer != NULL && len > 0) { bcopy(kenvp[i], buffer + done, len); done += len; } } mtx_unlock(&kenv_lock); if (buffer != NULL) { error = copyout(buffer, uap->value, done); free(buffer, M_TEMP); } td->td_retval[0] = ((done == needed) ? 0 : needed); return (error); } switch (uap->what) { case KENV_SET: error = priv_check(td, PRIV_KENV_SET); if (error) return (error); break; case KENV_UNSET: error = priv_check(td, PRIV_KENV_UNSET); if (error) return (error); break; } name = malloc(KENV_MNAMELEN + 1, M_TEMP, M_WAITOK); error = copyinstr(uap->name, name, KENV_MNAMELEN + 1, NULL); if (error) goto done; switch (uap->what) { case KENV_GET: #ifdef MAC error = mac_kenv_check_get(td->td_ucred, name); if (error) goto done; #endif value = kern_getenv(name); if (value == NULL) { error = ENOENT; goto done; } len = strlen(value) + 1; if (len > uap->len) len = uap->len; error = copyout(value, uap->value, len); freeenv(value); if (error) goto done; td->td_retval[0] = len; break; case KENV_SET: len = uap->len; if (len < 1) { error = EINVAL; goto done; } if (len > KENV_MVALLEN + 1) len = KENV_MVALLEN + 1; value = malloc(len, M_TEMP, M_WAITOK); error = copyinstr(uap->value, value, len, NULL); if (error) { free(value, M_TEMP); goto done; } #ifdef MAC error = mac_kenv_check_set(td->td_ucred, name, value); if (error == 0) #endif kern_setenv(name, value); free(value, M_TEMP); break; case KENV_UNSET: #ifdef MAC error = mac_kenv_check_unset(td->td_ucred, name); if (error) goto done; #endif error = kern_unsetenv(name); if (error) error = ENOENT; break; default: error = EINVAL; break; } done: free(name, M_TEMP); return (error); } /* * Populate the initial kernel environment. * * This is called very early in MD startup, either to provide a copy of the * environment obtained from a boot loader, or to provide an empty buffer into * which MD code can store an initial environment using kern_setenv() calls. * - * When a copy of an initial environment is passed in, we start by scanning that - * env for overrides to the compiled-in envmode and hintmode variables. + * kern_envp is set to the static_env generated by config(8). This implements + * the env keyword described in config(5). * - * If the global envmode is 1, the environment is initialized from the global - * static_env[], regardless of the arguments passed. This implements the env - * keyword described in config(5). In this case env_pos is set to env_len, - * causing kern_setenv() to return -1 (if len > 0) or panic (if len == 0) until - * the dynamic environment is available. The envmode and static_env variables - * are defined in env.c which is generated by config(8). - * * If len is non-zero, the caller is providing an empty buffer. The caller will * subsequently use kern_setenv() to add up to len bytes of initial environment * before the dynamic environment is available. * * If len is zero, the caller is providing a pre-loaded buffer containing * environment strings. Additional strings cannot be added until the dynamic * environment is available. The memory pointed to must remain stable at least - * until sysinit runs init_dynamic_kenv(). If no initial environment is - * available from the boot loader, passing a NULL pointer allows the static_env - * to be installed if it is configured. + * until sysinit runs init_dynamic_kenv() and preferably until after SI_SUB_KMEM + * is finished so that subr_hints routines may continue to use it until the + * environments have been fully merged at the end of the pass. If no initial + * environment is available from the boot loader, passing a NULL pointer allows + * the static_env to be installed if it is configured. In this case, any call + * to kern_setenv() prior to the setup of the dynamic environment will result in + * a panic. */ void init_static_kenv(char *buf, size_t len) { - char *cp; - - for (cp = buf; cp != NULL && cp[0] != '\0'; cp += strlen(cp) + 1) { - if (strcmp(cp, "static_env.disabled=1") == 0) - envmode = 0; - if (strcmp(cp, "static_hints.disabled=1") == 0) - hintmode = 0; - } + char *eval; - if (envmode == 1) { + md_envp = buf; + md_env_len = len; + md_env_pos = 0; + + /* + * static_env and static_hints may both be disabled, but in slightly + * different ways. For static_env, we just don't setup kern_envp and + * it's as if a static env wasn't even provided. For static_hints, + * we effectively zero out the buffer to stop the rest of the kernel + * from being able to use it. + * + * We're intentionally setting this up so that static_hints.disabled may + * be specified in either the MD env or the static env. This keeps us + * consistent in our new world view. + */ + eval = kern_getenv("static_env.disabled"); + if (eval == NULL || strcmp(eval, "1") != 0) kern_envp = static_env; - env_len = len; - env_pos = len; - } else { - kern_envp = buf; - env_len = len; - env_pos = 0; - } + eval = kern_getenv("static_hints.disabled"); + if (eval != NULL && strcmp(eval, "1") == 0) + *static_hints = '\0'; } -/* - * Setup the dynamic kernel environment. - */ static void -init_dynamic_kenv(void *data __unused) +init_dynamic_kenv_from(char *init_env, int *curpos) { - char *cp, *cpnext; + char *cp, *cpnext, *eqpos, *found; size_t len; int i; - kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV, - M_WAITOK | M_ZERO); - i = 0; - if (kern_envp && *kern_envp != '\0') { - for (cp = kern_envp; cp != NULL; cp = cpnext) { + if (init_env && *init_env != '\0') { + found = NULL; + i = *curpos; + for (cp = init_env; cp != NULL; cp = cpnext) { cpnext = kernenv_next(cp); len = strlen(cp) + 1; if (len > KENV_MNAMELEN + 1 + KENV_MVALLEN + 1) { printf( "WARNING: too long kenv string, ignoring %s\n", cp); - continue; + goto sanitize; } - if (i < KENV_SIZE) { - kenvp[i] = malloc(len, M_KENV, M_WAITOK); - strcpy(kenvp[i++], cp); - explicit_bzero(cp, strlen(cp)); - } else + eqpos = strchr(cp, '='); + if (eqpos == NULL) { printf( + "WARNING: malformed static env value, ignoring %s\n", + cp); + goto sanitize; + } + *eqpos = 0; + /* + * De-dupe the environment as we go. We don't add the + * duplicated assignments because config(8) will flip + * the order of the static environment around to make + * kernel processing match the order of specification + * in the kernel config. + */ + found = _getenv_dynamic_locked(cp, NULL); + *eqpos = '='; + if (found != NULL) + goto sanitize; + if (i > KENV_SIZE) { + printf( "WARNING: too many kenv strings, ignoring %s\n", cp); + goto sanitize; + } + + kenvp[i] = malloc(len, M_KENV, M_WAITOK); + strcpy(kenvp[i++], cp); +sanitize: + explicit_bzero(cp, len - 1); } + *curpos = i; } - kenvp[i] = NULL; +} +/* + * Setup the dynamic kernel environment. + */ +static void +init_dynamic_kenv(void *data __unused) +{ + int dynamic_envpos; + + kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV, + M_WAITOK | M_ZERO); + + dynamic_envpos = 0; + init_dynamic_kenv_from(md_envp, &dynamic_envpos); + init_dynamic_kenv_from(kern_envp, &dynamic_envpos); + kenvp[dynamic_envpos] = NULL; + mtx_init(&kenv_lock, "kernel environment", NULL, MTX_DEF); dynamic_kenv = 1; } SYSINIT(kenv, SI_SUB_KMEM, SI_ORDER_ANY, init_dynamic_kenv, NULL); void freeenv(char *env) { if (dynamic_kenv && env != NULL) { explicit_bzero(env, strlen(env)); free(env, M_KENV); } } /* * Internal functions for string lookup. */ static char * -_getenv_dynamic(const char *name, int *idx) +_getenv_dynamic_locked(const char *name, int *idx) { char *cp; int len, i; - mtx_assert(&kenv_lock, MA_OWNED); len = strlen(name); for (cp = kenvp[0], i = 0; cp != NULL; cp = kenvp[++i]) { if ((strncmp(cp, name, len) == 0) && (cp[len] == '=')) { if (idx != NULL) *idx = i; return (cp + len + 1); } } return (NULL); } static char * -_getenv_static(const char *name) +_getenv_dynamic(const char *name, int *idx) { + + mtx_assert(&kenv_lock, MA_OWNED); + return (_getenv_dynamic_locked(name, idx)); +} + +static char * +_getenv_static_from(char *chkenv, const char *name) +{ char *cp, *ep; int len; - for (cp = kern_envp; cp != NULL; cp = kernenv_next(cp)) { + for (cp = chkenv; cp != NULL; cp = kernenv_next(cp)) { for (ep = cp; (*ep != '=') && (*ep != 0); ep++) ; if (*ep != '=') continue; len = ep - cp; ep++; if (!strncmp(name, cp, len) && name[len] == 0) return (ep); } return (NULL); } +static char * +_getenv_static(const char *name) +{ + char *val; + + val = _getenv_static_from(md_envp, name); + if (val != NULL) + return (val); + val = _getenv_static_from(kern_envp, name); + if (val != NULL) + return (val); + return (NULL); +} + /* * Look up an environment variable by name. * Return a pointer to the string if found. * The pointer has to be freed with freeenv() * after use. */ char * kern_getenv(const char *name) { char buf[KENV_MNAMELEN + 1 + KENV_MVALLEN + 1]; char *ret; if (dynamic_kenv) { if (getenv_string(name, buf, sizeof(buf))) { ret = strdup(buf, M_KENV); } else { ret = NULL; WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, "getenv"); } } else ret = _getenv_static(name); return (ret); } /* * Test if an environment variable is defined. */ int testenv(const char *name) { char *cp; if (dynamic_kenv) { mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, NULL); mtx_unlock(&kenv_lock); } else cp = _getenv_static(name); if (cp != NULL) return (1); return (0); } +/* + * Set an environment variable in the MD-static environment. This cannot + * feasibly be done on config(8)-generated static environments as they don't + * generally include space for extra variables. + */ static int setenv_static(const char *name, const char *value) { int len; - if (env_pos >= env_len) + if (md_env_pos >= md_env_len) return (-1); /* Check space for x=y and two nuls */ len = strlen(name) + strlen(value); - if (len + 3 < env_len - env_pos) { - len = sprintf(&kern_envp[env_pos], "%s=%s", name, value); - env_pos += len+1; - kern_envp[env_pos] = '\0'; + if (len + 3 < md_env_len - md_env_pos) { + len = sprintf(&md_envp[md_env_pos], "%s=%s", name, value); + md_env_pos += len+1; + md_envp[md_env_pos] = '\0'; return (0); } else return (-1); } /* * Set an environment variable by name. */ int kern_setenv(const char *name, const char *value) { char *buf, *cp, *oldenv; int namelen, vallen, i; - if (dynamic_kenv == 0 && env_len > 0) + if (dynamic_kenv == 0 && md_env_len > 0) return (setenv_static(name, value)); KENV_CHECK; namelen = strlen(name) + 1; if (namelen > KENV_MNAMELEN + 1) return (-1); vallen = strlen(value) + 1; if (vallen > KENV_MVALLEN + 1) return (-1); buf = malloc(namelen + vallen, M_KENV, M_WAITOK); sprintf(buf, "%s=%s", name, value); mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, &i); if (cp != NULL) { oldenv = kenvp[i]; kenvp[i] = buf; mtx_unlock(&kenv_lock); free(oldenv, M_KENV); } else { /* We add the option if it wasn't found */ for (i = 0; (cp = kenvp[i]) != NULL; i++) ; /* Bounds checking */ if (i < 0 || i >= KENV_SIZE) { free(buf, M_KENV); mtx_unlock(&kenv_lock); return (-1); } kenvp[i] = buf; kenvp[i + 1] = NULL; mtx_unlock(&kenv_lock); } return (0); } /* * Unset an environment variable string. */ int kern_unsetenv(const char *name) { char *cp, *oldenv; int i, j; KENV_CHECK; mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, &i); if (cp != NULL) { oldenv = kenvp[i]; for (j = i + 1; kenvp[j] != NULL; j++) kenvp[i++] = kenvp[j]; kenvp[i] = NULL; mtx_unlock(&kenv_lock); explicit_bzero(oldenv, strlen(oldenv)); free(oldenv, M_KENV); return (0); } mtx_unlock(&kenv_lock); return (-1); } /* * Return a string value from an environment variable. */ int getenv_string(const char *name, char *data, int size) { char *cp; if (dynamic_kenv) { mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, NULL); if (cp != NULL) strlcpy(data, cp, size); mtx_unlock(&kenv_lock); } else { cp = _getenv_static(name); if (cp != NULL) strlcpy(data, cp, size); } return (cp != NULL); } /* * Return an array of integers at the given type size and signedness. */ int getenv_array(const char *name, void *pdata, int size, int *psize, int type_size, bool allow_signed) { char buf[KENV_MNAMELEN + 1 + KENV_MVALLEN + 1]; uint8_t shift; int64_t value; int64_t old; char *end; char *ptr; int n; if (getenv_string(name, buf, sizeof(buf)) == 0) return (0); /* get maximum number of elements */ size /= type_size; n = 0; for (ptr = buf; *ptr != 0; ) { value = strtoq(ptr, &end, 0); /* check if signed numbers are allowed */ if (value < 0 && !allow_signed) goto error; /* check for invalid value */ if (ptr == end) goto error; /* check for valid suffix */ switch (*end) { case 't': case 'T': shift = 40; end++; break; case 'g': case 'G': shift = 30; end++; break; case 'm': case 'M': shift = 20; end++; break; case 'k': case 'K': shift = 10; end++; break; case ' ': case '\t': case ',': case 0: shift = 0; break; default: /* garbage after numeric value */ goto error; } /* skip till next value, if any */ while (*end == '\t' || *end == ',' || *end == ' ') end++; /* update pointer */ ptr = end; /* apply shift */ old = value; value <<= shift; /* overflow check */ if ((value >> shift) != old) goto error; /* check for buffer overflow */ if (n >= size) goto error; /* store value according to type size */ switch (type_size) { case 1: if (allow_signed) { if (value < SCHAR_MIN || value > SCHAR_MAX) goto error; } else { if (value < 0 || value > UCHAR_MAX) goto error; } ((uint8_t *)pdata)[n] = (uint8_t)value; break; case 2: if (allow_signed) { if (value < SHRT_MIN || value > SHRT_MAX) goto error; } else { if (value < 0 || value > USHRT_MAX) goto error; } ((uint16_t *)pdata)[n] = (uint16_t)value; break; case 4: if (allow_signed) { if (value < INT_MIN || value > INT_MAX) goto error; } else { if (value > UINT_MAX) goto error; } ((uint32_t *)pdata)[n] = (uint32_t)value; break; case 8: ((uint64_t *)pdata)[n] = (uint64_t)value; break; default: goto error; } n++; } *psize = n * type_size; if (n != 0) return (1); /* success */ error: return (0); /* failure */ } /* * Return an integer value from an environment variable. */ int getenv_int(const char *name, int *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (int) tmp; return (rval); } /* * Return an unsigned integer value from an environment variable. */ int getenv_uint(const char *name, unsigned int *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (unsigned int) tmp; return (rval); } /* * Return an int64_t value from an environment variable. */ int getenv_int64(const char *name, int64_t *data) { quad_t tmp; int64_t rval; rval = getenv_quad(name, &tmp); if (rval) *data = (int64_t) tmp; return (rval); } /* * Return an uint64_t value from an environment variable. */ int getenv_uint64(const char *name, uint64_t *data) { quad_t tmp; uint64_t rval; rval = getenv_quad(name, &tmp); if (rval) *data = (uint64_t) tmp; return (rval); } /* * Return a long value from an environment variable. */ int getenv_long(const char *name, long *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (long) tmp; return (rval); } /* * Return an unsigned long value from an environment variable. */ int getenv_ulong(const char *name, unsigned long *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (unsigned long) tmp; return (rval); } /* * Return a quad_t value from an environment variable. */ int getenv_quad(const char *name, quad_t *data) { char value[KENV_MNAMELEN + 1 + KENV_MVALLEN + 1]; char *vtp; quad_t iv; if (!getenv_string(name, value, sizeof(value))) return (0); iv = strtoq(value, &vtp, 0); if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) return (0); switch (vtp[0]) { case 't': case 'T': iv *= 1024; case 'g': case 'G': iv *= 1024; case 'm': case 'M': iv *= 1024; case 'k': case 'K': iv *= 1024; case '\0': break; default: return (0); } *data = iv; return (1); } /* * Find the next entry after the one which (cp) falls within, return a * pointer to its start or NULL if there are no more. */ static char * kernenv_next(char *cp) { if (cp != NULL) { while (*cp != 0) cp++; cp++; if (*cp == 0) cp = NULL; } return (cp); } void tunable_int_init(void *data) { struct tunable_int *d = (struct tunable_int *)data; TUNABLE_INT_FETCH(d->path, d->var); } void tunable_long_init(void *data) { struct tunable_long *d = (struct tunable_long *)data; TUNABLE_LONG_FETCH(d->path, d->var); } void tunable_ulong_init(void *data) { struct tunable_ulong *d = (struct tunable_ulong *)data; TUNABLE_ULONG_FETCH(d->path, d->var); } void tunable_int64_init(void *data) { struct tunable_int64 *d = (struct tunable_int64 *)data; TUNABLE_INT64_FETCH(d->path, d->var); } void tunable_uint64_init(void *data) { struct tunable_uint64 *d = (struct tunable_uint64 *)data; TUNABLE_UINT64_FETCH(d->path, d->var); } void tunable_quad_init(void *data) { struct tunable_quad *d = (struct tunable_quad *)data; TUNABLE_QUAD_FETCH(d->path, d->var); } void tunable_str_init(void *data) { struct tunable_str *d = (struct tunable_str *)data; TUNABLE_STR_FETCH(d->path, d->var, d->size); } Index: head/sys/kern/subr_hints.c =================================================================== --- head/sys/kern/subr_hints.c (revision 335994) +++ head/sys/kern/subr_hints.c (revision 335995) @@ -1,479 +1,508 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2000,2001 Peter Wemm * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include #include #include -#define HINTMODE_KENV 0 -#define HINTMODE_STATIC 1 -#define HINTMODE_FALLBACK 2 +#define FBACK_MDENV 0 /* MD env (e.g. loader.conf) */ +#define FBACK_STENV 1 /* Static env */ +#define FBACK_STATIC 2 /* static_hints */ /* - * Access functions for device resources. + * We'll use hintenv_merged to indicate that the dynamic environment has been + * properly prepared for hint usage. This implies that the dynamic environment + * has already been setup (dynamic_kenv) and that we have added any supplied + * static_hints to the dynamic environment. */ +static int hintenv_merged; -static int checkmethod = 1; -static char *hintp; - /* - * Define kern.hintmode sysctl, which only accept value 2, that cause to - * switch from Static KENV mode to Dynamic KENV. So systems that have hints - * compiled into kernel will be able to see/modify KENV (and hints too). + * Access functions for device resources. */ -static int -sysctl_hintmode(SYSCTL_HANDLER_ARGS) +static void +static_hints_to_env(void *data __unused) { const char *cp; char *line, *eq; - int eqidx, error, i, value; + int eqidx, i; - value = hintmode; - - /* Fetch candidate for new hintmode value */ - error = sysctl_handle_int(oidp, &value, 0, req); - if (error || req->newptr == NULL) - return (error); - - if (value != HINTMODE_FALLBACK) - /* Only accept swithing to hintmode 2 */ - return (EINVAL); - - /* - * The rest of the sysctl handler is just making sure that our - * environment is consistent with the world we've already seen. - * If we came from kenv at all, then we have nothing to do: static - * kenv will get merged into dynamic kenv as soon as kmem becomes - * available, dynamic kenv is the environment we'd be setting these - * things in anyways. Therefore, we have nothing left to do unless - * we came from a static hints configuration. - */ - if (hintmode != HINTMODE_STATIC) { - hintmode = value; - return (0); - } - cp = static_hints; while (cp && *cp != '\0') { eq = strchr(cp, '='); if (eq == NULL) /* Bad hint value */ continue; eqidx = eq - cp; i = strlen(cp); - line = malloc(i+1, M_TEMP, M_WAITOK); + line = malloc(i + 1, M_TEMP, M_WAITOK); strcpy(line, cp); - line[eqidx] = '\0'; - kern_setenv(line, line + eqidx + 1); + line[eqidx] = line[i] = '\0'; + /* + * Before adding a hint to the dynamic environment, check if + * another value for said hint has already been added. This is + * needed because static environment overrides static hints and + * dynamic environment overrides all. + */ + if (testenv(line) == 0) + kern_setenv(line, line + eqidx + 1); free(line, M_TEMP); cp += i + 1; } - - hintmode = value; - return (0); + hintenv_merged = 1; } -SYSCTL_PROC(_kern, OID_AUTO, hintmode, CTLTYPE_INT|CTLFLAG_RW, - &hintmode, 0, sysctl_hintmode, "I", "Get/set current hintmode"); +/* Any time after dynamic env is setup */ +SYSINIT(hintenv, SI_SUB_KMEM, SI_ORDER_ANY, static_hints_to_env, NULL); /* + * Checks the environment to see if we even have any hints. If it has no hints, + * then res_find can take the hint that there's no point in searching it and + * either move on to the next environment or fail early. + */ +static bool +_res_checkenv(char *envp) +{ + char *cp; + + cp = envp; + while (cp) { + if (strncmp(cp, "hint.", 5) == 0) + return (true); + while (*cp != '\0') + cp++; + cp++; + if (*cp == '\0') + break; + } + return (false); +} + +/* * Evil wildcarding resource string lookup. * This walks the supplied env string table and returns a match. * The start point can be remembered for incremental searches. */ static int -res_find(int *line, int *startln, +res_find(char **hintp_cookie, int *line, int *startln, const char *name, int *unit, const char *resname, const char *value, const char **ret_name, int *ret_namelen, int *ret_unit, const char **ret_resname, int *ret_resnamelen, const char **ret_value) { - int n = 0, hit, i = 0; + int dyn_used = 0, fbacklvl = FBACK_MDENV, hit, i = 0, n = 0; char r_name[32]; - int r_unit, use_kenv = (hintmode != HINTMODE_STATIC && dynamic_kenv); + int r_unit; char r_resname[32]; char r_value[128]; const char *s, *cp; - char *p; + char *hintp, *p; - if (checkmethod) { - hintp = NULL; - switch (hintmode) { - case HINTMODE_KENV: /* loader hints in environment only */ - break; - case HINTMODE_STATIC: /* static hints only */ - hintp = static_hints; - checkmethod = 0; - break; - case HINTMODE_FALLBACK: /* fallback mode */ - if (dynamic_kenv) { - mtx_lock(&kenv_lock); - cp = kenvp[0]; - for (i = 0; cp != NULL; cp = kenvp[++i]) { - if (!strncmp(cp, "hint.", 5)) { - use_kenv = 1; - checkmethod = 0; - break; - } + /* + * We are expecting that the caller will pass us a hintp_cookie that + * they are tracking. Upon entry, if *hintp_cookie is *not* set, this + * indicates to us that we should be figuring out based on the current + * environment where to search. This keeps us sane throughout the + * entirety of a single search. + */ + if (*hintp_cookie == NULL) { + hintp = NULL; + if (hintenv_merged) { + /* + * static_hints, if it was previously used, has + * already been folded in to the environment + * by this point. + */ + mtx_lock(&kenv_lock); + cp = kenvp[0]; + for (i = 0; cp != NULL; cp = kenvp[++i]) { + if (!strncmp(cp, "hint.", 5)) { + hintp = kenvp[0]; + break; } - mtx_unlock(&kenv_lock); - } else { - cp = kern_envp; - while (cp) { - if (strncmp(cp, "hint.", 5) == 0) { - cp = NULL; - hintp = kern_envp; - break; - } - while (*cp != '\0') - cp++; - cp++; - if (*cp == '\0') { - cp = NULL; - hintp = static_hints; - break; - } - } } - break; - default: - break; - } - if (hintp == NULL) { - if (dynamic_kenv) { - use_kenv = 1; - checkmethod = 0; - } else + mtx_unlock(&kenv_lock); + dyn_used = 1; + } else { + /* + * We'll have a chance to keep coming back here until + * we've actually exhausted all of our possibilities. + * We might have chosen the MD/Static env because it + * had some kind of hints, but perhaps it didn't have + * the hint we are looking for. We don't provide any + * fallback when searching the dynamic environment. + */ +fallback: + if (dyn_used || fbacklvl >= FBACK_STATIC) + return (ENOENT); + + if (fbacklvl <= FBACK_MDENV && + _res_checkenv(md_envp)) { + hintp = md_envp; + goto found; + } + fbacklvl++; + + if (fbacklvl <= FBACK_STENV && + _res_checkenv(kern_envp)) { hintp = kern_envp; + goto found; + } + fbacklvl++; + + /* We'll fallback to static_hints if needed/can */ + if (fbacklvl <= FBACK_STATIC && + _res_checkenv(static_hints)) + hintp = static_hints; +found: + fbacklvl++; } + + if (hintp == NULL) + return (ENOENT); + *hintp_cookie = hintp; + } else { + hintp = *hintp_cookie; + if (hintenv_merged && hintp == kenvp[0]) + dyn_used = 1; + else + /* + * If we aren't using the dynamic environment, we need + * to run through the proper fallback procedure again. + * This is so that we do continuations right if we're + * working with *line and *startln. + */ + goto fallback; } - if (use_kenv) { + if (dyn_used) { mtx_lock(&kenv_lock); i = 0; - cp = kenvp[0]; - if (cp == NULL) { - mtx_unlock(&kenv_lock); - return (ENOENT); - } - } else - cp = hintp; + } + + cp = hintp; while (cp) { hit = 1; (*line)++; if (strncmp(cp, "hint.", 5) != 0) hit = 0; else n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s", r_name, &r_unit, r_resname, r_value); - if (hit && n != 4) { - printf("CONFIG: invalid hint '%s'\n", cp); - p = strchr(cp, 'h'); - *p = 'H'; - hit = 0; + /* We'll circumvent all of the checks if we already know */ + if (hit) { + if (n != 4) { + printf("CONFIG: invalid hint '%s'\n", cp); + p = strchr(cp, 'h'); + *p = 'H'; + hit = 0; + } + if (hit && startln && *startln >= 0 && *line < *startln) + hit = 0; + if (hit && name && strcmp(name, r_name) != 0) + hit = 0; + if (hit && unit && *unit != r_unit) + hit = 0; + if (hit && resname && strcmp(resname, r_resname) != 0) + hit = 0; + if (hit && value && strcmp(value, r_value) != 0) + hit = 0; + if (hit) + break; } - if (hit && startln && *startln >= 0 && *line < *startln) - hit = 0; - if (hit && name && strcmp(name, r_name) != 0) - hit = 0; - if (hit && unit && *unit != r_unit) - hit = 0; - if (hit && resname && strcmp(resname, r_resname) != 0) - hit = 0; - if (hit && value && strcmp(value, r_value) != 0) - hit = 0; - if (hit) - break; - if (use_kenv) { + if (dyn_used) { cp = kenvp[++i]; if (cp == NULL) break; } else { while (*cp != '\0') cp++; cp++; if (*cp == '\0') { cp = NULL; break; } } } - if (use_kenv) + if (dyn_used) mtx_unlock(&kenv_lock); if (cp == NULL) - return ENOENT; + goto fallback; s = cp; /* This is a bit of a hack, but at least is reentrant */ /* Note that it returns some !unterminated! strings. */ s = strchr(s, '.') + 1; /* start of device */ if (ret_name) *ret_name = s; s = strchr(s, '.') + 1; /* start of unit */ if (ret_namelen && ret_name) *ret_namelen = s - *ret_name - 1; /* device length */ if (ret_unit) *ret_unit = r_unit; s = strchr(s, '.') + 1; /* start of resname */ if (ret_resname) *ret_resname = s; s = strchr(s, '=') + 1; /* start of value */ if (ret_resnamelen && ret_resname) *ret_resnamelen = s - *ret_resname - 1; /* value len */ if (ret_value) *ret_value = s; if (startln) /* line number for anchor */ *startln = *line + 1; return 0; } /* * Search all the data sources for matches to our query. We look for * dynamic hints first as overrides for static or fallback hints. */ static int resource_find(int *line, int *startln, const char *name, int *unit, const char *resname, const char *value, const char **ret_name, int *ret_namelen, int *ret_unit, const char **ret_resname, int *ret_resnamelen, const char **ret_value) { int i; int un; + char *hintp; *line = 0; + hintp = NULL; /* Search for exact unit matches first */ - i = res_find(line, startln, name, unit, resname, value, + i = res_find(&hintp, line, startln, name, unit, resname, value, ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, ret_value); if (i == 0) return 0; if (unit == NULL) return ENOENT; /* If we are still here, search for wildcard matches */ un = -1; - i = res_find(line, startln, name, &un, resname, value, + i = res_find(&hintp, line, startln, name, &un, resname, value, ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, ret_value); if (i == 0) return 0; return ENOENT; } int resource_int_value(const char *name, int unit, const char *resname, int *result) { int error; const char *str; char *op; unsigned long val; int line; line = 0; error = resource_find(&line, NULL, name, &unit, resname, NULL, NULL, NULL, NULL, NULL, NULL, &str); if (error) return error; if (*str == '\0') return EFTYPE; val = strtoul(str, &op, 0); if (*op != '\0') return EFTYPE; *result = val; return 0; } int resource_long_value(const char *name, int unit, const char *resname, long *result) { int error; const char *str; char *op; unsigned long val; int line; line = 0; error = resource_find(&line, NULL, name, &unit, resname, NULL, NULL, NULL, NULL, NULL, NULL, &str); if (error) return error; if (*str == '\0') return EFTYPE; val = strtoul(str, &op, 0); if (*op != '\0') return EFTYPE; *result = val; return 0; } int resource_string_value(const char *name, int unit, const char *resname, const char **result) { int error; const char *str; int line; line = 0; error = resource_find(&line, NULL, name, &unit, resname, NULL, NULL, NULL, NULL, NULL, NULL, &str); if (error) return error; *result = str; return 0; } /* * This is a bit nasty, but allows us to not modify the env strings. */ static const char * resource_string_copy(const char *s, int len) { static char stringbuf[256]; static int offset = 0; const char *ret; if (len == 0) len = strlen(s); if (len > 255) return NULL; if ((offset + len + 1) > 255) offset = 0; bcopy(s, &stringbuf[offset], len); stringbuf[offset + len] = '\0'; ret = &stringbuf[offset]; offset += len + 1; return ret; } /* * err = resource_find_match(&anchor, &name, &unit, resname, value) * Iteratively fetch a list of devices wired "at" something * res and value are restrictions. eg: "at", "scbus0". * For practical purposes, res = required, value = optional. * *name and *unit are set. * set *anchor to zero before starting. */ int resource_find_match(int *anchor, const char **name, int *unit, const char *resname, const char *value) { const char *found_name; int found_namelen; int found_unit; int ret; int newln; newln = *anchor; ret = resource_find(anchor, &newln, NULL, NULL, resname, value, &found_name, &found_namelen, &found_unit, NULL, NULL, NULL); if (ret == 0) { *name = resource_string_copy(found_name, found_namelen); *unit = found_unit; } *anchor = newln; return ret; } /* * err = resource_find_dev(&anchor, name, &unit, res, value); * Iterate through a list of devices, returning their unit numbers. * res and value are optional restrictions. eg: "at", "scbus0". * *unit is set to the value. * set *anchor to zero before starting. */ int resource_find_dev(int *anchor, const char *name, int *unit, const char *resname, const char *value) { int found_unit; int newln; int ret; newln = *anchor; ret = resource_find(anchor, &newln, name, NULL, resname, value, NULL, NULL, &found_unit, NULL, NULL, NULL); if (ret == 0) { *unit = found_unit; } *anchor = newln; return ret; } /* * Check to see if a device is disabled via a disabled hint. */ int resource_disabled(const char *name, int unit) { int error, value; error = resource_int_value(name, unit, "disabled", &value); if (error) return (0); return (value); } /* * Clear a value associated with a device by removing it from * the kernel environment. This only removes a hint for an * exact unit. */ int resource_unset_value(const char *name, int unit, const char *resname) { char varname[128]; const char *retname, *retvalue; int error, line; size_t len; line = 0; error = resource_find(&line, NULL, name, &unit, resname, NULL, &retname, NULL, NULL, NULL, NULL, &retvalue); if (error) return (error); retname -= strlen("hint."); len = retvalue - retname - 1; if (len > sizeof(varname) - 1) return (ENAMETOOLONG); memcpy(varname, retname, len); varname[len] = '\0'; return (kern_unsetenv(varname)); } Index: head/sys/kern/tty.c =================================================================== --- head/sys/kern/tty.c (revision 335994) +++ head/sys/kern/tty.c (revision 335995) @@ -1,2348 +1,2355 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_capsicum.h" #include #include #include #include #include #include #include #include #ifdef COMPAT_43TTY #include #endif /* COMPAT_43TTY */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TTYDEFCHARS #include #undef TTYDEFCHARS #include #include #include static MALLOC_DEFINE(M_TTY, "tty", "tty device"); static void tty_rel_free(struct tty *tp); static TAILQ_HEAD(, tty) tty_list = TAILQ_HEAD_INITIALIZER(tty_list); static struct sx tty_list_sx; SX_SYSINIT(tty_list, &tty_list_sx, "tty list"); static unsigned int tty_list_count = 0; /* Character device of /dev/console. */ static struct cdev *dev_console; static const char *dev_console_filename; /* * Flags that are supported and stored by this implementation. */ #define TTYSUP_IFLAG (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|\ INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL) #define TTYSUP_OFLAG (OPOST|ONLCR|TAB3|ONOEOT|OCRNL|ONOCR|ONLRET) #define TTYSUP_LFLAG (ECHOKE|ECHOE|ECHOK|ECHO|ECHONL|ECHOPRT|\ ECHOCTL|ISIG|ICANON|ALTWERASE|IEXTEN|TOSTOP|\ FLUSHO|NOKERNINFO|NOFLSH) #define TTYSUP_CFLAG (CIGNORE|CSIZE|CSTOPB|CREAD|PARENB|PARODD|\ HUPCL|CLOCAL|CCTS_OFLOW|CRTS_IFLOW|CDTR_IFLOW|\ CDSR_OFLOW|CCAR_OFLOW) #define TTY_CALLOUT(tp,d) (dev2unit(d) & TTYUNIT_CALLOUT) static int tty_drainwait = 5 * 60; SYSCTL_INT(_kern, OID_AUTO, tty_drainwait, CTLFLAG_RWTUN, &tty_drainwait, 0, "Default output drain timeout in seconds"); /* * Set TTY buffer sizes. */ #define TTYBUF_MAX 65536 /* * Allocate buffer space if necessary, and set low watermarks, based on speed. * Note that the ttyxxxq_setsize() functions may drop and then reacquire the tty * lock during memory allocation. They will return ENXIO if the tty disappears * while unlocked. */ static int tty_watermarks(struct tty *tp) { size_t bs = 0; int error; /* Provide an input buffer for 2 seconds of data. */ if (tp->t_termios.c_cflag & CREAD) bs = MIN(tp->t_termios.c_ispeed / 5, TTYBUF_MAX); error = ttyinq_setsize(&tp->t_inq, tp, bs); if (error != 0) return (error); /* Set low watermark at 10% (when 90% is available). */ tp->t_inlow = (ttyinq_getallocatedsize(&tp->t_inq) * 9) / 10; /* Provide an output buffer for 2 seconds of data. */ bs = MIN(tp->t_termios.c_ospeed / 5, TTYBUF_MAX); error = ttyoutq_setsize(&tp->t_outq, tp, bs); if (error != 0) return (error); /* Set low watermark at 10% (when 90% is available). */ tp->t_outlow = (ttyoutq_getallocatedsize(&tp->t_outq) * 9) / 10; return (0); } static int tty_drain(struct tty *tp, int leaving) { sbintime_t timeout_at; size_t bytes; int error; if (ttyhook_hashook(tp, getc_inject)) /* buffer is inaccessible */ return (0); /* * For close(), use the recent historic timeout of "1 second without * making progress". For tcdrain(), use t_drainwait as the timeout, * with zero meaning "no timeout" which gives POSIX behavior. */ if (leaving) timeout_at = getsbinuptime() + SBT_1S; else if (tp->t_drainwait != 0) timeout_at = getsbinuptime() + SBT_1S * tp->t_drainwait; else timeout_at = 0; /* * Poll the output buffer and the hardware for completion, at 10 Hz. * Polling is required for devices which are not able to signal an * interrupt when the transmitter becomes idle (most USB serial devs). * The unusual structure of this loop ensures we check for busy one more * time after tty_timedwait() returns EWOULDBLOCK, so that success has * higher priority than timeout if the IO completed in the last 100mS. */ error = 0; bytes = ttyoutq_bytesused(&tp->t_outq); for (;;) { if (ttyoutq_bytesused(&tp->t_outq) == 0 && !ttydevsw_busy(tp)) return (0); if (error != 0) return (error); ttydevsw_outwakeup(tp); error = tty_timedwait(tp, &tp->t_outwait, hz / 10); if (error != 0 && error != EWOULDBLOCK) return (error); else if (timeout_at == 0 || getsbinuptime() < timeout_at) error = 0; else if (leaving && ttyoutq_bytesused(&tp->t_outq) < bytes) { /* In close, making progress, grant an extra second. */ error = 0; timeout_at += SBT_1S; bytes = ttyoutq_bytesused(&tp->t_outq); } } } /* * Though ttydev_enter() and ttydev_leave() seem to be related, they * don't have to be used together. ttydev_enter() is used by the cdev * operations to prevent an actual operation from being processed when * the TTY has been abandoned. ttydev_leave() is used by ttydev_open() * and ttydev_close() to determine whether per-TTY data should be * deallocated. */ static __inline int ttydev_enter(struct tty *tp) { tty_lock(tp); if (tty_gone(tp) || !tty_opened(tp)) { /* Device is already gone. */ tty_unlock(tp); return (ENXIO); } return (0); } static void ttydev_leave(struct tty *tp) { tty_lock_assert(tp, MA_OWNED); if (tty_opened(tp) || tp->t_flags & TF_OPENCLOSE) { /* Device is still opened somewhere. */ tty_unlock(tp); return; } tp->t_flags |= TF_OPENCLOSE; /* Stop asynchronous I/O. */ funsetown(&tp->t_sigio); /* Remove console TTY. */ if (constty == tp) constty_clear(); /* Drain any output. */ if (!tty_gone(tp)) tty_drain(tp, 1); ttydisc_close(tp); /* Free i/o queues now since they might be large. */ ttyinq_free(&tp->t_inq); tp->t_inlow = 0; ttyoutq_free(&tp->t_outq); tp->t_outlow = 0; knlist_clear(&tp->t_inpoll.si_note, 1); knlist_clear(&tp->t_outpoll.si_note, 1); if (!tty_gone(tp)) ttydevsw_close(tp); tp->t_flags &= ~TF_OPENCLOSE; cv_broadcast(&tp->t_dcdwait); tty_rel_free(tp); } /* * Operations that are exposed through the character device in /dev. */ static int ttydev_open(struct cdev *dev, int oflags, int devtype __unused, struct thread *td) { struct tty *tp; - int error; + int cflags, error; tp = dev->si_drv1; error = 0; tty_lock(tp); if (tty_gone(tp)) { /* Device is already gone. */ tty_unlock(tp); return (ENXIO); } /* * Block when other processes are currently opening or closing * the TTY. */ while (tp->t_flags & TF_OPENCLOSE) { error = tty_wait(tp, &tp->t_dcdwait); if (error != 0) { tty_unlock(tp); return (error); } } tp->t_flags |= TF_OPENCLOSE; /* * Make sure the "tty" and "cua" device cannot be opened at the * same time. The console is a "tty" device. */ if (TTY_CALLOUT(tp, dev)) { if (tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) { error = EBUSY; goto done; } } else { if (tp->t_flags & TF_OPENED_OUT) { error = EBUSY; goto done; } } if (tp->t_flags & TF_EXCLUDE && priv_check(td, PRIV_TTY_EXCLUSIVE)) { error = EBUSY; goto done; } if (!tty_opened(tp)) { /* Set proper termios flags. */ if (TTY_CALLOUT(tp, dev)) tp->t_termios = tp->t_termios_init_out; else tp->t_termios = tp->t_termios_init_in; ttydevsw_param(tp, &tp->t_termios); /* Prevent modem control on callout devices and /dev/console. */ if (TTY_CALLOUT(tp, dev) || dev == dev_console) tp->t_termios.c_cflag |= CLOCAL; - ttydevsw_modem(tp, SER_DTR|SER_RTS, 0); + cflags = 0; + if (tp->t_termios.c_cflag & CDTR_IFLOW) + cflags |= SER_DTR; + if (tp->t_termios.c_cflag & CRTS_IFLOW) + cflags |= SER_RTS; + + if (cflags != 0) + ttydevsw_modem(tp, cflags, 0); error = ttydevsw_open(tp); if (error != 0) goto done; ttydisc_open(tp); error = tty_watermarks(tp); if (error != 0) goto done; } /* Wait for Carrier Detect. */ if ((oflags & O_NONBLOCK) == 0 && (tp->t_termios.c_cflag & CLOCAL) == 0) { while ((ttydevsw_modem(tp, 0, 0) & SER_DCD) == 0) { error = tty_wait(tp, &tp->t_dcdwait); if (error != 0) goto done; } } if (dev == dev_console) tp->t_flags |= TF_OPENED_CONS; else if (TTY_CALLOUT(tp, dev)) tp->t_flags |= TF_OPENED_OUT; else tp->t_flags |= TF_OPENED_IN; MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || (tp->t_flags & TF_OPENED_OUT) == 0); done: tp->t_flags &= ~TF_OPENCLOSE; cv_broadcast(&tp->t_dcdwait); ttydev_leave(tp); return (error); } static int ttydev_close(struct cdev *dev, int fflag, int devtype __unused, struct thread *td __unused) { struct tty *tp = dev->si_drv1; tty_lock(tp); /* * Don't actually close the device if it is being used as the * console. */ MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || (tp->t_flags & TF_OPENED_OUT) == 0); if (dev == dev_console) tp->t_flags &= ~TF_OPENED_CONS; else tp->t_flags &= ~(TF_OPENED_IN|TF_OPENED_OUT); if (tp->t_flags & TF_OPENED) { tty_unlock(tp); return (0); } /* If revoking, flush output now to avoid draining it later. */ if (fflag & FREVOKE) tty_flush(tp, FWRITE); tp->t_flags &= ~TF_EXCLUDE; /* Properly wake up threads that are stuck - revoke(). */ tp->t_revokecnt++; tty_wakeup(tp, FREAD|FWRITE); cv_broadcast(&tp->t_bgwait); cv_broadcast(&tp->t_dcdwait); ttydev_leave(tp); return (0); } static __inline int tty_is_ctty(struct tty *tp, struct proc *p) { tty_lock_assert(tp, MA_OWNED); return (p->p_session == tp->t_session && p->p_flag & P_CONTROLT); } int tty_wait_background(struct tty *tp, struct thread *td, int sig) { struct proc *p = td->td_proc; struct pgrp *pg; ksiginfo_t ksi; int error; MPASS(sig == SIGTTIN || sig == SIGTTOU); tty_lock_assert(tp, MA_OWNED); for (;;) { PROC_LOCK(p); /* * The process should only sleep, when: * - This terminal is the controlling terminal * - Its process group is not the foreground process * group * - The parent process isn't waiting for the child to * exit * - the signal to send to the process isn't masked */ if (!tty_is_ctty(tp, p) || p->p_pgrp == tp->t_pgrp) { /* Allow the action to happen. */ PROC_UNLOCK(p); return (0); } if (SIGISMEMBER(p->p_sigacts->ps_sigignore, sig) || SIGISMEMBER(td->td_sigmask, sig)) { /* Only allow them in write()/ioctl(). */ PROC_UNLOCK(p); return (sig == SIGTTOU ? 0 : EIO); } pg = p->p_pgrp; if (p->p_flag & P_PPWAIT || pg->pg_jobc == 0) { /* Don't allow the action to happen. */ PROC_UNLOCK(p); return (EIO); } PROC_UNLOCK(p); /* * Send the signal and sleep until we're the new * foreground process group. */ if (sig != 0) { ksiginfo_init(&ksi); ksi.ksi_code = SI_KERNEL; ksi.ksi_signo = sig; sig = 0; } PGRP_LOCK(pg); pgsignal(pg, ksi.ksi_signo, 1, &ksi); PGRP_UNLOCK(pg); error = tty_wait(tp, &tp->t_bgwait); if (error) return (error); } } static int ttydev_read(struct cdev *dev, struct uio *uio, int ioflag) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) goto done; error = ttydisc_read(tp, uio, ioflag); tty_unlock(tp); /* * The read() call should not throw an error when the device is * being destroyed. Silently convert it to an EOF. */ done: if (error == ENXIO) error = 0; return (error); } static int ttydev_write(struct cdev *dev, struct uio *uio, int ioflag) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); if (tp->t_termios.c_lflag & TOSTOP) { error = tty_wait_background(tp, curthread, SIGTTOU); if (error) goto done; } if (ioflag & IO_NDELAY && tp->t_flags & TF_BUSY_OUT) { /* Allow non-blocking writes to bypass serialization. */ error = ttydisc_write(tp, uio, ioflag); } else { /* Serialize write() calls. */ while (tp->t_flags & TF_BUSY_OUT) { error = tty_wait(tp, &tp->t_outserwait); if (error) goto done; } tp->t_flags |= TF_BUSY_OUT; error = ttydisc_write(tp, uio, ioflag); tp->t_flags &= ~TF_BUSY_OUT; cv_signal(&tp->t_outserwait); } done: tty_unlock(tp); return (error); } static int ttydev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); switch (cmd) { case TIOCCBRK: case TIOCCONS: case TIOCDRAIN: case TIOCEXCL: case TIOCFLUSH: case TIOCNXCL: case TIOCSBRK: case TIOCSCTTY: case TIOCSETA: case TIOCSETAF: case TIOCSETAW: case TIOCSPGRP: case TIOCSTART: case TIOCSTAT: case TIOCSTI: case TIOCSTOP: case TIOCSWINSZ: #if 0 case TIOCSDRAINWAIT: case TIOCSETD: #endif #ifdef COMPAT_43TTY case TIOCLBIC: case TIOCLBIS: case TIOCLSET: case TIOCSETC: case OTIOCSETD: case TIOCSETN: case TIOCSETP: case TIOCSLTC: #endif /* COMPAT_43TTY */ /* * If the ioctl() causes the TTY to be modified, let it * wait in the background. */ error = tty_wait_background(tp, curthread, SIGTTOU); if (error) goto done; } if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { struct termios *old = &tp->t_termios; struct termios *new = (struct termios *)data; struct termios *lock = TTY_CALLOUT(tp, dev) ? &tp->t_termios_lock_out : &tp->t_termios_lock_in; int cc; /* * Lock state devices. Just overwrite the values of the * commands that are currently in use. */ new->c_iflag = (old->c_iflag & lock->c_iflag) | (new->c_iflag & ~lock->c_iflag); new->c_oflag = (old->c_oflag & lock->c_oflag) | (new->c_oflag & ~lock->c_oflag); new->c_cflag = (old->c_cflag & lock->c_cflag) | (new->c_cflag & ~lock->c_cflag); new->c_lflag = (old->c_lflag & lock->c_lflag) | (new->c_lflag & ~lock->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lock->c_cc[cc]) new->c_cc[cc] = old->c_cc[cc]; if (lock->c_ispeed) new->c_ispeed = old->c_ispeed; if (lock->c_ospeed) new->c_ospeed = old->c_ospeed; } error = tty_ioctl(tp, cmd, data, fflag, td); done: tty_unlock(tp); return (error); } static int ttydev_poll(struct cdev *dev, int events, struct thread *td) { struct tty *tp = dev->si_drv1; int error, revents = 0; error = ttydev_enter(tp); if (error) return ((events & (POLLIN|POLLRDNORM)) | POLLHUP); if (events & (POLLIN|POLLRDNORM)) { /* See if we can read something. */ if (ttydisc_read_poll(tp) > 0) revents |= events & (POLLIN|POLLRDNORM); } if (tp->t_flags & TF_ZOMBIE) { /* Hangup flag on zombie state. */ revents |= POLLHUP; } else if (events & (POLLOUT|POLLWRNORM)) { /* See if we can write something. */ if (ttydisc_write_poll(tp) > 0) revents |= events & (POLLOUT|POLLWRNORM); } if (revents == 0) { if (events & (POLLIN|POLLRDNORM)) selrecord(td, &tp->t_inpoll); if (events & (POLLOUT|POLLWRNORM)) selrecord(td, &tp->t_outpoll); } tty_unlock(tp); return (revents); } static int ttydev_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { struct tty *tp = dev->si_drv1; int error; /* Handle mmap() through the driver. */ error = ttydev_enter(tp); if (error) return (-1); error = ttydevsw_mmap(tp, offset, paddr, nprot, memattr); tty_unlock(tp); return (error); } /* * kqueue support. */ static void tty_kqops_read_detach(struct knote *kn) { struct tty *tp = kn->kn_hook; knlist_remove(&tp->t_inpoll.si_note, kn, 0); } static int tty_kqops_read_event(struct knote *kn, long hint __unused) { struct tty *tp = kn->kn_hook; tty_lock_assert(tp, MA_OWNED); if (tty_gone(tp) || tp->t_flags & TF_ZOMBIE) { kn->kn_flags |= EV_EOF; return (1); } else { kn->kn_data = ttydisc_read_poll(tp); return (kn->kn_data > 0); } } static void tty_kqops_write_detach(struct knote *kn) { struct tty *tp = kn->kn_hook; knlist_remove(&tp->t_outpoll.si_note, kn, 0); } static int tty_kqops_write_event(struct knote *kn, long hint __unused) { struct tty *tp = kn->kn_hook; tty_lock_assert(tp, MA_OWNED); if (tty_gone(tp)) { kn->kn_flags |= EV_EOF; return (1); } else { kn->kn_data = ttydisc_write_poll(tp); return (kn->kn_data > 0); } } static struct filterops tty_kqops_read = { .f_isfd = 1, .f_detach = tty_kqops_read_detach, .f_event = tty_kqops_read_event, }; static struct filterops tty_kqops_write = { .f_isfd = 1, .f_detach = tty_kqops_write_detach, .f_event = tty_kqops_write_event, }; static int ttydev_kqfilter(struct cdev *dev, struct knote *kn) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); switch (kn->kn_filter) { case EVFILT_READ: kn->kn_hook = tp; kn->kn_fop = &tty_kqops_read; knlist_add(&tp->t_inpoll.si_note, kn, 1); break; case EVFILT_WRITE: kn->kn_hook = tp; kn->kn_fop = &tty_kqops_write; knlist_add(&tp->t_outpoll.si_note, kn, 1); break; default: error = EINVAL; break; } tty_unlock(tp); return (error); } static struct cdevsw ttydev_cdevsw = { .d_version = D_VERSION, .d_open = ttydev_open, .d_close = ttydev_close, .d_read = ttydev_read, .d_write = ttydev_write, .d_ioctl = ttydev_ioctl, .d_kqfilter = ttydev_kqfilter, .d_poll = ttydev_poll, .d_mmap = ttydev_mmap, .d_name = "ttydev", .d_flags = D_TTY, }; /* * Init/lock-state devices */ static int ttyil_open(struct cdev *dev, int oflags __unused, int devtype __unused, struct thread *td) { struct tty *tp; int error; tp = dev->si_drv1; error = 0; tty_lock(tp); if (tty_gone(tp)) error = ENODEV; tty_unlock(tp); return (error); } static int ttyil_close(struct cdev *dev __unused, int flag __unused, int mode __unused, struct thread *td __unused) { return (0); } static int ttyil_rdwr(struct cdev *dev __unused, struct uio *uio __unused, int ioflag __unused) { return (ENODEV); } static int ttyil_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct tty *tp = dev->si_drv1; int error; tty_lock(tp); if (tty_gone(tp)) { error = ENODEV; goto done; } error = ttydevsw_cioctl(tp, dev2unit(dev), cmd, data, td); if (error != ENOIOCTL) goto done; error = 0; switch (cmd) { case TIOCGETA: /* Obtain terminal flags through tcgetattr(). */ *(struct termios*)data = *(struct termios*)dev->si_drv2; break; case TIOCSETA: /* Set terminal flags through tcsetattr(). */ error = priv_check(td, PRIV_TTY_SETA); if (error) break; *(struct termios*)dev->si_drv2 = *(struct termios*)data; break; case TIOCGETD: *(int *)data = TTYDISC; break; case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); break; default: error = ENOTTY; } done: tty_unlock(tp); return (error); } static struct cdevsw ttyil_cdevsw = { .d_version = D_VERSION, .d_open = ttyil_open, .d_close = ttyil_close, .d_read = ttyil_rdwr, .d_write = ttyil_rdwr, .d_ioctl = ttyil_ioctl, .d_name = "ttyil", .d_flags = D_TTY, }; static void tty_init_termios(struct tty *tp) { struct termios *t = &tp->t_termios_init_in; t->c_cflag = TTYDEF_CFLAG; t->c_iflag = TTYDEF_IFLAG; t->c_lflag = TTYDEF_LFLAG; t->c_oflag = TTYDEF_OFLAG; t->c_ispeed = TTYDEF_SPEED; t->c_ospeed = TTYDEF_SPEED; memcpy(&t->c_cc, ttydefchars, sizeof ttydefchars); tp->t_termios_init_out = *t; } void tty_init_console(struct tty *tp, speed_t s) { struct termios *ti = &tp->t_termios_init_in; struct termios *to = &tp->t_termios_init_out; if (s != 0) { ti->c_ispeed = ti->c_ospeed = s; to->c_ispeed = to->c_ospeed = s; } ti->c_cflag |= CLOCAL; to->c_cflag |= CLOCAL; } /* * Standard device routine implementations, mostly meant for * pseudo-terminal device drivers. When a driver creates a new terminal * device class, missing routines are patched. */ static int ttydevsw_defopen(struct tty *tp __unused) { return (0); } static void ttydevsw_defclose(struct tty *tp __unused) { } static void ttydevsw_defoutwakeup(struct tty *tp __unused) { panic("Terminal device has output, while not implemented"); } static void ttydevsw_definwakeup(struct tty *tp __unused) { } static int ttydevsw_defioctl(struct tty *tp __unused, u_long cmd __unused, caddr_t data __unused, struct thread *td __unused) { return (ENOIOCTL); } static int ttydevsw_defcioctl(struct tty *tp __unused, int unit __unused, u_long cmd __unused, caddr_t data __unused, struct thread *td __unused) { return (ENOIOCTL); } static int ttydevsw_defparam(struct tty *tp __unused, struct termios *t) { /* * Allow the baud rate to be adjusted for pseudo-devices, but at * least restrict it to 115200 to prevent excessive buffer * usage. Also disallow 0, to prevent foot shooting. */ if (t->c_ispeed < B50) t->c_ispeed = B50; else if (t->c_ispeed > B115200) t->c_ispeed = B115200; if (t->c_ospeed < B50) t->c_ospeed = B50; else if (t->c_ospeed > B115200) t->c_ospeed = B115200; t->c_cflag |= CREAD; return (0); } static int ttydevsw_defmodem(struct tty *tp __unused, int sigon __unused, int sigoff __unused) { /* Simulate a carrier to make the TTY layer happy. */ return (SER_DCD); } static int ttydevsw_defmmap(struct tty *tp __unused, vm_ooffset_t offset __unused, vm_paddr_t *paddr __unused, int nprot __unused, vm_memattr_t *memattr __unused) { return (-1); } static void ttydevsw_defpktnotify(struct tty *tp __unused, char event __unused) { } static void ttydevsw_deffree(void *softc __unused) { panic("Terminal device freed without a free-handler"); } static bool ttydevsw_defbusy(struct tty *tp __unused) { return (FALSE); } /* * TTY allocation and deallocation. TTY devices can be deallocated when * the driver doesn't use it anymore, when the TTY isn't a session's * controlling TTY and when the device node isn't opened through devfs. */ struct tty * tty_alloc(struct ttydevsw *tsw, void *sc) { return (tty_alloc_mutex(tsw, sc, NULL)); } struct tty * tty_alloc_mutex(struct ttydevsw *tsw, void *sc, struct mtx *mutex) { struct tty *tp; /* Make sure the driver defines all routines. */ #define PATCH_FUNC(x) do { \ if (tsw->tsw_ ## x == NULL) \ tsw->tsw_ ## x = ttydevsw_def ## x; \ } while (0) PATCH_FUNC(open); PATCH_FUNC(close); PATCH_FUNC(outwakeup); PATCH_FUNC(inwakeup); PATCH_FUNC(ioctl); PATCH_FUNC(cioctl); PATCH_FUNC(param); PATCH_FUNC(modem); PATCH_FUNC(mmap); PATCH_FUNC(pktnotify); PATCH_FUNC(free); PATCH_FUNC(busy); #undef PATCH_FUNC tp = malloc(sizeof(struct tty), M_TTY, M_WAITOK|M_ZERO); tp->t_devsw = tsw; tp->t_devswsoftc = sc; tp->t_flags = tsw->tsw_flags; tp->t_drainwait = tty_drainwait; tty_init_termios(tp); cv_init(&tp->t_inwait, "ttyin"); cv_init(&tp->t_outwait, "ttyout"); cv_init(&tp->t_outserwait, "ttyosr"); cv_init(&tp->t_bgwait, "ttybg"); cv_init(&tp->t_dcdwait, "ttydcd"); /* Allow drivers to use a custom mutex to lock the TTY. */ if (mutex != NULL) { tp->t_mtx = mutex; } else { tp->t_mtx = &tp->t_mtxobj; mtx_init(&tp->t_mtxobj, "ttymtx", NULL, MTX_DEF); } knlist_init_mtx(&tp->t_inpoll.si_note, tp->t_mtx); knlist_init_mtx(&tp->t_outpoll.si_note, tp->t_mtx); return (tp); } static void tty_dealloc(void *arg) { struct tty *tp = arg; /* * ttyydev_leave() usually frees the i/o queues earlier, but it is * not always called between queue allocation and here. The queues * may be allocated by ioctls on a pty control device without the * corresponding pty slave device ever being open, or after it is * closed. */ ttyinq_free(&tp->t_inq); ttyoutq_free(&tp->t_outq); seldrain(&tp->t_inpoll); seldrain(&tp->t_outpoll); knlist_destroy(&tp->t_inpoll.si_note); knlist_destroy(&tp->t_outpoll.si_note); cv_destroy(&tp->t_inwait); cv_destroy(&tp->t_outwait); cv_destroy(&tp->t_bgwait); cv_destroy(&tp->t_dcdwait); cv_destroy(&tp->t_outserwait); if (tp->t_mtx == &tp->t_mtxobj) mtx_destroy(&tp->t_mtxobj); ttydevsw_free(tp); free(tp, M_TTY); } static void tty_rel_free(struct tty *tp) { struct cdev *dev; tty_lock_assert(tp, MA_OWNED); #define TF_ACTIVITY (TF_GONE|TF_OPENED|TF_HOOK|TF_OPENCLOSE) if (tp->t_sessioncnt != 0 || (tp->t_flags & TF_ACTIVITY) != TF_GONE) { /* TTY is still in use. */ tty_unlock(tp); return; } /* TTY can be deallocated. */ dev = tp->t_dev; tp->t_dev = NULL; tty_unlock(tp); if (dev != NULL) { sx_xlock(&tty_list_sx); TAILQ_REMOVE(&tty_list, tp, t_list); tty_list_count--; sx_xunlock(&tty_list_sx); destroy_dev_sched_cb(dev, tty_dealloc, tp); } } void tty_rel_pgrp(struct tty *tp, struct pgrp *pg) { MPASS(tp->t_sessioncnt > 0); tty_lock_assert(tp, MA_OWNED); if (tp->t_pgrp == pg) tp->t_pgrp = NULL; tty_unlock(tp); } void tty_rel_sess(struct tty *tp, struct session *sess) { MPASS(tp->t_sessioncnt > 0); /* Current session has left. */ if (tp->t_session == sess) { tp->t_session = NULL; MPASS(tp->t_pgrp == NULL); } tp->t_sessioncnt--; tty_rel_free(tp); } void tty_rel_gone(struct tty *tp) { MPASS(!tty_gone(tp)); /* Simulate carrier removal. */ ttydisc_modem(tp, 0); /* Wake up all blocked threads. */ tty_wakeup(tp, FREAD|FWRITE); cv_broadcast(&tp->t_bgwait); cv_broadcast(&tp->t_dcdwait); tp->t_flags |= TF_GONE; tty_rel_free(tp); } /* * Exposing information about current TTY's through sysctl */ static void tty_to_xtty(struct tty *tp, struct xtty *xt) { tty_lock_assert(tp, MA_OWNED); xt->xt_size = sizeof(struct xtty); xt->xt_insize = ttyinq_getsize(&tp->t_inq); xt->xt_incc = ttyinq_bytescanonicalized(&tp->t_inq); xt->xt_inlc = ttyinq_bytesline(&tp->t_inq); xt->xt_inlow = tp->t_inlow; xt->xt_outsize = ttyoutq_getsize(&tp->t_outq); xt->xt_outcc = ttyoutq_bytesused(&tp->t_outq); xt->xt_outlow = tp->t_outlow; xt->xt_column = tp->t_column; xt->xt_pgid = tp->t_pgrp ? tp->t_pgrp->pg_id : 0; xt->xt_sid = tp->t_session ? tp->t_session->s_sid : 0; xt->xt_flags = tp->t_flags; xt->xt_dev = tp->t_dev ? dev2udev(tp->t_dev) : (uint32_t)NODEV; } static int sysctl_kern_ttys(SYSCTL_HANDLER_ARGS) { unsigned long lsize; struct xtty *xtlist, *xt; struct tty *tp; int error; sx_slock(&tty_list_sx); lsize = tty_list_count * sizeof(struct xtty); if (lsize == 0) { sx_sunlock(&tty_list_sx); return (0); } xtlist = xt = malloc(lsize, M_TTY, M_WAITOK); TAILQ_FOREACH(tp, &tty_list, t_list) { tty_lock(tp); tty_to_xtty(tp, xt); tty_unlock(tp); xt++; } sx_sunlock(&tty_list_sx); error = SYSCTL_OUT(req, xtlist, lsize); free(xtlist, M_TTY); return (error); } SYSCTL_PROC(_kern, OID_AUTO, ttys, CTLTYPE_OPAQUE|CTLFLAG_RD|CTLFLAG_MPSAFE, 0, 0, sysctl_kern_ttys, "S,xtty", "List of TTYs"); /* * Device node creation. Device has been set up, now we can expose it to * the user. */ int tty_makedevf(struct tty *tp, struct ucred *cred, int flags, const char *fmt, ...) { va_list ap; struct make_dev_args args; struct cdev *dev, *init, *lock, *cua, *cinit, *clock; const char *prefix = "tty"; char name[SPECNAMELEN - 3]; /* for "tty" and "cua". */ uid_t uid; gid_t gid; mode_t mode; int error; /* Remove "tty" prefix from devices like PTY's. */ if (tp->t_flags & TF_NOPREFIX) prefix = ""; va_start(ap, fmt); vsnrprintf(name, sizeof name, 32, fmt, ap); va_end(ap); if (cred == NULL) { /* System device. */ uid = UID_ROOT; gid = GID_WHEEL; mode = S_IRUSR|S_IWUSR; } else { /* User device. */ uid = cred->cr_ruid; gid = GID_TTY; mode = S_IRUSR|S_IWUSR|S_IWGRP; } flags = flags & TTYMK_CLONING ? MAKEDEV_REF : 0; flags |= MAKEDEV_CHECKNAME; /* Master call-in device. */ make_dev_args_init(&args); args.mda_flags = flags; args.mda_devsw = &ttydev_cdevsw; args.mda_cr = cred; args.mda_uid = uid; args.mda_gid = gid; args.mda_mode = mode; args.mda_si_drv1 = tp; error = make_dev_s(&args, &dev, "%s%s", prefix, name); if (error != 0) return (error); tp->t_dev = dev; init = lock = cua = cinit = clock = NULL; /* Slave call-in devices. */ if (tp->t_flags & TF_INITLOCK) { args.mda_devsw = &ttyil_cdevsw; args.mda_unit = TTYUNIT_INIT; args.mda_si_drv1 = tp; args.mda_si_drv2 = &tp->t_termios_init_in; error = make_dev_s(&args, &init, "%s%s.init", prefix, name); if (error != 0) goto fail; dev_depends(dev, init); args.mda_unit = TTYUNIT_LOCK; args.mda_si_drv2 = &tp->t_termios_lock_in; error = make_dev_s(&args, &lock, "%s%s.lock", prefix, name); if (error != 0) goto fail; dev_depends(dev, lock); } /* Call-out devices. */ if (tp->t_flags & TF_CALLOUT) { make_dev_args_init(&args); args.mda_flags = flags; args.mda_devsw = &ttydev_cdevsw; args.mda_cr = cred; args.mda_uid = UID_UUCP; args.mda_gid = GID_DIALER; args.mda_mode = 0660; args.mda_unit = TTYUNIT_CALLOUT; args.mda_si_drv1 = tp; error = make_dev_s(&args, &cua, "cua%s", name); if (error != 0) goto fail; dev_depends(dev, cua); /* Slave call-out devices. */ if (tp->t_flags & TF_INITLOCK) { args.mda_devsw = &ttyil_cdevsw; args.mda_unit = TTYUNIT_CALLOUT | TTYUNIT_INIT; args.mda_si_drv2 = &tp->t_termios_init_out; error = make_dev_s(&args, &cinit, "cua%s.init", name); if (error != 0) goto fail; dev_depends(dev, cinit); args.mda_unit = TTYUNIT_CALLOUT | TTYUNIT_LOCK; args.mda_si_drv2 = &tp->t_termios_lock_out; error = make_dev_s(&args, &clock, "cua%s.lock", name); if (error != 0) goto fail; dev_depends(dev, clock); } } sx_xlock(&tty_list_sx); TAILQ_INSERT_TAIL(&tty_list, tp, t_list); tty_list_count++; sx_xunlock(&tty_list_sx); return (0); fail: destroy_dev(dev); if (init) destroy_dev(init); if (lock) destroy_dev(lock); if (cinit) destroy_dev(cinit); if (clock) destroy_dev(clock); return (error); } /* * Signalling processes. */ void tty_signal_sessleader(struct tty *tp, int sig) { struct proc *p; tty_lock_assert(tp, MA_OWNED); MPASS(sig >= 1 && sig < NSIG); /* Make signals start output again. */ tp->t_flags &= ~TF_STOPPED; if (tp->t_session != NULL && tp->t_session->s_leader != NULL) { p = tp->t_session->s_leader; PROC_LOCK(p); kern_psignal(p, sig); PROC_UNLOCK(p); } } void tty_signal_pgrp(struct tty *tp, int sig) { ksiginfo_t ksi; tty_lock_assert(tp, MA_OWNED); MPASS(sig >= 1 && sig < NSIG); /* Make signals start output again. */ tp->t_flags &= ~TF_STOPPED; if (sig == SIGINFO && !(tp->t_termios.c_lflag & NOKERNINFO)) tty_info(tp); if (tp->t_pgrp != NULL) { ksiginfo_init(&ksi); ksi.ksi_signo = sig; ksi.ksi_code = SI_KERNEL; PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, sig, 1, &ksi); PGRP_UNLOCK(tp->t_pgrp); } } void tty_wakeup(struct tty *tp, int flags) { if (tp->t_flags & TF_ASYNC && tp->t_sigio != NULL) pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); if (flags & FWRITE) { cv_broadcast(&tp->t_outwait); selwakeup(&tp->t_outpoll); KNOTE_LOCKED(&tp->t_outpoll.si_note, 0); } if (flags & FREAD) { cv_broadcast(&tp->t_inwait); selwakeup(&tp->t_inpoll); KNOTE_LOCKED(&tp->t_inpoll.si_note, 0); } } int tty_wait(struct tty *tp, struct cv *cv) { int error; int revokecnt = tp->t_revokecnt; tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); MPASS(!tty_gone(tp)); error = cv_wait_sig(cv, tp->t_mtx); /* Bail out when the device slipped away. */ if (tty_gone(tp)) return (ENXIO); /* Restart the system call when we may have been revoked. */ if (tp->t_revokecnt != revokecnt) return (ERESTART); return (error); } int tty_timedwait(struct tty *tp, struct cv *cv, int hz) { int error; int revokecnt = tp->t_revokecnt; tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); MPASS(!tty_gone(tp)); error = cv_timedwait_sig(cv, tp->t_mtx, hz); /* Bail out when the device slipped away. */ if (tty_gone(tp)) return (ENXIO); /* Restart the system call when we may have been revoked. */ if (tp->t_revokecnt != revokecnt) return (ERESTART); return (error); } void tty_flush(struct tty *tp, int flags) { if (flags & FWRITE) { tp->t_flags &= ~TF_HIWAT_OUT; ttyoutq_flush(&tp->t_outq); tty_wakeup(tp, FWRITE); if (!tty_gone(tp)) { ttydevsw_outwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_FLUSHWRITE); } } if (flags & FREAD) { tty_hiwat_in_unblock(tp); ttyinq_flush(&tp->t_inq); tty_wakeup(tp, FREAD); if (!tty_gone(tp)) { ttydevsw_inwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_FLUSHREAD); } } } void tty_set_winsize(struct tty *tp, const struct winsize *wsz) { if (memcmp(&tp->t_winsize, wsz, sizeof(*wsz)) == 0) return; tp->t_winsize = *wsz; tty_signal_pgrp(tp, SIGWINCH); } static int tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, struct thread *td) { int error; switch (cmd) { /* * Modem commands. * The SER_* and TIOCM_* flags are the same, but one bit * shifted. I don't know why. */ case TIOCSDTR: ttydevsw_modem(tp, SER_DTR, 0); return (0); case TIOCCDTR: ttydevsw_modem(tp, 0, SER_DTR); return (0); case TIOCMSET: { int bits = *(int *)data; ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, ((~bits) & (TIOCM_DTR | TIOCM_RTS)) >> 1); return (0); } case TIOCMBIS: { int bits = *(int *)data; ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, 0); return (0); } case TIOCMBIC: { int bits = *(int *)data; ttydevsw_modem(tp, 0, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1); return (0); } case TIOCMGET: *(int *)data = TIOCM_LE + (ttydevsw_modem(tp, 0, 0) << 1); return (0); case FIOASYNC: if (*(int *)data) tp->t_flags |= TF_ASYNC; else tp->t_flags &= ~TF_ASYNC; return (0); case FIONBIO: /* This device supports non-blocking operation. */ return (0); case FIONREAD: *(int *)data = ttyinq_bytescanonicalized(&tp->t_inq); return (0); case FIONWRITE: case TIOCOUTQ: *(int *)data = ttyoutq_bytesused(&tp->t_outq); return (0); case FIOSETOWN: if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) /* Not allowed to set ownership. */ return (ENOTTY); /* Temporarily unlock the TTY to set ownership. */ tty_unlock(tp); error = fsetown(*(int *)data, &tp->t_sigio); tty_lock(tp); return (error); case FIOGETOWN: if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) /* Not allowed to set ownership. */ return (ENOTTY); /* Get ownership. */ *(int *)data = fgetown(&tp->t_sigio); return (0); case TIOCGETA: /* Obtain terminal flags through tcgetattr(). */ *(struct termios*)data = tp->t_termios; return (0); case TIOCSETA: case TIOCSETAW: case TIOCSETAF: { struct termios *t = data; /* * Who makes up these funny rules? According to POSIX, * input baud rate is set equal to the output baud rate * when zero. */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* Discard any unsupported bits. */ t->c_iflag &= TTYSUP_IFLAG; t->c_oflag &= TTYSUP_OFLAG; t->c_lflag &= TTYSUP_LFLAG; t->c_cflag &= TTYSUP_CFLAG; /* Set terminal flags through tcsetattr(). */ if (cmd == TIOCSETAW || cmd == TIOCSETAF) { error = tty_drain(tp, 0); if (error) return (error); if (cmd == TIOCSETAF) tty_flush(tp, FREAD); } /* * Only call param() when the flags really change. */ if ((t->c_cflag & CIGNORE) == 0 && (tp->t_termios.c_cflag != t->c_cflag || ((tp->t_termios.c_iflag ^ t->c_iflag) & (IXON|IXOFF|IXANY)) || tp->t_termios.c_ispeed != t->c_ispeed || tp->t_termios.c_ospeed != t->c_ospeed)) { error = ttydevsw_param(tp, t); if (error) return (error); /* XXX: CLOCAL? */ tp->t_termios.c_cflag = t->c_cflag & ~CIGNORE; tp->t_termios.c_ispeed = t->c_ispeed; tp->t_termios.c_ospeed = t->c_ospeed; /* Baud rate has changed - update watermarks. */ error = tty_watermarks(tp); if (error) return (error); } /* Copy new non-device driver parameters. */ tp->t_termios.c_iflag = t->c_iflag; tp->t_termios.c_oflag = t->c_oflag; tp->t_termios.c_lflag = t->c_lflag; memcpy(&tp->t_termios.c_cc, t->c_cc, sizeof t->c_cc); ttydisc_optimize(tp); if ((t->c_lflag & ICANON) == 0) { /* * When in non-canonical mode, wake up all * readers. Canonicalize any partial input. VMIN * and VTIME could also be adjusted. */ ttyinq_canonicalize(&tp->t_inq); tty_wakeup(tp, FREAD); } /* * For packet mode: notify the PTY consumer that VSTOP * and VSTART may have been changed. */ if (tp->t_termios.c_iflag & IXON && tp->t_termios.c_cc[VSTOP] == CTRL('S') && tp->t_termios.c_cc[VSTART] == CTRL('Q')) ttydevsw_pktnotify(tp, TIOCPKT_DOSTOP); else ttydevsw_pktnotify(tp, TIOCPKT_NOSTOP); return (0); } case TIOCGETD: /* For compatibility - we only support TTYDISC. */ *(int *)data = TTYDISC; return (0); case TIOCGPGRP: if (!tty_is_ctty(tp, td->td_proc)) return (ENOTTY); if (tp->t_pgrp != NULL) *(int *)data = tp->t_pgrp->pg_id; else *(int *)data = NO_PID; return (0); case TIOCGSID: if (!tty_is_ctty(tp, td->td_proc)) return (ENOTTY); MPASS(tp->t_session); *(int *)data = tp->t_session->s_sid; return (0); case TIOCSCTTY: { struct proc *p = td->td_proc; /* XXX: This looks awful. */ tty_unlock(tp); sx_xlock(&proctree_lock); tty_lock(tp); if (!SESS_LEADER(p)) { /* Only the session leader may do this. */ sx_xunlock(&proctree_lock); return (EPERM); } if (tp->t_session != NULL && tp->t_session == p->p_session) { /* This is already our controlling TTY. */ sx_xunlock(&proctree_lock); return (0); } if (p->p_session->s_ttyp != NULL || (tp->t_session != NULL && tp->t_session->s_ttyvp != NULL && tp->t_session->s_ttyvp->v_type != VBAD)) { /* * There is already a relation between a TTY and * a session, or the caller is not the session * leader. * * Allow the TTY to be stolen when the vnode is * invalid, but the reference to the TTY is * still active. This allows immediate reuse of * TTYs of which the session leader has been * killed or the TTY revoked. */ sx_xunlock(&proctree_lock); return (EPERM); } /* Connect the session to the TTY. */ tp->t_session = p->p_session; tp->t_session->s_ttyp = tp; tp->t_sessioncnt++; sx_xunlock(&proctree_lock); /* Assign foreground process group. */ tp->t_pgrp = p->p_pgrp; PROC_LOCK(p); p->p_flag |= P_CONTROLT; PROC_UNLOCK(p); return (0); } case TIOCSPGRP: { struct pgrp *pg; /* * XXX: Temporarily unlock the TTY to locate the process * group. This code would be lot nicer if we would ever * decompose proctree_lock. */ tty_unlock(tp); sx_slock(&proctree_lock); pg = pgfind(*(int *)data); if (pg != NULL) PGRP_UNLOCK(pg); if (pg == NULL || pg->pg_session != td->td_proc->p_session) { sx_sunlock(&proctree_lock); tty_lock(tp); return (EPERM); } tty_lock(tp); /* * Determine if this TTY is the controlling TTY after * relocking the TTY. */ if (!tty_is_ctty(tp, td->td_proc)) { sx_sunlock(&proctree_lock); return (ENOTTY); } tp->t_pgrp = pg; sx_sunlock(&proctree_lock); /* Wake up the background process groups. */ cv_broadcast(&tp->t_bgwait); return (0); } case TIOCFLUSH: { int flags = *(int *)data; if (flags == 0) flags = (FREAD|FWRITE); else flags &= (FREAD|FWRITE); tty_flush(tp, flags); return (0); } case TIOCDRAIN: /* Drain TTY output. */ return tty_drain(tp, 0); case TIOCGDRAINWAIT: *(int *)data = tp->t_drainwait; return (0); case TIOCSDRAINWAIT: error = priv_check(td, PRIV_TTY_DRAINWAIT); if (error == 0) tp->t_drainwait = *(int *)data; return (error); case TIOCCONS: /* Set terminal as console TTY. */ if (*(int *)data) { error = priv_check(td, PRIV_TTY_CONSOLE); if (error) return (error); /* * XXX: constty should really need to be locked! * XXX: allow disconnected constty's to be stolen! */ if (constty == tp) return (0); if (constty != NULL) return (EBUSY); tty_unlock(tp); constty_set(tp); tty_lock(tp); } else if (constty == tp) { constty_clear(); } return (0); case TIOCGWINSZ: /* Obtain window size. */ *(struct winsize*)data = tp->t_winsize; return (0); case TIOCSWINSZ: /* Set window size. */ tty_set_winsize(tp, data); return (0); case TIOCEXCL: tp->t_flags |= TF_EXCLUDE; return (0); case TIOCNXCL: tp->t_flags &= ~TF_EXCLUDE; return (0); case TIOCSTOP: tp->t_flags |= TF_STOPPED; ttydevsw_pktnotify(tp, TIOCPKT_STOP); return (0); case TIOCSTART: tp->t_flags &= ~TF_STOPPED; ttydevsw_outwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_START); return (0); case TIOCSTAT: tty_info(tp); return (0); case TIOCSTI: if ((fflag & FREAD) == 0 && priv_check(td, PRIV_TTY_STI)) return (EPERM); if (!tty_is_ctty(tp, td->td_proc) && priv_check(td, PRIV_TTY_STI)) return (EACCES); ttydisc_rint(tp, *(char *)data, 0); ttydisc_rint_done(tp); return (0); } #ifdef COMPAT_43TTY return tty_ioctl_compat(tp, cmd, data, fflag, td); #else /* !COMPAT_43TTY */ return (ENOIOCTL); #endif /* COMPAT_43TTY */ } int tty_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, struct thread *td) { int error; tty_lock_assert(tp, MA_OWNED); if (tty_gone(tp)) return (ENXIO); error = ttydevsw_ioctl(tp, cmd, data, td); if (error == ENOIOCTL) error = tty_generic_ioctl(tp, cmd, data, fflag, td); return (error); } dev_t tty_udev(struct tty *tp) { if (tp->t_dev) return (dev2udev(tp->t_dev)); else return (NODEV); } int tty_checkoutq(struct tty *tp) { /* 256 bytes should be enough to print a log message. */ return (ttyoutq_bytesleft(&tp->t_outq) >= 256); } void tty_hiwat_in_block(struct tty *tp) { if ((tp->t_flags & TF_HIWAT_IN) == 0 && tp->t_termios.c_iflag & IXOFF && tp->t_termios.c_cc[VSTOP] != _POSIX_VDISABLE) { /* * Input flow control. Only enter the high watermark when we * can successfully store the VSTOP character. */ if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTOP], 1) == 0) tp->t_flags |= TF_HIWAT_IN; } else { /* No input flow control. */ tp->t_flags |= TF_HIWAT_IN; } } void tty_hiwat_in_unblock(struct tty *tp) { if (tp->t_flags & TF_HIWAT_IN && tp->t_termios.c_iflag & IXOFF && tp->t_termios.c_cc[VSTART] != _POSIX_VDISABLE) { /* * Input flow control. Only leave the high watermark when we * can successfully store the VSTART character. */ if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTART], 1) == 0) tp->t_flags &= ~TF_HIWAT_IN; } else { /* No input flow control. */ tp->t_flags &= ~TF_HIWAT_IN; } if (!tty_gone(tp)) ttydevsw_inwakeup(tp); } /* * TTY hooks interface. */ static int ttyhook_defrint(struct tty *tp, char c, int flags) { if (ttyhook_rint_bypass(tp, &c, 1) != 1) return (-1); return (0); } int ttyhook_register(struct tty **rtp, struct proc *p, int fd, struct ttyhook *th, void *softc) { struct tty *tp; struct file *fp; struct cdev *dev; struct cdevsw *cdp; struct filedesc *fdp; cap_rights_t rights; int error, ref; /* Validate the file descriptor. */ fdp = p->p_fd; error = fget_unlocked(fdp, fd, cap_rights_init(&rights, CAP_TTYHOOK), &fp, NULL); if (error != 0) return (error); if (fp->f_ops == &badfileops) { error = EBADF; goto done1; } /* * Make sure the vnode is bound to a character device. * Unlocked check for the vnode type is ok there, because we * only shall prevent calling devvn_refthread on the file that * never has been opened over a character device. */ if (fp->f_type != DTYPE_VNODE || fp->f_vnode->v_type != VCHR) { error = EINVAL; goto done1; } /* Make sure it is a TTY. */ cdp = devvn_refthread(fp->f_vnode, &dev, &ref); if (cdp == NULL) { error = ENXIO; goto done1; } if (dev != fp->f_data) { error = ENXIO; goto done2; } if (cdp != &ttydev_cdevsw) { error = ENOTTY; goto done2; } tp = dev->si_drv1; /* Try to attach the hook to the TTY. */ error = EBUSY; tty_lock(tp); MPASS((tp->t_hook == NULL) == ((tp->t_flags & TF_HOOK) == 0)); if (tp->t_flags & TF_HOOK) goto done3; tp->t_flags |= TF_HOOK; tp->t_hook = th; tp->t_hooksoftc = softc; *rtp = tp; error = 0; /* Maybe we can switch into bypass mode now. */ ttydisc_optimize(tp); /* Silently convert rint() calls to rint_bypass() when possible. */ if (!ttyhook_hashook(tp, rint) && ttyhook_hashook(tp, rint_bypass)) th->th_rint = ttyhook_defrint; done3: tty_unlock(tp); done2: dev_relthread(dev, ref); done1: fdrop(fp, curthread); return (error); } void ttyhook_unregister(struct tty *tp) { tty_lock_assert(tp, MA_OWNED); MPASS(tp->t_flags & TF_HOOK); /* Disconnect the hook. */ tp->t_flags &= ~TF_HOOK; tp->t_hook = NULL; /* Maybe we need to leave bypass mode. */ ttydisc_optimize(tp); /* Maybe deallocate the TTY as well. */ tty_rel_free(tp); } /* * /dev/console handling. */ static int ttyconsdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct tty *tp; /* System has no console device. */ if (dev_console_filename == NULL) return (ENXIO); /* Look up corresponding TTY by device name. */ sx_slock(&tty_list_sx); TAILQ_FOREACH(tp, &tty_list, t_list) { if (strcmp(dev_console_filename, tty_devname(tp)) == 0) { dev_console->si_drv1 = tp; break; } } sx_sunlock(&tty_list_sx); /* System console has no TTY associated. */ if (dev_console->si_drv1 == NULL) return (ENXIO); return (ttydev_open(dev, oflags, devtype, td)); } static int ttyconsdev_write(struct cdev *dev, struct uio *uio, int ioflag) { log_console(uio); return (ttydev_write(dev, uio, ioflag)); } /* * /dev/console is a little different than normal TTY's. When opened, * it determines which TTY to use. When data gets written to it, it * will be logged in the kernel message buffer. */ static struct cdevsw ttyconsdev_cdevsw = { .d_version = D_VERSION, .d_open = ttyconsdev_open, .d_close = ttydev_close, .d_read = ttydev_read, .d_write = ttyconsdev_write, .d_ioctl = ttydev_ioctl, .d_kqfilter = ttydev_kqfilter, .d_poll = ttydev_poll, .d_mmap = ttydev_mmap, .d_name = "ttyconsdev", .d_flags = D_TTY, }; static void ttyconsdev_init(void *unused __unused) { dev_console = make_dev_credf(MAKEDEV_ETERNAL, &ttyconsdev_cdevsw, 0, NULL, UID_ROOT, GID_WHEEL, 0600, "console"); } SYSINIT(tty, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyconsdev_init, NULL); void ttyconsdev_select(const char *name) { dev_console_filename = name; } /* * Debugging routines. */ #include "opt_ddb.h" #ifdef DDB #include #include static const struct { int flag; char val; } ttystates[] = { #if 0 { TF_NOPREFIX, 'N' }, #endif { TF_INITLOCK, 'I' }, { TF_CALLOUT, 'C' }, /* Keep these together -> 'Oi' and 'Oo'. */ { TF_OPENED, 'O' }, { TF_OPENED_IN, 'i' }, { TF_OPENED_OUT, 'o' }, { TF_OPENED_CONS, 'c' }, { TF_GONE, 'G' }, { TF_OPENCLOSE, 'B' }, { TF_ASYNC, 'Y' }, { TF_LITERAL, 'L' }, /* Keep these together -> 'Hi' and 'Ho'. */ { TF_HIWAT, 'H' }, { TF_HIWAT_IN, 'i' }, { TF_HIWAT_OUT, 'o' }, { TF_STOPPED, 'S' }, { TF_EXCLUDE, 'X' }, { TF_BYPASS, 'l' }, { TF_ZOMBIE, 'Z' }, { TF_HOOK, 's' }, /* Keep these together -> 'bi' and 'bo'. */ { TF_BUSY, 'b' }, { TF_BUSY_IN, 'i' }, { TF_BUSY_OUT, 'o' }, { 0, '\0'}, }; #define TTY_FLAG_BITS \ "\20\1NOPREFIX\2INITLOCK\3CALLOUT\4OPENED_IN" \ "\5OPENED_OUT\6OPENED_CONS\7GONE\10OPENCLOSE" \ "\11ASYNC\12LITERAL\13HIWAT_IN\14HIWAT_OUT" \ "\15STOPPED\16EXCLUDE\17BYPASS\20ZOMBIE" \ "\21HOOK\22BUSY_IN\23BUSY_OUT" #define DB_PRINTSYM(name, addr) \ db_printf("%s " #name ": ", sep); \ db_printsym((db_addr_t) addr, DB_STGY_ANY); \ db_printf("\n"); static void _db_show_devsw(const char *sep, const struct ttydevsw *tsw) { db_printf("%sdevsw: ", sep); db_printsym((db_addr_t)tsw, DB_STGY_ANY); db_printf(" (%p)\n", tsw); DB_PRINTSYM(open, tsw->tsw_open); DB_PRINTSYM(close, tsw->tsw_close); DB_PRINTSYM(outwakeup, tsw->tsw_outwakeup); DB_PRINTSYM(inwakeup, tsw->tsw_inwakeup); DB_PRINTSYM(ioctl, tsw->tsw_ioctl); DB_PRINTSYM(param, tsw->tsw_param); DB_PRINTSYM(modem, tsw->tsw_modem); DB_PRINTSYM(mmap, tsw->tsw_mmap); DB_PRINTSYM(pktnotify, tsw->tsw_pktnotify); DB_PRINTSYM(free, tsw->tsw_free); } static void _db_show_hooks(const char *sep, const struct ttyhook *th) { db_printf("%shook: ", sep); db_printsym((db_addr_t)th, DB_STGY_ANY); db_printf(" (%p)\n", th); if (th == NULL) return; DB_PRINTSYM(rint, th->th_rint); DB_PRINTSYM(rint_bypass, th->th_rint_bypass); DB_PRINTSYM(rint_done, th->th_rint_done); DB_PRINTSYM(rint_poll, th->th_rint_poll); DB_PRINTSYM(getc_inject, th->th_getc_inject); DB_PRINTSYM(getc_capture, th->th_getc_capture); DB_PRINTSYM(getc_poll, th->th_getc_poll); DB_PRINTSYM(close, th->th_close); } static void _db_show_termios(const char *name, const struct termios *t) { db_printf("%s: iflag 0x%x oflag 0x%x cflag 0x%x " "lflag 0x%x ispeed %u ospeed %u\n", name, t->c_iflag, t->c_oflag, t->c_cflag, t->c_lflag, t->c_ispeed, t->c_ospeed); } /* DDB command to show TTY statistics. */ DB_SHOW_COMMAND(tty, db_show_tty) { struct tty *tp; if (!have_addr) { db_printf("usage: show tty \n"); return; } tp = (struct tty *)addr; db_printf("%p: %s\n", tp, tty_devname(tp)); db_printf("\tmtx: %p\n", tp->t_mtx); db_printf("\tflags: 0x%b\n", tp->t_flags, TTY_FLAG_BITS); db_printf("\trevokecnt: %u\n", tp->t_revokecnt); /* Buffering mechanisms. */ db_printf("\tinq: %p begin %u linestart %u reprint %u end %u " "nblocks %u quota %u\n", &tp->t_inq, tp->t_inq.ti_begin, tp->t_inq.ti_linestart, tp->t_inq.ti_reprint, tp->t_inq.ti_end, tp->t_inq.ti_nblocks, tp->t_inq.ti_quota); db_printf("\toutq: %p begin %u end %u nblocks %u quota %u\n", &tp->t_outq, tp->t_outq.to_begin, tp->t_outq.to_end, tp->t_outq.to_nblocks, tp->t_outq.to_quota); db_printf("\tinlow: %zu\n", tp->t_inlow); db_printf("\toutlow: %zu\n", tp->t_outlow); _db_show_termios("\ttermios", &tp->t_termios); db_printf("\twinsize: row %u col %u xpixel %u ypixel %u\n", tp->t_winsize.ws_row, tp->t_winsize.ws_col, tp->t_winsize.ws_xpixel, tp->t_winsize.ws_ypixel); db_printf("\tcolumn: %u\n", tp->t_column); db_printf("\twritepos: %u\n", tp->t_writepos); db_printf("\tcompatflags: 0x%x\n", tp->t_compatflags); /* Init/lock-state devices. */ _db_show_termios("\ttermios_init_in", &tp->t_termios_init_in); _db_show_termios("\ttermios_init_out", &tp->t_termios_init_out); _db_show_termios("\ttermios_lock_in", &tp->t_termios_lock_in); _db_show_termios("\ttermios_lock_out", &tp->t_termios_lock_out); /* Hooks */ _db_show_devsw("\t", tp->t_devsw); _db_show_hooks("\t", tp->t_hook); /* Process info. */ db_printf("\tpgrp: %p gid %d jobc %d\n", tp->t_pgrp, tp->t_pgrp ? tp->t_pgrp->pg_id : 0, tp->t_pgrp ? tp->t_pgrp->pg_jobc : 0); db_printf("\tsession: %p", tp->t_session); if (tp->t_session != NULL) db_printf(" count %u leader %p tty %p sid %d login %s", tp->t_session->s_count, tp->t_session->s_leader, tp->t_session->s_ttyp, tp->t_session->s_sid, tp->t_session->s_login); db_printf("\n"); db_printf("\tsessioncnt: %u\n", tp->t_sessioncnt); db_printf("\tdevswsoftc: %p\n", tp->t_devswsoftc); db_printf("\thooksoftc: %p\n", tp->t_hooksoftc); db_printf("\tdev: %p\n", tp->t_dev); } /* DDB command to list TTYs. */ DB_SHOW_ALL_COMMAND(ttys, db_show_all_ttys) { struct tty *tp; size_t isiz, osiz; int i, j; /* Make the output look like `pstat -t'. */ db_printf("PTR "); #if defined(__LP64__) db_printf(" "); #endif db_printf(" LINE INQ CAN LIN LOW OUTQ USE LOW " "COL SESS PGID STATE\n"); TAILQ_FOREACH(tp, &tty_list, t_list) { isiz = tp->t_inq.ti_nblocks * TTYINQ_DATASIZE; osiz = tp->t_outq.to_nblocks * TTYOUTQ_DATASIZE; db_printf("%p %10s %5zu %4u %4u %4zu %5zu %4u %4zu %5u %5d " "%5d ", tp, tty_devname(tp), isiz, tp->t_inq.ti_linestart - tp->t_inq.ti_begin, tp->t_inq.ti_end - tp->t_inq.ti_linestart, isiz - tp->t_inlow, osiz, tp->t_outq.to_end - tp->t_outq.to_begin, osiz - tp->t_outlow, MIN(tp->t_column, 99999), tp->t_session ? tp->t_session->s_sid : 0, tp->t_pgrp ? tp->t_pgrp->pg_id : 0); /* Flag bits. */ for (i = j = 0; ttystates[i].flag; i++) if (tp->t_flags & ttystates[i].flag) { db_printf("%c", ttystates[i].val); j++; } if (j == 0) db_printf("-"); db_printf("\n"); } } #endif /* DDB */ Index: head/sys/sys/systm.h =================================================================== --- head/sys/sys/systm.h (revision 335994) +++ head/sys/sys/systm.h (revision 335995) @@ -1,536 +1,535 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1982, 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)systm.h 8.7 (Berkeley) 3/29/95 * $FreeBSD$ */ #ifndef _SYS_SYSTM_H_ #define _SYS_SYSTM_H_ #include #include #include #include #include #include /* for people using printf mainly */ __NULLABILITY_PRAGMA_PUSH extern int cold; /* nonzero if we are doing a cold boot */ extern int suspend_blocked; /* block suspend due to pending shutdown */ extern int rebooting; /* kern_reboot() has been called. */ extern const char *panicstr; /* panic message */ extern char version[]; /* system version */ extern char compiler_version[]; /* compiler version */ extern char copyright[]; /* system copyright */ extern int kstack_pages; /* number of kernel stack pages */ extern u_long pagesizes[]; /* supported page sizes */ extern long physmem; /* physical memory */ extern long realmem; /* 'real' memory */ extern char *rootdevnames[2]; /* names of possible root devices */ extern int boothowto; /* reboot flags, from console subsystem */ extern int bootverbose; /* nonzero to print verbose messages */ extern int maxusers; /* system tune hint */ extern int ngroups_max; /* max # of supplemental groups */ extern int vm_guest; /* Running as virtual machine guest? */ /* * Detected virtual machine guest types. The intention is to expand * and/or add to the VM_GUEST_VM type if specific VM functionality is * ever implemented (e.g. vendor-specific paravirtualization features). * Keep in sync with vm_guest_sysctl_names[]. */ enum VM_GUEST { VM_GUEST_NO = 0, VM_GUEST_VM, VM_GUEST_XEN, VM_GUEST_HV, VM_GUEST_VMWARE, VM_GUEST_KVM, VM_GUEST_BHYVE, VM_LAST }; #if defined(WITNESS) || defined(INVARIANT_SUPPORT) void kassert_panic(const char *fmt, ...) __printflike(1, 2); #endif #ifdef INVARIANTS /* The option is always available */ #define KASSERT(exp,msg) do { \ if (__predict_false(!(exp))) \ kassert_panic msg; \ } while (0) #define VNASSERT(exp, vp, msg) do { \ if (__predict_false(!(exp))) { \ vn_printf(vp, "VNASSERT failed\n"); \ kassert_panic msg; \ } \ } while (0) #else #define KASSERT(exp,msg) do { \ } while (0) #define VNASSERT(exp, vp, msg) do { \ } while (0) #endif #ifndef CTASSERT /* Allow lint to override */ #define CTASSERT(x) _Static_assert(x, "compile-time assertion failed") #endif #if defined(_KERNEL) #include /* MAXCPU */ #include /* curthread */ #include #endif /* * Assert that a pointer can be loaded from memory atomically. * * This assertion enforces stronger alignment than necessary. For example, * on some architectures, atomicity for unaligned loads will depend on * whether or not the load spans multiple cache lines. */ #define ASSERT_ATOMIC_LOAD_PTR(var, msg) \ KASSERT(sizeof(var) == sizeof(void *) && \ ((uintptr_t)&(var) & (sizeof(void *) - 1)) == 0, msg) /* * Assert that a thread is in critical(9) section. */ #define CRITICAL_ASSERT(td) \ KASSERT((td)->td_critnest >= 1, ("Not in critical section")); /* * If we have already panic'd and this is the thread that called * panic(), then don't block on any mutexes but silently succeed. * Otherwise, the kernel will deadlock since the scheduler isn't * going to run the thread that holds any lock we need. */ #define SCHEDULER_STOPPED_TD(td) ({ \ MPASS((td) == curthread); \ __predict_false((td)->td_stopsched); \ }) #define SCHEDULER_STOPPED() SCHEDULER_STOPPED_TD(curthread) /* * Align variables. */ #define __read_mostly __section(".data.read_mostly") #define __read_frequently __section(".data.read_frequently") #define __exclusive_cache_line __aligned(CACHE_LINE_SIZE) \ __section(".data.exclusive_cache_line") /* * XXX the hints declarations are even more misplaced than most declarations * in this file, since they are needed in one file (per arch) and only used * in two files. * XXX most of these variables should be const. */ extern int osreldate; -extern int envmode; -extern int hintmode; /* 0 = off. 1 = config, 2 = fallback */ extern int dynamic_kenv; extern struct mtx kenv_lock; extern char *kern_envp; +extern char *md_envp; extern char static_env[]; extern char static_hints[]; /* by config for now */ extern char **kenvp; extern const void *zero_region; /* address space maps to a zeroed page */ extern int unmapped_buf_allowed; #ifdef __LP64__ #define IOSIZE_MAX iosize_max() #define DEVFS_IOSIZE_MAX devfs_iosize_max() #else #define IOSIZE_MAX SSIZE_MAX #define DEVFS_IOSIZE_MAX SSIZE_MAX #endif /* * General function declarations. */ struct inpcb; struct lock_object; struct malloc_type; struct mtx; struct proc; struct socket; struct thread; struct tty; struct ucred; struct uio; struct _jmp_buf; struct trapframe; struct eventtimer; int setjmp(struct _jmp_buf *) __returns_twice; void longjmp(struct _jmp_buf *, int) __dead2; int dumpstatus(vm_offset_t addr, off_t count); int nullop(void); int eopnotsupp(void); int ureadc(int, struct uio *); void hashdestroy(void *, struct malloc_type *, u_long); void *hashinit(int count, struct malloc_type *type, u_long *hashmask); void *hashinit_flags(int count, struct malloc_type *type, u_long *hashmask, int flags); #define HASH_NOWAIT 0x00000001 #define HASH_WAITOK 0x00000002 void *phashinit(int count, struct malloc_type *type, u_long *nentries); void *phashinit_flags(int count, struct malloc_type *type, u_long *nentries, int flags); void g_waitidle(void); void panic(const char *, ...) __dead2 __printflike(1, 2); void vpanic(const char *, __va_list) __dead2 __printflike(1, 0); void cpu_boot(int); void cpu_flush_dcache(void *, size_t); void cpu_rootconf(void); void critical_enter_KBI(void); void critical_exit_KBI(void); void critical_exit_preempt(void); void init_param1(void); void init_param2(long physpages); void init_static_kenv(char *, size_t); void tablefull(const char *); #if defined(KLD_MODULE) || defined(KTR_CRITICAL) || !defined(_KERNEL) || defined(GENOFFSET) #define critical_enter() critical_enter_KBI() #define critical_exit() critical_exit_KBI() #else static __inline void critical_enter(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; td->td_critnest++; } static __inline void critical_exit(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; KASSERT(td->td_critnest != 0, ("critical_exit: td_critnest == 0")); td->td_critnest--; __compiler_membar(); if (__predict_false(td->td_owepreempt)) critical_exit_preempt(); } #endif #ifdef EARLY_PRINTF typedef void early_putc_t(int ch); extern early_putc_t *early_putc; #endif int kvprintf(char const *, void (*)(int, void*), void *, int, __va_list) __printflike(1, 0); void log(int, const char *, ...) __printflike(2, 3); void log_console(struct uio *); void vlog(int, const char *, __va_list) __printflike(2, 0); int asprintf(char **ret, struct malloc_type *mtp, const char *format, ...) __printflike(3, 4); int printf(const char *, ...) __printflike(1, 2); int snprintf(char *, size_t, const char *, ...) __printflike(3, 4); int sprintf(char *buf, const char *, ...) __printflike(2, 3); int uprintf(const char *, ...) __printflike(1, 2); int vprintf(const char *, __va_list) __printflike(1, 0); int vasprintf(char **ret, struct malloc_type *mtp, const char *format, __va_list ap) __printflike(3, 0); int vsnprintf(char *, size_t, const char *, __va_list) __printflike(3, 0); int vsnrprintf(char *, size_t, int, const char *, __va_list) __printflike(4, 0); int vsprintf(char *buf, const char *, __va_list) __printflike(2, 0); int ttyprintf(struct tty *, const char *, ...) __printflike(2, 3); int sscanf(const char *, char const * _Nonnull, ...) __scanflike(2, 3); int vsscanf(const char * _Nonnull, char const * _Nonnull, __va_list) __scanflike(2, 0); long strtol(const char *, char **, int); u_long strtoul(const char *, char **, int); quad_t strtoq(const char *, char **, int); u_quad_t strtouq(const char *, char **, int); void tprintf(struct proc *p, int pri, const char *, ...) __printflike(3, 4); void vtprintf(struct proc *, int, const char *, __va_list) __printflike(3, 0); void hexdump(const void *ptr, int length, const char *hdr, int flags); #define HD_COLUMN_MASK 0xff #define HD_DELIM_MASK 0xff00 #define HD_OMIT_COUNT (1 << 16) #define HD_OMIT_HEX (1 << 17) #define HD_OMIT_CHARS (1 << 18) #define ovbcopy(f, t, l) bcopy((f), (t), (l)) void bcopy(const void * _Nonnull from, void * _Nonnull to, size_t len); #define bcopy(from, to, len) __builtin_memmove((to), (from), (len)) void bzero(void * _Nonnull buf, size_t len); #define bzero(buf, len) __builtin_memset((buf), 0, (len)) void explicit_bzero(void * _Nonnull, size_t); int bcmp(const void *b1, const void *b2, size_t len); #define bcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) void *memset(void * _Nonnull buf, int c, size_t len); #define memset(buf, c, len) __builtin_memset((buf), (c), (len)) void *memcpy(void * _Nonnull to, const void * _Nonnull from, size_t len); #define memcpy(to, from, len) __builtin_memcpy((to), (from), (len)) void *memmove(void * _Nonnull dest, const void * _Nonnull src, size_t n); #define memmove(dest, src, n) __builtin_memmove((dest), (src), (n)) int memcmp(const void *b1, const void *b2, size_t len); #define memcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) int copystr(const void * _Nonnull __restrict kfaddr, void * _Nonnull __restrict kdaddr, size_t len, size_t * __restrict lencopied); int copyinstr(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len, size_t * __restrict lencopied); int copyin(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); int copyin_nofault(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); int copyout(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); int copyout_nofault(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); int fubyte(volatile const void *base); long fuword(volatile const void *base); int fuword16(volatile const void *base); int32_t fuword32(volatile const void *base); int64_t fuword64(volatile const void *base); int fueword(volatile const void *base, long *val); int fueword32(volatile const void *base, int32_t *val); int fueword64(volatile const void *base, int64_t *val); int subyte(volatile void *base, int byte); int suword(volatile void *base, long word); int suword16(volatile void *base, int word); int suword32(volatile void *base, int32_t word); int suword64(volatile void *base, int64_t word); uint32_t casuword32(volatile uint32_t *base, uint32_t oldval, uint32_t newval); u_long casuword(volatile u_long *p, u_long oldval, u_long newval); int casueword32(volatile uint32_t *base, uint32_t oldval, uint32_t *oldvalp, uint32_t newval); int casueword(volatile u_long *p, u_long oldval, u_long *oldvalp, u_long newval); void realitexpire(void *); int sysbeep(int hertz, int period); void hardclock(int usermode, uintfptr_t pc); void hardclock_cnt(int cnt, int usermode); void hardclock_cpu(int usermode); void hardclock_sync(int cpu); void softclock(void *); void statclock(int usermode); void statclock_cnt(int cnt, int usermode); void profclock(int usermode, uintfptr_t pc); void profclock_cnt(int cnt, int usermode, uintfptr_t pc); int hardclockintr(void); void startprofclock(struct proc *); void stopprofclock(struct proc *); void cpu_startprofclock(void); void cpu_stopprofclock(void); void suspendclock(void); void resumeclock(void); sbintime_t cpu_idleclock(void); void cpu_activeclock(void); void cpu_new_callout(int cpu, sbintime_t bt, sbintime_t bt_opt); void cpu_et_frequency(struct eventtimer *et, uint64_t newfreq); extern int cpu_disable_c2_sleep; extern int cpu_disable_c3_sleep; char *kern_getenv(const char *name); void freeenv(char *env); int getenv_int(const char *name, int *data); int getenv_uint(const char *name, unsigned int *data); int getenv_long(const char *name, long *data); int getenv_ulong(const char *name, unsigned long *data); int getenv_string(const char *name, char *data, int size); int getenv_int64(const char *name, int64_t *data); int getenv_uint64(const char *name, uint64_t *data); int getenv_quad(const char *name, quad_t *data); int kern_setenv(const char *name, const char *value); int kern_unsetenv(const char *name); int testenv(const char *name); int getenv_array(const char *name, void *data, int size, int *psize, int type_size, bool allow_signed); #define GETENV_UNSIGNED false /* negative numbers not allowed */ #define GETENV_SIGNED true /* negative numbers allowed */ typedef uint64_t (cpu_tick_f)(void); void set_cputicker(cpu_tick_f *func, uint64_t freq, unsigned var); extern cpu_tick_f *cpu_ticks; uint64_t cpu_tickrate(void); uint64_t cputick2usec(uint64_t tick); #ifdef APM_FIXUP_CALLTODO struct timeval; void adjust_timeout_calltodo(struct timeval *time_change); #endif /* APM_FIXUP_CALLTODO */ #include /* Initialize the world */ void consinit(void); void cpu_initclocks(void); void cpu_initclocks_bsp(void); void cpu_initclocks_ap(void); void usrinfoinit(void); /* Finalize the world */ void kern_reboot(int) __dead2; void shutdown_nice(int); /* Timeouts */ typedef void timeout_t(void *); /* timeout function type */ #define CALLOUT_HANDLE_INITIALIZER(handle) \ { NULL } void callout_handle_init(struct callout_handle *); struct callout_handle timeout(timeout_t *, void *, int); void untimeout(timeout_t *, void *, struct callout_handle); /* Stubs for obsolete functions that used to be for interrupt management */ static __inline intrmask_t splbio(void) { return 0; } static __inline intrmask_t splcam(void) { return 0; } static __inline intrmask_t splclock(void) { return 0; } static __inline intrmask_t splhigh(void) { return 0; } static __inline intrmask_t splimp(void) { return 0; } static __inline intrmask_t splnet(void) { return 0; } static __inline intrmask_t spltty(void) { return 0; } static __inline void splx(intrmask_t ipl __unused) { return; } /* * Common `proc' functions are declared here so that proc.h can be included * less often. */ int _sleep(void * _Nonnull chan, struct lock_object *lock, int pri, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep(chan, mtx, pri, wmesg, timo) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), \ tick_sbt * (timo), 0, C_HARDCLOCK) #define msleep_sbt(chan, mtx, pri, wmesg, bt, pr, flags) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), (bt), (pr), \ (flags)) int msleep_spin_sbt(void * _Nonnull chan, struct mtx *mtx, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep_spin(chan, mtx, wmesg, timo) \ msleep_spin_sbt((chan), (mtx), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) int pause_sbt(const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define pause(wmesg, timo) \ pause_sbt((wmesg), tick_sbt * (timo), 0, C_HARDCLOCK) #define pause_sig(wmesg, timo) \ pause_sbt((wmesg), tick_sbt * (timo), 0, C_HARDCLOCK | C_CATCH) #define tsleep(chan, pri, wmesg, timo) \ _sleep((chan), NULL, (pri), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) #define tsleep_sbt(chan, pri, wmesg, bt, pr, flags) \ _sleep((chan), NULL, (pri), (wmesg), (bt), (pr), (flags)) void wakeup(void * chan); void wakeup_one(void * chan); /* * Common `struct cdev *' stuff are declared here to avoid #include poisoning */ struct cdev; dev_t dev2udev(struct cdev *x); const char *devtoname(struct cdev *cdev); #ifdef __LP64__ size_t devfs_iosize_max(void); size_t iosize_max(void); #endif int poll_no_poll(int events); /* XXX: Should be void nanodelay(u_int nsec); */ void DELAY(int usec); /* Root mount holdback API */ struct root_hold_token; struct root_hold_token *root_mount_hold(const char *identifier); void root_mount_rel(struct root_hold_token *h); int root_mounted(void); /* * Unit number allocation API. (kern/subr_unit.c) */ struct unrhdr; struct unrhdr *new_unrhdr(int low, int high, struct mtx *mutex); void init_unrhdr(struct unrhdr *uh, int low, int high, struct mtx *mutex); void delete_unrhdr(struct unrhdr *uh); void clear_unrhdr(struct unrhdr *uh); void clean_unrhdr(struct unrhdr *uh); void clean_unrhdrl(struct unrhdr *uh); int alloc_unr(struct unrhdr *uh); int alloc_unr_specific(struct unrhdr *uh, u_int item); int alloc_unrl(struct unrhdr *uh); void free_unr(struct unrhdr *uh, u_int item); void intr_prof_stack_use(struct thread *td, struct trapframe *frame); void counted_warning(unsigned *counter, const char *msg); /* * APIs to manage deprecation and obsolescence. */ struct device; void _gone_in(int major, const char *msg); void _gone_in_dev(struct device *dev, int major, const char *msg); #ifdef NO_OBSOLETE_CODE #define __gone_ok(m, msg) \ _Static_assert(m < P_OSREL_MAJOR(__FreeBSD_version)), \ "Obsolete code" msg); #else #define __gone_ok(m, msg) #endif #define gone_in(major, msg) __gone_ok(major, msg) _gone_in(major, msg) #define gone_in_dev(dev, major, msg) __gone_ok(major, msg) _gone_in_dev(dev, major, msg) __NULLABILITY_PRAGMA_POP #endif /* !_SYS_SYSTM_H_ */ Index: head/usr.sbin/config/config.5 =================================================================== --- head/usr.sbin/config/config.5 (revision 335994) +++ head/usr.sbin/config/config.5 (revision 335995) @@ -1,433 +1,464 @@ .\" Copyright (c) 2003 Joseph Koshy .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd June 26, 2018 +.Dd July 5, 2018 .Dt CONFIG 5 .Os .Sh NAME .Nm config .Nd kernel configuration file format .Sh DESCRIPTION A kernel configuration file specifies the configuration of a .Fx kernel. It is processed by .Xr config 8 to create a build environment where a kernel may be built using .Xr make 1 . .Ss Lexical Structure A kernel configuration file comprises a sequence of specification directives. .Pp A specification directive starts with a keyword at the beginning of the line and is followed by additional parameters. .Pp A specification directive may be terminated by a semicolon .Ql \&; or by a newline. Long input lines may be broken into shorter lines by starting the second and subsequent lines with a white space character. .Pp Case is significant, .Dq Li machine and .Dq Li MACHINE are different tokens. .Pp A double quote character .Ql \[dq] starts a quoted string. All characters up to the next quote character form the value of the quoted string. A .Ql \[dq] character may be inserted into a quoted string by using the sequence .Ql \e\[dq] . .Pp Numbers are specified using .Tn C Ns -style syntax. .Pp A .Ql # character starts a comment; all characters from the .Ql # character till the end of the current line are ignored. .Pp Whitespace between tokens is ignored, except inside quoted strings. Whitespace following a comment line is ignored. .Ss Configuration Directives Kernel configuration directives may appear in any order in a kernel configuration file. Directives are processed in order of appearance with subsequent directive lines overriding the effect of prior ones. .Pp The list of keywords and their meanings are as follows: .Pp .Bl -tag -width indent -compact .\" -------- CPU -------- .It Ic cpu Ar cputype Specify the CPU this kernel will run on. There can be more than one .Ic cpu directive in a configuration file. The allowed list of CPU names is architecture specific and is defined in the file .Pa sys/conf/options. Ns Aq Ar arch . .\" -------- DEVICE -------- .Pp .It Ic device Ar name Op , Ar name Op ... .It Ic devices Ar name Op , Ar name Op ... Configures the specified devices for inclusion into the kernel image. Devices that are common to all architectures are defined in the file .Pa sys/conf/files . Devices that are specific to architecture .Ar arch are defined in the file .Pa sys/conf/files. Ns Aq Ar arch . .\" -------- ENV -------- .Pp .It Ic env Ar filename Specifies a filename containing a kernel environment definition. -The kernel normally uses an environment prepared for it at boot time -by +.Pp +The kernel will augment this compiled-in environment with the environment +prepared for it at boot time by .Xr loader 8 . -This directive makes the kernel ignore the boot environment and use -the compiled-in environment instead, unless the boot environment contains -.Va static_env.disabled=1 . +Environment variables specified in the +.Xr loader 8 +environment will take precedence over environment variables specified in +.Ar filename , +and environment variables specified in the dynamic environment take precedence +over both of these. .Pp +.Va static_env.disabled=1 +may be specified in the +.Xr loader 8 +environment to disable use of this compiled-in environment. +This option has no effect if specified in any environment after the +.Xr loader 8 +environment is processed. +.Pp This directive is useful for setting kernel tunables in embedded environments that do not start from .Xr loader 8 . .Pp All .Ic env and .Ic envvar directives will be processed and added to the static environment in reversed order of appearance so that later specified variables properly override earlier specified variables. Note that within .Ar filename , the first appearance of a given variable will be the first one seen by the kernel, effectively shadowing any later appearances of the same variable within .Ar filename . .\" -------- ENVVAR -------- .Pp .It Ic envvar Ar setting Specifies an individual environment setting to be added to the kernel's compiled-in environment. .Ar setting must be of the form .Dq Va name=value . Optional quotes are supported in both name and value. .Pp All .Ic env and .Ic envvar directives will be processed and added to the static environment in reversed order of appearance so that later specified variables properly override earlier specified variables. .\" -------- FILES -------- .Pp .It Ic files Ar filename Specifies a file containing a list of files specific to that kernel configuration file (a la .Pa files. Ns Aq Ar arch ) . .\" -------- HINTS -------- .Pp .It Ic hints Ar filename Specifies a file to load a static device configuration specification from. From .Fx 5.0 onwards, the kernel reads the system's device configuration at boot time (see .Xr device.hints 5 ) . This directive configures the kernel to use the static device configuration listed in -.Ar filename , -unless the boot environment contains -.Va static_hints.disabled=1 . +.Ar filename . +.Pp +Hints provided in this static device configuration will be overwritten in the +order in which they're encountered. +Hints in the compiled-in environment takes precedence over compiled-in hints, +and hints in the environment prepared for the kernel by +.Xr loader 8 +takes precedence over hints in the compiled-in environment. +.Pp +Once the dynamic environment becomes available, all compiled-in hints will be +added to the dynamic environment if they do not already have an override in +the dynamic environment. +The dynamic environment will then be used for all searches of hints. +.Pp +.Va static_hints.disabled=1 +may be specified in either a compiled-in environment or the +.Xr loader 8 +environment to disable use of these hints files. +This option has no effect if specified in any environment after the +.Xr loader 8 +environment is processed. +.Pp The file .Ar filename must conform to the syntax specified by .Xr device.hints 5 . Multiple hints lines are allowed. The resulting hints will be the files concatenated in reverse order of appearance so that hints in later files properly override hints in earlier files. .\" -------- IDENT -------- .Pp .It Ic ident Ar name Set the kernel name to .Ar name . At least one .Ic ident directive is required. .\" -------- INCLUDE -------- .Pp .It Ic include Ar filename Read subsequent text from file .Ar filename and return to the current file after .Ar filename is successfully processed. .\" -------- MACHINE -------- .Pp .It Ic machine Ar arch Op Ar cpuarch Specifies the architecture of the machine the kernel is being compiled for. Legal values for .Ar arch include: .Pp .Bl -tag -width ".Cm powerpc" -compact .It Cm alpha The DEC Alpha architecture. .It Cm arm The ARM architecture. .It Cm amd64 The AMD x86-64 architecture. .It Cm i386 The Intel x86 based PC architecture. .It Cm mips The MIPS architecture. .It Cm powerpc The IBM PowerPC architecture. .It Cm sparc64 The Sun Sparc64 architecture. .El .Pp If argument .Ar cpuarch is specified, it points .Xr config 8 to the cpu architecture of the machine. When .Ar cpuarch is not specified, it is assumed to be the same as .Ar arch . .Ar arch corresponds to MACHINE. .Ar cpuarch corresponds to MACHINE_ARCH. .Pp A kernel configuration file may have only one .Ic machine directive. .\" -------- MAKEOPTION -------- .Pp .It Ic makeoption Ar options .It Ic makeoptions Ar options Add .Ar options to the generated makefile. .Pp The .Ar options argument is a comma separated list of one or more option specifications. Each option specification has the form .Pp .D1 Ar MakeVariableName Ns Op = Ns Ar Value .D1 Ar MakeVariableName Ns += Ns Ar Value .Pp and results in the appropriate .Xr make 1 variable definition being inserted into the generated makefile. If only the name of the .Xr make 1 variable is specified, .Ar value is assumed to be the empty string. .Pp Example: .Bd -literal -offset indent -compact makeoptions MYMAKEOPTION="foo" makeoptions MYMAKEOPTION+="bar" makeoptions MYNULLMAKEOPTION .Ed .\" -------- MAXUSERS -------- .Pp .It Ic maxusers Ar number This optional directive is used to configure the size of some kernel data structures. The parameter .Ar number can be 0 (the default) or an integer greater than or equal to 2. A value of 0 indicates that the kernel should configure its data structures according to the size of available physical memory. If auto configuration is requested, the kernel will set this tunable to a value between 32 and 384. .Pp As explained in .Xr tuning 7 , this tunable can also be set at boot time using .Xr loader 8 . .\" -------- NOCPU -------- .Pp .It Ic nocpu Ar cputype Remove the specified CPU from the list of previously selected CPUs. This directive can be used to cancel the effect of .Ic cpu directives in files included using .Ic include . .\" -------- NODEVICE -------- .Pp .It Ic nodevice Ar name Op , Ar name Op ... .It Ic nodevices Ar name Op , Ar name Op ... Remove the specified devices from the list of previously selected devices. This directive can be used to cancel the effects of .Ic device or .Ic devices directives in files included using .Ic include . .\" -------- NOMAKEOPTION -------- .Pp .It Ic nomakeoption Ar name .It Ic nomakeoptions Ar name Removes previously defined .Xr make 1 option .Ar name from the kernel build. This directive can be used to cancel the effects of .Ic makeoption directives in files included using .Ic include . .\" -------- NOOPTION -------- .Pp .It Ic nooption Ar name Op , Ar name Op ... .It Ic nooptions Ar name Op , Ar name Op ... Remove the specified kernel options from the list of previously defined options. This directive can be used to cancel the effects of .Ic option or .Ic options directives in files included using .Ic include . .\" -------- OPTIONS -------- .Pp .It Ic option Ar optionspec Op , Ar optionspec Op ... .It Ic options Ar optionspec Op , Ar optionspec Op ... Add compile time kernel options to the kernel build. Each option specification has the form .Pp .D1 Ar name Ns Op = Ns Ar value .Pp If .Ar value is not specified, it is assumed to be .Dv NULL . Options common to all architectures are specified in the file .Pa sys/conf/options . Options specific to architecture .Ar arch are specified in the file .Pa sys/conf/options. Ns Aq Ar arch . .\" -------- PROFILE -------- .Pp .It Ic profile Ar number Enables kernel profiling if .Ar number is non-zero. If .Ar number is 2 or greater, the kernel is configured for high-resolution profiling. Kernels can also be built for profiling using the .Fl p option to .Xr config 8 . .El .Ss Obsolete Directives The following kernel configuration directives are obsolete. .Bl -tag -width indent .\" -------- CONFIG -------- .It Ic config This directive was used to specify the device to be used for the root file system. From .Fx 4.0 onwards, this information is passed to a booting kernel by .Xr loader 8 . .El .Sh FILES .Bl -tag -width ".Pa sys/conf/Makefile. Ns Ar arch" -compact .It Pa sys/compile/ Ns Ar NAME Compile directory created from a kernel configuration. .It Pa sys/conf/Makefile. Ns Ar arch .Pa Makefile fragments for architecture .Ar arch . .It Pa sys/conf/files Devices common to all architectures. .It Pa sys/conf/files. Ns Ar arch Devices for architecture .Ar arch . .It Pa sys/conf/options Options common to all architectures. .It Pa sys/conf/options. Ns Ar arch Options for architecture .Ar arch . .El .Sh SEE ALSO .Xr kenv 1 , .Xr make 1 , .Xr device.hints 5 , .Xr loader.conf 5 , .Xr config 8 , .Xr kldload 8 , .Xr loader 8 .Rs .%T "Building 4.4BSD Kernels with Config" .%A "Samuel J. Leffler" .%A "Michael J. Karels" .Re .Sh HISTORY The .Xr config 8 utility first appeared in .Bx 4.1 , and was subsequently revised in .Bx 4.4 . .Pp The kernel configuration mechanism changed further in .Fx 4.0 and .Fx 5.0 , moving toward an architecture supporting dynamic kernel configuration. Index: head/usr.sbin/config/config.h =================================================================== --- head/usr.sbin/config/config.h (revision 335994) +++ head/usr.sbin/config/config.h (revision 335995) @@ -1,222 +1,220 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)config.h 8.1 (Berkeley) 6/6/93 * $FreeBSD$ */ /* * Config. */ #include #include #include #include #include struct cfgfile { STAILQ_ENTRY(cfgfile) cfg_next; char *cfg_path; }; STAILQ_HEAD(, cfgfile) cfgfiles; struct file_list { STAILQ_ENTRY(file_list) f_next; char *f_fn; /* the name */ int f_type; /* type */ u_char f_flags; /* see below */ char *f_compilewith; /* special make rule if present */ char *f_depends; /* additional dependencies */ char *f_clean; /* File list to add to clean rule */ char *f_warn; /* warning message */ const char *f_objprefix; /* prefix string for object name */ const char *f_srcprefix; /* source prefix such as $S/ */ }; struct files_name { char *f_name; STAILQ_ENTRY(files_name) f_next; }; /* * Types. */ #define NORMAL 1 #define PROFILING 3 #define NODEPEND 4 #define LOCAL 5 #define DEVDONE 0x80000000 #define TYPEMASK 0x7fffffff /* * Attributes (flags). */ #define NO_IMPLCT_RULE 1 #define NO_OBJ 2 #define BEFORE_DEPEND 4 #define NOWERROR 16 struct device { int d_done; /* processed */ char *d_name; /* name of device (e.g. rk11) */ #define UNKNOWN -2 /* -2 means not set yet */ STAILQ_ENTRY(device) d_next; /* Next one in list */ }; struct config { char *s_sysname; }; /* * Config has a global notion of which machine type is * being used. It uses the name of the machine in choosing * files and directories. Thus if the name of the machine is ``i386'', * it will build from ``Makefile.i386'' and use ``../i386/inline'' * in the makerules, etc. machinearch is the global notion of the * MACHINE_ARCH for this MACHINE. */ char *machinename; char *machinearch; /* * For each machine, a set of CPU's may be specified as supported. * These and the options (below) are put in the C flags in the makefile. */ struct cputype { char *cpu_name; SLIST_ENTRY(cputype) cpu_next; }; SLIST_HEAD(, cputype) cputype; /* * A set of options may also be specified which are like CPU types, * but which may also specify values for the options. * A separate set of options may be defined for make-style options. */ struct opt { char *op_name; char *op_value; int op_ownfile; /* true = own file, false = makefile */ SLIST_ENTRY(opt) op_next; SLIST_ENTRY(opt) op_append; }; SLIST_HEAD(opt_head, opt) opt, mkopt, rmopts; struct opt_list { char *o_name; char *o_file; int o_flags; #define OL_ALIAS 1 SLIST_ENTRY(opt_list) o_next; }; SLIST_HEAD(, opt_list) otab; struct envvar { char *env_str; bool env_is_file; STAILQ_ENTRY(envvar) envvar_next; }; STAILQ_HEAD(envvar_head, envvar) envvars; struct hint { char *hint_name; STAILQ_ENTRY(hint) hint_next; }; STAILQ_HEAD(hint_head, hint) hints; struct includepath { char *path; SLIST_ENTRY(includepath) path_next; }; SLIST_HEAD(, includepath) includepath; /* * Tag present in the kernconf.tmpl template file. It's mandatory for those * two strings to be the same. Otherwise you'll get into trouble. */ #define KERNCONFTAG "%%KERNCONFFILE%%" /* * Faked option to note, that the configuration file has been taken from the * kernel file and inclusion of DEFAULTS etc.. isn't nessesery, because we * already have a list of all required devices. */ #define OPT_AUTOGEN "CONFIG_AUTOGENERATED" extern char *ident; extern char kernconfstr[]; extern int do_trace; -extern int envmode; -extern int hintmode; extern int incignore; char *get_word(FILE *); char *get_quoted_word(FILE *); char *path(const char *); char *raisestr(char *); void remember(const char *); void moveifchanged(const char *, const char *); int yylex(void); void options(void); void makefile(void); void makeenv(void); void makehints(void); void headers(void); void cfgfile_add(const char *); void cfgfile_removeall(void); FILE *open_makefile_template(void); extern STAILQ_HEAD(device_head, device) dtab; extern char errbuf[80]; extern int yyline; extern const char *yyfile; extern STAILQ_HEAD(file_list_head, file_list) ftab; extern STAILQ_HEAD(files_name_head, files_name) fntab; extern int profiling; extern int debugging; extern int found_defaults; extern int maxusers; extern char *PREFIX; /* Config file name - for error messages */ extern char srcdir[]; /* root of the kernel source tree */ #define eq(a,b) (!strcmp(a,b)) #define ns(s) strdup(s) Index: head/usr.sbin/config/config.y =================================================================== --- head/usr.sbin/config/config.y (revision 335994) +++ head/usr.sbin/config/config.y (revision 335995) @@ -1,485 +1,482 @@ %union { char *str; int val; struct file_list *file; } %token ARCH %token COMMA %token CONFIG %token CPU %token NOCPU %token DEVICE %token NODEVICE %token ENV %token ENVVAR %token EQUALS %token PLUSEQUALS %token HINTS %token IDENT %token MAXUSERS %token PROFILE %token OPTIONS %token NOOPTION %token MAKEOPTIONS %token NOMAKEOPTION %token SEMICOLON %token INCLUDE %token FILES %token ENVLINE %token ID %token NUMBER %type Save_id %type Opt_value %type Dev %token PATH %{ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)config.y 8.1 (Berkeley) 6/6/93 * $FreeBSD$ */ #include #include #include #include #include #include "config.h" struct device_head dtab; char *ident; -int envmode; -int hintmode; +char *env; int yyline; const char *yyfile; struct file_list_head ftab; struct files_name_head fntab; char errbuf[80]; int maxusers; #define ns(s) strdup(s) int include(const char *, int); void yyerror(const char *s); int yywrap(void); static void newdev(char *name); static void newfile(char *name); static void newenvvar(char *name, bool is_file); static void rmdev_schedule(struct device_head *dh, char *name); static void newopt(struct opt_head *list, char *name, char *value, int append); static void rmopt_schedule(struct opt_head *list, char *name); static char * devopt(char *dev) { char *ret = malloc(strlen(dev) + 5); sprintf(ret, "DEV_%s", dev); raisestr(ret); return ret; } %} %% Configuration: Many_specs ; Many_specs: Many_specs Spec | /* lambda */ ; Spec: Device_spec SEMICOLON | Config_spec SEMICOLON | INCLUDE PATH SEMICOLON { if (incignore == 0) include($2, 0); }; | INCLUDE ID SEMICOLON { if (incignore == 0) include($2, 0); }; | FILES ID SEMICOLON { newfile($2); }; | SEMICOLON | error SEMICOLON ; Config_spec: ARCH Save_id { if (machinename != NULL && !eq($2, machinename)) errx(1, "%s:%d: only one machine directive is allowed", yyfile, yyline); machinename = $2; machinearch = $2; } | ARCH Save_id Save_id { if (machinename != NULL && !(eq($2, machinename) && eq($3, machinearch))) errx(1, "%s:%d: only one machine directive is allowed", yyfile, yyline); machinename = $2; machinearch = $3; } | CPU Save_id { struct cputype *cp = (struct cputype *)calloc(1, sizeof (struct cputype)); if (cp == NULL) err(EXIT_FAILURE, "calloc"); cp->cpu_name = $2; SLIST_INSERT_HEAD(&cputype, cp, cpu_next); } | NOCPU Save_id { struct cputype *cp, *cp2; SLIST_FOREACH_SAFE(cp, &cputype, cpu_next, cp2) { if (eq(cp->cpu_name, $2)) { SLIST_REMOVE(&cputype, cp, cputype, cpu_next); free(cp); } } } | OPTIONS Opt_list | NOOPTION NoOpt_list | MAKEOPTIONS Mkopt_list | NOMAKEOPTION Save_id { rmopt_schedule(&mkopt, $2); } | IDENT ID { ident = $2; } | System_spec | MAXUSERS NUMBER { maxusers = $2; } | PROFILE NUMBER { profiling = $2; } | ENV ID { newenvvar($2, true); } | ENVVAR ENVLINE { newenvvar($2, false); } | HINTS ID { struct hint *hint; hint = (struct hint *)calloc(1, sizeof (struct hint)); if (hint == NULL) err(EXIT_FAILURE, "calloc"); hint->hint_name = $2; STAILQ_INSERT_HEAD(&hints, hint, hint_next); - hintmode = 1; } System_spec: CONFIG System_id System_parameter_list { errx(1, "%s:%d: root/dump/swap specifications obsolete", yyfile, yyline); } | CONFIG System_id ; System_id: Save_id { newopt(&mkopt, ns("KERNEL"), $1, 0); }; System_parameter_list: System_parameter_list ID | ID ; Opt_list: Opt_list COMMA Option | Option ; NoOpt_list: NoOpt_list COMMA NoOption | NoOption ; Option: Save_id { newopt(&opt, $1, NULL, 0); if (strchr($1, '=') != NULL) errx(1, "%s:%d: The `=' in options should not be " "quoted", yyfile, yyline); } | Save_id EQUALS Opt_value { newopt(&opt, $1, $3, 0); } ; NoOption: Save_id { rmopt_schedule(&opt, $1); }; Opt_value: ID { $$ = $1; } | NUMBER { char buf[80]; (void) snprintf(buf, sizeof(buf), "%d", $1); $$ = ns(buf); } ; Save_id: ID { $$ = $1; } ; Mkopt_list: Mkopt_list COMMA Mkoption | Mkoption ; Mkoption: Save_id { newopt(&mkopt, $1, ns(""), 0); } | Save_id EQUALS { newopt(&mkopt, $1, ns(""), 0); } | Save_id EQUALS Opt_value { newopt(&mkopt, $1, $3, 0); } | Save_id PLUSEQUALS Opt_value { newopt(&mkopt, $1, $3, 1); } ; Dev: ID { $$ = $1; } ; Device_spec: DEVICE Dev_list | NODEVICE NoDev_list ; Dev_list: Dev_list COMMA Device | Device ; NoDev_list: NoDev_list COMMA NoDevice | NoDevice ; Device: Dev { newopt(&opt, devopt($1), ns("1"), 0); /* and the device part */ newdev($1); } NoDevice: Dev { char *s = devopt($1); rmopt_schedule(&opt, s); free(s); /* and the device part */ rmdev_schedule(&dtab, $1); } ; %% void yyerror(const char *s) { errx(1, "%s:%d: %s", yyfile, yyline + 1, s); } int yywrap(void) { if (found_defaults) { if (freopen(PREFIX, "r", stdin) == NULL) err(2, "%s", PREFIX); yyfile = PREFIX; yyline = 0; found_defaults = 0; return 0; } return 1; } /* * Add a new file to the list of files. */ static void newfile(char *name) { struct files_name *nl; nl = (struct files_name *) calloc(1, sizeof *nl); if (nl == NULL) err(EXIT_FAILURE, "calloc"); nl->f_name = name; STAILQ_INSERT_TAIL(&fntab, nl, f_next); } static void newenvvar(char *name, bool is_file) { struct envvar *envvar; envvar = (struct envvar *)calloc(1, sizeof (struct envvar)); if (envvar == NULL) err(EXIT_FAILURE, "calloc"); envvar->env_str = name; envvar->env_is_file = is_file; STAILQ_INSERT_HEAD(&envvars, envvar, envvar_next); - envmode = 1; } /* * Find a device in the list of devices. */ static struct device * finddev(struct device_head *dlist, char *name) { struct device *dp; STAILQ_FOREACH(dp, dlist, d_next) if (eq(dp->d_name, name)) return (dp); return (NULL); } /* * Add a device to the list of devices. */ static void newdev(char *name) { struct device *np; if (finddev(&dtab, name)) { fprintf(stderr, "WARNING: duplicate device `%s' encountered.\n", name); return; } np = (struct device *) calloc(1, sizeof *np); if (np == NULL) err(EXIT_FAILURE, "calloc"); np->d_name = name; STAILQ_INSERT_TAIL(&dtab, np, d_next); } /* * Schedule a device to removal. */ static void rmdev_schedule(struct device_head *dh, char *name) { struct device *dp; dp = finddev(dh, name); if (dp != NULL) { STAILQ_REMOVE(dh, dp, device, d_next); free(dp->d_name); free(dp); } } /* * Find an option in the list of options. */ static struct opt * findopt(struct opt_head *list, char *name) { struct opt *op; SLIST_FOREACH(op, list, op_next) if (eq(op->op_name, name)) return (op); return (NULL); } /* * Add an option to the list of options. */ static void newopt(struct opt_head *list, char *name, char *value, int append) { struct opt *op, *op2; /* * Ignore inclusions listed explicitly for configuration files. */ if (eq(name, OPT_AUTOGEN)) { incignore = 1; return; } op2 = findopt(list, name); if (op2 != NULL && !append) { fprintf(stderr, "WARNING: duplicate option `%s' encountered.\n", name); return; } op = (struct opt *)calloc(1, sizeof (struct opt)); if (op == NULL) err(EXIT_FAILURE, "calloc"); op->op_name = name; op->op_ownfile = 0; op->op_value = value; if (op2 != NULL) { while (SLIST_NEXT(op2, op_append) != NULL) op2 = SLIST_NEXT(op2, op_append); SLIST_NEXT(op2, op_append) = op; } else SLIST_INSERT_HEAD(list, op, op_next); } /* * Remove an option from the list of options. */ static void rmopt_schedule(struct opt_head *list, char *name) { struct opt *op; op = findopt(list, name); if (op != NULL) { SLIST_REMOVE(list, op, opt, op_next); free(op->op_name); free(op); } } Index: head/usr.sbin/config/mkmakefile.c =================================================================== --- head/usr.sbin/config/mkmakefile.c (revision 335994) +++ head/usr.sbin/config/mkmakefile.c (revision 335995) @@ -1,819 +1,817 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)mkmakefile.c 8.1 (Berkeley) 6/6/93"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ /* * Build the makefile for the system, from * the information in the files files and the * additional files for the machine being compiled to. */ #include #include #include #include #include #include #include #include "y.tab.h" #include "config.h" #include "configvers.h" static char *tail(char *); static void do_clean(FILE *); static void do_rules(FILE *); static void do_xxfiles(char *, FILE *); static void do_objs(FILE *); static void do_before_depend(FILE *); static int opteq(const char *, const char *); static void read_files(void); static void errout(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); exit(1); } /* * Lookup a file, by name. */ static struct file_list * fl_lookup(char *file) { struct file_list *fp; STAILQ_FOREACH(fp, &ftab, f_next) { if (eq(fp->f_fn, file)) return (fp); } return (0); } /* * Make a new file list entry */ static struct file_list * new_fent(void) { struct file_list *fp; fp = (struct file_list *) calloc(1, sizeof *fp); if (fp == NULL) err(EXIT_FAILURE, "calloc"); STAILQ_INSERT_TAIL(&ftab, fp, f_next); return (fp); } /* * Open the correct Makefile and return it, or error out. */ FILE * open_makefile_template(void) { FILE *ifp; char line[BUFSIZ]; snprintf(line, sizeof(line), "../../conf/Makefile.%s", machinename); ifp = fopen(line, "r"); if (ifp == NULL) { snprintf(line, sizeof(line), "Makefile.%s", machinename); ifp = fopen(line, "r"); } if (ifp == NULL) err(1, "%s", line); return (ifp); } /* * Build the makefile from the skeleton */ void makefile(void) { FILE *ifp, *ofp; char line[BUFSIZ]; struct opt *op, *t; read_files(); ifp = open_makefile_template(); ofp = fopen(path("Makefile.new"), "w"); if (ofp == NULL) err(1, "%s", path("Makefile.new")); fprintf(ofp, "KERN_IDENT=%s\n", ident); fprintf(ofp, "MACHINE=%s\n", machinename); fprintf(ofp, "MACHINE_ARCH=%s\n", machinearch); SLIST_FOREACH_SAFE(op, &mkopt, op_next, t) { fprintf(ofp, "%s=%s", op->op_name, op->op_value); while ((op = SLIST_NEXT(op, op_append)) != NULL) fprintf(ofp, " %s", op->op_value); fprintf(ofp, "\n"); } if (debugging) fprintf(ofp, "DEBUG=-g\n"); if (profiling) fprintf(ofp, "PROFLEVEL=%d\n", profiling); if (*srcdir != '\0') fprintf(ofp,"S=%s\n", srcdir); while (fgets(line, BUFSIZ, ifp) != NULL) { if (*line != '%') { fprintf(ofp, "%s", line); continue; } if (eq(line, "%BEFORE_DEPEND\n")) do_before_depend(ofp); else if (eq(line, "%OBJS\n")) do_objs(ofp); else if (strncmp(line, "%FILES.", 7) == 0) do_xxfiles(line, ofp); else if (eq(line, "%RULES\n")) do_rules(ofp); else if (eq(line, "%CLEAN\n")) do_clean(ofp); else if (strncmp(line, "%VERSREQ=", 9) == 0) line[0] = '\0'; /* handled elsewhere */ else fprintf(stderr, "Unknown %% construct in generic makefile: %s", line); } (void) fclose(ifp); (void) fclose(ofp); moveifchanged(path("Makefile.new"), path("Makefile")); } /* * Build hints.c from the skeleton */ void makehints(void) { FILE *ifp, *ofp; char line[BUFSIZ]; char *s; struct hint *hint; ofp = fopen(path("hints.c.new"), "w"); if (ofp == NULL) err(1, "%s", path("hints.c.new")); fprintf(ofp, "#include \n"); fprintf(ofp, "#include \n"); fprintf(ofp, "\n"); - fprintf(ofp, "int hintmode = %d;\n", hintmode); fprintf(ofp, "char static_hints[] = {\n"); STAILQ_FOREACH(hint, &hints, hint_next) { ifp = fopen(hint->hint_name, "r"); if (ifp == NULL) err(1, "%s", hint->hint_name); while (fgets(line, BUFSIZ, ifp) != NULL) { /* zap trailing CR and/or LF */ while ((s = strrchr(line, '\n')) != NULL) *s = '\0'; while ((s = strrchr(line, '\r')) != NULL) *s = '\0'; /* remove # comments */ s = strchr(line, '#'); if (s) *s = '\0'; /* remove any whitespace and " characters */ s = line; while (*s) { if (*s == ' ' || *s == '\t' || *s == '"') { while (*s) { s[0] = s[1]; s++; } /* start over */ s = line; continue; } s++; } /* anything left? */ if (*line == '\0') continue; fprintf(ofp, "\"%s\\0\"\n", line); } fclose(ifp); } fprintf(ofp, "\"\\0\"\n};\n"); fclose(ofp); moveifchanged(path("hints.c.new"), path("hints.c")); } static void sanitize_envline(char *result, const char *src) { const char *eq; char c, *dst; bool leading; /* If there is no '=' it's not a well-formed name=value line. */ if ((eq = strchr(src, '=')) == NULL) { *result = 0; return; } dst = result; /* Copy chars before the '=', skipping any leading spaces/quotes. */ leading = true; while (src < eq) { c = *src++; if (leading && (isspace(c) || c == '"')) continue; *dst++ = c; leading = false; } /* If it was all leading space, we don't have a well-formed line. */ if (leading) { *result = 0; return; } /* Trim spaces/quotes immediately before the '=', then copy the '='. */ while (isspace(dst[-1]) || dst[-1] == '"') --dst; *dst++ = *src++; /* Copy chars after the '=', skipping any leading whitespace. */ leading = true; while ((c = *src++) != 0) { if (leading && (isspace(c) || c == '"')) continue; *dst++ = c; leading = false; } /* If it was all leading space, it's a valid 'var=' (nil value). */ if (leading) { *dst = 0; return; } /* Trim trailing whitespace and quotes. */ while (isspace(dst[-1]) || dst[-1] == '"') --dst; *dst = 0; } /* * Build env.c from the skeleton */ void makeenv(void) { FILE *ifp, *ofp; char line[BUFSIZ], result[BUFSIZ], *linep; struct envvar *envvar; ofp = fopen(path("env.c.new"), "w"); if (ofp == NULL) err(1, "%s", path("env.c.new")); fprintf(ofp, "#include \n"); fprintf(ofp, "#include \n"); fprintf(ofp, "\n"); - fprintf(ofp, "int envmode = %d;\n", envmode); fprintf(ofp, "char static_env[] = {\n"); STAILQ_FOREACH(envvar, &envvars, envvar_next) { if (envvar->env_is_file) { ifp = fopen(envvar->env_str, "r"); if (ifp == NULL) err(1, "%s", envvar->env_str); while (fgets(line, BUFSIZ, ifp) != NULL) { sanitize_envline(result, line); /* anything left? */ if (*result == '\0') continue; fprintf(ofp, "\"%s\\0\"\n", result); } fclose(ifp); } else { linep = envvar->env_str; sanitize_envline(result, linep); if (*result == '\0') continue; fprintf(ofp, "\"%s\\0\"\n", result); } } fprintf(ofp, "\"\\0\"\n};\n"); fclose(ofp); moveifchanged(path("env.c.new"), path("env.c")); } static void read_file(char *fname) { char ifname[MAXPATHLEN]; FILE *fp; struct file_list *tp; struct device *dp; struct opt *op; char *wd, *this, *compilewith, *depends, *clean, *warning; const char *objprefix; int compile, match, nreqs, std, filetype, not, imp_rule, no_obj, before_depend, nowerror; fp = fopen(fname, "r"); if (fp == NULL) err(1, "%s", fname); next: /* * include "filename" * filename [ standard | optional ] * [ dev* [ | dev* ... ] | profiling-routine ] [ no-obj ] * [ compile-with "compile rule" [no-implicit-rule] ] * [ dependency "dependency-list"] [ before-depend ] * [ clean "file-list"] [ warning "text warning" ] * [ obj-prefix "file prefix"] */ wd = get_word(fp); if (wd == (char *)EOF) { (void) fclose(fp); return; } if (wd == NULL) goto next; if (wd[0] == '#') { while (((wd = get_word(fp)) != (char *)EOF) && wd) ; goto next; } if (eq(wd, "include")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: missing include filename.\n", fname); (void) snprintf(ifname, sizeof(ifname), "../../%s", wd); read_file(ifname); while (((wd = get_word(fp)) != (char *)EOF) && wd) ; goto next; } this = ns(wd); wd = get_word(fp); if (wd == (char *)EOF) return; if (wd == NULL) errout("%s: No type for %s.\n", fname, this); tp = fl_lookup(this); compile = 0; match = 1; nreqs = 0; compilewith = 0; depends = 0; clean = 0; warning = 0; std = 0; imp_rule = 0; no_obj = 0; before_depend = 0; nowerror = 0; not = 0; filetype = NORMAL; objprefix = ""; if (eq(wd, "standard")) std = 1; else if (!eq(wd, "optional")) errout("%s: \"%s\" %s must be optional or standard\n", fname, wd, this); for (wd = get_word(fp); wd; wd = get_word(fp)) { if (wd == (char *)EOF) return; if (eq(wd, "!")) { not = 1; continue; } if (eq(wd, "|")) { if (nreqs == 0) errout("%s: syntax error describing %s\n", fname, this); compile += match; match = 1; nreqs = 0; continue; } if (eq(wd, "no-obj")) { no_obj++; continue; } if (eq(wd, "no-implicit-rule")) { if (compilewith == NULL) errout("%s: alternate rule required when " "\"no-implicit-rule\" is specified for" " %s.\n", fname, this); imp_rule++; continue; } if (eq(wd, "before-depend")) { before_depend++; continue; } if (eq(wd, "dependency")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: %s missing dependency string.\n", fname, this); depends = ns(wd); continue; } if (eq(wd, "clean")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: %s missing clean file list.\n", fname, this); clean = ns(wd); continue; } if (eq(wd, "compile-with")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: %s missing compile command string.\n", fname, this); compilewith = ns(wd); continue; } if (eq(wd, "warning")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: %s missing warning text string.\n", fname, this); warning = ns(wd); continue; } if (eq(wd, "obj-prefix")) { wd = get_quoted_word(fp); if (wd == (char *)EOF || wd == NULL) errout("%s: %s missing object prefix string.\n", fname, this); objprefix = ns(wd); continue; } if (eq(wd, "nowerror")) { nowerror = 1; continue; } if (eq(wd, "local")) { filetype = LOCAL; continue; } if (eq(wd, "no-depend")) { filetype = NODEPEND; continue; } nreqs++; if (eq(wd, "profiling-routine")) { filetype = PROFILING; continue; } if (std) errout("standard entry %s has optional inclusion specifier %s!\n", this, wd); STAILQ_FOREACH(dp, &dtab, d_next) if (eq(dp->d_name, wd)) { if (not) match = 0; else dp->d_done |= DEVDONE; goto nextparam; } SLIST_FOREACH(op, &opt, op_next) if (op->op_value == 0 && opteq(op->op_name, wd)) { if (not) match = 0; goto nextparam; } match &= not; nextparam:; not = 0; } compile += match; if (compile && tp == NULL) { if (std == 0 && nreqs == 0) errout("%s: what is %s optional on?\n", fname, this); if (filetype == PROFILING && profiling == 0) goto next; tp = new_fent(); tp->f_fn = this; tp->f_type = filetype; if (filetype == LOCAL) tp->f_srcprefix = ""; else tp->f_srcprefix = "$S/"; if (imp_rule) tp->f_flags |= NO_IMPLCT_RULE; if (no_obj) tp->f_flags |= NO_OBJ; if (before_depend) tp->f_flags |= BEFORE_DEPEND; if (nowerror) tp->f_flags |= NOWERROR; tp->f_compilewith = compilewith; tp->f_depends = depends; tp->f_clean = clean; tp->f_warn = warning; tp->f_objprefix = objprefix; } goto next; } /* * Read in the information about files used in making the system. * Store it in the ftab linked list. */ static void read_files(void) { char fname[MAXPATHLEN]; struct files_name *nl, *tnl; (void) snprintf(fname, sizeof(fname), "../../conf/files"); read_file(fname); (void) snprintf(fname, sizeof(fname), "../../conf/files.%s", machinename); read_file(fname); for (nl = STAILQ_FIRST(&fntab); nl != NULL; nl = tnl) { read_file(nl->f_name); tnl = STAILQ_NEXT(nl, f_next); free(nl->f_name); free(nl); } } static int opteq(const char *cp, const char *dp) { char c, d; for (; ; cp++, dp++) { if (*cp != *dp) { c = isupper(*cp) ? tolower(*cp) : *cp; d = isupper(*dp) ? tolower(*dp) : *dp; if (c != d) return (0); } if (*cp == 0) return (1); } } static void do_before_depend(FILE *fp) { struct file_list *tp; int lpos, len; fputs("BEFORE_DEPEND=", fp); lpos = 15; STAILQ_FOREACH(tp, &ftab, f_next) if (tp->f_flags & BEFORE_DEPEND) { len = strlen(tp->f_fn); if ((len = 3 + len) + lpos > 72) { lpos = 8; fputs("\\\n\t", fp); } if (tp->f_flags & NO_IMPLCT_RULE) fprintf(fp, "%s ", tp->f_fn); else fprintf(fp, "%s%s ", tp->f_srcprefix, tp->f_fn); lpos += len + 1; } if (lpos != 8) putc('\n', fp); } static void do_objs(FILE *fp) { struct file_list *tp; int lpos, len; char *cp, och, *sp; fprintf(fp, "OBJS="); lpos = 6; STAILQ_FOREACH(tp, &ftab, f_next) { if (tp->f_flags & NO_OBJ) continue; sp = tail(tp->f_fn); cp = sp + (len = strlen(sp)) - 1; och = *cp; *cp = 'o'; len += strlen(tp->f_objprefix); if (len + lpos > 72) { lpos = 8; fprintf(fp, "\\\n\t"); } fprintf(fp, "%s%s ", tp->f_objprefix, sp); lpos += len + 1; *cp = och; } if (lpos != 8) putc('\n', fp); } static void do_xxfiles(char *tag, FILE *fp) { struct file_list *tp; int lpos, len, slen; char *suff, *SUFF; if (tag[strlen(tag) - 1] == '\n') tag[strlen(tag) - 1] = '\0'; suff = ns(tag + 7); SUFF = ns(suff); raisestr(SUFF); slen = strlen(suff); fprintf(fp, "%sFILES=", SUFF); free(SUFF); lpos = 8; STAILQ_FOREACH(tp, &ftab, f_next) if (tp->f_type != NODEPEND) { len = strlen(tp->f_fn); if (tp->f_fn[len - slen - 1] != '.') continue; if (strcasecmp(&tp->f_fn[len - slen], suff) != 0) continue; if ((len = 3 + len) + lpos > 72) { lpos = 8; fputs("\\\n\t", fp); } fprintf(fp, "%s%s ", tp->f_srcprefix, tp->f_fn); lpos += len + 1; } free(suff); if (lpos != 8) putc('\n', fp); } static char * tail(char *fn) { char *cp; cp = strrchr(fn, '/'); if (cp == NULL) return (fn); return (cp+1); } /* * Create the makerules for each file * which is part of the system. */ static void do_rules(FILE *f) { char *cp, *np, och; struct file_list *ftp; char *compilewith; char cmd[128]; STAILQ_FOREACH(ftp, &ftab, f_next) { if (ftp->f_warn) fprintf(stderr, "WARNING: %s\n", ftp->f_warn); cp = (np = ftp->f_fn) + strlen(ftp->f_fn) - 1; och = *cp; if (ftp->f_flags & NO_IMPLCT_RULE) { if (ftp->f_depends) fprintf(f, "%s%s: %s\n", ftp->f_objprefix, np, ftp->f_depends); else fprintf(f, "%s%s: \n", ftp->f_objprefix, np); } else { *cp = '\0'; if (och == 'o') { fprintf(f, "%s%so:\n\t-cp %s%so .\n\n", ftp->f_objprefix, tail(np), ftp->f_srcprefix, np); continue; } if (ftp->f_depends) { fprintf(f, "%s%so: %s%s%c %s\n", ftp->f_objprefix, tail(np), ftp->f_srcprefix, np, och, ftp->f_depends); } else { fprintf(f, "%s%so: %s%s%c\n", ftp->f_objprefix, tail(np), ftp->f_srcprefix, np, och); } } compilewith = ftp->f_compilewith; if (compilewith == NULL) { const char *ftype = NULL; switch (ftp->f_type) { case NORMAL: ftype = "NORMAL"; break; case PROFILING: if (!profiling) continue; ftype = "PROFILE"; break; default: fprintf(stderr, "config: don't know rules for %s\n", np); break; } snprintf(cmd, sizeof(cmd), "${%s_%c%s}", ftype, toupper(och), ftp->f_flags & NOWERROR ? "_NOWERROR" : ""); compilewith = cmd; } *cp = och; if (strlen(ftp->f_objprefix)) fprintf(f, "\t%s %s%s\n", compilewith, ftp->f_srcprefix, np); else fprintf(f, "\t%s\n", compilewith); if (!(ftp->f_flags & NO_OBJ)) fprintf(f, "\t${NORMAL_CTFCONVERT}\n\n"); else fprintf(f, "\n"); } } static void do_clean(FILE *fp) { struct file_list *tp; int lpos, len; fputs("CLEAN=", fp); lpos = 7; STAILQ_FOREACH(tp, &ftab, f_next) if (tp->f_clean) { len = strlen(tp->f_clean); if (len + lpos > 72) { lpos = 8; fputs("\\\n\t", fp); } fprintf(fp, "%s ", tp->f_clean); lpos += len + 1; } if (lpos != 8) putc('\n', fp); } char * raisestr(char *str) { char *cp = str; while (*str) { if (islower(*str)) *str = toupper(*str); str++; } return (cp); }