diff --git a/Makefile b/Makefile index 5cdd0092..64506bbd 100644 --- a/Makefile +++ b/Makefile @@ -34,8 +34,7 @@ ARCHIVE = $(PKG).tar.xz EXEC = finit reboot HEADERS = plugin.h svc.h helpers.h queue.h DISTFILES = LICENSE README ChangeLog finit.conf services -OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o -OBJS += tty.o +OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o tty.o SRCS = $(OBJS:.o=.c) DEPS = $(addprefix .,$(SRCS:.c=.d)) diff --git a/conf.c b/conf.c index 358aad02..438f1d6e 100644 --- a/conf.c +++ b/conf.c @@ -26,6 +26,7 @@ #include "finit.h" #include "svc.h" +#include "tty.h" #include "lite.h" #include "helpers.h" @@ -49,6 +50,42 @@ static char *strip_line(char *line) return line; } +/* Convert optional "[!123456789S]" string into a bitmask */ +int parse_runlevels(char *runlevels) +{ + int i, not = 0, bitmask = 0; + + if (!runlevels) + runlevels = "[234]"; + i = 1; + while (i) { + int level; + char lvl = runlevels[i++]; + + if (']' == lvl || 0 == lvl) + break; + if ('!' == lvl) { + not = 1; + bitmask = 0x3FE; + continue; + } + + if ('s' == lvl || 'S' == lvl) + lvl = ':'; /* RUNLEVEL_BOOT */ + + level = lvl - '0'; + if (level > RUNLEVEL_BOOT || level < 0) + continue; + + if (not) + CLRBIT(bitmask, level); + else + SETBIT(bitmask, level); + } + + return bitmask; +} + void parse_finit_conf(char *file) { FILE *fp; @@ -67,9 +104,8 @@ void parse_finit_conf(char *file) while (!feof(fp)) { if (!fgets(line, sizeof(line), fp)) continue; - - _d("conf: %s", line); chomp(line); + _d("conf: %s", line); /* Skip comments. */ if (MATCH_CMD(line, "#", x)) @@ -179,9 +215,7 @@ void parse_finit_conf(char *file) continue; } if (MATCH_CMD(line, "tty ", x)) { - char *tty = strdup(strip_line(x)); - int baud = 115200; /* XXX - Read from config file */ - tty_add(tty, baud); + tty_register(strip_line(x)); continue; } } diff --git a/finit.c b/finit.c index 952ec632..d38a29ee 100644 --- a/finit.c +++ b/finit.c @@ -34,6 +34,7 @@ #include "plugin.h" #include "svc.h" #include "sig.h" +#include "tty.h" #include "lite.h" int debug = 0; @@ -273,7 +274,7 @@ int main(int argc, char* argv[]) plugin_run_hooks(HOOK_SYSTEM_UP); /* Start GETTY on console(s) */ - tty_start(); + tty_runlevel(runlevel); /* * Enter main loop to monior /dev/initctl and services diff --git a/finit.h b/finit.h index 4e0748de..93736e7e 100644 --- a/finit.h +++ b/finit.h @@ -37,12 +37,16 @@ #if defined EMBEDDED_SYSTEM # define CONSOLE "/dev/console" # define SETUP_DEVFS "/sbin/mdev -s" -# define GETTY "/sbin/getty -L 115200 %s vt100" +# define GETTY "/sbin/getty -L" +# define GETTY_BUSYBOX +# define BAUDRATE 115200 # define RUNLEVEL 2 #else /* Debian/Ubuntu based distributions */ # define CONSOLE "/dev/tty1" # define SETUP_DEVFS "/sbin/udevd --daemon" -# define GETTY "/sbin/getty -8 38400 %s" +# define GETTY "/sbin/getty -8" +# define GETTY_AGETTY +# define BAUDRATE 38400 # define RUNLEVEL 2 # define RANDOMSEED "/var/lib/urandom/random-seed" # define REMOUNT_ROOTFS_RW @@ -84,11 +88,8 @@ extern char *console; extern char *__progname; /* conf.c */ -void parse_finit_conf(char *file); - -/* tty.c */ -void tty_start(void); -int tty_add(char *tty, int baud); +int parse_runlevels (char *runlevels); +void parse_finit_conf (char *file); #endif /* FINIT_H_ */ diff --git a/helpers.c b/helpers.c index f1ae7113..e63d2c74 100644 --- a/helpers.c +++ b/helpers.c @@ -505,7 +505,7 @@ int run_interactive(char *cmd, char *fmt, ...) return status; } -pid_t run_getty(char *cmd, int console) +pid_t run_getty(char *cmd, char *args[], int console) { pid_t pid = fork(); @@ -557,7 +557,7 @@ pid_t run_getty(char *cmd, int console) if (fexist(SYNC_STOPPED)) continue; - run(cmd); + execv(cmd, args); } exit(0); @@ -657,6 +657,13 @@ void chomp(char *str) *x = 0; } +/* Signal safe sleep ... we get a lot of SIGCHLD at reboot */ +void do_sleep(unsigned int sec) +{ + while ((sec = sleep(sec))) + ; +} + void set_hostname(char *hostname) { FILE *fp; diff --git a/helpers.h b/helpers.h index 269f5359..5cb98e7f 100644 --- a/helpers.h +++ b/helpers.h @@ -74,6 +74,10 @@ #define _e(fmt, args...) do { fprintf(stderr, "finit:%s:%s() - " fmt "\n", __FILE__, __func__, ##args); } while (0) #define _pe(fmt, args...) do { fprintf(stderr, "finit:%s:%s() - " fmt ". Error %d: %s\n", __FILE__, __func__, ##args, errno, strerror(errno)); } while (0) +#define ISSET(a,i) ((a & (1 << (i)) ? 1 : 0)) +#define SETBIT(a,i) (a |= (1 << (i))) +#define CLRBIT(a,i) (a &= ~(1 << (i))) + extern int debug; extern int verbose; @@ -93,6 +97,7 @@ void print_desc (char *action, char *desc); int print_result (int fail); int start_process (char *cmd, char *args[], int console); void chomp (char *str); +void do_sleep (unsigned int sec); int getuser (char *username); int getgroup (char *group); void set_hostname (char *hostname); @@ -100,7 +105,7 @@ void set_hostname (char *hostname); int complete (char *cmd, int pid); int run (char *cmd); int run_interactive (char *cmd, char *fmt, ...); -pid_t run_getty (char *cmd, int console); +pid_t run_getty (char *cmd, char *args[], int console); int run_parts (char *dir, char *cmd); #endif /* FINIT_HELPERS_H_ */ diff --git a/plugin.c b/plugin.c index 13ea7d73..b01739f9 100644 --- a/plugin.c +++ b/plugin.c @@ -124,9 +124,9 @@ int plugin_unregister(plugin_t *plugin) } +// _d("Comparing %s against plugin %s", str, p->name); #define SEARCH_PLUGIN(str) \ PLUGIN_ITERATOR(p, tmp) { \ - _d("Comparing %s against plugin %s", str, p->name); \ if (!strcmp(p->name, str)) \ return p; \ } @@ -305,9 +305,9 @@ static void check_plugin_depends(plugin_t *plugin) int i; for (i = 0; i < PLUGIN_DEP_MAX && plugin->depends[i]; i++) { - _d("Plugin %s depends on %s ...", plugin->name, plugin->depends[i]); +// _d("Plugin %s depends on %s ...", plugin->name, plugin->depends[i]); if (plugin_find(plugin->depends[i])) { - _d("OK plugin %s was already loaded.", plugin->depends[i]); +// _d("OK plugin %s was already loaded.", plugin->depends[i]); continue; } diff --git a/plugins/Makefile b/plugins/Makefile index 97eda6fc..d6528e8a 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -4,7 +4,7 @@ CPPFLAGS += -I.. LDFLAGS := $(filter-out -rdynamic, $(LDFLAGS)) LDFLAGS += -shared PLUGINS ?= initctl.so alsa-utils.so bootmisc.so dbus.so hwclock.so \ - resolvconf.so urandom.so x11-common.so + resolvconf.so urandom.so x11-common.so tty.so DEPS = $(addprefix .,$(PLUGINS:.so=.d)) include ../common.mk diff --git a/plugins/tty.c b/plugins/tty.c new file mode 100644 index 00000000..91ffcee6 --- /dev/null +++ b/plugins/tty.c @@ -0,0 +1,94 @@ +/* Optional TTY Watcher, used to catch new TTYs that are discovered, e.g., USB + * + * Copyright (c) 2013 Mattias Walström + * Copyright (c) 2013 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include /* O_RDONLY et al */ +#include /* read() */ +#include +#include + +#include "finit.h" +#include "helpers.h" +#include "plugin.h" +#include "tty.h" + +static void watcher(void *arg, int fd, int events); + +static plugin_t plugin = { + .io = { + .cb = watcher, + .flags = PLUGIN_IO_READ, + }, +}; + +static void setup(void) +{ + plugin.io.fd = inotify_init1(IN_NONBLOCK); + if (-1 == plugin.io.fd || inotify_add_watch(plugin.io.fd, "/dev", IN_CREATE | IN_DELETE) < 0) + _e("Failed starting TTY watcher: %s", strerror(errno)); +} + +static void watcher(void *UNUSED(arg), int fd, int UNUSED(events)) +{ + int len = 0; + char buf[EVENT_SIZE], name[42]; + tty_node_t *entry; + struct inotify_event *notified = (struct inotify_event *)buf; + + while ((len = read(fd, buf, sizeof(buf)))) { + if (-1 == len) { + if (errno == EINTR) + continue; + break; + } + + snprintf(name, sizeof(name), "/dev/%s", notified->name); + entry = tty_find(name); + if (entry && tty_enabled(&entry->data, runlevel)) { + if (notified->mask & IN_CREATE) + tty_start(&entry->data); + else if (entry->data.pid) + tty_stop(&entry->data); + } + } +} + +PLUGIN_INIT(plugin_init) +{ + setup(); + plugin_register(&plugin); +} + +PLUGIN_EXIT(plugin_exit) +{ + plugin_unregister(&plugin); +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/sig.c b/sig.c index 8548df7d..ec11a49d 100644 --- a/sig.c +++ b/sig.c @@ -49,13 +49,6 @@ static int stopped = 0; -/* Signal safe sleep ... we get a lot of SIGCHLD at reboot */ -void do_sleep(unsigned int sec) -{ - while ((sec = sleep(sec))) - ; -} - void do_shutdown (int sig) { touch(SYNC_SHUTDOWN); diff --git a/svc.c b/svc.c index d45e5a14..00486a56 100644 --- a/svc.c +++ b/svc.c @@ -29,13 +29,10 @@ #include "helpers.h" #include "private.h" #include "sig.h" +#include "tty.h" #include "lite.h" #include "svc.h" -#define ISSET(a,i) ((a & (1 << (i)) ? 1 : 0)) -#define SETBIT(a,i) (a |= (1 << (i))) -#define CLRBIT(a,i) (a &= ~(1 << (i))) - /* The registered services are kept in shared memory for easy read-only access * by 3rd party APIs. Mostly because of the ability to, from the outside, query * liveness state of all daemons that should run. */ @@ -151,14 +148,14 @@ void svc_bootstrap(void) * Stops all services not in @newlevel and starts, or lets continue to run, * those in @newlevel. Also updates @prevlevel and active @runlevel. */ -void svc_runlevel (int newlevel) +void svc_runlevel(int newlevel) { svc_t *svc; if (runlevel == newlevel) return; - if (newlevel > 5 || newlevel < 1) + if (newlevel < 0 || newlevel > 9) return; prevlevel = runlevel; @@ -176,10 +173,22 @@ void svc_runlevel (int newlevel) } } + if (0 == runlevel) { + do_shutdown(SIGUSR2); + return; + } + if (6 == runlevel) { + do_shutdown(SIGUSR1); + return; + } + if (runlevel == 1) touch("/etc/nologin"); /* Disable login in single-user mode */ else remove("/etc/nologin"); + + if (RUNLEVEL_BOOT != prevlevel) + tty_runlevel(runlevel); } /** @@ -214,7 +223,7 @@ void svc_runlevel (int newlevel) */ int svc_register(int type, char *line, char *username) { - int i = 0, not = 0; + int i = 0; char *cmd, *desc, *runlevels = NULL; svc_t *svc; @@ -269,34 +278,7 @@ int svc_register(int type, char *line, char *username) strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); svc->args[i][0] = 0; - if (!runlevels) - runlevels = "[234]"; - i = 1; - while (i) { - int level; - char lvl = runlevels[i++]; - - if (']' == lvl || 0 == lvl) - break; - if ('!' == lvl) { - not = 1; - svc->runlevels = 0x3FE; - continue; - } - - if ('s' == lvl || 'S' == lvl) - lvl = ':'; /* RUNLEVEL_BOOT */ - - level = lvl - '0'; - if (level > RUNLEVEL_BOOT || level < 0) - continue; - - if (not) - CLRBIT(svc->runlevels, level); - else - SETBIT(svc->runlevels, level); - } - + svc->runlevels = parse_runlevels(runlevels); _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); return 0; @@ -358,6 +340,9 @@ void svc_monitor(void) if (is_norespawn()) return; + if (tty_respawn(lost)) + return; + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { if (lost != svc->pid) continue; diff --git a/tty.c b/tty.c index 0d0f736a..21ede70f 100644 --- a/tty.c +++ b/tty.c @@ -1,6 +1,7 @@ -/* TTY handling in finit +/* Finit TTY handling * - * Copyright (c) 2013 Mattias Walström + * Copyright (c) 2013 Mattias Walström + * Copyright (c) 2013 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,119 +22,181 @@ * THE SOFTWARE. */ -#include #include -#include #include -#include "queue.h" #include "finit.h" #include "helpers.h" #include "lite.h" +#include "tty.h" -typedef struct finit_tty_t { - char *name; - int baud; - - int pid; /* Runtime information*/ -} finit_tty_t; - -typedef struct node { - LIST_ENTRY(node) link; - finit_tty_t data; -} node_t; +LIST_HEAD(, tty_node) tty_list = LIST_HEAD_INITIALIZER(); -/* We need to make room for the filename also, which is sent after inotify_event. */ -#define EVENT_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)) - -LIST_HEAD(, node) node_list = LIST_HEAD_INITIALIZER(); -int tty_add(char *tty, int baud) +/* tty [!1-9,S] [BAUD[,BAUD,...]] [TERM] */ +int tty_register(char *line) { - node_t *entry; + tty_node_t *entry; + int insert = 0, baud = 0; + char *cmd, *dev; + char *runlevels = NULL, *term = NULL; - if (!tty) + if (!line) { + _e("Invalid input argument."); return errno = EINVAL; + } - entry = malloc(sizeof(*entry)); - if (!entry) - return errno = ENOMEM; + cmd = strtok(line, " "); + if (!cmd) { + incomplete: + _e("Incomplete tty, cannot register."); + return errno = EINVAL; + } - entry->data.name = tty; + if (cmd[0] == '[') { /* [runlevels] */ + runlevels = &cmd[0]; + dev = strtok(NULL, " "); + if (!dev) + goto incomplete; + } else { + dev = cmd; + } + + cmd = strtok(NULL, " "); + if (cmd) { + baud = strtonum(cmd, 50, 4000000, NULL); + term = strtok(NULL, " "); + } + + entry = tty_find(dev); + if (!entry) { + insert = 1; + entry = malloc(sizeof(*entry)); + if (!entry) + return errno = ENOMEM; + } + + entry->data.name = strdup(dev); + if (!baud) + baud = BAUDRATE; entry->data.baud = baud; + entry->data.term = term ? strdup(term) : NULL; + entry->data.runlevels = parse_runlevels(runlevels); + _d("Registering tty %s at %d baud with term=%s on runlevels %s", dev, baud, term ?: "N/A", runlevels); - LIST_INSERT_HEAD(&node_list, entry, link); + if (insert) + LIST_INSERT_HEAD(&tty_list, entry, link); return 0; } -static void tty_startstop_one(finit_tty_t *tty, uint32_t mask) +tty_node_t *tty_find(char *dev) { - int is_console = 0; - char cmd[strlen(GETTY) + FILENAME_MAX]; + tty_node_t *entry; + + LIST_FOREACH(entry, &tty_list, link) { + if (!strcmp(dev, entry->data.name)) + return entry; + } + + return NULL; +} + +tty_node_t *tty_find_by_pid(pid_t pid) +{ + tty_node_t *entry; + + LIST_FOREACH(entry, &tty_list, link) { + if (entry->data.pid == pid) + return entry; + } + + return NULL; +} + +void tty_start(finit_tty_t *tty) +{ + int i = 0, is_console = 0; + char *cmd, *arg; + char line[FILENAME_MAX]; + char *args[42]; + + if (tty->pid) + return; if (console && !strcmp(tty->name, console)) is_console = 1; - if (mask & IN_CREATE) { - _d("Starting %s as %s", tty->name, is_console ? "console" : "TTY"); - snprintf(cmd, sizeof(cmd), GETTY, tty->name); - tty->pid = run_getty(cmd, is_console); - } +#if defined(GETTY_AGETTY) + snprintf(line, sizeof(line), "%s %s %d %s", GETTY, tty->name, tty->baud, tty->term ?: ""); +#elif defined(GETTY_BUSYBOX) + snprintf(line, sizeof(line), "%s %d %s %s", GETTY, tty->baud, tty->name, tty->term ?: ""); +#else +#error No GETTY defined! +#endif + cmd = strtok(line, " "); + args[i++] = basename(cmd); + while ((arg = strtok(NULL, " "))) + args[i++] = arg; + args[i] = NULL; - /* Kill the spawned child, and recollect it. */ - if ((mask & IN_DELETE) && tty->pid) { - _d("Stopping %s", tty->name); - kill(tty->pid, SIGKILL); - waitpid(tty->pid, NULL, 0); - tty->pid = 0; - } + _d("Starting %s: %s on %s", is_console ? "console" : "TTY", cmd, tty->name); + tty->pid = run_getty(cmd, args, is_console); } -static void tty_watcher(node_t *entry) +void tty_stop(finit_tty_t *tty) { - int fd = inotify_init(); + if (!tty->pid) + return; - prctl(PR_SET_NAME, "tty-watcher", 0, 0, 0); - - if (-1 == fd || inotify_add_watch(fd, "/dev", IN_CREATE | IN_DELETE) < 0) { - fprintf(stderr, "finit: Failed starting TTY watcher: %s\n", strerror(errno)); - exit(1); - } - - while (1) { - int len = 0; - char buf[EVENT_SIZE]; - struct inotify_event *notified = (struct inotify_event *)buf; - - while ((len = read(fd, buf, EVENT_SIZE))) { - if (len == -1) { - if (errno == EAGAIN || errno == EINTR) - continue; - } - - LIST_FOREACH(entry, &node_list, link) { - if (!strcmp(notified->name, entry->data.name)) - tty_startstop_one(&entry->data, notified->mask); - } - } - } + _d("Stopping TTY %s", tty->name); + kill(tty->pid, SIGTERM); + do_sleep(2); + kill(tty->pid, SIGKILL); + waitpid(tty->pid, NULL, 0); + tty->pid = 0; } -void tty_start(void) +int tty_enabled(finit_tty_t *tty, int runlevel) { - node_t *entry; + if (!tty) + return 0; - /* Start all TTYs that already exist in the system */ - LIST_FOREACH(entry, &node_list, link) { - chdir("/dev"); - if (fexist (entry->data.name)) - tty_startstop_one(&entry->data, IN_CREATE); + if (!ISSET(tty->runlevels, runlevel)) + return 0; + else if (fexist(tty->name)) + return 1; + + return 0; +} + +/* TTY monitor, called by svc_monitor() */ +int tty_respawn(pid_t pid) +{ + tty_node_t *entry = tty_find_by_pid(pid); + + if (!entry) + return 0; + + if (!tty_enabled(&entry->data, runlevel)) + tty_stop(&entry->data); + else + tty_start(&entry->data); + + return 1; +} + +/* Start all TTYs that exist in the system and are allowed at this runlevel */ +void tty_runlevel(int runlevel) +{ + tty_node_t *entry; + + LIST_FOREACH(entry, &tty_list, link) { + if (!tty_enabled(&entry->data, runlevel)) + tty_stop(&entry->data); + else + tty_start(&entry->data); } - - /* Start a watcher to catch new TTYs that is discovered, e.g., USB */ - if (!fork()) - tty_watcher(entry); } /** diff --git a/tty.h b/tty.h new file mode 100644 index 00000000..46f4ad86 --- /dev/null +++ b/tty.h @@ -0,0 +1,65 @@ +/* Finit TTY handling + * + * Copyright (c) 2013 Mattias Walström + * Copyright (c) 2013 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_TTY_H_ +#define FINIT_TTY_H_ + +#include "queue.h" /* BSD sys/queue.h API */ + +#define EVENT_SIZE ((sizeof(struct inotify_event) + 20)) + +typedef struct { + char *name; + int baud; + char *term; + int runlevels; + + int pid; +} finit_tty_t; + +typedef struct tty_node { + LIST_ENTRY(tty_node) link; + finit_tty_t data; +} tty_node_t; + +//extern LIST_HEAD(, tty_node) tty_list; + +int tty_register (char *line); +tty_node_t *tty_find (char *dev); +tty_node_t *tty_find_by_pid (pid_t pid); +void tty_start (finit_tty_t *tty); +void tty_stop (finit_tty_t *tty); +int tty_enabled (finit_tty_t *tty, int runlevel); +int tty_respawn (pid_t pid); +void tty_runlevel (int runlevel); + +#endif /* FINIT_TTY_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */