// $Id: minit-svc.c,v 1.4 2003/08/15 10:14:21 ensc Exp $    --*- c++ -*--

// Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
//  
// 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.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#define _GNU_SOURCE

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <assert.h>
#include <alloca.h>
#include <stdio.h>

#define WRITEMSG(FD,X)		(void)write((FD), (X), sizeof(X)-1)

typedef enum { posTOP, posBOTTOM, posBEFORE, posAFTER }		PositionType;
typedef enum { sucUNDECIDED, sucCHANGED, sucUNCHANGED }		SuccessType;

extern char **split(char *buf,int c,int *len,int plus,int ofs);                                                                                                 
extern int openreadclose(char const *fn, char **buf, size_t *len);

static void
addLine(char **buf, size_t *len, char const *line)
{
  int	l = strlen(line);
  
  memcpy(*buf, line, l);
  *buf += l+1;
  *len += l+1;

  (*buf)[-1] = '\n';
}

static SuccessType
removeService(char *buf, size_t *len, char * const *deps, size_t depc,
	      char const *service)
{
  size_t	i;
  bool		found = false;

  *len = 0;

  for (i=0; i<depc; ++i) {
    if (strcmp(deps[i], service)==0) found=true;
    else addLine(&buf, len, deps[i]);
  }

  if (found) return sucCHANGED;
  else       return sucUNCHANGED;
}

static SuccessType
addService(char *buf, size_t *len, char * const *deps, size_t depc,
	   char const *service, PositionType pos, char const *pos_arg)
{
  size_t	i;
  bool		found = false;

  for (i=0; i<depc; ++i) {
    if (deps[i][0]=='#') continue;

    if (strcmp(deps[i], service)==0) found=true;
  }

  if (found) return sucUNCHANGED;


  found = false;
  *len  = 0;

  if (pos==posTOP) { addLine(&buf, len, service); found=true; }

  for (i=0; i<depc; ++i) {
    switch (pos) {
      case posBEFORE	:
      case posAFTER	:
	if (found || strcmp(deps[i], pos_arg)!=0)
	  addLine(&buf, len, deps[i]);
	else {
	  if (pos==posBEFORE) addLine(&buf, len, service);
	  addLine(&buf, len, deps[i]);
	  if (pos==posAFTER)  addLine(&buf, len, service);

	  found = true;
	}

	break;

      default		:
	addLine(&buf, len, deps[i]);
	break;
    }
  }

  if (pos==posBOTTOM) { addLine(&buf, len, service); found=true; }

  if (found) return sucCHANGED;
  else       return sucUNDECIDED;
}


static inline void
usage(int fd, char const *cmd)
{
  WRITEMSG(fd,
	   "minit-svc 0.1 -- manages minit services\n"
	   "\n"
	   "Usage:\n    ");
  write(fd, cmd, strlen(cmd));
  WRITEMSG(fd,
	   " add|del <service> <depends> <position>*\n"
	   "\n"
	   "        add          ...  add <service> to <depends> file\n"
	   "        del          ...  remove <service> from <depends> file\n"
	   "        <service>    ...  minit service-name\n"
	   "        <depends>    ...  'depends' file to be modified; this file\n"
	   "                          must exists before calling this program. When\n"
	   "                          it is not a absolute or relative path, the file\n"
	   "                          will be searched in " MINIT_DIR ".\n"
	   "                          When it ends on '/', the 'depends' string will\n"
	   "                          be appended automatically\n"
	   "        <position>*  ...  position for added services (optional)\n"
	   "\n"
	   "    When the <service> exists already in <depends>, 'add' will return\n"
	   "    without modifying <depends>. Else, the <service> will be added\n"
	   "    accordingly the given position(s).\n"
	   "\n"
	   "    Possible values for <position> are:\n"
	   "        top          ...  add <service> at the top of <depends>\n"
	   "        bottom       ...  add <service> at the bottom of <depends>\n"
	   "        before <svc> ...  add <service> before service <svc>\n"
	   "        after <svc>  ...  add <service> after service <svc>\n"
	   "\n"
	   "    When no <position> is specified, 'bottom' will be assumed. Else,\n"
	   "    the program tries all given positions until it finds a fitting\n"
	   "    one. If no such position exists, the program fails.\n"
	   "\n"
	   "Please report errors to <enrico.scholz@informatik.tu-chemnitz.de>.\n");
}

static int
writeFile(char const *fname, struct stat *stat, void const *buf, size_t len)
{
  char	new_name[strlen(fname) + 5];
  int	fd;
      
  strcpy(new_name, fname);
  strcat(new_name, ".bak");

  if (rename(fname, new_name)==-1) {
    WRITEMSG(2, "Failed to create the backup-file.\n");
    return 1;
  }
      
  fd = open(fname, O_WRONLY|O_CREAT, stat->st_mode);
  if (fd==-1) {
    WRITEMSG(2, "Failed to open/create the depends-file.\n");
    return 1;
  }

  if ((ssize_t)(len)!=write(fd, buf, len) ||
      fchown(fd, stat->st_uid, stat->st_gid)==-1 ||
      close(fd)==-1) {
    rename(new_name, fname);
    close(fd);

    WRITEMSG(2, "Failed to write/close the depends-file.\n");
    return 1;
  }

  return 0;
}

