<commit_before>/*
 *  module-logic-v4.c
 *  einit
 *
 *  Created by <NAME> on 09/04/2007.
 *  Copyright 2006, 2007 <NAME>. All rights reserved.
 *
 */

/*
Copyright (c) 2006, 2007, <NAME>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
	  this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
	  this list of conditions and the following disclaimer in the documentation
	  and/or other materials provided with the distribution.
    * Neither the name of the project nor the names of its contributors may be
	  used to endorse or promote products derived from this software without
	  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <einit/config.h>
#include <einit/module-logic.h>
#include <einit/module.h>
#include <einit/tree.h>
#include <pthread.h>
#include <string.h>
#include <einit/bitch.h>
#include <einit-modules/ipc.h>
#include <einit-modules/configuration.h>

#ifdef _POSIX_PRIORITY_SCHEDULING
#include <sched.h>
#endif

#define MAX_ITERATIONS 1000

int einit_module_logic_v3_configure (struct lmodule *);

#if defined(EINIT_MODULE) || defined(EINIT_MODULE_HEADER)
const struct smodule einit_module_logic_v3_self = {
 .eiversion = EINIT_VERSION,
 .eibuild   = BUILDNUMBER,
 .version   = 1,
 .mode      = 0,
 .name      = "Module Logic Core (V3)",
 .rid       = "module-logic-v3",
 .si        = {
  .provides = NULL,
  .requires = NULL,
  .after    = NULL,
  .before   = NULL
 },
 .configure = einit_module_logic_v3_configure
};

module_register(einit_module_logic_v3_self);

#endif

char shutting_down = 0;

void module_logic_ipc_event_handler (struct einit_event *);
void module_logic_einit_event_handler (struct einit_event *);
double mod_get_plan_progress_f (struct mloadplan *);
char initdone = 0;
char mod_isbroken (char *service);
char mod_mark (char *service, char task);
struct group_data *mod_group_get_data (char *group);

/* new functions: */
char mod_examine_group (char *);
void mod_examine_module (struct lmodule *);
void mod_examine (char *);

void mod_commit_and_wait (char **, char **);

struct module_taskblock
  current = { NULL, NULL, NULL },
  target_state = { NULL, NULL, NULL };

struct stree *module_logics_service_list = NULL; // value is a (struct lmodule **)
struct stree *module_logics_group_data = NULL;

pthread_mutex_t
  ml_tb_current_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_tb_target_state_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_service_list_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_group_data_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_unresolved_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_currently_provided_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_service_update_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t
  ml_cond_service_update = PTHREAD_COND_INITIALIZER;

char **unresolved_services = NULL;
char **broken_services = NULL;

struct group_data {
 char **members;
 uint32_t options;
};

#define MOD_PLAN_GROUP_SEQ_ANY     0x00000001
#define MOD_PLAN_GROUP_SEQ_ALL     0x00000002
#define MOD_PLAN_GROUP_SEQ_ANY_IOP 0x00000004
#define MOD_PLAN_GROUP_SEQ_MOST    0x00000008

#define MARK_BROKEN                0x01
#define MARK_UNRESOLVED            0x02

/* module header functions */

int einit_module_logic_v3_cleanup (struct lmodule *this) {
 function_unregister ("module-logic-get-plan-progress", 1, mod_get_plan_progress_f);

 event_ignore (einit_event_subsystem_core, module_logic_einit_event_handler);
 event_ignore (einit_event_subsystem_ipc, module_logic_ipc_event_handler);

 return 0;
}

int einit_module_logic_v3_configure (struct lmodule *this) {
 module_init(this);

 thismodule->cleanup = einit_module_logic_v3_cleanup;

 event_listen (einit_event_subsystem_ipc, module_logic_ipc_event_handler);
 event_listen (einit_event_subsystem_core, module_logic_einit_event_handler);

 function_register ("module-logic-get-plan-progress", 1, mod_get_plan_progress_f);

 return 0;
}

/* end module header */
/* start common functions with v3 */

char mod_isbroken (char *service) {
 int retval = 0;

 emutex_lock (&ml_unresolved_mutex);

 retval = inset ((const void **)broken_services, (void *)service, SET_TYPE_STRING) ||
   inset ((const void **)unresolved_services, (void *)service, SET_TYPE_STRING);

 emutex_unlock (&ml_unresolved_mutex);

 return retval;
}

struct group_data *mod_group_get_data (char *group) {
 struct group_data *ret = NULL;

/* eputs ("mod_group_get_data", stderr);
 fflush (stderr);*/

 emutex_lock (&ml_group_data_mutex);

/* eputs ("got mutex", stderr);
 fflush (stderr);*/

