From: Karel Zak Date: Thu, 14 May 2026 09:21:48 +0000 (+0200) Subject: agetty: split out issue file functions to issuefile.c X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b694eee5340db69c01d5ebbc9eff1423b4eaf2c8;p=thirdparty%2Futil-linux.git agetty: split out issue file functions to issuefile.c Move issue file parsing, display and reload functions to a new issuefile.c file: agetty_print_issue_file(), agetty_eval_issue_file(), agetty_show_issue(), agetty_issue_is_changed(), agetty_reload(), read_os_release(), output_special_char(), and all issuedir/issuefile read helpers and network interface display functions. Keep do_prompt(), get_logname() and wait_for_term_input() in agetty.c as they are tightly coupled to the main login flow. Signed-off-by: Karel Zak --- diff --git a/agetty-cmd/Makemodule.am b/agetty-cmd/Makemodule.am index 875ab41ae..79b173c70 100644 --- a/agetty-cmd/Makemodule.am +++ b/agetty-cmd/Makemodule.am @@ -6,6 +6,7 @@ dist_noinst_DATA += term-utils/agetty.8.adoc agetty_SOURCES = agetty-cmd/agetty.c \ agetty-cmd/agetty.h \ agetty-cmd/credentials.c \ + agetty-cmd/issuefile.c \ agetty-cmd/tty.c \ agetty-cmd/utils.c diff --git a/agetty-cmd/agetty.c b/agetty-cmd/agetty.c index b3416383c..ff33cffc8 100644 --- a/agetty-cmd/agetty.c +++ b/agetty-cmd/agetty.c @@ -100,9 +100,6 @@ # include static int inotify_fd = AGETTY_RELOAD_FDNONE; #endif -#ifdef USE_NETLINK -static uint32_t netlink_groups; -#endif #define serial_tty_option(opt, flag) \ (((opt)->flags & (F_VCONSOLE|(flag))) == (flag)) @@ -110,8 +107,7 @@ static uint32_t netlink_groups; static void init_special_char(char* arg, struct agetty_options *op); static void parse_args(int argc, char **argv, struct agetty_options *op); static void parse_speeds(struct agetty_options *op, char *arg); -static void output_special_char (struct agetty_issue *ie, unsigned char c, struct agetty_options *op, - struct termios *tp, FILE *fp); +static int wait_for_term_input(struct agetty_issue *ie, int fd); static void do_prompt(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp); static char *get_logname(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp, struct chardata *cp); @@ -122,10 +118,6 @@ static ssize_t append(char *dest, size_t len, const char *sep, const char *src) #endif static void check_username (const char* nm); static void login_options_to_argv(char *argv[], int *argc, char *str, char *username); -static void reload_agettys(void); -static void print_issue_file(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp); -static void eval_issue_file(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp); -static void show_issue(struct agetty_options *op); /* Fake hostname for ut_host specified on command line. */ @@ -272,8 +264,8 @@ int main(int argc, char **argv) } if (options.flags & F_NOPROMPT) { /* --skip-login */ - eval_issue_file(&issue, &options, &termios); - print_issue_file(&issue, &options, &termios); + agetty_eval_issue_file(&issue, &options, &termios); + agetty_print_issue_file(&issue, &options, &termios); } else { /* regular (auto)login */ if ((options.flags & F_NOHOSTNAME) == 0 && @@ -283,7 +275,7 @@ int main(int argc, char **argv) if (options.autolog) { /* Autologin prompt */ - eval_issue_file(&issue, &options, &termios); + agetty_eval_issue_file(&issue, &options, &termios); do_prompt(&issue, &options, &termios); printf(_("%s%s (automatic login)\n"), LOGIN_PROMPT, options.autolog); @@ -668,7 +660,7 @@ static void parse_args(int argc, char **argv, struct agetty_options *op) op->killchars = optarg; break; case RELOAD_OPTION: - reload_agettys(); + agetty_reload(); exit(EXIT_SUCCESS); case LIST_SPEEDS_OPTION: agetty_list_speeds(); @@ -687,7 +679,7 @@ static void parse_args(int argc, char **argv, struct agetty_options *op) } if (opt_show_issue) { - show_issue(op); + agetty_show_issue(op); exit(EXIT_SUCCESS); } @@ -763,506 +755,21 @@ static void parse_speeds(struct agetty_options *op, char *arg) } -static char *read_os_release(struct agetty_options *op, const char *varname) -{ - int fd = -1; - struct stat st; - size_t varsz = strlen(varname); - char *p, *buf = NULL, *ret = NULL; - - /* read the file only once */ - if (!op->osrelease) { - fd = open(_PATH_OS_RELEASE_ETC, O_RDONLY); - if (fd == -1) { - fd = open(_PATH_OS_RELEASE_USR, O_RDONLY); - if (fd == -1) { - agetty_log_warn(_("cannot open os-release file")); - return NULL; - } - } - - if (fstat(fd, &st) < 0 || st.st_size > 4 * 1024 * 1024) - goto done; - - op->osrelease = malloc(st.st_size + 1); - if (!op->osrelease) - agetty_log_err(_("failed to allocate memory: %m")); - if (read_all(fd, op->osrelease, st.st_size) != (ssize_t) st.st_size) { - free(op->osrelease); - op->osrelease = NULL; - goto done; - } - op->osrelease[st.st_size] = 0; - } - buf = strdup(op->osrelease); - if (!buf) - agetty_log_err(_("failed to allocate memory: %m")); - p = buf; - - for (;;) { - char *eol, *eon; - - p += strspn(p, "\n\r"); - p += strspn(p, " \t\n\r"); - if (!*p) - break; - if (strspn(p, "#;\n") != 0) { - p += strcspn(p, "\n\r"); - continue; - } - if (strncmp(p, varname, varsz) != 0) { - p += strcspn(p, "\n\r"); - continue; - } - p += varsz; - p += strspn(p, " \t\n\r"); - - if (*p != '=') - continue; - - p += strspn(p, " \t\n\r=\""); - eol = p + strcspn(p, "\n\r"); - *eol = '\0'; - eon = eol-1; - while (eon > p) { - if (*eon == '\t' || *eon == ' ') { - eon--; - continue; - } - if (*eon == '"') { - *eon = '\0'; - break; - } - break; - } - free(ret); - ret = strdup(p); - if (!ret) - agetty_log_err(_("failed to allocate memory: %m")); - p = eol + 1; - } -done: - free(buf); - if (fd >= 0) - close(fd); - return ret; -} - -#ifdef AGETTY_RELOAD -static int wait_for_term_input(struct agetty_issue *ie, int fd) -{ - char buffer[sizeof(struct inotify_event) + NAME_MAX + 1]; - fd_set rfds; - - if (inotify_fd == AGETTY_RELOAD_FDNONE) { - /* make sure the reload trigger file exists */ - int reload_fd = open(AGETTY_RELOAD_FILENAME, - O_CREAT|O_CLOEXEC|O_RDONLY, - S_IRUSR|S_IWUSR); - - /* initialize reload trigger inotify stuff */ - if (reload_fd >= 0) { - inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if (inotify_fd > 0) - inotify_add_watch(inotify_fd, AGETTY_RELOAD_FILENAME, - IN_ATTRIB | IN_MODIFY); - - close(reload_fd); - } else - agetty_log_warn(_("failed to create reload file: %s: %m"), - AGETTY_RELOAD_FILENAME); - } - - while (1) { - int nfds = fd; - - FD_ZERO(&rfds); - FD_SET(fd, &rfds); - - if (inotify_fd >= 0) { - FD_SET(inotify_fd, &rfds); - nfds = max(nfds, inotify_fd); - } - -#ifdef USE_NETLINK - if (ie->nl.fd >= 0) { - FD_SET(ie->nl.fd, &rfds); - nfds = max(nfds, ie->nl.fd); - } -#endif - /* If waiting fails, just fall through, presumably reading input will fail */ - if (select(nfds + 1, &rfds, NULL, NULL, NULL) < 0) - return 1; - - if (FD_ISSET(fd, &rfds)) { - return 1; - - } - -#ifdef USE_NETLINK - if (ie->nl.fd >= 0 && FD_ISSET(ie->nl.fd, &rfds)) { - int rc; - - /* We are looping until it returns UL_NL_WOULDBLOCK. - * To prevent infinite loop, we are leaving on any other - * error except UL_NL_SOFT_ERROR. To prevent unability - * of further processing, we never exit. */ - do { - rc = ul_nl_process(&(ie->nl), UL_NL_ASYNC, - UL_NL_ONESHOT); - } - while (!rc || rc == UL_NL_SOFT_ERROR); - - /* Just drain the inotify buffer */ - } else -#endif /* USE_NETLINK */ - if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) { - while (read(inotify_fd, buffer, sizeof (buffer)) > 0); - } - - return 0; - } -} -#endif /* AGETTY_RELOAD */ - -#ifdef ISSUEDIR_SUPPORT -static int issuedir_filter(const struct dirent *d) -{ - size_t namesz; - -#ifdef _DIRENT_HAVE_D_TYPE - if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && - d->d_type != DT_LNK) - return 0; -#endif - if (*d->d_name == '.') - return 0; - - namesz = strlen(d->d_name); - if (!namesz || namesz < ISSUEDIR_EXTSIZ + 1 || - strcmp(d->d_name + (namesz - ISSUEDIR_EXTSIZ), "." ISSUEDIR_EXT) != 0) - return 0; - - /* Accept this */ - return 1; -} - - -static int issuefile_read_stream(struct agetty_issue *ie, FILE *f, struct agetty_options *op, struct termios *tp); - -/* returns: 0 on success, 1 cannot open, <0 on error - */ -static int issuedir_read(struct agetty_issue *ie, const char *dirname, - struct agetty_options *op, struct termios *tp) -{ - int dd, nfiles, i; - struct dirent **namelist = NULL; - - dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (dd < 0) - return 1; - - nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort); - if (nfiles <= 0) - goto done; - - ie->do_tcsetattr = 1; - - for (i = 0; i < nfiles; i++) { - struct dirent *d = namelist[i]; - FILE *f; - - f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); - if (f) { - issuefile_read_stream(ie, f, op, tp); - fclose(f); - } - } - - for (i = 0; i < nfiles; i++) - free(namelist[i]); - free(namelist); -done: - close(dd); - return 0; -} - -#else /* !ISSUEDIR_SUPPORT */ -static int issuedir_read(struct agetty_issue *ie __attribute__((__unused__)), - const char *dirname __attribute__((__unused__)), - struct agetty_options *op __attribute__((__unused__)), - struct termios *tp __attribute__((__unused__))) -{ - return 1; -} -#endif /* ISSUEDIR_SUPPORT */ - -#ifndef ISSUE_SUPPORT -static void print_issue_file(struct agetty_issue *ie __attribute__((__unused__)), - struct agetty_options *op, - struct termios *tp __attribute__((__unused__))) -{ - if ((op->flags & F_NONL) == 0) { - /* Issue not in use, start with a new line. */ - write_all(STDOUT_FILENO, "\r\n", 2); - } -} - -static void eval_issue_file(struct agetty_issue *ie __attribute__((__unused__)), - struct agetty_options *op __attribute__((__unused__)), - struct termios *tp __attribute__((__unused__))) -{ -} - -static void show_issue(struct agetty_options *op __attribute__((__unused__))) -{ -} - -#else /* ISSUE_SUPPORT */ - -static int issuefile_read_stream( - struct agetty_issue *ie, FILE *f, - struct agetty_options *op, struct termios *tp) -{ - struct stat st; - int c; - - if (fstat(fileno(f), &st) || !S_ISREG(st.st_mode)) - return 1; - - if (!ie->output) { - free(ie->mem); - ie->mem_sz = 0; - ie->mem = NULL; - ie->output = open_memstream(&ie->mem, &ie->mem_sz); - } - - while ((c = fgetc(f)) != EOF) { - if (c == '\\') - output_special_char(ie, fgetc(f), op, tp, f); - else - putc(c, ie->output); - } - - return 0; -} - -static int issuefile_read( - struct agetty_issue *ie, const char *filename, - struct agetty_options *op, struct termios *tp) -{ - FILE *f = fopen(filename, "r" UL_CLOEXECSTR); - int rc = 1; - - if (f) { - rc = issuefile_read_stream(ie, f, op, tp); - fclose(f); - } - return rc; -} - - -#ifdef AGETTY_RELOAD -static int issue_is_changed(struct agetty_issue *ie) -{ - if (ie->mem_old && ie->mem - && strcmp(ie->mem_old, ie->mem) == 0) { - free(ie->mem_old); - ie->mem_old = ie->mem; - ie->mem = NULL; - ie->mem_sz = 0; - return 0; - } - - return 1; -} -#endif - -static void print_issue_file(struct agetty_issue *ie, - struct agetty_options *op, - struct termios *tp) -{ - int oflag = tp->c_oflag; /* Save current setting. */ - - if ((op->flags & F_NONL) == 0) { - /* Issue not in use, start with a new line. */ - write_all(STDOUT_FILENO, "\r\n", 2); - } - - if (ie->do_tcsetattr) { - if ((op->flags & F_VCONSOLE) == 0) { - /* Map new line in output to carriage return & new line. */ - tp->c_oflag |= (ONLCR | OPOST); - tcsetattr(STDIN_FILENO, TCSADRAIN, tp); - } - } - - if (ie->mem_sz && ie->mem) - write_all(STDOUT_FILENO, ie->mem, ie->mem_sz); - - if (ie->do_tcrestore) { - /* Restore settings. */ - tp->c_oflag = oflag; - /* Wait till output is gone. */ - tcsetattr(STDIN_FILENO, TCSADRAIN, tp); - } - -#ifdef AGETTY_RELOAD - free(ie->mem_old); - ie->mem_old = ie->mem; - ie->mem = NULL; - ie->mem_sz = 0; -#else - free(ie->mem); - ie->mem = NULL; - ie->mem_sz = 0; -#endif -} - -static void eval_issue_file(struct agetty_issue *ie, - struct agetty_options *op, - struct termios *tp) -{ - if (!(op->flags & F_ISSUE)) - goto done; - -#ifdef USE_NETLINK -/* TODO: - * Two pass processing for eval_issue_file() - * Implement pass 1: Just evaluate list of netlink_groups (IP protocols) and - * interfaces to monitor. - * That is why again label is here: netlink_groups will be re-evaluated and - * dump will be performed again. - */ - /* netlink_groups = 0; */ - netlink_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; - - /* Already initialized? */ - if (ie->nl.fd >= 0) - goto skip; - /* Prepare netlink. */ - ul_nl_init(&(ie->nl)); - if ((ul_netaddrq_init(&(ie->nl), NULL, NULL, (void *)ie))) - goto skip; - - /* Open netlink and create address list. */ - if (ul_nl_open(&(ie->nl), - RTMGRP_LINK | netlink_groups)) - goto skip; - if (ul_nl_request_dump(&(ie->nl), RTM_GETADDR)) - goto error; - if (ul_nl_process(&(ie->nl), UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE) - goto error; - goto skip; -error: - /* In case of any error, the addrq list is just empty, and we can use - * the code without any error checking. */ - ul_nl_close(&(ie->nl)); - ie->nl.fd = -1; -skip: -#endif /* USE_NETLINK */ - /* - * The custom issue file or directory list specified by: - * agetty --issue-file - * Note that nothing is printed if the file/dir does not exist. - */ - if (op->issue) { - char *list = strdup(op->issue); - char *file; - - if (!list) - agetty_log_err(_("failed to allocate memory: %m")); - - for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) { - struct stat st; - - if (stat(file, &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) - issuedir_read(ie, file, op, tp); - else - issuefile_read(ie, file, op, tp); - } - free(list); - goto done; - } - -#ifdef ISSUEDIR_SUPPORT - struct list_head file_list; - struct list_head *current = NULL; - char *name = NULL; - - /* Reading all issue files and concatenating all contents to one content. - * The ordering rules are defineded in: - * https://github.com/uapi-group/specifications/blob/main/specs/configuration_files_specification.md - * - * Note that _PATH_RUNSTATEDIR (/run) is always read by ul_configs_file_list(). - */ - ul_configs_file_list(&file_list, - NULL, - _PATH_SYSCONFDIR, - _PATH_RUNSTATEDIR, - _PATH_SYSCONFSTATICDIR, - "issue", - ISSUEDIR_EXT); - - while (ul_configs_next_filename(&file_list, ¤t, &name) == 0) { - issuefile_read(ie, name, op, tp); - } - - ul_configs_free_list(&file_list); -#endif - -done: - if (ie->output) { - fclose(ie->output); - ie->output = NULL; - } -} - -/* This is --show-issue backend, executed by normal user on the current - * terminal. - */ -static void show_issue(struct agetty_options *op) -{ - struct agetty_issue ie = { - .output = NULL, -#ifdef USE_NETLINK - .nl.fd = -1 -#endif - }; - struct termios tp; - - memset(&tp, 0, sizeof(struct termios)); - if (tcgetattr(STDIN_FILENO, &tp) < 0) - err(EXIT_FAILURE, _("failed to get terminal attributes: %m")); - - eval_issue_file(&ie, op, &tp); - - if (ie.mem_sz) - write_all(STDOUT_FILENO, ie.mem, ie.mem_sz); - if (ie.output) - fclose(ie.output); - free(ie.mem); -} - -#endif /* ISSUE_SUPPORT */ - /* Show login prompt, optionally preceded by /etc/issue contents. */ static void do_prompt(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp) { #ifdef AGETTY_RELOAD again: #endif - print_issue_file(ie, op, tp); + agetty_print_issue_file(ie, op, tp); if (op->flags & F_LOGINPAUSE) { puts(_("[press ENTER to login]")); #ifdef AGETTY_RELOAD /* reload issue */ if (!wait_for_term_input(ie, STDIN_FILENO)) { - eval_issue_file(ie, op, tp); - if (issue_is_changed(ie)) { + agetty_eval_issue_file(ie, op, tp); + if (agetty_issue_is_changed(ie)) { if ((op->flags & F_VCONSOLE) && (op->flags & F_NOCLEAR) == 0) agetty_termio_clear(STDOUT_FILENO); @@ -1378,7 +885,7 @@ static char *get_logname(struct agetty_issue *ie, struct agetty_options *op, str visual_bp = visual_widths; *bp = '\0'; - eval_issue_file(ie, op, tp); + agetty_eval_issue_file(ie, op, tp); while (*logname == '\0') { /* Write issue file and prompt */ do_prompt(ie, op, tp); @@ -1391,8 +898,8 @@ static char *get_logname(struct agetty_issue *ie, struct agetty_options *op, str */ if ((op->flags & F_VCONSOLE) == 0) sleep(1); - eval_issue_file(ie, op, tp); - if (!issue_is_changed(ie)) + agetty_eval_issue_file(ie, op, tp); + if (!agetty_issue_is_changed(ie)) goto no_reload; /* if (ie->nl.fd >= 0) ul_nl_close(&(ie->nl)); * ie->nl.fd = -1; */ @@ -1647,406 +1154,83 @@ static void __attribute__((__noreturn__)) usage(void) } -#ifdef USE_NETLINK -static void print_iface_best(struct agetty_issue *ie, - const char *ifname, - uint8_t ifa_family) -{ - struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX]; - struct ul_netaddrq_iface *ifaceq; - struct list_head *l; - enum ul_netaddrq_ip_rating threshold; - - if (!ie->nl.data_addr) - return; /* error: init failed */ - - if ((ifaceq = ul_netaddrq_iface_by_name(&(ie->nl), ifname))) - { - memset(best, 0, sizeof(best)); - if (ifa_family == AF_INET) - l = &(ifaceq->ip_quality_list_4); - else - /* if (ifa_family == AF_INET6) */ - l = &(ifaceq->ip_quality_list_6); - - threshold = - ul_netaddrq_iface_bestaddr(l, &best); - if (threshold != __ULNETLINK_RATING_MAX) - fputs(ul_nl_addr_ntop_address(best[threshold]->addr), - ie->output); - } -} - -static void print_addrq_bestofall(struct agetty_issue *ie, - uint8_t ifa_family) -{ - struct ul_netaddrq_iface *best_ifaceq; - enum ul_netaddrq_ip_rating threshold; - const char *best_ipp; - - if (!ie->nl.data_addr) - return; /* error: init failed */ - - best_ipp = ul_netaddrq_get_best_ipp(&(ie->nl), ifa_family, - &threshold, &best_ifaceq); - if (best_ipp) - fputs(best_ipp, ie->output); -} - -static void dump_iface_good(struct agetty_issue *ie, - struct ul_netaddrq_iface *ifaceq) -{ - struct ul_netaddrq_ip *best4[__ULNETLINK_RATING_MAX]; - struct ul_netaddrq_ip *best6[__ULNETLINK_RATING_MAX]; - struct list_head *li; - enum ul_netaddrq_ip_rating threshold = __ULNETLINK_RATING_MAX - 1; - enum ul_netaddrq_ip_rating fthreshold; /* per family threshold */ - bool first = true; - - memset(best4, 0, sizeof(best4)); - threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4), - &best4); - memset(best6, 0, sizeof(best6)); - fthreshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6), - &best6); - if (fthreshold < threshold) - threshold = fthreshold; - - list_for_each(li, &(ifaceq->ip_quality_list_4)) - { - struct ul_netaddrq_ip *ipq; - - ipq = list_entry(li, struct ul_netaddrq_ip, entry); - if (threshold <= ULNETLINK_RATING_SCOPE_LINK && - ( ipq->quality <= threshold || - /* Consider site addresses equally good as global */ - ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && - best4[threshold]) - { - if (first) - { - fprintf(ie->output, "%s: ", ifaceq->ifname); - first = false; - } - else - fprintf(ie->output, " "); - /* Write only the longest living temporary address */ - if (threshold == ULNETLINK_RATING_F_TEMPORARY) - { - fputs(ul_nl_addr_ntop_address(best4[ULNETLINK_RATING_F_TEMPORARY]->addr), - ie->output); - goto temp_cont4; - } - else - fputs(ul_nl_addr_ntop_address(ipq->addr), - ie->output); - } - temp_cont4:; - } - - list_for_each(li, &(ifaceq->ip_quality_list_6)) - { - struct ul_netaddrq_ip *ipq; - - ipq = list_entry(li, struct ul_netaddrq_ip, entry); - if (threshold <= ULNETLINK_RATING_SCOPE_LINK && - ( ipq->quality <= threshold || - /* Consider site addresses equally good as global */ - ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && - best6[threshold]) - { - if (first) - { - fprintf(ie->output, "%s: ", ifaceq->ifname); - first = false; - } - else - fprintf(ie->output, " "); - /* Write only the longest living temporary address */ - if (threshold == ULNETLINK_RATING_F_TEMPORARY) - { - fputs(ul_nl_addr_ntop_address(best6[ULNETLINK_RATING_F_TEMPORARY]->addr), - ie->output); - goto temp_cont6; - } - else - fputs(ul_nl_addr_ntop_address(ipq->addr), - ie->output); - } - temp_cont6:; - } - if (!first) - fputs("\n", ie->output); -} - -static void dump_iface_all(struct agetty_issue *ie, - struct ul_netaddrq_iface *ifaceq) -{ - struct list_head *li; - struct ul_netaddrq_ip *ipq; - bool first = true; - - list_for_each(li, &(ifaceq->ip_quality_list_4)) - { - ipq = list_entry(li, struct ul_netaddrq_ip, entry); - if (first) - { - fprintf(ie->output, "%s: ", ifaceq->ifname); - first = false; - } - else - fprintf(ie->output, " "); - fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); - } - list_for_each(li, &(ifaceq->ip_quality_list_6)) - { - ipq = list_entry(li, struct ul_netaddrq_ip, entry); - if (first) - { - fprintf(ie->output, "%s: ", ifaceq->ifname); - first = false; - } - else - fprintf(ie->output, " "); - fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); - } - if (!first) - fputs("\n", ie->output); -} -#endif /* USE_NETLINK */ -/* - * parses \x{argument}, if not argument specified then returns NULL, the @fd - * has to point to one char after the sequence (it means '{'). - */ -static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz) -{ - size_t i = 0; - int c = fgetc(fd); - - if (c == EOF || (unsigned char) c != '{') { - ungetc(c, fd); - return NULL; - } - - do { - c = fgetc(fd); - if (c == EOF) - return NULL; - if ((unsigned char) c != '}' && i < bufsz - 1) - buf[i++] = (unsigned char) c; - - } while ((unsigned char) c != '}'); - - buf[i] = '\0'; - return buf; -} - -static void output_special_char(struct agetty_issue *ie, - unsigned char c, - struct agetty_options *op, - struct termios *tp, - FILE *fp) +#ifdef AGETTY_RELOAD +static int wait_for_term_input(struct agetty_issue *ie, int fd) { - struct utsname uts; + char buffer[sizeof(struct inotify_event) + NAME_MAX + 1]; + fd_set rfds; - switch (c) { - case 'e': - { - char escname[UL_COLORNAME_MAXSZ]; + if (inotify_fd == AGETTY_RELOAD_FDNONE) { + /* make sure the reload trigger file exists */ + int reload_fd = open(AGETTY_RELOAD_FILENAME, + O_CREAT|O_CLOEXEC|O_RDONLY, + S_IRUSR|S_IWUSR); - if (get_escape_argument(fp, escname, sizeof(escname))) { - char *esc = color_get_sequence(escname); + /* initialize reload trigger inotify stuff */ + if (reload_fd >= 0) { + inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (inotify_fd > 0) + inotify_add_watch(inotify_fd, AGETTY_RELOAD_FILENAME, + IN_ATTRIB | IN_MODIFY); - if (esc) { - fputs(esc, ie->output); - free(esc); - } + close(reload_fd); } else - fputs("\033", ie->output); - break; - } - case 's': - uname(&uts); - fprintf(ie->output, "%s", uts.sysname); - break; - case 'n': - uname(&uts); - fprintf(ie->output, "%s", uts.nodename); - break; - case 'r': - uname(&uts); - fprintf(ie->output, "%s", uts.release); - break; - case 'v': - uname(&uts); - fprintf(ie->output, "%s", uts.version); - break; - case 'm': - uname(&uts); - fprintf(ie->output, "%s", uts.machine); - break; - case 'o': - { - char *dom = agetty_xgetdomainname(); - - fputs(dom ? dom : "unknown_domain", ie->output); - free(dom); - break; + agetty_log_warn(_("failed to create reload file: %s: %m"), + AGETTY_RELOAD_FILENAME); } - case 'O': - { - char *dom = NULL; - char *host = agetty_xgethostname(); - struct addrinfo hints, *info = NULL; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_CANONNAME; + while (1) { + int nfds = fd; - if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) { - char *canon; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); - if (info->ai_canonname && - (canon = strchr(info->ai_canonname, '.'))) - dom = canon + 1; + if (inotify_fd >= 0) { + FD_SET(inotify_fd, &rfds); + nfds = max(nfds, inotify_fd); } - fputs(dom ? dom : "unknown_domain", ie->output); - if (info) - freeaddrinfo(info); - free(host); - break; - } - case 'd': - case 't': - { - time_t now; - struct tm tm; - - time(&now); - localtime_r(&now, &tm); - - if (c == 'd') /* ISO 8601 */ - fprintf(ie->output, "%s %s %2d %d", - nl_langinfo(ABDAY_1 + tm.tm_wday), - nl_langinfo(ABMON_1 + tm.tm_mon), - tm.tm_mday, - tm.tm_year < 70 ? tm.tm_year + 2000 : - tm.tm_year + 1900); - else - fprintf(ie->output, "%02d:%02d:%02d", - tm.tm_hour, tm.tm_min, tm.tm_sec); - break; - } - case 'l': - fprintf (ie->output, "%s", op->tty); - break; - case 'b': - agetty_fprint_speed(ie->output, cfgetispeed(tp)); - break; - case 'S': - { - char *var = NULL, varname[64]; - - /* \S{varname} */ - if (get_escape_argument(fp, varname, sizeof(varname))) { - var = read_os_release(op, varname); - if (var) { - if (strcmp(varname, "ANSI_COLOR") == 0) - fprintf(ie->output, "\033[%sm", var); - else - fputs(var, ie->output); - } - /* \S */ - } else if ((var = read_os_release(op, "PRETTY_NAME"))) { - fputs(var, ie->output); - /* \S and PRETTY_NAME not found */ - } else { - uname(&uts); - fputs(uts.sysname, ie->output); +#ifdef USE_NETLINK + if (ie->nl.fd >= 0) { + FD_SET(ie->nl.fd, &rfds); + nfds = max(nfds, ie->nl.fd); } +#endif + /* If waiting fails, just fall through, presumably reading input will fail */ + if (select(nfds + 1, &rfds, NULL, NULL, NULL) < 0) + return 1; - free(var); + if (FD_ISSET(fd, &rfds)) { + return 1; - break; - } - case 'u': - case 'U': - { - int users = 0; -#ifdef USE_SYSTEMD - if (sd_booted() > 0) { - users = sd_get_sessions(NULL); - if (users < 0) - users = 0; - } else -#endif - { - users = 0; - struct utmpx *ut; - setutxent(); - while ((ut = getutxent())) - if (ut->ut_type == USER_PROCESS) - users++; - endutxent(); } - if (c == 'U') - fprintf(ie->output, P_("%d user", "%d users", users), users); - else - fprintf (ie->output, "%d ", users); - break; - } -#ifdef USE_NETLINK - case '4': - case '6': - { - char iface[IF_NAMESIZE]; - uint8_t ifa_family = c == '4' ? AF_INET : AF_INET6; - - if (get_escape_argument(fp, iface, sizeof(iface))) - print_iface_best(ie, iface, ifa_family); - else - print_addrq_bestofall(ie, ifa_family); - /* TODO: Move to pass 1 */ - if (c == '4') - netlink_groups |= RTMGRP_IPV4_IFADDR; - else - netlink_groups |= RTMGRP_IPV6_IFADDR; - break; - } - case 'a': - { - struct list_head *li; - struct ul_netaddrq_iface *ifaceq; +#ifdef USE_NETLINK + if (ie->nl.fd >= 0 && FD_ISSET(ie->nl.fd, &rfds)) { + int rc; - list_for_each_netaddrq_iface(li, &(ie->nl)) - { - ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); + /* We are looping until it returns UL_NL_WOULDBLOCK. + * To prevent infinite loop, we are leaving on any other + * error except UL_NL_SOFT_ERROR. To prevent unability + * of further processing, we never exit. */ + do { + rc = ul_nl_process(&(ie->nl), UL_NL_ASYNC, + UL_NL_ONESHOT); + } + while (!rc || rc == UL_NL_SOFT_ERROR); - dump_iface_good(ie, ifaceq); + /* Just drain the inotify buffer */ + } else +#endif /* USE_NETLINK */ + if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) { + while (read(inotify_fd, buffer, sizeof (buffer)) > 0); } - } - break; - case 'A': - { - struct list_head *li; - struct ul_netaddrq_iface *ifaceq; - - list_for_each_netaddrq_iface(li, &(ie->nl)) - { - ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); - dump_iface_all(ie, ifaceq); - } - } - break; -#endif /* USE_NETLINK */ - default: - putc(c, ie->output); - break; + return 0; } } +#endif /* AGETTY_RELOAD */ static void init_special_char(char* arg, struct agetty_options *op) { @@ -2146,21 +1330,3 @@ err: errno = EPERM; agetty_log_err(_("checkname failed: %m")); } - -static void reload_agettys(void) -{ -#ifdef AGETTY_RELOAD - int fd = open(AGETTY_RELOAD_FILENAME, O_CREAT|O_CLOEXEC|O_WRONLY, - S_IRUSR|S_IWUSR); - if (fd < 0) - err(EXIT_FAILURE, _("cannot open %s"), AGETTY_RELOAD_FILENAME); - - if (futimens(fd, NULL) < 0 || close(fd) < 0) - err(EXIT_FAILURE, _("cannot touch file %s"), - AGETTY_RELOAD_FILENAME); -#else - /* very unusual */ - errx(EXIT_FAILURE, _("--reload is unsupported on your system")); -#endif -} - diff --git a/agetty-cmd/agetty.h b/agetty-cmd/agetty.h index b00491113..fcef62955 100644 --- a/agetty-cmd/agetty.h +++ b/agetty-cmd/agetty.h @@ -140,4 +140,10 @@ extern void agetty_auto_baud(struct termios *tp); extern void agetty_next_speed(struct agetty_options *op, struct termios *tp); extern void agetty_erase_char(int visual_count, struct chardata *cp); +extern void agetty_print_issue_file(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp); +extern void agetty_eval_issue_file(struct agetty_issue *ie, struct agetty_options *op, struct termios *tp); +extern int agetty_issue_is_changed(struct agetty_issue *ie); +extern void agetty_show_issue(struct agetty_options *op); +extern void agetty_reload(void); + #endif /* UTIL_LINUX_AGETTY_H */ diff --git a/agetty-cmd/issuefile.c b/agetty-cmd/issuefile.c new file mode 100644 index 000000000..dc6d00dec --- /dev/null +++ b/agetty-cmd/issuefile.c @@ -0,0 +1,870 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "all-io.h" +#include "agetty.h" +#include "c.h" +#include "color-names.h" +#include "nls.h" +#include "fileutils.h" +#include "pathnames.h" +#include "widechar.h" + +#ifdef ISSUEDIR_SUPPORT +# include "configs.h" +# include +# define ISSUEDIR_EXT "issue" +# define ISSUEDIR_EXTSIZ sizeof(ISSUEDIR_EXT) +#endif + +#ifdef USE_SYSTEMD +# include +# include +#endif + +#ifdef USE_NETLINK +# include +# include +static uint32_t netlink_groups; +#endif + +static void output_special_char(struct agetty_issue *ie, unsigned char c, + struct agetty_options *op, struct termios *tp, FILE *fp); + +static char *read_os_release(struct agetty_options *op, const char *varname) +{ + int fd = -1; + struct stat st; + size_t varsz = strlen(varname); + char *p, *buf = NULL, *ret = NULL; + + /* read the file only once */ + if (!op->osrelease) { + fd = open(_PATH_OS_RELEASE_ETC, O_RDONLY); + if (fd == -1) { + fd = open(_PATH_OS_RELEASE_USR, O_RDONLY); + if (fd == -1) { + agetty_log_warn(_("cannot open os-release file")); + return NULL; + } + } + + if (fstat(fd, &st) < 0 || st.st_size > 4 * 1024 * 1024) + goto done; + + op->osrelease = malloc(st.st_size + 1); + if (!op->osrelease) + agetty_log_err(_("failed to allocate memory: %m")); + if (read_all(fd, op->osrelease, st.st_size) != (ssize_t) st.st_size) { + free(op->osrelease); + op->osrelease = NULL; + goto done; + } + op->osrelease[st.st_size] = 0; + } + buf = strdup(op->osrelease); + if (!buf) + agetty_log_err(_("failed to allocate memory: %m")); + p = buf; + + for (;;) { + char *eol, *eon; + + p += strspn(p, "\n\r"); + p += strspn(p, " \t\n\r"); + if (!*p) + break; + if (strspn(p, "#;\n") != 0) { + p += strcspn(p, "\n\r"); + continue; + } + if (strncmp(p, varname, varsz) != 0) { + p += strcspn(p, "\n\r"); + continue; + } + p += varsz; + p += strspn(p, " \t\n\r"); + + if (*p != '=') + continue; + + p += strspn(p, " \t\n\r=\""); + eol = p + strcspn(p, "\n\r"); + *eol = '\0'; + eon = eol-1; + while (eon > p) { + if (*eon == '\t' || *eon == ' ') { + eon--; + continue; + } + if (*eon == '"') { + *eon = '\0'; + break; + } + break; + } + free(ret); + ret = strdup(p); + if (!ret) + agetty_log_err(_("failed to allocate memory: %m")); + p = eol + 1; + } +done: + free(buf); + if (fd >= 0) + close(fd); + return ret; +} + + +#ifdef ISSUEDIR_SUPPORT +static int issuedir_filter(const struct dirent *d) +{ + size_t namesz; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && + d->d_type != DT_LNK) + return 0; +#endif + if (*d->d_name == '.') + return 0; + + namesz = strlen(d->d_name); + if (!namesz || namesz < ISSUEDIR_EXTSIZ + 1 || + strcmp(d->d_name + (namesz - ISSUEDIR_EXTSIZ), "." ISSUEDIR_EXT) != 0) + return 0; + + /* Accept this */ + return 1; +} + + +static int issuefile_read_stream(struct agetty_issue *ie, FILE *f, struct agetty_options *op, struct termios *tp); + +/* returns: 0 on success, 1 cannot open, <0 on error + */ +static int issuedir_read(struct agetty_issue *ie, const char *dirname, + struct agetty_options *op, struct termios *tp) +{ + int dd, nfiles, i; + struct dirent **namelist = NULL; + + dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dd < 0) + return 1; + + nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort); + if (nfiles <= 0) + goto done; + + ie->do_tcsetattr = 1; + + for (i = 0; i < nfiles; i++) { + struct dirent *d = namelist[i]; + FILE *f; + + f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + issuefile_read_stream(ie, f, op, tp); + fclose(f); + } + } + + for (i = 0; i < nfiles; i++) + free(namelist[i]); + free(namelist); +done: + close(dd); + return 0; +} + +#else /* !ISSUEDIR_SUPPORT */ +static int issuedir_read(struct agetty_issue *ie __attribute__((__unused__)), + const char *dirname __attribute__((__unused__)), + struct agetty_options *op __attribute__((__unused__)), + struct termios *tp __attribute__((__unused__))) +{ + return 1; +} +#endif /* ISSUEDIR_SUPPORT */ + +#ifndef ISSUE_SUPPORT +void agetty_print_issue_file(struct agetty_issue *ie __attribute__((__unused__)), + struct agetty_options *op, + struct termios *tp __attribute__((__unused__))) +{ + if ((op->flags & F_NONL) == 0) { + /* Issue not in use, start with a new line. */ + write_all(STDOUT_FILENO, "\r\n", 2); + } +} + +void agetty_eval_issue_file(struct agetty_issue *ie __attribute__((__unused__)), + struct agetty_options *op __attribute__((__unused__)), + struct termios *tp __attribute__((__unused__))) +{ +} + +void agetty_show_issue(struct agetty_options *op __attribute__((__unused__))) +{ +} + +#else /* ISSUE_SUPPORT */ + +static int issuefile_read_stream( + struct agetty_issue *ie, FILE *f, + struct agetty_options *op, struct termios *tp) +{ + struct stat st; + int c; + + if (fstat(fileno(f), &st) || !S_ISREG(st.st_mode)) + return 1; + + if (!ie->output) { + free(ie->mem); + ie->mem_sz = 0; + ie->mem = NULL; + ie->output = open_memstream(&ie->mem, &ie->mem_sz); + } + + while ((c = fgetc(f)) != EOF) { + if (c == '\\') + output_special_char(ie, fgetc(f), op, tp, f); + else + putc(c, ie->output); + } + + return 0; +} + +static int issuefile_read( + struct agetty_issue *ie, const char *filename, + struct agetty_options *op, struct termios *tp) +{ + FILE *f = fopen(filename, "r" UL_CLOEXECSTR); + int rc = 1; + + if (f) { + rc = issuefile_read_stream(ie, f, op, tp); + fclose(f); + } + return rc; +} + + +#ifdef AGETTY_RELOAD +int agetty_issue_is_changed(struct agetty_issue *ie) +{ + if (ie->mem_old && ie->mem + && strcmp(ie->mem_old, ie->mem) == 0) { + free(ie->mem_old); + ie->mem_old = ie->mem; + ie->mem = NULL; + ie->mem_sz = 0; + return 0; + } + + return 1; +} +#endif + +void agetty_print_issue_file(struct agetty_issue *ie, + struct agetty_options *op, + struct termios *tp) +{ + int oflag = tp->c_oflag; /* Save current setting. */ + + if ((op->flags & F_NONL) == 0) { + /* Issue not in use, start with a new line. */ + write_all(STDOUT_FILENO, "\r\n", 2); + } + + if (ie->do_tcsetattr) { + if ((op->flags & F_VCONSOLE) == 0) { + /* Map new line in output to carriage return & new line. */ + tp->c_oflag |= (ONLCR | OPOST); + tcsetattr(STDIN_FILENO, TCSADRAIN, tp); + } + } + + if (ie->mem_sz && ie->mem) + write_all(STDOUT_FILENO, ie->mem, ie->mem_sz); + + if (ie->do_tcrestore) { + /* Restore settings. */ + tp->c_oflag = oflag; + /* Wait till output is gone. */ + tcsetattr(STDIN_FILENO, TCSADRAIN, tp); + } + +#ifdef AGETTY_RELOAD + free(ie->mem_old); + ie->mem_old = ie->mem; + ie->mem = NULL; + ie->mem_sz = 0; +#else + free(ie->mem); + ie->mem = NULL; + ie->mem_sz = 0; +#endif +} + +void agetty_eval_issue_file(struct agetty_issue *ie, + struct agetty_options *op, + struct termios *tp) +{ + if (!(op->flags & F_ISSUE)) + goto done; + +#ifdef USE_NETLINK +/* TODO: + * Two pass processing for agetty_eval_issue_file() + * Implement pass 1: Just evaluate list of netlink_groups (IP protocols) and + * interfaces to monitor. + * That is why again label is here: netlink_groups will be re-evaluated and + * dump will be performed again. + */ + /* netlink_groups = 0; */ + netlink_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + + /* Already initialized? */ + if (ie->nl.fd >= 0) + goto skip; + /* Prepare netlink. */ + ul_nl_init(&(ie->nl)); + if ((ul_netaddrq_init(&(ie->nl), NULL, NULL, (void *)ie))) + goto skip; + + /* Open netlink and create address list. */ + if (ul_nl_open(&(ie->nl), + RTMGRP_LINK | netlink_groups)) + goto skip; + if (ul_nl_request_dump(&(ie->nl), RTM_GETADDR)) + goto error; + if (ul_nl_process(&(ie->nl), UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE) + goto error; + goto skip; +error: + /* In case of any error, the addrq list is just empty, and we can use + * the code without any error checking. */ + ul_nl_close(&(ie->nl)); + ie->nl.fd = -1; +skip: +#endif /* USE_NETLINK */ + /* + * The custom issue file or directory list specified by: + * agetty --issue-file + * Note that nothing is printed if the file/dir does not exist. + */ + if (op->issue) { + char *list = strdup(op->issue); + char *file; + + if (!list) + agetty_log_err(_("failed to allocate memory: %m")); + + for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) { + struct stat st; + + if (stat(file, &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) + issuedir_read(ie, file, op, tp); + else + issuefile_read(ie, file, op, tp); + } + free(list); + goto done; + } + +#ifdef ISSUEDIR_SUPPORT + struct list_head file_list; + struct list_head *current = NULL; + char *name = NULL; + + /* Reading all issue files and concatenating all contents to one content. + * The ordering rules are defineded in: + * https://github.com/uapi-group/specifications/blob/main/specs/configuration_files_specification.md + * + * Note that _PATH_RUNSTATEDIR (/run) is always read by ul_configs_file_list(). + */ + ul_configs_file_list(&file_list, + NULL, + _PATH_SYSCONFDIR, + _PATH_RUNSTATEDIR, + _PATH_SYSCONFSTATICDIR, + "issue", + ISSUEDIR_EXT); + + while (ul_configs_next_filename(&file_list, ¤t, &name) == 0) { + issuefile_read(ie, name, op, tp); + } + + ul_configs_free_list(&file_list); +#endif + +done: + if (ie->output) { + fclose(ie->output); + ie->output = NULL; + } +} + +/* This is --show-issue backend, executed by normal user on the current + * terminal. + */ +void agetty_show_issue(struct agetty_options *op) +{ + struct agetty_issue ie = { + .output = NULL, +#ifdef USE_NETLINK + .nl.fd = -1 +#endif + }; + struct termios tp; + + memset(&tp, 0, sizeof(struct termios)); + if (tcgetattr(STDIN_FILENO, &tp) < 0) + err(EXIT_FAILURE, _("failed to get terminal attributes: %m")); + + agetty_eval_issue_file(&ie, op, &tp); + + if (ie.mem_sz) + write_all(STDOUT_FILENO, ie.mem, ie.mem_sz); + if (ie.output) + fclose(ie.output); + free(ie.mem); +} + +#endif /* ISSUE_SUPPORT */ + +#ifdef USE_NETLINK +static void print_iface_best(struct agetty_issue *ie, + const char *ifname, + uint8_t ifa_family) +{ + struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX]; + struct ul_netaddrq_iface *ifaceq; + struct list_head *l; + enum ul_netaddrq_ip_rating threshold; + + if (!ie->nl.data_addr) + return; /* error: init failed */ + + if ((ifaceq = ul_netaddrq_iface_by_name(&(ie->nl), ifname))) + { + memset(best, 0, sizeof(best)); + if (ifa_family == AF_INET) + l = &(ifaceq->ip_quality_list_4); + else + /* if (ifa_family == AF_INET6) */ + l = &(ifaceq->ip_quality_list_6); + + threshold = + ul_netaddrq_iface_bestaddr(l, &best); + if (threshold != __ULNETLINK_RATING_MAX) + fputs(ul_nl_addr_ntop_address(best[threshold]->addr), + ie->output); + } +} + +static void print_addrq_bestofall(struct agetty_issue *ie, + uint8_t ifa_family) +{ + struct ul_netaddrq_iface *best_ifaceq; + enum ul_netaddrq_ip_rating threshold; + const char *best_ipp; + + if (!ie->nl.data_addr) + return; /* error: init failed */ + + best_ipp = ul_netaddrq_get_best_ipp(&(ie->nl), ifa_family, + &threshold, &best_ifaceq); + if (best_ipp) + fputs(best_ipp, ie->output); +} + +static void dump_iface_good(struct agetty_issue *ie, + struct ul_netaddrq_iface *ifaceq) +{ + struct ul_netaddrq_ip *best4[__ULNETLINK_RATING_MAX]; + struct ul_netaddrq_ip *best6[__ULNETLINK_RATING_MAX]; + struct list_head *li; + enum ul_netaddrq_ip_rating threshold = __ULNETLINK_RATING_MAX - 1; + enum ul_netaddrq_ip_rating fthreshold; /* per family threshold */ + bool first = true; + + memset(best4, 0, sizeof(best4)); + threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4), + &best4); + memset(best6, 0, sizeof(best6)); + fthreshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6), + &best6); + if (fthreshold < threshold) + threshold = fthreshold; + + list_for_each(li, &(ifaceq->ip_quality_list_4)) + { + struct ul_netaddrq_ip *ipq; + + ipq = list_entry(li, struct ul_netaddrq_ip, entry); + if (threshold <= ULNETLINK_RATING_SCOPE_LINK && + ( ipq->quality <= threshold || + /* Consider site addresses equally good as global */ + ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && + best4[threshold]) + { + if (first) + { + fprintf(ie->output, "%s: ", ifaceq->ifname); + first = false; + } + else + fprintf(ie->output, " "); + /* Write only the longest living temporary address */ + if (threshold == ULNETLINK_RATING_F_TEMPORARY) + { + fputs(ul_nl_addr_ntop_address(best4[ULNETLINK_RATING_F_TEMPORARY]->addr), + ie->output); + goto temp_cont4; + } + else + fputs(ul_nl_addr_ntop_address(ipq->addr), + ie->output); + } + temp_cont4:; + } + + list_for_each(li, &(ifaceq->ip_quality_list_6)) + { + struct ul_netaddrq_ip *ipq; + + ipq = list_entry(li, struct ul_netaddrq_ip, entry); + if (threshold <= ULNETLINK_RATING_SCOPE_LINK && + ( ipq->quality <= threshold || + /* Consider site addresses equally good as global */ + ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && + best6[threshold]) + { + if (first) + { + fprintf(ie->output, "%s: ", ifaceq->ifname); + first = false; + } + else + fprintf(ie->output, " "); + /* Write only the longest living temporary address */ + if (threshold == ULNETLINK_RATING_F_TEMPORARY) + { + fputs(ul_nl_addr_ntop_address(best6[ULNETLINK_RATING_F_TEMPORARY]->addr), + ie->output); + goto temp_cont6; + } + else + fputs(ul_nl_addr_ntop_address(ipq->addr), + ie->output); + } + temp_cont6:; + } + if (!first) + fputs("\n", ie->output); +} + +static void dump_iface_all(struct agetty_issue *ie, + struct ul_netaddrq_iface *ifaceq) +{ + struct list_head *li; + struct ul_netaddrq_ip *ipq; + bool first = true; + + list_for_each(li, &(ifaceq->ip_quality_list_4)) + { + ipq = list_entry(li, struct ul_netaddrq_ip, entry); + if (first) + { + fprintf(ie->output, "%s: ", ifaceq->ifname); + first = false; + } + else + fprintf(ie->output, " "); + fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); + } + list_for_each(li, &(ifaceq->ip_quality_list_6)) + { + ipq = list_entry(li, struct ul_netaddrq_ip, entry); + if (first) + { + fprintf(ie->output, "%s: ", ifaceq->ifname); + first = false; + } + else + fprintf(ie->output, " "); + fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); + } + if (!first) + fputs("\n", ie->output); +} +#endif /* USE_NETLINK */ + +/* + * parses \x{argument}, if not argument specified then returns NULL, the @fd + * has to point to one char after the sequence (it means '{'). + */ +static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz) +{ + size_t i = 0; + int c = fgetc(fd); + + if (c == EOF || (unsigned char) c != '{') { + ungetc(c, fd); + return NULL; + } + + do { + c = fgetc(fd); + if (c == EOF) + return NULL; + if ((unsigned char) c != '}' && i < bufsz - 1) + buf[i++] = (unsigned char) c; + + } while ((unsigned char) c != '}'); + + buf[i] = '\0'; + return buf; +} + +static void output_special_char(struct agetty_issue *ie, + unsigned char c, + struct agetty_options *op, + struct termios *tp, + FILE *fp) +{ + struct utsname uts; + + switch (c) { + case 'e': + { + char escname[UL_COLORNAME_MAXSZ]; + + if (get_escape_argument(fp, escname, sizeof(escname))) { + char *esc = color_get_sequence(escname); + + if (esc) { + fputs(esc, ie->output); + free(esc); + } + } else + fputs("\033", ie->output); + break; + } + case 's': + uname(&uts); + fprintf(ie->output, "%s", uts.sysname); + break; + case 'n': + uname(&uts); + fprintf(ie->output, "%s", uts.nodename); + break; + case 'r': + uname(&uts); + fprintf(ie->output, "%s", uts.release); + break; + case 'v': + uname(&uts); + fprintf(ie->output, "%s", uts.version); + break; + case 'm': + uname(&uts); + fprintf(ie->output, "%s", uts.machine); + break; + case 'o': + { + char *dom = agetty_xgetdomainname(); + + fputs(dom ? dom : "unknown_domain", ie->output); + free(dom); + break; + } + case 'O': + { + char *dom = NULL; + char *host = agetty_xgethostname(); + struct addrinfo hints, *info = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + + if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) { + char *canon; + + if (info->ai_canonname && + (canon = strchr(info->ai_canonname, '.'))) + dom = canon + 1; + } + fputs(dom ? dom : "unknown_domain", ie->output); + if (info) + freeaddrinfo(info); + free(host); + break; + } + case 'd': + case 't': + { + time_t now; + struct tm tm; + + time(&now); + localtime_r(&now, &tm); + + if (c == 'd') /* ISO 8601 */ + fprintf(ie->output, "%s %s %2d %d", + nl_langinfo(ABDAY_1 + tm.tm_wday), + nl_langinfo(ABMON_1 + tm.tm_mon), + tm.tm_mday, + tm.tm_year < 70 ? tm.tm_year + 2000 : + tm.tm_year + 1900); + else + fprintf(ie->output, "%02d:%02d:%02d", + tm.tm_hour, tm.tm_min, tm.tm_sec); + break; + } + case 'l': + fprintf (ie->output, "%s", op->tty); + break; + case 'b': + agetty_fprint_speed(ie->output, cfgetispeed(tp)); + break; + case 'S': + { + char *var = NULL, varname[64]; + + /* \S{varname} */ + if (get_escape_argument(fp, varname, sizeof(varname))) { + var = read_os_release(op, varname); + if (var) { + if (strcmp(varname, "ANSI_COLOR") == 0) + fprintf(ie->output, "\033[%sm", var); + else + fputs(var, ie->output); + } + /* \S */ + } else if ((var = read_os_release(op, "PRETTY_NAME"))) { + fputs(var, ie->output); + + /* \S and PRETTY_NAME not found */ + } else { + uname(&uts); + fputs(uts.sysname, ie->output); + } + + free(var); + + break; + } + case 'u': + case 'U': + { + int users = 0; +#ifdef USE_SYSTEMD + if (sd_booted() > 0) { + users = sd_get_sessions(NULL); + if (users < 0) + users = 0; + } else +#endif + { + users = 0; + struct utmpx *ut; + setutxent(); + while ((ut = getutxent())) + if (ut->ut_type == USER_PROCESS) + users++; + endutxent(); + } + if (c == 'U') + fprintf(ie->output, P_("%d user", "%d users", users), users); + else + fprintf (ie->output, "%d ", users); + break; + } +#ifdef USE_NETLINK + case '4': + case '6': + { + char iface[IF_NAMESIZE]; + uint8_t ifa_family = c == '4' ? AF_INET : AF_INET6; + + if (get_escape_argument(fp, iface, sizeof(iface))) + print_iface_best(ie, iface, ifa_family); + else + print_addrq_bestofall(ie, ifa_family); + + /* TODO: Move to pass 1 */ + if (c == '4') + netlink_groups |= RTMGRP_IPV4_IFADDR; + else + netlink_groups |= RTMGRP_IPV6_IFADDR; + break; + } + case 'a': + { + struct list_head *li; + struct ul_netaddrq_iface *ifaceq; + + list_for_each_netaddrq_iface(li, &(ie->nl)) + { + ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); + + dump_iface_good(ie, ifaceq); + } + } + break; + case 'A': + { + struct list_head *li; + struct ul_netaddrq_iface *ifaceq; + + list_for_each_netaddrq_iface(li, &(ie->nl)) + { + ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); + + dump_iface_all(ie, ifaceq); + } + } + break; +#endif /* USE_NETLINK */ + default: + putc(c, ie->output); + break; + } +} + + +void agetty_reload(void) +{ +#ifdef AGETTY_RELOAD + int fd = open(AGETTY_RELOAD_FILENAME, O_CREAT|O_CLOEXEC|O_WRONLY, + S_IRUSR|S_IWUSR); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), AGETTY_RELOAD_FILENAME); + + if (futimens(fd, NULL) < 0 || close(fd) < 0) + err(EXIT_FAILURE, _("cannot touch file %s"), + AGETTY_RELOAD_FILENAME); +#else + /* very unusual */ + errx(EXIT_FAILURE, _("--reload is unsupported on your system")); +#endif +} diff --git a/agetty-cmd/meson.build b/agetty-cmd/meson.build index edc77cc57..54606f80b 100644 --- a/agetty-cmd/meson.build +++ b/agetty-cmd/meson.build @@ -2,6 +2,7 @@ agetty_sources = files( 'agetty.c', 'agetty.h', 'credentials.c', + 'issuefile.c', 'tty.c', 'utils.c', )