C implementation of the lsbinstall command

Jiri Dluhos <[email protected]>
Newsgroups gmane.linux.lsb.test-suite
Message-ID <[email protected]>
[This message should probably belong to another list, but I don't know which 
one, so please bear with me...]

Hello,

I have written a C implementation of the lsbinstall command, as described at 
the LSB 3.0 Future Annex. It is slightly geared towards SuSE Linux, but I 
think the implementation can be easily adapted. Could you please look at it 
and say what you think? :-)

Best regards,

    Jiri Dluhos

_______________________________________________
lsb-test mailing list
[email protected]
http://mail.freestandards.org/mailman/listinfo/lsb-test
lsbinstall.c (text/x-csrc, 53.8 KB)
#include <stdio.h>
#include <getopt.h>
#include <assert.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <netdb.h>
#include <pwd.h>
#include <grp.h>

/* Options that can be passed on the command line. */
struct option command_line_options[] = {
    { "check", 0, NULL, 'c' },
    { "dry-run", 0, NULL, 'n' },
    { "help", 0, NULL, 'h' },
    { "package", 1, NULL, 'p' },
    { "remove", 0, NULL, 'r' },
    { "type", 1, NULL, 't' },
    { "verbose", 0, NULL, 'v' },
};

/* Return codes of the program. */
#define RETURN_CODE_SUCCESS                 0
#define RETURN_CODE_FAILURE                 1
#define RETURN_CODE_INVALID_ARGUMENTS       127

/* Flags specified via the command line options. */
#define COMMAND_LINE_FLAG_VERBOSE       1<<1
#define COMMAND_LINE_FLAG_DRY_RUN       1<<2
#define COMMAND_LINE_FLAG_REMOVE        1<<3
#define COMMAND_LINE_FLAG_CHECK         1<<4

/* Supported object types. */
#define OBJECT_TYPE_UNSET               0
#define OBJECT_TYPE_PROFILE_SCRIPT      1
#define OBJECT_TYPE_SERVICE             2
#define OBJECT_TYPE_INETD_ENTRY         3

/* Maximum number of extra options supported. */
#define MAX_EXTRA_OPTIONS               10

/* Globally accessible flags as specified on the command line. */
int command_line_flags = 0;

/*-------------------------------------------------------------------------*/

/* Prints an error message formatted in printf-style and with a newline. */
void complain(const char * format, ...)
{
    va_list args;

    fprintf(stderr, "lsbinstall: ");
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fputc('\n', stderr);
}

/* Prints a message formatted in printf-style and with a newline. */
void notify(const char * format, ...)
{
    va_list args;

    printf("lsbinstall: ");
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    fputc('\n', stdout);
}

/* Like notify() but only prints text if the verbose mode is enabled. */
void verbose_notify(const char * format, ...)
{
    va_list args;

    if (command_line_flags & COMMAND_LINE_FLAG_VERBOSE) {
        printf("lsbinstall: ");
        va_start(args, format);
        vprintf(format, args);
        va_end(args);
        fputc('\n', stdout);
    }
}

/* Prints a short help on the standard output. */
void print_help()
{
    printf(
        "Usage: lsbinstall -t OBJECT_TYPE [-p PACKAGE_NAME] [OPTIONS]"
        " OBJECT_NAME ...\n\n"
        "Installs, removes or checks for presence of the specified"
        " object.\n\n"
        "Options:\n"
        "-c, --check          Check if object is installed\n"
        "-h, --help           Print this help\n"
        "-n, --dry-run        Show actions without performing them\n"
        "-p, --package        Name of the package the object belongs to\n"
        "-r, --remove         Remove object\n"
        "-t, --type           Object type (profile/service/inet)\n"
        "-v, --verbose        Print more information\n"
        "\n"
    );
}

/*-------------------------------------------------------------------------*/

/* Frees the memory referenced by the specified pointer
 * and resets the pointer to NULL. Passing NULL pointer is safe.
 */
#define free_and_reset(P) { \
    free(P); \
    P = NULL; \
}

/* Frees the memory referenced by the specified pointer
 * and sets the pointer to the specified value. Passing NULL is safe.
 */
#define free_and_set(P, X) { \
    free(P); \
    P = X; \
}

/*-------------------------------------------------------------------------*/

/* String helpers */

/* Returns nonzero if the strings are equal, zero otherwise. */
int string_equals(const char * str1, const char * str2)
{
    return (strcmp(str1, str2) == 0);
}

/* Returns nonzero if the specified string is equal to one
 * of the listed strings, zero otherwise. The list of options
 * is terminated by a NULL. */
int string_in_list(const char * str, ...)
{
    va_list list;
    const char * list_item;

    va_start(list, str);

    while (1) {

        /* Get the next item from the list. */
        list_item = va_arg(list, const char *);

        if (list_item == NULL) {

            /* Stop at NULL. */
            break;
        }
        if (strcmp(str, list_item) == 0) {

            /* The list item matches. */
            va_end(list);
            return 1;
        }
    }
    va_end(list);

    /* No match. */
    return 0;
}

/* Returns nonzero if the specified string starts with the specified
 * substring, zero otherwise. */
int string_starts_with(const char * str, const char * substr)
{
    int substring_length = strlen(substr);
    if (strncmp(str, substr, substring_length) == 0) {
        return 1;
    }
    return 0;
}

/* Subdivides the source string into two parts at the separator,
 * returning the first part as a newly allocated string
 * and storing pointer to the first character of the second part
 * in the source string.
 *
 * Works correctly even if 'second_part' points to 'source'.
 *
 * It is safe to provide NULL as the source; the call
 * returns NULL and stores NULL in the second part pointer. */
