]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
agetty: split out issue file functions to issuefile.c
authorKarel Zak <kzak@redhat.com>
Thu, 14 May 2026 09:21:48 +0000 (11:21 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 14 May 2026 11:14:57 +0000 (13:14 +0200)
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 <kzak@redhat.com>
agetty-cmd/Makemodule.am
agetty-cmd/agetty.c
agetty-cmd/agetty.h
agetty-cmd/issuefile.c [new file with mode: 0644]
agetty-cmd/meson.build

index 875ab41ae3f5286e7c22ee75b610242a4ed58b78..79b173c700554fd4ce07b76fa93ab699dae57867 100644 (file)
@@ -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
 
index b3416383c626e7ff1275b94d8d6e2e344af7ea7b..ff33cffc87830eaf816389d90913a9f0bc473267 100644 (file)
 # include <sys/inotify.h>
 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 <path[:path]...>
-        * 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, &current, &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
-}
-
index b00491113e0a5c9efd4c1bb1c18ac975fce155c1..fcef629550abf73176c261e510cbc73cf599fd4f 100644 (file)
@@ -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 (file)
index 0000000..dc6d00d
--- /dev/null
@@ -0,0 +1,870 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#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 <dirent.h>
+# define ISSUEDIR_EXT  "issue"
+# define ISSUEDIR_EXTSIZ       sizeof(ISSUEDIR_EXT)
+#endif
+
+#ifdef USE_SYSTEMD
+# include <systemd/sd-daemon.h>
+# include <systemd/sd-login.h>
+#endif
+
+#ifdef USE_NETLINK
+# include <net/if.h>
+# include <arpa/inet.h>
+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 <path[:path]...>
+        * 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, &current, &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
+}
index edc77cc5769667d8c4b5cf8d87e0618085257174..54606f80b4389cde03c9ff8603854ccdf7e9f261 100644 (file)
@@ -2,6 +2,7 @@ agetty_sources = files(
   'agetty.c',
   'agetty.h',
   'credentials.c',
+  'issuefile.c',
   'tty.c',
   'utils.c',
 )