diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/librc/librc-daemon.c | 45 | ||||
| -rw-r--r-- | src/librc/librc-depend.c | 133 | ||||
| -rw-r--r-- | src/librc/librc.c | 420 | ||||
| -rw-r--r-- | src/openrc-run/meson.build | 4 | ||||
| -rw-r--r-- | src/openrc-run/openrc-run.c | 77 | ||||
| -rw-r--r-- | src/openrc/meson.build | 4 | ||||
| -rw-r--r-- | src/openrc/rc.c | 137 | ||||
| -rw-r--r-- | src/rc-service/meson.build | 2 | ||||
| -rw-r--r-- | src/rc-service/rc-service.c | 16 | ||||
| -rw-r--r-- | src/rc-status/meson.build | 2 | ||||
| -rw-r--r-- | src/rc-status/rc-status.c | 13 | ||||
| -rw-r--r-- | src/rc-update/meson.build | 2 | ||||
| -rw-r--r-- | src/rc-update/rc-update.c | 15 | ||||
| -rw-r--r-- | src/shared/_usage.h | 10 | ||||
| -rw-r--r-- | src/shared/misc.c | 91 | ||||
| -rw-r--r-- | src/shared/misc.h | 12 | ||||
| -rw-r--r-- | src/supervise-daemon/supervise-daemon.c | 23 | 
17 files changed, 866 insertions, 140 deletions
| diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c index 50245129..10bf883a 100644 --- a/src/librc/librc-daemon.c +++ b/src/librc/librc-daemon.c @@ -405,13 +405,26 @@ rc_service_daemon_set(const char *service, const char *exec,  	RC_STRINGLIST *match;  	int i = 0;  	FILE *fp; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif +  	if (!exec && !pidfile) {  		errno = EINVAL;  		return false;  	} -	xasprintf(&dirpath, RC_SVCDIR "/daemons/%s", basename_c(service)); +	xasprintf(&dirpath, "%s/daemons/%s", svcdir, basename_c(service)); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif  	/* Regardless, erase any existing daemon info */  	if ((dp = opendir(dirpath))) { @@ -477,13 +490,25 @@ rc_service_started_daemon(const char *service,  	bool retval = false;  	DIR *dp;  	struct dirent *d; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	if (!service || !exec)  		return false; -	xasprintf(&dirpath, RC_SVCDIR "/daemons/%s", basename_c(service)); +	xasprintf(&dirpath, "%s/daemons/%s", svcdir, basename_c(service));  	match = _match_list(exec, argv, NULL); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif +  	if (indx > 0) {  		xasprintf(&file, "%03d", indx);  		retval = _match_daemon(dirpath, file, match); @@ -532,9 +557,21 @@ rc_service_daemons_crashed(const char *service)  	size_t i;  	char *ch_root;  	char *spidfile; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif + +	path += snprintf(dirpath, sizeof(dirpath), "%s/daemons/%s", +	    svcdir, basename_c(service)); -	path += snprintf(dirpath, sizeof(dirpath), RC_SVCDIR "/daemons/%s", -	    basename_c(service)); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif  	if (!(dp = opendir(dirpath)))  		return false; diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index d584af2c..dfb275bf 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -35,7 +35,8 @@  #define GENDEP          RC_LIBEXECDIR "/sh/gendepends.sh" -#define RC_DEPCONFIG    RC_SVCDIR "/depconfig" +#define RC_DEPCONFIG_FILE    "/depconfig" +#define RC_DEPCONFIG         RC_SVCDIR RC_DEPCONFIG_FILE  static const char *bootlevel = NULL; @@ -121,7 +122,26 @@ get_deptype(const RC_DEPINFO *depinfo, const char *type)  RC_DEPTREE *  rc_deptree_load(void) { -	return rc_deptree_load_file(RC_DEPTREE_CACHE); + +	char *cache = RC_DEPTREE_CACHE; +#ifdef RC_USER_SERVICES +	char *user_svcdir; +	RC_DEPTREE *tree; + +	if (rc_is_user()) { +		user_svcdir = rc_user_svcdir(); +		xasprintf(&cache, "%s/%s", user_svcdir, RC_DEPTREE_CACHE_FILE); + +		tree = rc_deptree_load_file(cache); + +		free(cache); +		free(user_svcdir); + +		return tree; +	} +#endif + +	return rc_deptree_load_file(cache);  }  RC_DEPTREE * @@ -668,19 +688,19 @@ static const DEPPAIR deppairs[] = {  static const char *const depdirs[] =  { -	RC_SVCDIR, -	RC_SVCDIR "/starting", -	RC_SVCDIR "/started", -	RC_SVCDIR "/stopping", -	RC_SVCDIR "/inactive", -	RC_SVCDIR "/wasinactive", -	RC_SVCDIR "/failed", -	RC_SVCDIR "/hotplugged", -	RC_SVCDIR "/daemons", -	RC_SVCDIR "/options", -	RC_SVCDIR "/exclusive", -	RC_SVCDIR "/scheduled", -	RC_SVCDIR "/tmp", +	"", +	"/starting", +	"/started", +	"/stopping", +	"/inactive", +	"/wasinactive", +	"/failed", +	"/hotplugged", +	"/daemons", +	"/options", +	"/exclusive", +	"/scheduled", +	"/tmp",  	NULL  }; @@ -693,16 +713,31 @@ rc_deptree_update_needed(time_t *newest, char *file)  	int i;  	struct stat buf;  	time_t mtime; +	char *depconfig = RC_DEPCONFIG, *cache = RC_DEPTREE_CACHE; +	char *depdir = NULL; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	char *user_sysconfdir, *tmp; + +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +		user_sysconfdir = rc_user_sysconfdir(); +		xasprintf(&cache, "%s/%s", svcdir, RC_DEPTREE_CACHE_FILE); +	} +#endif  	/* Create base directories if needed */ -	for (i = 0; depdirs[i]; i++) -		if (mkdir(depdirs[i], 0755) != 0 && errno != EEXIST) -			fprintf(stderr, "mkdir `%s': %s\n", depdirs[i], strerror(errno)); +	for (i = 0; depdirs[i]; i++) { +		xasprintf(&depdir, "%s/%s", svcdir, depdirs[i]); +		if (mkdir(depdir, 0755) != 0 && errno != EEXIST) +			fprintf(stderr, "mkdir `%s': %s\n", depdir, strerror(errno)); +		free(depdir); +	}  	/* Quick test to see if anything we use has changed and we have  	 * data in our deptree. */ -	if (stat(RC_DEPTREE_CACHE, &buf) == 0) { +	if (stat(cache, &buf) == 0) {  		mtime = buf.st_mtime;  	} else {  		/* No previous cache found. @@ -727,11 +762,38 @@ rc_deptree_update_needed(time_t *newest, char *file)  #ifdef RC_LOCAL_CONFDIR      newer |= !deep_mtime_check(RC_LOCAL_CONFDIR,true,&mtime,file);  #endif +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		newer |= !deep_mtime_check(RC_SYS_USER_INITDIR,true,&mtime,file); +		newer |= !deep_mtime_check(RC_SYS_USER_CONFDIR,true,&mtime,file); + +		xasprintf(&tmp, "%s/%s", user_sysconfdir, RC_USER_INITDIR_FOLDER); +		newer |= !deep_mtime_check(tmp,true,&mtime,file); +		free(tmp); + +		xasprintf(&tmp, "%s/%s", user_sysconfdir, RC_USER_CONFDIR_FOLDER); +		newer |= !deep_mtime_check(tmp,true,&mtime,file); +		free(tmp); + +		free(user_sysconfdir); +	} +#endif      newer |= !deep_mtime_check(RC_CONF,true,&mtime,file);  	/* Some init scripts dependencies change depending on config files  	 * outside of baselayout, like syslog-ng, so we check those too. */ -	config = rc_config_list(RC_DEPCONFIG); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		xasprintf(&depconfig, "%s/%s", svcdir, RC_DEPCONFIG_FILE); +		free(svcdir); +	} +#endif +	config = rc_config_list(depconfig); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(depconfig); +	} +#endif  	TAILQ_FOREACH(s, config, entries) {  		newer |= !deep_mtime_check(s->value, true, &mtime, file);  	} @@ -768,6 +830,11 @@ rc_deptree_update(void)  	char *line = NULL;  	size_t len = 0;  	char *depend, *depends, *service, *type, *nosys, *onosys; +	char *cache = RC_DEPTREE_CACHE; +	char *depconfig = RC_DEPCONFIG; +#ifdef RC_USER_SERVICES +	char *user_svcdir; +#endif  	size_t i, k, l;  	bool retval = true;  	const char *sys = rc_sys(); @@ -1048,7 +1115,16 @@ rc_deptree_update(void)  	   This works and should be entirely shell parseable provided that depend  	   names don't have any non shell variable characters in  	   */ -	if ((fp = fopen(RC_DEPTREE_CACHE, "w"))) { + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		user_svcdir = rc_user_svcdir(); +		xasprintf(&cache, "%s/%s", user_svcdir, RC_DEPTREE_CACHE_FILE); +		xasprintf(&depconfig, "%s/%s", user_svcdir, RC_DEPCONFIG_FILE); +		free(user_svcdir); +	} +#endif +	if ((fp = fopen(cache, "w"))) {  		i = 0;  		TAILQ_FOREACH(depinfo, deptree, entries) {  			fprintf(fp, "depinfo_%zu_service='%s'\n", @@ -1067,25 +1143,32 @@ rc_deptree_update(void)  		fclose(fp);  	} else {  		fprintf(stderr, "fopen `%s': %s\n", -			RC_DEPTREE_CACHE, strerror(errno)); +			cache, strerror(errno));  		retval = false;  	}  	/* Save our external config files to disk */  	if (TAILQ_FIRST(config)) { -		if ((fp = fopen(RC_DEPCONFIG, "w"))) { +		if ((fp = fopen(depconfig, "w"))) {  			TAILQ_FOREACH(s, config, entries)  				fprintf(fp, "%s\n", s->value);  			fclose(fp);  		} else {  			fprintf(stderr, "fopen `%s': %s\n", -				RC_DEPCONFIG, strerror(errno)); +				depconfig, strerror(errno));  			retval = false;  		}  	} else { -		unlink(RC_DEPCONFIG); +		unlink(depconfig);  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(cache); +		free(depconfig); +	} +#endif +  	rc_stringlist_free(config);  	free(deptree);  	return retval; diff --git a/src/librc/librc.c b/src/librc/librc.c index b3c98adb..9063b934 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -40,7 +40,8 @@  #  include <sys/sysctl.h>  #endif -#define RC_RUNLEVEL	RC_SVCDIR "/softlevel" +#define RC_RUNLEVEL_FOLDER "/softlevel" +#define RC_RUNLEVEL RC_SVCDIR RC_RUNLEVEL_FOLDER  #ifndef S_IXUGO  #  define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) @@ -477,6 +478,7 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS  	RC_STRINGLIST *dirs;  	RC_STRING *d, *parent;  	const char *nextlevel; +	char *runlevel_dir = RC_RUNLEVELDIR;  	/*  	 * If we haven't been passed a runlevel or a level list, or @@ -498,7 +500,15 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS  	 * We can now do exactly the above procedure for our chained  	 * runlevels.  	 */ -	snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			char *user_sysconf = rc_user_sysconfdir(); +			xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +			free(user_sysconf); +		} +#endif + +	snprintf(path, sizeof(path), "%s/%s", runlevel_dir, runlevel);  	dirs = ls_dir(path, LS_DIR);  	TAILQ_FOREACH(d, dirs, entries) {  		nextlevel = d->value; @@ -521,33 +531,78 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS  		rc_stringlist_delete(ancestor_list, nextlevel);  	}  	rc_stringlist_free(dirs); + +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			free(runlevel_dir); +		} +#endif  }  bool  rc_runlevel_starting(void)  { -	return exists(RC_STARTING); +	char *rc_starting = RC_STARTING; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_svcdir = rc_user_svcdir(); +		xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); +		free(user_svcdir); +	} +#endif +	return exists(rc_starting);  }  bool  rc_runlevel_stopping(void)  { -	return exists(RC_STOPPING); +	char *rc_stopping = RC_STOPPING; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_svcdir = rc_user_svcdir(); +		xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); +		free(user_svcdir); +	} +#endif +	return exists(rc_stopping);  }  RC_STRINGLIST *rc_runlevel_list(void)  { -	return ls_dir(RC_RUNLEVELDIR, LS_DIR); +	char *runlevel_dir = RC_RUNLEVELDIR; +	RC_STRINGLIST *runlevels; +#ifdef RC_USER_SERVICES +	char *user_sysconfdir; +	if (rc_is_user()) { +		user_sysconfdir = rc_user_sysconfdir(); +		xasprintf(&runlevel_dir, "%s/%s", user_sysconfdir, RC_USER_RUNLEVELS_FOLDER); +	} +#endif +	 runlevels = ls_dir(runlevel_dir, LS_DIR); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_dir); +	} +#endif +	return runlevels;  }  char *  rc_runlevel_get(void)  {  	FILE *fp; -	char *runlevel = NULL; +	char *runlevel = NULL, *runlevel_path = RC_RUNLEVEL;  	size_t i; -	if ((fp = fopen(RC_RUNLEVEL, "r"))) { +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *path = rc_user_svcdir(); +		xasprintf(&runlevel_path, "%s/%s", path, RC_RUNLEVEL_FOLDER); +		free(path); +	} +#endif + +	if ((fp = fopen(runlevel_path, "r"))) {  		runlevel = xmalloc(sizeof(char) * PATH_MAX);  		if (fgets(runlevel, PATH_MAX, fp)) {  			i = strlen(runlevel) - 1; @@ -558,6 +613,13 @@ rc_runlevel_get(void)  		fclose(fp);  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_path); +	} +#endif + +  	if (!runlevel || !*runlevel) {  		free(runlevel);  		runlevel = xstrdup(RC_LEVEL_SYSINIT); @@ -569,12 +631,30 @@ rc_runlevel_get(void)  bool  rc_runlevel_set(const char *runlevel)  { -	FILE *fp = fopen(RC_RUNLEVEL, "w"); +	char *runlevel_path = RC_RUNLEVEL; +	FILE *fp; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *path = rc_user_svcdir(); +		xasprintf(&runlevel_path, "%s/%s", path, RC_RUNLEVEL_FOLDER); +		free(path); +	} +#endif + +	fp = fopen(runlevel_path, "w");  	if (!fp)  		return false;  	fprintf(fp, "%s", runlevel);  	fclose(fp); + + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_path); +	} +#endif  	return true;  } @@ -587,7 +667,19 @@ rc_runlevel_exists(const char *runlevel)  	if (!runlevel || strcmp(runlevel, "") == 0 || strcmp(runlevel, ".") == 0 ||  		strcmp(runlevel, "..") == 0)  		return false; -	snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_path = rc_user_sysconfdir(); +		snprintf(path, sizeof(path), "%s/%s/%s", +				user_path, RC_USER_RUNLEVELS_FOLDER, runlevel); +	} else { +#endif +		snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); +#ifdef RC_USER_SERVICES +	} +#endif +  	if (stat(path, &buf) == 0 && S_ISDIR(buf.st_mode))  		return true;  	return false; @@ -597,11 +689,28 @@ bool  rc_runlevel_stack(const char *dst, const char *src)  {  	char d[PATH_MAX], s[PATH_MAX]; +	char *runlevel_dir = RC_RUNLEVELDIR;  	if (!rc_runlevel_exists(dst) || !rc_runlevel_exists(src))  		return false; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_sysconf = rc_user_sysconfdir(); +		xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +		free(user_sysconf); +	} +#endif +  	snprintf(s, sizeof(s), "../%s", src); -	snprintf(d, sizeof(s), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); +	snprintf(d, sizeof(s), "%s/%s/%s", runlevel_dir, dst, src); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_dir); +	} +#endif +  	return (symlink(s, d) == 0 ? true : false);  } @@ -609,8 +718,23 @@ bool  rc_runlevel_unstack(const char *dst, const char *src)  {  	char path[PATH_MAX]; +	char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_sysconf = rc_user_sysconfdir(); +		xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +		free(user_sysconf); +	} +#endif + +	snprintf(path, sizeof(path), "%s/%s/%s", runlevel_dir, dst, src); -	snprintf(path, sizeof(path), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_dir); +	} +#endif  	return (unlink(path) == 0 ? true : false);  } @@ -632,9 +756,16 @@ rc_service_resolve(const char *service)  {  	char buffer[PATH_MAX];  	char file[PATH_MAX]; +	char *svcdir = RC_SVCDIR;  	int r;  	struct stat buf; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif +  	if (!service)  		return NULL; @@ -642,14 +773,20 @@ rc_service_resolve(const char *service)  		return xstrdup(service);  	/* First check started services */ -	snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", "started", service); +	snprintf(file, sizeof(file), "%s/%s/%s", svcdir, "started", service);  	if (lstat(file, &buf) || !S_ISLNK(buf.st_mode)) { -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    "inactive", service); +		snprintf(file, sizeof(file), "%s/%s/%s", +		    svcdir, "inactive", service);  		if (lstat(file, &buf) || !S_ISLNK(buf.st_mode))  			*file = '\0';  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif +  	if (*file) {  		memset(buffer, 0, sizeof(buffer));  		r = readlink(file, buffer, sizeof(buffer)-1); @@ -657,6 +794,24 @@ rc_service_resolve(const char *service)  			return xstrdup(buffer);  	} +#ifdef RC_USER_SERVICES +	/* If we're in user services mode, try user services*/ +	if (rc_is_user()) { +		/* User defined services have preference to system provided */ +		char *user_sysconfdir = rc_user_sysconfdir(); +		snprintf(file, sizeof(file), "%s/%s/%s", +				user_sysconfdir, RC_USER_INITDIR_FOLDER, service); +		free(user_sysconfdir); +		if (stat(file, &buf) == 0) +			return xstrdup(file); + +		snprintf(file, sizeof(file), "%s/%s", +				RC_SYS_USER_INITDIR, service); +		if (stat(file, &buf) == 0) +			return xstrdup(file); +	} +#endif +  #ifdef RC_LOCAL_INITDIR  	/* Nope, so lets see if the user has written it */  	snprintf(file, sizeof(file), RC_LOCAL_INITDIR "/%s", service); @@ -791,9 +946,24 @@ bool  rc_service_in_runlevel(const char *service, const char *runlevel)  {  	char file[PATH_MAX]; +	char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			char *user_sysconf = rc_user_sysconfdir(); +			xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +			free(user_sysconf); +		} +#endif +	snprintf(file, sizeof(file), "%s/%s/%s", +	    runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			free(runlevel_dir); +		} +#endif -	snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", -	    runlevel, basename_c(service));  	return exists(file);  } @@ -811,6 +981,12 @@ rc_service_mark(const char *service, const RC_SERVICE state)  	RC_STRINGLIST *dirs;  	RC_STRING *dir;  	int serrno; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	if (!init)  		return false; @@ -822,8 +998,8 @@ rc_service_mark(const char *service, const RC_SERVICE state)  			return false;  		} -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    rc_parse_service_state(state), base); +		snprintf(file, sizeof(file), "%s/%s/%s", +		    svcdir,rc_parse_service_state(state), base);  		if (exists(file))  			unlink(file);  		i = symlink(init, file); @@ -849,15 +1025,16 @@ rc_service_mark(const char *service, const RC_SERVICE state)  			s != RC_SERVICE_SCHEDULED) &&  		    (!skip_wasinactive || s != RC_SERVICE_WASINACTIVE))  		{ -			snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -			    rc_service_state_names[i].name, base); +			snprintf(file, sizeof(file), "%s/%s/%s", +			    svcdir, rc_service_state_names[i].name, base);  			if (exists(file)) {  				if ((state == RC_SERVICE_STARTING ||  					state == RC_SERVICE_STOPPING) &&  				    s == RC_SERVICE_INACTIVE)  				{  					snprintf(was, sizeof(was), -					    RC_SVCDIR "/%s/%s", +					    "%s/%s/%s", +						svcdir,  					    rc_parse_service_state(RC_SERVICE_WASINACTIVE),  					    base);  					if (symlink(init, was) == -1) { @@ -879,19 +1056,19 @@ rc_service_mark(const char *service, const RC_SERVICE state)  	    state == RC_SERVICE_STOPPED ||  	    state == RC_SERVICE_INACTIVE)  	{ -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    "exclusive", base); +		snprintf(file, sizeof(file), "%s/%s/%s", +		    svcdir, "exclusive", base);  		unlink(file);  	}  	/* Remove any options and daemons the service may have stored */  	if (state == RC_SERVICE_STOPPED) { -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    "options", base); +		snprintf(file, sizeof(file), "%s/%s/%s", +		    svcdir, "options", base);  		rm_dir(file, true); -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    "daemons", base); +		snprintf(file, sizeof(file), "%s/%s/%s", +		   svcdir, "daemons", base);  		rm_dir(file, true);  		rc_service_schedule_clear(service); @@ -899,7 +1076,7 @@ rc_service_mark(const char *service, const RC_SERVICE state)  	/* These are final states, so remove us from scheduled */  	if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { -		snprintf(file, sizeof(file), RC_SVCDIR "/%s", "scheduled"); +		snprintf(file, sizeof(file), "%s/%s", svcdir, "scheduled");  		dirs = ls_dir(file, 0);  		TAILQ_FOREACH(dir, dirs, entries) {  			snprintf(was, sizeof(was), "%s/%s/%s", @@ -927,10 +1104,16 @@ rc_service_state(const char *service)  	RC_STRINGLIST *dirs;  	RC_STRING *dir;  	const char *base = basename_c(service); +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	for (i = 0; rc_service_state_names[i].name; i++) { -		snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", -		    rc_service_state_names[i].name, base); +		snprintf(file, sizeof(file), "%s/%s/%s", +		    svcdir, rc_service_state_names[i].name, base);  		if (exists(file)) {  			if (rc_service_state_names[i].state <= 0x10)  				state = rc_service_state_names[i].state; @@ -944,11 +1127,14 @@ rc_service_state(const char *service)  			state |= RC_SERVICE_CRASHED;  	}  	if (state & RC_SERVICE_STOPPED) { -		dirs = ls_dir(RC_SVCDIR "/scheduled", 0); +		char *scheduled; +		xasprintf(&scheduled, "%s/%s", svcdir, "/scheduled"); +		dirs = ls_dir(scheduled, 0); +		free(scheduled);  		TAILQ_FOREACH(dir, dirs, entries) {  			snprintf(file, sizeof(file), -			    RC_SVCDIR "/scheduled/%s/%s", -			    dir->value, service); +				"%s/scheduled/%s/%s", +				svcdir, dir->value, service);  			if (exists(file)) {  				state |= RC_SERVICE_SCHEDULED;  				break; @@ -966,9 +1152,15 @@ rc_service_value_get(const char *service, const char *option)  	char *buffer = NULL;  	size_t len = 0;  	char file[PATH_MAX]; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif -	snprintf(file, sizeof(file), RC_SVCDIR "/options/%s/%s", -	    service, option); +	snprintf(file, sizeof(file), "%s/options/%s/%s", +	    svcdir, service, option);  	rc_getfile(file, &buffer, &len);  	return buffer; @@ -981,8 +1173,14 @@ rc_service_value_set(const char *service, const char *option,  	FILE *fp;  	char file[PATH_MAX];  	char *p = file; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif -	p += snprintf(file, sizeof(file), RC_SVCDIR "/options/%s", service); +	p += snprintf(file, sizeof(file), "%s/options/%s", svcdir, service);  	if (mkdir(file, 0755) != 0 && errno != EEXIST)  		return false; @@ -1006,13 +1204,19 @@ rc_service_schedule_start(const char *service, const char *service_to_start)  	char *p = file;  	char *init;  	bool retval; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	/* service may be a provided service, like net */  	if (!service || !rc_service_exists(service_to_start))  		return false; -	p += snprintf(file, sizeof(file), RC_SVCDIR "/scheduled/%s", -	    basename_c(service)); +	p += snprintf(file, sizeof(file), "%s/scheduled/%s", +	    svcdir, basename_c(service));  	if (mkdir(file, 0755) != 0 && errno != EEXIST)  		return false; @@ -1028,9 +1232,15 @@ bool  rc_service_schedule_clear(const char *service)  {  	char dir[PATH_MAX]; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif -	snprintf(dir, sizeof(dir), RC_SVCDIR "/scheduled/%s", -	    basename_c(service)); +	snprintf(dir, sizeof(dir), "%s/scheduled/%s", +	    svcdir, basename_c(service));  	if (!rm_dir(dir, true) && errno == ENOENT)  		return true;  	return false; @@ -1041,6 +1251,7 @@ rc_services_in_runlevel(const char *runlevel)  {  	char dir[PATH_MAX];  	RC_STRINGLIST *list = NULL; +	char *runlevel_dir = RC_RUNLEVELDIR;  	if (!runlevel) {  #ifdef RC_PKG_INITDIR @@ -1049,9 +1260,28 @@ rc_services_in_runlevel(const char *runlevel)  #ifdef RC_LOCAL_INITDIR  		RC_STRINGLIST *local = ls_dir(RC_LOCAL_INITDIR, LS_INITD);  #endif +#ifdef RC_USER_SERVICES +	RC_STRINGLIST *local_user; +	RC_STRINGLIST *sys_user; +	char *user_sysconf; +	char *user_initdir; +	if (rc_is_user()) { +		user_sysconf = rc_user_sysconfdir(); +		xasprintf(&user_initdir, "%s/%s", user_sysconf, RC_USER_INITDIR_FOLDER); + +		local_user = ls_dir(user_initdir, LS_INITD); +		sys_user = ls_dir(RC_SYS_USER_INITDIR, LS_INITD); + +		list = rc_stringlist_new(); +	} else { +#endif  		list = ls_dir(RC_INITDIR, LS_INITD); +#ifdef RC_USER_SERVICES +	} +#endif +  #ifdef RC_PKG_INITDIR  		TAILQ_CONCAT(list, pkg, entries);  		rc_stringlist_free(pkg); @@ -1060,13 +1290,37 @@ rc_services_in_runlevel(const char *runlevel)  		TAILQ_CONCAT(list, local, entries);  		rc_stringlist_free(local);  #endif +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		TAILQ_CONCAT(list, local_user, entries); +		TAILQ_CONCAT(list, sys_user, entries); +		rc_stringlist_free(local_user); +		rc_stringlist_free(sys_user); +		free(user_sysconf); +		free(user_initdir); +	} +#endif  		return list;  	}  	/* These special levels never contain any services */  	if (strcmp(runlevel, RC_LEVEL_SINGLE) != 0) { -		snprintf(dir, sizeof(dir), RC_RUNLEVELDIR "/%s", runlevel); +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			char *user_sysconf = rc_user_sysconfdir(); +			xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +			free(user_sysconf); +		} +#endif +		snprintf(dir, sizeof(dir), "%s/%s", runlevel_dir, runlevel);  		list = ls_dir(dir, LS_INITD); + +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			free(runlevel_dir); +		} +#endif +  	}  	if (!list)  		list = rc_stringlist_new(); @@ -1099,9 +1353,16 @@ rc_services_in_state(RC_SERVICE state)  	RC_STRING *d;  	char dir[PATH_MAX];  	char *p = dir; +	char *svcdir = RC_SVCDIR; -	p += snprintf(dir, sizeof(dir), RC_SVCDIR "/%s", -	    rc_parse_service_state(state)); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif + +	p += snprintf(dir, sizeof(dir), "%s/%s", +	    svcdir, rc_parse_service_state(state));  	if (state != RC_SERVICE_SCHEDULED)  		return ls_dir(dir, LS_INITD); @@ -1120,6 +1381,13 @@ rc_services_in_state(RC_SERVICE state)  		}  	}  	rc_stringlist_free(dirs); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif +  	return list;  } @@ -1131,7 +1399,15 @@ rc_service_add(const char *runlevel, const char *service)  	char file[PATH_MAX];  	char path[MAXPATHLEN] = { '\0' };  	char binit[PATH_MAX]; +	char *runlevel_dir = RC_RUNLEVELDIR;  	char *i; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_sysconf = rc_user_sysconfdir(); +		xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +		free(user_sysconf); +	} +#endif  	if (!rc_runlevel_exists(runlevel)) {  		errno = ENOENT; @@ -1144,8 +1420,14 @@ rc_service_add(const char *runlevel, const char *service)  	}  	i = init = rc_service_resolve(service); -	snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", -	    runlevel, basename_c(service)); +	snprintf(file, sizeof(file), "%s/%s/%s", +	    runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_dir); +	} +#endif  	/* We need to ensure that only things in /etc/init.d are added  	 * to the boot runlevel */ @@ -1172,9 +1454,25 @@ bool  rc_service_delete(const char *runlevel, const char *service)  {  	char file[PATH_MAX]; +	char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *user_sysconf = rc_user_sysconfdir(); +		xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); +		free(user_sysconf); +	} +#endif + +	snprintf(file, sizeof(file), "%s/%s/%s", +	    runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(runlevel_dir); +	} +#endif -	snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", -	    runlevel, basename_c(service));  	if (unlink(file) == 0)  		return true;  	return false; @@ -1183,14 +1481,25 @@ rc_service_delete(const char *runlevel, const char *service)  RC_STRINGLIST *  rc_services_scheduled_by(const char *service)  { -	RC_STRINGLIST *dirs = ls_dir(RC_SVCDIR "/scheduled", 0); +	RC_STRINGLIST *dirs;  	RC_STRINGLIST *list = rc_stringlist_new();  	RC_STRING *dir;  	char file[PATH_MAX]; +	char *scheduled; +	char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif +	xasprintf(&scheduled, "%s/%s", svcdir, "/scheduled"); +	dirs = ls_dir(scheduled, 0); +	free(scheduled);  	TAILQ_FOREACH(dir, dirs, entries) { -		snprintf(file, sizeof(file), RC_SVCDIR "/scheduled/%s/%s", -		    dir->value, service); +		snprintf(file, sizeof(file), "%s/scheduled/%s/%s", +		    svcdir, dir->value, service);  		if (exists(file))  			rc_stringlist_add(list, file);  	} @@ -1202,8 +1511,15 @@ RC_STRINGLIST *  rc_services_scheduled(const char *service)  {  	char dir[PATH_MAX]; +	char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif -	snprintf(dir, sizeof(dir), RC_SVCDIR "/scheduled/%s", -	    basename_c(service)); +	snprintf(dir, sizeof(dir), "%s/scheduled/%s", +	    svcdir, basename_c(service));  	return ls_dir(dir, LS_INITD);  } diff --git a/src/openrc-run/meson.build b/src/openrc-run/meson.build index 2e4b29a0..5de60456 100644 --- a/src/openrc-run/meson.build +++ b/src/openrc-run/meson.build @@ -1,6 +1,6 @@  executable('openrc-run',    ['openrc-run.c', misc_c, plugin_c, selinux_c, usage_c, version_h], -  c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags], +  c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    dependencies: [audit_dep, dl_dep, pam_dep, pam_misc_dep, selinux_dep, util_dep, crypt_dep],    include_directories: [incdir, einfo_incdir, rc_incdir], @@ -9,7 +9,7 @@ executable('openrc-run',  executable('runscript',    ['openrc-run.c', misc_c, plugin_c, selinux_c, usage_c, version_h], -  c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags], +  c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    dependencies: [audit_dep, dl_dep, pam_dep, pam_misc_dep, selinux_dep, util_dep, crypt_dep],    include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 6b28a809..6ce5ddec 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -54,7 +54,7 @@  #include "_usage.h"  #include "helpers.h" -#define PREFIX_LOCK	RC_SVCDIR "/prefix.lock" +#define PREFIX_LOCK_FILE "/prefix.lock"  #define WAIT_INTERVAL	20000000	/* usecs to poll the lock file */  #define WAIT_TIMEOUT	60		/* seconds until we timeout */ @@ -164,8 +164,21 @@ static void  unhotplug(void)  {  	char *file = NULL; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif + +	xasprintf(&file, "%s/hotplugged/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif -	xasprintf(&file, RC_SVCDIR "/hotplugged/%s", applet);  	if (exists(file) && unlink(file) != 0)  		eerror("%s: unlink `%s': %s", applet, file, strerror(errno));  	free(file); @@ -286,12 +299,25 @@ write_prefix(const char *buffer, size_t bytes, bool *prefixed)  	const char *ec_normal = ecolor(ECOLOR_NORMAL);  	ssize_t ret = 0;  	int fd = fileno(stdout), lock_fd = -1; +	char *prefix_lock = RC_SVCDIR PREFIX_LOCK_FILE; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		char *svcdir = rc_user_svcdir(); +		xasprintf(&prefix_lock, "%s/%s", svcdir, PREFIX_LOCK_FILE); +		free(svcdir); +	} +#endif  	/*  	 * Lock the prefix. -	 * open() may fail here when running as user, as RC_SVCDIR may not be writable.  	 */ -	lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664); +	lock_fd = open(prefix_lock, O_WRONLY | O_CREAT, 0664); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(prefix_lock); +	} +#endif  	if (lock_fd != -1) {  		while (flock(lock_fd, LOCK_EX) != 0) { @@ -350,6 +376,13 @@ svc_exec(const char *arg1, const char *arg2)  	int slave_tty;  	sigset_t sigchldmask;  	sigset_t oldmask; +	char *runsh; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	/* Setup our signal pipe */  	if (pipe(signal_pipe) == -1) @@ -390,20 +423,19 @@ svc_exec(const char *arg1, const char *arg2)  			dup2(slave_tty, STDERR_FILENO);  		} -		if (exists(RC_SVCDIR "/openrc-run.sh")) { +		xasprintf(&runsh, "%s/%s", svcdir, "openrc-run.sh"); +		if (exists(runsh)) {  			if (arg2)  				einfov("Executing: %s %s %s %s %s", -					RC_SVCDIR "/openrc-run.sh", RC_SVCDIR "/openrc-run.sh", +					runsh, runsh,  					service, arg1, arg2);  			else  				einfov("Executing: %s %s %s %s", -					RC_SVCDIR "/openrc-run.sh", RC_SVCDIR "/openrc-run.sh", +					runsh, runsh,  					service, arg1); -			execl(RC_SVCDIR "/openrc-run.sh", -			    RC_SVCDIR "/openrc-run.sh", -			    service, arg1, arg2, (char *) NULL); -			eerror("%s: exec `" RC_SVCDIR "/openrc-run.sh': %s", -			    service, strerror(errno)); +			execl(runsh, runsh, service, arg1, arg2, (char *) NULL); +			eerror("%s: exec '%s': %s", +			    service, runsh, strerror(errno));  			_exit(EXIT_FAILURE);  		} else {  			if (arg2) @@ -423,8 +455,15 @@ svc_exec(const char *arg1, const char *arg2)  			    service, strerror(errno));  			_exit(EXIT_FAILURE);  		} +		free(runsh);  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif +  	buffer = xmalloc(sizeof(char) * BUFSIZ);  	fd[0].fd = signal_pipe[0];  	fd[0].events = fd[1].events = POLLIN; @@ -500,6 +539,12 @@ svc_wait(const char *svc)  	bool forever = false;  	RC_STRINGLIST *keywords;  	struct timespec interval, timeout, warn; +	char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif  	/* Some services don't have a timeout, like fsck */  	keywords = rc_deptree_depend(deptree, svc, "keyword"); @@ -508,7 +553,13 @@ svc_wait(const char *svc)  		forever = true;  	rc_stringlist_free(keywords); -	xasprintf(&file, RC_SVCDIR "/exclusive/%s", basename_c(svc)); +	xasprintf(&file, "%s/exclusive/%s", svcdir, basename_c(svc)); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif  	interval.tv_sec = 0;  	interval.tv_nsec = WAIT_INTERVAL; diff --git a/src/openrc/meson.build b/src/openrc/meson.build index 9ab32ef4..3ad0d62b 100644 --- a/src/openrc/meson.build +++ b/src/openrc/meson.build @@ -1,6 +1,6 @@  executable('openrc',    ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h], -  c_args : cc_branding_flags, +  c_args : [cc_branding_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    dependencies: [dl_dep, util_dep],    include_directories: [incdir, einfo_incdir, rc_incdir], @@ -9,7 +9,7 @@ executable('openrc',  executable('rc',    ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h], -  c_args : cc_branding_flags, +  c_args : [cc_branding_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    dependencies: [dl_dep, util_dep],    include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/openrc/rc.c b/src/openrc/rc.c index 36b2e2e2..dca94a7f 100644 --- a/src/openrc/rc.c +++ b/src/openrc/rc.c @@ -49,12 +49,20 @@  #include "helpers.h"  const char *extraopts = NULL; -const char getoptstring[] = "a:no:s:S" getoptstring_COMMON; +const char getoptstring[] = "a:no:s:S" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +; +  const struct option longopts[] = {  	{ "no-stop", 0, NULL, 'n' },  	{ "override",    1, NULL, 'o' },  	{ "service",     1, NULL, 's' },  	{ "sys",         0, NULL, 'S' }, +#ifdef RC_USER_SERVICES +	longopts_USER_SERVICES +#endif  	longopts_COMMON  };  const char * const longopts_help[] = { @@ -62,6 +70,9 @@ const char * const longopts_help[] = {  	"override the next runlevel to change into\nwhen leaving single user or boot runlevels",  	"runs the service specified with the rest\nof the arguments",  	"output the RC system type, if any", +#ifdef RC_USER_SERVICES +	longopts_help_USER_SERVICES +#endif  	longopts_help_COMMON  };  const char *usagestring = ""					\ @@ -70,7 +81,8 @@ const char *usagestring = ""					\  #define INITSH                  RC_LIBEXECDIR "/sh/init.sh"  #define INITEARLYSH             RC_LIBEXECDIR "/sh/init-early.sh" -#define INTERACTIVE             RC_SVCDIR "/interactive" +#define INTERACTIVE_FILE        "/interactive" +#define INTERACTIVE             RC_SVCDIR INTERACTIVE_FILE  #define DEVBOOT			"/dev/.rcboot" @@ -95,16 +107,24 @@ clean_failed(void)  	DIR *dp;  	struct dirent *d;  	char *path; +	char *svcdir = RC_SVCDIR; +	char *failed_path; +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif +	xasprintf(&failed_path, "%s/%s", svcdir, "/failed");  	/* Clean the failed services state dir now */ -	if ((dp = opendir(RC_SVCDIR "/failed"))) { +	if ((dp = opendir(failed_path))) {  		while ((d = readdir(dp))) {  			if (d->d_name[0] == '.' &&  			    (d->d_name[1] == '\0' ||  				(d->d_name[1] == '.' && d->d_name[2] == '\0')))  				continue; -			xasprintf(&path, RC_SVCDIR "/failed/%s", d->d_name); +			xasprintf(&path, "%s/%s", failed_path, d->d_name);  			if (unlink(path))  				eerror("%s: unlink `%s': %s",  				    applet, path, strerror(errno)); @@ -112,12 +132,24 @@ clean_failed(void)  		}  		closedir(dp);  	} +	free(failed_path); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif  }  static void  cleanup(void)  {  	RC_PID *p, *tmp; +	char *rc_starting = RC_STARTING; +	char *rc_stopping = RC_STOPPING; + +#ifdef RC_USER_SERVICES +	char *user_svcdir; +#endif  	if (!rc_in_logger && !rc_in_plugin &&  	    applet && (strcmp(applet, "rc") == 0 || strcmp(applet, "openrc") == 0)) @@ -132,11 +164,27 @@ cleanup(void)  			free(termios_orig);  		} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		user_svcdir = rc_user_svcdir(); +		xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); +		xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); +		free(user_svcdir); +	} +#endif +  		/* Clean runlevel start, stop markers */ -		rmdir(RC_STARTING); -		rmdir(RC_STOPPING); +		rmdir(rc_starting); +		rmdir(rc_stopping);  		clean_failed();  		rc_logger_close(); + +#ifdef RC_USER_SERVICES +		if (rc_is_user()) { +			free(rc_starting); +			free(rc_stopping); +		} +#endif  	}  	LIST_FOREACH_SAFE(p, &service_pids, entries, tmp) { @@ -211,9 +259,24 @@ want_interactive(void)  static void  mark_interactive(void)  { -	FILE *fp = fopen(INTERACTIVE, "w"); +	FILE *fp; +	char *interactive_path = INTERACTIVE; +#ifdef RC_USER_SERVICES +	char *svcdir; +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +		xasprintf(&interactive_path, "%s/%s", svcdir, INTERACTIVE_FILE); +		free(svcdir); +	} +#endif +	fp = fopen(interactive_path, "w");  	if (fp)  		fclose(fp); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(interactive_path); +	} +#endif  }  static void @@ -649,9 +712,18 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel)  	bool interactive = false;  	RC_SERVICE state;  	bool crashed = false; +	char *interactive_path = INTERACTIVE; +#ifdef RC_USER_SERVICES +	char *svcdir; +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +		xasprintf(&interactive_path, "%s/%s", svcdir, INTERACTIVE_FILE); +		free(svcdir); +	} +#endif  	if (!rc_yesno(getenv("EINFO_QUIET"))) -		interactive = exists(INTERACTIVE); +		interactive = exists(interactive_path);  	errno = 0;  	crashed = rc_conf_yesno("rc_crashed_start");  	if (errno == ENOENT) @@ -710,10 +782,15 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel)  		strcmp(runlevel, getenv("RC_BOOTLEVEL")) == 0))  		mark_interactive();  	else { -		if (exists(INTERACTIVE)) -			unlink(INTERACTIVE); +		if (exists(interactive_path)) +			unlink(interactive_path);  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(interactive_path); +	} +#endif  }  #ifdef RC_DEBUG @@ -754,10 +831,16 @@ int main(int argc, char **argv)  	int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;  	char *krunlevel = NULL;  	char *pidstr = NULL; +	char *rc_starting = RC_STARTING; +	char *rc_stopping = RC_STOPPING;  	int opt;  	bool parallel;  	int regen = 0;  	bool nostop = false; +#ifdef RC_USER_SERVICES +	char *user_svcdir = NULL; +	char *user_sysconfdir = NULL; +#endif  #ifdef __linux__  	char *proc;  	char *p; @@ -784,11 +867,6 @@ int main(int argc, char **argv)  	if (chdir("/") == -1)  		eerror("chdir: %s", strerror(errno)); -	/* Ensure our environment is pure -	 * Also, add our configuration to it */ -	env_filter(); -	env_config(); -  	/* complain about old configuration settings if they exist */  	if (exists(RC_CONF_OLD)) {  		ewarn("%s still exists on your system and should be removed.", @@ -833,10 +911,18 @@ int main(int argc, char **argv)  				printf("%s\n", systype);  			exit(EXIT_SUCCESS);  			/* NOTREACHED */ +#ifdef RC_USER_SERVICES +		case_RC_USER_SERVICES +#endif  		case_RC_COMMON_GETOPT  		}  	} +	/* Ensure our environment is pure +	 * Also, add our configuration to it */ +	env_filter(); +	env_config(); +  	if (strcmp(applet, "rc") == 0)  		ewarn("rc is deprecated, please use openrc instead.");  	newlevel = argv[optind++]; @@ -858,6 +944,15 @@ int main(int argc, char **argv)  	setenv("RC_PID", pidstr, 1);  	free(pidstr); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		user_svcdir = rc_user_svcdir(); +		xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); +		xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); +		free(user_svcdir); +	} +#endif +  	/* Create a list of all services which should be started for the new or  	* current runlevel including those in boot, sysinit and hotplugged  	* runlevels.  Clearly, some of these will already be started so we @@ -960,7 +1055,7 @@ int main(int argc, char **argv)  	/* Clean the failed services state dir */  	clean_failed(); -	if (mkdir(RC_STOPPING, 0755) != 0) { +	if (mkdir(rc_stopping, 0755) != 0) {  		if (errno == EACCES)  			eerrorx("%s: superuser access required", applet);  		eerrorx("%s: failed to create stopping dir `%s': %s", @@ -1046,7 +1141,7 @@ int main(int argc, char **argv)  	    going_down ? newlevel : runlevel);  	hook_out = 0; -	rmdir(RC_STOPPING); +	rmdir(rc_stopping);  	/* Store the new runlevel */  	if (newlevel) { @@ -1063,7 +1158,7 @@ int main(int argc, char **argv)  		rc_logger_close();  #endif -	mkdir(RC_STARTING, 0755); +	mkdir(rc_starting, 0755);  	rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, runlevel);  	hook_out = RC_HOOK_RUNLEVEL_START_OUT; @@ -1111,6 +1206,12 @@ int main(int argc, char **argv)  		}  		rc_stringlist_free(runlevel_chain);  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(rc_stopping); +		free(rc_starting); +	} +#endif  #ifdef __linux__  	/* If the "noinit" parameter was passed on the kernel command line then diff --git a/src/rc-service/meson.build b/src/rc-service/meson.build index 3a109d8e..543daf45 100644 --- a/src/rc-service/meson.build +++ b/src/rc-service/meson.build @@ -1,6 +1,6 @@  executable('rc-service',    ['rc-service.c', misc_c, usage_c, version_h], -  c_args : cc_branding_flags, +  c_args : [cc_branding_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    include_directories: [incdir, einfo_incdir, rc_incdir],    install: true, diff --git a/src/rc-service/rc-service.c b/src/rc-service/rc-service.c index 3d2eb730..5127e75f 100644 --- a/src/rc-service/rc-service.c +++ b/src/rc-service/rc-service.c @@ -31,7 +31,11 @@  const char *applet = NULL;  const char *extraopts = NULL; -const char getoptstring[] = "cdDe:ilr:INsSZ" getoptstring_COMMON; +const char getoptstring[] = "cdDe:ilr:INsSZ" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +;  const struct option longopts[] = {  	{ "debug",     0, NULL, 'd' },  	{ "nodeps",     0, NULL, 'D' }, @@ -45,6 +49,9 @@ const struct option longopts[] = {  	{ "list",     0, NULL, 'l' },  	{ "resolve",  1, NULL, 'r' },  	{ "dry-run",     0, NULL, 'Z' }, +#ifdef RC_USER_SERVICES +	longopts_USER_SERVICES +#endif  	longopts_COMMON  };  const char * const longopts_help[] = { @@ -60,6 +67,9 @@ const char * const longopts_help[] = {  	"list all available services",  	"resolve the service name to an init script",  	"dry run (show what would happen)", +#ifdef RC_USER_SERVICES +	longopts_help_USER_SERVICES +#endif  	longopts_help_COMMON  };  const char *usagestring = ""							\ @@ -141,7 +151,9 @@ int main(int argc, char **argv)  		case 'Z':  			setenv("IN_DRYRUN", "yes", 1);  			break; - +#ifdef RC_USER_SERVICES +		case_RC_USER_SERVICES +#endif  		case_RC_COMMON_GETOPT  		}  	} diff --git a/src/rc-status/meson.build b/src/rc-status/meson.build index 7377d837..ee3f3782 100644 --- a/src/rc-status/meson.build +++ b/src/rc-status/meson.build @@ -1,6 +1,6 @@  executable('rc-status',    ['rc-status.c', misc_c, usage_c, version_h], -  c_args : cc_branding_flags, +  c_args : [cc_branding_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    dependencies: [util_dep],    include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/rc-status/rc-status.c b/src/rc-status/rc-status.c index 78d5a6af..d56fe830 100644 --- a/src/rc-status/rc-status.c +++ b/src/rc-status/rc-status.c @@ -39,7 +39,11 @@ enum format_t {  const char *applet = NULL;  const char *extraopts = NULL; +#ifdef RC_USER_SERVICES +const char getoptstring[] = "acf:lmrsSu" getoptstring_COMMON getoptstring_USER_SERVICES; +#else  const char getoptstring[] = "acf:lmrsSu" getoptstring_COMMON; +#endif  const struct option longopts[] = {  	{"all",         0, NULL, 'a'},  	{"crashed",     0, NULL, 'c'}, @@ -50,6 +54,9 @@ const struct option longopts[] = {  	{"servicelist", 0, NULL, 's'},  	{"supervised", 0, NULL, 'S'},  	{"unused",      0, NULL, 'u'}, +#ifdef RC_USER_SERVICES +	longopts_USER_SERVICES +#endif  	longopts_COMMON  };  const char * const longopts_help[] = { @@ -62,6 +69,9 @@ const char * const longopts_help[] = {  	"Show service list",  	"show supervised services",  	"Show services not assigned to any runlevel", +#ifdef RC_USER_SERVICES +	longopts_help_USER_SERVICES +#endif  	longopts_help_COMMON  };  const char *usagestring = ""						\ @@ -354,6 +364,9 @@ int main(int argc, char **argv)  			goto exit;  			/* NOTREACHED */ +#ifdef RC_USER_SERVICES +		case_RC_USER_SERVICES +#endif  		case_RC_COMMON_GETOPT  		} diff --git a/src/rc-update/meson.build b/src/rc-update/meson.build index 7d284979..4d3b9015 100644 --- a/src/rc-update/meson.build +++ b/src/rc-update/meson.build @@ -1,6 +1,6 @@  executable('rc-update',    ['rc-update.c', misc_c, usage_c, version_h], -  c_args : cc_branding_flags, +  c_args : [cc_branding_flags, cc_user_services_flags],    link_with: [libeinfo, librc],    include_directories: [incdir, einfo_incdir, rc_incdir],    install: true, diff --git a/src/rc-update/rc-update.c b/src/rc-update/rc-update.c index 64d781a5..f4bbbe88 100644 --- a/src/rc-update/rc-update.c +++ b/src/rc-update/rc-update.c @@ -36,17 +36,27 @@ const char *usagestring = ""							\  	"Usage: rc-update [options] add <service> [<runlevel>...]\n"	\  	"   or: rc-update [options] del <service> [<runlevel>...]\n"	\  	"   or: rc-update [options] [show [<runlevel>...]]"; -const char getoptstring[] = "asu" getoptstring_COMMON; +const char getoptstring[] = "asu" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +;  const struct option longopts[] = {  	{ "all",             0, NULL, 'a' },  	{ "stack",           0, NULL, 's' },  	{ "update",          0, NULL, 'u' }, +#ifdef RC_USER_SERVICES +	longopts_USER_SERVICES +#endif  	longopts_COMMON  };  const char * const longopts_help[] = {  	"Process all runlevels",  	"Stack a runlevel instead of a service",  	"Force an update of the dependency tree", +#ifdef RC_USER_SERVICES +	longopts_help_USER_SERVICES +#endif  	longopts_help_COMMON  }; @@ -236,6 +246,9 @@ int main(int argc, char **argv)  			if (deptree)  				rc_deptree_free(deptree);  			return ret; +#ifdef RC_USER_SERVICES +		case_RC_USER_SERVICES +#endif  		case_RC_COMMON_GETOPT  		} diff --git a/src/shared/_usage.h b/src/shared/_usage.h index 91b956e0..3264ad6f 100644 --- a/src/shared/_usage.h +++ b/src/shared/_usage.h @@ -45,6 +45,16 @@  	case 'q': case_RC_COMMON_getopt_case_q; break;			      \  	default:  case_RC_COMMON_getopt_default; break; +#ifdef RC_USER_SERVICES +#define case_RC_USER_SERVICES \ +	case 'U': rc_set_user(); break; +#define getoptstring_USER_SERVICES "U" +#define longopts_USER_SERVICES \ +	{ "user",          0, NULL, 'U'}, +#define longopts_help_USER_SERVICES \ +	"use user facing services", +#endif +  extern const char *applet;  extern const char *extraopts;  extern const char getoptstring[]; diff --git a/src/shared/misc.c b/src/shared/misc.c index 28f4e235..6970513f 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -79,6 +79,16 @@ env_filter(void)  	/* Add the user defined list of vars */  	env_allow = rc_stringlist_split(rc_conf_value("rc_env_allow"), " "); + +#ifdef RC_USER_SERVICES +	/* Needed for local user services to be found */ +	if (rc_is_user()) { +		rc_stringlist_addu(env_allow, "HOME"); +		rc_stringlist_addu(env_allow, "XDG_RUNTIME_DIR"); +		rc_stringlist_addu(env_allow, "XDG_CONFIG_HOME"); +		rc_stringlist_addu(env_allow, "RC_USER_SERVICES"); +	} +#endif  	/*  	 * If '*' is an entry in rc_env_allow, do nothing as we are to pass  	 * through all environment variables. @@ -247,8 +257,22 @@ svc_lock(const char *applet, bool ignore_lock_failure)  {  	char *file = NULL;  	int fd; +	char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif + +	xasprintf(&file, "%s/exclusive/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif -	xasprintf(&file, RC_SVCDIR "/exclusive/%s", applet);  	fd = open(file, O_WRONLY | O_CREAT | O_NONBLOCK, 0664);  	free(file);  	if (fd == -1) @@ -273,8 +297,22 @@ int  svc_unlock(const char *applet, int fd)  {  	char *file = NULL; +	char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +	} +#endif + +	xasprintf(&file, "%s/exclusive/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif -	xasprintf(&file, RC_SVCDIR "/exclusive/%s", applet);  	close(fd);  	unlink(file);  	free(file); @@ -385,15 +423,33 @@ RC_DEPTREE * _rc_deptree_load(int force, int *regen)  	struct stat st;  	struct utimbuf ut;  	FILE *fp; +	char *cache = RC_DEPTREE_CACHE; +	char *skew = RC_DEPTREE_SKEWED; +#ifdef RC_USER_SERVICES +	char *user_svcdir; +	if (rc_is_user()) { +		user_svcdir = rc_user_svcdir(); +		xasprintf(&cache, "%s%s", user_svcdir, RC_DEPTREE_CACHE_FILE); +		xasprintf(&skew, "%s%s", user_svcdir, RC_DEPTREE_SKEWED_FILE); +		free(user_svcdir); +	} +#endif  	t = 0;  	if (rc_deptree_update_needed(&t, file) || force != 0) {  		/* Test if we have permission to update the deptree */ -		fd = open(RC_DEPTREE_CACHE, O_WRONLY); +		fd = open(cache, O_WRONLY);  		merrno = errno;  		errno = serrno; -		if (fd == -1 && merrno == EACCES) +		if (fd == -1 && merrno == EACCES) { +#ifdef RC_USER_SERVICES +			if (rc_is_user()) { +				free(cache); +				free(skew); +			} +#endif  			return rc_deptree_load(); +		}  		close(fd);  		if (regen) @@ -403,30 +459,41 @@ RC_DEPTREE * _rc_deptree_load(int force, int *regen)  		eend (retval, "Failed to update the dependency tree");  		if (retval == 0) { -			if (stat(RC_DEPTREE_CACHE, &st) != 0) { -				eerror("stat(%s): %s", RC_DEPTREE_CACHE, strerror(errno)); +			if (stat(cache, &st) != 0) { +				eerror("stat(%s): %s", cache, strerror(errno)); +#ifdef RC_USER_SERVICES +				if (rc_is_user()) { +					free(cache); +					free(skew); +				} +#endif  				return NULL;  			}  			if (st.st_mtime < t) {  				eerror("Clock skew detected with `%s'", file); -				eerrorn("Adjusting mtime of `" RC_DEPTREE_CACHE -				    "' to %s", ctime(&t)); -				fp = fopen(RC_DEPTREE_SKEWED, "w"); +				eerrorn("Adjusting mtime of '%s' to %s", cache, ctime(&t)); +				fp = fopen(skew, "w");  				if (fp != NULL) {  					fprintf(fp, "%s\n", file);  					fclose(fp);  				}  				ut.actime = t;  				ut.modtime = t; -				utime(RC_DEPTREE_CACHE, &ut); +				utime(cache, &ut);  			} else { -				if (exists(RC_DEPTREE_SKEWED)) -					unlink(RC_DEPTREE_SKEWED); +				if (exists(skew)) +					unlink(skew);  			}  		}  		if (force == -1 && regen != NULL)  			*regen = retval;  	} +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(cache); +		free(skew); +	} +#endif  	return rc_deptree_load();  } diff --git a/src/shared/misc.h b/src/shared/misc.h index b158a786..132f4b80 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -33,11 +33,15 @@  #define RC_LEVEL_BOOT           "boot"  #define RC_LEVEL_DEFAULT        "default" -#define RC_DEPTREE_CACHE        RC_SVCDIR "/deptree" -#define RC_DEPTREE_SKEWED	RC_SVCDIR "/clock-skewed" +#define RC_DEPTREE_CACHE_FILE   "/deptree" +#define RC_DEPTREE_CACHE        RC_SVCDIR RC_DEPTREE_CACHE_FILE +#define RC_DEPTREE_SKEWED_FILE  "/clock-skewed" +#define RC_DEPTREE_SKEWED       RC_SVCDIR RC_DEPTREE_SKEWED_FILE  #define RC_KRUNLEVEL            RC_SVCDIR "/krunlevel" -#define RC_STARTING             RC_SVCDIR "/rc.starting" -#define RC_STOPPING             RC_SVCDIR "/rc.stopping" +#define RC_STARTING_FOLDER      "/rc.starting" +#define RC_STARTING             RC_SVCDIR RC_STARTING_FOLDER +#define RC_STOPPING_FOLDER      "/rc.stopping" +#define RC_STOPPING             RC_SVCDIR RC_STOPPING_FOLDER  #define RC_SVCDIR_STARTING      RC_SVCDIR "/starting"  #define RC_SVCDIR_INACTIVE      RC_SVCDIR "/inactive" diff --git a/src/supervise-daemon/supervise-daemon.c b/src/supervise-daemon/supervise-daemon.c index adf35630..845b118c 100644 --- a/src/supervise-daemon/supervise-daemon.c +++ b/src/supervise-daemon/supervise-daemon.c @@ -817,6 +817,19 @@ int main(int argc, char **argv)  	char **child_argv = NULL;  	char *str = NULL;  	char *cmdline = NULL; +	char *svcdir = RC_SVCDIR; +	char *pidfile_path = "/var/run"; +#ifdef RC_USER_SERVICES +	char *env; +	if (rc_is_user()) { +		svcdir = rc_user_svcdir(); +		if ((env = getenv("XDG_RUNTIME_DIR"))) { +			pidfile_path = env; +		} else { +			pidfile_path = "/tmp/" +		} +	} +#endif  	applet = basename_c(argv[0]);  	atexit(cleanup); @@ -1085,8 +1098,14 @@ int main(int argc, char **argv)  	umask(numask);  	if (!pidfile) -		xasprintf(&pidfile, "/var/run/supervise-%s.pid", svcname); -	xasprintf(&fifopath, "%s/supervise-%s.ctl", RC_SVCDIR, svcname); +		xasprintf(&pidfile, "%s/supervise-%s.pid", pidfile_path, svcname); +	xasprintf(&fifopath, "%s/supervise-%s.ctl", svcdir, svcname); +#ifdef RC_USER_SERVICES +	if (rc_is_user()) { +		free(svcdir); +	} +#endif +  	if (mkfifo(fifopath, 0600) == -1 && errno != EEXIST)  		eerrorx("%s: unable to create control fifo: %s",  				applet, strerror(errno)); | 