char * subdivide_string(const char * source, char separator,
    const char ** second_part)
{
    const char * separator_position = NULL;
    char * result;
    unsigned int first_part_length;

    assert(second_part);

    /* Special case. */
    if (source == NULL) {
        *second_part = NULL;
        return NULL;
    }

    /* Subdivide the string into two parts at the first separator. */
    separator_position = strchr(source, separator);
    if (separator_position == NULL) {

        /* Copy the whole string as the first part. */
        result = strdup(source);

        /* There is no second part. */
        *second_part = NULL;
    }
    else {

        /* Duplicate the first part of the string. */
        first_part_length = (unsigned int) (separator_position - source);
        result = malloc(first_part_length + 1);
        assert(result);
        memcpy(result, source, first_part_length);
        result[first_part_length] = '\0';

        /* Store pointer to the second part. */
        *second_part = separator_position + 1;
    }

    return result;
}

/* Converts a string to an unsigned integer number. Returns nonzero
 * on success, zero on failure.
 */
int string_to_unsigned_int(const char * str, unsigned int * result)
{
    unsigned long value;
    char * end_pointer;

    assert(str);

    value = strtoul(str, &end_pointer, 0);
    if (*end_pointer != '\0') {

        /* Not a complete conversion. */
        *result = 0;
        return 0;
    }

    /* Success. */
    *result = (unsigned int)(value);
    return 1;
}

/* Returns a pointer to the first non-whitespace character
 * in the string.
 */
const char * find_first_nonwhitespace(const char * str)
{
    assert(str);

    return str + strspn(str, " \t");
}

/* Separate a first word from the string and returns it as a newly
 * allocated string. If 'rest' is not NULL, stores a pointer
 * to the rest of the string.
 *
 * Any whitespace preceding the first word is automatically skipped.
 *
 * Works correctly even if 'rest' points to 'str'.
 */
char * separate_first_word(const char * str, const char ** rest)
{
    const char * start;
    unsigned int length;
    char * result;

    assert(str);

    /* Skip whitespace and determine the length of the following word. */
    start = find_first_nonwhitespace(str);
    length = strspn(start, "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "0123456789"
        "-_");

    /* Copy the result to a newly allocated string. */
    result = malloc(length + 1);
    memcpy(result, start, length);
    result[length] = '\0';

    /* Store pointer to the rest of the string (if requested). */
    if (rest) {
        *rest = start + length;
    }

    return result;
}

/* Resolves the printf-style format and additional arguments
 * and returns the result as a newly allocated string.
 *
 * (Taken from the glibc manual.)
 */
char * make_string(const char *fmt, ...)
{
    /* Guess we need no more than 100 bytes. */
    int n, size = 100;
    char *p, *np;
    va_list ap;

    if ((p = malloc (size)) == NULL)
    return NULL;

    while (1) {

        /* Try to print in the allocated space. */
        va_start(ap, fmt);
        n = vsnprintf(p, size, fmt, ap);
        va_end(ap);

        /* If that worked, return the string. */
        if (n > -1 && n < size)
            return p;

        /* Else try again with more space. */
        if (n > -1)    /* glibc 2.1 */
            size = n+1; /* precisely what is needed */
        else           /* glibc 2.0 */
            size *= 2;  /* twice the old size */

        if ((np = realloc(p, size)) == NULL) {
            free(p);
            return NULL;
        } else {
            p = np;
        }
    }
}

/*-------------------------------------------------------------------------*/

/* Text file helpers */

/* A single line of text. */
typedef struct tag_TextLine {

    /* Pointer to the next line (NULL if this is the last one). */
    struct tag_TextLine * next_line;

    /* The textual content of the line (zero terminated). */
    char * text;
}
TextLine;

/* Builds a TextLine structure using the dynamically allocated string
 * passed as an argument (the string becomes part of the structure,
 * it is not copied).
 */
TextLine * build_line(char * text)
{
    TextLine * new_line;

    assert(text);

    new_line = (TextLine *) malloc(sizeof(TextLine));
    assert(new_line);
    new_line->text = text;
    new_line->next_line = NULL;
    return new_line;
}

/* Creates a TextLine structure and initializes its contents
 * to the copy of the provided string.
 */
TextLine * create_line(const char * text)
{
    assert(text);

    return build_line(strdup(text));
}

/* Inserts a new line after the specified line. */
void insert_line_after(TextLine * line, TextLine * new_line)
{
    assert(line);
    assert(new_line);
    assert(new_line->next_line == NULL);

    new_line->next_line = line->next_line;
    line->next_line = new_line;
}

/* Deletes a line following the specified line. Returns a pointer
 * to the new next line, or NULL if there is no next line.
 * If the line is the last one, the call has no effect and
 * returns NULL. */
TextLine * delete_line_after(TextLine * line)
{
    TextLine * deleted_line;

    assert(line);

    deleted_line = line->next_line;

    /* Do nothing if this is the last line. */
    if (deleted_line == NULL) {
        return NULL;
    }

    /* Unlink the next line. */
    line->next_line = deleted_line->next_line;

    /* Free the line. */
    free(deleted_line->text);
    free(deleted_line);

    /* Return the new following line. */
    return line->next_line;
}

/* A text file broken into lines. */
typedef struct {

    /* First line. */
    TextLine * first_line;
}
TextFile;

#define MAX_LINE_LENGTH 1023

/* Loads a text file and returns its contents in a newly allocated
 * TextFile structure. Returns NULL if an error occurred. */
TextFile * load_text_file(const char * path)
{
    char line_buffer[MAX_LINE_LENGTH + 1];
    FILE * file = NULL;
    int line_length;
    TextLine * line;
    TextLine * first_line = NULL;
    TextLine * last_line = NULL;
    TextFile * result = NULL;

    file = fopen(path, "r");
    if (file == NULL) {
        return NULL;
    }

    /* Read the file line by line. */
    while (1) {

        /* Read next line; stop at the end of file or on error. */
        if (fgets(line_buffer, MAX_LINE_LENGTH + 1, file) == NULL) {
            break;
        }

        /* Strip the end of line character. */
        line_length = strlen(line_buffer);
        if (line_length > 0) {
            if (line_buffer[line_length - 1] == '\n') {
                line_buffer[line_length - 1] = '\0';
                line_length--;
            }
        }

        /* Create a new line. */
        line = (TextLine *) malloc(sizeof(TextLine));
        assert(line);
        line->next_line = NULL;

        /* Read the line from the buffer. */
        line->text = malloc(line_length + 1);
        assert(line->text);
        memcpy(line->text, line_buffer, line_length + 1);

        /* Link the new line to the others. */
        if (last_line) {
            last_line->next_line = line;
        }
        else {
            first_line = line;
        }
        last_line = line;
    }

    fclose(file);

    /* Create the TextFile structure. */
    result = (TextFile *) malloc(sizeof(TextFile));
    assert(result);
    result->first_line = first_line;
    return result;
}

