diff --git a/sh/gendepends.sh.in b/sh/gendepends.sh.in index 4e32f2422..9d4233cc7 100644 --- a/sh/gendepends.sh.in +++ b/sh/gendepends.sh.in @@ -52,6 +52,71 @@ keyword() { depend() { : } +gendepend() { + [ -x "$RC_SERVICE" -a -f "$RC_SERVICE" ] || return + + # Only generate dependencies for OpenRC scripts + read one two three <"$RC_SERVICE" + case "$one" in + \#*/openrc-run) ;; + \#*/runscript) ;; + \#!) + case "$two" in + */openrc-run) ;; + */runscript) ;; + *) + return + ;; + esac + ;; + *) + return + ;; + esac + unset one two three + + RC_SVCNAME=${RC_SERVICE##*/} ; export RC_SVCNAME + + # Compat + SVCNAME=$RC_SVCNAME ; export SVCNAME + + ( + # Save stdout in fd3, then remap it to stderr + exec 3>&1 1>&2 + + _rc_c=${RC_SVCNAME%%.*} + if [ -n "$_rc_c" -a "$_rc_c" != "$RC_SVCNAME" ]; then + if [ -e "$_dir/../conf.d/$_rc_c" ]; then + . "$_dir/../conf.d/$_rc_c" + fi + fi + unset _rc_c + + if [ -e "$_dir/../conf.d/$RC_SVCNAME" ]; then + . "$_dir/../conf.d/$RC_SVCNAME" + fi + + [ -e @SYSCONFDIR@/rc.conf ] && . @SYSCONFDIR@/rc.conf + if [ -d "@SYSCONFDIR@/rc.conf.d" ]; then + for _f in "@SYSCONFDIR@"/rc.conf.d/*.conf; do + [ -e "$_f" ] && . "$_f" + done + fi + + if . "$RC_SERVICE"; then + echo "$RC_SVCNAME" >&3 + _depend + fi + ) +} + +if [ $# -gt 0 ]; then + _dir="$RC_SCRIPTDIR" + cd "$_dir" + for RC_SERVICE in "$@"; do + gendepend + done +fi _done_dirs= for _dir in $RC_SCRIPTDIRS @@ -67,61 +132,7 @@ do _done_dirs="$_done_dirs $_dir" cd "$_dir" - for RC_SERVICE in *; do - [ -x "$RC_SERVICE" -a -f "$RC_SERVICE" ] || continue - - # Only generate dependencies for OpenRC scripts - read one two three <"$RC_SERVICE" - case "$one" in - \#*/openrc-run) ;; - \#*/runscript) ;; - \#!) - case "$two" in - */openrc-run) ;; - */runscript) ;; - *) - continue - ;; - esac - ;; - *) - continue - ;; - esac - unset one two three - - RC_SVCNAME=${RC_SERVICE##*/} ; export RC_SVCNAME - - # Compat - SVCNAME=$RC_SVCNAME ; export SVCNAME - - ( - # Save stdout in fd3, then remap it to stderr - exec 3>&1 1>&2 - - _rc_c=${RC_SVCNAME%%.*} - if [ -n "$_rc_c" -a "$_rc_c" != "$RC_SVCNAME" ]; then - if [ -e "$_dir/../conf.d/$_rc_c" ]; then - . "$_dir/../conf.d/$_rc_c" - fi - fi - unset _rc_c - - if [ -e "$_dir/../conf.d/$RC_SVCNAME" ]; then - . "$_dir/../conf.d/$RC_SVCNAME" - fi - - [ -e @SYSCONFDIR@/rc.conf ] && . @SYSCONFDIR@/rc.conf - if [ -d "@SYSCONFDIR@/rc.conf.d" ]; then - for _f in "@SYSCONFDIR@"/rc.conf.d/*.conf; do - [ -e "$_f" ] && . "$_f" - done - fi - - if . "$_dir/$RC_SVCNAME"; then - echo "$RC_SVCNAME" >&3 - _depend - fi - ) + for RC_SERVICE in "$_dir"/*; do + gendepend done done diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index aff8b5c4d..6c102c564 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -777,11 +777,185 @@ rc_deptree_update_needed(time_t *newest, char *file) return newer; } +static bool +rc_deptree_save(RC_DEPTREE *deptree, RC_STRINGLIST *config, + const char *detree_path, const char *config_path) +{ + FILE *fp; + RC_DEPINFO *depinfo; + RC_DEPTYPE *deptype; + bool retval = true; + RC_STRING *s; + + /* + Now that we're purely in C, do we need to keep a shell parseable file? + I think yes as then it stays human readable + This works and should be entirely shell parseable provided that depend + names don't have any non shell variable characters in + */ + if ((fp = fopen(detree_path, "w"))) { + size_t i = 0; + TAILQ_FOREACH(depinfo, deptree, entries) { + fprintf(fp, "depinfo_%zu_service='%s'\n", i, depinfo->service); + TAILQ_FOREACH(deptype, &depinfo->depends, entries) { + size_t k = 0; + TAILQ_FOREACH(s, deptype->services, entries) + fprintf(fp, "depinfo_%zu_%s_%zu='%s'\n", i, deptype->type, k++, s->value); + } + i++; + } + fclose(fp); + } else { + fprintf(stderr, "fopen '%s': %s\n", detree_path, strerror(errno)); + retval = false; + } + + /* Save our external config files to disk */ + if (TAILQ_FIRST(config)) { + if ((fp = fopen(config_path, "w"))) { + TAILQ_FOREACH(s, config, entries) + fprintf(fp, "%s\n", s->value); + fclose(fp); + } else { + fprintf(stderr, "fopen '%s': %s\n", config_path, strerror(errno)); + retval = false; + } + } else { + unlink(config_path); + } + + return retval; +} + static void -setup_environment(void) { +rc_deptree_backref(RC_DEPTREE *deptree) +{ + RC_DEPINFO *depinfo = NULL; + RC_STRINGLIST *dupes, *types, *sorted, *visited; + + /* Phase 4 - backreference our depends */ + TAILQ_FOREACH(depinfo, deptree, entries) { + for (size_t i = 0; deppairs[i].depend; i++) { + RC_STRING *s; + RC_DEPTYPE *deptype = get_deptype(depinfo, deppairs[i].depend); + if (!deptype) + continue; + TAILQ_FOREACH(s, deptype->services, entries) { + RC_DEPINFO *di = get_depinfo(deptree, s->value); + RC_DEPTYPE *dt; + if (!di) { + if (strcmp(deptype->type, "ineed") == 0) { + fprintf(stderr, "Service '%s' needs non existent service '%s'\n", + depinfo->service, s->value); + dt = get_deptype(depinfo, "broken"); + if (!dt) + dt = make_deptype(depinfo, "broken"); + rc_stringlist_addu(dt->services, s->value); + } + continue; + } + + dt = get_deptype(di, deppairs[i].addto); + if (!dt) + dt = make_deptype(di, deppairs[i].addto); + rc_stringlist_addu(dt->services, depinfo->service); + } + } + } + + /* Phase 5 - Remove broken before directives */ + types = rc_stringlist_new(); + rc_stringlist_add(types, "ineed"); + rc_stringlist_add(types, "iwant"); + rc_stringlist_add(types, "iuse"); + rc_stringlist_add(types, "iafter"); + TAILQ_FOREACH(depinfo, deptree, entries) { + RC_STRING *s2, *s2_np, *s3, *s4; + RC_DEPTYPE *deptype = get_deptype(depinfo, "ibefore"); + if (!deptype) + continue; + sorted = rc_stringlist_new(); + visited = rc_stringlist_new(); + visit_service(deptree, types, sorted, visited, depinfo, + NULL, 0); + rc_stringlist_free(visited); + TAILQ_FOREACH_SAFE(s2, deptype->services, entries, s2_np) { + TAILQ_FOREACH(s3, sorted, entries) { + RC_DEPINFO *di = get_depinfo(deptree, s3->value); + RC_DEPTYPE *dt; + if (!di) + continue; + if (strcmp(s2->value, s3->value) == 0) { + dt = get_deptype(di, "iafter"); + if (dt) + rc_stringlist_delete(dt->services, depinfo->service); + break; + } + dt = get_deptype(di, "iprovide"); + if (!dt) + continue; + TAILQ_FOREACH(s4, dt->services, entries) { + if (strcmp(s4->value, s2->value) == 0) + break; + } + if (s4) { + di = get_depinfo(deptree, s4->value); + if (di) { + dt = get_deptype(di, "iafter"); + if (dt) + rc_stringlist_delete(dt->services, depinfo->service); + } + break; + } + } + if (s3) + rc_stringlist_delete(deptype->services, s2->value); + } + rc_stringlist_free(sorted); + } + rc_stringlist_free(types); + + /* Phase 6 - Print errors for duplicate services */ + dupes = rc_stringlist_new(); + TAILQ_FOREACH(depinfo, deptree, entries) { + int serrno = errno; + errno = 0; + rc_stringlist_addu(dupes,depinfo->service); + if (errno == EEXIST) { + fprintf(stderr, + "Error: %s is the name of a real and virtual service.\n", + depinfo->service); + } + errno = serrno; + } + rc_stringlist_free(dupes); +} + +static bool +setup_environment(const char *service) +{ + char svcpath[PATH_MAX]; char *scriptdirs, *env; size_t env_size = 0; struct utsname uts; + char *scriptdir; + + /* Some init scripts need RC_LIBEXECDIR to source stuff + Ideally we should be setting our full env instead */ + if (!getenv("RC_LIBEXECDIR")) + setenv("RC_LIBEXECDIR", RC_LIBEXECDIR, 0); + + if (uname(&uts) == 0) + setenv("RC_UNAME", uts.sysname, 1); + + if (service) { + if (!realpath(service, svcpath)) + return false; + + scriptdir = dirname(svcpath); + setenv("RC_SCRIPTDIR", scriptdir, true); + return true; + } for (const char * const *dirs = rc_scriptdirs(); *dirs; dirs++) env_size += strlen(*dirs) + sizeof(' '); @@ -797,53 +971,39 @@ setup_environment(void) { setenv("RC_SCRIPTDIRS", env, 1); free(env); - /* Some init scripts need RC_LIBEXECDIR to source stuff - Ideally we should be setting our full env instead */ - if (!getenv("RC_LIBEXECDIR")) - setenv("RC_LIBEXECDIR", RC_LIBEXECDIR, 0); - - if (uname(&uts) == 0) - setenv("RC_UNAME", uts.sysname, 1); + return true; } -/* This is a 7 phase operation - Phase 1 is a shell script which loads each init script and config in turn - and echos their dependency info to stdout - Phase 2 takes that and populates a depinfo object with that data - Phase 3 adds any provided services to the depinfo object - Phase 4 scans that depinfo object and puts in backlinks - Phase 5 removes broken before dependencies - Phase 6 looks for duplicate services indicating a real and virtual service - with the same names - Phase 7 saves the depinfo object to disk - */ -bool -rc_deptree_update(void) +static bool +rc_deptree_generate(RC_DEPTREE *deptree, + RC_STRINGLIST *config, const char *service) { - - FILE *fp; - RC_DEPTREE *deptree, *providers; - RC_DEPINFO *depinfo = NULL, *depinfo_np, *di; RC_DEPTYPE *deptype = NULL, *dt_np, *dt, *provide; - RC_STRINGLIST *config, *dupes, *types, *sorted, *visited; - RC_STRING *s, *s2, *s2_np, *s3, *s4; - char *line = NULL; - size_t size; - char *depend, *depends, *service, *type; - char *deptree_cache, *depconfig; - size_t i, l; - bool retval = true; + RC_DEPINFO *depinfo = NULL, *depinfo_np, *di; + RC_STRING *s, *s2; + char *depend, *depends, *type; const char *sys = rc_sys(); - int serrno; + RC_DEPTREE *providers; + size_t size; + char *line; + FILE *fp; - /* Phase 1 - source all init scripts and print dependencies */ - setup_environment(); - if (!(fp = popen(GENDEP, "r"))) + if (!setup_environment(service)) return false; - config = rc_stringlist_new(); + /* Phase 1 - source all init scripts and print dependencies */ + if (service) { + char *cmd; + xasprintf(&cmd, "%s %s", GENDEP, service); + fp = popen(cmd, "r"); + free(cmd); + } else { + fp = popen(GENDEP, "r"); + } + + if (!fp) + return false; - deptree = make_deptree(); while (xgetline(&line, &size, fp) != -1) { depends = line; service = strsep(&depends, " "); @@ -874,6 +1034,7 @@ rc_deptree_update(void) /* Now add each depend to our type. We do this individually so we handle multiple spaces gracefully */ while ((depend = strsep(&depends, " "))) { + size_t len; if (depend[0] == 0) continue; @@ -887,11 +1048,11 @@ rc_deptree_update(void) continue; /* .sh files are not init scripts */ - l = strlen(depend); - if (l > 2 && - depend[l - 3] == '.' && - depend[l - 2] == 's' && - depend[l - 1] == 'h') + len = strlen(depend); + if (len > 2 && + depend[len - 3] == '.' && + depend[len - 2] == 's' && + depend[len - 1] == 'h') continue; /* Remove our dependency if instructed */ @@ -905,10 +1066,9 @@ rc_deptree_update(void) /* We need to allow `after *; before local;` to work. * Conversely, we need to allow 'before *; after modules' also */ /* If we're before something, remove us from the after list */ - if (strcmp(type, "ibefore") == 0) { - if ((dt = get_deptype(depinfo, "iafter"))) - rc_stringlist_delete(dt->services, depend); - } + if (strcmp(type, "ibefore") == 0 && (dt = get_deptype(depinfo, "iafter"))) + rc_stringlist_delete(dt->services, depend); + /* If we're after something, remove us from the before list */ if (strcmp(type, "iafter") == 0 || strcmp(type, "ineed") == 0 || @@ -925,8 +1085,9 @@ rc_deptree_update(void) /* Phase 2 - if we're a special system, remove services that don't * work for them. This doesn't stop them from being run directly. */ if (sys) { - char *nosys, *onosys; size_t len = strlen(sys); + char *nosys, *onosys; + size_t i; nosys = xmalloc(len + 2); nosys[0] = '-'; @@ -984,141 +1145,41 @@ rc_deptree_update(void) TAILQ_CONCAT(deptree, providers, entries); free(providers); - /* Phase 4 - backreference our depends */ - TAILQ_FOREACH(depinfo, deptree, entries) { - for (i = 0; deppairs[i].depend; i++) { - deptype = get_deptype(depinfo, deppairs[i].depend); - if (!deptype) - continue; - TAILQ_FOREACH(s, deptype->services, entries) { - di = get_depinfo(deptree, s->value); - if (!di) { - if (strcmp(deptype->type, "ineed") == 0) { - fprintf(stderr, "Service '%s' needs non existent service '%s'\n", - depinfo->service, s->value); - dt = get_deptype(depinfo, "broken"); - if (!dt) - dt = make_deptype(depinfo, "broken"); - rc_stringlist_addu(dt->services, s->value); - } - continue; - } - - dt = get_deptype(di, deppairs[i].addto); - if (!dt) - dt = make_deptype(di, deppairs[i].addto); - rc_stringlist_addu(dt->services, depinfo->service); - } - } - } + return true; +} - /* Phase 5 - Remove broken before directives */ - types = rc_stringlist_new(); - rc_stringlist_add(types, "ineed"); - rc_stringlist_add(types, "iwant"); - rc_stringlist_add(types, "iuse"); - rc_stringlist_add(types, "iafter"); - TAILQ_FOREACH(depinfo, deptree, entries) { - deptype = get_deptype(depinfo, "ibefore"); - if (!deptype) - continue; - sorted = rc_stringlist_new(); - visited = rc_stringlist_new(); - visit_service(deptree, types, sorted, visited, depinfo, - NULL, 0); - rc_stringlist_free(visited); - TAILQ_FOREACH_SAFE(s2, deptype->services, entries, s2_np) { - TAILQ_FOREACH(s3, sorted, entries) { - di = get_depinfo(deptree, s3->value); - if (!di) - continue; - if (strcmp(s2->value, s3->value) == 0) { - dt = get_deptype(di, "iafter"); - if (dt) - rc_stringlist_delete(dt->services, depinfo->service); - break; - } - dt = get_deptype(di, "iprovide"); - if (!dt) - continue; - TAILQ_FOREACH(s4, dt->services, entries) { - if (strcmp(s4->value, s2->value) == 0) - break; - } - if (s4) { - di = get_depinfo(deptree, s4->value); - if (di) { - dt = get_deptype(di, "iafter"); - if (dt) - rc_stringlist_delete(dt->services, depinfo->service); - } - break; - } - } - if (s3) - rc_stringlist_delete(deptype->services, s2->value); - } - rc_stringlist_free(sorted); - } - rc_stringlist_free(types); +/* This is a 7 phase operation + Phase 1 is a shell script which loads each init script and config in turn + and echos their dependency info to stdout + Phase 2 takes that and populates a depinfo object with that data + Phase 3 adds any provided services to the depinfo object + Phase 4 scans that depinfo object and puts in backlinks + Phase 5 removes broken before dependencies + Phase 6 looks for duplicate services indicating a real and virtual service + with the same names + Phase 7 saves the depinfo object to disk + */ +bool +rc_deptree_update(void) +{ + char *deptree_cache, *depconfig; + RC_STRINGLIST *config; + RC_DEPTREE *deptree; + bool retval = true; - /* Phase 6 - Print errors for duplicate services */ - dupes = rc_stringlist_new(); - TAILQ_FOREACH(depinfo, deptree, entries) { - serrno = errno; - errno = 0; - rc_stringlist_addu(dupes,depinfo->service); - if (errno == EEXIST) { - fprintf(stderr, - "Error: %s is the name of a real and virtual service.\n", - depinfo->service); - } - errno = serrno; - } - rc_stringlist_free(dupes); + deptree = make_deptree(); + config = rc_stringlist_new(); - /* Phase 7 - save to disk - Now that we're purely in C, do we need to keep a shell parseable file? - I think yes as then it stays human readable - This works and should be entirely shell parseable provided that depend - names don't have any non shell variable characters in - */ xasprintf(&deptree_cache, "%s/deptree", rc_svcdir()); - if ((fp = fopen(deptree_cache, "w"))) { - i = 0; - TAILQ_FOREACH(depinfo, deptree, entries) { - fprintf(fp, "depinfo_%zu_service='%s'\n", i, depinfo->service); - TAILQ_FOREACH(deptype, &depinfo->depends, entries) { - size_t k = 0; - TAILQ_FOREACH(s, deptype->services, entries) { - fprintf(fp, "depinfo_%zu_%s_%zu='%s'\n", - i, deptype->type, k++, s->value); - } - } - i++; - } - fclose(fp); - } else { - fprintf(stderr, "fopen '%s': %s\n", deptree_cache, strerror(errno)); - retval = false; - } - free(deptree_cache); - - /* Save our external config files to disk */ xasprintf(&depconfig, "%s/depconfig", rc_svcdir()); - if (TAILQ_FIRST(config)) { - 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", depconfig, strerror(errno)); - retval = false; - } - } else { - unlink(depconfig); - } - free(depconfig); + + /* Phases 1 - 3 */ + rc_deptree_generate(deptree, config, NULL); + /* Phases 4 - 6 */ + rc_deptree_backref(deptree); + + /* Phase 7 - save to disk */ + retval = rc_deptree_save(deptree, config, deptree_cache, depconfig); rc_stringlist_free(config); rc_deptree_free(deptree);