root/openrc/cursplash.c

Revision 57ff712c4bcb367c8f9a3347a42ea321d5200d03, 14.3 KB (checked in by Roy Marples <roy@marples.name>, 10 months ago)

Of course that should be RC_SVCDIR

  • Property mode set to 100644
Line 
1/*
2 * cursplash.c
3 * curses splash with progress bar for OpenRC
4 */
5
6/*
7 * Copyright 2008 Roy Marples <roy@marples.name>
8 * All rights reserved
9
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32const char cs_copyright[] = "Copyright (c) 2008 Roy Marples";
33
34#define _BSD_SOURCE
35
36#include <sys/ioctl.h>
37#include <sys/param.h>
38#include <sys/resource.h>
39#include <sys/socket.h>
40#include <sys/stat.h>
41#include <sys/termios.h>
42#include <sys/types.h>
43#include <sys/un.h>
44
45#if defined(__linux__)
46# include <asm/setup.h>
47# include <linux/vt.h>
48#elif defined(__NetBSD__) || defined(__OpenBSD__)
49# include <dev/wscons/wsdisplay_usl_io.h>
50#endif
51
52#include <curses.h>
53#include <errno.h>
54#include <fcntl.h>
55#include <poll.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <unistd.h>
60
61#include <einfo.h>
62#include <rc.h>
63
64#define UNCONST(a)              ((void *)(unsigned long)(const void *)(a))
65
66#define SOCKET                  RC_SVCDIR "/cursplash.sock"
67#define NFDS                    5
68
69#ifndef THEME
70# define THEME                  "gentoo"
71#endif
72
73#ifndef SYSCONFDIR
74# define SYSCONFDIR             "/etc/cursplash"
75#endif
76
77#if defined(__linux__)
78# define SPLASH_TTY             "17"
79# define SPLASH_TTY_PATH        "/dev/tty%d"
80# define TERM                   "linux"
81#elif defined(__NetBSD__)
82# define SPLASH_TTY             "5"
83# define SPLASH_TTY_PATH        "/dev/ttyE%d"
84# define TERM                   "wscons25"
85#endif
86
87static struct pollfd fds[NFDS];
88static struct sockaddr_un sun;
89
90static RC_STRINGLIST *svc_todo;
91static RC_STRINGLIST svc_done;
92static int starting;
93
94static WINDOW *mainwnd;
95static WINDOW *mbox;
96
97static int stdout_fd = -1;
98static int stderr_fd = -1;
99static int console_fd = -1;
100static int splash_fd = -1;
101static int orig_vt = -1;
102
103static void
104cleanup(void)
105{
106        unlink(SOCKET);
107        erase();
108        refresh();
109        endwin();
110        close(splash_fd);
111        if (orig_vt != -1) {
112                ioctl(console_fd, VT_ACTIVATE, orig_vt);
113                ioctl(console_fd, VT_WAITACTIVE, orig_vt);
114        }
115        close(STDIN_FILENO);
116        close(STDOUT_FILENO);
117        close(STDERR_FILENO);
118#ifdef VT_DISALLOCATE
119        ioctl(console_fd, VT_DISALLOCATE, SPLASH_TTY);
120#endif
121        close(console_fd);
122}
123
124static void
125splash_message(const char *message, const char *service)
126{
127        char buffer[1024], *p;
128        int cols, i;
129
130        cols = getmaxx(mbox) - 2;
131        p = buffer;
132        memset(buffer, 0, sizeof(buffer));
133        p += snprintf(buffer, sizeof(buffer), " %s %s", message, service);
134        for (i = (p - buffer); i < cols; i++)
135                *p++ = ' ';
136        *p = '\0';
137        wcolor_set(mbox, COLOR_BLUE, NULL);
138        mvwprintw(mbox, 1, 1, "%s", buffer);
139        wrefresh(mbox);
140        refresh();
141}
142
143static void
144splash_progress(void)
145{
146        double done = 0, todo = 0, total, perc;
147        int cols, split, i;
148        double upto;
149        RC_STRING *s;
150        char buffer[1024], *p;
151
152        if (svc_todo) {
153                TAILQ_FOREACH(s, svc_todo, entries)
154                        todo++;
155                TAILQ_FOREACH(s, &svc_done, entries)
156                        done++;
157        }
158        total = todo + done;
159        cols = getmaxx(mbox) - 4; 
160        split = (cols - 6) / 2;
161        upto = (cols / total) * done;
162        p = buffer;
163        *p++ = ' ';
164        for (i = 0; i < cols; i++) {
165                if (i == split) {
166                        if (total)
167                                perc = (100 * done) / total;
168                        else
169                                perc = 0;
170                        p += snprintf(p, sizeof(buffer) - (p - buffer),
171                                      " %02d%s%% ", (int)perc,
172                                      (total && done == total) ? "" : " ");
173                        i += 5;
174                } else if (i < upto)
175                        *p++ = '#';
176                else
177                        *p++ = ' ';
178        }
179        *p++ = ' ';
180        *p = '\0';
181        wattron(mbox, A_BOLD);
182        wcolor_set(mbox, COLOR_YELLOW, NULL);
183        mvwprintw(mbox, 2, 1, "%s", buffer);
184        wrefresh(mbox);
185}
186
187static int
188service_starting(const char *service)
189{
190        RC_STRING *s;
191
192        if (!starting)
193                return 0;
194        TAILQ_FOREACH(s, svc_todo, entries)
195                if (s->value && strcmp(s->value, service) == 0)
196                        break;
197        if (s)
198                splash_message("Starting", service);
199        return 0;
200}
201
202static int
203service_started(const char *service)
204{
205        RC_STRING *s;
206
207        if (!starting)
208                return 0;
209        TAILQ_FOREACH(s, svc_todo, entries)
210                if (strcmp(s->value, service) == 0)
211                        break;
212        if (s) {
213                TAILQ_REMOVE(svc_todo, s, entries);
214                TAILQ_INSERT_TAIL(&svc_done, s, entries);
215                splash_progress();
216                splash_message("Started", service);
217        }
218        return 0;
219}
220
221static int
222service_stopping(const char *service)
223{
224        RC_STRING *s;
225
226        if (starting)
227                return 0;
228        TAILQ_FOREACH(s, svc_todo, entries)
229                if (strcmp(s->value, service) == 0)
230                        break;
231        if (s)
232                splash_message("Stopping", service);
233        return 0;
234}
235
236static int
237service_stopped(const char *service)
238{
239        RC_STRING *s;
240
241        if (starting)
242                return 0;
243        TAILQ_FOREACH(s, svc_todo, entries)
244                if (strcmp(s->value, service) == 0)
245                        break;
246        if (s) {
247                TAILQ_REMOVE(svc_todo, s, entries);
248                TAILQ_INSERT_TAIL(&svc_done, s, entries);
249                splash_progress();
250                splash_message("Stopped", service);
251        }
252        return 0;
253}
254
255#ifdef __linux__
256static int
257closefrom(int fd)
258{
259        struct rlimit rl;
260        size_t i;
261        int r = 0;
262
263        if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
264                return -1;
265        for (i = fd; i < rl.rlim_cur; i++)
266                r += close(i);
267        return r;
268}
269#endif
270
271static int
272set_nonblock(int fd)
273{
274        int flags;
275
276        if ((flags = fcntl(fd, F_GETFL, 0)) == -1
277            || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
278        {
279                return -1;
280        }
281        return 0;
282}
283
284static int
285make_sock(socklen_t *len)
286{
287        int fd;
288
289        if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
290                return -1;
291        memset(&sun, 0, sizeof(sun));
292        sun.sun_family = AF_UNIX;
293        snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", SOCKET);
294        if (len)
295                *len = sizeof(sun.sun_family) + strlen(sun.sun_path) + 1;
296        return fd;
297}
298
299static int
300process_message(int fd)
301{
302        char buffer[1024], *cmd, *service;
303        ssize_t bytes;
304
305        bytes = read(fd, buffer, sizeof(buffer) - 1);
306        if (bytes == -1)
307                return -1;
308        if (bytes == 0)
309                return 0;
310        buffer[bytes] = '\0';
311        cmd = buffer;
312        service = strchr(buffer, ' ');
313        if (service) {
314                *service++ = '\0';
315                if (strcmp(cmd, "starting") == 0)
316                        return service_starting(service);
317                if (strcmp(cmd, "started") == 0)
318                        return service_started(service);
319                if (strcmp(cmd, "stopping") == 0)
320                        return service_stopping(service);
321                if (strcmp(cmd, "stopped") == 0)
322                        return service_stopped(service);
323                if (strcmp(cmd, "quit") == 0) {
324                        splash_message("Entering", service);
325                        if (strcmp(service, RC_LEVEL_SHUTDOWN) == 0) {
326                                unlink(SOCKET);
327                                _exit(0);
328                        } else
329                                exit(0);
330                }
331        }
332        errno = EINVAL;
333        return -1;
334}
335
336#ifdef __linux__
337static char *proc_getent(const char *ent)
338{
339        FILE *fp;
340        char proc[COMMAND_LINE_SIZE];
341        char *p;
342        char *value = NULL;
343        int i;
344
345        if (! (fp = fopen("/proc/cmdline", "r")))
346                return NULL;
347
348        memset(proc, 0, sizeof(proc));
349        if (fgets(proc, sizeof(proc), fp) &&
350            *proc && (p = strstr(proc, ent)))
351        {
352                i = p - proc;
353                if (i == '\0' || proc[i - 1] == ' ') {
354                        p += strlen(ent);
355                        if (*p == '=')
356                                p++;
357                        value = strdup(strsep(&p, " "));
358                }
359        } else
360                errno = ENOENT;
361        fclose(fp);
362
363        return value;
364}
365#endif
366
367static int
368make_screen(void)
369{
370        struct vt_stat vtstat;
371        char buffer[1024], file[PATH_MAX], *theme, *ttys = NULL, *r;
372        const char *etc = SYSCONFDIR;
373        size_t len;
374        WINDOW *cbox;
375        FILE *f;
376        int null_fd, reset_tio = 0, tty;
377        struct termios tio;
378
379#ifdef __linux__
380        theme = proc_getent("vtsplash.theme");
381        ttys = proc_getent("vtsplash.tty");
382#else
383        theme = strdup(THEME);
384#endif
385        if (!theme) {
386                if (errno == ENOENT)
387                        return 0;
388                return -1;
389        }
390        if (!*theme)
391                return 0;
392        if (!ttys)
393                ttys = strdup(SPLASH_TTY);
394        if (!ttys)
395                return -1;
396        errno = 0;
397        tty = strtol(ttys, &r, 10);
398        if (errno != 0 || *r) {
399                errno = ENOTTY;
400                return -1;
401        }
402        free(ttys);
403
404        console_fd = open("/dev/console", O_RDWR, 0);
405        if (console_fd == -1)
406                return -1;
407        if (ioctl(console_fd, VT_GETSTATE, &vtstat) == -1) {
408                close(console_fd);
409                return -1;
410        }
411        null_fd = open("/dev/null", O_WRONLY, 0);
412        if (null_fd == -1)
413                return -1;
414        snprintf(file, sizeof(file), SPLASH_TTY_PATH, tty);
415        if ((splash_fd = open(file, O_RDWR, 0)) == -1) {
416                close(console_fd);
417                return -1;
418        }
419        if (ioctl(console_fd, VT_ACTIVATE, tty) == 0) {
420                orig_vt = vtstat.v_active;
421                if (orig_vt == tty)
422                        orig_vt = 1;
423        } else 
424                orig_vt = 1;
425        if (ioctl(console_fd, VT_WAITACTIVE, tty) == -1)
426                return -1;
427        stdout_fd = STDOUT_FILENO;
428        stderr_fd = STDERR_FILENO;
429        dup2(splash_fd, stdout_fd);
430        dup2(splash_fd, stderr_fd);
431        dup2(null_fd, STDIN_FILENO);
432
433        /* We need to set the right term for the console */
434        setenv("TERM", TERM, 1);
435
436        mainwnd = initscr();
437        if (has_colors()) {
438                start_color();
439                init_pair(COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
440                init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLUE);
441        }
442        curs_set(0);
443        nodelay(mainwnd, TRUE);
444        wrefresh(mainwnd);
445        refresh();
446
447        /* We need to set ONLCR to display this properly */
448        tcgetattr(splash_fd, &tio);
449        if (!(tio.c_oflag & ONLCR)) {
450                tio.c_oflag |= ONLCR;
451                tcsetattr(splash_fd, TCSANOW, &tio);
452                reset_tio = 1;
453        }
454        if (*theme == '/')
455                etc = "";
456        snprintf(file, sizeof(file), "%s/%s", etc, theme);
457        free(theme);
458        if ((f = fopen(file, "r"))) {
459                while ((fgets(buffer, sizeof(buffer), f))) {
460                        len = strlen(buffer);
461                        if (write(STDOUT_FILENO, buffer, len) == -1)
462                                perror("write");
463                }
464                fclose(f);
465        }
466        if (reset_tio) {
467                tio.c_oflag &= ~ONLCR;
468                tcsetattr(splash_fd, TCSANOW, &tio);
469        }
470
471        snprintf(buffer, sizeof(buffer), " OpenRC is %s this host ",
472                 starting ? "starting up" : "shutting down");
473        cbox = newwin(2, strlen(buffer) + 2, 0, COLS - (strlen(buffer) + 2));
474        wcolor_set(cbox, COLOR_BLUE, NULL);
475        wborder(cbox, ACS_VLINE, 0, 0, ACS_HLINE, 0, 0, 0, 0);
476        mvwprintw(cbox, 0, 1, "%s", buffer);
477        wrefresh(cbox);
478        mbox = newwin(0, 0, LINES - 4, 0);
479        wcolor_set(mbox, COLOR_BLUE, NULL);
480        box(mbox, ACS_VLINE, ACS_HLINE);
481        wrefresh(mbox);
482        splash_progress();
483        splash_message("Waiting for", "services");
484        return 0;
485}
486
487static int
488spawn_daemon(const char *runlevel, int opts)
489{
490        RC_DEPTREE *deptree;
491        RC_STRING *s, *t;
492        RC_SERVICE state;
493        pid_t pid;
494        int fd, n;
495        size_t i;
496        struct sockaddr_un run;
497        socklen_t len;
498
499        /* First calculate our deptree */
500        deptree = rc_deptree_load();
501        if (deptree == NULL)
502                return -1;
503        svc_todo = rc_deptree_order(deptree, runlevel, opts | RC_DEP_STRICT);
504        if (svc_todo == NULL)
505                return 0;
506        starting = (opts & RC_DEP_START);
507        /* Remove things from the todo list we just won't do */
508        TAILQ_FOREACH_SAFE(s, svc_todo, entries, t) {
509                state = rc_service_state(s->value);
510                if ((starting && !(state & RC_SERVICE_STOPPED)) ||
511                    (!starting && (state & RC_SERVICE_STOPPED)))
512                {
513                        TAILQ_REMOVE(svc_todo, s, entries);
514                        free(s);
515                }
516        }
517        TAILQ_INIT(&svc_done);
518
519        unlink(SOCKET);
520        if ((fd = make_sock(&len)) == -1 ||
521            bind(fd, (struct sockaddr *)&sun, len) == -1 ||
522            chmod(SOCKET, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1 ||
523            set_nonblock(fd) == -1 ||
524            listen(fd, NFDS) == -1)
525        {
526                close(fd);
527                fd = -1;
528                return -1;
529        }
530
531        if ((pid = fork()) != 0) {
532                if (pid != -1)
533                        setpgrp(pid, 0);
534                close(fd);
535                close(console_fd);
536                close(splash_fd);
537                fd = -1;
538                return pid;
539        }
540
541        fclose(rc_environ_fd);
542        atexit(cleanup);
543
544        make_screen();
545       
546        for (i = 0; i < NFDS; i++) {
547                fds[i].fd = -1;
548                fds[i].events = POLLIN;
549        }
550        fds[0].fd = fd;
551        for (;;) {
552                for (i = 0; i < NFDS; i++)
553                        fds[i].revents = 0;
554                n = poll(fds, NFDS, -1);
555                if (n == -1) {
556                        if (errno == EAGAIN)
557                                continue;
558                        break;
559                }
560                if (n == 0) /* should not happen, but just in case */
561                        continue;
562                /* Service messages before new connections
563                 * This frees existing connections for new ones */
564                for (i = 1; i < NFDS; i++) {
565                        if (fds[i].revents & (POLLIN | POLLHUP)) {
566                                process_message(fds[i].fd);
567                                close(fds[i].fd);
568                                fds[i].fd = -1;
569                        }
570                }
571                if (fds[0].revents & (POLLIN | POLLHUP)) {
572                        for (i = 1; i < NFDS; i++) {
573                                if (fds[i].fd == -1) {
574                                        len = sizeof(run);
575                                        fds[i].fd = accept(fds[0].fd,
576                                                           (struct sockaddr *)&run,
577                                                           &len);
578                                        set_nonblock(fds[i].fd);
579                                        break;
580                                }
581                        }
582                }
583        }
584        return 0;
585}
586
587static ssize_t
588send_message(const char *message, const char *service)
589{
590        int fd;
591        socklen_t len;
592        struct iovec iov[3];
593        ssize_t bytes;
594
595        if ((fd = make_sock(&len)) == -1)
596                return -1;
597        if (connect(fd, (struct sockaddr *)&sun, len) == -1)
598                return -1;
599        if (service) {
600                iov[0].iov_base = UNCONST(message);
601                iov[0].iov_len = strlen(message);
602                iov[1].iov_base = UNCONST(" ");
603                iov[1].iov_len = 1;
604                iov[2].iov_base = UNCONST(service);
605                iov[2].iov_len = strlen(service);
606                bytes = writev(fd, iov, 3);
607        } else {
608                bytes = write(fd, message, strlen(message));
609        }
610        close(fd);
611        return bytes;
612}
613
614int
615rc_plugin_hook(RC_HOOK hook, const char *name)
616{
617        if (rc_yesno(getenv("IN_BACKGROUND")))
618                return 0;
619
620        switch (hook) {
621        case RC_HOOK_RUNLEVEL_STOP_IN:
622                if (strcmp(name, RC_LEVEL_SHUTDOWN) == 0 ||
623                    strcmp(name, RC_LEVEL_SINGLE) == 0)
624                {
625                        if (rc_yesno(getenv("RC_GOINGDOWN")))
626                                return spawn_daemon(name, RC_DEP_STOP);
627                        else
628                                return spawn_daemon("default", RC_DEP_START);
629                }
630                break;
631        case RC_HOOK_RUNLEVEL_STOP_OUT:
632                break;
633        case RC_HOOK_RUNLEVEL_START_IN:
634                if (strcmp(name, RC_LEVEL_SYSINIT) == 0) {
635                        starting = 1;
636                        make_screen();
637                        splash_message("Starting", RC_LEVEL_SYSINIT);
638                }
639                break;
640        case RC_HOOK_RUNLEVEL_START_OUT:
641                if (strcmp(name, RC_LEVEL_SYSINIT) == 0)
642                        return spawn_daemon("default", RC_DEP_START);
643                if (strcmp(name, "boot") != 0)
644                        return send_message("quit", name);
645                break;
646        case RC_HOOK_ABORT:
647                return send_message("quit", NULL);
648        case RC_HOOK_SERVICE_STOP_IN:
649                break;
650        case RC_HOOK_SERVICE_STOP_NOW:
651                return send_message("stopping", name);
652        case RC_HOOK_SERVICE_STOP_DONE:
653                return send_message("stopped", name);
654        case RC_HOOK_SERVICE_STOP_OUT:
655                break;
656        case RC_HOOK_SERVICE_START_IN:
657                break;
658        case RC_HOOK_SERVICE_START_NOW:
659                return send_message("starting", name);
660        case RC_HOOK_SERVICE_START_DONE:
661                return send_message("started", name);
662        case RC_HOOK_SERVICE_START_OUT:
663                break;
664        }
665        return 0;
666}
Note: See TracBrowser for help on using the browser.