 struct stree *cur = module_logics_group_data ? streefind (module_logics_group_data, group, tree_find_first) : NULL;
 if (cur) { ret = (struct group_data *)cur->value; }
 else {
  char *tnodeid = emalloc (strlen (group)+17);
  struct cfgnode *gnode = NULL;

  memcpy (tnodeid, "services-alias-", 16);
  strcat (tnodeid, group);

  ret = ecalloc (1, sizeof (struct group_data));

  if ((gnode = cfg_getnode (tnodeid, NULL)) && gnode->arbattrs) {
   ssize_t r = 0;

   for (r = 0; gnode->arbattrs[r]; r+=2) {
    if (strmatch (gnode->arbattrs[r], "group")) {
     ret->members = str2set (':', gnode->arbattrs[r+1]);
    } else if (strmatch (gnode->arbattrs[r], "seq")) {
     if (strmatch (gnode->arbattrs[r+1], "any"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ANY;
     else if (strmatch (gnode->arbattrs[r+1], "all"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ALL;
     else if (strmatch (gnode->arbattrs[r+1], "any-iop"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ANY_IOP;
     else if (strmatch (gnode->arbattrs[r+1], "most"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_MOST;
    }
   }
  }
  free (tnodeid);

  if (!ret->members || !ret->options) {
   free (ret);
   ret = NULL;
  } else {
   module_logics_group_data = streeadd (module_logics_group_data, group, (void *)ret, SET_NOALLOC, (void *)ret);
  }
 }

 emutex_unlock (&ml_group_data_mutex);

 return ret;
}

void cross_taskblock (struct module_taskblock *source, struct module_taskblock *target) {
 if (source->enable) {
  char **tmp = (char **)setcombine ((const void **)target->enable, (const void **)source->enable, SET_TYPE_STRING);
  if (target->enable) free (target->enable);
  target->enable = tmp;

  tmp = (char **)setslice ((const void **)target->disable, (const void **)source->enable, SET_TYPE_STRING);
  if (target->disable) free (target->disable);
  target->disable = tmp;
 }
 if (source->critical) {
  char **tmp = (char **)setcombine ((const void **)target->critical, (const void **)source->critical, SET_TYPE_STRING);
  if (target->critical) free (target->critical);
  target->critical = tmp;
 }

 if (source->disable) {
  char **tmp = (char **)setcombine ((const void **)target->disable, (const void **)source->disable, SET_TYPE_STRING);
  if (target->disable) free (target->disable);
  target->disable = tmp;

  tmp = (char **)setslice ((const void **)target->enable, (const void **)source->disable, SET_TYPE_STRING);
  if (target->enable) free (target->enable);
  target->enable = tmp;

  tmp = (char **)setslice ((const void **)target->critical, (const void **)source->disable, SET_TYPE_STRING);
  if (target->critical) free (target->critical);
  target->critical = tmp;
 }
}

struct mloadplan *mod_plan (struct mloadplan *plan, char **atoms, unsigned int task, struct cfgnode *mode) {
 char disable_all_but_feedback = 0, disable_all = 0, auto_add_feedback = 0;

 if (!plan) {
  plan = emalloc (sizeof (struct mloadplan));
  memset (plan, 0, sizeof (struct mloadplan));
 }

 if (mode) {
  char **base = NULL;
  uint32_t xi = 0;
  char **enable   = str2set (':', cfg_getstring ("enable/services", mode));
  char **disable  = str2set (':', cfg_getstring ("disable/services", mode));
  char **critical = str2set (':', cfg_getstring ("enable/critical", mode));
  char *strng = cfg_getstring ("feedback/auto-add", mode);

  if ((strng = cfg_getstring ("feedback/auto-add", mode)))
   auto_add_feedback = parse_boolean(strng);
  if ((strng = cfg_getstring ("options/shutdown", mode)) && parse_boolean(strng)) {
   plan->options |= plan_option_shutdown;
  }

  if (!enable)
   enable  = str2set (':', cfg_getstring ("enable/mod", mode));
  if (!disable)
   disable = str2set (':', cfg_getstring ("disable/mod", mode));

  if (mode->arbattrs) for (; mode->arbattrs[xi]; xi+=2) {
   if (strmatch(mode->arbattrs[xi], "base")) {
    base = str2set (':', mode->arbattrs[xi+1]);
   }
  }

  if (enable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.enable, (const void **)enable, SET_TYPE_STRING);
   if (plan->changes.enable) free (plan->changes.enable);
   plan->changes.enable = tmp;
  }
  if (disable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.disable, (const void **)disable, SET_TYPE_STRING);
   if (plan->changes.disable) free (plan->changes.disable);
   plan->changes.disable = tmp;
  }
  if (critical) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.critical, (const void **)critical, SET_TYPE_STRING);
   if (plan->changes.critical) free (plan->changes.critical);
   plan->changes.critical = tmp;
  }

  if (auto_add_feedback && (strng = cfg_getstring("feedback/default", mode))) {
   if (plan->changes.enable) {
    uint32_t y = 0;

    for (; plan->changes.enable[y]; y++) {
     struct stree *st = NULL;

     emutex_lock (&ml_service_list_mutex);

     if ((st = streefind (module_logics_service_list, plan->changes.enable[y], tree_find_first))) {
      struct lmodule **lmod = st->value;

      if (lmod[0] && lmod[0]->module && lmod[0]->module->mode & einit_module_feedback) {
       emutex_unlock (&ml_service_list_mutex);
       goto have_feedback;
      }
     }

     emutex_unlock (&ml_service_list_mutex);
    }
   }

   plan->changes.enable = (char **)setadd ((void **)plan->changes.enable, (void *)strng, SET_TYPE_STRING);
  }
  have_feedback:

  if (base) {
   int y = 0;
   struct cfgnode *cno;
   while (base[y]) {
    if (!inset ((const void **)plan->used_modes, (void *)base[y], SET_TYPE_STRING)) {
     cno = cfg_findnode (base[y], einit_node_mode, NULL);
     if (cno) {
      plan = mod_plan (plan, NULL, 0, cno);
     }
    }

    y++;
   }

   free (base);
  }

  if (mode->id) {
   plan->used_modes = (char **)setadd ((void **)plan->used_modes, mode->id, SET_TYPE_STRING);
  }

  plan->mode = mode;
 } else {
  if (task & einit_module_enable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.enable, (const void **)atoms, SET_TYPE_STRING);
   if (plan->changes.enable) free (plan->changes.enable);
   plan->changes.enable = tmp;
  }
  if (task & einit_module_disable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.disable, (const void **)atoms, SET_TYPE_STRING);
   if (plan->changes.disable) free (plan->changes.disable);
   plan->changes.disable = tmp;
  }
 }

 disable_all = inset ((const void **)plan->changes.disable, (void *)"all", SET_TYPE_STRING);
 disable_all_but_feedback = inset ((const void **)plan->changes.disable, (void *)"all-but-feedback", SET_TYPE_STRING);

 if (disable_all || disable_all_but_feedback) {
  struct stree *cur;
  ssize_t i = 0;
  char **tmpy = service_usage_query_cr (service_is_provided, NULL, NULL);

  emutex_lock (&ml_service_list_mutex);
  emutex_lock (&ml_tb_target_state_mutex);
//  char **tmpx = (char **)setcombine ((const void **)plan->changes.enable, (const void **)target_state.enable, SET_TYPE_STRING);
  char **tmpx = (char **)setcombine ((const void **)tmpy, (const void **)target_state.enable, SET_TYPE_STRING);

  emutex_unlock (&ml_tb_target_state_mutex);

  char **tmp = (char **)setcombine ((const void **)tmpx, (const void **)plan->changes.disable, SET_TYPE_STRING);

  free (tmpx);
  free (tmpy);

  if (plan->changes.disable) {
   free (plan->changes.disable);
   plan->changes.disable = NULL;
  }

  if (tmp) {
   for (; tmp[i]; i++) {
    char add = 1;

    if (inset ((const void **)plan->changes.disable, (void *)tmp[i], SET_TYPE_STRING)) {
     add = 0;
    } else if ((disable_all && strmatch(tmp[i], "all")) ||
               (disable_all_but_feedback && strmatch(tmp[i], "all-but-feedback"))) {
     add = 0;
    } else if (module_logics_service_list && (cur = streefind (module_logics_service_list, tmp[i], tree_find_first))) {
     struct lmodule **lm = (struct lmodule **)cur->value;
     if (lm) {
      ssize_t y = 0;
      for (; lm[y]; y++) {
       if (disable_all_but_feedback && (lm[y]->module->mode & einit_module_feedback)) {
        add = 0;

        break;
       }
      }
     }
    } else if (!service_usage_query (service_is_provided, NULL, tmp[i])) {
     add = 0;
    }

    if (add) {
     plan->changes.disable = (char **)setadd((void **)plan->changes.disable, (void *)tmp[i], SET_TYPE_STRING);
    }
   }

   free (tmp);
  }

  emutex_unlock (&ml_service_list_mutex);
 }

 return plan;
}

unsigned int mod_plan_commit (struct mloadplan *plan) {
 struct einit_event *fb = evinit (einit_feedback_plan_status);

 if (!plan) return 0;

 if (plan->options & plan_option_shutdown)
  shutting_down = 1;

// do some extra work if the plan was derived from a mode
 if (plan->mode) {
  char *cmdt;
  cmode = plan->mode;

  struct einit_event eex = evstaticinit (einit_core_mode_switching);
  eex.para = (void *)plan->mode;
  event_emit (&eex, einit_event_flag_broadcast);
  evstaticdestroy (eex);

  if ((cmdt = cfg_getstring ("before-switch/emit-event", cmode))) {
   struct einit_event ee = evstaticinit (event_string_to_code(cmdt));
   event_emit (&ee, einit_event_flag_broadcast);
   evstaticdestroy (ee);
  }

  if ((cmdt = cfg_getstring ("before-switch/ipc", cmode))) {
   char **cmdts = str2set (':', cmdt);
   uint32_t in = 0;

   if (cmdts) {
    for (; cmdts[in]; in++)
     ipc_process(cmdts[in], stderr);

    free (cmdts);
   }
  }
 }

 fb->task = MOD_SCHEDULER_PLAN_COMMIT_START;
 fb->para = (void *)plan;
 status_update (fb);

 emutex_lock (&ml_tb_target_state_mutex);

 cross_taskblock (&(plan->changes), &target_state);

 emutex_lock (&ml_tb_current_mutex);

 cross_taskblock (&target_state, &current);

 uint32_t i = 0;

 if (current.enable) {
  char **tmp = NULL;
  for (i = 0; current.enable[i]; i++) {
   if (!service_usage_query (service_is_provided, NULL, current.enable[i])) {
    tmp = (char **)setadd ((void **)tmp, (void *)current.enable[i], SET_TYPE_STRING);
   }
  }
  free (current.enable);
  current.enable = tmp;
 }
 if (current.disable) {
  char **tmp = NULL;
  for (i = 0; current.disable[i]; i++) {
   if (service_usage_query (service_is_provided, NULL, current.disable[i])) {
    tmp = (char **)setadd ((void **)tmp, (void *)current.disable[i], SET_TYPE_STRING);
   }
  }
  free (current.disable);
  current.disable = tmp;
 }

 emutex_unlock (&ml_tb_current_mutex);
 emutex_unlock (&ml_tb_target_state_mutex);

 mod_commit_and_wait (plan->changes.enable, plan->changes.disable);

 fb->task = MOD_SCHEDULER_PLAN_COMMIT_FINISH;
 status_update (fb);

// do some more extra work if the plan was derived from a mode
 if (plan->mode) {
  char *cmdt;
  cmode = plan->mode;
  amode = plan->mode;

  struct einit_event eex = evstaticinit (einit_core_mode_switch_done);
  eex.para = (void *)plan->mode;
  event_emit (&eex, einit_event_flag_broadcast);
  evstaticdestroy (eex);

  if (amode->id) {
   struct einit_event eema = evstaticinit (einit_core_plan_update);
   eema.string = estrdup(amode->id);
   eema.para   = (void *)amode;
   event_emit (&eema, einit_event_flag_broadcast);
   free (eema.string);
   evstaticdestroy (eema);
  }

  if ((cmdt = cfg_getstring ("after-switch/emit-event", amode))) {
   struct einit_event ee = evstaticinit (event_string_to_code(cmdt));
   event_emit (&ee, einit_event_flag_broadcast);
   evstaticdestroy (ee);
  }

  if ((cmdt = cfg_getstring ("after-switch/ipc", amode))) {
   char **cmdts = str2set (':', cmdt);
   uint32_t in = 0;

   if (cmdts) {
    for (; cmdts[in]; in++) {
     ipc_process(cmdts[in], stderr);
    }
    free (cmdts);
   }
  }
 }

 evdestroy (fb);

 if (plan->mode) return 0; // always return "OK" if it's based on a mode

 return 0;
}

int mod_plan_free (struct mloadplan *plan) {
 if (plan) {
  if (plan->changes.enable) free (plan->changes.enable);
  if (plan->changes.disable) free (plan->changes.disable);
  if (plan->changes.critical) free (plan->changes.critical);

  if (plan->used_modes) free (plan->used_modes);

  free (plan);
 }
 return 0;
}

double mod_get_plan_progress_f (struct mloadplan *plan) {
 if (plan) {
  return 0.0;
 } else {
  double all = 0, left = 0;
  emutex_lock (&ml_tb_target_state_mutex);
  if (target_state.enable) all += setcount ((const void **)target_state.enable);
  if (target_state.disable) all += setcount ((const void **)target_state.disable);
  emutex_unlock (&ml_tb_target_state_mutex);

  emutex_lock (&ml_tb_current_mutex);
  if (current.enable) left += setcount ((const void **)current.enable);
  if (current.disable) left += setcount ((const void **)current.disable);
  emutex_unlock (&ml_tb_current_mutex);

  return 1.0 - (double)(left / all);
 }
}

void mod_sort_service_list_items_by_preference() {
 struct stree *cur;

 emutex_lock (&ml_service_list_mutex);

 cur = module_logics_service_list;

 while (cur) {
  struct lmodule **lm = (struct lmodule **)cur->value;

  if (lm) {
   /* order modules that should be enabled according to the user's preference */
   uint32_t mpx, mpy, mpz = 0;
   char *pnode = NULL, **preference = NULL;

   /* first make sure all modules marked as "deprecated" are last */
   for (mpx = 0; lm[mpx]; mpx++); mpx--;
   for (mpy = 0; mpy < mpx; mpy++) {
    if (lm[mpy]->module && (lm[mpy]->module->mode & einit_module_deprecated)) {
     struct lmodule *t = lm[mpx];
     lm[mpx] = lm[mpy];
     lm[mpy] = t;
     mpx--;
    }
   }

   /* now to the sorting bit... */
   pnode = emalloc (strlen (cur->key)+18);
   pnode[0] = 0;
   strcat (pnode, "services-prefer-");
   strcat (pnode, cur->key);

   if ((preference = str2set (':', cfg_getstring (pnode, NULL)))) {
    debugx ("applying module preferences for service %s", cur->key);

    for (mpx = 0; preference[mpx]; mpx++) {
     for (mpy = 0; lm[mpy]; mpy++) {
      if (lm[mpy]->module && lm[mpy]->module->rid && strmatch(lm[mpy]->module->rid, preference[mpx])) {
       struct lmodule *tm = lm[mpy];

       lm[mpy] = lm[mpz];
       lm[mpz] = tm;

       mpz++;
      }
     }
    }
    free (preference);
   }

   free (pnode);
  }

  cur = streenext(cur);
 }

 emutex_unlock (&ml_service_list_mutex);
}

int mod_switchmode (char *mode) {
 if (!mode) return -1;
 struct cfgnode *cur = cfg_findnode (mode, einit_node_mode, NULL);
 struct mloadplan *plan = NULL;

 if (!cur) {
  notice (1, "module-logic-v3: scheduled mode \"%s\" not defined, aborting", mode);
  return -1;
 }

 plan = mod_plan (NULL, NULL, 0, cur);
 if (!plan) {
  notice (1, "module-logic-v3: scheduled mode \"%s\" defined but nothing to be done", mode);
 } else {
  pthread_t th;
  mod_plan_commit (plan);
  /* make it so that the erase operation will not disturb the flow of the program */
  ethread_create (&th, &thread_attribute_detached, (void *(*)(void *))mod_plan_free, (void *)plan);
 }

 return 0;
}

int mod_modaction (char **argv) {
 int argc = setcount ((const void **)argv), ret = 1;
 int32_t task = 0;
 struct mloadplan *plan;
 if (!argv || (argc != 2)) return -1;

 if (strmatch (argv[1], "enable")) task = einit_module_enable;
 else if (strmatch (argv[1], "disable")) task = einit_module_disable;
 else {<commit_msg>added start/stop as an alias to enable/disable<commit_after>/*
 *  module-logic-v4.c
 *  einit
 *
 *  Created by <NAME> on 09/04/2007.
 *  Copyright 2006, 2007 <NAME>. All rights reserved.
 *
 */

/*
Copyright (c) 2006, 2007, <NAME>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
	  this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
	  this list of conditions and the following disclaimer in the documentation
	  and/or other materials provided with the distribution.
    * Neither the name of the project nor the names of its contributors may be
	  used to endorse or promote products derived from this software without
	  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <einit/config.h>
#include <einit/module-logic.h>
#include <einit/module.h>
#include <einit/tree.h>
#include <pthread.h>
#include <string.h>
#include <einit/bitch.h>
#include <einit-modules/ipc.h>
#include <einit-modules/configuration.h>

#ifdef _POSIX_PRIORITY_SCHEDULING
#include <sched.h>
#endif

#define MAX_ITERATIONS 1000

int einit_module_logic_v3_configure (struct lmodule *);

#if defined(EINIT_MODULE) || defined(EINIT_MODULE_HEADER)
const struct smodule einit_module_logic_v3_self = {
 .eiversion = EINIT_VERSION,
 .eibuild   = BUILDNUMBER,
 .version   = 1,
 .mode      = 0,
 .name      = "Module Logic Core (V3)",
 .rid       = "module-logic-v3",
 .si        = {
  .provides = NULL,
  .requires = NULL,
  .after    = NULL,
  .before   = NULL
 },
 .configure = einit_module_logic_v3_configure
};

module_register(einit_module_logic_v3_self);

#endif

char shutting_down = 0;

void module_logic_ipc_event_handler (struct einit_event *);
void module_logic_einit_event_handler (struct einit_event *);
double mod_get_plan_progress_f (struct mloadplan *);
char initdone = 0;
char mod_isbroken (char *service);
char mod_mark (char *service, char task);
struct group_data *mod_group_get_data (char *group);

/* new functions: */
char mod_examine_group (char *);
void mod_examine_module (struct lmodule *);
void mod_examine (char *);

void mod_commit_and_wait (char **, char **);

struct module_taskblock
  current = { NULL, NULL, NULL },
  target_state = { NULL, NULL, NULL };

struct stree *module_logics_service_list = NULL; // value is a (struct lmodule **)
struct stree *module_logics_group_data = NULL;

pthread_mutex_t
  ml_tb_current_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_tb_target_state_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_service_list_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_group_data_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_unresolved_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_currently_provided_mutex = PTHREAD_MUTEX_INITIALIZER,
  ml_service_update_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t
  ml_cond_service_update = PTHREAD_COND_INITIALIZER;

char **unresolved_services = NULL;
char **broken_services = NULL;

struct group_data {
 char **members;
 uint32_t options;
};

#define MOD_PLAN_GROUP_SEQ_ANY     0x00000001
#define MOD_PLAN_GROUP_SEQ_ALL     0x00000002
#define MOD_PLAN_GROUP_SEQ_ANY_IOP 0x00000004
#define MOD_PLAN_GROUP_SEQ_MOST    0x00000008

#define MARK_BROKEN                0x01
#define MARK_UNRESOLVED            0x02

/* module header functions */

int einit_module_logic_v3_cleanup (struct lmodule *this) {
 function_unregister ("module-logic-get-plan-progress", 1, mod_get_plan_progress_f);

 event_ignore (einit_event_subsystem_core, module_logic_einit_event_handler);
 event_ignore (einit_event_subsystem_ipc, module_logic_ipc_event_handler);

 return 0;
}

int einit_module_logic_v3_configure (struct lmodule *this) {
 module_init(this);

 thismodule->cleanup = einit_module_logic_v3_cleanup;

 event_listen (einit_event_subsystem_ipc, module_logic_ipc_event_handler);
 event_listen (einit_event_subsystem_core, module_logic_einit_event_handler);

 function_register ("module-logic-get-plan-progress", 1, mod_get_plan_progress_f);

 return 0;
}

/* end module header */
/* start common functions with v3 */

char mod_isbroken (char *service) {
 int retval = 0;

 emutex_lock (&ml_unresolved_mutex);

 retval = inset ((const void **)broken_services, (void *)service, SET_TYPE_STRING) ||
   inset ((const void **)unresolved_services, (void *)service, SET_TYPE_STRING);

 emutex_unlock (&ml_unresolved_mutex);

 return retval;
}

struct group_data *mod_group_get_data (char *group) {
 struct group_data *ret = NULL;

/* eputs ("mod_group_get_data", stderr);
 fflush (stderr);*/

 emutex_lock (&ml_group_data_mutex);

/* eputs ("got mutex", stderr);
 fflush (stderr);*/

 struct stree *cur = module_logics_group_data ? streefind (module_logics_group_data, group, tree_find_first) : NULL;
 if (cur) { ret = (struct group_data *)cur->value; }
 else {
  char *tnodeid = emalloc (strlen (group)+17);
  struct cfgnode *gnode = NULL;

  memcpy (tnodeid, "services-alias-", 16);
  strcat (tnodeid, group);

  ret = ecalloc (1, sizeof (struct group_data));

  if ((gnode = cfg_getnode (tnodeid, NULL)) && gnode->arbattrs) {
   ssize_t r = 0;

   for (r = 0; gnode->arbattrs[r]; r+=2) {
    if (strmatch (gnode->arbattrs[r], "group")) {
     ret->members = str2set (':', gnode->arbattrs[r+1]);
    } else if (strmatch (gnode->arbattrs[r], "seq")) {
     if (strmatch (gnode->arbattrs[r+1], "any"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ANY;
     else if (strmatch (gnode->arbattrs[r+1], "all"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ALL;
     else if (strmatch (gnode->arbattrs[r+1], "any-iop"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_ANY_IOP;
     else if (strmatch (gnode->arbattrs[r+1], "most"))
      ret->options |=  MOD_PLAN_GROUP_SEQ_MOST;
    }
   }
  }
  free (tnodeid);

  if (!ret->members || !ret->options) {
   free (ret);
   ret = NULL;
  } else {
   module_logics_group_data = streeadd (module_logics_group_data, group, (void *)ret, SET_NOALLOC, (void *)ret);
  }
 }

 emutex_unlock (&ml_group_data_mutex);

 return ret;
}

void cross_taskblock (struct module_taskblock *source, struct module_taskblock *target) {
 if (source->enable) {
  char **tmp = (char **)setcombine ((const void **)target->enable, (const void **)source->enable, SET_TYPE_STRING);
  if (target->enable) free (target->enable);
  target->enable = tmp;

  tmp = (char **)setslice ((const void **)target->disable, (const void **)source->enable, SET_TYPE_STRING);
  if (target->disable) free (target->disable);
  target->disable = tmp;
 }
 if (source->critical) {
  char **tmp = (char **)setcombine ((const void **)target->critical, (const void **)source->critical, SET_TYPE_STRING);
  if (target->critical) free (target->critical);
  target->critical = tmp;
 }

 if (source->disable) {
  char **tmp = (char **)setcombine ((const void **)target->disable, (const void **)source->disable, SET_TYPE_STRING);
  if (target->disable) free (target->disable);
  target->disable = tmp;

  tmp = (char **)setslice ((const void **)target->enable, (const void **)source->disable, SET_TYPE_STRING);
  if (target->enable) free (target->enable);
  target->enable = tmp;

  tmp = (char **)setslice ((const void **)target->critical, (const void **)source->disable, SET_TYPE_STRING);
  if (target->critical) free (target->critical);
  target->critical = tmp;
 }
}

struct mloadplan *mod_plan (struct mloadplan *plan, char **atoms, unsigned int task, struct cfgnode *mode) {
 char disable_all_but_feedback = 0, disable_all = 0, auto_add_feedback = 0;

 if (!plan) {
  plan = emalloc (sizeof (struct mloadplan));
  memset (plan, 0, sizeof (struct mloadplan));
 }

 if (mode) {
  char **base = NULL;
  uint32_t xi = 0;
  char **enable   = str2set (':', cfg_getstring ("enable/services", mode));
  char **disable  = str2set (':', cfg_getstring ("disable/services", mode));
  char **critical = str2set (':', cfg_getstring ("enable/critical", mode));
  char *strng = cfg_getstring ("feedback/auto-add", mode);

  if ((strng = cfg_getstring ("feedback/auto-add", mode)))
   auto_add_feedback = parse_boolean(strng);
  if ((strng = cfg_getstring ("options/shutdown", mode)) && parse_boolean(strng)) {
   plan->options |= plan_option_shutdown;
  }

  if (!enable)
   enable  = str2set (':', cfg_getstring ("enable/mod", mode));
  if (!disable)
   disable = str2set (':', cfg_getstring ("disable/mod", mode));

  if (mode->arbattrs) for (; mode->arbattrs[xi]; xi+=2) {
   if (strmatch(mode->arbattrs[xi], "base")) {
    base = str2set (':', mode->arbattrs[xi+1]);
   }
  }

  if (enable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.enable, (const void **)enable, SET_TYPE_STRING);
   if (plan->changes.enable) free (plan->changes.enable);
   plan->changes.enable = tmp;
  }
  if (disable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.disable, (const void **)disable, SET_TYPE_STRING);
   if (plan->changes.disable) free (plan->changes.disable);
   plan->changes.disable = tmp;
  }
  if (critical) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.critical, (const void **)critical, SET_TYPE_STRING);
   if (plan->changes.critical) free (plan->changes.critical);
   plan->changes.critical = tmp;
  }

  if (auto_add_feedback && (strng = cfg_getstring("feedback/default", mode))) {
   if (plan->changes.enable) {
    uint32_t y = 0;

    for (; plan->changes.enable[y]; y++) {
     struct stree *st = NULL;

     emutex_lock (&ml_service_list_mutex);

     if ((st = streefind (module_logics_service_list, plan->changes.enable[y], tree_find_first))) {
      struct lmodule **lmod = st->value;

      if (lmod[0] && lmod[0]->module && lmod[0]->module->mode & einit_module_feedback) {
       emutex_unlock (&ml_service_list_mutex);
       goto have_feedback;
      }
     }

     emutex_unlock (&ml_service_list_mutex);
    }
   }

   plan->changes.enable = (char **)setadd ((void **)plan->changes.enable, (void *)strng, SET_TYPE_STRING);
  }
  have_feedback:

  if (base) {
   int y = 0;
   struct cfgnode *cno;
   while (base[y]) {
    if (!inset ((const void **)plan->used_modes, (void *)base[y], SET_TYPE_STRING)) {
     cno = cfg_findnode (base[y], einit_node_mode, NULL);
     if (cno) {
      plan = mod_plan (plan, NULL, 0, cno);
     }
    }

    y++;
   }

   free (base);
  }

  if (mode->id) {
   plan->used_modes = (char **)setadd ((void **)plan->used_modes, mode->id, SET_TYPE_STRING);
  }

  plan->mode = mode;
 } else {
  if (task & einit_module_enable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.enable, (const void **)atoms, SET_TYPE_STRING);
   if (plan->changes.enable) free (plan->changes.enable);
   plan->changes.enable = tmp;
  }
  if (task & einit_module_disable) {
   char **tmp = (char **)setcombine ((const void **)plan->changes.disable, (const void **)atoms, SET_TYPE_STRING);
   if (plan->changes.disable) free (plan->changes.disable);
   plan->changes.disable = tmp;
  }
 }

 disable_all = inset ((const void **)plan->changes.disable, (void *)"all", SET_TYPE_STRING);
 disable_all_but_feedback = inset ((const void **)plan->changes.disable, (void *)"all-but-feedback", SET_TYPE_STRING);

 if (disable_all || disable_all_but_feedback) {
  struct stree *cur;
  ssize_t i = 0;
  char **tmpy = service_usage_query_cr (service_is_provided, NULL, NULL);

  emutex_lock (&ml_service_list_mutex);
  emutex_lock (&ml_tb_target_state_mutex);
//  char **tmpx = (char **)setcombine ((const void **)plan->changes.enable, (const void **)target_state.enable, SET_TYPE_STRING);
  char **tmpx = (char **)setcombine ((const void **)tmpy, (const void **)target_state.enable, SET_TYPE_STRING);

  emutex_unlock (&ml_tb_target_state_mutex);

  char **tmp = (char **)setcombine ((const void **)tmpx, (const void **)plan->changes.disable, SET_TYPE_STRING);

  free (tmpx);
  free (tmpy);

  if (plan->changes.disable) {
   free (plan->changes.disable);
   plan->changes.disable = NULL;
  }

  if (tmp) {
   for (; tmp[i]; i++) {
    char add = 1;

    if (inset ((const void **)plan->changes.disable, (void *)tmp[i], SET_TYPE_STRING)) {
     add = 0;
    } else if ((disable_all && strmatch(tmp[i], "all")) ||
               (disable_all_but_feedback && strmatch(tmp[i], "all-but-feedback"))) {
     add = 0;
    } else if (module_logics_service_list && (cur = streefind (module_logics_service_list, tmp[i], tree_find_first))) {
     struct lmodule **lm = (struct lmodule **)cur->value;
     if (lm) {
      ssize_t y = 0;
      for (; lm[y]; y++) {
       if (disable_all_but_feedback && (lm[y]->module->mode & einit_module_feedback)) {
        add = 0;

        break;
       }
      }
     }
    } else if (!service_usage_query (service_is_provided, NULL, tmp[i])) {
     add = 0;
    }

    if (add) {
     plan->changes.disable = (char **)setadd((void **)plan->changes.disable, (void *)tmp[i], SET_TYPE_STRING);
    }
   }

   free (tmp);
  }

  emutex_unlock (&ml_service_list_mutex);
 }

 return plan;
}

unsigned int mod_plan_commit (struct mloadplan *plan) {
 struct einit_event *fb = evinit (einit_feedback_plan_status);

 if (!plan) return 0;

 if (plan->options & plan_option_shutdown)
  shutting_down = 1;

// do some extra work if the plan was derived from a mode
 if (plan->mode) {
  char *cmdt;
  cmode = plan->mode;

  struct einit_event eex = evstaticinit (einit_core_mode_switching);
  eex.para = (void *)plan->mode;
  event_emit (&eex, einit_event_flag_broadcast);
  evstaticdestroy (eex);

  if ((cmdt = cfg_getstring ("before-switch/emit-event", cmode))) {
   struct einit_event ee = evstaticinit (event_string_to_code(cmdt));
   event_emit (&ee, einit_event_flag_broadcast);
   evstaticdestroy (ee);
  }

  if ((cmdt = cfg_getstring ("before-switch/ipc", cmode))) {
   char **cmdts = str2set (':', cmdt);
   uint32_t in = 0;

   if (cmdts) {
    for (; cmdts[in]; in++)
     ipc_process(cmdts[in], stderr);

    free (cmdts);
   }
  }
 }

 fb->task = MOD_SCHEDULER_PLAN_COMMIT_START;
 fb->para = (void *)plan;
 status_update (fb);

 emutex_lock (&ml_tb_target_state_mutex);

 cross_taskblock (&(plan->changes), &target_state);

 emutex_lock (&ml_tb_current_mutex);

 cross_taskblock (&target_state, &current);

 uint32_t i = 0;

 if (current.enable) {
  char **tmp = NULL;
  for (i = 0; current.enable[i]; i++) {
   if (!service_usage_query (service_is_provided, NULL, current.enable[i])) {
    tmp = (char **)setadd ((void **)tmp, (void *)current.enable[i], SET_TYPE_STRING);
   }
  }
  free (current.enable);
  current.enable = tmp;
 }
 if (current.disable) {
  char **tmp = NULL;
  for (i = 0; current.disable[i]; i++) {
   if (service_usage_query (service_is_provided, NULL, current.disable[i])) {
    tmp = (char **)setadd ((void **)tmp, (void *)current.disable[i], SET_TYPE_STRING);
   }
  }
  free (current.disable);
  current.disable = tmp;
 }

 emutex_unlock (&ml_tb_current_mutex);
 emutex_unlock (&ml_tb_target_state_mutex);

 mod_commit_and_wait (plan->changes.enable, plan->changes.disable);

 fb->task = MOD_SCHEDULER_PLAN_COMMIT_FINISH;
 status_update (fb);

// do some more extra work if the plan was derived from a mode
 if (plan->mode) {
  char *cmdt;
  cmode = plan->mode;
  amode = plan->mode;

  struct einit_event eex = evstaticinit (einit_core_mode_switch_done);
  eex.para = (void *)plan->mode;
  event_emit (&eex, einit_event_flag_broadcast);
  evstaticdestroy (eex);

  if (amode->id) {
   struct einit_event eema = evstaticinit (einit_core_plan_update);
   eema.string = estrdup(amode->id);
   eema.para   = (void *)amode;
   event_emit (&eema, einit_event_flag_broadcast);
   free (eema.string);
   evstaticdestroy (eema);
  }

  if ((cmdt = cfg_getstring ("after-switch/emit-event", amode))) {
   struct einit_event ee = evstaticinit (event_string_to_code(cmdt));
   event_emit (&ee, einit_event_flag_broadcast);
   evstaticdestroy (ee);
  }

  if ((cmdt = cfg_getstring ("after-switch/ipc", amode))) {
   char **cmdts = str2set (':', cmdt);
   uint32_t in = 0;

   if (cmdts) {
    for (; cmdts[in]; in++) {
     ipc_process(cmdts[in], stderr);
    }
    free (cmdts);
   }
  }
 }

 evdestroy (fb);

 if (plan->mode) return 0; // always return "OK" if it's based on a mode

 return 0;
}

int mod_plan_free (struct mloadplan *plan) {
 if (plan) {
  if (plan->changes.enable) free (plan->changes.enable);
  if (plan->changes.disable) free (plan->changes.disable);
  if (plan->changes.critical) free (plan->changes.critical);

  if (plan->used_modes) free (plan->used_modes);

  free (plan);
 }
 return 0;
}

double mod_get_plan_progress_f (struct mloadplan *plan) {
 if (plan) {
  return 0.0;
 } else {
  double all = 0, left = 0;
  emutex_lock (&ml_tb_target_state_mutex);
  if (target_state.enable) all += setcount ((const void **)target_state.enable);
  if (target_state.disable) all += setcount ((const void **)target_state.disable);
  emutex_unlock (&ml_tb_target_state_mutex);

  emutex_lock (&ml_tb_current_mutex);
  if (current.enable) left += setcount ((const void **)current.enable);
  if (current.disable) left += setcount ((const void **)current.disable);
  emutex_unlock (&ml_tb_current_mutex);

  return 1.0 - (double)(left / all);
 }
}

void mod_sort_service_list_items_by_preference() {
 struct stree *cur;

 emutex_lock (&ml_service_list_mutex);

 cur = module_logics_service_list;

 while (cur) {
  struct lmodule **lm = (struct lmodule **)cur->value;

  if (lm) {
   /* order modules that should be enabled according to the user's preference */
   uint32_t mpx, mpy, mpz = 0;
   char *pnode = NULL, **preference = NULL;

   /* first make sure all modules marked as "deprecated" are last */
   for (mpx = 0; lm[mpx]; mpx++); mpx--;
   for (mpy = 0; mpy < mpx; mpy++) {
    if (lm[mpy]->module && (lm[mpy]->module->mode & einit_module_deprecated)) {
     struct lmodule *t = lm[mpx];
     lm[mpx] = lm[mpy];
     lm[mpy] = t;
     mpx--;
    }
   }

   /* now to the sorting bit... */
   pnode = emalloc (strlen (cur->key)+18);
   pnode[0] = 0;
   strcat (pnode, "services-prefer-");
   strcat (pnode, cur->key);

   if ((preference = str2set (':', cfg_getstring (pnode, NULL)))) {
    debugx ("applying module preferences for service %s", cur->key);

    for (mpx = 0; preference[mpx]; mpx++) {
     for (mpy = 0; lm[mpy]; mpy++) {
      if (lm[mpy]->module && lm[mpy]->module->rid && strmatch(lm[mpy]->module->rid, preference[mpx])) {
       struct lmodule *tm = lm[mpy];

       lm[mpy] = lm[mpz];
       lm[mpz] = tm;

       mpz++;
      }
     }
    }
    free (preference);
   }

   free (pnode);
  }

  cur = streenext(cur);
 }

 emutex_unlock (&ml_service_list_mutex);
}

int mod_switchmode (char *mode) {
 if (!mode) return -1;
 struct cfgnode *cur = cfg_findnode (mode, einit_node_mode, NULL);
 struct mloadplan *plan = NULL;

 if (!cur) {
  notice (1, "module-logic-v3: scheduled mode \"%s\" not defined, aborting", mode);
  return -1;
 }

 plan = mod_plan (NULL, NULL, 0, cur);
 if (!plan) {
  notice (1, "module-logic-v3: scheduled mode \"%s\" defined but nothing to be done", mode);
 } else {
  pthread_t th;
  mod_plan_commit (plan);
  /* make it so that the erase operation will not disturb the flow of the program */
  ethread_create (&th, &thread_attribute_detached, (void *(*)(void *))mod_plan_free, (void *)plan);
 }

 return 0;
}

int mod_modaction (char **argv) {
 int argc = setcount ((const void **)argv), ret = 1;
 int32_t task = 0;
 struct mloadplan *plan;
 if (!argv || (argc != 2)) return -1;

 if (strmatch (argv[1], "enable") || strmatch (argv[1], "start")) task = einit_module_enable;
 else if (strmatch (argv[1], "disable") || strmatch (argv[1], "stop")) task = einit_module_disable;
 else {
{'fraction_non_alphanumeric': 0.1450928769928331, 'fraction_numerical': 0.009214567792891619, 'mean_word_length': 4.138794939245898, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 317, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 8, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4720870', 'n_tokens_mistral': 15041, 'n_tokens_neox': 14316, 'n_words': 3668}