LCOV - code coverage report
Current view: top level - sysv-generator - sysv-generator.c (source / functions) Hit Total Coverage
Test: systemd_full.info Lines: 344 503 68.4 %
Date: 2019-08-23 13:36:53 Functions: 16 17 94.1 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 284 544 52.2 %

           Branch data     Line data    Source code
       1                 :            : /* SPDX-License-Identifier: LGPL-2.1+ */
       2                 :            : 
       3                 :            : #include <errno.h>
       4                 :            : #include <stdio.h>
       5                 :            : #include <unistd.h>
       6                 :            : 
       7                 :            : #include "alloc-util.h"
       8                 :            : #include "dirent-util.h"
       9                 :            : #include "exit-status.h"
      10                 :            : #include "fd-util.h"
      11                 :            : #include "fileio.h"
      12                 :            : #include "generator.h"
      13                 :            : #include "hashmap.h"
      14                 :            : #include "hexdecoct.h"
      15                 :            : #include "install.h"
      16                 :            : #include "log.h"
      17                 :            : #include "main-func.h"
      18                 :            : #include "mkdir.h"
      19                 :            : #include "path-lookup.h"
      20                 :            : #include "path-util.h"
      21                 :            : #include "set.h"
      22                 :            : #include "special.h"
      23                 :            : #include "specifier.h"
      24                 :            : #include "stat-util.h"
      25                 :            : #include "string-util.h"
      26                 :            : #include "strv.h"
      27                 :            : #include "unit-name.h"
      28                 :            : #include "util.h"
      29                 :            : 
      30                 :            : static const struct {
      31                 :            :         const char *path;
      32                 :            :         const char *target;
      33                 :            : } rcnd_table[] = {
      34                 :            :         /* Standard SysV runlevels for start-up */
      35                 :            :         { "rc1.d",  SPECIAL_RESCUE_TARGET     },
      36                 :            :         { "rc2.d",  SPECIAL_MULTI_USER_TARGET },
      37                 :            :         { "rc3.d",  SPECIAL_MULTI_USER_TARGET },
      38                 :            :         { "rc4.d",  SPECIAL_MULTI_USER_TARGET },
      39                 :            :         { "rc5.d",  SPECIAL_GRAPHICAL_TARGET  },
      40                 :            : 
      41                 :            :         /* We ignore the SysV runlevels for shutdown here, as SysV services get default dependencies anyway, and that
      42                 :            :          * means they are shut down anyway at system power off if running. */
      43                 :            : };
      44                 :            : 
      45                 :            : static const char *arg_dest = NULL;
      46                 :            : 
      47                 :            : typedef struct SysvStub {
      48                 :            :         char *name;
      49                 :            :         char *path;
      50                 :            :         char *description;
      51                 :            :         int sysv_start_priority;
      52                 :            :         char *pid_file;
      53                 :            :         char **before;
      54                 :            :         char **after;
      55                 :            :         char **wants;
      56                 :            :         char **wanted_by;
      57                 :            :         bool has_lsb;
      58                 :            :         bool reload;
      59                 :            :         bool loaded;
      60                 :            : } SysvStub;
      61                 :            : 
      62                 :         96 : static void free_sysvstub(SysvStub *s) {
      63         [ -  + ]:         96 :         if (!s)
      64                 :          0 :                 return;
      65                 :            : 
      66                 :         96 :         free(s->name);
      67                 :         96 :         free(s->path);
      68                 :         96 :         free(s->description);
      69                 :         96 :         free(s->pid_file);
      70                 :         96 :         strv_free(s->before);
      71                 :         96 :         strv_free(s->after);
      72                 :         96 :         strv_free(s->wants);
      73                 :         96 :         strv_free(s->wanted_by);
      74                 :         96 :         free(s);
      75                 :            : }
      76                 :            : 
      77         [ -  + ]:        104 : DEFINE_TRIVIAL_CLEANUP_FUNC(SysvStub*, free_sysvstub);
      78                 :            : 
      79                 :         76 : static void free_sysvstub_hashmapp(Hashmap **h) {
      80         [ +  + ]:        172 :         hashmap_free_with_destructor(*h, free_sysvstub);
      81                 :         76 : }
      82                 :            : 
      83                 :         32 : static int add_alias(const char *service, const char *alias) {
      84                 :            :         const char *link;
      85                 :            :         int r;
      86                 :            : 
      87         [ -  + ]:         32 :         assert(service);
      88         [ -  + ]:         32 :         assert(alias);
      89                 :            : 
      90   [ -  +  #  #  :         32 :         link = prefix_roota(arg_dest, alias);
          -  +  -  +  -  
          +  +  -  -  +  
                   +  - ]
      91                 :            : 
      92                 :         32 :         r = symlink(service, link);
      93         [ +  + ]:         32 :         if (r < 0) {
      94         [ +  - ]:          4 :                 if (errno == EEXIST)
      95                 :          4 :                         return 0;
      96                 :            : 
      97                 :          0 :                 return -errno;
      98                 :            :         }
      99                 :            : 
     100                 :         28 :         return 1;
     101                 :            : }
     102                 :            : 
     103                 :         96 : static int generate_unit_file(SysvStub *s) {
     104                 :         96 :         _cleanup_free_ char *path_escaped = NULL;
     105                 :         96 :         _cleanup_fclose_ FILE *f = NULL;
     106                 :            :         const char *unit;
     107                 :            :         char **p;
     108                 :            :         int r;
     109                 :            : 
     110         [ -  + ]:         96 :         assert(s);
     111                 :            : 
     112         [ -  + ]:         96 :         if (!s->loaded)
     113                 :          0 :                 return 0;
     114                 :            : 
     115                 :         96 :         path_escaped = specifier_escape(s->path);
     116         [ -  + ]:         96 :         if (!path_escaped)
     117                 :          0 :                 return log_oom();
     118                 :            : 
     119   [ -  +  #  #  :         96 :         unit = prefix_roota(arg_dest, s->name);
          -  +  -  +  -  
          +  +  -  -  +  
                   +  - ]
     120                 :            : 
     121                 :            :         /* We might already have a symlink with the same name from a Provides:,
     122                 :            :          * or from backup files like /etc/init.d/foo.bak. Real scripts always win,
     123                 :            :          * so remove an existing link */
     124         [ +  + ]:         96 :         if (is_symlink(unit) > 0) {
     125         [ +  - ]:          8 :                 log_warning("Overwriting existing symlink %s with real service.", unit);
     126                 :          8 :                 (void) unlink(unit);
     127                 :            :         }
     128                 :            : 
     129                 :         96 :         f = fopen(unit, "wxe");
     130         [ -  + ]:         96 :         if (!f)
     131         [ #  # ]:          0 :                 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
     132                 :            : 
     133                 :         96 :         fprintf(f,
     134                 :            :                 "# Automatically generated by systemd-sysv-generator\n\n"
     135                 :            :                 "[Unit]\n"
     136                 :            :                 "Documentation=man:systemd-sysv-generator(8)\n"
     137                 :            :                 "SourcePath=%s\n",
     138                 :            :                 path_escaped);
     139                 :            : 
     140         [ +  + ]:         96 :         if (s->description) {
     141         [ +  - ]:         88 :                 _cleanup_free_ char *t;
     142                 :            : 
     143                 :         88 :                 t = specifier_escape(s->description);
     144         [ -  + ]:         88 :                 if (!t)
     145                 :          0 :                         return log_oom();
     146                 :            : 
     147                 :         88 :                 fprintf(f, "Description=%s\n", t);
     148                 :            :         }
     149                 :            : 
     150   [ +  +  +  + ]:        308 :         STRV_FOREACH(p, s->before)
     151                 :        212 :                 fprintf(f, "Before=%s\n", *p);
     152   [ +  +  +  + ]:        128 :         STRV_FOREACH(p, s->after)
     153                 :         32 :                 fprintf(f, "After=%s\n", *p);
     154   [ +  +  +  + ]:        100 :         STRV_FOREACH(p, s->wants)
     155                 :          4 :                 fprintf(f, "Wants=%s\n", *p);
     156                 :            : 
     157                 :         96 :         fprintf(f,
     158                 :            :                 "\n[Service]\n"
     159                 :            :                 "Type=forking\n"
     160                 :            :                 "Restart=no\n"
     161                 :            :                 "TimeoutSec=5min\n"
     162                 :            :                 "IgnoreSIGPIPE=no\n"
     163                 :            :                 "KillMode=process\n"
     164                 :            :                 "GuessMainPID=no\n"
     165                 :            :                 "RemainAfterExit=%s\n",
     166                 :         96 :                 yes_no(!s->pid_file));
     167                 :            : 
     168         [ -  + ]:         96 :         if (s->pid_file) {
     169         [ #  # ]:          0 :                 _cleanup_free_ char *t;
     170                 :            : 
     171                 :          0 :                 t = specifier_escape(s->pid_file);
     172         [ #  # ]:          0 :                 if (!t)
     173                 :          0 :                         return log_oom();
     174                 :            : 
     175                 :          0 :                 fprintf(f, "PIDFile=%s\n", t);
     176                 :            :         }
     177                 :            : 
     178                 :            :         /* Consider two special LSB exit codes a clean exit */
     179         [ +  + ]:         96 :         if (s->has_lsb)
     180                 :         88 :                 fprintf(f,
     181                 :            :                         "SuccessExitStatus=%i %i\n",
     182                 :            :                         EXIT_NOTINSTALLED,
     183                 :            :                         EXIT_NOTCONFIGURED);
     184                 :            : 
     185                 :         96 :         fprintf(f,
     186                 :            :                 "ExecStart=%s start\n"
     187                 :            :                 "ExecStop=%s stop\n",
     188                 :            :                 path_escaped, path_escaped);
     189                 :            : 
     190         [ -  + ]:         96 :         if (s->reload)
     191                 :          0 :                 fprintf(f, "ExecReload=%s reload\n", path_escaped);
     192                 :            : 
     193                 :         96 :         r = fflush_and_check(f);
     194         [ -  + ]:         96 :         if (r < 0)
     195         [ #  # ]:          0 :                 return log_error_errno(r, "Failed to write unit %s: %m", unit);
     196                 :            : 
     197   [ +  +  +  + ]:        304 :         STRV_FOREACH(p, s->wanted_by)
     198                 :        208 :                 (void) generator_add_symlink(arg_dest, *p, "wants", s->name);
     199                 :            : 
     200                 :         96 :         return 1;
     201                 :            : }
     202                 :            : 
     203                 :          0 : static bool usage_contains_reload(const char *line) {
     204                 :          0 :         return (strcasestr(line, "{reload|") ||
     205         [ #  # ]:          0 :                 strcasestr(line, "{reload}") ||
     206         [ #  # ]:          0 :                 strcasestr(line, "{reload\"") ||
     207         [ #  # ]:          0 :                 strcasestr(line, "|reload|") ||
     208   [ #  #  #  # ]:          0 :                 strcasestr(line, "|reload}") ||
     209         [ #  # ]:          0 :                 strcasestr(line, "|reload\""));
     210                 :            : }
     211                 :            : 
     212                 :        372 : static char *sysv_translate_name(const char *name) {
     213                 :        372 :         _cleanup_free_ char *c = NULL;
     214                 :            :         char *res;
     215                 :            : 
     216                 :        372 :         c = strdup(name);
     217         [ -  + ]:        372 :         if (!c)
     218                 :          0 :                 return NULL;
     219                 :            : 
     220                 :        372 :         res = endswith(c, ".sh");
     221         [ +  + ]:        372 :         if (res)
     222                 :         44 :                 *res = 0;
     223                 :            : 
     224         [ -  + ]:        372 :         if (unit_name_mangle(c, 0, &res) < 0)
     225                 :          0 :                 return NULL;
     226                 :            : 
     227                 :        372 :         return res;
     228                 :            : }
     229                 :            : 
     230                 :        220 : static int sysv_translate_facility(SysvStub *s, unsigned line, const char *name, char **ret) {
     231                 :            : 
     232                 :            :         /* We silently ignore the $ prefix here. According to the LSB
     233                 :            :          * spec it simply indicates whether something is a
     234                 :            :          * standardized name or a distribution-specific one. Since we
     235                 :            :          * just follow what already exists and do not introduce new
     236                 :            :          * uses or names we don't care who introduced a new name. */
     237                 :            : 
     238                 :            :         static const char * const table[] = {
     239                 :            :                 /* LSB defined facilities */
     240                 :            :                 "local_fs",             NULL,
     241                 :            :                 "network",              SPECIAL_NETWORK_ONLINE_TARGET,
     242                 :            :                 "named",                SPECIAL_NSS_LOOKUP_TARGET,
     243                 :            :                 "portmap",              SPECIAL_RPCBIND_TARGET,
     244                 :            :                 "remote_fs",            SPECIAL_REMOTE_FS_TARGET,
     245                 :            :                 "syslog",               NULL,
     246                 :            :                 "time",                 SPECIAL_TIME_SYNC_TARGET,
     247                 :            :         };
     248                 :            : 
     249                 :            :         const char *filename;
     250                 :            :         char *filename_no_sh, *e, *m;
     251                 :            :         const char *n;
     252                 :            :         unsigned i;
     253                 :            :         int r;
     254                 :            : 
     255         [ -  + ]:        220 :         assert(name);
     256         [ -  + ]:        220 :         assert(s);
     257         [ -  + ]:        220 :         assert(ret);
     258                 :            : 
     259                 :        220 :         filename = basename(s->path);
     260                 :            : 
     261         [ +  + ]:        220 :         n = *name == '$' ? name + 1 : name;
     262                 :            : 
     263         [ +  + ]:       1168 :         for (i = 0; i < ELEMENTSOF(table); i += 2) {
     264         [ +  + ]:       1036 :                 if (!streq(table[i], n))
     265                 :        948 :                         continue;
     266                 :            : 
     267         [ +  + ]:         88 :                 if (!table[i+1]) {
     268                 :         76 :                         *ret = NULL;
     269                 :         76 :                         return 0;
     270                 :            :                 }
     271                 :            : 
     272                 :         12 :                 m = strdup(table[i+1]);
     273         [ -  + ]:         12 :                 if (!m)
     274                 :          0 :                         return log_oom();
     275                 :            : 
     276                 :         12 :                 *ret = m;
     277                 :         12 :                 return 1;
     278                 :            :         }
     279                 :            : 
     280                 :            :         /* If we don't know this name, fallback heuristics to figure
     281                 :            :          * out whether something is a target or a service alias. */
     282                 :            : 
     283                 :            :         /* Facilities starting with $ are most likely targets */
     284         [ -  + ]:        132 :         if (*name == '$')  {
     285                 :          0 :                 r = unit_name_build(n, NULL, ".target", ret);
     286         [ #  # ]:          0 :                 if (r < 0)
     287         [ #  # ]:          0 :                         return log_error_errno(r, "[%s:%u] Could not build name for facility %s: %m", s->path, line, name);
     288                 :            : 
     289                 :          0 :                 return 1;
     290                 :            :         }
     291                 :            : 
     292                 :            :         /* Strip ".sh" suffix from file name for comparison */
     293                 :        132 :         filename_no_sh = strdupa(filename);
     294                 :        132 :         e = endswith(filename_no_sh, ".sh");
     295         [ +  + ]:        132 :         if (e) {
     296                 :         12 :                 *e = '\0';
     297                 :         12 :                 filename = filename_no_sh;
     298                 :            :         }
     299                 :            : 
     300                 :            :         /* Names equaling the file name of the services are redundant */
     301         [ +  + ]:        132 :         if (streq_ptr(n, filename)) {
     302                 :         84 :                 *ret = NULL;
     303                 :         84 :                 return 0;
     304                 :            :         }
     305                 :            : 
     306                 :            :         /* Everything else we assume to be normal service names */
     307                 :         48 :         m = sysv_translate_name(n);
     308         [ -  + ]:         48 :         if (!m)
     309                 :          0 :                 return log_oom();
     310                 :            : 
     311                 :         48 :         *ret = m;
     312                 :         48 :         return 1;
     313                 :            : }
     314                 :            : 
     315                 :         88 : static int handle_provides(SysvStub *s, unsigned line, const char *full_text, const char *text) {
     316                 :            :         int r;
     317                 :            : 
     318         [ -  + ]:         88 :         assert(s);
     319         [ -  + ]:         88 :         assert(full_text);
     320         [ -  + ]:         88 :         assert(text);
     321                 :            : 
     322                 :        116 :         for (;;) {
     323   [ +  -  +  +  :        376 :                 _cleanup_free_ char *word = NULL, *m = NULL;
             +  -  +  + ]
     324                 :            : 
     325                 :        204 :                 r = extract_first_word(&text, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
     326         [ -  + ]:        204 :                 if (r < 0)
     327         [ #  # ]:          0 :                         return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line);
     328         [ +  + ]:        204 :                 if (r == 0)
     329                 :         88 :                         break;
     330                 :            : 
     331                 :        116 :                 r = sysv_translate_facility(s, line, word, &m);
     332         [ +  + ]:        116 :                 if (r <= 0) /* continue on error */
     333                 :         84 :                         continue;
     334                 :            : 
     335   [ +  -  -  - ]:         32 :                 switch (unit_name_to_type(m)) {
     336                 :            : 
     337                 :         32 :                 case UNIT_SERVICE:
     338         [ +  - ]:         32 :                         log_debug("Adding Provides: alias '%s' for '%s'", m, s->name);
     339                 :         32 :                         r = add_alias(s->name, m);
     340         [ -  + ]:         32 :                         if (r < 0)
     341         [ #  # ]:          0 :                                 log_warning_errno(r, "[%s:%u] Failed to add LSB Provides name %s, ignoring: %m", s->path, line, m);
     342                 :         32 :                         break;
     343                 :            : 
     344                 :          0 :                 case UNIT_TARGET:
     345                 :            : 
     346                 :            :                         /* NB: SysV targets which are provided by a
     347                 :            :                          * service are pulled in by the services, as
     348                 :            :                          * an indication that the generic service is
     349                 :            :                          * now available. This is strictly one-way.
     350                 :            :                          * The targets do NOT pull in SysV services! */
     351                 :            : 
     352                 :          0 :                         r = strv_extend(&s->before, m);
     353         [ #  # ]:          0 :                         if (r < 0)
     354                 :          0 :                                 return log_oom();
     355                 :            : 
     356                 :          0 :                         r = strv_extend(&s->wants, m);
     357         [ #  # ]:          0 :                         if (r < 0)
     358                 :          0 :                                 return log_oom();
     359                 :            : 
     360         [ #  # ]:          0 :                         if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET)) {
     361                 :          0 :                                 r = strv_extend(&s->before, SPECIAL_NETWORK_TARGET);
     362         [ #  # ]:          0 :                                 if (r < 0)
     363                 :          0 :                                         return log_oom();
     364                 :          0 :                                 r = strv_extend(&s->wants, SPECIAL_NETWORK_TARGET);
     365         [ #  # ]:          0 :                                 if (r < 0)
     366                 :          0 :                                         return log_oom();
     367                 :            :                         }
     368                 :            : 
     369                 :          0 :                         break;
     370                 :            : 
     371                 :          0 :                 case _UNIT_TYPE_INVALID:
     372         [ #  # ]:          0 :                         log_warning("Unit name '%s' is invalid", m);
     373                 :          0 :                         break;
     374                 :            : 
     375                 :          0 :                 default:
     376         [ #  # ]:          0 :                         log_warning("Unknown unit type for unit '%s'", m);
     377                 :            :                 }
     378                 :            :         }
     379                 :            : 
     380                 :         88 :         return 0;
     381                 :            : }
     382                 :            : 
     383                 :         92 : static int handle_dependencies(SysvStub *s, unsigned line, const char *full_text, const char *text) {
     384                 :            :         int r;
     385                 :            : 
     386         [ -  + ]:         92 :         assert(s);
     387         [ -  + ]:         92 :         assert(full_text);
     388         [ -  + ]:         92 :         assert(text);
     389                 :            : 
     390                 :        104 :         for (;;) {
     391   [ +  -  +  +  :        364 :                 _cleanup_free_ char *word = NULL, *m = NULL;
             +  -  +  + ]
     392                 :            :                 bool is_before;
     393                 :            : 
     394                 :        196 :                 r = extract_first_word(&text, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX);
     395         [ -  + ]:        196 :                 if (r < 0)
     396         [ #  # ]:          0 :                         return log_error_errno(r, "[%s:%u] Failed to parse word from provides string: %m", s->path, line);
     397         [ +  + ]:        196 :                 if (r == 0)
     398                 :         92 :                         break;
     399                 :            : 
     400                 :        104 :                 r = sysv_translate_facility(s, line, word, &m);
     401         [ +  + ]:        104 :                 if (r <= 0) /* continue on error */
     402                 :         76 :                         continue;
     403                 :            : 
     404                 :         28 :                 is_before = startswith_no_case(full_text, "X-Start-Before:");
     405                 :            : 
     406   [ +  +  +  - ]:         28 :                 if (streq(m, SPECIAL_NETWORK_ONLINE_TARGET) && !is_before) {
     407                 :            :                         /* the network-online target is special, as it needs to be actively pulled in */
     408                 :          4 :                         r = strv_extend(&s->after, m);
     409         [ -  + ]:          4 :                         if (r < 0)
     410                 :          0 :                                 return log_oom();
     411                 :            : 
     412                 :          4 :                         r = strv_extend(&s->wants, m);
     413                 :            :                 } else
     414         [ -  + ]:         24 :                         r = strv_extend(is_before ? &s->before : &s->after, m);
     415         [ -  + ]:         28 :                 if (r < 0)
     416                 :          0 :                         return log_oom();
     417                 :            :         }
     418                 :            : 
     419                 :         92 :         return 0;
     420                 :            : }
     421                 :            : 
     422                 :         96 : static int load_sysv(SysvStub *s) {
     423                 :         96 :         _cleanup_fclose_ FILE *f;
     424                 :         96 :         unsigned line = 0;
     425                 :            :         int r;
     426                 :            :         enum {
     427                 :            :                 NORMAL,
     428                 :            :                 DESCRIPTION,
     429                 :            :                 LSB,
     430                 :            :                 LSB_DESCRIPTION,
     431                 :            :                 USAGE_CONTINUATION
     432                 :         96 :         } state = NORMAL;
     433                 :         96 :         _cleanup_free_ char *short_description = NULL, *long_description = NULL, *chkconfig_description = NULL;
     434                 :            :         char *description;
     435                 :         96 :         bool supports_reload = false;
     436                 :            : 
     437         [ -  + ]:         96 :         assert(s);
     438                 :            : 
     439                 :         96 :         f = fopen(s->path, "re");
     440         [ -  + ]:         96 :         if (!f) {
     441         [ #  # ]:          0 :                 if (errno == ENOENT)
     442                 :          0 :                         return 0;
     443                 :            : 
     444         [ #  # ]:          0 :                 return log_error_errno(errno, "Failed to open %s: %m", s->path);
     445                 :            :         }
     446                 :            : 
     447         [ +  - ]:         96 :         log_debug("Loading SysV script %s", s->path);
     448                 :            : 
     449                 :        988 :         for (;;) {
     450   [ +  -  +  + ]:       1084 :                 _cleanup_free_ char *l = NULL;
     451                 :            :                 char *t;
     452                 :            : 
     453                 :       1084 :                 r = read_line(f, LONG_LINE_MAX, &l);
     454         [ -  + ]:       1084 :                 if (r < 0)
     455         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to read configuration file '%s': %m", s->path);
     456         [ +  + ]:       1084 :                 if (r == 0)
     457                 :         96 :                         break;
     458                 :            : 
     459                 :        988 :                 line++;
     460                 :            : 
     461                 :        988 :                 t = strstrip(l);
     462         [ +  + ]:        988 :                 if (*t != '#') {
     463                 :            :                         /* Try to figure out whether this init script supports
     464                 :            :                          * the reload operation. This heuristic looks for
     465                 :            :                          * "Usage" lines which include the reload option. */
     466   [ +  -  +  - ]:         96 :                         if (state == USAGE_CONTINUATION ||
     467         [ -  + ]:         96 :                             (state == NORMAL && strcasestr(t, "usage"))) {
     468         [ #  # ]:          0 :                                 if (usage_contains_reload(t)) {
     469                 :          0 :                                         supports_reload = true;
     470                 :          0 :                                         state = NORMAL;
     471         [ #  # ]:          0 :                                 } else if (t[strlen(t)-1] == '\\')
     472                 :          0 :                                         state = USAGE_CONTINUATION;
     473                 :            :                                 else
     474                 :          0 :                                         state = NORMAL;
     475                 :            :                         }
     476                 :            : 
     477                 :         96 :                         continue;
     478                 :            :                 }
     479                 :            : 
     480   [ +  +  +  + ]:        892 :                 if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
     481                 :         88 :                         state = LSB;
     482                 :         88 :                         s->has_lsb = true;
     483                 :         88 :                         continue;
     484                 :            :                 }
     485                 :            : 
     486   [ +  +  +  +  :        804 :                 if (IN_SET(state, LSB_DESCRIPTION, LSB) && streq(t, "### END INIT INFO")) {
                   +  + ]
     487                 :         88 :                         state = NORMAL;
     488                 :         88 :                         continue;
     489                 :            :                 }
     490                 :            : 
     491                 :        716 :                 t++;
     492                 :        716 :                 t += strspn(t, WHITESPACE);
     493                 :            : 
     494         [ +  + ]:        716 :                 if (state == NORMAL) {
     495                 :            : 
     496                 :            :                         /* Try to parse Red Hat style description */
     497                 :            : 
     498         [ -  + ]:         96 :                         if (startswith_no_case(t, "description:")) {
     499                 :            : 
     500                 :            :                                 size_t k;
     501                 :            :                                 const char *j;
     502                 :            : 
     503                 :          0 :                                 k = strlen(t);
     504   [ #  #  #  # ]:          0 :                                 if (k > 0 && t[k-1] == '\\') {
     505                 :          0 :                                         state = DESCRIPTION;
     506                 :          0 :                                         t[k-1] = 0;
     507                 :            :                                 }
     508                 :            : 
     509                 :          0 :                                 j = empty_to_null(strstrip(t+12));
     510                 :            : 
     511                 :          0 :                                 r = free_and_strdup(&chkconfig_description, j);
     512         [ #  # ]:          0 :                                 if (r < 0)
     513                 :          0 :                                         return log_oom();
     514                 :            : 
     515         [ -  + ]:         96 :                         } else if (startswith_no_case(t, "pidfile:")) {
     516                 :            :                                 const char *fn;
     517                 :            : 
     518                 :          0 :                                 state = NORMAL;
     519                 :            : 
     520                 :          0 :                                 fn = strstrip(t+8);
     521         [ #  # ]:          0 :                                 if (!path_is_absolute(fn)) {
     522         [ #  # ]:          0 :                                         log_error("[%s:%u] PID file not absolute. Ignoring.", s->path, line);
     523                 :          0 :                                         continue;
     524                 :            :                                 }
     525                 :            : 
     526                 :          0 :                                 r = free_and_strdup(&s->pid_file, fn);
     527         [ #  # ]:          0 :                                 if (r < 0)
     528                 :          0 :                                         return log_oom();
     529                 :            :                         }
     530                 :            : 
     531         [ -  + ]:        620 :                 } else if (state == DESCRIPTION) {
     532                 :            : 
     533                 :            :                         /* Try to parse Red Hat style description
     534                 :            :                          * continuation */
     535                 :            : 
     536                 :            :                         size_t k;
     537                 :            :                         char *j;
     538                 :            : 
     539                 :          0 :                         k = strlen(t);
     540   [ #  #  #  # ]:          0 :                         if (k > 0 && t[k-1] == '\\')
     541                 :          0 :                                 t[k-1] = 0;
     542                 :            :                         else
     543                 :          0 :                                 state = NORMAL;
     544                 :            : 
     545                 :          0 :                         j = strstrip(t);
     546         [ #  # ]:          0 :                         if (!isempty(j)) {
     547                 :          0 :                                 char *d = NULL;
     548                 :            : 
     549         [ #  # ]:          0 :                                 if (chkconfig_description)
     550                 :          0 :                                         d = strjoin(chkconfig_description, " ", j);
     551                 :            :                                 else
     552                 :          0 :                                         d = strdup(j);
     553         [ #  # ]:          0 :                                 if (!d)
     554                 :          0 :                                         return log_oom();
     555                 :            : 
     556                 :          0 :                                 free(chkconfig_description);
     557                 :          0 :                                 chkconfig_description = d;
     558                 :            :                         }
     559                 :            : 
     560   [ +  -  +  - ]:        620 :                 } else if (IN_SET(state, LSB, LSB_DESCRIPTION)) {
     561                 :            : 
     562         [ +  + ]:        620 :                         if (startswith_no_case(t, "Provides:")) {
     563                 :         88 :                                 state = LSB;
     564                 :            : 
     565                 :         88 :                                 r = handle_provides(s, line, t, t + 9);
     566         [ -  + ]:         88 :                                 if (r < 0)
     567                 :          0 :                                         return r;
     568                 :            : 
     569   [ +  +  +  + ]:        976 :                         } else if (startswith_no_case(t, "Required-Start:") ||
     570         [ +  - ]:        884 :                                    startswith_no_case(t, "Should-Start:") ||
     571         [ -  + ]:        880 :                                    startswith_no_case(t, "X-Start-Before:") ||
     572                 :        440 :                                    startswith_no_case(t, "X-Start-After:")) {
     573                 :            : 
     574                 :         92 :                                 state = LSB;
     575                 :            : 
     576                 :         92 :                                 r = handle_dependencies(s, line, t, strchr(t, ':') + 1);
     577         [ -  + ]:         92 :                                 if (r < 0)
     578                 :          0 :                                         return r;
     579                 :            : 
     580         [ +  + ]:        440 :                         } else if (startswith_no_case(t, "Description:")) {
     581                 :            :                                 const char *j;
     582                 :            : 
     583                 :         88 :                                 state = LSB_DESCRIPTION;
     584                 :            : 
     585                 :         88 :                                 j = empty_to_null(strstrip(t+12));
     586                 :            : 
     587                 :         88 :                                 r = free_and_strdup(&long_description, j);
     588         [ -  + ]:         88 :                                 if (r < 0)
     589                 :          0 :                                         return log_oom();
     590                 :            : 
     591         [ +  + ]:        352 :                         } else if (startswith_no_case(t, "Short-Description:")) {
     592                 :            :                                 const char *j;
     593                 :            : 
     594                 :         88 :                                 state = LSB;
     595                 :            : 
     596                 :         88 :                                 j = empty_to_null(strstrip(t+18));
     597                 :            : 
     598                 :         88 :                                 r = free_and_strdup(&short_description, j);
     599         [ -  + ]:         88 :                                 if (r < 0)
     600                 :          0 :                                         return log_oom();
     601                 :            : 
     602         [ -  + ]:        264 :                         } else if (state == LSB_DESCRIPTION) {
     603                 :            : 
     604   [ #  #  #  # ]:          0 :                                 if (startswith(l, "#\t") || startswith(l, "#  ")) {
     605                 :            :                                         const char *j;
     606                 :            : 
     607                 :          0 :                                         j = strstrip(t);
     608         [ #  # ]:          0 :                                         if (!isempty(j)) {
     609                 :          0 :                                                 char *d = NULL;
     610                 :            : 
     611         [ #  # ]:          0 :                                                 if (long_description)
     612                 :          0 :                                                         d = strjoin(long_description, " ", t);
     613                 :            :                                                 else
     614                 :          0 :                                                         d = strdup(j);
     615         [ #  # ]:          0 :                                                 if (!d)
     616                 :          0 :                                                         return log_oom();
     617                 :            : 
     618                 :          0 :                                                 free(long_description);
     619                 :          0 :                                                 long_description = d;
     620                 :            :                                         }
     621                 :            : 
     622                 :            :                                 } else
     623                 :          0 :                                         state = LSB;
     624                 :            :                         }
     625                 :            :                 }
     626                 :            :         }
     627                 :            : 
     628                 :         96 :         s->reload = supports_reload;
     629                 :            : 
     630                 :            :         /* We use the long description only if
     631                 :            :          * no short description is set. */
     632                 :            : 
     633         [ +  + ]:         96 :         if (short_description)
     634                 :         88 :                 description = short_description;
     635         [ -  + ]:          8 :         else if (chkconfig_description)
     636                 :          0 :                 description = chkconfig_description;
     637         [ -  + ]:          8 :         else if (long_description)
     638                 :          0 :                 description = long_description;
     639                 :            :         else
     640                 :          8 :                 description = NULL;
     641                 :            : 
     642         [ +  + ]:         96 :         if (description) {
     643                 :            :                 char *d;
     644                 :            : 
     645         [ +  - ]:         88 :                 d = strjoin(s->has_lsb ? "LSB: " : "SYSV: ", description);
     646         [ -  + ]:         88 :                 if (!d)
     647                 :          0 :                         return log_oom();
     648                 :            : 
     649                 :         88 :                 s->description = d;
     650                 :            :         }
     651                 :            : 
     652                 :         96 :         s->loaded = true;
     653                 :         96 :         return 0;
     654                 :            : }
     655                 :            : 
     656                 :         96 : static int fix_order(SysvStub *s, Hashmap *all_services) {
     657                 :            :         SysvStub *other;
     658                 :            :         Iterator j;
     659                 :            :         int r;
     660                 :            : 
     661         [ -  + ]:         96 :         assert(s);
     662                 :            : 
     663         [ -  + ]:         96 :         if (!s->loaded)
     664                 :          0 :                 return 0;
     665                 :            : 
     666         [ +  + ]:         96 :         if (s->sysv_start_priority < 0)
     667                 :         36 :                 return 0;
     668                 :            : 
     669         [ +  + ]:        196 :         HASHMAP_FOREACH(other, all_services, j) {
     670         [ +  + ]:        136 :                 if (s == other)
     671                 :         60 :                         continue;
     672                 :            : 
     673         [ -  + ]:         76 :                 if (!other->loaded)
     674                 :          0 :                         continue;
     675                 :            : 
     676         [ +  + ]:         76 :                 if (other->sysv_start_priority < 0)
     677                 :          4 :                         continue;
     678                 :            : 
     679                 :            :                 /* If both units have modern headers we don't care
     680                 :            :                  * about the priorities */
     681   [ +  +  +  - ]:         72 :                 if (s->has_lsb && other->has_lsb)
     682                 :         64 :                         continue;
     683                 :            : 
     684         [ +  + ]:          8 :                 if (other->sysv_start_priority < s->sysv_start_priority) {
     685                 :          4 :                         r = strv_extend(&s->after, other->name);
     686         [ -  + ]:          4 :                         if (r < 0)
     687                 :          0 :                                 return log_oom();
     688                 :            : 
     689         [ +  - ]:          4 :                 } else if (other->sysv_start_priority > s->sysv_start_priority) {
     690                 :          4 :                         r = strv_extend(&s->before, other->name);
     691         [ -  + ]:          4 :                         if (r < 0)
     692                 :          0 :                                 return log_oom();
     693                 :            :                 } else
     694                 :          0 :                         continue;
     695                 :            : 
     696                 :            :                 /* FIXME: Maybe we should compare the name here lexicographically? */
     697                 :            :         }
     698                 :            : 
     699                 :         60 :         return 0;
     700                 :            : }
     701                 :            : 
     702                 :        152 : static int acquire_search_path(const char *def, const char *envvar, char ***ret) {
     703                 :        152 :         _cleanup_strv_free_ char **l = NULL;
     704                 :            :         const char *e;
     705                 :            :         int r;
     706                 :            : 
     707         [ -  + ]:        152 :         assert(def);
     708         [ -  + ]:        152 :         assert(envvar);
     709                 :            : 
     710                 :        152 :         e = getenv(envvar);
     711         [ +  - ]:        152 :         if (e) {
     712                 :        152 :                 r = path_split_and_make_absolute(e, &l);
     713         [ -  + ]:        152 :                 if (r < 0)
     714         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to make $%s search path absolute: %m", envvar);
     715                 :            :         }
     716                 :            : 
     717         [ -  + ]:        152 :         if (strv_isempty(l)) {
     718                 :          0 :                 strv_free(l);
     719                 :            : 
     720                 :          0 :                 l = strv_new(def);
     721         [ #  # ]:          0 :                 if (!l)
     722                 :          0 :                         return log_oom();
     723                 :            :         }
     724                 :            : 
     725         [ -  + ]:        152 :         if (!path_strv_resolve_uniq(l, NULL))
     726                 :          0 :                 return log_oom();
     727                 :            : 
     728                 :        152 :         *ret = TAKE_PTR(l);
     729                 :            : 
     730                 :        152 :         return 0;
     731                 :            : }
     732                 :            : 
     733                 :         76 : static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) {
     734                 :         76 :         _cleanup_strv_free_ char **sysvinit_path = NULL;
     735                 :            :         char **path;
     736                 :            :         int r;
     737                 :            : 
     738         [ -  + ]:         76 :         assert(lp);
     739                 :            : 
     740                 :         76 :         r = acquire_search_path(SYSTEM_SYSVINIT_PATH, "SYSTEMD_SYSVINIT_PATH", &sysvinit_path);
     741         [ -  + ]:         76 :         if (r < 0)
     742                 :          0 :                 return r;
     743                 :            : 
     744   [ +  -  +  + ]:        152 :         STRV_FOREACH(path, sysvinit_path) {
     745      [ +  -  - ]:         76 :                 _cleanup_closedir_ DIR *d = NULL;
     746                 :            :                 struct dirent *de;
     747                 :            : 
     748                 :         76 :                 d = opendir(*path);
     749         [ -  + ]:         76 :                 if (!d) {
     750         [ #  # ]:          0 :                         if (errno != ENOENT)
     751         [ #  # ]:          0 :                                 log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path);
     752                 :          0 :                         continue;
     753                 :            :                 }
     754                 :            : 
     755   [ +  +  -  +  :        360 :                 FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) {
             #  #  +  + ]
     756   [ +  +  -  +  :        112 :                         _cleanup_free_ char *fpath = NULL, *name = NULL;
                   +  - ]
     757      [ +  +  - ]:        104 :                         _cleanup_(free_sysvstubp) SysvStub *service = NULL;
     758                 :            :                         struct stat st;
     759                 :            : 
     760         [ -  + ]:        104 :                         if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
     761         [ #  # ]:          0 :                                 log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name);
     762                 :          0 :                                 continue;
     763                 :            :                         }
     764                 :            : 
     765         [ +  + ]:        104 :                         if (!(st.st_mode & S_IXUSR))
     766                 :          4 :                                 continue;
     767                 :            : 
     768         [ -  + ]:        100 :                         if (!S_ISREG(st.st_mode))
     769                 :          0 :                                 continue;
     770                 :            : 
     771                 :        100 :                         name = sysv_translate_name(de->d_name);
     772         [ -  + ]:        100 :                         if (!name)
     773                 :          0 :                                 return log_oom();
     774                 :            : 
     775         [ -  + ]:        100 :                         if (hashmap_contains(all_services, name))
     776                 :          0 :                                 continue;
     777                 :            : 
     778                 :        100 :                         r = unit_file_exists(UNIT_FILE_SYSTEM, lp, name);
     779   [ -  +  #  #  :        100 :                         if (r < 0 && !IN_SET(r, -ELOOP, -ERFKILL, -EADDRNOTAVAIL)) {
                   #  # ]
     780         [ #  # ]:          0 :                                 log_debug_errno(r, "Failed to detect whether %s exists, skipping: %m", name);
     781                 :          0 :                                 continue;
     782         [ +  + ]:        100 :                         } else if (r != 0) {
     783         [ +  - ]:          4 :                                 log_debug("Native unit for %s already exists, skipping.", name);
     784                 :          4 :                                 continue;
     785                 :            :                         }
     786                 :            : 
     787                 :         96 :                         fpath = path_join(*path, de->d_name);
     788         [ -  + ]:         96 :                         if (!fpath)
     789                 :          0 :                                 return log_oom();
     790                 :            : 
     791                 :         96 :                         service = new0(SysvStub, 1);
     792         [ -  + ]:         96 :                         if (!service)
     793                 :          0 :                                 return log_oom();
     794                 :            : 
     795                 :         96 :                         service->sysv_start_priority = -1;
     796                 :         96 :                         service->name = TAKE_PTR(name);
     797                 :         96 :                         service->path = TAKE_PTR(fpath);
     798                 :            : 
     799                 :         96 :                         r = hashmap_put(all_services, service->name, service);
     800         [ -  + ]:         96 :                         if (r < 0)
     801                 :          0 :                                 return log_oom();
     802                 :            : 
     803                 :         96 :                         service = NULL;
     804                 :            :                 }
     805                 :            :         }
     806                 :            : 
     807                 :         76 :         return 0;
     808                 :            : }
     809                 :            : 
     810                 :         76 : static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_services) {
     811                 :         76 :         Set *runlevel_services[ELEMENTSOF(rcnd_table)] = {};
     812                 :         76 :         _cleanup_strv_free_ char **sysvrcnd_path = NULL;
     813                 :            :         SysvStub *service;
     814                 :            :         unsigned i;
     815                 :            :         Iterator j;
     816                 :            :         char **p;
     817                 :            :         int r;
     818                 :            : 
     819         [ -  + ]:         76 :         assert(lp);
     820                 :            : 
     821                 :         76 :         r = acquire_search_path(SYSTEM_SYSVRCND_PATH, "SYSTEMD_SYSVRCND_PATH", &sysvrcnd_path);
     822         [ -  + ]:         76 :         if (r < 0)
     823                 :          0 :                 return r;
     824                 :            : 
     825   [ +  -  +  + ]:        152 :         STRV_FOREACH(p, sysvrcnd_path) {
     826         [ +  + ]:        456 :                 for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
     827                 :            : 
     828      [ +  -  + ]:        380 :                         _cleanup_closedir_ DIR *d = NULL;
     829      [ +  -  + ]:        380 :                         _cleanup_free_ char *path = NULL;
     830                 :            :                         struct dirent *de;
     831                 :            : 
     832                 :        380 :                         path = path_join(*p, rcnd_table[i].path);
     833         [ -  + ]:        380 :                         if (!path) {
     834                 :          0 :                                 r = log_oom();
     835                 :          0 :                                 goto finish;
     836                 :            :                         }
     837                 :            : 
     838                 :        380 :                         d = opendir(path);
     839         [ +  + ]:        380 :                         if (!d) {
     840         [ -  + ]:        204 :                                 if (errno != ENOENT)
     841         [ #  # ]:          0 :                                         log_warning_errno(errno, "Opening %s failed, ignoring: %m", path);
     842                 :            : 
     843                 :        204 :                                 continue;
     844                 :            :                         }
     845                 :            : 
     846   [ +  +  -  +  :        808 :                         FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) {
             #  #  +  + ]
     847   [ +  +  -  +  :        352 :                                 _cleanup_free_ char *name = NULL, *fpath = NULL;
                   +  - ]
     848                 :            :                                 int a, b;
     849                 :            : 
     850         [ +  + ]:        280 :                                 if (de->d_name[0] != 'S')
     851                 :         56 :                                         continue;
     852                 :            : 
     853         [ -  + ]:        224 :                                 if (strlen(de->d_name) < 4)
     854                 :          0 :                                         continue;
     855                 :            : 
     856                 :        224 :                                 a = undecchar(de->d_name[1]);
     857                 :        224 :                                 b = undecchar(de->d_name[2]);
     858                 :            : 
     859   [ +  -  -  + ]:        224 :                                 if (a < 0 || b < 0)
     860                 :          0 :                                         continue;
     861                 :            : 
     862                 :        224 :                                 fpath = path_join(*p, de->d_name);
     863         [ -  + ]:        224 :                                 if (!fpath) {
     864                 :          0 :                                         r = log_oom();
     865                 :          0 :                                         goto finish;
     866                 :            :                                 }
     867                 :            : 
     868                 :        224 :                                 name = sysv_translate_name(de->d_name + 3);
     869         [ -  + ]:        224 :                                 if (!name) {
     870                 :          0 :                                         r = log_oom();
     871                 :          0 :                                         goto finish;
     872                 :            :                                 }
     873                 :            : 
     874                 :        224 :                                 service = hashmap_get(all_services, name);
     875         [ +  + ]:        224 :                                 if (!service) {
     876         [ +  - ]:         16 :                                         log_debug("Ignoring %s symlink in %s, not generating %s.", de->d_name, rcnd_table[i].path, name);
     877                 :         16 :                                         continue;
     878                 :            :                                 }
     879                 :            : 
     880                 :        208 :                                 service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority);
     881                 :            : 
     882                 :        208 :                                 r = set_ensure_allocated(&runlevel_services[i], NULL);
     883         [ -  + ]:        208 :                                 if (r < 0) {
     884                 :          0 :                                         log_oom();
     885                 :          0 :                                         goto finish;
     886                 :            :                                 }
     887                 :            : 
     888                 :        208 :                                 r = set_put(runlevel_services[i], service);
     889         [ -  + ]:        208 :                                 if (r < 0) {
     890                 :          0 :                                         log_oom();
     891                 :          0 :                                         goto finish;
     892                 :            :                                 }
     893                 :            :                         }
     894                 :            :                 }
     895                 :            :         }
     896                 :            : 
     897         [ +  + ]:        456 :         for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
     898         [ +  + ]:        588 :                 SET_FOREACH(service, runlevel_services[i], j) {
     899                 :        208 :                         r = strv_extend(&service->before, rcnd_table[i].target);
     900         [ -  + ]:        208 :                         if (r < 0) {
     901                 :          0 :                                 log_oom();
     902                 :          0 :                                 goto finish;
     903                 :            :                         }
     904                 :        208 :                         r = strv_extend(&service->wanted_by, rcnd_table[i].target);
     905         [ -  + ]:        208 :                         if (r < 0) {
     906                 :          0 :                                 log_oom();
     907                 :          0 :                                 goto finish;
     908                 :            :                         }
     909                 :            :                 }
     910                 :            : 
     911                 :         76 :         r = 0;
     912                 :            : 
     913                 :         76 : finish:
     914         [ +  + ]:        456 :         for (i = 0; i < ELEMENTSOF(rcnd_table); i++)
     915                 :        380 :                 set_free(runlevel_services[i]);
     916                 :            : 
     917                 :         76 :         return r;
     918                 :            : }
     919                 :            : 
     920                 :         76 : static int run(const char *dest, const char *dest_early, const char *dest_late) {
     921                 :         76 :         _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL;
     922                 :         76 :         _cleanup_(lookup_paths_free) LookupPaths lp = {};
     923                 :            :         SysvStub *service;
     924                 :            :         Iterator j;
     925                 :            :         int r;
     926                 :            : 
     927         [ -  + ]:         76 :         assert_se(arg_dest = dest_late);
     928                 :            : 
     929                 :         76 :         r = lookup_paths_init(&lp, UNIT_FILE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL);
     930         [ -  + ]:         76 :         if (r < 0)
     931         [ #  # ]:          0 :                 return log_error_errno(r, "Failed to find lookup paths: %m");
     932                 :            : 
     933                 :         76 :         all_services = hashmap_new(&string_hash_ops);
     934         [ -  + ]:         76 :         if (!all_services)
     935                 :          0 :                 return log_oom();
     936                 :            : 
     937                 :         76 :         r = enumerate_sysv(&lp, all_services);
     938         [ -  + ]:         76 :         if (r < 0)
     939                 :          0 :                 return r;
     940                 :            : 
     941                 :         76 :         r = set_dependencies_from_rcnd(&lp, all_services);
     942         [ -  + ]:         76 :         if (r < 0)
     943                 :          0 :                 return r;
     944                 :            : 
     945         [ +  + ]:        172 :         HASHMAP_FOREACH(service, all_services, j)
     946                 :         96 :                 (void) load_sysv(service);
     947                 :            : 
     948         [ +  + ]:        172 :         HASHMAP_FOREACH(service, all_services, j) {
     949                 :         96 :                 (void) fix_order(service, all_services);
     950                 :         96 :                 (void) generate_unit_file(service);
     951                 :            :         }
     952                 :            : 
     953                 :         76 :         return 0;
     954                 :            : }
     955                 :            : 
     956   [ +  -  -  +  :         76 : DEFINE_MAIN_GENERATOR_FUNCTION(run);
          #  #  +  -  +  
                -  +  - ]

Generated by: LCOV version 1.14