Пример #1
0
static void
write_status(restarter_inst_t *inst, const char *mname, int stat)
{
	int r;

again:
	if (inst->ri_mi_deleted)
		return;

	r = libscf_write_method_status(inst->ri_m_inst, mname, stat);
	switch (r) {
	case 0:
		break;

	case ECONNABORTED:
		libscf_reget_instance(inst);
		goto again;

	case ECANCELED:
		inst->ri_mi_deleted = 1;
		break;

	case EPERM:
	case EACCES:
	case EROFS:
		log_framework(LOG_INFO, "Could not write exit status "
		    "for %s method of %s: %s.\n", mname,
		    inst->ri_i.i_fmri, strerror(r));
		break;

	case ENAMETOOLONG:
	default:
		bad_error("libscf_write_method_status", r);
	}
}
Пример #2
0
/*
 * void method_remove_contract()
 *   Remove any non-permanent contracts from internal structures and
 *   the repository, then abandon them.
 *   Returns
 *     0 - success
 *     ECANCELED - inst was deleted from the repository
 *
 *   If the repository connection was broken, it is rebound.
 */
void
method_remove_contract(restarter_inst_t *inst, boolean_t primary,
    boolean_t abandon)
{
	ctid_t * const ctidp = primary ? &inst->ri_i.i_primary_ctid :
	    &inst->ri_i.i_transient_ctid;

	int r;

	assert(*ctidp != 0);

	log_framework(LOG_DEBUG, "Removing %s contract %lu for %s.\n",
	    primary ? "primary" : "transient", *ctidp, inst->ri_i.i_fmri);

	if (abandon)
		contract_abandon(*ctidp);

again:
	if (inst->ri_mi_deleted) {
		r = ECANCELED;
		goto out;
	}

	r = restarter_remove_contract(inst->ri_m_inst, *ctidp, primary ?
	    RESTARTER_CONTRACT_PRIMARY : RESTARTER_CONTRACT_TRANSIENT);
	switch (r) {
	case 0:
		break;

	case ECANCELED:
		inst->ri_mi_deleted = B_TRUE;
		break;

	case ECONNABORTED:
		libscf_handle_rebind(scf_instance_handle(inst->ri_m_inst));
		/* FALLTHROUGH */

	case EBADF:
		libscf_reget_instance(inst);
		goto again;

	case ENOMEM:
	case EPERM:
	case EACCES:
	case EROFS:
		log_error(LOG_INFO, "%s: Couldn't remove contract id %ld: "
		    "%s.\n", inst->ri_i.i_fmri, *ctidp, strerror(r));
		break;

	case EINVAL:
	default:
		bad_error("restarter_remove_contract", r);
	}

out:
	if (primary)
		contract_hash_remove(*ctidp);

	*ctidp = 0;
}
Пример #3
0
/*
 * Returns
 *   0 - success
 *   ECANCELED - pg was deleted
 *   ECONNABORTED - repository disconnected
 *   ENOMEM - out of memory
 */
int
load_pg_attrs(const scf_propertygroup_t *pg, pgroup_t **ipgp)
{
	pgroup_t *ipg;

	ipg = internal_pgroup_new();

	if (scf_pg_get_flags(pg, &ipg->sc_pgroup_flags) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			internal_pgroup_free(ipg);
			return (ECANCELED);

		case SCF_ERROR_CONNECTION_BROKEN:
			internal_pgroup_free(ipg);
			return (ECONNABORTED);

		case SCF_ERROR_NOT_SET:
		case SCF_ERROR_NOT_BOUND:
		default:
			bad_error("scf_pg_get_name", scf_error());
		}
	}

	if (scf_pg_get_name(pg, loadbuf, loadbuf_sz) < 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			internal_pgroup_free(ipg);
			return (ECANCELED);

		case SCF_ERROR_CONNECTION_BROKEN:
			internal_pgroup_free(ipg);
			return (ECONNABORTED);

		case SCF_ERROR_NOT_SET:
		case SCF_ERROR_NOT_BOUND:
		default:
			bad_error("scf_pg_get_name", scf_error());
		}
	}

	ipg->sc_pgroup_name = strdup(loadbuf);
	if (ipg->sc_pgroup_name == NULL) {
		internal_pgroup_free(ipg);
		return (ENOMEM);
	}

	if (scf_pg_get_type(pg, loadbuf, loadbuf_sz) < 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			free((char *)ipg->sc_pgroup_name);
			internal_pgroup_free(ipg);
			return (ECANCELED);

		case SCF_ERROR_CONNECTION_BROKEN:
			free((char *)ipg->sc_pgroup_name);
			internal_pgroup_free(ipg);
			return (ECONNABORTED);

		case SCF_ERROR_NOT_SET:
		case SCF_ERROR_NOT_BOUND:
		default:
			bad_error("scf_pg_get_name", scf_error());
		}
	}

	ipg->sc_pgroup_type = strdup(loadbuf);
	if (ipg->sc_pgroup_type == NULL) {
		free((char *)ipg->sc_pgroup_name);
		internal_pgroup_free(ipg);
		return (ENOMEM);
	}

	*ipgp = ipg;
	return (0);
}
Пример #4
0
/*
 * Create a property_t which represents an scf_property_t.  Returns
 *   0 - success
 *   ECANCELED - prop's pg was deleted
 *   ECONNABORTED - repository disconnected
 *   ENOMEM - out of memory
 *   EACCES - permission denied when reading property
 */
static int
load_property(scf_property_t *prop, property_t **ipp)
{
	property_t *iprop;
	int r;
	ssize_t ssz;

	/* get name */
	if (scf_property_get_name(prop, loadbuf, loadbuf_sz) < 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			return (ECANCELED);

		case SCF_ERROR_CONNECTION_BROKEN:
			return (ECONNABORTED);

		case SCF_ERROR_NOT_BOUND:
		case SCF_ERROR_NOT_SET:
		default:
			bad_error("scf_property_get_name", scf_error());
		}
	}

	iprop = internal_property_new();
	iprop->sc_property_name = strdup(loadbuf);
	if (iprop->sc_property_name == NULL) {
		internal_property_free(iprop);
		return (ENOMEM);
	}

	/* get type */
	if (scf_property_type(prop, &iprop->sc_value_type) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			r = ECANCELED;
			goto out;

		case SCF_ERROR_CONNECTION_BROKEN:
			r = ECONNABORTED;
			goto out;

		case SCF_ERROR_NOT_BOUND:
		case SCF_ERROR_NOT_SET:
		default:
			bad_error("scf_property_type", scf_error());
		}
	}

	/* get values */
	if (scf_iter_property_values(load_valiter, prop) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			r = ECANCELED;
			goto out;

		case SCF_ERROR_CONNECTION_BROKEN:
			r = ECONNABORTED;
			goto out;

		case SCF_ERROR_HANDLE_MISMATCH:
		case SCF_ERROR_NOT_BOUND:
		case SCF_ERROR_NOT_SET:
		default:
			bad_error("scf_iter_property_values", scf_error());
		}
	}

	for (;;) {
		value_t *ival;

		r = scf_iter_next_value(load_valiter, load_val);
		if (r == 0)
			break;
		if (r != 1) {
			switch (scf_error()) {
			case SCF_ERROR_DELETED:
				r = ECANCELED;
				goto out;

			case SCF_ERROR_CONNECTION_BROKEN:
				r = ECONNABORTED;
				goto out;

			case SCF_ERROR_PERMISSION_DENIED:
				r = EACCES;
				goto out;

			case SCF_ERROR_HANDLE_MISMATCH:
			case SCF_ERROR_NOT_BOUND:
			case SCF_ERROR_NOT_SET:
			case SCF_ERROR_INVALID_ARGUMENT:
			default:
				bad_error("scf_iter_next_value", scf_error());
			}
		}

		ival = internal_value_new();
		ival->sc_type = scf_value_type(load_val);
		assert(ival->sc_type != SCF_TYPE_INVALID);

		switch (ival->sc_type) {
		case SCF_TYPE_BOOLEAN: {
			uint8_t b;

			r = scf_value_get_boolean(load_val, &b);
			if (r != 0)
				bad_error("scf_value_get_boolean", scf_error());
			ival->sc_u.sc_count = b;
			break;
		}

		case SCF_TYPE_COUNT:
			r = scf_value_get_count(load_val, &ival->sc_u.sc_count);
			if (r != 0)
				bad_error("scf_value_get_count", scf_error());
			break;

		case SCF_TYPE_INTEGER:
			r = scf_value_get_integer(load_val,
			    &ival->sc_u.sc_integer);
			if (r != 0)
				bad_error("scf_value_get_integer", scf_error());
			break;

		default:
			ssz = scf_value_get_as_string(load_val, loadbuf,
			    loadbuf_sz);
			if (ssz < 0)
				bad_error("scf_value_get_as_string",
				    scf_error());

			ival->sc_u.sc_string = strdup(loadbuf);
			if (ival->sc_u.sc_string == NULL) {
				r = ENOMEM;
				goto out;
			}

			ival->sc_free = internal_value_free_str;
		}

		internal_attach_value(iprop, ival);
	}

	*ipp = iprop;
	return (0);

out:
	free(iprop->sc_property_name);
	internal_property_free(iprop);
	return (r);
}
Пример #5
0
/*
 * Load the instance for fmri from the repository into memory.  The
 * property groups that define the instances pg_patterns and prop_patterns
 * are also loaded.
 *
 * Returns 0 on success and non-zero on failure.
 */
int
load_instance(const char *fmri, const char *name, entity_t **inst_ptr)
{
	entity_t *e = NULL;
	scf_instance_t *inst;
	pgroup_t *ipg;
	int rc;
	char *type = NULL;
	ssize_t tsize;

	assert(inst_ptr != NULL);

	if ((inst = scf_instance_create(g_hndl)) == NULL) {
		switch (scf_error()) {
		case SCF_ERROR_NO_MEMORY:
		case SCF_ERROR_NO_RESOURCES:
			rc = EAGAIN;
			goto errout;
		default:
			bad_error("scf_instance_create", scf_error());
		}
	}
	if (scf_handle_decode_fmri(g_hndl, fmri, NULL, NULL, inst, NULL, NULL,
	    SCF_DECODE_FMRI_EXACT|SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_CONNECTION_BROKEN:
			rc = ECONNABORTED;
			goto errout;
		case SCF_ERROR_DELETED:
		case SCF_ERROR_NOT_FOUND:
			rc = ENOENT;
			goto errout;
		case SCF_ERROR_INVALID_ARGUMENT:
			rc = EINVAL;
			goto errout;
		case SCF_ERROR_CONSTRAINT_VIOLATED:
			rc = ENOTSUP;
			goto errout;
		default:
			bad_error("scf_handle_decode_fmri", scf_error());
		}
	}
	if (scf_iter_instance_pgs_composed(load_pgiter, inst, NULL) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			rc = ECANCELED;
			goto errout;
		case SCF_ERROR_CONNECTION_BROKEN:
			rc = ECONNABORTED;
			goto errout;
		default:
			bad_error("scf_iter_instance_pgs_composed",
			    scf_error());
		}
	}

	tsize = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH);
	type = uu_zalloc(tsize);
	if (type == NULL) {
		rc = ENOMEM;
		goto errout;
	}

	/*
	 * Initialize our entity structure.
	 */
	e = internal_instance_new(name);
	if (e == NULL) {
		rc = ENOMEM;
		goto errout;
	}
	e->sc_fmri = uu_strdup(fmri);
	if (e->sc_fmri == NULL) {
		rc = ENOMEM;
		goto errout;
	}

	/*
	 * Walk through the property group's of the instance and capture
	 * the property groups that are of type
	 * SCF_GROUP_TEMPLATE_PG_PATTERN and
	 * SCF_GROUP_TEMPLATE_PROP_PATTERN.  In other words grab the
	 * pg_pattern and prop_pattern property groups.
	 */
	while ((rc = scf_iter_next_pg(load_pgiter, load_pgroup)) == 1) {
		if (scf_pg_get_type(load_pgroup, type, tsize) <= 0) {
			switch (scf_error()) {
			case SCF_ERROR_DELETED:
				rc = ENOENT;
				break;
			case SCF_ERROR_CONNECTION_BROKEN:
				rc = ECONNABORTED;
				break;
			default:
				bad_error("scf_pg_get_type", scf_error());
			}
			goto errout;
		}
		if ((strcmp(type, SCF_GROUP_TEMPLATE_PG_PATTERN) != 0) &&
		    (strcmp(type, SCF_GROUP_TEMPLATE_PROP_PATTERN) != 0)) {
			continue;
		}
		if ((rc = load_pg(load_pgroup, &ipg, fmri, NULL)) != 0) {
			switch (rc) {
			case ECANCELED:
			case ECONNABORTED:
			case EACCES:
			case ENOMEM:
				break;
			default:
				bad_error("load_pg", rc);
			}
			goto errout;
		}
		if (internal_attach_pgroup(e, ipg) != 0) {
			rc = EBADF;
			goto errout;
		}
	}
	if (rc == -1) {
		/* Error in iteration. */
		switch (scf_error()) {
		case SCF_ERROR_CONNECTION_BROKEN:
			rc = ECONNABORTED;
			break;
		case SCF_ERROR_DELETED:
			rc = ENOENT;
			break;
		case SCF_ERROR_NO_RESOURCES:
			rc = EAGAIN;
			break;
		default:
			bad_error("scf_iter_next_pg", scf_error());
		}
		goto errout;
	}

	*inst_ptr = e;
	scf_instance_destroy(inst);
	return (0);

errout:
	if (type != NULL)
		uu_free(type);
	if (inst != NULL)
		scf_instance_destroy(inst);
	if (e != NULL)
		internal_instance_free(e);
	return (rc);
}
Пример #6
0
/*
 * Load a property group into a pgroup_t.  Returns
 *   0 - success
 *   ECANCELED - pg was deleted
 *   ECONNABORTED - repository disconnected
 *   EBADF - pg is corrupt (error printed if fmri is given)
 *   ENOMEM - out of memory
 *   EACCES - permission denied when reading property
 */
int
load_pg(const scf_propertygroup_t *pg, pgroup_t **ipgp, const char *fmri,
    const char *snapname)
{
	pgroup_t *ipg;
	int r;

	if (scf_iter_pg_properties(load_propiter, pg) != 0) {
		switch (scf_error()) {
		case SCF_ERROR_DELETED:
			return (ECANCELED);

		case SCF_ERROR_CONNECTION_BROKEN:
			return (ECONNABORTED);

		case SCF_ERROR_HANDLE_MISMATCH:
		case SCF_ERROR_NOT_SET:
		case SCF_ERROR_NOT_BOUND:
		default:
			bad_error("scf_iter_pg_properties", scf_error());
		}
	}

	r = load_pg_attrs(pg, &ipg);
	switch (r) {
	case 0:
		break;

	case ECANCELED:
	case ECONNABORTED:
	case ENOMEM:
		return (r);

	default:
		bad_error("load_pg_attrs", r);
	}

	for (;;) {
		property_t *iprop;

		r = scf_iter_next_property(load_propiter, load_prop);
		if (r == 0)
			break;
		if (r != 1) {
			switch (scf_error()) {
			case SCF_ERROR_DELETED:
				r = ECANCELED;
				goto out;

			case SCF_ERROR_CONNECTION_BROKEN:
				r = ECONNABORTED;
				goto out;

			case SCF_ERROR_HANDLE_MISMATCH:
			case SCF_ERROR_NOT_BOUND:
			case SCF_ERROR_NOT_SET:
			case SCF_ERROR_INVALID_ARGUMENT:
			default:
				bad_error("scf_iter_next_property",
				    scf_error());
			}
		}

		r = load_property(load_prop, &iprop);
		switch (r) {
		case 0:
			break;

		case ECANCELED:
		case ECONNABORTED:
		case ENOMEM:
		case EACCES:
			goto out;

		default:
			bad_error("load_property", r);
		}

		r = internal_attach_property(ipg, iprop);
		if (r != 0) {
			if (fmri != NULL) {
				if (snapname == NULL)
					warn(gettext("Property group \"%s\" of "
					    "%s has multiple definitions of "
					    "property \"%s\".\n"),
					    ipg->sc_pgroup_name, fmri,
					    iprop->sc_property_name);
				else
					warn(gettext("Property group \"%s\" of "
					    "the \"%s\" snapshot of %s has "
					    "multiple definitions of property "
					    "\"%s\".\n"),
					    ipg->sc_pgroup_name, snapname, fmri,
					    iprop->sc_property_name);
			}
			r = EBADF;
			goto out;
		}
	}

	*ipgp = ipg;
	return (0);

out:
	internal_pgroup_free(ipg);
	return (r);
}
Пример #7
0
word get_next_word(char* buffer, int* it, int bufSize, int* lineNum) {
  word w;
  // deal with END
  if (*it == bufSize) {
    w.type = END;
    return w;
  }
  
  // remove beginning whitespace
  while (buffer[*it] == ' ' || buffer[*it] == '\t') {
    (*it)++;
    if (*it == bufSize) {
      w.type = END;
      return w;
    }
  }
  
  // deal with single-char tokens, ; | ( ) < > #
  // don't need to update string component of the word bc we don't use it
  char c = buffer[*it];
  switch (c) {
    case ';':
      w.type = SEMICOLON;
      w.string = ";";
      (*it) = (*it) + 1;
      return w;
    case '|':
      w.type = PIPE;
      w.string = "|";
      (*it) = (*it) + 1;
      return w;
    case '(':
      w.type = LPARENS;
      w.string = "(";
      (*it) = (*it) + 1;
      return w;
    case ')':
      w.type = RPARENS;
      w.string = ")";
      (*it) = (*it) + 1;
      return w;
    case '<':
      w.type = INPUT;
      w.string = "<";
      (*it) = (*it) + 1;
      return w;
    case '>':
      w.type = OUTPUT;
      w.string = ">";
      (*it) = (*it) + 1;
      return w;

    case '\n':
      w.type = NEWLINE;
      w.string = "\n";
      (*lineNum) = (*lineNum) + 1;
      (*it) = (*it) + 1;
      return w;
      
    case '#':
      w.type = COMMENT;
      w.string = "#";
      while (*it < bufSize && buffer[*it] != '\n')
        (*it) = (*it) + 1;
      if (*it != bufSize) {
        (*it) = (*it) + 1;  // move past new line, point to start of next word
        (*lineNum) = (*lineNum) + 1;
      }
      return w;
      
    default:
      break;
  }
  
  // deal with words
  int wordLen = 64;
  w.string = (char*)checked_malloc(sizeof(char)*wordLen);
  
  // create string
  int stringIndex = 0;
  while (buffer[*it] != ' ' && buffer[*it] != '\t' && buffer[*it] != '\n' && 
        buffer[*it] != ';' && buffer[*it] != '|' && buffer[*it] != '(' && 
        buffer[*it] != ')' && buffer[*it] != '<' && buffer[*it] != '>' && 
        *it != bufSize) 
  {
    if (stringIndex == wordLen)
    {
      wordLen = wordLen*2;
      size_t size_size = sizeof(char)*wordLen;
      w.string = (char*)checked_grow_alloc((void*)w.string, &(size_size));
    }
    if (!(isalpha(buffer[*it]) || isdigit(buffer[*it]) ||
          buffer[*it]=='!' || buffer[*it]=='%' || buffer[*it]=='+' ||
          buffer[*it]==',' || buffer[*it]=='-' || buffer[*it]=='.' ||
          buffer[*it]=='/' || buffer[*it]==':' || buffer[*it]=='@' ||
          buffer[*it]=='^' || buffer[*it]=='_'
        ))
      bad_error(lineNum, __LINE__);
    w.string[stringIndex] = buffer[*it];
    (*it) = (*it) + 1;
    stringIndex++;
  }
  w.string[stringIndex] = '\0';
  
  // assign special words
  if (strcmp(w.string, "if") == 0) {
    w.type = IF;
    w.string = "IF";
    return w;
  }
  else if (strcmp(w.string, "then") == 0) {
    w.type = THEN;
    w.string = "THEN";
    return w;
  }
  else if (strcmp(w.string, "else") == 0) {
    w.type = ELSE;
    w.string = "ELSE";
    return w;
  }
  else if (strcmp(w.string, "fi") == 0) {
    w.type = FI;
    w.string = "FI";
    return w;
  }
  else if (strcmp(w.string, "while") == 0) {
    w.type = WHILE;
    w.string = "WHILE";
    return w;
  }
  else if (strcmp(w.string, "until") == 0) {
    w.type = UNTIL;
    w.string = "UNTIL";
    return w;
  }
  else if (strcmp(w.string, "do") == 0) {
    w.type = DO;
    w.string = "DO";
    return w;
  }
  else if (strcmp(w.string, "done") == 0) {
    w.type = DONE;
    w.string = "DONE";
    return w;
  }
  else {
    w.type = SIMPLE;
    return w;
  }
}
Пример #8
0
int get_command(char* buffer, int* it, int bufSize, command_t com, int* lineNum) {
  word next_word = get_next_word(buffer, it, bufSize, lineNum);
  com->status = -1;
  command_t newCom = checked_malloc(sizeof(struct command));
  
  switch (next_word.type) {
    case IF:
      com->type = IF_COMMAND;
      // IF
      com->u.command[0] = newCom;
      if (get_command(buffer, it, bufSize, newCom, lineNum)) {
          next_word = get_next_word(buffer, it, bufSize, lineNum);
          while (next_word.type == NEWLINE)
          {
            next_word = get_next_word(buffer, it, bufSize, lineNum);
          }
          // THEN
          if (next_word.type == THEN) {
              command_t thenCom = checked_malloc(sizeof(struct command));
              com->u.command[1] = thenCom;
              if (get_command(buffer, it, bufSize, thenCom, lineNum)) {
                  next_word = get_next_word(buffer, it, bufSize, lineNum);
                  while (next_word.type == NEWLINE)
                  {
                    next_word = get_next_word(buffer, it, bufSize, lineNum);
                  }
                  // ELSE
                  if (next_word.type == ELSE) {
                    command_t elseCom = checked_malloc(sizeof(struct command));
                    com->u.command[2] = elseCom;
                    if (get_command(buffer, it, bufSize, elseCom, lineNum)) {
                        next_word = get_next_word(buffer, it, bufSize, lineNum);
                        while (next_word.type == NEWLINE)
                        {
                          next_word = get_next_word(buffer, it, bufSize, lineNum);
                        }
                        // FI AFTER ELSE
                        if (next_word.type == FI)
                          return 1;
                        else
                          bad_error(lineNum, __LINE__);
                    }
                    else
                      bad_error(lineNum, __LINE__);
                  }
                  // NO ELSE - STRAIGHT TO FI
                  else if (next_word.type == FI)
                    return 1;
                  else
                    bad_error(lineNum, __LINE__);
              }
              else
                bad_error(lineNum, __LINE__);
          }
          else
            bad_error(lineNum, __LINE__);
      }
      else
        bad_error(lineNum, __LINE__);

    case WHILE:
      ;
      int w = 1;
      com->type = WHILE_COMMAND;
      
      // WHILE
      command_t whileCom = checked_malloc(sizeof(struct command));
      whileCom->type = SEQUENCE_COMMAND;
      command_t whileWhileCom = whileCom;
      whileCom->u.command[0] = newCom;

      while (get_command(buffer, it, bufSize, newCom, lineNum))
      {
        int s = *it;
        int* whileIt = &(s);
        
        next_word = get_next_word(buffer, it, bufSize, lineNum);

        if (next_word.type != DO)
        {
          whileCom->type = SEQUENCE_COMMAND;
          whileCom->u.command[1] = checked_malloc(sizeof(struct command));
          newCom = whileCom->u.command[1];
          whileCom = whileCom->u.command[1];
          *it = *whileIt;
          w++;
        }
        else 
        {
          if (w == 1)
            com->u.command[0] = whileWhileCom->u.command[0];
          else
            com->u.command[0] = whileWhileCom;

          command_t doCom = checked_malloc(sizeof(struct command));
          com->u.command[1] = doCom;
          if (get_command(buffer, it, bufSize, doCom, lineNum)) {
            next_word = get_next_word(buffer, it, bufSize, lineNum);
            while (next_word.type == NEWLINE)
            {
              next_word = get_next_word(buffer, it, bufSize, lineNum);
            }
            // DONE
            if (next_word.type == DONE) {
              // can't have another command after done
              int v = *it;
              int* doneIt = &(v);
              next_word = get_next_word(buffer, it, bufSize, lineNum);
              if (next_word.type == NEWLINE || next_word.type == SEMICOLON ||
                  next_word.type == INPUT   || next_word.type == OUTPUT    ||
                  next_word.type == END) {
                *it = *doneIt;
                return generate_from_simple(com, 0, buffer, it, bufSize, com, lineNum);
              }
              else
                bad_error(lineNum, __LINE__);
            }
            else
              bad_error(lineNum, __LINE__);
          }
          else
            bad_error(lineNum, __LINE__);
        }
      }
      bad_error(lineNum, __LINE__);
      return 0;

    case UNTIL:
      ;
      int u = 1;
      com->type = UNTIL_COMMAND;
      
      // UNTIL
      command_t untilCom = checked_malloc(sizeof(struct command));
      untilCom->type = SEQUENCE_COMMAND;
      command_t untilUntilCom = untilCom;
      untilCom->u.command[0] = newCom;

      while (get_command(buffer, it, bufSize, newCom, lineNum))
      {
        int t = *it;
        int* untilIt = &(t);
        
        next_word = get_next_word(buffer, it, bufSize, lineNum);

        if (next_word.type != DO)
        {
          untilCom->type = SEQUENCE_COMMAND;
          untilCom = untilCom->u.command[1];
          untilCom = newCom;
          *it = *untilIt;
          u++;
        }
        else 
        {
          if (u == 1)
            com->u.command[0] = untilUntilCom->u.command[0];
          else
            com->u.command[0] = untilUntilCom;

          command_t udoCom = checked_malloc(sizeof(struct command));
          com->u.command[1] = udoCom;
          if (get_command(buffer, it, bufSize, udoCom, lineNum)) {
            next_word = get_next_word(buffer, it, bufSize, lineNum);
            while (next_word.type == NEWLINE)
            {
              next_word = get_next_word(buffer, it, bufSize, lineNum);
            }
            // DONE
            if (next_word.type == DONE) {
              // can't have another command after done
              int z = *it;
              int* udoneIt = &(z);
              next_word = get_next_word(buffer, it, bufSize, lineNum);
              if (next_word.type == NEWLINE || next_word.type == SEMICOLON ||
                  next_word.type == INPUT   || next_word.type == OUTPUT    ||
                  next_word.type == END) {
                *it = *udoneIt;
                return generate_from_simple(com, 0, buffer, it, bufSize, com, lineNum);
              }
              else
                bad_error(lineNum, __LINE__);
            }
            else
              bad_error(lineNum, __LINE__);
          }
          else
            bad_error(lineNum, __LINE__);
        }
      }
      bad_error(lineNum, __LINE__);
      return 0;
      
    case LPARENS:
      com->type = SUBSHELL_COMMAND;
      if (get_command(buffer, it, bufSize, newCom, lineNum)) {
        next_word = get_next_word(buffer, it, bufSize, lineNum);
        if (next_word.type == RPARENS) {
          com->u.command[0] = newCom;
          return 1;
        }
        else bad_error(lineNum, __LINE__);
      }
      else bad_error(lineNum, __LINE__);


    case COMMENT:
    case NEWLINE:
      return get_command(buffer, it, bufSize, com, lineNum);

    case SEMICOLON:
      bad_error(lineNum, __LINE__);
      return 0;

    case END:
      return 0;

    case SIMPLE: 
      ;
      // make temporary command
      command_t tempCom = checked_malloc(sizeof(struct command));
      tempCom->type = SIMPLE_COMMAND;

      //TODO FIX ALLOCATION MAYBE (number of words)
      tempCom->u.word = checked_malloc(sizeof(char*)*256);

      int word_len = strlen(next_word.string);
      tempCom->u.word[0] = checked_malloc(sizeof(char)*word_len);
      tempCom->u.word[0] = next_word.string;
      
      int word_count = 1;
      return generate_from_simple(tempCom, word_count, buffer, it, bufSize, com, lineNum);
      
    case THEN:
    case ELSE:
    case FI:
    case DO:
    case DONE:  
    default:
      bad_error(lineNum, __LINE__);
      return 0;
  } 
}
Пример #9
0
int generate_from_simple(command_t tempCom, int word_count, char* buffer, int* it, int bufSize, command_t com, int* lineNum)
{
    word next_word = get_next_word(buffer, it, bufSize, lineNum);
    //command_t newCom = checked_malloc(sizeof(struct command));

    switch(next_word.type) {
        case PIPE:
          com->type = PIPE_COMMAND;
          com->u.command[0] = checked_malloc(sizeof(struct command));
          *(com->u.command[0]) = *tempCom;
          command_t secondCom = checked_malloc(sizeof(struct command));
          if (get_command(buffer, it, bufSize, secondCom, lineNum)) {
            com->u.command[1] = checked_malloc(sizeof(struct command));
            *(com->u.command[1]) = *secondCom;
            return 1;
          }
          else bad_error(lineNum, __LINE__);

        case SEMICOLON:
          ;
          int s = *it;
          int* seqIt = &(s);
          next_word = get_next_word(buffer, it, bufSize, lineNum);
          if (next_word.type == NEWLINE || next_word.type == END || next_word.type == THEN
              || next_word.type == ELSE || next_word.type == DO || next_word.type == DONE
              || next_word.type == FI)
          {
            *it = *seqIt;
            *com = *tempCom;
            return 1;
          }
          else 
          {
            com->type = SEQUENCE_COMMAND;
            com->u.command[0] = tempCom;
            command_t secondCom = checked_malloc(sizeof(struct command));
            get_command(buffer, seqIt, bufSize, secondCom, lineNum);
            com->u.command[1] = secondCom;
            *it = *seqIt;
            return 1;
          }  

        case LPARENS:
          // NOTE: subshells cannot be a part of simple commands in bash so disregard
          // tempCom->u.word[1] = checked_malloc(sizeof(char)*128);
          // next_word = get_next_word(buffer, it, bufSize, lineNum);
          // char* tempString = checked_malloc(sizeof(char)*128);
          // tempString = next_word.string;

          // while (next_word.type != RPARENS) {
          //   next_word = get_next_word(buffer, it, bufSize, lineNum);
          //   strcat(tempString, next_word.string);
          //   if (next_word.type == NEWLINE || next_word.type == END ||
          //     next_word.type == PIPE || next_word.type == LPARENS)
          //   {
          //     bad_error(lineNum, __LINE__);
          //     return 0;
          //   }
          // }
          // strcat(tempString, next_word.string);
          // *(tempCom->u.word[1]) = *tempString;
          // *com = *tempCom;
          // return 1;
          bad_error(lineNum, __LINE__);
          return 0;
          

        case RPARENS:
          *it = (*it)-1;
          *com = *tempCom;    
          return 1;       

        case INPUT:
          next_word = get_next_word(buffer, it, bufSize, lineNum);

          if (next_word.type == NEWLINE || next_word.type == SEMICOLON || next_word.type == END ||
              next_word.type == PIPE || next_word.type == LPARENS || next_word.type == RPARENS ||
              next_word.type == INPUT || next_word.type == OUTPUT)
          {
            bad_error(lineNum, __LINE__);
            return 0;
          }

          tempCom->input = next_word.string;
          int t = *it;
          int* inIt = &(t);
          next_word = get_next_word(buffer, inIt, bufSize, lineNum);

          if (next_word.type == OUTPUT)
          {
            next_word = get_next_word(buffer, inIt, bufSize, lineNum);
            if (next_word.type == NEWLINE || next_word.type == SEMICOLON || next_word.type == END ||
              next_word.type == PIPE || next_word.type == LPARENS || next_word.type == RPARENS ||
              next_word.type == INPUT || next_word.type == OUTPUT)
            {
              bad_error(lineNum, __LINE__);
              return 0;
            }
            tempCom->output = next_word.string;
            *it = *inIt;
          }
          return generate_from_simple(tempCom, word_count, buffer, it, bufSize, com, lineNum);

        case OUTPUT:
          next_word = get_next_word(buffer, it, bufSize, lineNum);
          if (next_word.type == NEWLINE || next_word.type == SEMICOLON || next_word.type == END ||
              next_word.type == PIPE || next_word.type == LPARENS || next_word.type == RPARENS ||
              next_word.type == INPUT || next_word.type == OUTPUT)
          {
            bad_error(lineNum, __LINE__);
            return 0;
          }
          tempCom->output = next_word.string;
          return generate_from_simple(tempCom, word_count, buffer, it, bufSize, com, lineNum);

        case COMMENT:
          next_word = get_next_word(buffer, it, bufSize, lineNum);
          while (next_word.type != NEWLINE && next_word.type != END) {
            next_word = get_next_word(buffer, it, bufSize, lineNum);
          }
          return 1;

        case NEWLINE:
        case END:
          *com = *tempCom;
          return 1;

        case SIMPLE:
          ;
          int word_len = strlen(next_word.string);
          tempCom->u.word[word_count] = checked_malloc(sizeof(char)*word_len);
          strcpy(tempCom->u.word[word_count], next_word.string);
          *com = *tempCom;
          word_count++;
          return generate_from_simple(tempCom, word_count, buffer, it, bufSize, com, lineNum);

        case IF:
        case THEN:
        case ELSE:
        case FI:
        case WHILE:
        case DO:
        case DONE:
        case UNTIL:
        default:
          bad_error(lineNum, __LINE__);
          return 0;
    }
}
Пример #10
0
/*
 * The method thread executes a service method to effect a state transition.
 * The next_state of info->sf_id should be non-_NONE on entrance, and it will
 * be _NONE on exit (state will either be what next_state was (on success), or
 * it will be _MAINT (on error)).
 *
 * There are six classes of methods to consider: start & other (stop, refresh)
 * for each of "normal" services, wait services, and transient services.  For
 * each, the method must be fetched from the repository & executed.  fork()ed
 * methods must be waited on, except for the start method of wait services
 * (which must be registered with the wait subsystem via wait_register()).  If
 * the method succeeded (returned 0), then for start methods its contract
 * should be recorded as the primary contract for the service.  For other
 * methods, it should be abandoned.  If the method fails, then depending on
 * the failure, either the method should be reexecuted or the service should
 * be put into maintenance.  Either way the contract should be abandoned.
 */
