root/usr/src/cmd/cmd-inet/usr.sbin/inetconv/inetconv.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * inetconv - convert inetd.conf entries into smf(7) service manifests,
 *            import them into smf(7) repository
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <libintl.h>
#include <libscf.h>
#include <inetsvc.h>
#include <rpc/nettype.h>

/* exit codes */
#define EXIT_SUCCESS    0       /* succeeded */
#define EXIT_USAGE      1       /* bad options */
#define EXIT_ERROR_CONV 2       /* error(s) coverting inetd.conf entries */
#define EXIT_ERROR_IMP  3       /* error(s) importing manifests */
#define EXIT_ERROR_SYS  4       /* system error */
#define EXIT_ERROR_ENBL 5       /* error(s) enabling services */

#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN             "SUNW_OST_OSCMD"
#endif

#define MAIN_CONFIG             "/etc/inet/inetd.conf"
#define ALT_CONFIG              "/etc/inetd.conf"

#define MANIFEST_DIR            "/lib/svc/manifest/network"
#define MANIFEST_RPC_DIR        MANIFEST_DIR  "/rpc"
#define SVCCFG_PATH             "/usr/sbin/svccfg"

#define RPCBIND_FMRI            "svc:/network/rpc/bind"

/* maximum allowed length of an inetd.conf format line */
#define MAX_SRC_LINELEN         32768

/* Version of inetconv, used as a marker in services we generate */
#define INETCONV_VERSION        1

struct inetconfent {
        /* fields as read from inetd.conf format line */
        char *service;
        char *endpoint;
        char *protocol;
        char *wait_status;
        char *username;
        char *server_program;
        char *server_args;
        /* information derived from above fields */
        boolean_t wait;
        boolean_t isrpc;
        int rpc_low_version;
        int rpc_high_version;
        char *rpc_prog;
        char *groupname;
        char *exec;
        char *arg0;
};

struct fileinfo {
        FILE *fp;
        char *filename;
        int lineno;
        int failcnt;
};

static char *progname;

static boolean_t import = B_TRUE;

/* start of manifest XML template strings */
static const char xml_header[] =
"<?xml version='1.0'?>\n"
"<!DOCTYPE service_bundle SYSTEM "
"'/usr/share/lib/xml/dtd/service_bundle.dtd.1'>\n";

static const char xml_comment[] =
"<!--\n"
"    Service manifest for the %s service.\n"
"\n"
"    Generated by inetconv(8) from inetd.conf(5).\n"
"-->\n\n";

static const char xml_service_bundle[] =
"<service_bundle type='manifest' name='inetconv:%s'>\n\n";

static const char xml_service_name[] =
"<service\n"
"       name='network/%s'\n"
"       type='service'\n"
"       version='1'>\n\n";

static const char xml_dependency[] =
"       <dependency\n"
"               name='%s'\n"
"               grouping='require_all'\n"
"               restart_on='restart'\n"
"               type='service'>\n"
"               <service_fmri value='%s' />\n"
"       </dependency>\n\n";

static const char xml_instance[] =
"       <create_default_instance enabled='true'/>\n\n";

static const char xml_restarter[] =
"       <restarter>\n"
"               <service_fmri value='%s' />\n"
"       </restarter>\n\n";

static const char xml_exec_method_start[] =
"       <!--\n"
"           Set a timeout of 0 to signify to inetd that we don't want to\n"
"           timeout this service, since the forked process is the one that\n"
"           does the service's work. This is the case for most/all legacy\n"
"           inetd services; for services written to take advantage of SMF\n"
"           capabilities, the start method should fork off a process to\n"
"           handle the request and return a success code.\n"
"       -->\n"
"       <exec_method\n"
"               type='method'\n"
"               name='%s'\n"
"               %s='%s'\n"
"               timeout_seconds='0'>\n"
"               <method_context>\n"
"                       <method_credential %s='%s' group='%s' />\n"
"               </method_context>\n";

static const char xml_arg0[] =
"               <propval name='%s' type='astring'\n"
"                   value='%s' />\n";

static const char xml_exec_method_end[] =
"       </exec_method>\n\n";

static const char xml_exec_method_disable[] =
"       <!--\n"
"           Use inetd's built-in kill support to disable services.\n"
"       -->\n"
"       <exec_method\n"
"               type='method'\n"
"               name='%s'\n"
"               %s=':kill'\n"
"               timeout_seconds='0'>\n";

static const char xml_exec_method_offline[] =
"       <!--\n"
"           Use inetd's built-in process kill support to offline wait type\n"
"           services.\n"
"       -->\n"
"       <exec_method\n"
"               type='method'\n"
"               name='%s'\n"
"               %s=':kill_process'\n"
"               timeout_seconds='0'>\n";

static const char xml_inetconv_group_start[] =
"       <!--\n"
"           This property group is used to record information about\n"
"           how this manifest was created.  It is an implementation\n"
"           detail which should not be modified or deleted.\n"
"       -->\n"
"       <property_group name='%s' type='framework'>\n"
"               <propval name='%s' type='boolean' value='%s' />\n"
"               <propval name='%s' type='integer' value='%d' />\n"
"               <propval name='%s' type='astring' value=\n"
"'%s %s %s %s %s %s%s%s'\n"
"               />\n";

static const char xml_property_group_start[] =
"       <property_group name='%s' type='framework'>\n"
"               <propval name='%s' type='astring' value='%s' />\n"
"               <propval name='%s' type='astring' value='%s' />\n"
"               <propval name='%s' type='astring' value='%s' />\n"
"               <propval name='%s' type='boolean' value='%s' />\n"
"               <propval name='%s' type='boolean' value='%s' />\n";

static const char xml_property_group_rpc[] =
"               <propval name='%s' type='integer' value='%d' />\n"
"               <propval name='%s' type='integer' value='%d' />"
"\n";

static const char xml_property_group_end[] =
"       </property_group>\n\n";

static const char xml_stability[] =
"       <stability value='External' />\n\n";

static const char xml_template[] =
"       <template>\n"
"               <common_name>\n"
"                       <loctext xml:lang='C'>\n"
"%s\n"
"                       </loctext>\n"
"               </common_name>\n"
"       </template>\n";

static const char xml_footer[] =
"</service>\n"
"\n"
"</service_bundle>\n";
/* end of manifest XML template strings */

static void *
safe_malloc(size_t size)
{
        void *cp;

        if ((cp = malloc(size)) == NULL) {
                (void) fprintf(stderr, gettext("%s: malloc failed: %s\n"),
                    progname, strerror(errno));
                exit(EXIT_ERROR_SYS);
        }
        return (cp);
}

static char *
safe_strdup(char *s)
{
        char *cp;

        if ((cp = strdup(s)) == NULL) {
                (void) fprintf(stderr, gettext("%s: strdup failed: %s\n"),
                    progname, strerror(errno));
                exit(EXIT_ERROR_SYS);
        }
        return (cp);
}

static char *
propertyname(char *name, char *prefix)
{
        static char *buf;
        size_t len;
        int c;
        char *cp;

        /* free any memory allocated by a previous call */
        free(buf);

        len = strlen(name) + strlen(prefix) + 1;
        buf = safe_malloc(len);
        buf[0] = '\0';

        /*
         * Property names must match the regular expression:
         * ([A-Za-z][_A-Za-z0-9.-]*,)?[A-Za-z][_A-Za-z0-9-]*
         */

        /*
         * Make sure the first character is alphabetic, if not insert prefix.
         * Can't use isalpha() here as it's locale dependent but the property
         * name regular expression isn't.
         */
        c = name[0];
        if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
                (void) strlcat(buf, prefix, len);
        }
        (void) strlcat(buf, name, len);

        /* convert any disallowed characters into '_' */
        for (cp = buf; *cp != '\0'; cp++) {
                if ((*cp < 'A' || *cp > 'Z') && (*cp < 'a' || *cp > 'z') &&
                    (*cp < '0' || *cp > '9') && (*cp != '.') && (*cp != '-'))
                        *cp = '_';
        }
        return (buf);
}

static char *
servicename(struct inetconfent *iconf)
{
        static char *buf;
        size_t len;
        char *cp, *proto;

        /* free any memory allocated by a previous call */
        free(buf);

        len = strlen(iconf->service) + strlen(iconf->protocol) +
            sizeof ("rpc-/visible");
        buf = safe_malloc(len);

        /*
         * Combine the service and protocol fields to produce a unique
         * manifest service name. The syntax of a service name is:
         * prop(/prop)*
         */
        (void) strlcpy(buf, propertyname(iconf->service,
            iconf->isrpc ? "rpc-": "s-"), len);
        (void) strlcat(buf, "/", len);

        proto = iconf->protocol;
        if (iconf->isrpc && (strcmp(iconf->protocol, "rpc/*") == 0))
                proto = "rpc/visible";

        /*
         * SMF service names may not contain '.', but IANA services do
         * allow its use, and property names can contain '.' as returned
         * by propertyname().  So if the resultant SMF service name
         * would contain a '.' we fix it here.
         */
        for (cp = buf; *cp != '\0'; cp++) {
                if (*cp == '.')
                        *cp = '_';
        }
        (void) strlcat(buf, propertyname(proto, "p-"), len);
        return (buf);
}

static boolean_t
is_v6only(char *protocol)
{
        /* returns true if protocol is an IPv6 only protocol */
        if ((strcmp(protocol, SOCKET_PROTO_TCP6_ONLY) == 0) ||
            (strcmp(protocol, SOCKET_PROTO_UDP6_ONLY) == 0))
                return (B_TRUE);
        return (B_FALSE);
}

static char *
invalid_props(inetd_prop_t *p)
{
        static char
            buf[sizeof (" service-name endpoint-type protocol wait-status")];

        buf[0] = '\0';
        if ((p[PT_SVC_NAME_INDEX].ip_error == IVE_INVALID) ||
            (p[PT_SVC_NAME_INDEX].ip_error == IVE_UNSET) ||
            (p[PT_RPC_LW_VER_INDEX].ip_error == IVE_INVALID) ||
            (p[PT_RPC_HI_VER_INDEX].ip_error == IVE_INVALID))
                (void) strlcat(buf, " service-name", sizeof (buf));
        if ((p[PT_SOCK_TYPE_INDEX].ip_error == IVE_INVALID) ||
            (p[PT_SOCK_TYPE_INDEX].ip_error == IVE_UNSET))
                (void) strlcat(buf, " endpoint-type", sizeof (buf));
        if ((p[PT_PROTO_INDEX].ip_error == IVE_INVALID) ||
            (p[PT_PROTO_INDEX].ip_error == IVE_UNSET) ||
            (p[PT_ISRPC_INDEX].ip_error == IVE_INVALID))
                (void) strlcat(buf, " protocol", sizeof (buf));
        if (p[PT_ISWAIT_INDEX].ip_error == IVE_INVALID)
                (void) strlcat(buf, " wait-status", sizeof (buf));
        return (buf);
}

static boolean_t
valid_basic_properties(struct inetconfent *iconf, struct fileinfo *finfo)
{
        size_t prop_size;
        inetd_prop_t *prop, *inetd_properties;
        boolean_t valid = B_TRUE;
        char *proto = iconf->protocol;
        char *svc_name = iconf->service;

        inetd_properties = get_prop_table(&prop_size);
        prop = safe_malloc(prop_size * sizeof (inetd_prop_t));
        (void) memcpy(prop, inetd_properties,
            prop_size * sizeof (inetd_prop_t));

        put_prop_value_boolean(prop, PR_ISRPC_NAME, iconf->isrpc);
        put_prop_value_boolean(prop, PR_ISWAIT_NAME, iconf->wait);
        if (iconf->isrpc) {
                put_prop_value_int(prop, PR_RPC_LW_VER_NAME,
                    iconf->rpc_low_version);
                put_prop_value_int(prop, PR_RPC_HI_VER_NAME,
                    iconf->rpc_high_version);
                svc_name = iconf->rpc_prog;
                proto += 4;     /* skip 'rpc/' */
        }

        if (!put_prop_value_string(prop, PR_SOCK_TYPE_NAME, iconf->endpoint) ||
            !put_prop_value_string(prop, PR_SVC_NAME_NAME, svc_name)) {
                valid = B_FALSE;

                if (errno == ENOMEM) {
                        (void) fprintf(stderr,
                            gettext("%s: failed to allocate memory: %s\n"),
                            progname, strerror(errno));
                        exit(EXIT_ERROR_SYS);
                }
        }

        put_prop_value_string_list(prop, PR_PROTO_NAME, get_protos(proto));

        if (!valid_props(prop, NULL, NULL, NULL, NULL) || !valid) {
                valid = B_FALSE;
                (void) fprintf(stderr, gettext("%s: Error %s line %d "
                    "invalid or inconsistent fields:%s\n"), progname,
                    finfo->filename, finfo->lineno,
                    invalid_props(prop));
        }

        free_instance_props(prop);
        return (valid);
}

