Compare commits

...
26 Commits
Author SHA1 Message Date
Joachim Nilsson 44137806e4 Basic support for cpuset cgroup config and process assignment
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-05-14 10:18:45 +02:00
Joachim Nilsson 0848b9b07a Minor, replace cgroup hard-coded path with path from configure
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-05-14 10:15:10 +02:00
Joachim Nilsson c64188bfc5 plugins: rtc: Simplify failed restore from RTC warning
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-04-29 18:20:38 +02:00
Joachim Nilsson 5e789401d4 Rename hwclock.so plugin to rtc.so since it's now stand-alone
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-04-05 21:35:13 +02:00
Joachim Nilsson 744fdd654f plugins: hwclock: Native ioctl support for RTC, skip hwclock tool
The BusyBox hwclock is really bad at handling, or rather displaying,
errors properly.  When the kernel signals EINVAL, or ENOENT, it just
shows that message on stderr.

This patch implements proper RTC load/store at boot/halt with quite
some improved error handling suitable for embedded systems.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-04-05 21:27:39 +02:00
Joachim Nilsson f51fde4d46 Initial support for cgroups
Similar to how systemd creates cgroups for all services, for purposes of
tracking, Finit now does the same.  We also mount all available cgroups
in the /sys/fs/cgroup namespace.

This patch adds support for grouping processes in logical cgroups, like
systemd, and an `initctl ps` command to list the hieararchy.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-02-05 18:35:36 +01:00
Joachim Nilsson 85261c9acd initctl: Create 'enabled' services directory if it doesn't exist
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-02-05 12:42:13 +01:00
Joachim Nilsson 6a1082a947 New helper function: echo(), like shell echo but for C
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-02-05 10:33:00 +01:00
Joachim Nilsson 2094d2a53a Warn if Finit fail to mount /proc and /sys
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-02-05 10:31:36 +01:00
Joachim Nilsson d7da177742 Relocate images to img/
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-01-27 10:56:39 +01:00
Joachim Nilsson 170e5aa3fd docs: TODO: Drop simple conf item, contrib/finit.conf exists
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2019-01-27 10:54:47 +01:00
Joachim Nilsson f8ef8a1616 Merge pull request #115 from jonasj76/id-as-string
Add support to specify service ID as a string
2018-12-11 21:12:50 +01:00
Jonas Johansson f5f1a55747 Add support to specify service ID as a string
Signed-off-by: Jonas Johansson <jonasj76@gmail.com>
2018-12-11 16:43:05 +01:00
Joachim Nilsson a36aade7f4 Merge pull request #113 from srand/master
Bugfixes and features
2018-10-23 07:15:20 +02:00
Robert Andersson 76fc72770e pidfile: monitor renamed files, e.g. rsyslogd.pid.tmp
Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:53:57 +02:00
Robert Andersson b64dbbf8d0 fix condition string lengths
Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson d371b85c47 fix C++ compilation issues
Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson 053304012f Add option to log service/task output to the console
Example:
    service log:console [2] /usr/sbin/udhcpd -f -- DHCP Server

Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson 12d14aae84 fix service enable/disable support
Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson 6a7f115781 Add config option for manually started services
Example:
    service manual:yes [2] /usr/sbin/lighttpd -- HTTP Server

Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson 423c8d41a7 modules-load: add task name config
Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson f2cab53e55 Add service/task name config option
Allows the service/task name derived from the binary to be
overridden with a custom name.

Example:
    service name:dhcpd [2] /usr/sbin/udhcpd -f -- DHCP Server

Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Robert Andersson 33fd6f0049 Add modules-load.d plugin
This plugin scans /etc/modules-load.d for modules to modprobe.

Signed-off-by: Robert Andersson <robert.m.andersson@se.atlascopco.com>
2018-10-22 12:51:58 +02:00
Joachim Nilsson e83dcafb02 stty(): Restore cursor as well, if it was hidden previously
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2018-10-19 17:52:00 +02:00
Joachim Nilsson 9a98bff23f Run all runparts scripts using /bin/sh -c foo
This patch is an attempt to address an issue with runparts scripts found
by Magnus Malm, @magnusmalm.  Any script in:

    runparts /foo/bar.d/

were required to have the #!/bin/sh she-bang, otherwise Finit would not
start them.  He tested with the standard run-parts tool, which had no
such restrictions.