void *
method_thread(void *arg)
{
	fork_info_t *info = arg;
	restarter_inst_t *inst;
	scf_handle_t	*local_handle;
	scf_instance_t	*s_inst = NULL;
	int r, exit_code;
	boolean_t retryable;
	const char *aux;

	assert(0 <= info->sf_method_type && info->sf_method_type <= 2);

	/* Get (and lock) the restarter_inst_t. */
	inst = inst_lookup_by_id(info->sf_id);

	assert(inst->ri_method_thread != 0);
	assert(instance_in_transition(inst) == 1);

	/*
	 * We cannot leave this function with inst in transition, because
	 * protocol.c withholds messages for inst otherwise.
	 */

	log_framework(LOG_DEBUG, "method_thread() running %s method for %s.\n",
	    method_names[info->sf_method_type], inst->ri_i.i_fmri);

	local_handle = libscf_handle_create_bound_loop();

rebind_retry:
	/* get scf_instance_t */
	switch (r = libscf_fmri_get_instance(local_handle, inst->ri_i.i_fmri,
	    &s_inst)) {
	case 0:
		break;

	case ECONNABORTED:
		libscf_handle_rebind(local_handle);
		goto rebind_retry;

	case ENOENT:
		/*
		 * It's not there, but we need to call this so protocol.c
		 * doesn't think it's in transition anymore.
		 */
		(void) restarter_instance_update_states(local_handle, inst,
		    inst->ri_i.i_state, RESTARTER_STATE_NONE, RERR_NONE,
		    NULL);
		goto out;

	case EINVAL:
	case ENOTSUP:
	default:
		bad_error("libscf_fmri_get_instance", r);
	}

	inst->ri_m_inst = s_inst;
	inst->ri_mi_deleted = B_FALSE;

retry:
	if (info->sf_method_type == METHOD_START)
		log_transition(inst, START_REQUESTED);

	r = method_run(&inst, info->sf_method_type, &exit_code);

	if (r == 0 && exit_code == 0) {
		/* Success! */
		assert(inst->ri_i.i_next_state != RESTARTER_STATE_NONE);

		/*
		 * When a stop method succeeds, remove the primary contract of
		 * the service, unless we're going to offline, in which case
		 * retain the contract so we can transfer inherited contracts to
		 * the replacement service.
		 */

		if (info->sf_method_type == METHOD_STOP &&
		    inst->ri_i.i_primary_ctid != 0) {
			if (inst->ri_i.i_next_state == RESTARTER_STATE_OFFLINE)
				inst->ri_i.i_primary_ctid_stopped = 1;
			else
				method_remove_contract(inst, B_TRUE, B_TRUE);
		}
		/*
		 * We don't care whether the handle was rebound because this is
		 * the last thing we do with it.
		 */
		(void) restarter_instance_update_states(local_handle, inst,
		    inst->ri_i.i_next_state, RESTARTER_STATE_NONE,
		    info->sf_event_type, NULL);

		(void) update_fault_count(inst, FAULT_COUNT_RESET);

		goto out;
	}

	/* Failure.  Retry or go to maintenance. */

	if (r != 0 && r != EAGAIN) {
		retryable = B_FALSE;
	} else {
		switch (exit_code) {
		case SMF_EXIT_ERR_CONFIG:
		case SMF_EXIT_ERR_NOSMF:
		case SMF_EXIT_ERR_PERM:
		case SMF_EXIT_ERR_FATAL:
			retryable = B_FALSE;
			break;

		default:
			retryable = B_TRUE;
		}
	}

	if (retryable && update_fault_count(inst, FAULT_COUNT_INCR) != 1)
		goto retry;

	/* maintenance */
	if (r == ELOOP)
		log_transition(inst, START_FAILED_REPEATEDLY);
	else if (r == ERANGE)
		log_transition(inst, START_FAILED_TIMEOUT_FATAL);
	else if (exit_code == SMF_EXIT_ERR_CONFIG)
		log_transition(inst, START_FAILED_CONFIGURATION);
	else if (exit_code == SMF_EXIT_ERR_FATAL)
		log_transition(inst, START_FAILED_FATAL);
	else
		log_transition(inst, START_FAILED_OTHER);

	if (r == ELOOP)
		aux = "restarting_too_quickly";
	else if (retryable)
		aux = "fault_threshold_reached";
	else
		aux = "method_failed";

	(void) restarter_instance_update_states(local_handle, inst,
	    RESTARTER_STATE_MAINT, RESTARTER_STATE_NONE, RERR_FAULT,
	    (char *)aux);

	if (!method_is_transient(inst, info->sf_method_type) &&
	    inst->ri_i.i_primary_ctid != 0)
		method_remove_contract(inst, B_TRUE, B_TRUE);

out:
	inst->ri_method_thread = 0;
	MUTEX_UNLOCK(&inst->ri_lock);
	(void) pthread_cond_broadcast(&inst->ri_method_cv);

	scf_instance_destroy(s_inst);
	scf_handle_destroy(local_handle);
	startd_free(info, sizeof (fork_info_t));
	return (NULL);
}
Пример #11
0
/*
 * int method_ready_contract(restarter_inst_t *, int, method_restart_t, int)
 *
 *   Activate a contract template for the type method of inst.  type,
 *   restart_on, and cte_mask dictate the critical events term of the contract.
 *   Returns
 *     0 - success
 *     ECANCELED - inst has been deleted from the repository
 */