/* Frees the TextFile structure. */
void free_text_file(TextFile * file)
{
    TextLine * line;
    TextLine * next_line;

    /* Ignore NULL as free() does. */
    if (file == NULL) {
        return;
    }

    /* Free the text lines. */
    line = file->first_line;
    while (line) {
        next_line = line->next_line;

        /* Free the line and its associated text. */
        assert(line->text);
        free(line->text);
        free(line);

        line = next_line;
    }

    /* Free the TextFile structure. */
    free(file);
}

/* Writes the text file back to disk. */
int write_text_file(TextFile * file, const char * path)
{
    FILE * handle;
    TextLine * line;

    assert(file);

    handle = fopen(path, "w");
    if (handle == NULL) {
        return 0;
    }

    /* Write the contents of all lines to the file. */
    for (line = file->first_line; line; line = line->next_line) {
        assert(line->text);
        fprintf(handle, "%s\n", line->text);
    }

    /* Success. */
    fclose(handle);
    return 1;
}

/*-------------------------------------------------------------------------*/

/* File helpers. */

/* Separates a base name from file path and returns it as a newly
 * allocated string.
 */
char * get_file_base_name(const char * path)
{
    const char * last_slash;

    /* Find the last slash in the path. */
    last_slash = strrchr(path, '/');
    if (last_slash) {

        /* Return only the part after the last slash. */
        return strdup(last_slash + 1);
    }
    else {

        /* No slash, the whole path should be the base name. */
        return strdup(path);
    }
}

/* Checks whether the file exists, returning 1 if it exists,
 * 0 if it does not exist, and -1 if an error occurred.
 */
int file_exists(const char * path)
{
    struct stat stat_buffer;
    int test_result;

    test_result = stat(path, &stat_buffer);
    if (test_result == 0) {

        /* File exists. */
        return 1;
    }
    else if (errno == ENOENT) {

        /* File does not exist. */
        return 0;
    }
    else {

        /* An error has occurred, file existence can't be determined. */
        return -1;
    }
}

#define FTRANSFER_BUFFER_SIZE   1024

/* Transfers all data from source stream to the target.
 * Returns 0 on success, -1 on failure (to be coherent with stdio routines).
 */
int ftransfer(FILE * target, FILE * source)
{
    char buffer[FTRANSFER_BUFFER_SIZE];
    int bytes_read;
    int bytes_written;

    while (1) {

        /* Read as much data in the buffer as can fit. */
        bytes_read = fread(buffer, 1, FTRANSFER_BUFFER_SIZE, source);
        if (bytes_read > 0) {
            bytes_written = fwrite(buffer, 1, bytes_read, target);
            if (bytes_written != bytes_read) {

                /* Ouch! Writing error. */
                return -1;
            }
        }

        if (bytes_read < FTRANSFER_BUFFER_SIZE) {

            /* Buffer was not filled completely; this means that
             * either all data were already transferred, or an I/O error
             * occurred on the source side.
             */
            break;
        }
    }

    if (ferror(source)) {

        /* Reading error. */
        return -1;
    }

    /* All data were successfully transferred. */
    return 0;
}

/*-------------------------------------------------------------------------*/

/* Installation helpers. */

/* Checks if the file is installed, printing error and informative messages.
 * Returns positive number if the file is installed, zero if it is not
 * installed, and negative number if an error has occurred.
 */
int check_if_file_installed(const char * target_path)
{
    int test_result;

    /* Check if the file exists. */
    test_result = file_exists(target_path);

    if (test_result == 1) {

        /* Script exists. */
        verbose_notify("File is installed (as '%s')", target_path);
        return 1;
    }
    else if (test_result == 0) {

        /* Script file does not exist. */
        verbose_notify("File is not installed (would be '%s')", target_path);
        return 0;
    }
    else {

        /* An error has occurred. */
        complain("Error accessing file '%s'", target_path);
        return -1;
    }
}

/* Uninstalls the specified file, printing error and informative messages.
 *
 * If the dry-run flag is set, the file is merely checked but not removed.
 * Returns nonzero on success, zero on failure.
 */
int uninstall_file(const char * target_path)
{
    int test_result;

    /* In dry-run mode, only check if the target file exists,
     * but do not remove it. */
    if (command_line_flags & COMMAND_LINE_FLAG_DRY_RUN) {
        test_result = file_exists(target_path);
        if (test_result == 0) {

            /* The file does not exist. This is also considered
             * success as the result is the same. */
            verbose_notify("File is not installed (would be '%s')",
                target_path);
            return 1;
        }
        else if (test_result == 1) {

            /* Success. */
            verbose_notify("Would remove file '%s'", target_path);
            return 1;
        }
        else {

            /* Error. */
            complain("Cannot access file '%s'", target_path);
            return 0;
        }
    }

    /* Remove the file. */
    if (unlink(target_path) != 0) {
        if (errno == ENOENT) {

            /* The file does not exist at all. This is also considered
             * success as the result is the same. */
            verbose_notify("File is not installed (would be '%s')",
                target_path);
            return 1;
        }
        complain("Could not remove file '%s'", target_path);
        return 0;
    }

    /* Success. */
    verbose_notify("File successfully removed (was '%s')", target_path);
    return 1;
}

/* Creates a file and opens it for writing; prints error and informative
 * messages. Returns nonzero on success, zero on failure. The file handle
 * (NULL on failure) is stored in the specified place.
 *
 * If the dry-run flag is set, only a message is printed about
 * what would be done, and the file handle is always NULL.
 */
