diff options
Diffstat (limited to 'src/core/librccore/depend.c')
-rw-r--r-- | src/core/librccore/depend.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/core/librccore/depend.c b/src/core/librccore/depend.c new file mode 100644 index 0000000..0255547 --- /dev/null +++ b/src/core/librccore/depend.c @@ -0,0 +1,672 @@ +/* + * depend.c + * + * Dependancy engine for Gentoo style rc-scripts. + * + * Copyright (C) 2004,2005 Martin Schlemmer <azarah@nosferatu.za.org> + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Header$ + */ + +#include <errno.h> +#include <string.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "internal/rccore.h" + +LIST_HEAD (service_info_list); + +/* Names for service types (service_type_t) in depend.h. + * Note that this should sync with service_type_t */ +char *service_type_names[] = { + "NEED", + "NEED_ME", + "USE", + "USE_ME", + "BEFORE", + "AFTER", + "BROKEN", + "PROVIDE", + NULL +}; + +static char *service_is_recursive_dependency (char *servicename, + char *dependency, + bool checkuse); +static int __service_resolve_dependency (char *servicename, char *dependency, + service_type_t type); + +service_info_t * +service_get_info (char *servicename) +{ + service_info_t *info; + + if (!check_arg_str (servicename)) + return NULL; + + list_for_each_entry (info, &service_info_list, node) + { + if (NULL != info->name) + if (0 == strcmp (info->name, servicename)) + return info; + } + + /* We use this to check if a service exists, so rather do not + * add debugging, otherwise it is very noisy! */ + /* DBG_MSG("Invalid service name '%s'!\n", servicename); */ + + return NULL; +} + +int +service_add (char *servicename) +{ + service_info_t *info; + service_info_t *sorted; + int count; + + if (!check_arg_str (servicename)) + return -1; + + info = service_get_info (servicename); + if (NULL == info) + { + DBG_MSG ("Adding service '%s'.\n", servicename); + + info = xmalloc (sizeof (service_info_t)); + if (NULL == info) + return -1; + + info->name = xstrndup (servicename, strlen (servicename)); + if (NULL == info->name) + { + free (info); + return -1; + } + + for (count = 0; count < ALL_SERVICE_TYPE_T; count++) + info->depend_info[count] = NULL; + info->provide = NULL; + + /* We want to keep the list sorted */ + list_for_each_entry (sorted, &service_info_list, node) + { + if (strcmp (sorted->name, servicename) > 0) + { + break; + } + } + + list_add_tail (&info->node, &sorted->node); + + return 0; + } + else + { + DBG_MSG ("Tried to add duplicate service '%s'!\n", servicename); + } + + return -1; +} + +int +service_is_dependency (char *servicename, char *dependency, + service_type_t type) +{ + service_info_t *info; + char *service; + int count = 0; + + if ((!check_arg_str (servicename)) || (!check_arg_str (dependency))) + return -1; + + info = service_get_info (servicename); + if (NULL != info) + { + str_list_for_each_item (info->depend_info[type], service, count) + { + if (0 == strcmp (dependency, service)) + return 0; + } + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + } + + return -1; +} + +char * +service_is_recursive_dependency (char *servicename, char *dependency, + bool checkuse) +{ + service_info_t *info; + char *depend; + int count = 0; + + if ((!check_arg_str (servicename)) || (!check_arg_str (dependency))) + return NULL; + + info = service_get_info (dependency); + if (NULL != info) + { + str_list_for_each_item (info->depend_info[NEED_ME], depend, count) + { + if ((0 == service_is_dependency (servicename, depend, NEED)) + || (0 == service_is_dependency (servicename, depend, USE))) + return depend; + } + if (checkuse) + { + str_list_for_each_item (info->depend_info[USE_ME], depend, count) + { + if ((0 == service_is_dependency (servicename, depend, NEED)) + || (0 == service_is_dependency (servicename, depend, USE))) + return depend; + } + } + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + } + + return NULL; +} + +int +service_add_dependency (char *servicename, char *dependency, + service_type_t type) +{ + service_info_t *info; + char *buf; + + if ((!check_arg_str (servicename)) || (!check_arg_str (dependency))) + return -1; + + info = service_get_info (servicename); + if (NULL != info) + { + /* Do not add duplicates */ + if (-1 == service_is_dependency (servicename, dependency, type)) + { + DBG_MSG ("Adding dependency '%s' of service '%s', type '%s'.\n", + dependency, servicename, service_type_names[type]); + + buf = xstrndup (dependency, strlen (dependency)); + if (NULL == buf) + return -1; + + str_list_add_item_sorted (info->depend_info[type], buf, error); + } + else + { + DBG_MSG ("Duplicate dependency '%s' for service '%s', type '%s'!\n", + dependency, servicename, service_type_names[type]); + /* Rather do not fail here, as we add a lot of doubles + * during resolving of dependencies */ + } + + return 0; + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + } + +error: + return -1; +} + +int +service_del_dependency (char *servicename, char *dependency, + service_type_t type) +{ + service_info_t *info; + + if ((!check_arg_str (servicename)) || (!check_arg_str (dependency))) + return -1; + + if (-1 == service_is_dependency (servicename, dependency, type)) + { + DBG_MSG ("Tried to remove invalid dependency '%s'!\n", dependency); + return -1; + } + + info = service_get_info (servicename); + if (NULL != info) + { + DBG_MSG ("Removing dependency '%s' of service '%s', type '%s'.\n", + dependency, servicename, service_type_names[type]); + + str_list_del_item (info->depend_info[type], dependency, error); + return 0; + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + } + +error: + return -1; +} + +service_info_t * +service_get_virtual (char *virtual) +{ + service_info_t *info; + + if (!check_arg_str (virtual)) + return NULL; + + list_for_each_entry (info, &service_info_list, node) + { + if (NULL != info->provide) + if (0 == strcmp (info->provide, virtual)) + return info; + } + + /* We use this to check if a virtual exists, so rather do not + * add debugging, otherwise it is very noisy! */ + /* DBG_MSG("Invalid service name '%s'!\n", virtual); */ + + return NULL; +} + +int +service_add_virtual (char *servicename, char *virtual) +{ + service_info_t *info; + + if ((!check_arg_str (servicename)) || (!check_arg_str (virtual))) + return -1; + + if (NULL != service_get_info (virtual)) + { + EERROR + (" Cannot add provide '%s', as a service with the same name exists!\n", + virtual); + /* Do not fail here as we do have a service that resolves + * the virtual */ + } + + info = service_get_virtual (virtual); + if (NULL != info) + { + /* We cannot have more than one service Providing a virtual */ + EWARN (" Service '%s' already provides '%s'!;\n", info->name, virtual); + EWARN (" Not adding service '%s'...\n", servicename); + /* Do not fail here as we do have a service that resolves + * the virtual */ + } + else + { + info = service_get_info (servicename); + if (NULL != info) + { + DBG_MSG ("Adding virtual '%s' of service '%s'.\n", + virtual, servicename); + + info->provide = xstrndup (virtual, strlen (virtual)); + if (NULL == info->provide) + return -1; + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + return -1; + } + } + + return 0; +} + +int +service_set_mtime (char *servicename, time_t mtime) +{ + service_info_t *info; + + if (!check_arg_str (servicename)) + return -1; + + info = service_get_info (servicename); + if (NULL != info) + { + DBG_MSG ("Setting mtime '%li' of service '%s'.\n", mtime, servicename); + + info->mtime = mtime; + + return 0; + } + else + { + DBG_MSG ("Invalid service name '%s'!\n", servicename); + } + + return -1; +} + +int +__service_resolve_dependency (char *servicename, char *dependency, + service_type_t type) +{ + service_info_t *info; + int retval; + + if ((!check_arg_str (servicename)) || (!check_arg_str (dependency))) + return -1; + + info = service_get_info (servicename); + if (NULL == info) + { + DBG_MSG ("Invalid service name passed!\n"); + return -1; + } + + DBG_MSG ("Checking dependency '%s' of service '%s', type '%s'.\n", + dependency, servicename, service_type_names[type]); + + /* If there are no existing service 'dependency', try to resolve + * possible virtual services */ + info = service_get_info (dependency); + if (NULL == info) + { + info = service_get_virtual (dependency); + if (NULL != info) + { + DBG_MSG ("Virtual '%s' -> '%s' for service '%s', type '%s'.\n", + dependency, info->name, servicename, + service_type_names[type]); + + retval = service_del_dependency (servicename, dependency, type); + if (-1 == retval) + { + DBG_MSG ("Failed to delete dependency!\n"); + return -1; + } + + /* Add the actual service name for the virtual */ + dependency = info->name; + retval = service_add_dependency (servicename, dependency, type); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + } + } + + /* Handle 'need', as it is the only dependency type that should + * handle invalid database entries currently. */ + if (NULL == info) + { + if ((type == NEED) || (type == NEED_ME)) + { + EWARN (" Can't find service '%s' needed by '%s'; continuing...\n", + dependency, servicename); + + retval = service_add_dependency (servicename, dependency, BROKEN); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + + /* Delete invalid entry */ + goto remove; + } + + /* For the rest, if the dependency is not 'net', just silently + * die without error. Should not be needed as we add a 'net' + * service manually before we start, but you never know ... */ + if (0 != strcmp (dependency, "net")) + { + /* Delete invalid entry */ + goto remove; + } + } + + /* Ugly bug ... if a service depends on itself, it creates a + * 'mini fork bomb' effect, and breaks things horribly ... */ + if (0 == strcmp (servicename, dependency)) + { + /* Dont work too well with the '*' before and after */ + if ((type != BEFORE) && (type != AFTER)) + EWARN (" Service '%s' can't depend on itself; continuing...\n", + servicename); + + /* Delete invalid entry */ + goto remove; + } + + /* Currently only these depend/order types are supported */ + if ((type == NEED) || (type == USE) || (type == BEFORE) || (type == AFTER)) + { + if (type == BEFORE) + { + char *depend; + + /* NEED and USE override BEFORE + * ('servicename' BEFORE 'dependency') */ + if ((0 == service_is_dependency (servicename, dependency, NEED)) + || (0 == service_is_dependency (servicename, dependency, USE))) + { + /* Delete invalid entry */ + goto remove; + } + + depend = service_is_recursive_dependency (servicename, dependency, + TRUE); + if (NULL != depend) + { + EWARN (" Service '%s' should be BEFORE service '%s', but '%s'\n", + servicename, dependency, depend); + EWARN (" needed by '%s', depends in return on '%s'!\n", + servicename, dependency); + + /* Delete invalid entry */ + goto remove; + } + } + + if (type == AFTER) + { + char *depend; + + /* NEED and USE override AFTER + * ('servicename' AFTER 'dependency') */ + if ((0 == service_is_dependency (dependency, servicename, NEED)) + || (0 == service_is_dependency (dependency, servicename, USE))) + { + /* Delete invalid entry */ + goto remove; + } + + depend = service_is_recursive_dependency (dependency, servicename, + TRUE); + if (NULL != depend) + { + EWARN (" Service '%s' should be AFTER service '%s', but '%s'\n", + servicename, dependency, depend); + EWARN (" needed by '%s', depends in return on '%s'!\n", + dependency, servicename); + + /* Delete invalid entry */ + goto remove; + } + } + + /* We do not want to add circular dependencies ... */ + if (0 == service_is_dependency (dependency, servicename, type)) + { + EWARN (" Services '%s' and '%s' have circular\n", + servicename, dependency); + EWARN (" dependency of type '%s'; continuing...\n", + service_type_names[type]); + + /* For now remove this dependency */ + goto remove; + } + + /* Reverse mapping */ + if (type == NEED) + { + retval = service_add_dependency (dependency, servicename, NEED_ME); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + } + + /* Reverse mapping */ + if (type == USE) + { + retval = service_add_dependency (dependency, servicename, USE_ME); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + } + + /* Reverse mapping */ + if (type == BEFORE) + { + retval = service_add_dependency (dependency, servicename, AFTER); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + } + + /* Reverse mapping */ + if (type == AFTER) + { + retval = service_add_dependency (dependency, servicename, BEFORE); + if (-1 == retval) + { + DBG_MSG ("Failed to add dependency!\n"); + return -1; + } + } + } + + return 0; + +remove: + /* Delete invalid entry */ + DBG_MSG ("Removing invalid dependency '%s' of service '%s', type '%s'.\n", + dependency, servicename, service_type_names[type]); + + retval = service_del_dependency (servicename, dependency, type); + if (-1 == retval) + { + DBG_MSG ("Failed to delete dependency!\n"); + return -1; + } + + /* Here we should not die with error */ + return 0; +} + +int +service_resolve_dependencies (void) +{ + service_info_t *info; + char *service = NULL; + char *next = NULL; + int count; + + /* Add our 'net' service */ + if (NULL == service_get_info ("net")) + { + if (-1 == service_add ("net")) + { + DBG_MSG ("Failed to add virtual!\n"); + return -1; + } + service_set_mtime ("net", 0); + } + + /* Calculate all virtuals */ + list_for_each_entry (info, &service_info_list, node) + { + str_list_for_each_item_safe (info->depend_info[PROVIDE], service, next, + count) + { + if (-1 == service_add_virtual (info->name, service)) + { + DBG_MSG ("Failed to add virtual!\n"); + return -1; + } + } + } + + /* Now do NEED, USE, BEFORE and AFTER */ + list_for_each_entry (info, &service_info_list, node) + { + str_list_for_each_item_safe (info->depend_info[NEED], service, next, count) + { + if (-1 == __service_resolve_dependency (info->name, service, NEED)) + { + DBG_MSG ("Failed to resolve dependency!\n"); + return -1; + } + } + } + list_for_each_entry (info, &service_info_list, node) + { + str_list_for_each_item_safe (info->depend_info[USE], service, next, count) + { + if (-1 == __service_resolve_dependency (info->name, service, USE)) + { + DBG_MSG ("Failed to resolve dependency!\n"); + return -1; + } + } + } + list_for_each_entry (info, &service_info_list, node) + { + str_list_for_each_item_safe (info->depend_info[BEFORE], service, next, + count) + { + if (-1 == __service_resolve_dependency (info->name, service, BEFORE)) + { + DBG_MSG ("Failed to resolve dependency!\n"); + return -1; + } + } + } + list_for_each_entry (info, &service_info_list, node) + { + str_list_for_each_item_safe (info->depend_info[AFTER], service, next, count) + { + if (-1 == __service_resolve_dependency (info->name, service, AFTER)) + { + DBG_MSG ("Failed to resolve dependency!\n"); + return -1; + } + } + } + + return 0; +} |