static int
method_ready_contract(restarter_inst_t *inst, int type,
    method_restart_t restart_on, uint_t cte_mask)
{
	int tmpl, err, istrans, iswait, ret;
	uint_t cevents, fevents;

	/*
	 * Correctly supporting wait-style services is tricky without
	 * rearchitecting startd to cope with multiple event sources
	 * simultaneously trying to stop an instance.  Until a better
	 * solution is implemented, we avoid this problem for
	 * wait-style services by making contract events fatal and
	 * letting the wait code alone handle stopping the service.
	 */
	iswait = instance_is_wait_style(inst);
	istrans = method_is_transient(inst, type);

	tmpl = open64(CTFS_ROOT "/process/template", O_RDWR);
	if (tmpl == -1)
		uu_die("Could not create contract template");

	/*
	 * We assume non-login processes are unlikely to create
	 * multiple process groups, and set CT_PR_PGRPONLY for all
	 * wait-style services' contracts.
	 */
	err = ct_pr_tmpl_set_param(tmpl, CT_PR_INHERIT | CT_PR_REGENT |
	    (iswait ? CT_PR_PGRPONLY : 0));
	assert(err == 0);

	if (istrans) {
		cevents = 0;
		fevents = 0;
	} else {
		assert(restart_on >= 0);
		assert(restart_on <= METHOD_RESTART_ANY_FAULT);
		cevents = method_events[restart_on] & ~cte_mask;
		fevents = iswait ?
		    (method_events[restart_on] & ~cte_mask & CT_PR_ALLFATAL) :
		    0;
	}

	err = ct_tmpl_set_critical(tmpl, cevents);
	assert(err == 0);

	err = ct_tmpl_set_informative(tmpl, 0);
	assert(err == 0);
	err = ct_pr_tmpl_set_fatal(tmpl, fevents);
	assert(err == 0);

	err = ct_tmpl_set_cookie(tmpl, istrans ?  METHOD_OTHER_COOKIE :
	    METHOD_START_COOKIE);
	assert(err == 0);

	if (type == METHOD_START && inst->ri_i.i_primary_ctid != 0) {
		ret = ct_pr_tmpl_set_transfer(tmpl, inst->ri_i.i_primary_ctid);
		switch (ret) {
		case 0:
			break;

		case ENOTEMPTY:
			/* No contracts for you! */
			method_remove_contract(inst, B_TRUE, B_TRUE);
			if (inst->ri_mi_deleted) {
				ret = ECANCELED;
				goto out;
			}
			break;

		case EINVAL:
		case ESRCH:
		case EACCES:
		default:
			bad_error("ct_pr_tmpl_set_transfer", ret);
		}
	}

	err = ct_tmpl_activate(tmpl);
	assert(err == 0);

	ret = 0;

out:
	err = close(tmpl);
	assert(err == 0);

	return (ret);
}
Пример #12
0
/*
 * void method_store_contract()
 *   Store the newly created contract id into local structures and
 *   the repository.  If the repository connection is broken it is rebound.
 */