int install_file(const char * target_path, FILE ** handle)
{
    int test_result;
    FILE * target;

    /* Start with NULL handle. */
    *handle = NULL;

    /* Look if the file exists. */
    test_result = file_exists(target_path);
    if (test_result > 0) {

        /* File already exists. */
        complain("File is already installed (as '%s')", target_path);
        return 0;
    }
    else if (test_result < 0) {

        /* Error. */
        complain("Could not access file '%s'", target_path);
        return 0;
    }

    /* In dry-run mode, do nothing more and finish. */
    if (command_line_flags & COMMAND_LINE_FLAG_DRY_RUN) {
        verbose_notify("Would write file '%s'", target_path);
        return 1;
    }

    /* Open the target file. */
    target = fopen(target_path, "a+");
    if (target == NULL) {
        complain("Could not write file '%s': %s", target_path,
            strerror(errno));
        return 0;
    }

    /* Guard against near-miss overwrites. */
    if (ftell(target) != 0) {
        complain("Clash on write to file '%s'", target_path);
        fclose(target);
        return 0;
    }

    /* Success. */
    *handle = target;
    return 1;
}

/* Determines the path to an inetd file for the specified
 * package name, service name and protocol name, and returns it
 * as a newly allocated string.
 */
char * get_inetd_file_path(const char * package_name,
    const char * service_name,
    const char * protocol_name)
{
    /* Determine the path of the file in /etc/xinetd.d. */
    if (strcmp(protocol_name, "tcp") == 0) {

        /* The 'tcp' protocol is omitted from name. */
        return make_string("/etc/xinetd.d/%s-%s", package_name,
            service_name);
    } else {

        /* Otherwise add the protocol name to the file name. */
        return make_string("/etc/xinetd.d/%s-%s-%s", package_name,
            service_name, protocol_name);
    }
}

/* Determines the path to a profile script file for the specified
 * package and object name, and returns it as a newly allocated
 * string.
 */
char * get_profile_file_path(
    const char * package_name,
    const char * object_name
)
{
    return make_string("/etc/profile.d/%s-%s", package_name, object_name);
}

/* Checks whether the specified line from the /etc/services file
 * is a full-line comment (comments after a valid line are not
 * considered full-line comments as the line itself is valid).
 */
int is_services_line_comment(const char * line)
{
    assert(line);

    /* Full-line comments start with '#', which may be preceded
     * by some whitespace.
     */
    if (*(find_first_nonwhitespace(line)) == '#') {
        return 1;
    }
    return 0;
}

/* Attempts to decode the provided string as a single line
 * of the /etc/services file. On success, returns 1 and fills
 * the service name, port number and protocol name.
 * On failure, returns 0.
 */
int decode_services_line(
    const char * line,
    char ** service_name,
    unsigned int * port_number,
    char ** protocol_name)
{
    char * temporary_service_name = NULL;
    char * temporary_port_string = NULL;
    char * temporary_protocol_name = NULL;
    const char * rest_of_line;
    unsigned int temporary_port_number = 0;

    assert(line);

    /* Reject full-line comments. */
    if (is_services_line_comment(line)) {
        goto fail;
    }

    /* Read the service name and port from the line
     * (format is 'service_name      port/protocol').
     */
    temporary_service_name = separate_first_word(line, &rest_of_line);
    temporary_port_string = separate_first_word(rest_of_line, &rest_of_line);

    /* If the following character is not '/', the line is malformed. */
    if (rest_of_line[0] != '/') {
        goto fail;
    }

    /* Read the protocol name (and ignore the rest of the line). */
    temporary_protocol_name = separate_first_word(rest_of_line + 1, NULL);

    /* Convert the protocol port to a number. */
    if (!string_to_unsigned_int(temporary_port_string,
        &temporary_port_number)) {

        /* Invalid port number, the line is malformed. */
        goto fail;
    }

    /* Success, send the results out. */
    if (service_name) {
        *service_name = temporary_service_name;
    }
    if (port_number) {
        *port_number = temporary_port_number;
    }
    if (protocol_name) {
        *protocol_name = temporary_protocol_name;
    }
    free(temporary_port_string);
    return 1;

fail:
    free(temporary_service_name);
    free(temporary_port_string);
    free(temporary_protocol_name);
    return 0;
}

/*-------------------------------------------------------------------------*/

/* Workers. */

/* Format of worker function.
 *
 * Workers that install or uninstall things return positive number on success
 * and zero on failure.
 *
 * Workers that check for installed things return positive number if found,
 * zero if not found, and negative number if an error has occurred.
 */
typedef int WorkerFunction(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count);

/* Checks whether the specified script exists in /etc/profile.d. */
int check_profile_script(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * target_path;
    struct stat stat_buffer;
    int test_result;

    assert(package_name);
    assert(object_name);

    /* No extra options are needed. */
    if (extra_option_count > 0) {
        complain("Too many arguments");
        return 0;
    }

    /* Build the path of the installed script (if installed). */
    target_path = get_profile_file_path(package_name, object_name);

    /* Check if the installed script exists. */
    test_result = check_if_file_installed(target_path);

    free(target_path);
    return test_result;
}

/* Installs a profile script to /etc/profile.d. The name of the script
 * is composed from the package name and base name of the source script,
 * e.g. if the source script is "bla.sh" and the package name is "foo",
 * the script will be installed as /etc/profile.d/foo-bla.sh. */