int main(int argc, char *argv[])
{
    // HACK: Because we access argv[2/3] below, make sure that there are enough params. In C99 the
    // questionable declarations could be moved after the check, but C89 compilers do not allow it.
  size_t		len = ( argc<4 ? (usage(2, argv[0]), exit(1), 0) : 0 );
    // HACK: this declaration is causing the trick above
  char const * const	service_name = argv[2];

  enum {tpADD, tpDEL}	operation;
  char			*buf = 0;
  char			**deps;
  int			depc;
  SuccessType		success;
  char			*result;
  struct stat		depfile_stat;

  char *		depends_name;
  bool			is_absolute, is_depends;
  size_t		depfile_len, alloca_len=0;

  depfile_len  = strlen(argv[3]);
  is_depends   =  depfile_len==0 || argv[3][depfile_len-1]!='/';
  is_absolute  = (depfile_len==0 || argv[3][0]=='/' ||
		  (argv[3][0]=='.' && argv[3][1]=='/') ||
		  (argv[3][0]=='.' && argv[3][1]=='.' && argv[3][2]=='/'));

  if (!is_absolute) alloca_len += sizeof(MINIT_DIR "/")-1;
  if (!is_depends)  alloca_len += sizeof("depends")-1;

  depends_name    = alloca_len!=0 ? alloca(depfile_len+alloca_len + 1) : argv[3];
  if (alloca_len!=0) depends_name[0] = '\0';
  if (!is_absolute)  strcat(depends_name, MINIT_DIR "/");
  if (alloca_len!=0) strcat(depends_name, argv[3]);
  if (!is_depends)   strcat(depends_name, "depends");

  
  if      (strcmp(argv[1], "add")==0) operation=tpADD;
  else if (strcmp(argv[1], "del")==0) operation=tpDEL;
  else {
    WRITEMSG(2, "Unknown operation; only 'add' or 'del' allowed.\n");
    exit(1);
  }

  if (stat(depends_name, &depfile_stat)==-1 ||
      !S_ISREG(depfile_stat.st_mode)) {
    WRITEMSG(2, "depends-file could not be stat'ed or is not a regular file\n");
    exit(1);
  }
  
  if (openreadclose(depends_name, &buf, &len)==-1) {
    WRITEMSG(2, "Failed to open/read depends-file.\n");
    exit(1);
  }


    // HACK: split does not handle empty strings correctly and returns depc==1
  if (len==0) { deps=(void *)0xdeadbeaf; depc=0; }
  else {
      // HACK: split() will split "x\n" into ("x", "")
    if (buf[len-1]=='\n') buf[len-1]='\0';

    deps = split(buf, '\n', &depc, 0,0);
  }


  result = alloca(len + (operation==tpADD ? (strlen(service_name)+1) : 0) + 1);
  
  if (operation==tpDEL) success = removeService(result, &len, deps, depc, service_name);
  else {
    char * const *	ptr = argv + 4;
    assert(operation==tpADD);

    do {
      PositionType	pos;
      
      if      (argc==4)                   pos=posBOTTOM;
      else if (strcmp(*ptr, "bottom")==0) pos=posBOTTOM;
      else if (strcmp(*ptr, "top"   )==0) pos=posTOP;
      else if (strcmp(*ptr, "before")==0) pos=posBEFORE;
      else if (strcmp(*ptr, "after" )==0) pos=posAFTER;
      else {
	WRITEMSG(2, "Unknown position specifier.\n");
	exit(1);
      }

      switch (pos) {
	case posBEFORE	:
	case posAFTER	:
	  ++ptr;
	  if (*ptr==0) {
	    WRITEMSG(2, "Missing argument for position specifier.\n");
	    exit(1);
	  }
	  break;
	default		:  break;
      }

      success = addService(result, &len, deps, depc, service_name, pos, *ptr);
      ++ptr;
    } while ((success==sucUNDECIDED) && *ptr!=0);
  }

  switch (success) {
    case sucUNDECIDED	:
      WRITEMSG(2, "Could not find the specified position(s).\n");
      exit(1);
    case sucUNCHANGED	:  return 0;
    case sucCHANGED	:  return writeFile(depends_name, &depfile_stat, result, len);
    default		:  assert(false); exit(1);
  }
}

  /// Local Variables:
  /// compile-command: "diet gcc -Wall -W -pedantic -std=c99 -g3 -O0 -DMINIT_DIR=\\\"/tmp\\\" split.c openreadclose.c minit-svc.c -ominit-svc"
  /// End:
