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;
}
}