sysvinit/src/consoles.c
Jesse Smith ab53132154 Removed old original source file that was left behind by
patch action.

Fixed typos in source code, comments, manual pages, and
changelog. Corrections provided by Jens Schleusener of
FOSSIES (fossies.org).
2020-11-17 20:31:37 -04:00

511 lines
10 KiB
C

/*
* consoles.c Routines to detect the system consoles
*
* Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* Author: Werner Fink <werner@suse.de>
*/
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/ttydefaults.h>
#ifdef __linux__
# include <sys/vt.h>
# include <sys/kd.h>
# include <linux/serial.h>
#include <sys/sysmacros.h>
#endif
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include "consoles.h"
#ifdef __linux__
# include <linux/major.h>
#endif
#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
# ifndef typeof
# define typeof __typeof__
# endif
# ifndef restrict
# define restrict __restrict__
# endif
#endif
#define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
struct console *consoles;
/*
* Read and allocate one line from file,
* the caller has to free the result
*/
static
#ifdef __GNUC__
__attribute__((__nonnull__))
#endif
char *oneline(const char *file)
{
FILE *fp;
char *ret = (char*)0, *nl;
size_t len = 0;
if ((fp = fopen(file, "re")) == (FILE*)0)
goto err;
if (getline(&ret, &len, fp) < 0)
goto out;
if (len)
ret[len-1] = '\0';
if ((nl = strchr(ret, '\n')))
*nl = '\0';
out:
fclose(fp);
err:
return ret;
}
#ifdef __linux__
/*
* Read and determine active attribute for tty below
* /sys/class/tty, the caller has to free the result.
*/
static
__attribute__((__malloc__))
char *actattr(const char *tty)
{
char *ret = (char*)0;
char *path;
if (!tty || *tty == '\0')
goto err;
if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0)
goto err;
if ((ret = oneline(path)) == (char*)0)
goto out;
out:
free(path);
err:
return ret;
}
/*
* Read and determine device attribute for tty below
* /sys/class/tty.
*/
static
dev_t devattr(const char *tty)
{
unsigned int maj, min;
dev_t dev = 0;
char *path, *value;
if (!tty || *tty == '\0')
goto err;
if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0)
goto err;
if ((value = oneline(path)) == (char*)0)
goto out;
if (sscanf(value, "%u:%u", &maj, &min) == 2)
dev = makedev(maj, min);
free(value);
out:
free(path);
err:
return dev;
}
#endif /* __linux__ */
/*
* Search below /dev for the character device in
* the local `dev_t comparedev' variable.
*/
static dev_t comparedev;
static
#ifdef __GNUC__
__attribute__((__nonnull__,__malloc__,__hot__))
#endif
char* scandev(DIR *dir)
{
char *name = (char*)0;
struct dirent *dent;
int fd;
fd = dirfd(dir);
rewinddir(dir);
while ((dent = readdir(dir))) {
char *path;
struct stat st;
if (fstatat(fd, dent->d_name, &st, 0) < 0)
continue;
if (!S_ISCHR(st.st_mode))
continue;
if (comparedev != st.st_rdev)
continue;
if (asprintf(&path, "/dev/%s", dent->d_name) < 0)
continue;
name = realpath(path, NULL);
free(path);
break;
}
return name;
}
/*
* Default control characters for an unknown terminal line.
*/
static
struct chardata initcp = {
CERASE,
CKILL,
CTRL('r'),
0
};
/*
* Allocate an aligned `struct console' memory area,
* initialize its default values, and append it to
* the global linked list.
*/
static int concount; /* Counter for console IDs */
static
#ifdef __GNUC__
__attribute__((__nonnull__,__hot__))
#endif
void consalloc(char * name)
{
struct console *restrict tail;
if (posix_memalign((void*)&tail, sizeof(void*), alignof(typeof(struct console))) != 0)
perror("memory allocation");
tail->next = (struct console*)0;
tail->tty = name;
tail->file = (FILE*)0;
tail->flags = 0;
tail->fd = -1;
tail->id = concount++;
tail->pid = 0;
memset(&tail->tio, 0, sizeof(tail->tio));
memcpy(&tail->cp, &initcp, sizeof(struct chardata));
if (!consoles)
consoles = tail;
else
consoles->next = tail;
}
/*
* Try to detect the real device(s) used for the system console
* /dev/console if but only if /dev/console is used. On Linux
* this can be more than one device, e.g. a serial line as well
* as a virtual console as well as a simple printer.
*
* Returns 1 if stdout and stderr should be reconnected and 0
* otherwise.
*/
int detect_consoles(const char *device, int fallback)
{
int fd, ret = 0;
#ifdef __linux__
char *attrib, *cmdline;
FILE *fc;
#endif
if (!device || *device == '\0')
fd = dup(fallback);
else {
fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
ret = 1;
}
if (fd >= 0) {
DIR *dir;
char *name;
struct stat st;
#ifdef TIOCGDEV
unsigned int devnum;
#endif
if (fstat(fd, &st) < 0) {
close(fd);
goto fallback;
}
comparedev = st.st_rdev;
if (ret && (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
dup2(fd, fallback);
#ifdef __linux__
/*
* Check if the device detection for Linux system console should be used.
*/
if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */
close(fd);
device = "/dev/tty";
goto fallback;
}
if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */
close(fd);
goto console;
}
if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */
close(fd);
device = "/dev/tty";
goto fallback;
}
if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */
struct vt_stat vt;
if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
close(fd);
goto fallback;
}
comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
}
#endif
#ifdef TIOCGDEV
if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
close(fd);
goto fallback;
}
comparedev = (dev_t)devnum;
#endif
close(fd);
dir = opendir("/dev");
if (!dir)
goto fallback;
name = scandev(dir);
if (name)
consalloc(name);
closedir(dir);
if (!consoles)
goto fallback;
return ret;
}
#ifdef __linux__
console:
/*
* Detection of devices used for Linux system consolei using
* the /proc/consoles API with kernel 2.6.38 and higher.
*/
if ((fc = fopen("/proc/consoles", "re"))) {
char fbuf[16];
int maj, min;
DIR *dir;
dir = opendir("/dev");
if (!dir) {
fclose(fc);
goto fallback;
}
while ((fscanf(fc, "%*s %*s (%[^)]) %d:%d", &fbuf[0], &maj, &min) == 3)) {
char * name;
if (!strchr(fbuf, 'E'))
continue;
comparedev = makedev(maj, min);
name = scandev(dir);
if (!name)
continue;
consalloc(name);
}
closedir(dir);
fclose(fc);
return ret;
}
/*
* Detection of devices used for Linux system console using
* the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
*/
if ((attrib = actattr("console"))) {
char *words = attrib, *token;
DIR *dir;
dir = opendir("/dev");
if (!dir) {
free(attrib);
goto fallback;
}
while ((token = strsep(&words, " \t\r\n"))) {
char * name;
if (*token == '\0')
continue;
comparedev = devattr(token);
if (comparedev == makedev(TTY_MAJOR, 0)) {
char *tmp = actattr(token);
if (!tmp)
continue;
comparedev = devattr(tmp);
free(tmp);
}
name = scandev(dir);
if (!name)
continue;
consalloc(name);
}
closedir(dir);
free(attrib);
if (!consoles)
goto fallback;
return ret;
}
/*
* Detection of devices used for Linux system console using
* kernel parameter on the kernels command line.
*/
if ((cmdline = oneline("/proc/cmdline"))) {
char *words= cmdline, *token;
DIR *dir;
dir = opendir("/dev");
if (!dir) {
free(cmdline);
goto fallback;
}
while ((token = strsep(&words, " \t\r\n"))) {
#ifdef TIOCGDEV
unsigned int devnum;
#else
struct vt_stat vt;
struct stat st;
#endif
char *colon, *name;
if (*token != 'c')
continue;
if (strncmp(token, "console=", 8) != 0)
continue;
token += 8;
if (strcmp(token, "brl") == 0)
token += 4;
if ((colon = strchr(token, ',')))
*colon = '\0';
if (asprintf(&name, "/dev/%s", token) < 0)
continue;
if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) {
free(name);
continue;
}
free(name);
#ifdef TIOCGDEV
if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
close(fd);
continue;
}
comparedev = (dev_t)devnum;
#else
if (fstat(fd, &st) < 0) {
close(fd);
continue;
}
comparedev = st.st_rdev;
if (comparedev == makedev(TTY_MAJOR, 0)) {
if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
close(fd);
continue;
}
comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
}
#endif
close(fd);
name = scandev(dir);
if (!name)
continue;
consalloc(name);
}
closedir(dir);
free(cmdline);
/*
* Detection of the device used for Linux system console using
* the ioctl TIOCGDEV if available (e.g. official 2.6.38).
*/
if (!consoles) {
#ifdef TIOCGDEV
unsigned int devnum;
const char *name;
if (!device || *device == '\0')
fd = dup(fallback);
else fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
goto fallback;
if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
close(fd);
goto fallback;
}
comparedev = (dev_t)devnum;
close(fd);
if (device && *device != '\0')
name = device;
else name = ttyname(fallback);
if (!name)
name = "/dev/tty1";
consalloc(strdup(name));
if (consoles) {
if (!device || *device == '\0')
consoles->fd = fallback;
return ret;
}
#endif
goto fallback;
}
return ret;
}
#endif /* __linux __ */
fallback:
if (fallback >= 0) {
const char *name;
if (device && *device != '\0')
name = device;
else name = ttyname(fallback);
if (!name)
name = "/dev/tty";
consalloc(strdup(name));
if (consoles)
consoles->fd = fallback;
}
return ret;
}