static boolean_t
valid_inetconfent(struct inetconfent *iconf, struct fileinfo *finfo)
{
        boolean_t valid = B_TRUE;
        size_t len;
        char *cp, *endp;
        struct passwd *pwd;
        struct group *grp;
        struct stat statb;
        char *proto = iconf->protocol;

        iconf->isrpc = B_FALSE;
        if (strncmp(iconf->protocol, "rpc/", 4) == 0) {
                iconf->isrpc = B_TRUE;
                iconf->rpc_prog = safe_strdup(iconf->service);

                /* set RPC version numbers */
                iconf->rpc_low_version = 1;
                iconf->rpc_high_version = 1;
                if ((cp = strrchr(iconf->rpc_prog, '/')) != NULL) {
                        *cp = '\0';
                        if (*++cp != '\0') {
                                errno = 0;
                                iconf->rpc_low_version = strtol(cp, &endp, 10);
                                if (errno != 0)
                                        goto vererr;
                                cp = endp;
                                if (*cp == '-') {
                                        if (*++cp == '\0')
                                                goto vererr;
                                        errno = 0;
                                        iconf->rpc_high_version = strtol(cp,
                                            &endp, 10);
                                        if ((errno != 0) || (*endp != '\0'))
                                                goto vererr;
                                } else if (*cp == '\0') {
                                        iconf->rpc_high_version =
                                            iconf->rpc_low_version;
                                } else {
vererr:
                                        (void) fprintf(stderr, gettext(
                                            "%s: Error %s line %d invalid RPC "
                                            "version in service: %s\n"),
                                            progname, finfo->filename,
                                            finfo->lineno, iconf->service);
                                        valid = B_FALSE;
                                }
                        }
                }
                proto += 4;     /* skip 'rpc/' */
        }
        /* tcp6only and udp6only are not valid in inetd.conf */
        if (is_v6only(proto)) {
                (void) fprintf(stderr, gettext("%s: Error %s line %d "
                    "invalid protocol: %s\n"), progname,
                    finfo->filename, finfo->lineno, proto);
                valid = B_FALSE;
        }

        if (strcmp(iconf->wait_status, "wait") == 0) {
                iconf->wait = B_TRUE;
        } else if (strcmp(iconf->wait_status, "nowait") == 0) {
                iconf->wait = B_FALSE;
        } else {
                (void) fprintf(stderr,
                    gettext("%s: Error %s line %d invalid wait-status: %s\n"),
                    progname, finfo->filename, finfo->lineno,
                    iconf->wait_status);
                valid = B_FALSE;
        }

        /* look up the username to set the groupname */
        if ((pwd = getpwnam(iconf->username)) == NULL) {
                (void) fprintf(stderr,
                    gettext("%s: Error %s line %d unknown user: %s\n"),
                    progname, finfo->filename, finfo->lineno,
                    iconf->username);
                valid = B_FALSE;
        } else {
                if ((grp = getgrgid(pwd->pw_gid)) != NULL) {
                        iconf->groupname = safe_strdup(grp->gr_name);
                } else {
                        /* use the group ID if no groupname */
                        char s[1];

                        len = snprintf(s, 1, "%d", pwd->pw_gid) + 1;
                        iconf->groupname = safe_malloc(len);
                        (void) snprintf(iconf->groupname, len, "%d",
                            pwd->pw_gid);
                }
        }

        /* check for internal services */
        if (strcmp(iconf->server_program, "internal") == 0) {
                valid = B_FALSE;
                if ((strcmp(iconf->service, "echo") == 0) ||
                    (strcmp(iconf->service, "discard") == 0) ||
                    (strcmp(iconf->service, "time") == 0) ||
                    (strcmp(iconf->service, "daytime") == 0) ||
                    (strcmp(iconf->service, "chargen") == 0)) {
                        (void) fprintf(stderr, gettext(
                            "%s: Error %s line %d the SUNWcnsr and SUNWcnsu"
                            " packages contain the internal services\n"),
                            progname, finfo->filename, finfo->lineno);
                } else {
                        (void) fprintf(stderr, gettext("%s: Error %s line %d "
                            "unknown internal service: %s\n"), progname,
                            finfo->filename, finfo->lineno, iconf->service);
                }
        } else if ((stat(iconf->server_program, &statb) == -1) &&
            (errno == ENOENT)) {
                (void) fprintf(stderr, gettext(
                    "%s: Error %s line %d server-program not found: %s\n"),
                    progname, finfo->filename, finfo->lineno,
                    iconf->server_program);
                valid = B_FALSE;
        }

        return (valid && valid_basic_properties(iconf, finfo));
}

static void
free_inetconfent(struct inetconfent *iconf)
{
        if (iconf == NULL)
                return;

        free(iconf->service);
        free(iconf->endpoint);
        free(iconf->protocol);
        free(iconf->wait_status);
        free(iconf->username);
        free(iconf->server_program);
        free(iconf->server_args);
        free(iconf->rpc_prog);
        free(iconf->groupname);
        free(iconf->exec);
        free(iconf->arg0);

        free(iconf);
}

