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


#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/param.h>
#include "startd.h"

/*
 * The service deathrow mechanism addresses the problem of removing services
 * from a non accessible SMF repository. In this case, we can't simply use the
 * "SVCCFG_REPOSITORY=$ROOT/etc/svc/repository.db svccfg delete service_fmri"
 * command as the alternate repository format is not committed and could be
 * incompatible with the local SMF commands version.
 *
 * The idea is to manage a file (/etc/svc/deathrow) on the alternate root
 * directory that lists the FMRIs that need to disappear from the repository
 * when the system that uses this root directory boots up.
 * r.manifest and i.manifest update the file /etc/svc/deathrow in the alternate
 * root case.
 *
 * When svc.startd daemon launches, it first reads the /etc/svc/deathrow file
 * and for all FMRIs listed in this file, the service is not configured and
 * dependencies on it are forced satisfied (during svc.startd init time only).
 *
 * Than manifest-import service will actually, as first task, delete the
 * unconfigured services found in the /etc/svc/deathrow file and the
 * manifest hash entry from the repository.
 *
 */

#define	SVC_DEATHROW_FILE	"/etc/svc/deathrow"

/*
 * These data structures are unprotected because they
 * are modified by a single thread, at startup time.
 * After initialization, these data structures are
 * used only in read mode, thus requiring no protection.
 */

/* list of deathrow fmris, created from the file SVC_DEATHROW_FILE */
typedef struct deathrow {
    char *fmri;
    uu_list_node_t deathrow_link;
} deathrow_t;

static uu_list_pool_t *deathrow_pool;
static uu_list_t *deathrow_list;

static boolean_t deathrow_handling_status = B_FALSE;

static deathrow_t *fmri_in_deathrow_internal(const char *);
static void deathrow_add(const char *);

static void
deathrow_handling_start()
{
	assert(deathrow_handling_status == B_FALSE);
	deathrow_handling_status = B_TRUE;
}

static void
deathrow_handling_stop()
{
	assert(deathrow_handling_status == B_TRUE);
	deathrow_handling_status = B_FALSE;
}

void
deathrow_init()
{
	FILE *file;
	char *line;
	char *fmri;
	char *manifest;
	char *pkgname;
	size_t line_size, sz;
	unsigned int line_parsed = 0;

	log_framework(LOG_DEBUG, "Deathrow init\n");

	while ((file = fopen(SVC_DEATHROW_FILE, "r")) == NULL) {
		if (errno == EINTR) {
			continue;
		}
		if (errno != ENOENT) {
			log_framework(LOG_ERR,
			    "Deathrow not processed. "
			    "Error opening file (%s): %s\n",
			    SVC_DEATHROW_FILE, strerror(errno));
		}
		return;
	}

	deathrow_pool = uu_list_pool_create("deathrow",
	    sizeof (deathrow_t), offsetof(deathrow_t, deathrow_link),
	    NULL, UU_LIST_POOL_DEBUG);
	if (deathrow_pool == NULL) {
		uu_die("deathrow_init couldn't create deathrow_pool");
	}

	deathrow_list = uu_list_create(deathrow_pool,  deathrow_list, 0);
	if (deathrow_list == NULL) {
		uu_die("deathrow_init couldn't create deathrow_list");
	}

	/*
	 * A deathrow file line looks like:
	 * <fmri>< ><manifest path>< ><package name><\n>
	 * (field separator is a space character)
	 */
	line_size = max_scf_fmri_size + 3 + MAXPATHLEN + MAXNAMELEN;
	line = (char *)startd_alloc(line_size);
	*line = '\0';

	while (fgets(line, line_size, file) != NULL) {
		line_parsed++;
		fmri = NULL;
		manifest = NULL;
		pkgname = NULL;
		sz = strlen(line);
		if (sz > 0) {
			/* remove linefeed */
			if (line[sz - 1] == '\n') {
				line[sz - 1] = '\0';
			}
			manifest = strchr(line, ' ');
			if (manifest != NULL) {
				fmri = line;
				*manifest = '\0';
				manifest++;
				pkgname = strchr(manifest, ' ');
				if (pkgname != NULL) {
					*pkgname = '\0';
					pkgname++;
				}
			}
		}
		if (fmri != NULL && strlen(fmri) > 0 &&
		    strlen(fmri) < max_scf_fmri_size &&
		    manifest != NULL && strlen(manifest) > 0 &&
		    pkgname != NULL && strlen(pkgname) > 0) {
			log_framework(LOG_DEBUG,
			    "Deathrow parser <%s><%s><%s>\n",
			    fmri, manifest, pkgname);
			if (fmri_in_deathrow_internal(fmri) == NULL) {
				/* fmri is not in list, add fmri */
				deathrow_add(fmri);
			}
		} else {
			log_framework(LOG_ERR,
			    "Deathrow error processing file (%s). "
			    "Skipping line %u.\n",
			    SVC_DEATHROW_FILE, line_parsed);
		}
		*line = '\0';
	}
	startd_free(line, line_size);
	(void) fclose(file);

	if (uu_list_first(deathrow_list) != NULL) {
		deathrow_handling_start();
	}
}

void
deathrow_fini()
{
	deathrow_t *d;
	void *cookie = NULL;

	if (deathrow_handling_status == B_FALSE) {
		log_framework(LOG_DEBUG, "Deathrow fini\n");
		return;
	}
	deathrow_handling_stop();

	while ((d = uu_list_teardown(deathrow_list, &cookie)) != NULL) {
		startd_free(d->fmri, strlen(d->fmri) + 1);
		startd_free(d, sizeof (deathrow_t));
	}

	uu_list_destroy(deathrow_list);
	uu_list_pool_destroy(deathrow_pool);
	deathrow_pool = NULL;
	deathrow_list = NULL;
	log_framework(LOG_DEBUG, "Deathrow fini\n");
}

static void
deathrow_add(const char *fmri)
{
	deathrow_t *d;

	assert(fmri != NULL);

	d = startd_alloc(sizeof (deathrow_t));
	d->fmri = startd_alloc(strlen(fmri) + 1);
	(void) strcpy(d->fmri, fmri);
	uu_list_node_init(d, &d->deathrow_link, deathrow_pool);
	(void) uu_list_insert_after(deathrow_list, NULL, d);

	log_framework(LOG_DEBUG, "Deathrow added <%s>\n", d->fmri);
}

static deathrow_t *
fmri_in_deathrow_internal(const char *fmri)
{
	deathrow_t *d;

	assert(fmri != NULL);
	assert(deathrow_pool != NULL);
	assert(deathrow_list != NULL);

	for ((d = uu_list_first(deathrow_list)); d != NULL;
	    d = uu_list_next(deathrow_list, d)) {
		if (strcmp(fmri, d->fmri) == 0) {
			return (d);
		}
	}
	return (NULL);
}

boolean_t
is_fmri_in_deathrow(const char *fmri)
{
	if (deathrow_handling_status == B_FALSE) {
		return (B_FALSE);
	}
	return ((fmri_in_deathrow_internal(fmri) != NULL) ? B_TRUE : B_FALSE);
}