int install_profile_script(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * base_name;
    char * target_path;
    FILE * source = NULL;
    FILE * target = NULL;
    int test_result;
    int success = 0;

    assert(package_name);
    assert(object_name);

    /* No extra options are needed. */
    if (extra_option_count > 0) {
        complain("Too many arguments");
        return 0;
    }

    /* Get the base name of the source script. */
    base_name = get_file_base_name(object_name);

    /* Build the path of the installed script (if installed). */
    target_path = get_profile_file_path(package_name, object_name);

    /* Check that the target file does not already exist. */
    test_result = file_exists(target_path);
    if (test_result > 0) {
        complain("File is already installed (as '%s')", target_path);
        goto finish;
    }
    else if (test_result < 0) {
        complain("Cannot access target file '%s'", target_path);
        goto finish;
    }

    /* Open the source file for reading. */
    source = fopen(object_name, "r");
    if (source == NULL) {
        complain("Cannot read source file '%s': %s", object_name,
            strerror(errno));
        goto finish;
    }

    /* Create the target file (or simulate the action if in dry-run mode). */
    test_result = install_file(target_path, &target);
    if (test_result > 0) {

        /* Copy the file (except in dry-run mode). */
        if (target) {
            if (ftransfer(target, source) != 0) {
                complain("I/O error");
                goto finish;
            }

            /* Done. */
            verbose_notify("File successfully installed as '%s'", target_path);
        }

        success = 1;
    }

finish:
    if (source) fclose(source);
    if (target) fclose(target);
    free(base_name);
    free(target_path);
    return success;
}

/* Removes the profile script from /etc/profile.d. */
int remove_profile_script(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * target_path;
    int test_result;

    /* No extra options are needed. */
    if (extra_option_count > 0) {
        complain("Too many arguments");
        return 0;
    }

    /* Build the path of the installed script (if installed). */
    target_path = get_profile_file_path(package_name, object_name);

    /* Uninstall it (or simulate the action if dry-run mode is enabled). */
    test_result = uninstall_file(target_path);

    free(target_path);
    return test_result;
}

/* Checks whether the service is registered in /etc/services. */
int check_service(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    int port_number;
    char protocol_name[32];
    struct protoent * protocol_description = NULL;
    struct servent * service_description = NULL;

    assert(object_name);
    /* package name is not used */

    /* No extra options are needed. */
    if (extra_option_count > 0) {
        complain("Too many arguments");
        return 0;
    }

    /* The object name can be either a port/protocol string,
     * or the service name. Try the port/protocol first.
     */
    if (sscanf(object_name, "%d/%31s", &port_number, &protocol_name) == 2) {

        /* Look if there is a service with that port number and protocol. */
        service_description = getservbyport(htons(port_number), protocol_name);
        if (service_description == NULL) {

            /* Not found. */
            verbose_notify("No service is installed at port/protocol %u/%s",
                port_number, protocol_name);
            return 0;
        }

        /* Some service exists at that port and protocol. */
        verbose_notify("Port/protocol %u/%s is used by the '%s' service",
            port_number, protocol_name, service_description->s_name);
        return 1;
    }
    else {

        /* Seems that it is a service name.  Look if it exists. */
        service_description = getservbyname(object_name, NULL);
        if (service_description == NULL) {

            /* Service was not found. */
            verbose_notify("Service is not installed");
            return 0;
        }

        /* Service was found. */
        verbose_notify("Service '%s' is installed using port/protocol %u/%s",
            object_name, ntohs(service_description->s_port),
            service_description->s_proto);
        return 1;
    }
}

int install_service(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    TextFile * services_file = NULL;
    TextLine * line;
    TextLine * highest_port_line;
    TextLine * new_line;
    unsigned int port_number;
    char * port_number_string = NULL;
    const char * protocol_name;
    const char * service_name;
    const char * rest_of_line;
    int success = 0;
    unsigned int tested_port_number;
    unsigned int highest_port_number;
    unsigned int alias_index;
    struct servent * service_description = NULL;

    assert(object_name);
    /* package name is not used */

    /* At least one extra option is required (2 arguments in total). */
    if (extra_option_count < 1) {
        complain("At least 2 arguments are required (port/protocol"
            " and service name)");
        goto finish;
    }

    /* Read the port number and protocol name from the object name. */
    port_number_string = subdivide_string(object_name, '/', &protocol_name);
    if ((protocol_name == NULL) ||
        (!string_to_unsigned_int(port_number_string, &port_number))) {

        complain("First argument must be in form 'port number/protocol'");
        goto finish;
    }

    /* First check if all service names are either not in use,
     * or known but bound to the same port/protocol combination.
     * If not, report an error immediately.
     */
    for (alias_index = 0; alias_index < extra_option_count;
        alias_index++) {

        service_name = extra_options[alias_index];
        service_description = getservbyname(service_name, protocol_name);
        if (service_description) {

            /* Service already exists, check if it has the same port number
             * as requested.
             */
            if (ntohs(service_description->s_port) != port_number) {
                complain(
                    "Service '%s' already exists with different port (%u/%s)",
                    service_name, service_description->s_port,
                    service_description->s_proto);
                goto finish;
            }
        }
    }

    /* Load the /etc/services file and break it up to lines. */
    services_file = load_text_file("/etc/services");
    if (services_file == NULL) {
        complain("Could not read file '/etc/services'");
        goto finish;
    }

    /* Scan line by line. */
    highest_port_number = 0;
    for (line = services_file->first_line; line; line = line->next_line) {

        assert(line->text);

        /* Skip full-line comments. */
        if (is_services_line_comment(line->text)) {

            /* If this comment is after the currently highest port line,
             * count it into lines we need to skip before inserting
             * our line. This is needed because /etc/services seems
             * to have comments *after* the line they relate to.
             */
            if (tested_port_number == highest_port_number) {
                highest_port_line = line;
            }

            /* Otherwise skip it. */
            continue;
        }

        /* Decode the line (only the port number is interesting). */
        if (!decode_services_line(line->text, NULL, &tested_port_number, NULL)) {

            /* Skip unrecognized lines. */
            continue;
        }

        /* Stop when finding a line with higher port number than ours.
         * We will add our line(s) before this line so the rest
         * of the file is not interesting.
         */
        if (tested_port_number > port_number) {
            break;
        }

        /* Remember the line with the highest port number
         * lower than ours. We will add our line(s) after this line.
         */
        if (tested_port_number >= highest_port_number) {
            highest_port_number = tested_port_number;
            highest_port_line = line;
        }
    }

    /* Every additional parameter is (should be) an alias for the service.
     * Install them all.
     */
    for (alias_index = 0; alias_index < extra_option_count;
        alias_index++) {

        /* Read the service name/alias. */
        service_name = extra_options[alias_index];
        service_description = getservbyname(service_name, protocol_name);
        if (service_description) {

            /* If the service is already known under this name, do nothing. */
            verbose_notify("Entry '%s %u/%s' already exists in /etc/services",
                service_name, port_number, protocol_name);
        }
        else {
            if (command_line_flags & COMMAND_LINE_FLAG_DRY_RUN) {

                /* Do not add anything, only print what would be done. */
                verbose_notify("Would add entry '%s %u/%s' to /etc/services",
                    service_name, port_number, protocol_name);
            }
            else {

                /* Create a line describing our new service. */
                new_line = build_line(make_string("%-15s %u/%-5s # added by lsbinstall",
                    service_name, port_number, protocol_name));

                /* Insert it after the existing line with nearest lower port number. */
                insert_line_after(highest_port_line, new_line);

                verbose_notify("Adding entry '%s %u/%s' to /etc/services",
                    service_name, port_number, protocol_name);
            }
        }
    }

    if (!(command_line_flags & COMMAND_LINE_FLAG_DRY_RUN)) {

        /* Write the services file back. */
        if (!write_text_file(services_file, "/etc/services")) {
            complain("Error writing file '/etc/services': %s", strerror(errno));
            goto finish;
        }
    }

finish:
    free(port_number_string);
    free_text_file(services_file);
    return 0;
}