static struct inetconfent *
line_to_inetconfent(char *line)
{
        char *cp;
        struct inetconfent *iconf;

        iconf = safe_malloc(sizeof (struct inetconfent));
        (void) memset(iconf, 0, sizeof (struct inetconfent));

        if ((cp = strtok(line, " \t\n")) == NULL)
                goto fail;
        iconf->service = safe_strdup(cp);

        if ((cp = strtok(NULL, " \t\n")) == NULL)
                goto fail;
        iconf->endpoint = safe_strdup(cp);

        if ((cp = strtok(NULL, " \t\n")) == NULL)
                goto fail;
        iconf->protocol = safe_strdup(cp);

        if ((cp = strtok(NULL, " \t\n")) == NULL)
                goto fail;
        iconf->wait_status = safe_strdup(cp);

        if ((cp = strtok(NULL, " \t\n")) == NULL)
                goto fail;
        iconf->username = safe_strdup(cp);

        if ((cp = strtok(NULL, " \t\n")) == NULL)
                goto fail;
        iconf->server_program = safe_strdup(cp);

        /* last field is optional */
        if ((cp = strtok(NULL, "\n")) != NULL)
                iconf->server_args = safe_strdup(cp);

        /* Combine args and server name to construct exec and args fields */
        if (iconf->server_args == NULL) {
                iconf->exec = safe_strdup(iconf->server_program);
        } else {
                int len;
                char *args, *endp;

                len = strlen(iconf->server_program) +
                    strlen(iconf->server_args) + 1;
                iconf->exec = safe_malloc(len);
                (void) strlcpy(iconf->exec, iconf->server_program, len);

                args = safe_strdup(iconf->server_args);
                if ((cp = strtok(args, " \t")) != NULL) {
                        if ((endp = strrchr(iconf->exec, '/')) == NULL)
                                endp = iconf->exec;
                        else
                                endp++;
                        /* only set arg0 property value if needed */
                        if (strcmp(endp, cp) != 0)
                                iconf->arg0 = safe_strdup(cp);
                        while ((cp = strtok(NULL, " \t")) != NULL) {
                                (void) strlcat(iconf->exec, " ", len);
                                (void) strlcat(iconf->exec, cp, len);
                        }
                }
                free(args);
        }

        return (iconf);
fail:
        free_inetconfent(iconf);
        return (NULL);
}

static void
skipline(FILE *fp)
{
        int c;

        /* skip remainder of a line */
        while (((c = getc(fp)) != EOF) && (c != '\n'))
                ;
}

static struct inetconfent *
fgetinetconfent(struct fileinfo *finfo, boolean_t validate)
{
        char *cp;
        struct inetconfent *iconf;
        char line[MAX_SRC_LINELEN];

        while (fgets(line, sizeof (line), finfo->fp) != NULL) {
                finfo->lineno++;

                /* skip empty or commented out lines */
                if (*line == '\n')
                        continue;
                if (*line == '#') {
                        if (line[strlen(line) - 1] != '\n')
                                skipline(finfo->fp);
                        continue;
                }
                /* check for lines which are too long */
                if (line[strlen(line) - 1] != '\n') {
                        (void) fprintf(stderr,
                            gettext("%s: Error %s line %d too long, skipped\n"),
                            progname, finfo->filename, finfo->lineno);
                        skipline(finfo->fp);
                        finfo->failcnt++;
                        continue;
                }
                /* remove in line comments and newline character */
                if ((cp = strchr(line, '#')) == NULL)
                        cp = strchr(line, '\n');
                if (cp)
                        *cp = '\0';

                if ((iconf = line_to_inetconfent(line)) == NULL) {
                        (void) fprintf(stderr, gettext(
                            "%s: Error %s line %d too few fields, skipped\n"),
                            progname, finfo->filename, finfo->lineno);
                        finfo->failcnt++;
                        continue;
                }

                if (!validate || valid_inetconfent(iconf, finfo))
                        return (iconf);

                finfo->failcnt++;
                free_inetconfent(iconf);
        }
        return (NULL);
}

static char *
boolstr(boolean_t val)
{
        if (val)
                return ("true");
        return ("false");
}

static int
print_manifest(FILE *f, char *filename, struct inetconfent *iconf)
{
        if (fprintf(f, xml_header) < 0)
                goto print_err;

        if (fprintf(f, xml_comment,
            iconf->isrpc ? iconf->rpc_prog : iconf->service) < 0)
                goto print_err;

        if (fprintf(f, xml_service_bundle, iconf->service) < 0)
                goto print_err;
        if (fprintf(f, xml_service_name, servicename(iconf)) < 0)
                goto print_err;
        if (fprintf(f, xml_instance) < 0)
                goto print_err;
        if (fprintf(f, xml_restarter, INETD_INSTANCE_FMRI) < 0)
                goto print_err;
        if (iconf->isrpc) {
                if (fprintf(f, xml_dependency, "rpcbind", RPCBIND_FMRI) < 0)
                        goto print_err;
        }

        if (fprintf(f, xml_exec_method_start, START_METHOD_NAME, PR_EXEC_NAME,
            iconf->exec, PR_USER_NAME, iconf->username, iconf->groupname) < 0)
                goto print_err;
        if (iconf->arg0 != NULL) {
                if (fprintf(f, xml_arg0, PR_ARG0_NAME, iconf->arg0) < 0)
                        goto print_err;
        }
        if (fprintf(f, xml_exec_method_end) < 0)
                goto print_err;

        if (fprintf(f, xml_exec_method_disable, DISABLE_METHOD_NAME,
            PR_EXEC_NAME) < 0)
                goto print_err;
        if (fprintf(f, xml_exec_method_end) < 0)
                goto print_err;

        if (iconf->wait) {
                if (fprintf(f, xml_exec_method_offline, OFFLINE_METHOD_NAME,
                    PR_EXEC_NAME) < 0)
                        goto print_err;
                if (fprintf(f, xml_exec_method_end) < 0)
                        goto print_err;
        }

        if (fprintf(f, xml_inetconv_group_start, PG_NAME_INETCONV,
            PR_AUTO_CONVERTED_NAME, boolstr(B_TRUE),
            PR_VERSION_NAME, INETCONV_VERSION,
            PR_SOURCE_LINE_NAME, iconf->service,
            iconf->endpoint, iconf->protocol, iconf->wait_status,
            iconf->username, iconf->server_program,
            iconf->server_args == NULL ? "" : " ",
            iconf->server_args == NULL ? "" : iconf->server_args) < 0)
                goto print_err;
        if (fprintf(f, xml_property_group_end) < 0)
                goto print_err;

        if (fprintf(f, xml_property_group_start, PG_NAME_SERVICE_CONFIG,
            PR_SVC_NAME_NAME, iconf->isrpc ? iconf->rpc_prog : iconf->service,
            PR_SOCK_TYPE_NAME, iconf->endpoint,
            PR_PROTO_NAME, iconf->isrpc ? iconf->protocol + 4 :
            iconf->protocol,
            PR_ISWAIT_NAME, boolstr(iconf->wait),
            PR_ISRPC_NAME, boolstr(iconf->isrpc)) < 0)
                goto print_err;
        if (iconf->isrpc) {
                if (fprintf(f, xml_property_group_rpc,
                    PR_RPC_LW_VER_NAME, iconf->rpc_low_version,
                    PR_RPC_HI_VER_NAME, iconf->rpc_high_version) < 0)
                        goto print_err;
        }
        if (fprintf(f, xml_property_group_end) < 0)
                goto print_err;

        if (fprintf(f, xml_stability) < 0)
                goto print_err;
        if (fprintf(f, xml_template,
            iconf->isrpc ? iconf->rpc_prog : iconf->service) < 0)
                goto print_err;
        if (fprintf(f, xml_footer) < 0)
                goto print_err;

        (void) printf("%s -> %s\n", iconf->service, filename);
        return (0);

print_err:
        (void) fprintf(stderr, gettext("%s: Error writing manifest %s: %s\n"),
            progname, filename, strerror(errno));
        return (-1);
}

static struct fileinfo *
open_srcfile(char *filename)
{
        struct fileinfo *finfo = NULL;
        FILE *fp;

        if (filename != NULL) {
                if ((fp = fopen(filename, "r")) == NULL) {
                        (void) fprintf(stderr,
                            gettext("%s: Error opening %s: %s\n"),
                            progname, filename, strerror(errno));
                }
        } else {
                /*
                 * If no source file specified, do the same as inetd and first
                 * try /etc/inet/inetd.conf, followed by /etc/inetd.conf.
                 */
                filename = MAIN_CONFIG;
                if ((fp = fopen(filename, "r")) == NULL) {
                        (void) fprintf(stderr,
                            gettext("%s: Error opening %s: %s\n"),
                            progname, filename, strerror(errno));
                        filename = ALT_CONFIG;
                        if ((fp = fopen(filename, "r")) == NULL) {
                                (void) fprintf(stderr, gettext(
                                    "%s: Error opening %s: %s\n"), progname,
                                    filename, strerror(errno));
                        }
                }
        }
        if (fp != NULL) {
                finfo = safe_malloc(sizeof (struct fileinfo));
                finfo->fp = fp;
                finfo->filename = filename;
                finfo->lineno = 0;
                finfo->failcnt = 0;
                (void) fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
        }
        return (finfo);
}

/*
 * Opens manifest output file.  Returns 0 on success, -1 if the file
 * exists, -2 on other errors.
 */
static int
open_dstfile(
    char *destdir,
    boolean_t overwrite,
    struct inetconfent *iconf,
    struct fileinfo **finfo)
{
        int fd;
        size_t len;
        char *dstfile, *cp, *proto;
        FILE *fp;

        /* if no destdir specified, use appropriate default */
        if (destdir == NULL) {
                if (iconf->isrpc)
                        destdir = MANIFEST_RPC_DIR;
                else
                        destdir = MANIFEST_DIR;
        }

        len = strlen(destdir) + strlen(iconf->service) +
            strlen(iconf->protocol) + sizeof ("/-visible.xml");
        dstfile = safe_malloc(len);

        (void) strlcpy(dstfile, destdir, len);
        if (dstfile[strlen(dstfile) - 1] != '/')
                (void) strlcat(dstfile, "/", len);
        cp = dstfile + strlen(dstfile);

        (void) strlcat(dstfile, iconf->service, len);
        (void) strlcat(dstfile, "-", len);

        proto = iconf->protocol;
        if (iconf->isrpc && (strcmp(iconf->protocol, "rpc/*") == 0))
                proto = "rpc/visible";

        (void) strlcat(dstfile, proto, len);
        (void) strlcat(dstfile, ".xml", len);

        /* convert any '/' chars in service or protocol to '_' chars */
        while ((cp = strchr(cp, '/')) != NULL)
                *cp = '_';

        fd = open(dstfile, O_WRONLY|O_CREAT|(overwrite ? O_TRUNC : O_EXCL),
            0644);
        if (fd == -1) {
                if (!overwrite && (errno == EEXIST)) {
                        (void) fprintf(stderr,
                            gettext("%s: Notice: Service manifest for "
                            "%s already generated as %s, skipped\n"),
                            progname, iconf->service, dstfile);
                        free(dstfile);
                        return (-1);
                } else {
                        (void) fprintf(stderr,
                            gettext("%s: Error opening %s: %s\n"),
                            progname, dstfile, strerror(errno));
                        free(dstfile);
                        return (-2);
                }
        }
        /* Clear errno to catch the "no stdio streams" case */
        errno = 0;
        if ((fp = fdopen(fd, "w")) == NULL) {
                char *s = strerror(errno);
                if (errno == 0)
                        s = gettext("No stdio streams available");
                (void) fprintf(stderr, gettext("%s: Error fdopen failed: %s\n"),
                    progname, s);
                (void) close(fd);
                free(dstfile);
                return (-2);
        }
        *finfo = safe_malloc(sizeof (struct fileinfo));
        (*finfo)->fp = fp;
        (*finfo)->filename = dstfile;
        (*finfo)->lineno = 0;
        (*finfo)->failcnt = 0;
        return (0);
}