static void
method_store_contract(restarter_inst_t *inst, int type, ctid_t *cid)
{
	int r;
	boolean_t primary;

	if (errno = contract_latest(cid))
		uu_die("%s: Couldn't get new contract's id", inst->ri_i.i_fmri);

	primary = !method_is_transient(inst, type);

	if (!primary) {
		if (inst->ri_i.i_transient_ctid != 0) {
			log_framework(LOG_INFO,
			    "%s: transient ctid expected to be 0 but "
			    "was set to %ld\n", inst->ri_i.i_fmri,
			    inst->ri_i.i_transient_ctid);
		}

		inst->ri_i.i_transient_ctid = *cid;
	} else {
		if (inst->ri_i.i_primary_ctid != 0) {
			/*
			 * There was an old contract that we transferred.
			 * Remove it.
			 */
			method_remove_contract(inst, B_TRUE, B_FALSE);
		}

		if (inst->ri_i.i_primary_ctid != 0) {
			log_framework(LOG_INFO,
			    "%s: primary ctid expected to be 0 but "
			    "was set to %ld\n", inst->ri_i.i_fmri,
			    inst->ri_i.i_primary_ctid);
		}

		inst->ri_i.i_primary_ctid = *cid;
		inst->ri_i.i_primary_ctid_stopped = 0;

		contract_hash_store(*cid, inst->ri_id);
	}

again:
	if (inst->ri_mi_deleted)
		return;

	r = restarter_store_contract(inst->ri_m_inst, *cid, primary ?
	    RESTARTER_CONTRACT_PRIMARY : RESTARTER_CONTRACT_TRANSIENT);
	switch (r) {
	case 0:
		break;

	case ECANCELED:
		inst->ri_mi_deleted = B_TRUE;
		break;

	case ECONNABORTED:
		libscf_handle_rebind(scf_instance_handle(inst->ri_m_inst));
		/* FALLTHROUGH */

	case EBADF:
		libscf_reget_instance(inst);
		goto again;

	case ENOMEM:
	case EPERM:
	case EACCES:
	case EROFS:
		uu_die("%s: Couldn't store contract id %ld",
		    inst->ri_i.i_fmri, *cid);
		/* NOTREACHED */

	case EINVAL:
	default:
		bad_error("restarter_store_contract", r);
	}
}
Пример #13
0
/*ARGSUSED*/
void *
wait_thread(void *args)
{
	for (;;) {
		port_event_t pe;
		int fd;
		wait_info_t *wi;

		if (port_get(port_fd, &pe, NULL) != 0) {
			if (errno == EINTR)
				continue;
			else {
				log_error(LOG_WARNING,
				    "port_get() failed with %s\n",
				    strerror(errno));
				bad_error("port_get", errno);
			}
		}

		fd = pe.portev_object;
		wi = pe.portev_user;
		assert(wi != NULL);
		assert(fd == wi->wi_fd);

		if ((pe.portev_events & POLLHUP) == POLLHUP) {
			psinfo_t psi;

			if (lseek(fd, 0, SEEK_SET) != 0 ||
			    read(fd, &psi, sizeof (psinfo_t)) !=
			    sizeof (psinfo_t)) {
				log_framework(LOG_WARNING,
				    "couldn't get psinfo data for %s (%s); "
				    "assuming failed\n", wi->wi_fmri,
				    strerror(errno));
				goto err_remove;
			}

			if (psi.pr_nlwp != 0 ||
			    psi.pr_nzomb != 0 ||
			    psi.pr_lwp.pr_lwpid != 0) {
				/*
				 * We have determined, in accordance with the
				 * definition in proc(4), this process is not a
				 * zombie.  Reassociate.
				 */
				if (port_associate(port_fd, PORT_SOURCE_FD, fd,
				    0, wi))
					log_error(LOG_WARNING,
					    "port_association of %d / %s "
					    "failed\n", fd, wi->wi_fmri);
				continue;
			}
		} else if (
		    (pe.portev_events & POLLERR) == 0) {
			if (port_associate(port_fd, PORT_SOURCE_FD, fd, 0, wi))
				log_error(LOG_WARNING,
				    "port_association of %d / %s "
				    "failed\n", fd, wi->wi_fmri);
			continue;
		}

err_remove:
		wait_remove(wi, 0);
	}

	/*LINTED E_FUNC_HAS_NO_RETURN_STMT*/
}
Пример #14
0
int
engine_import(uu_list_t *args)
{
	int ret, argc, i, o;
	bundle_t *b;
	char *file, *pname;
	uchar_t hash[MHASH_SIZE];
	char **argv;
	string_list_t *slp;
	boolean_t verify = B_FALSE;
	uint_t flags = SCI_GENERALLAST;

	argc = uu_list_numnodes(args);
	if (argc < 1)
		return (-2);

	argv = calloc(argc + 1, sizeof (char *));
	if (argv == NULL)
		uu_die(gettext("Out of memory.\n"));

	for (slp = uu_list_first(args), i = 0;
	    slp != NULL;
	    slp = uu_list_next(args, slp), ++i)
		argv[i] = slp->str;

	argv[i] = NULL;

	opterr = 0;
	optind = 0;				/* Remember, no argv[0]. */
	for (;;) {
		o = getopt(argc, argv, "nV");
		if (o == -1)
			break;

		switch (o) {
		case 'n':
			flags |= SCI_NOREFRESH;
			break;

		case 'V':
			verify = B_TRUE;
			break;

		case '?':
			free(argv);
			return (-2);

		default:
			bad_error("getopt", o);
		}
	}

	argc -= optind;
	if (argc != 1) {
		free(argv);
		return (-2);
	}

	file = argv[optind];
	free(argv);

	lscf_prep_hndl();

	ret = mhash_test_file(g_hndl, file, 0, &pname, hash);
	if (ret != MHASH_NEWFILE)
		return (ret);

	/* Load */
	b = internal_bundle_new();

	if (lxml_get_bundle_file(b, file, 0) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	/* Import */
	if (lscf_bundle_import(b, file, flags) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	internal_bundle_free(b);

	if (g_verbose)
		warn(gettext("Successful import.\n"));

	if (pname) {
		char *errstr;

		if (mhash_store_entry(g_hndl, pname, hash, &errstr)) {
			if (errstr)
				semerr(errstr);
			else
				semerr(gettext("Unknown error from "
					"mhash_store_entry()\n"));
		}

		free(pname);
	}

	/* Verify */
	if (verify)
		warn(gettext("import -V not implemented.\n"));

	return (0);
}