The patch not only makes sure to start scripts using `/bin/sh -c`

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2018-10-18 22:19:51 +02:00
Joachim Nilsson 1a2f7d162b Helpful symlink to config.md
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2018-10-17 14:12:04 +02:00
41 changed files with 982 additions and 199 deletions
+4 -1
View File
@@ -32,7 +32,10 @@ Bug fix release.
event, because services may depend on each other
* Require new libuEv API: `uev_init1()` to reduce event cache so that
the kernel can invalidate deleted events before enqueing to userspace
* Rename `hwclock.so` plugin to `rtc.so` since it now is stand-alone
from the `hwclock` tool. Note: the kernel can also be set to load
and store RTC to/from system clock at boot/halt as well.
### Fixes
* Fix #96: Start udevd as a proper service
+2 -2
View File
@@ -1,4 +1,4 @@
<img align="right" src="images/finit3.png" alt="Finit: Fast Init">
<img align="right" src="img/finit3.png" alt="Finit: Fast Init">
* [Introduction](#introduction)
* [Features](#features)
@@ -35,7 +35,7 @@ on server and desktop systems as well. For working examples, see the
* [Debian GNU/Linux](contrib/debian/), and
* [Void Linux](contrib/void/)
![Alpine screenshot](images/alpine-screenshot.png)
![Alpine screenshot](img/alpine-screenshot.png)
The screenshot shows [Alpine Linux](https://www.alpinelinux.org/).
+1
View File
@@ -95,6 +95,7 @@ AC_PLUGIN([inetd-chargen], [no], [Inetd plugin: character generator, RFC864])
AC_PLUGIN([inetd-daytime], [no], [Inetd plugin: daytime server, RFC867])
AC_PLUGIN([inetd-discard], [no], [Inetd plugin: discard server, RFC863])
AC_PLUGIN([inetd-time], [no], [Inetd plugin: time (rdate) server, RFC868])
AC_PLUGIN([modules-load], [no], [Scans /etc/modules-load.d for modules to load])
AC_PLUGIN([resolvconf], [no], [Setup necessary files for resolvconf])
AC_PLUGIN([x11-common], [no], [Console setup (for X)])
AC_PLUGIN([netlink], [yes], [Basic netlink plugin for IFUP/IFDN and GW events. Can be replaced with externally built plugin that links with libnl or similar.])
+1
View File
@@ -0,0 +1 @@
config.md
+2 -4
View File
@@ -30,8 +30,6 @@ Near Future
* Cron/At support, see below
* Write man pages for finit and `finit.conf`, steal from the excellent
`pimd` man pages ...
* Add simple, *very* simple, `finit-simple.conf` example. To illustrate
how close Finit3 still is to the original easy-to-use Finit0 :)
General
@@ -59,6 +57,8 @@ General
now inotify listeners should be automatically created (and removed)
when a subdirectory is added (or removed).
[Solaris SMF]: http://www.oracle.com/technetwork/articles/servers-storage-admin/intro-smf-basics-s11-1729181.html
Init
----
@@ -114,5 +114,3 @@ could support the basic functionality of the `at` command directly from
the shell -- that way setting up one-time jobs would not entail
re-reading `/etc/finit.conf`
[Solaris SMF]: http://www.oracle.com/technetwork/articles/servers-storage-admin/intro-smf-basics-s11-1729181.html
+61
View File
@@ -98,6 +98,54 @@ Syntax
* `network <PATH>`
Script or program to bring up networking, with optional arguments
* `cgroup <NAME> cpuset:SPEC cpu:SPEC mem:SPEC`
Create a control group for resource limiting services using cgroups.
Unlike `rlimit` this setting defines a common group to be selected for
any set of run/task/services using the `group` directive (below).
A cpuset for a cgroup can be the traditional list of CPU cores to
assign to the group, or a range that Finit will use to dynamically
allocate cores based on the amount available. The range values can be
either in absolute form or percent.
```shell
cgroup first cpuset:1
```
To create a cgroup with one exclusive CPU:
```shell
cgroup single cpuset:[1,1]
```
To create cgroup that uses at most two shared CPUs:
```shell
cgroup uptotwo cpuset:[,2]
```
To create cgroup with at least two shared CPUs:
```shell
cgroup dual cpuset:[2,]
```
To create cgroup with at least one CPU and at most half of all
available CPUs:
```shell
cgroup greedy cpuset:[1,50%]
```
* `group <NAME>`
All subsequent `service`, `task`, or `run` directives are executed in the
given control group `NAME`, which must be defined prior to this line. If
`NAME` does not yet exist the default group `default` is used.
When separate Finit `.conf` files are used for services the default group
is reset for each `.conf` file read.
* `rlimit [hard|soft] RESOURCE <LIMIT|unlimited>` Set the hard or soft
limit for a resource, or both if that argument is omitted. `RESOURCE`
is the lower-case `RLIMIT_` string constants from `setrlimit(2)`,
@@ -185,6 +233,18 @@ Syntax
For a detailed description of conditions, and how to debug them, see
the [Finit Conditions](conditions.md) document.
If a service should not be automatically started, it can be configured
as manual with the optional `manual` argument. The service can then be
started at any time by running `initctl start <service>`.
manual:yes
The name of a service, shown by the `initctl` tool, defaults to the
basename of the service executable. It can be changed with the
optional `name` argument:
name:<service-name>
* `inetd service/proto[@iflist] <wait|nowait> [LVLS] /path/to/daemon args`
Launch a daemon when a client initiates a connection on an Internet
port. Available services are listed in the UNIX `/etc/services` file.
@@ -338,6 +398,7 @@ syslog using the native `logit` tool. The full syntax is:
log:/path/to/file
log:prio:facility.level,tag:ident
log:console
log:null
log
+3 -2
View File
@@ -42,11 +42,12 @@ For your convenience a set of *optional* plugins are available:
* *discard.so*: RFC 863 plugin. Start as inetd service, like time below.
* *hwclock.so*: Restore and save system clock from/to RTC on
startup/shutdown.
* *rtc.so*: Restore and save system clock from/to RTC on boot/halt.
* *initctl.so*: Extends finit with a traditional `initctl` functionality.
* *modules-load.so*: Scans /etc/modules-load.d for modules to modprobe.
* *netlink.so*: Listens to Linux kernel Netlink events for gateway and
interfaces. These events are then sent to the Finit service monitor
for services that may want to be SIGHUP'ed on new default route or

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

+10 -2
View File
@@ -7,7 +7,7 @@ AM_CPPFLAGS += $(lite_CFLAGS)
if STATIC
noinst_LTLIBRARIES = libplug.la
libplug_la_SOURCES = bootmisc.c hwclock.c initctl.c pidfile.c procps.c tty.c urandom.c
libplug_la_SOURCES = bootmisc.c rtc.c initctl.c pidfile.c procps.c tty.c urandom.c
if BUILD_ALSA_UTILS_PLUGIN
libplug_la_SOURCES += alsa-utils.c
@@ -39,6 +39,10 @@ libplug_la_SOURCES += time.c
endif
endif
if BUILD_MODULES_LOAD_PLUGIN
libplug_la_SOURCES += modules-load.c
endif
if BUILD_NETLINK_PLUGIN
libplug_la_SOURCES += netlink.c
endif
@@ -48,7 +52,7 @@ libplug_la_SOURCES += resolvconf.c
endif
else
pkglib_LTLIBRARIES = bootmisc.la hwclock.la initctl.la pidfile.la procps.la tty.la urandom.la
pkglib_LTLIBRARIES = bootmisc.la rtc.la initctl.la pidfile.la procps.la tty.la urandom.la
if BUILD_ALSA_UTILS_PLUGIN
pkglib_LTLIBRARIES += alsa-utils.la
@@ -80,6 +84,10 @@ pkglib_LTLIBRARIES += time.la
endif
endif
if BUILD_MODULES_LOAD_PLUGIN
pkglib_LTLIBRARIES += modules-load.la
endif
if BUILD_NETLINK_PLUGIN
pkglib_LTLIBRARIES += netlink.la
endif
+107
View File
@@ -0,0 +1,107 @@
/* Scans /etc/modules-load.d for modules to load.
*
* Copyright (c) 2018 Robert Andersson <robert.m.andersson@se.atlascopco.com>
*
* 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 <lite/lite.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include "finit.h"
#include "helpers.h"
#include "plugin.h"
#include "service.h"
#include "conf.h"
#define MODULES_LOAD_PATH "/etc/modules-load.d"
#define SERVICE_LINE \
":%d name:modprobe.%s [2345] /sbin/modprobe %s %s -- Kernel module: %s"
static void load(void *arg)
{
FILE *fp;
DIR *dirp;
struct dirent *direntp;
char line[256];
int index = 1;
_d("Scanning " MODULES_LOAD_PATH " for config files ...");
dirp = opendir(MODULES_LOAD_PATH);
if (!dirp) return;
while ((direntp = readdir(dirp)))
{
char module_path[PATH_MAX];
strlcpy(module_path, MODULES_LOAD_PATH "/", sizeof(module_path));
strlcat(module_path, direntp->d_name, sizeof(module_path));
fp = fopen(module_path, "r");
if (!fp) continue;
while (fgets(line, sizeof(line), fp)) {
char cmd[CMD_SIZE], *mod, *args;
mod = strip_line(line);
if (!*mod) continue;
mod = chomp(mod);
if (!mod || !*mod) continue;
mod = strtok_r(mod, " ", &args);
snprintf(cmd, sizeof(cmd), SERVICE_LINE,
index, mod, mod, args, mod);
service_register(SVC_TYPE_TASK, cmd, global_rlimit, NULL);
index++;
}
fclose(fp);
}
closedir(dirp);
}
static plugin_t plugin = {
.name = __FILE__,
.hook[HOOK_BASEFS_UP] = {
.cb = load
},
};
PLUGIN_INIT(plugin_init)
{
plugin_register(&plugin);
}
PLUGIN_EXIT(plugin_exit)
{
plugin_unregister(&plugin);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+3 -2
View File
@@ -64,7 +64,7 @@ static void pidfile_callback(void *arg, int fd, int events)
continue;
mkcond(cond, sizeof(cond), svc->cmd);
if (ev->mask & (IN_CREATE | IN_ATTRIB | IN_MODIFY)) {
if (ev->mask & (IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVED_TO)) {
svc_started(svc);
cond_set(cond);
} else if (ev->mask & IN_DELETE)
@@ -111,6 +111,7 @@ static void pidfile_init(void *arg)
{
char *path;
struct context *ctx = arg;
uint32_t mask = IN_CREATE | IN_ATTRIB | IN_DELETE | IN_MODIFY | IN_MOVED_TO;
/*
* The bootmisc plugin is responsible for setting up /var/run.
@@ -123,7 +124,7 @@ static void pidfile_init(void *arg)
return;
}
ctx->wd = inotify_add_watch(ctx->fd, path, IN_CREATE | IN_ATTRIB | IN_DELETE | IN_MODIFY);
ctx->wd = inotify_add_watch(ctx->fd, path, mask);
free(path);
if (ctx->wd < 0) {
+160
View File
@@ -0,0 +1,160 @@
/* Save and restore RTC using hwclock
*
* Copyright (c) 2012-2019 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <time.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <lite/lite.h>
#include "finit.h"
#include "helpers.h"
#include "plugin.h"
/* Kernel RTC driver validates against this date for sanity check */
#define RTC_TIMESTAMP_BEGIN_2000 946684800LL /* 2000-01-01 00:00:00 */
static int rtc_open(void)
{
char *alt[] = {
"/dev/rtc",
"/dev/rtc0",
"/dev/misc/rtc"
};
size_t i;
int fd;
for (i = 0; i < NELEMS(alt); i++) {
fd = open(alt[i], O_RDONLY);
if (fd < 0)
continue;
return fd;
}
return -1;
}
static void rtc_save(void *arg)
{
struct timeval tv = { 0 };
struct tm tm = { 0 };
int fd, rc = 0;
fd = rtc_open();
if (fd < 0)
return;
rc = gettimeofday(&tv, NULL);
if (rc < 0 || tv.tv_sec < RTC_TIMESTAMP_BEGIN_2000) {
print_desc(NULL, "System clock invalid, not saving to RTC");
invalid:
logit(LOG_ERR, "System clock invalid, before 2000-01-01 00:00, not saving to RTC");
rc = 2;
goto out;
}
print_desc(NULL, "Saving system time (UTC) to RTC");
gmtime_r(&tv.tv_sec, &tm);
if (ioctl(fd, RTC_SET_TIME, &tm) < 0) {
if (EINVAL == errno)
goto invalid;
rc = 1;
goto out;
}
out:
print(rc, NULL);
close(fd);
}
static void rtc_restore(void *arg)
{
struct timeval tv = { 0 };
struct tm tm = { 0 };
int fd, rc = 0;
fd = rtc_open();
if (fd < 0)
return;
if (ioctl(fd, RTC_RD_TIME, &tm) < 0) {
char msg[120];
snprintf(msg, sizeof(msg), "Failed restoring system clock, %s",
EINVAL == errno ? "RTC time is too old" :
ENOENT == errno ? "RTC has no saved time" : "see log for details");
print_desc(NULL, msg);
invalid:
logit(LOG_ERR, "Failed restoring system clock from RTC.");
if (EINVAL == errno)
logit(LOG_ERR, "RTC time is too old (before 2000-01-01 00:00)");
else if (ENOENT == errno)
logit(LOG_ERR, "RTC has no previously saved (valid) time.");
else
logit(LOG_ERR, "RTC error code %d: %m", errno);
rc = 2;
goto out;
}
print_desc(NULL, "Restoring system clock (UTC) from RTC");
tv.tv_sec = mktime(&tm);
if (tv.tv_sec < RTC_TIMESTAMP_BEGIN_2000) {
errno = EINVAL;
goto invalid;
}
if (settimeofday(&tv, NULL) == -1)
rc = 1;
out:
print(rc, NULL);
close(fd);
}
static plugin_t plugin = {
.name = __FILE__,
.hook[HOOK_BASEFS_UP] = {
.cb = rtc_restore
},
.hook[HOOK_SHUTDOWN] = {
.cb = rtc_save
}
};
PLUGIN_INIT(plugin_init)
{
plugin_register(&plugin);
}
PLUGIN_EXIT(plugin_exit)
{
plugin_unregister(&plugin);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+2 -1
View File
@@ -10,6 +10,7 @@ endif
bin_PROGRAMS = logit
sbin_PROGRAMS = finit initctl reboot
pkglibexec_SCRIPTS = cgreaper.sh
if WATCHDOGD
pkglibexec_PROGRAMS = watchdogd
endif
@@ -17,7 +18,7 @@ endif
logit_SOURCES = logit.c
logit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
finit_SOURCES = api.c \
finit_SOURCES = api.c cgroup.c cgroup.h \
cond.c cond-w.c cond.h \
telinit.c \
conf.c conf.h \
+28 -11
View File
@@ -93,17 +93,16 @@ static int do_stop (char *buf, size_t len) { return call(stop, buf, len); }
static int do_restart(char *buf, size_t len) { return call(restart, buf, len); }
static char query_buf[368];
static int missing(char *job, int id)
static int missing(char *job, char *id)
{
char buf[20];
char idstr[13] = "";
if (!job)
job = "";
if (id > 1)
snprintf(idstr, sizeof(idstr), ":%d", id);
if (!id)
id = "";
snprintf(buf, sizeof(buf), "%s%s ", job, idstr);
snprintf(buf, sizeof(buf), "%s:%s ", job, id);
strlcat(query_buf, buf, sizeof(query_buf));
return 1;
@@ -122,8 +121,9 @@ static int do_query(struct init_request *rq, size_t len)
static svc_t *do_find(char *buf, size_t len)
{
int id = 1;
char *id = NULL;
char *ptr, *input;
svc_t *iter = NULL;
input = sanitize(buf, len);
if (!input)
@@ -132,11 +132,28 @@ static svc_t *do_find(char *buf, size_t len)
ptr = strchr(input, ':');
if (ptr) {
*ptr++ = 0;
id = atonum(ptr);
id = ptr;
}
if (isdigit(input[0]))
return svc_find_by_jobid(atonum(input), id);
if (isdigit(input[0])) {
char *ep;
long job = 0;
errno = 0;
job = strtol(input, &ep, 10);
if ((errno == ERANGE && (job == LONG_MAX || job == LONG_MIN)) ||
(errno != 0 && job == 0) ||
(input == ep))
return NULL;
if (!id)
return svc_job_iterator(&iter, 1, job);
return svc_find_by_jobid(job, id);
}
if (!id)
return svc_named_iterator(&iter, 1, input);
return svc_find_by_nameid(input, id);
}
@@ -352,8 +369,8 @@ static void api_cb(uev_t *w, void *arg, int events)
if (wdog) {
logit(LOG_NOTICE, "Stopping and removing %s (PID:%d)", wdog->cmd, wdog->pid);
stop(wdog);
if (wdog->protected) {
wdog->protected = 0;
if (wdog->protect) {
wdog->protect = 0;
wdog->runlevels = 0;
}
}
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# Called by kernel when last child in cgroup exists
BASE=/sys/fs/cgroup/finit
PROC=`basename $1`
DIR="$BASE$1"
logit -p daemon.info -t finit "$PROC stopped, cleaning up control group $DIR"
rmdir $DIR
exit 0
+219
View File
@@ -0,0 +1,219 @@
/* Finit control group support functions
*
* Copyright (c) 2019 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <errno.h>
#include <stdio.h>
#include <string.h>
#include <lite/lite.h>
#include <sys/mount.h>
#include "log.h"
#include "util.h"
static struct {
int active;
char cgroup[16];
char path[32];
} cg_avail[200];
static int cg_init = 0;
static int cg_num = 0;
/*
* Called by Finit at early boot to mount initial cgroups
*/
void cgroup_init(void)
{
FILE *fp;
char buf[80];
int opts = MS_NODEV | MS_NOEXEC | MS_NOSUID;
fp = fopen("/proc/cgroups", "r");
if (!fp) {
_d("No cgroup support");
return;
}
if (mount("none", "/sys/fs/cgroup", "tmpfs", opts, NULL)) {
_d("Failed mounting cgroups");
goto fail;
}
/* Skip first line, header */
fgets(buf, sizeof(buf), fp);
while (fgets(buf, sizeof(buf), fp)) {
char *cgroup;
char rc[80];
cgroup = strtok(buf, "\t ");
if (!cgroup)
continue;
snprintf(rc, sizeof(rc), "/sys/fs/cgroup/%s", cgroup);
mkdir(rc, 0755);
if (mount("cgroup", rc, "cgroup", opts, cgroup)) {
_d("Failed mounting %s cgroup on %s", cgroup, rc);
continue;
}
cg_avail[cg_num].active = 1;
strlcpy(cg_avail[cg_num].cgroup, cgroup, sizeof(cg_avail[cg_num].cgroup));
strlcpy(cg_avail[cg_num].path, rc, sizeof(cg_avail[cg_num].path));
cg_num++;
/* Create default group in all available cgroups */
strlcat(rc, "/default", sizeof(rc));
mkdir(rc, 0755);
}
/* Default cgroups for process monitoring */
mkdir("/sys/fs/cgroup/finit", 0755);
if (mount("none", "/sys/fs/cgroup/finit", "cgroup", opts, "none,name=finit")) {
_pe("Failed mounting Finit cgroup hierarchy");
goto fail;
}
/* Set up reaper and enable callbacks */
echo("/sys/fs/cgroup/finit/release_agent", 0, FINIT_LIBPATH_ "/cgreaper.sh");
echo("/sys/fs/cgroup/finit/notify_on_release", 0, "1");
/* Default groups, PID 1, services, and user/login processes */
mkdir("/sys/fs/cgroup/finit/init", 0755);
mkdir("/sys/fs/cgroup/finit/system", 0755);
mkdir("/sys/fs/cgroup/finit/user", 0755);
/* Move ourselves to init */
echo("/sys/fs/cgroup/finit/init/cgroup.procs", 0, "1");
/* We have signal, main screen turn on! */
cg_init = 1;
fail:
fclose(fp);
}
int cgroup_add(char *name, void (*cb)(char *, void *), void *arg)
{
int rc = 0;
int i;
for (i = 0; i < cg_num; i++) {
char path[80];
snprintf(path, sizeof(path), "%s/%s", cg_avail[i].path, name);
if (mkdir(path, 0755) && EEXIST != errno) {
rc++;
continue;
}
/* XXX: Add to cache of added groups, for cgroup_find() */
if (cb)
cb(path, arg);
}
return 0;
}
/* XXX: Temporary hackish implementation */
int cgroup_find(char *name, char *group, size_t len)
{
char path[80];
if (cg_num <= 0)
return 1;
snprintf(path, sizeof(path), "%s/%s", cg_avail[0].path, name);
if (access(path, F_OK))
return 1;
strlcpy(group, name, len);
return 0;
}
/* int cgroup_cpuset_range(char *group, int min, int max) */
/* { */
/* } */
static int move_pid(char *group, char *name, int pid)
{
char path[256];
snprintf(path, sizeof(path), "/sys/fs/cgroup/%s/%s", group, name);
if (mkdir(path, 0755) && errno != EEXIST)
return 1;
strlcat(path, "/cgroup.procs", sizeof(path));
return echo(path, 0, "%d", pid);
}
int cgroup_user(char *name)
{
if (!cg_init)
return 0;
return move_pid("finit/user", name, getpid());
}
int cgroup_service(char *cmd, int pid)
{
char *nm;
if (!cg_init)
return 0;
if (pid <= 0) {
errno = EINVAL;
return 1;
}
nm = strrchr(cmd, '/');
if (nm)
nm++;
else
nm = cmd;
return move_pid("finit/system", nm, pid);
}
int cgroup_assign(char *name, int pid)
{
char path[120];
int i, rc = 0;
for (i = 0; i < cg_num; i++) {
snprintf(path, sizeof(path), "%s/%s/cgroup.procs",
cg_avail[i].path, name);
rc += echo(path, 0, "%d", pid);
}
return rc;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+11 -44
View File
@@ -1,6 +1,6 @@
/* Save and restore RTC using hwclock
/* Finit control group support functions
*
* Copyright (c) 2012 Joachim Nilsson <troglobit@gmail.com>
* Copyright (c) 2019 Joachim Nilsson <troglobit@gmail.com>
*
* 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,49 +21,16 @@
* THE SOFTWARE.
*/
#include <lite/lite.h>
#ifndef FINIT_CGROUP_H_
#define FINIT_CGROUP_H_
#include "finit.h"
#include "helpers.h"
#include "plugin.h"
void cgroup_init (void);
static void save(void *arg)
{
_d("Saving system clock to RTC ...");
/* -w,--systohc, -u,--utc */
run_interactive("hwclock -w -u", "Saving system time (UTC) to RTC");
}
int cgroup_add (char *name, void (*cb)(char *, void *), void *arg);
int cgroup_find (char *name, char *group, size_t len);
static void restore(void *arg)
{
_d("Restoring system clock from RTC ...");
/* -s,--hctosys, -u,--utc */
run_interactive("hwclock -s -u", "Restoring system clock (UTC) from RTC");
}
int cgroup_user (char *name);
int cgroup_service (char *cmd, int pid);
int cgroup_assign (char *name, int pid);
static plugin_t plugin = {
.name = __FILE__,
.hook[HOOK_BASEFS_UP] = {
.cb = restore
},
.hook[HOOK_SHUTDOWN] = {
.cb = save
}
};
PLUGIN_INIT(plugin_init)
{
plugin_register(&plugin);
}
PLUGIN_EXIT(plugin_exit)
{
plugin_unregister(&plugin);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
#endif /* FINIT_CGROUP_H_ */
+1 -1
View File
@@ -125,7 +125,7 @@ error:
return NULL;
}
svc_t *client_svc_find(char *arg)
svc_t *client_svc_find(const char *arg)
{
int sd = -1;
struct init_request rq = {
+1 -1
View File
@@ -32,6 +32,6 @@ int client_disconnect (void);
int client_send (struct init_request *rq, ssize_t len);
svc_t *client_svc_iterator (int first);
svc_t *client_svc_find (char *arg);
svc_t *client_svc_find (const char *arg);
#endif /* FINIT_CLIENT_H_ */
+2 -2
View File
@@ -101,7 +101,7 @@ enum cond_state cond_get_agg(const char *names)
{
char *cond;
enum cond_state s = COND_ON;
static char conds[MAX_ARG_LEN];
static char conds[MAX_COND_LEN];
if (!names)
return COND_ON;
@@ -116,7 +116,7 @@ enum cond_state cond_get_agg(const char *names)
int cond_affects(const char *name, const char *names)
{
char *cond;
static char conds[MAX_ARG_LEN];
static char conds[MAX_COND_LEN];
if (!name || !names)
return 0;
+78 -26
View File
@@ -30,8 +30,10 @@
#include <lite/lite.h>
#include <lite/queue.h> /* BSD sys/queue.h API */
#include <sys/time.h>
#include <glob.h>
#include "finit.h"
#include "cgroup.h"
#include "cond.h"
#include "service.h"
#include "tty.h"
@@ -52,7 +54,8 @@ struct conf_change {
char *name;
};
static uev_t w1, w2, w3;
static char cgroup[32];
static uev_t w1, w2, w3, w4;
static TAILQ_HEAD(head, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
static int parse_conf(char *file);
@@ -341,6 +344,45 @@ error:
logit(LOG_WARNING, "rlimit: parse error");
}
static void cpuset_cb(char *grp, void *arg)
{
int cpuset = *(int *)arg;
char path[128];
if (!strstr(grp, "cpuset"))
return;
/* Both .cpus and .mems are mandatory settings */
snprintf(path, sizeof(path), "%s/cpuset.cpus", grp);
echo(path, 0, "%d", cpuset);
snprintf(path, sizeof(path), "%s/cpuset.mems", grp);
echo(path, 0, "0");
/* XXX: HACK for now, fix proper CPU allocation */
snprintf(path, sizeof(path), "%s/cpuset.cpu_exclusive", grp);
echo(path, 0, "1");
}
static void parse_cgroup(char *line)
{
char *group, *token;
int cpuset = -1;
group = strtok(line, " ");
if (!group)
return;
while ((token = strtok(NULL, " "))) {
if (!strncmp(token, "cpuset:", 7)) {
token += 7;
cpuset = atoi(token); /* XXX: fixme */
}
}
if (cpuset >= 0)
cgroup_add(group, cpuset_cb, &cpuset);
}
static void parse_static(char *line)
{
char *x;
@@ -429,6 +471,11 @@ static void parse_static(char *line)
cfglevel = 2; /* Fallback */
return;
}
if (MATCH_CMD(line, "cgroup ", x)) {
parse_cgroup(strip_line(x));
return;
}
}
static void parse_dynamic(char *line, struct rlimit rlimit[], char *file)
@@ -447,26 +494,26 @@ static void parse_dynamic(char *line, struct rlimit rlimit[], char *file)
/* Monitored daemon, will be respawned on exit */
if (MATCH_CMD(line, "service ", x)) {
service_register(SVC_TYPE_SERVICE, x, rlimit, file);
service_register(SVC_TYPE_SERVICE, x, rlimit, cgroup, file);
return;
}
/* One-shot task, will not be respawned */
if (MATCH_CMD(line, "task ", x)) {
service_register(SVC_TYPE_TASK, x, rlimit, file);
service_register(SVC_TYPE_TASK, x, rlimit, cgroup, file);
return;
}
/* Like task but waits for completion, useful w/ [S] */
if (MATCH_CMD(line, "run ", x)) {
service_register(SVC_TYPE_RUN, x, rlimit, file);
service_register(SVC_TYPE_RUN, x, rlimit, cgroup, file);
return;
}
/* Classic inetd service */
if (MATCH_CMD(line, "inetd ", x)) {
#ifdef INETD_ENABLED
service_register(SVC_TYPE_INETD, x, rlimit, file);
service_register(SVC_TYPE_INETD, x, rlimit, cgroup, file);
#else
_e("Finit built with inetd support disabled, cannot register service inetd %s!", x);
#endif
@@ -479,9 +526,15 @@ static void parse_dynamic(char *line, struct rlimit rlimit[], char *file)
return;
}
/* All subsequent run/task/service/tty/etc. directives to use this cgroup */
if (MATCH_CMD(line, "group ", x)) {
cgroup_find(x, cgroup, sizeof(cgroup));
return;
}
/* Regular or serial TTYs to run getty */
if (MATCH_CMD(line, "tty ", x)) {
tty_register(strip_line(x), rlimit, file);
tty_register(strip_line(x), rlimit, cgroup, file);
return;
}
}
@@ -499,8 +552,8 @@ static void tabstospaces(char *line)
static int parse_conf_dynamic(char *file)
{
FILE *fp;
struct rlimit rlimit[RLIMIT_NLIMITS];
FILE *fp;
fp = fopen(file, "r");
if (!fp) {
@@ -511,6 +564,9 @@ static int parse_conf_dynamic(char *file)
/* Prepare default limits for each service */
memcpy(rlimit, global_rlimit, sizeof(rlimit));
/* Prepare default cgroup for each service */
strlcpy(cgroup, "default", sizeof(cgroup));
_d("Parsing %s <<<<<<", file);
while (!feof(fp)) {
char line[LINE_SIZE] = "";
@@ -577,8 +633,8 @@ static int parse_conf(char *file)
*/
int conf_reload(void)
{
int i, num;
struct dirent **e;
size_t i;
glob_t gl;
/* Mark and sweep */
svc_mark_dynamic();
@@ -591,7 +647,7 @@ int conf_reload(void)
/* If rescue.conf is missing, fall back to a root shell */
rc = parse_conf(RESCUE_CONF);
if (rc)
tty_register(line, global_rlimit, NULL);
tty_register(line, global_rlimit, cgroup, NULL);
print(rc, "Entering rescue mode");
goto done;
@@ -601,20 +657,14 @@ int conf_reload(void)
parse_conf(FINIT_CONF);
/* Next, read all *.conf in /etc/finit.d/ */
num = scandir(rcsd, &e, NULL, alphasort);
if (num < 0) {
_d("Skipping %s, no files found ...", rcsd);
goto done;
}
glob("/etc/finit.d/*.conf", 0, NULL, &gl);
glob("/etc/finit.d/enabled/*.conf", GLOB_APPEND, NULL, &gl);
for (i = 0; i < num; i++) {
char *name = e[i]->d_name;
char path[LINE_SIZE];
for (i = 0; i < gl.gl_pathc; i++) {
char *path = gl.gl_pathv[i];
size_t len;
struct stat st;
snprintf(path, sizeof(path), "%s/%s", rcsd, name);
/* Check that it's an actual file ... beyond any symlinks */
if (lstat(path, &st)) {
_d("Skipping %s, cannot access: %s", path, strerror(errno));
@@ -650,9 +700,7 @@ int conf_reload(void)
parse_conf_dynamic(path);
}
while (num--)
free(e[num]);
free(e);
globfree(&gl);
done:
/* Drop record of all .conf changes */
@@ -783,6 +831,9 @@ static void conf_cb(uev_t *w, void *arg, int events)
break;
}
}
if (conf_any_change())
service_reload_dynamic();
}
static int add_watcher(uev_ctx_t *ctx, uev_t *w, char *path, uint32_t opt)
@@ -857,8 +908,9 @@ int conf_monitor(uev_ctx_t *ctx)
* changes to either symlink or target.
*/
rc += add_watcher(ctx, &w1, FINIT_RCSD, 0);
rc += add_watcher(ctx, &w2, FINIT_RCSD "/available", IN_DONT_FOLLOW);
rc += add_watcher(ctx, &w3, FINIT_CONF, 0);
rc += add_watcher(ctx, &w2, FINIT_RCSD "/available/", IN_DONT_FOLLOW);
rc += add_watcher(ctx, &w3, FINIT_RCSD "/enabled/", 0);
rc += add_watcher(ctx, &w4, FINIT_CONF, 0);
return rc + conf_reload();
}
@@ -869,7 +921,7 @@ int conf_monitor(uev_ctx_t *ctx)
int conf_init(void)
{
hostname = strdup(DEFHOST);
w1.fd = w2.fd = w3.fd = -1;
w1.fd = w2.fd = w3.fd = w4.fd = -1;
return conf_monitor(NULL);
}
+15 -18
View File
@@ -449,14 +449,18 @@ int run_parts(char *dir, char *cmd)
}
for (i = 0; i < num; i++) {
int j = 0;
pid_t pid = 0;
struct stat st;
char *args[NUM_ARGS];
char path[512];
char *argv[4] = {
"sh",
"-c",
path,
NULL
};
char *name = e[i]->d_name;
char path[LINE_SIZE];
pid_t pid = 0;
snprintf(path, sizeof(path), "%s/%s", dir, name);
snprintf(path, sizeof(path), "%s/%s ", dir, name);
if (stat(path, &st)) {
_d("Failed stat(%s): %s", path, strerror(errno));
continue;
@@ -467,29 +471,22 @@ int run_parts(char *dir, char *cmd)
continue;
}
/* Fill in args[], starting with full path to executable */
args[j++] = path;
/* If the callee didn't supply a run_parts() argument */
if (!cmd) {
/* Check if S<NUM>service or K<NUM>service notation is used */
_d("Checking if %s is a sysvinit startstop script ...", name);
if (name[0] == 'S' && isdigit(name[1])) {
args[j++] = "start";
} else if (name[0] == 'K' && isdigit(name[1])) {
args[j++] = "stop";
}
if (name[0] == 'S' && isdigit(name[1]))
strlcat(path, "start", sizeof(path));
else if (name[0] == 'K' && isdigit(name[1]))
strlcat(path, "stop", sizeof(path));
} else {
args[j++] = cmd;
strlcat(path, cmd, sizeof(path));
}
args[j++] = NULL;
pid = fork();
if (!pid) {
_d("Calling %s ...", path);
sig_unblock();
execv(path, args);
exit(0);
return execvp(_PATH_BSHELL, argv);
}
complete(path, pid);
+15 -7
View File
@@ -36,6 +36,7 @@
#include <lite/lite.h>
#include "finit.h"
#include "cgroup.h"
#include "cond.h"
#include "conf.h"
#include "helpers.h"
@@ -222,7 +223,7 @@ static void finalize(void)
/*
* Track bundled watchdogd in case a better one turns up
*/
svc = svc_find(FINIT_LIBPATH_ "/watchdogd", 1);
svc = svc_find(FINIT_LIBPATH_ "/watchdogd", "1");
if (svc)
wdog = svc;
@@ -345,11 +346,18 @@ int main(int argc, char* argv[])
*/
chdir("/");
umask(0);
mount("none", "/proc", "proc", 0, NULL);
mount("none", "/sys", "sysfs", 0, NULL);
if (mount("none", "/proc", "proc", 0, NULL))
_pe("Failed mounting /proc");
if (mount("none", "/sys", "sysfs", 0, NULL))
_pe("Failed mounting /sysfs");
if (fisdir("/proc/bus/usb"))
mount("none", "/proc/bus/usb", "usbfs", 0, NULL);
/*
* Initialize default control groups, if available
*/
cgroup_init();
/*
* Parse kernel command line (debug, rescue, splash, etc.)
* Also calls log_init() to set correct log level
@@ -442,19 +450,19 @@ int main(int argc, char* argv[])
/* Register udevd as a monitored service */
snprintf(cmd, sizeof(cmd), "[S12345789] pid:udevd %s -- Device event managing daemon", path);
if (service_register(SVC_TYPE_SERVICE, cmd, global_rlimit, NULL)) {
if (service_register(SVC_TYPE_SERVICE, cmd, global_rlimit, "default", NULL)) {
_pe("Failed registering %s", path);
udev = 0;
} else {
snprintf(cmd, sizeof(cmd), ":1 [S] <svc%s> "
"udevadm trigger -c add -t devices "
"-- Requesting device events", path);
service_register(SVC_TYPE_RUN, cmd, global_rlimit, NULL);
service_register(SVC_TYPE_RUN, cmd, global_rlimit, "default", NULL);
snprintf(cmd, sizeof(cmd), ":2 [S] <svc%s> "
"udevadm trigger -c add -t subsystems "
"-- Requesting subsystem events", path);
service_register(SVC_TYPE_RUN, cmd, global_rlimit, NULL);
service_register(SVC_TYPE_RUN, cmd, global_rlimit, "default", NULL);
}
free(path);
} else {
@@ -475,7 +483,7 @@ int main(int argc, char* argv[])
* Start bundled watchdogd as soon as possible, if enabled
*/
if (which(FINIT_LIBPATH_ "/watchdogd"))
service_register(SVC_TYPE_SERVICE, FINIT_LIBPATH_ "/watchdogd", global_rlimit, NULL);
service_register(SVC_TYPE_SERVICE, FINIT_LIBPATH_ "/watchdogd", global_rlimit, "default", NULL);
/*
* Mount filesystems
+2
View File
@@ -32,6 +32,7 @@
#include <termios.h>
#include "finit.h"
#include "cgroup.h"
#include "helpers.h"
#include "sig.h"
#include "utmp-api.h"
@@ -192,6 +193,7 @@ static int do_login(char *name)
{
struct stat st;
cgroup_user(name);
execl(_PATH_LOGIN, _PATH_LOGIN, name, NULL);
/*
+4 -1
View File
@@ -169,6 +169,7 @@ static void socket_cb(uev_t *w, void *arg, int events)
svc_t *svc = (svc_t *)arg, *task;
const char *conn = " connection";
char iifname[IF_NAMESIZE + 1] = "UNKNOWN";
char id[MAX_ID_LEN];
int stdin;
_d("%s: Got socket event ...", svc->cmd);
@@ -194,7 +195,8 @@ static void socket_cb(uev_t *w, void *arg, int events)
return;
}
task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN);
snprintf(id, sizeof(id), "%d", svc->inetd.next_id++);
task = svc_new(svc->cmd, id, SVC_TYPE_INETD_CONN);
if (!task) {
logit(LOG_CRIT, "%s: Unable to allocate service for inetd client", svc->cmd);
if (svc->inetd.type == SOCK_STREAM)
@@ -226,6 +228,7 @@ static void socket_cb(uev_t *w, void *arg, int events)
strlcpy(task->desc, svc->desc, sizeof(task->desc) - strlen(conn));
strlcat(task->desc, conn, sizeof(task->desc));
strlcpy(task->iifname, iifname, sizeof(task->iifname));
strlcpy(task->name, svc->name, sizeof(task->name));
task->stdin_fd = stdin;
service_step(task);
+72 -11
View File
@@ -173,7 +173,7 @@ static int do_restart(char *arg) { return do_startstop(INIT_CMD_RESTART_SVC, arg
static void show_cond_one(const char *_conds)
{
static char conds[MAX_ARG_LEN];
static char conds[MAX_COND_LEN];
char *cond;
strlcpy(conds, _conds, sizeof(conds));
@@ -438,12 +438,12 @@ static int show_status(char *arg)
printheader(NULL, "# STATUS PID RUNLEVELS SERVICE DESCRIPTION", 0);
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
char jobid[10], args[512] = "", *lvls;
char jobid[20], args[512] = "", *lvls;
// if (svc_is_unique(svc))
// snprintf(jobid, sizeof(jobid), "%d", svc->job);
// else
snprintf(jobid, sizeof(jobid), "%d:%d", svc->job, svc->id);
snprintf(jobid, sizeof(jobid), "%d:%s", svc->job, svc->id);
printf("%-9s %7s ", jobid, svc_status(svc));
if (svc_is_inetd(svc))
@@ -459,14 +459,7 @@ static int show_status(char *arg)
if (!verbose) {
int adj = screen_cols - 60;
char *name = strrchr(svc->cmd, '/');
if (!name)
name = svc->cmd;
else
name++;
printf("%-16.16s %-*.*s\n", name, adj, adj, svc->desc);
printf("%-16.16s %-*.*s\n", svc->name, adj, adj, svc->desc);
continue;
}
@@ -516,6 +509,70 @@ static int show_status(char *arg)
return 0;
}
static int dump_cgroup(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf)
{
FILE *fp;
char *group;
char path[256];
char buf[256];
int user = 0;
int num = 0;
if (tflag == FTW_F)
return 0;
snprintf(path, sizeof(path), "%s/cgroup.procs", fpath);
fp = fopen(path, "r");
if (!fp)
return 0;
if (strstr(fpath, "finit/user"))
user = 1;
group = strrchr(fpath, '/');
if (!group)
return 0;
group++;
while (fgets(buf, sizeof(buf), fp)) {
FILE *cfp;
int pid;
if (num == 0)
printf("%c :- %s/", user ? ' ' : '|', group);
num++;
pid = atoi(chomp(buf));
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
cfp = fopen(path, "r");
if (!cfp)
continue;
fgets(buf, sizeof(buf), cfp);
fclose(cfp);
printf("\n%c :- %d %s", user ? ' ' : '|', pid, buf);
}
fclose(fp);
if (num)
printf("\r%c\n", user ? ' ' : '|');
return 0;
}
static int show_cgroup(char *arg)
{
puts("finit/");
puts("|- init/");
nftw("/sys/fs/cgroup/finit/init", dump_cgroup, 20, 0);
puts("|- system/");
nftw("/sys/fs/cgroup/finit/system", dump_cgroup, 20, 0);
puts("`- user/");
nftw("/sys/fs/cgroup/finit/user", dump_cgroup, 20, 0);
return 0;
}
static int usage(int rc)
{
fprintf(stderr,
@@ -550,6 +607,8 @@ static int usage(int rc)
" status <JOB|NAME>[:ID] Show service status, by job# or name\n"
" status | show Show status of services, default command\n"
"\n"
" ps List processes based on cgroups\n"
"\n"
" runlevel [0-9] Show or set runlevel: 0 halt, 6 reboot\n"
" reboot Reboot system\n"
" halt Halt system\n"
@@ -590,6 +649,8 @@ int main(int argc, char *argv[])
{ "status", show_status },
{ "show", show_status }, /* Convenience alias */
{ "ps", show_cgroup },
{ "runlevel", do_runlevel },
{ "reboot", do_reboot },
{ "halt", do_halt },
+6 -4
View File
@@ -120,7 +120,7 @@ int serv_list(char *arg)
if (fisdir(enabled))
do_list("ENABLED ", enabled);
if (fisdir(FINIT_RCSD))
do_list("ENABLED ", FINIT_RCSD);
do_list("STATIC ", FINIT_RCSD);
return 0;
}
@@ -140,11 +140,13 @@ int serv_enable(char *arg)
}
pushd(FINIT_RCSD);
snprintf(path, sizeof(path), "available/%s", arg);
mkdir("enabled", 0755);
snprintf(path, sizeof(path), "%s/%s", available, arg);
if (!fexist(path))
errx(1, "Cannot find %s", path);
snprintf(link, sizeof(link), "%s/%s", FINIT_RCSD, arg);
snprintf(link, sizeof(link), "%s/%s", enabled, arg);
if (fexist(link))
errx(1, "%s already exists", link);
@@ -165,7 +167,7 @@ int serv_disable(char *arg)
arg = corr;
}
snprintf(link, sizeof(link), "%s/%s", FINIT_RCSD, arg);
snprintf(link, sizeof(link), "%s/%s", enabled, arg);
if (stat(link, &st))
err(1, "Cannot find %s", link);
+63 -11
View File
@@ -32,6 +32,7 @@
#include <net/if.h>
#include <lite/lite.h>
#include "cgroup.h"
#include "conf.h"
#include "cond.h"
#include "finit.h"
@@ -139,7 +140,7 @@ static int is_norespawn(void)
* @svc: Service to start
*
* Returns:
* 0 if the service was successfully started. Non-zero otherwise.
* 0 if the service was successfully started. Non-zero otherwise.
*/
static int service_start(svc_t *svc)
{
@@ -185,6 +186,12 @@ static int service_start(svc_t *svc)
sigprocmask(SIG_BLOCK, &nmask, &omask);
pid = fork();
if (pid > 0) {
cgroup_service(svc->cmd, pid);
cgroup_assign(svc->cgroup, pid);
}
if (pid == 0) {
int status;
char *home = NULL;
@@ -243,6 +250,9 @@ static int service_start(svc_t *svc)
redirect_null();
goto logit_done;
}
if (svc->log.console) {
goto logit_done;
}
/*
* Open PTY to connect to logger. A pty isn't buffered
@@ -466,7 +476,7 @@ static int service_stop(svc_t *svc)
*
* This function does some basic checks of the runtime state of Finit
* and a sanity check of the @svc before sending %SIGHUP.
*
*
* Returns:
* POSIX OK(0) or non-zero on error.
*/
@@ -558,6 +568,8 @@ static void parse_log(svc_t *svc, char *arg)
svc->log.enabled = 1;
else if (!strcmp(tok, "null") || !strcmp(tok, "/dev/null"))
svc->log.null = 1;
else if (!strcmp(tok, "console") || !strcmp(tok, "/dev/console"))
svc->log.console = 1;
else if (tok[0] == '/')
strlcpy(svc->log.file, tok, sizeof(svc->log.file));
else if (!strcmp(tok, "priority") || !strcmp(tok, "prio"))
@@ -569,6 +581,23 @@ static void parse_log(svc_t *svc, char *arg)
}
}
/*
* name:<name>
*/
static void parse_name(svc_t *svc, char *arg)
{
char *name = NULL;
if (arg && !strncasecmp(arg, "name:", 5)) {
name = arg + 5;
} else {
name = strrchr(svc->cmd, '/');
name = name ? name + 1 : svc->cmd;
}
strlcpy(svc->name, name, sizeof(svc->name));
}
/**
* parse_cmdline_args - Update the command line args in the svc struct
*
@@ -601,6 +630,7 @@ static void parse_cmdline_args(svc_t *svc, char *cmd)
* @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2)
* @cfg: Configuration, complete command, with -- for description text
* @rlimit: Limits for this service/task/run/inetd, may be global limits
* @cgroup: Control group, may be default
* @file: The file name service was loaded from
*
* This function is used to register commands to be run on different
@@ -648,17 +678,20 @@ static void parse_cmdline_args(svc_t *svc, char *cmd)
* Returns:
* POSIX OK(0) on success, or non-zero errno exit status on failure.
*/
int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
int service_register(int type, char *cfg, struct rlimit rlimit[], char *cgroup, char *file)
{
int id = -1;
char id_str[MAX_ID_LEN];
#ifdef INETD_ENABLED
int forking = 0;
#endif
int levels = 0;
int manual = 0;
char *line;
char *id = NULL;
char *username = NULL, *log = NULL, *pid = NULL;
char *service = NULL, *proto = NULL, *ifaces = NULL;
char *cmd, *desc, *runlevels = NULL, *cond = NULL;
char *name = NULL;
svc_t *svc;
plugin_t *plugin = NULL;
@@ -705,7 +738,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
else if (cmd[0] == '<') /* <[!][cond][,cond..]> */
cond = &cmd[1];
else if (cmd[0] == ':') /* :ID */
id = atoi(&cmd[1]);
id = &cmd[1];
#ifdef INETD_ENABLED
else if (!strncasecmp(cmd, "nowait", 6))
forking = 1;
@@ -716,6 +749,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
log = cmd;
else if (!strncasecmp(cmd, "pid", 3))
pid = cmd;
else if (!strncasecmp(cmd, "name:", 5))
name = cmd;
else if (!strncasecmp(cmd, "manual:yes", 10))
manual = 1;
else if (cmd[0] != '/' && strchr(cmd, '/'))
service = cmd; /* inetd service/proto */
else
@@ -773,24 +810,34 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
parse_cmdline_args(svc, cmd);
goto inetd_setup;
}
if (id <= 0)
id = svc_next_id(cmd);
if (!id) {
int n = svc_next_id_int(cmd);
if (n) {
snprintf(id_str, sizeof(id_str), "%d", n);
id = id_str;
}
}
}
recreate:
#endif
if (id <= 0)
id = 1;
if (!id)
id = "1";
svc = svc_find(cmd, id);
if (!svc) {
_d("Creating new svc for %s id #%d type %d", cmd, id, type);
_d("Creating new svc for %s id #%s type %d", cmd, id, type);
svc = svc_new(cmd, id, type);
if (!svc) {
_e("Out of memory, cannot register service %s", cmd);
free(line);
return errno = ENOMEM;
}
if (type == SVC_TYPE_SERVICE && manual) {
svc_stop(svc);
}
}
#ifdef INETD_ENABLED
else {
@@ -831,6 +878,8 @@ recreate:
conf_parse_cond(svc, cond);
parse_name(svc, name);
if (log)
parse_log(svc, log);
if (desc)
@@ -868,6 +917,9 @@ recreate:
/* Set configured limits */
memcpy(svc->rlimit, rlimit, sizeof(svc->rlimit));
/* Register desired control group */
strlcpy(svc->cgroup, cgroup, sizeof(svc->cgroup));
/* New, recently modified or unchanged ... used on reload. */
if (file && conf_changed(file))
svc_mark_dirty(svc);
@@ -875,7 +927,7 @@ recreate:
svc_mark_clean(svc);
if (!file)
svc->protected = 1;
svc->protect = 1;
/* Free duped line, from above */
free(line);
+1 -1
View File
@@ -28,7 +28,7 @@
#include "svc.h"
void service_runlevel (int newlevel);
int service_register (int type, char *line, struct rlimit rlimit[], char *file);
int service_register (int type, char *line, struct rlimit rlimit[], char *cgroup, char *file);
void service_unregister (svc_t *svc);
void service_runtask_clean (void);
+3
View File
@@ -115,6 +115,9 @@ void stty(int fd, speed_t speed)
term.c_cc[VKILL] = CTRL('U');
term.c_cc[VERASE] = CERASE;
tcsetattr(fd, TCSANOW, &term);
/* Restore cursor if it was hidden previously */
write(fd, "\033[?25h", 6);
}
/**
+39 -37
View File
@@ -77,10 +77,9 @@ static void svc_gc(void *arg)
* Returns:
* A pointer to a new &svc_t object, or %NULL if out of empty slots.
*/
svc_t *svc_new(char *cmd, int id, int type)
svc_t *svc_new(char *cmd, char *id, int type)
{
int job = -1;
char *desc;
svc_t *svc, *iter = NULL;
/* Find first job n:o if registering multiple instances */
@@ -99,16 +98,11 @@ svc_t *svc_new(char *cmd, int id, int type)
svc->type = type;
svc->job = job;
svc->id = id;
strlcpy(svc->id, id, sizeof(svc->id));
strlcpy(svc->cmd, cmd, sizeof(svc->cmd));
/* Default description, if missing */
desc = strrchr(cmd, '/');
if (desc)
desc++;
else
desc = cmd;
strlcpy(svc->desc, desc, sizeof(svc->desc));
strlcpy(svc->desc, svc->name, sizeof(svc->desc));
TAILQ_INSERT_TAIL(&svc_list, svc, link);
@@ -204,7 +198,7 @@ svc_t *svc_named_iterator(svc_t **iter, int first, char *cmd)
svc_t *svc;
for (svc = svc_iterator(iter, first); svc; svc = svc_iterator(iter, 0)) {
char *name = basename(svc->cmd);
char *name = svc->name;
if (!strncmp(name, cmd, strlen(name)))
return svc;
@@ -301,12 +295,12 @@ svc_t *svc_stop_completed(void)
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find(char *cmd, int id)
svc_t *svc_find(char *cmd, char *id)
{
svc_t *svc, *iter = NULL;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
if (svc->id == id && !strncmp(svc->cmd, cmd, strlen(svc->cmd)))
if (svc->id && !strcmp(svc->id, id) && !strncmp(svc->cmd, cmd, strlen(svc->cmd)))
return svc;
}
@@ -340,12 +334,12 @@ svc_t *svc_find_by_pid(pid_t pid)
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find_by_jobid(int job, int id)
svc_t *svc_find_by_jobid(int job, char *id)
{
svc_t *svc, *iter = NULL;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
if (svc->job == job && svc->id == id)
if (svc->job == job && svc->id && !strcmp(svc->id, id))
return svc;
}
@@ -360,19 +354,12 @@ svc_t *svc_find_by_jobid(int job, int id)
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find_by_nameid(char *name, int id)
svc_t *svc_find_by_nameid(char *name, char *id)
{
char *ptr;
svc_t *svc, *iter = NULL;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
ptr = strrchr(svc->cmd, '/');
if (ptr)
ptr++;
else
ptr = svc->cmd;
if (svc->id == id && !strcmp(name, ptr))
if (svc->id && !strcmp(svc->id, id) && !strcmp(name, svc->name))
return svc;
}
@@ -415,7 +402,7 @@ void svc_mark_dynamic(void)
svc_t *svc, *iter = NULL;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
if (svc->protected)
if (svc->protect)
continue;
if (svc_is_inetd_conn(svc))
continue;
@@ -509,18 +496,24 @@ int svc_enabled(svc_t *svc)
return 1;
}
/* Same base service, return unique ID */
int svc_next_id(char *cmd)
/* Same base service, return unique ID as an integer */
int svc_next_id_int(char *cmd)
{
int id = 0;
int n = 1;
svc_t *svc, *iter = NULL;
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
if (!strcmp(svc->cmd, cmd) && id < svc->id)
id = svc->id;
char id[MAX_ID_LEN];
snprintf(id, sizeof(id), "%d", n);
if (!strcmp(svc->cmd, cmd) && svc->id && strcmp(svc->id, id))
return n;
n++;
}
return id + 1;
return 0;
}
int svc_is_unique(svc_t *svc)
@@ -547,7 +540,7 @@ int svc_is_unique(svc_t *svc)
/*
* Used by api.c (to start/stop/restart) and initctl.c (for input validation)
*/
int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, int))
int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, char *))
{
int result = 0;
char *input, *token, *pos;
@@ -558,17 +551,27 @@ int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_foun
token = strtok_r(input, " ", &pos);
while (token) {
int id = 1;
char *id = NULL;
svc_t *svc, *iter = NULL;
char *ptr = strchr(token, ':');
if (isdigit(token[0])) {
int job = atonum(token);
char *ep;
long job = 0;
errno = 0;
job = strtol(token, &ep, 10);
if ((errno == ERANGE && (job == LONG_MAX || job == LONG_MIN)) ||
(errno != 0 && job == 0) ||
(token == ep)) {
result++;
continue;
}
if (!ptr) {
svc = svc_job_iterator(&iter, 1, job);
if (!svc && not_found)
result += not_found(NULL, job);
result += not_found(NULL, token);
while (svc) {
if (found)
@@ -577,8 +580,7 @@ int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_foun
}
} else {
*ptr++ = 0;
id = atonum(ptr);
job = atonum(token);
id = ptr;
svc = svc_find_by_jobid(job, id);
if (!svc && not_found)
@@ -599,7 +601,7 @@ int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_foun
}
} else {
*ptr++ = 0;
id = atonum(ptr);
id = ptr;
svc = svc_find_by_nameid(token, id);
if (!svc && not_found)
+15 -8
View File
@@ -66,6 +66,7 @@ typedef enum {
SVC_BLOCK_RESTARTING,
} svc_block_t;
#define MAX_ID_LEN 16
#define MAX_ARG_LEN 64
#define MAX_STR_LEN 64
#define MAX_COND_LEN (MAX_ARG_LEN * 3)
@@ -86,24 +87,29 @@ typedef struct svc {
TAILQ_ENTRY(svc) link;
/* Instance specifics */
int job, id; /* JOB:ID */
int job; /* JOB: */
char id[MAX_ID_LEN]; /* :ID */
/* Limits and scoping */
struct rlimit rlimit[RLIMIT_NLIMITS];
/* Control group */
char cgroup[32];
/* Service details */
pid_t pid;
char pidfile[MAX_ARG_LEN];
long start_time; /* Start time, as seconds since boot, from sysinfo() */
const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */
svc_type_t type; /* Service, run, task, inetd, ... */
int protected; /* Services like dbus-daemon & udev by Finit */
int protect; /* Services like dbus-daemon & udev by Finit */
const int dirty; /* -1: removal, 0: unmodified, 1: modified */
int starting; /* ... waiting for pidfile to be re-asserted */
int runlevels;
int sighup; /* This service supports SIGHUP :) */
svc_block_t block; /* Reason that this service is currently stopped */
char cond[MAX_COND_LEN];
char name[MAX_ARG_LEN];
/* Counters */
char once; /* run/task, (at least) once per runlevel */
@@ -118,6 +124,7 @@ typedef struct svc {
struct {
char enabled;
char null;
char console;
char file[64];
char prio[20];
char ident[20];
@@ -143,13 +150,13 @@ typedef struct svc {
struct timespec gc;
} svc_t;
svc_t *svc_new (char *cmd, int id, int type);
svc_t *svc_new (char *cmd, char *id, int type);
int svc_del (svc_t *svc);
svc_t *svc_find (char *cmd, int id);
svc_t *svc_find (char *cmd, char *id);
svc_t *svc_find_by_pid (pid_t pid);
svc_t *svc_find_by_jobid (int job, int id);
svc_t *svc_find_by_nameid (char *name, int id);
svc_t *svc_find_by_jobid (int job, char *id);
svc_t *svc_find_by_nameid (char *name, char *id);
svc_t *svc_find_by_pidfile (char *fn);
svc_t *svc_iterator (svc_t **iter, int first);
@@ -170,10 +177,10 @@ int svc_clean_bootstrap (svc_t *svc);
void svc_prune_bootstrap (void);
int svc_enabled (svc_t *svc);
int svc_next_id (char *cmd);
int svc_next_id_int (char *cmd);
int svc_is_unique (svc_t *svc);
int svc_parse_jobstr (char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, int));
int svc_parse_jobstr (char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, char *));
static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; }
static inline int svc_is_inetd_conn(svc_t *svc) { return svc && SVC_TYPE_INETD_CONN == svc->type; }
+8 -1
View File
@@ -32,6 +32,7 @@
#include "config.h" /* Generated by configure script */
#include "finit.h"
#include "cgroup.h"
#include "conf.h"
#include "helpers.h"
#include "tty.h"
@@ -100,6 +101,7 @@ void tty_sweep(void)
* tty_register - Register a getty on a device
* @line: Configuration, text after initial "tty"
* @rlimit: Limits for this service/task/run/inetd, may be global limits
* @cgroup: Control group, may be default
* @file: The file name TTY was loaded from
*
* A Finit tty line can use the internal getty implementation or an
@@ -118,7 +120,7 @@ void tty_sweep(void)
* Different getty implementations prefer the TTY device argument in
* different order, so take care to investigate this first.
*/
int tty_register(char *line, struct rlimit rlimit[], char *file)
int tty_register(char *line, struct rlimit rlimit[], char *cgroup, char *file)
{
struct tty *entry;
size_t i, num = 0;
@@ -274,6 +276,9 @@ again:
/* Register configured limits */
memcpy(entry->rlimit, rlimit, sizeof(entry->rlimit));
/* Register desired control group */
strlcpy(entry->cgroup, cgroup, sizeof(entry->cgroup));
if (file && conf_changed(file))
entry->dirty = 1; /* Modified, restart */
else
@@ -410,6 +415,8 @@ void tty_start(struct tty *tty)
tty->pid = run_getty(dev, tty->baud, tty->term, tty->noclear, tty->nowait, tty->rlimit);
else
tty->pid = run_getty2(dev, tty->cmd, tty->args, tty->noclear, tty->nowait, tty->rlimit);
cgroup_assign(tty->cgroup, tty->pid);
}
void tty_stop(struct tty *tty)
+4 -1
View File
@@ -51,6 +51,9 @@ struct tty {
/* Limits and scoping */
struct rlimit rlimit[RLIMIT_NLIMITS];
/* Control group */
char cgroup[32];
/* Set if modified => reloaded, or -1 when marked for removal */
int dirty;
};
@@ -58,7 +61,7 @@ struct tty {
void tty_mark (void);
void tty_sweep (void);
int tty_register (char *line, struct rlimit rlimit[], char *file);
int tty_register (char *line, struct rlimit rlimit[], char *cgroup, char *file);
int tty_unregister (struct tty *tty);
struct tty *tty_find (char *dev);
+27
View File
@@ -25,6 +25,7 @@
#include <ctype.h> /* isprint() */
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sysinfo.h> /* sysinfo() */
@@ -46,6 +47,32 @@ char *progname(char *arg0)
return prognm;
}
int echo(char *file, int append, char *fmt, ...)
{
va_list ap;
FILE *fp;
if (!file)
fp = stdout;
else
fp = fopen(file, append ? "a" : "w");
if (!fp)
return -1;
if (fmt) {
va_start(ap, fmt);
vfprintf(fp, fmt, ap);
va_end(ap);
}
/* echo(1) always adds a newline */
fprintf(fp, "\n");
if (file)
fclose(fp);
return 0;
}
int strtobytes(char *arg)
{
int mod = 0, bytes;
+1
View File
@@ -39,6 +39,7 @@ extern char *prognm;
#include <lite/conio.h>
char *progname (char *arg0);
int echo (char *file, int append, char *fmt, ...);
int strtobytes (char *arg);
void do_sleep (unsigned int sec);