static int
import_manifest(char *filename)
{
        int status;
        pid_t pid, wpid;
        char *cp;

        if ((cp = strrchr(filename, '/')) == NULL)
                cp = filename;
        else
                cp++;
        (void) printf(gettext("Importing %s ..."), cp);

        if ((pid = fork()) == -1) {
                (void) fprintf(stderr,
                    gettext("\n%s: fork failed, %s not imported: %s\n"),
                    progname, filename, strerror(errno));
                exit(EXIT_ERROR_SYS);
        }
        if (pid == 0) {
                /* child */
                (void) fclose(stdin);
                (void) setenv("SVCCFG_CHECKHASH", "1", 1);
                (void) execl(SVCCFG_PATH, "svccfg", "import", filename, NULL);
                (void) fprintf(stderr, gettext("\n%s: exec of %s failed: %s"),
                    progname, SVCCFG_PATH, strerror(errno));
                _exit(EXIT_ERROR_SYS);
        }
        /* parent */
        if ((wpid = waitpid(pid, &status, 0)) != pid) {
                (void) fprintf(stderr, gettext(
                    "\n%s: unexpected wait (%d) from import of %s: %s\n"),
                    progname, wpid, filename, strerror(errno));
                return (-1);
        }
        if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) {
                (void) fprintf(stderr,
                    gettext("\n%s: import failure (%d) for %s\n"),
                    progname, WEXITSTATUS(status), filename);
                return (-1);
        }
        (void) printf(gettext("Done\n"));
        return (0);
}

static int
inetd_config_path(char **path)
{
        int fd;
        char *arg1, *configfile, *configstr;
        scf_simple_prop_t *sp;
        char cpath[PATH_MAX];

        if ((sp = scf_simple_prop_get(NULL, INETD_INSTANCE_FMRI, "start",
            SCF_PROPERTY_EXEC)) == NULL)
                return (-1);
        if ((configstr = scf_simple_prop_next_astring(sp)) == NULL) {
                scf_simple_prop_free(sp);
                return (-1);
        }
        configstr = safe_strdup(configstr);
        scf_simple_prop_free(sp);

        /*
         * Look for the optional configuration file, the syntax is:
         * /usr/lib/inet/inetd [config-file] start|stop|refresh|disable|%m
         */
        if (strtok(configstr, " \t") == NULL) {
                free(configstr);
                return (-1);
        }
        if ((arg1 = strtok(NULL, " \t")) == NULL) {
                free(configstr);
                return (-1);
        }
        if (strtok(NULL, " \t") == NULL) {
                /*
                 * No configuration file specified, do the same as inetd and
                 * first try /etc/inet/inetd.conf, followed by /etc/inetd.conf.
                 */
                configfile = MAIN_CONFIG;
                if ((fd = open(configfile, O_RDONLY)) >= 0)
                        (void) close(fd);
                else
                        configfile = ALT_CONFIG;

        } else {
                /* make sure there are no more arguments */
                if (strtok(NULL, " \t") != NULL) {
                        free(configstr);
                        return (-1);
                }
                configfile = arg1;
        }

        /* configuration file must be an absolute pathname */
        if (*configfile != '/') {
                free(configstr);
                return (-1);
        }

        if (realpath(configfile, cpath) == NULL)
                (void) strlcpy(cpath, configfile, sizeof (cpath));

        free(configstr);
        *path = safe_strdup(cpath);
        return (0);
}

static int
update_hash(char *srcfile)
{
        scf_error_t rval;
        char *inetd_cpath, *hashstr;
        char cpath[PATH_MAX];

        /* determine the config file inetd is using */
        if (inetd_config_path(&inetd_cpath) == -1) {
                (void) fprintf(stderr,
                    gettext("%s: Error reading from repository\n"), progname);
                return (-1);
        }

        /* resolve inetconv input filename */
        if (realpath(srcfile, cpath) == NULL)
                (void) strlcpy(cpath, srcfile, sizeof (cpath));

        /* if inetconv and inetd are using the same config file, update hash */
        if (strcmp(cpath, inetd_cpath) != 0) {
                free(inetd_cpath);
                return (0);
        }
        free(inetd_cpath);

        /* generic error message as use of hash is not exposed to the user */
        if (calculate_hash(cpath, &hashstr) != 0) {
                (void) fprintf(stderr,
                    gettext("%s: Error unable to update repository\n"),
                    progname);
                return (-1);
        }
        /* generic error message as use of hash is not exposed to the user */
        if ((rval = store_inetd_hash(hashstr)) != SCF_ERROR_NONE) {
                (void) fprintf(stderr,
                    gettext("%s: Error updating repository: %s\n"),
                    progname, scf_strerror(rval));
                free(hashstr);
                return (-1);
        }
        free(hashstr);
        return (0);
}

static void
property_error(const char *fmri, const char *prop)
{
        (void) fprintf(stderr,
            gettext("Error: Instance %1$s is missing property '%2$s'.\n"),
            fmri, prop);
}

/*
 * modify_sprop takes a handle, an instance, a property group, a property,
 * and an astring value, and modifies the instance (or service's) specified
 * property in the repository to the submitted value.
 *
 * returns -1 on error, 1 on successful transaction completion.
 */

static int
modify_sprop(scf_handle_t *h, const scf_instance_t *inst,
    const char *pg, const char *prop, const char *value)
{
        scf_transaction_t               *tx = NULL;
        scf_transaction_entry_t         *ent = NULL;
        scf_propertygroup_t             *gpg = NULL;
        scf_property_t                  *eprop = NULL;
        scf_value_t                     *v = NULL;
        scf_service_t                   *svc = NULL;
        int                             ret = 0, create = 0;

        if ((gpg = scf_pg_create(h)) == NULL)
                return (-1);

        /* Get the property group */
        if (scf_instance_get_pg(inst, pg, gpg) == -1) {
                /* Not a property of the instance, try the service instead */
                if ((svc = scf_service_create(h)) == NULL) {
                        ret = -1;
                        goto out;
                }
                if ((scf_instance_get_parent(inst, svc) == -1) ||
                    (scf_service_get_pg(svc, pg, gpg) == -1)) {
                        ret = -1;
                        goto out;
                }
        }

        if ((eprop = scf_property_create(h)) == NULL) {
                ret = -1;
                goto out;
        }

        if (scf_pg_get_property(gpg, prop, eprop) == -1) {
                if (scf_error() != SCF_ERROR_NOT_FOUND) {
                        ret = -1;
                        goto out;
                }

                create = 1;
        }

        if ((tx = scf_transaction_create(h)) == NULL ||
            (ent = scf_entry_create(h)) == NULL) {
                ret = -1;
                goto out;
        }

        do {
                if (scf_transaction_start(tx, gpg) == -1) {
                        ret = -1;
                        goto out;
                }

                /* Modify the property */
                if (create)
                        ret = scf_transaction_property_new(tx, ent, prop,
                            SCF_TYPE_ASTRING);
                else
                        ret = scf_transaction_property_change_type(tx, ent,
                            prop, SCF_TYPE_ASTRING);

                if (ret == -1)
                        goto out;

                if ((v = scf_value_create(h)) == NULL) {
                        ret = -1;
                        goto out;
                }

                if (scf_value_set_astring(v, value) == -1) {
                        ret = -1;
                        goto out;
                }

                if (scf_entry_add_value(ent, v) == -1) {
                        ret = -1;
                        goto out;
                }

                ret = scf_transaction_commit(tx);

                if (ret == 0) {
                        /* Property group was stale, retry */
                        if (scf_pg_update(gpg) == -1) {
                                ret = -1;
                                goto out;
                        }
                        scf_transaction_reset(tx);
                }

        } while (ret == 0);
out:
        scf_value_destroy(v);
        scf_entry_destroy(ent);
        scf_transaction_destroy(tx);
        scf_property_destroy(eprop);
        scf_service_destroy(svc);
        scf_pg_destroy(gpg);

        return (ret);
}

/*
 * list_callback is the callback function to be handed to simple_walk_instances
 * in main.  It is called once on every instance on a machine.  If that
 * instance is controlled by inetd, we test whether it's the same
 * service that we're looking at from the inetd.conf file, and enable it if
 * they are the same.
 */

/*ARGSUSED*/
static int
list_callback(scf_handle_t *h, scf_instance_t *inst, void *buf)
{
        ssize_t                 max_name_length;
        char                    *svc_name;
        scf_simple_prop_t       *prop = NULL;
        scf_simple_prop_t       *sockprop = NULL;
        scf_simple_prop_t       *rpcprop = NULL;
        scf_simple_prop_t       *progprop = NULL;
        const char              *name, *endpoint, *restart_str, *prog;
        struct inetconfent      *iconf = (struct inetconfent *)buf;
        uint8_t                 *isrpc;

        max_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
        if ((svc_name = malloc(max_name_length + 1)) == NULL) {
                (void) fprintf(stderr, gettext("Error: Out of memory.\n"));
                return (SCF_FAILED);
        }

        /*
         * Get the FMRI of the instance, and check if its delegated restarter
         * is inetd.  A missing or empty restarter property implies that
         * svc.startd is the restarter.
         */

        if (scf_instance_to_fmri(inst, svc_name, max_name_length) < 0) {
                (void) fprintf(stderr,
                    gettext("Error: Unable to obtain FMRI for service %1$s."),
                    svc_name);
                free(svc_name);
                return (SCF_FAILED);
        }

        if ((prop = scf_simple_prop_get(h, svc_name, SCF_PG_GENERAL,
            SCF_PROPERTY_RESTARTER)) == NULL)
                goto out;

        if ((restart_str = scf_simple_prop_next_ustring(prop)) == NULL)
                goto out;

        if (strcmp(restart_str, INETD_INSTANCE_FMRI) != 0)
                goto out;

        /* Free restarter prop so it can be reused below */
        scf_simple_prop_free(prop);

        /*
         * We know that this instance is managed by inetd.
         * Now get the properties needed to decide if it matches this
         * line in the old config file.
         */

        if (((prop = scf_simple_prop_get(h, svc_name, PG_NAME_SERVICE_CONFIG,
            PR_SVC_NAME_NAME)) == NULL) ||
            ((name = scf_simple_prop_next_astring(prop)) == NULL)) {
                property_error(svc_name, PR_SVC_NAME_NAME);
                goto out;
        }

        if (((sockprop = scf_simple_prop_get(h, svc_name,
            PG_NAME_SERVICE_CONFIG, PR_SOCK_TYPE_NAME)) == NULL) ||
            ((endpoint = scf_simple_prop_next_astring(sockprop)) == NULL)) {
                property_error(svc_name, PR_SOCK_TYPE_NAME);
                goto out;
        }

        if (((rpcprop = scf_simple_prop_get(h, svc_name,
            PG_NAME_SERVICE_CONFIG, PR_ISRPC_NAME)) == NULL) ||
            ((isrpc = scf_simple_prop_next_boolean(rpcprop)) == NULL)) {
                property_error(svc_name, PR_ISRPC_NAME);
                goto out;
        }

        if (((progprop = scf_simple_prop_get(h, svc_name, START_METHOD_NAME,
            PR_EXEC_NAME)) == NULL) ||
            ((prog = scf_simple_prop_next_astring(progprop)) == NULL)) {
                property_error(svc_name, PR_EXEC_NAME);
        }


        /* If it's RPC, we truncate off the version portion for comparison */
        if (*isrpc) {
                char *cp;

                cp = strchr(iconf->service, '/');
                if (cp != NULL)
                        *cp = '\0';
        }

        /*
         * If name of this service and endpoint are equal to values from
         * iconf fields, and they're either both RPC or both non-RPC,
         * then we have a match; update the exec and arg0 properties if
         * necessary, then enable it.
         * We don't return an error if either operation fails so that we
         * continue to try all the other services.
         */
        if (strcmp(name, iconf->service) == 0 &&
            strcmp(endpoint, iconf->endpoint) == 0 &&
            *isrpc == (strncmp(iconf->protocol, "rpc/", 4) == 0)) {
                /* Can't update exec on internal services */
                if ((strcmp(iconf->server_program, "internal") != 0) &&
                    (strcmp(iconf->exec, prog) != 0)) {
                        /* User had edited the command */
                        if (!import) {
                                /* Dry run only */
                                (void) printf(
                                    gettext("Would update %s to %s %s"),
                                    svc_name, PR_EXEC_NAME, iconf->exec);
                                if (iconf->arg0 != NULL) {
                                        (void) printf(
                                            gettext(" with %s of %s\n"),
                                            PR_ARG0_NAME, iconf->arg0);
                                } else {
                                        (void) printf("\n");
                                }
                        } else {
                                /* Update instance's exec property */
                                if (modify_sprop(h, inst, START_METHOD_NAME,
                                    PR_EXEC_NAME, iconf->exec) != 1)
                                        (void) fprintf(stderr,
                                            gettext("Error: Unable to update "
                                            "%s property of %s, %s\n"),
                                            PR_EXEC_NAME, svc_name,
                                            scf_strerror(scf_error()));
                                else
                                        (void) printf("%s will %s %s\n",
                                            svc_name, PR_EXEC_NAME,
                                            iconf->exec);

                                /* Update arg0 prop, if needed */
                                if (iconf->arg0 != NULL) {
                                        if (modify_sprop(h, inst,
                                            START_METHOD_NAME, PR_ARG0_NAME,
                                            iconf->arg0) != 1) {
                                                (void) fprintf(stderr,
                                                    gettext("Error: Unable to "
                                                    "update %s property of "
                                                    "%s, %s\n"), PR_ARG0_NAME,
                                                    svc_name,
                                                    scf_strerror(scf_error()));
                                        } else {
                                                (void) printf("%s will have an "
                                                    "%s of %s\n", svc_name,
                                                    PR_ARG0_NAME, iconf->arg0);
                                        }
                                }
                        }
                }

                if (!import) {
                        /* Dry-run only */
                        (void) printf("Would enable %s\n", svc_name);
                } else {
                        if (smf_enable_instance(svc_name, 0) != 0)
                                (void) fprintf(stderr,
                                    gettext("Error: Failed to enable %s\n"),
                                    svc_name);
                        else
                                (void) printf("%s enabled\n", svc_name);
                }
        }

out:
        free(svc_name);
        scf_simple_prop_free(prop);
        scf_simple_prop_free(sockprop);
        scf_simple_prop_free(rpcprop);
        scf_simple_prop_free(progprop);
        return (SCF_SUCCESS);
}

static void
usage(void)
{
        (void) fprintf(stderr, gettext(
            "Usage: %s [-fn] [-i srcfile] [-o destdir]\n"
            "       %1$s -e [-n] [-i srcfile]\n"
            "-?          Display this usage message\n"
            "-e          Enable smf services which are enabled in the input\n"
            "            file\n"
            "-f          Force overwrite of existing manifests\n"
            "-n          Do not import converted manifests,\n"
            "            or only display services which would be enabled\n"
            "-i srcfile  Alternate input file\n"
            "-o destdir  Alternate output directory for manifests\n"),
            progname);
        exit(EXIT_USAGE);
}

int
main(int argc, char *argv[])
{
        int c, rval, convert_err, import_err = 0, enable_err = 0;
        boolean_t overwrite = B_FALSE;
        boolean_t enable = B_FALSE;
        char *srcfile = NULL;
        char *destdir = NULL;
        struct fileinfo *srcfinfo, *dstfinfo;
        struct inetconfent *iconf;

        setbuf(stdout, NULL);
        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        if ((progname = strrchr(argv[0], '/')) == NULL)
                progname = argv[0];
        else
                progname++;

        while ((c = getopt(argc, argv, "?efni:o:")) != -1) {
                switch (c) {
                case 'e':
                        /* enable services based on existing file config */
                        enable = B_TRUE;
                        break;

                case 'f':
                        /* overwrite existing manifests */
                        overwrite = B_TRUE;
                        break;
                case 'n':
                        /* don't import manifests, or dry-run enable */
                        import = B_FALSE;
                        break;
                case 'i':
                        /* alternate input file */
                        if (srcfile != NULL) {
                                (void) fprintf(stderr,
                                    gettext("%s: Error only one -%c allowed\n"),
                                    progname, optopt);
                                usage();
                        }
                        srcfile = optarg;
                        break;
                case 'o':
                        /* alternate output directory */
                        if (destdir != NULL) {
                                (void) fprintf(stderr,
                                    gettext("%s: Error only one -%c allowed\n"),
                                    progname, optopt);
                                usage();
                        }
                        destdir = optarg;
                        break;
                case '?': /*FALLTHROUGH*/
                default:
                        usage();
                        break;
                }
        }

        /*
         * Display usage if extraneous args supplied or enable specified in
         * combination with overwrite or destdir
         */
        if ((optind != argc) || (enable && (overwrite || destdir != NULL)))
                usage();

        if ((srcfinfo = open_srcfile(srcfile)) == NULL)
                return (EXIT_ERROR_CONV);

        while ((iconf = fgetinetconfent(srcfinfo, !enable)) != NULL) {
                /*
                 * If we're enabling, then just walk all the services for each
                 * line and enable those which match.
                 */
                if (enable) {
                        rval = scf_simple_walk_instances(SCF_STATE_ALL, iconf,
                            list_callback);
                        free_inetconfent(iconf);
                        if (rval == SCF_FAILED) {
                                /* Only print msg if framework error */
                                if (scf_error() != SCF_ERROR_CALLBACK_FAILED)
                                        (void) fprintf(stderr, gettext(
                                            "Error walking instances: %s.\n"),
                                            scf_strerror(scf_error()));
                                enable_err++;
                                break;
                        }
                        continue;
                }

                /* Remainder of loop used for conversion & import */
                if ((rval = open_dstfile(destdir, overwrite, iconf, &dstfinfo))
                    < 0) {
                        /*
                         * Only increment error counter if the failure was
                         * other than the file already existing.
                         */
                        if (rval == -2)
                                srcfinfo->failcnt++;
                        free_inetconfent(iconf);
                        continue;
                }
                rval = print_manifest(dstfinfo->fp, dstfinfo->filename, iconf);
                (void) fclose(dstfinfo->fp);
                if (rval == 0) {
                        if (import &&
                            (import_manifest(dstfinfo->filename) != 0))
                                import_err++;
                } else {
                        (void) unlink(dstfinfo->filename);
                        srcfinfo->failcnt++;
                }
                free(dstfinfo->filename);
                free(dstfinfo);
                free_inetconfent(iconf);
        }
        (void) fclose(srcfinfo->fp);
        convert_err = srcfinfo->failcnt;

        /* Update hash only if not in enable mode, and only if importing */
        if (!enable && import && (update_hash(srcfinfo->filename) != 0))
                import_err++;

        free(srcfinfo);

        if (enable_err != 0)
                return (EXIT_ERROR_ENBL);
        if (import_err != 0)
                return (EXIT_ERROR_IMP);
        if (convert_err != 0)
                return (EXIT_ERROR_CONV);
        return (EXIT_SUCCESS);
}