int remove_service(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    int port_number;
    char protocol_name[32];
    struct protoent * protocol_description = NULL;
    struct servent * service_description = NULL;
    TextFile * services_file = NULL;
    TextLine * line;
    TextLine * previous_line;
    int have_port_and_protocol = 0;
    int success = 0;
    char * tested_service_name = NULL;
    char * tested_protocol_name = NULL;
    unsigned int tested_port;
    int line_was_just_deleted = 0;

    assert(object_name);
    /* package name is not used */

    /* No extra options are needed. */
    if (extra_option_count > 0) {
        complain("Too many arguments");
        return 0;
    }

    /* Load the /etc/services file and break it up to lines. */
    services_file = load_text_file("/etc/services");
    if (services_file == NULL) {
        complain("Could not read file '/etc/services'");
        goto finish;
    }

    /* The object name can be either a port/protocol string,
     * or the service name. Try the port/protocol, otherwise
     * assume it is the service name.
     */
    if (sscanf(object_name, "%d/%31s", &port_number, &protocol_name) == 2) {
        have_port_and_protocol = 1;
    }
    else {
        have_port_and_protocol = 0;
    }

    /* Scan the lines of the services file, searching for the line
     * containing the service name or port/protocol.
     */
    previous_line = NULL;
    for (line = services_file->first_line; line; line = line->next_line) {

        if (line_was_just_deleted && is_services_line_comment(line->text)) {

            /* Special case: comments right after a deleted line are deleted
             * as well.
             *
             * BUG: This is pretty unsatisfactory as not all comments after
             * a line are related to that line, and even if they do
             * they not always should be removed (as they can relate
             * to a whole group of lines).
             */
            assert(previous_line);
            delete_line_after(previous_line);
            line = previous_line;
        }
        else {

            /* Decode the line. */
            if (decode_services_line(line->text, &tested_service_name,
                &tested_port, &tested_protocol_name)) {

                line_was_just_deleted = 0;

                /* Look if it is the line we look for. */
                if (have_port_and_protocol) {
                    if ((tested_port == port_number) &&
                        string_equals(protocol_name, tested_protocol_name)) {

                        if (command_line_flags & COMMAND_LINE_FLAG_DRY_RUN) {

                            /* Do no real change, just print what
                             * would be done.
                             */
                            verbose_notify("Would remove entry '%s %u/%s'"
                                " from /etc/services", tested_service_name,
                                tested_port, tested_protocol_name);
                        }
                        else {

                            /* Remove it. */
                            assert(previous_line);
                            delete_line_after(previous_line);
                            line = previous_line;
                            line_was_just_deleted = 1;

                            verbose_notify("Removing entry '%s %u/%s'"
                                " from /etc/services", tested_service_name,
                                tested_port, tested_protocol_name);
                        }
                    }
                }
                else {
                    if (string_equals(tested_service_name, object_name)) {
                        if (command_line_flags & COMMAND_LINE_FLAG_DRY_RUN) {

                            /* Do no real change, just print what
                             * would be done.
                             */
                            verbose_notify("Would remove entry '%s %u/%s'"
                                " from /etc/services", tested_service_name,
                                tested_port, tested_protocol_name);
                        }
                        else {

                            /* Remove it. */
                            assert(previous_line);
                            delete_line_after(previous_line);
                            line = previous_line;
                            line_was_just_deleted = 1;

                            verbose_notify("Removed entry '%s %u/%s'"
                                " from /etc/services", tested_service_name,
                                tested_port, tested_protocol_name);
                        }
                    }
                }

                free(tested_service_name);
                free(tested_protocol_name);
            }
        }

        /* Store the previous line (we need it for keeping the links
         * when removing a line).
         */
        previous_line = line;
    }

    if (!(command_line_flags & COMMAND_LINE_FLAG_DRY_RUN)) {

        /* Write the services file back. */
        if (!write_text_file(services_file, "/etc/services")) {
            complain("Error writing file '/etc/services': %s", strerror(errno));
            goto finish;
        }
    }

finish:
    free_text_file(services_file);
    return success;
}

int check_inetd_entry(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * target_path;
    const char * protocol_name;
    char * service_name;
    int test_result;
    int success = 0;

    assert(object_name);
    assert(package_name);

    /* The object name contains the service name, optionally with
     * the protocol name separated by colon. */
    service_name = subdivide_string(object_name, ':', &protocol_name);
    assert(service_name);

    /* If no protocol is given, use "tcp". */
    if (protocol_name == NULL) {
        protocol_name = "tcp";
    }

    /* Determine the path of the file in /etc/xinetd.d. */
    target_path = get_inetd_file_path(package_name, service_name,
        protocol_name);

    /* Check if the file is installed. */
    test_result = check_if_file_installed(target_path);

    free(service_name);
    free(target_path);
    return test_result;
}

int install_inetd_entry(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * service_name = NULL;
    char * protocol_name = NULL;
    char * socket_type = NULL;
    char * wait_flag = NULL;
    char * user_and_group = NULL;
    char * user_name = NULL;
    char * server_name = NULL;
    char * target_path = NULL;
    const char * group_name;
    const char * server_command_line;
    const char * command_line;
    const char * next_part;
    int success = 0;
    FILE * target = NULL;
    int test_result;

    assert(package_name);
    assert(object_name);

    /* Subdivide the argument to fields. */
    service_name = subdivide_string(object_name, ':', &next_part);
    protocol_name = subdivide_string(next_part, ':', &next_part);
    socket_type = subdivide_string(next_part, ':', &next_part);
    wait_flag = subdivide_string(next_part, ':', &next_part);
    user_and_group = subdivide_string(next_part, ':', &server_command_line);
    user_name = subdivide_string(user_and_group, '.', &group_name);
    server_name = subdivide_string(server_command_line, ' ', &command_line);

    /* Report an error if the argument has not enough fields. */
    if (server_command_line == NULL) {
        complain("Argument must be in form 'service:protocol:socket type:"
            "wait flag:user:server binary'");
        goto finish;
    }

    /* Check if the protocol is valid. */
    if (getprotobyname(protocol_name) == NULL) {
        complain("Unrecognized protocol '%s'", protocol_name);
        goto finish;
    }

    /* Check if the service is valid. */
    if (getservbyname(service_name, protocol_name) == NULL) {
        complain("Service '%s' is not installed with protocol '%s'",
            service_name, protocol_name);
        goto finish;
    }

    /* Check if the socket type is valid. */
    if (!string_in_list(socket_type, "stream", "dgram", "seqpacket", NULL)) {
        complain("Socket type must be one of: 'stream', 'dgram', "
            "'seqpacket'");
        goto finish;
    }

    /* Check if the wait flag is valid. */
    if (!string_in_list(wait_flag, "wait", "nowait", NULL)) {
        complain("Wait flag must be one of: 'wait', 'nowait'");
        goto finish;
    }

    /* Check if the user name is valid. */
    if (getpwnam(user_name) == NULL) {
        complain("Unknown user '%s'", user_name);
        goto finish;
    }

    /* Check if the group name (if specified) is valid. */
    if (group_name) {
        if (getgrnam(group_name) == NULL) {
            complain("Unknown group '%s'", group_name);
            goto finish;
        }
    }

    /* Determine the path of the file in /etc/xinetd.d. */
    target_path = get_inetd_file_path(package_name, service_name,
        protocol_name);

    /* Create the file (or simulate the action if in dry-run mode). */
    test_result = install_file(target_path, &target);
    if (test_result > 0) {

        /* Write the file contents (not in dry-run mode). */
        if (target) {

            fprintf(target, "# Created by lsbinstall\n\n");
            fprintf(target, "service %s-%s\n{\n", package_name, service_name);
            fprintf(target, "        id = %s\n", service_name);
            fprintf(target, "        socket_type = %s\n", socket_type);
            fprintf(target, "        protocol = %s\n", protocol_name);
            fprintf(target, "        user = %s\n", user_name);
            if (group_name) {
                fprintf(target, "        group = %s\n", group_name);
            }
            fprintf(target, "        wait = %s\n", (wait_flag[0] == 'w') ?
                "yes" : "no");
            fprintf(target, "        server = %s\n", server_name);
            if (command_line) {
                fprintf(target, "        server_args = %s\n", command_line);
            }
            fprintf(target, "}\n");

            /* Done. */
            verbose_notify("File successfully installed as '%s'",
                target_path);
        }

        /* Success. */
        success = 1;
    }

finish:
    if (target) fclose(target);
    free(service_name);
    free(protocol_name);
    free(socket_type);
    free(wait_flag);
    free(user_and_group);
    free(user_name);
    free(server_name);
    free(target_path);
    return success;
}

int remove_inetd_entry(
    const char * package_name,
    const char * object_name,
    const char * const * extra_options,
    int extra_option_count)
{
    char * target_path;
    const char * protocol_name;
    char * service_name;
    int test_result;
    int success = 0;

    assert(object_name);
    assert(package_name);

    /* The object name contains the service name, optionally with
     * the protocol name separated by colon. */
    service_name = subdivide_string(object_name, ':', &protocol_name);
    assert(service_name);

    /* If no protocol is given, use "tcp". */
    if (protocol_name == NULL) {
        protocol_name = "tcp";
    }

    /* Determine the path of the file in /etc/xinetd.d. */
    target_path = get_inetd_file_path(package_name, service_name,
        protocol_name);

    /* Uninstall it (or simulate the action if dry-run mode is enabled). */
    success = uninstall_file(target_path);

    free(service_name);
    return success;
}

/*-------------------------------------------------------------------------*/

/* The main function. */
int main(int argc, char ** argv)
{
    int option_index;
    const char * option;
    int expect_type = 0;
    int expect_package_name = 0;
    int worker_result = 0;
    int object_type = 0;
    char * extra_options[MAX_EXTRA_OPTIONS];
    int extra_option_count = 0;
    char * object_name = NULL;
    char * package_name = NULL;
    WorkerFunction * worker = NULL;
    int i;

    /* If no arguments were given, print help and exit. */
    if (argc == 1) {
        complain("No arguments specified (try 'lsbinstall -h' for help)");
        return RETURN_CODE_INVALID_ARGUMENTS;
    }

    /* Iterate over command line options and process them. */
    for (option_index = 1; option_index < argc; option_index++) {
        option = argv[option_index];

        if (expect_type) {

            /* After the '-t' option, an object type should follow,
             * which is one of 'profile', 'service' or 'inet'. */
            if (string_equals(option, "profile")) {
                object_type = OBJECT_TYPE_PROFILE_SCRIPT;
            }
            else if (string_equals(option, "service")) {
                object_type = OBJECT_TYPE_SERVICE;
            }
            else if (string_equals(option, "inet")) {
                object_type = OBJECT_TYPE_INETD_ENTRY;
            }
            else {
                complain("Invalid object type '%s'", option);
                return RETURN_CODE_INVALID_ARGUMENTS;
            }

            expect_type = 0;
        }

        else if (expect_package_name) {

            /* After the '-p' option, a package name should follow. */
            package_name = strdup(option);

            expect_package_name = 0;
        }

        else
        {
            if (string_in_list(option, "-c", "--check", NULL)) {
                command_line_flags |= COMMAND_LINE_FLAG_CHECK;
            }
            else if (string_in_list(option, "-n", "--dry-run", NULL)) {
                command_line_flags |= COMMAND_LINE_FLAG_DRY_RUN;
            }
            else if (string_in_list(option, "-r", "--remove", NULL)) {
                command_line_flags |= COMMAND_LINE_FLAG_REMOVE;
            }
            else if (string_in_list(option, "-v", "--verbose", NULL)) {
                command_line_flags |= COMMAND_LINE_FLAG_VERBOSE;
            }
            else if (string_in_list(option, "-h", "--help", "-?", NULL)) {

                /* Print help and do nothing more. */
                print_help();
                return RETURN_CODE_SUCCESS;
            }
            else if (string_in_list(option, "-t", "--type", NULL)) {

                /* Check for multiple '-t' option. */
                if (object_type != OBJECT_TYPE_UNSET) {
                    complain("Only one object type can be specified");
                    return RETURN_CODE_INVALID_ARGUMENTS;
                }

                /* Type name should follow after this option. */
                expect_type = 1;
            }
            else if (string_in_list(option, "-p", "--package", NULL)) {

                /* Check for multiple '-p' option. */
                if (package_name) {
                    complain("Only one package name can be specified");
                    return RETURN_CODE_INVALID_ARGUMENTS;
                }

                /* Package name should follow after this option. */
                expect_package_name = 1;
            }
            else if (string_starts_with(option, "-")) {

                /* Anything other starting with '-' is a syntax error. */
                complain("Unrecognized option '%s' (try '-h' for help)",
                    option);
                return RETURN_CODE_INVALID_ARGUMENTS;
            }
            else {

                /* First non-switch option is the object name. */
                if (object_name == NULL) {
                    object_name = strdup(option);
                }
                else {

                    /* Check for extra option array overflow. */
                    if (extra_option_count == MAX_EXTRA_OPTIONS) {
                        complain("Too many additional options");
                        return RETURN_CODE_INVALID_ARGUMENTS;
                    }

                    /* Otherwise it is an extra option. */
                    extra_options[extra_option_count] = strdup(option);
                    extra_option_count++;
                }
            }
        }
    }

    /* Check for missing parameter after the '-t' or '-p' option. */
    if (expect_package_name || expect_type) {
        if (expect_package_name) {
            complain("Missing package name");
        }
        else {
            complain("Missing object name");
        }
        return RETURN_CODE_INVALID_ARGUMENTS;
    }

    /* Check for conflicting '-c' and '-r' options. */
    if ((command_line_flags & COMMAND_LINE_FLAG_CHECK)
        && (command_line_flags & COMMAND_LINE_FLAG_REMOVE)) {
        complain("At most one of 'check' and 'remove' actions "
            "must be specified");
        return RETURN_CODE_INVALID_ARGUMENTS;
    }

    /* Object type must be always specified. */
    if (object_type == OBJECT_TYPE_UNSET) {
        complain("Object type must be specified");
        return RETURN_CODE_INVALID_ARGUMENTS;
    }

    /* Object name must be always specified. */
    if (object_name == NULL) {
        complain("Object name must be specified");
        return RETURN_CODE_INVALID_ARGUMENTS;
    }

    /* Package name must be specified for profile scripts and
     * for inetd entries, but not for network services.
     */
    if ((object_type == OBJECT_TYPE_PROFILE_SCRIPT) ||
        (object_type == OBJECT_TYPE_INETD_ENTRY)) {
        if (package_name == NULL) {
            complain("Package name must be specified");
            return RETURN_CODE_INVALID_ARGUMENTS;
        }
    }

    /* Choose appropriate worker function. */
    if (command_line_flags & COMMAND_LINE_FLAG_CHECK) {

        switch (object_type) {

            case OBJECT_TYPE_PROFILE_SCRIPT:
                worker = check_profile_script;
                break;

            case OBJECT_TYPE_SERVICE:
                worker = check_service;
                break;

            case OBJECT_TYPE_INETD_ENTRY:
                worker = check_inetd_entry;
                break;
        }
    }
    else if (command_line_flags & COMMAND_LINE_FLAG_REMOVE) {

        switch (object_type) {

            case OBJECT_TYPE_PROFILE_SCRIPT:
                worker = remove_profile_script;
                break;

            case OBJECT_TYPE_SERVICE:
                worker = remove_service;
                break;

            case OBJECT_TYPE_INETD_ENTRY:
                worker = remove_inetd_entry;
                break;
        }
    }
    else {

        /* Install the object. */
        switch (object_type) {

            case OBJECT_TYPE_PROFILE_SCRIPT:
                worker = install_profile_script;
                break;

            case OBJECT_TYPE_SERVICE:
                worker = install_service;
                break;

            case OBJECT_TYPE_INETD_ENTRY:
                worker = install_inetd_entry;
                break;
        }
    }

    /* Call the worker function. */
    assert(worker);
    worker_result = worker(package_name, object_name, extra_options,
        extra_option_count);

    /* Clean up. */
    free(object_name);
    free(package_name);
    for (i=0; i < extra_option_count; i++) {
        free(extra_options[i]);
    }

    /* Return success or failure code according to the result
     * of the worker function. Possible error message was already printed
     * by the worker. */
    if (worker_result) {
        return RETURN_CODE_SUCCESS;
    }
    else {
        return RETURN_CODE_FAILURE;
    }
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.