Пример #1
0
Group_desc *
ld_get_group(Ofl_desc *ofl, Is_desc *isp)
{
	Ifl_desc	*ifl = isp->is_file;
	uint_t		scnndx = isp->is_scnndx;
	Group_desc	*gdp;
	Aliste		idx;

	/*
	 * Scan the GROUP sections associated with this file to find the
	 * matching group section.
	 */
	for (ALIST_TRAVERSE(ifl->ifl_groups, idx, gdp)) {
		size_t	ndx;
		Word	*data;

		if (isp->is_shdr->sh_type == SHT_GROUP) {
			if (isp->is_scnndx == gdp->gd_isc->is_scnndx)
				return (gdp);
			continue;
		}

		data = gdp->gd_data;
		for (ndx = 1; ndx < gdp->gd_cnt; ndx++) {
			if (data[ndx] == scnndx)
				return (gdp);
		}
	}

	ld_eprintf(ofl, ERR_FATAL, MSG_INTL(MSG_ELF_NOGROUPSECT),
	    ifl->ifl_name, EC_WORD(isp->is_scnndx), isp->is_name);
	return (NULL);
}
Пример #2
0
/*
 * Clean up (free) an audit descriptor.  First, gather a list of all handles,
 * and then close each one down.  This is done rather than using the handles
 * directly from the auditors, as the audit list can be torn down as a result
 * of the dlclose.  In other words, what you're pointing at can be removed
 * while your still pointing at it.
 */
void
audit_desc_cleanup(Audit_desc *adp, Rt_map *clmp)
{
	Audit_list	*alp;
	Listnode	*lnp, *olnp;
	Alist		*ghalp = 0;

	if (adp == 0)
		return;
	if (adp->ad_name)
		free(adp->ad_name);

	olnp = 0;
	for (LIST_TRAVERSE(&(adp->ad_list), lnp, alp)) {
		(void) alist_append(&ghalp, &(alp->al_ghp), sizeof (Grp_hdl *),
		    AL_CNT_GROUPS);

		if (olnp)
			free(olnp);
		olnp = lnp;
	}
	if (olnp)
		free(olnp);

	if (ghalp) {
		Grp_hdl **	ghpp;
		Aliste		off;

		for (ALIST_TRAVERSE(ghalp, off, ghpp))
			(void) dlclose_intn(*ghpp, clmp);
		free(ghalp);
	}
	free(adp);
}
Пример #3
0
static Ver_index *
vers_index(Ofl_desc *ofl, Ifl_desc *ifl, int avail)
{
	size_t		idx1;
	Ver_desc	*vdp;
	Ver_index	*vip;
	Sdf_desc	*sdf = ifl->ifl_sdfdesc;
	Elf64_Word		count = ifl->ifl_vercnt;
	Sdv_desc	*sdv;

	/*
	 * Allocate an index array large enough to hold all of the files
	 * version descriptors.
	 */
	if ((vip = libld_calloc(sizeof (Ver_index), (count + 1))) == NULL)
		return ((Ver_index *)S_ERROR);

	for (APLIST_TRAVERSE(ifl->ifl_verdesc, idx1, vdp)) {
		int	ndx = vdp->vd_ndx;

		vip[ndx].vi_name = vdp->vd_name;
		vip[ndx].vi_desc = vdp;

		/*
		 * Any relocatable object versions, and the `base' version are
		 * always available.
		 */
		if (avail || (vdp->vd_flags & VER_FLG_BASE))
			vip[ndx].vi_flags |= FLG_VER_AVAIL;

		/*
		 * If this is a weak version mark it as such.  Weak versions
		 * are always dragged into any version dependencies created,
		 * and if a weak version is referenced it will be promoted to
		 * a non-weak version dependency.
		 */
		if (vdp->vd_flags & VER_FLG_WEAK)
			vip[ndx].vi_flags |= VER_FLG_WEAK;
		/*
		 * If this version is mentioned in a mapfile using ADDVERS
		 * syntax then check to see if it corresponds to an actual
		 * version in the file.
		 */
		if (sdf && (sdf->sdf_flags & FLG_SDF_ADDVER)) {
			size_t	idx2;

			for (ALIST_TRAVERSE(sdf->sdf_verneed, idx2, sdv)) {
				if (strcmp(vip[ndx].vi_name, sdv->sdv_name))
					continue;

				vip[ndx].vi_flags |= FLG_VER_REFER;
				sdv->sdv_flags |= FLG_SDV_MATCHED;
				break;
			}
		}
	}
Пример #4
0
/*
 * Traverse all segments looking for section ordering information that hasn't
 * been used.  If found give a warning message to the user.  Also, check if
 * there are any SHF_ORDERED key sections, and if so set up sort key values.
 */
void
ld_sec_validate(Ofl_desc *ofl)
{
	Listnode	*lnp1;
	Sg_desc		*sgp;
	int 		key = 1;

	for (LIST_TRAVERSE(&ofl->ofl_segs, lnp1, sgp)) {
		Sec_order	**scopp;
		Os_desc		**ospp;
		Aliste		off;

		for (ALIST_TRAVERSE(sgp->sg_secorder, off, scopp)) {
			Sec_order	*scop = *scopp;

			if ((scop->sco_flags & FLG_SGO_USED) == 0) {
				eprintf(ofl->ofl_lml, ERR_WARNING,
				    MSG_INTL(MSG_MAP_SECORDER),
				    sgp->sg_name, scop->sco_secname);
			}
		}
		if ((sgp->sg_flags & FLG_SG_KEY) == 0)
			continue;

		for (ALIST_TRAVERSE(sgp->sg_osdescs, off, ospp)) {
			Listnode	*lnp2;
			Is_desc		*isp;
			Os_desc		*osp = *ospp;

			if ((osp->os_flags & FLG_OS_ORDER_KEY) == 0)
				continue;

			for (LIST_TRAVERSE(&(osp->os_isdescs), lnp2, isp)) {
				if (isp->is_flags & FLG_IS_KEY)
					isp->is_key = key++;
			}
		}
	}
}
Пример #5
0
/*
 * Obtain a head link-map cookie.  Local auditors can provide la_preinit() and
 * la_activity() routines, and these routines require a cookie that represents
 * the object that heads the link-map of the object being audited.  A list of
 * these cookies is maintained on the link-map list.  This list allows multiple
 * local objects to specify the same auditor, and to obtain the same cookie
 * for the link-map that heads the link-map list.
 *
 * The initial cookie is created by _audit_create_head_client() which is called
 * from _audit_add_head().  This cookies address is then passed to the local
 * auditors ld_objopen() and la_activity() routines.  Subsequent preinit and
 * activity events use _audit_get_head_client() to dynamically retrieve the
 * cookies address.
 */
static Audit_client *
_audit_get_head_client(Rt_map *hlmp, Rt_map *almp)
{
	Audit_client	*acp;
	Aliste		idx;
	Lm_list		*hlml = LIST(hlmp);

	for (ALIST_TRAVERSE(hlml->lm_aud_cookies, idx, acp)) {
		if (acp->ac_lmp == almp)
			return (acp);
	}
	return (NULL);
}
Пример #6
0
/*
 * Add a section name to the output section sort list for the given
 * segment.
 *
 * entry:
 *	mf - Mapfile descriptor
 *	sgp - Segment in question
 *	sec_name - Name of section to be added.
 *
 * exit:
 *	Returns true for success, false for failure.
 */
bool
ld_map_seg_os_order_add(Mapfile *mf, Sg_desc *sgp, const char *sec_name)
{
	size_t		idx;
	Sec_order	*scop;

	/*
	 * Make sure it's not already on the list
	 */
	for (ALIST_TRAVERSE(sgp->sg_os_order, idx, scop))
		if (strcmp(scop->sco_secname, sec_name) == 0) {
			mf_fatal(mf, (MSG_MAP_DUP_OS_ORD), sec_name);
			return (false);
		}


	scop = alist_append(&sgp->sg_os_order, NULL, sizeof (Sec_order),
	    AL_CNT_SG_SECORDER);
	if (scop == NULL)
		return (false);

	scop->sco_secname = sec_name;

	//DBG_CALL(Dbg_map_seg_os_order(mf->mf_ofl->ofl_lml, sgp, sec_name,
	//    alist_nitems(sgp->sg_os_order), mf->mf_lineno));

	/*
	 * Output section ordering is a relatively expensive operation,
	 * and one that is generally not used. In order to avoid needless
	 * work, the FLG_OF_OS_ORDER must be set when it will be needed.
	 * The section we just added needs this flag to be set. However,
	 * it is possible that a subsequent mapfile directive may come
	 * along and clear the order list, making it unnecessary.
	 *
	 * Instead of setting it here, we do a final pass over the segments
	 * in ld_map_finalize() and set it there if a segment with sorting
	 * requirements is seen.
	 */

	return (true);
}
Пример #7
0
/*
 * Assign move descriptors with the associated target symbol.
 */
static uintptr_t
append_move_desc(Ofl_desc *ofl, Sym_desc *sdp, Elf64_Move *mvp, Is_desc *isp)
{
	int 	i, cnt = mvp->m_repeat;

	for (i = 0; i < cnt; i++) {
		size_t		idx;
		Mv_desc		*omdp, nmd;

		/* LINTED */
		nmd.md_len = ELF_M_SIZE(mvp->m_info);
		nmd.md_start = mvp->m_poffset + i *
		    ((mvp->m_stride + 1) * nmd.md_len);
		nmd.md_move = mvp;

		/*
		 * Verify that this move descriptor doesn't overlap any existing
		 * move descriptors.
		 */
		for (ALIST_TRAVERSE(sdp->sd_move, idx, omdp)) {
			Mv_desc	*smdp, *lmdp;

			if (nmd.md_start > omdp->md_start) {
				smdp = omdp;
				lmdp = &nmd;
			} else {
				smdp = &nmd;
				lmdp = omdp;
			}

			/*
			 * If this move entry is exactly the same as that of
			 * a symbol that has overridden this symbol (for example
			 * should two identical COMMON definitions be associated
			 * with the same move data), simply ignore this move
			 * element.
			 */
			if ((nmd.md_start == omdp->md_start) &&
			    ((nmd.md_len == smdp->md_len) &&
			    sdp->sd_file != isp->is_file))
				continue;

			if ((nmd.md_start != omdp->md_start) &&
			    ((smdp->md_start + smdp->md_len) <= lmdp->md_start))
				continue;

			ld_eprintf(ofl, ERR_FATAL, MSG_MOVE_OVERLAP,
			    sdp->sd_file->ifl_name, EC_WORD(isp->is_scnndx),
			    isp->is_name, demangle(sdp->sd_name),
			    EC_XWORD(nmd.md_start), EC_XWORD(nmd.md_len),
			    EC_XWORD(omdp->md_start), EC_XWORD(omdp->md_len));

			/*
			 * Indicate that an error has occurred, so that
			 * processing can be terminated once all move errors
			 * are flushed out.
			 */
			sdp->sd_flags |= FLG_SY_OVERLAP;
			return (1);
		}

		if (alist_append(&sdp->sd_move, &nmd, sizeof (Mv_desc),
		    AL_CNT_SDP_MOVE) == NULL)
			return (S_ERROR);
	}
	return (1);
}
Пример #8
0
/*
 * Enter a mapfile defined symbol into the given version
 *
 * entry:
 *	mf - Mapfile descriptor
 *	ms - Information related to symbol being added to version
 *
 * exit:
 *	On success, returns true. On failure that requires an immediate
 *	halt, returns false.
 *
 *	On failure that requires eventual halt, but for which it would
 *	be OK to continue parsing in hopes of flushing out additional
 *	problems, increments mv->mv_errcnt, and returns true.
 */
bool
ld_map_sym_enter(Mapfile *mf, ld_map_ver_t *mv, ld_map_sym_t *ms)
{
	Ofl_desc	*ofl = mf->mf_ofl;
	Elf64_Word	hash;
	avl_index_t	where;
	Elf64_Sym	*sym;
	Sym_desc	*sdp;
	const char	*conflict;

	/*
	 * Add the new symbol.  It should be noted that all
	 * symbols added by the mapfile start out with global
	 * scope, thus they will fall through the normal symbol
	 * resolution process.  Elf64_Symbols defined as locals will
	 * be reduced in scope after all input file processing.
	 */
	/* LINTED */
	hash = (Elf64_Word)elf_hash(ms->ms_name);
	//DBG_CALL(Dbg_map_version(ofl->ofl_lml, mv->mv_name, ms->ms_name,
	//    mv->mv_scope));

	/*
	 * Make sure that any parent or external declarations fall back to
	 * references.
	 */
	if (ms->ms_sdflags & (FLG_SY_PARENT | FLG_SY_EXTERN)) {
		/*
		 * Turn it into a reference by setting the section index
		 * to UNDEF.
		 */
		ms->ms_shndx = SHN_UNDEF;

		/*
		 * It is wrong to specify size or value for an external symbol.
		 */
		if (ms->ms_value_set || (ms->ms_size != 0)) {
			mf_fatal0(mf, (MSG_MAP_NOEXVLSZ));
			mv->mv_errcnt++;
			return (true);
		}
	}

	if ((sdp = ld_sym_find(ms->ms_name, hash, &where, ofl)) == NULL) {
		if ((sym = libld_calloc(sizeof (Elf64_Sym), 1)) == NULL)
			return (false);

		sym->st_shndx = (Elf64_Half)ms->ms_shndx;
		sym->st_value = ms->ms_value;
		sym->st_size = ms->ms_size;
		sym->st_info = ELF_ST_INFO(STB_GLOBAL, ms->ms_type);

		if ((sdp = ld_sym_enter(ms->ms_name, sym, hash,
		    ld_map_ifl(mf), ofl, 0, ms->ms_shndx, ms->ms_sdflags,
		    &where)) == (Sym_desc *)S_ERROR)
			return (false);

		sdp->sd_flags &= ~FLG_SY_CLEAN;

		/*
		 * Identify any references.  FLG_SY_MAPREF is
		 * turned off once a relocatable object with
		 * the same symbol is found, thus the existence
		 * of FLG_SY_MAPREF at symbol validation is
		 * used to flag undefined/misspelled entries.
		 */
		if (sym->st_shndx == SHN_UNDEF)
			sdp->sd_flags |= (FLG_SY_MAPREF | FLG_SY_GLOBREF);

	} else {
		conflict = NULL;
		sym = sdp->sd_sym;

		/*
		 * If this symbol already exists, make sure this
		 * definition doesn't conflict with the former.
		 * Provided it doesn't, multiple definitions
		 * from different mapfiles can augment each
		 * other.
		 */
		if (sym->st_value) {
			if (ms->ms_value && (sym->st_value != ms->ms_value))
				conflict = (MSG_MAP_DIFF_SYMVAL);
		} else {
			sym->st_value = ms->ms_value;
		}
		if (sym->st_size) {
			if (ms->ms_size && (sym->st_size != ms->ms_size))
				conflict = (MSG_MAP_DIFF_SYMSZ);
		} else {
			sym->st_size = ms->ms_size;
		}
		if (ELF_ST_TYPE(sym->st_info) != STT_NOTYPE) {
			if ((ms->ms_type != STT_NOTYPE) &&
			    (ELF_ST_TYPE(sym->st_info) != ms->ms_type))
				conflict = (MSG_MAP_DIFF_SYMTYP);
		} else {
			sym->st_info = ELF_ST_INFO(STB_GLOBAL, ms->ms_type);
		}
		if (sym->st_shndx != SHN_UNDEF) {
			if ((ms->ms_shndx != SHN_UNDEF) &&
			    (sym->st_shndx != ms->ms_shndx))
				conflict = (MSG_MAP_DIFF_SYMNDX);
		} else {
			sym->st_shndx = sdp->sd_shndx = ms->ms_shndx;
		}

		if ((sdp->sd_flags & MSK_SY_GLOBAL) &&
		    (sdp->sd_aux->sa_overndx != VER_NDX_GLOBAL) &&
		    (mv->mv_vdp->vd_ndx != VER_NDX_GLOBAL) &&
		    (sdp->sd_aux->sa_overndx != mv->mv_vdp->vd_ndx)) {
			conflict = (MSG_MAP_DIFF_SYMVER);
		}

		if (conflict) {
			mf_fatal(mf, (MSG_MAP_SYMDEF1),
			    demangle(ms->ms_name),
			    sdp->sd_file->ifl_name, conflict);
			mv->mv_errcnt++;
			return (true);
		}

		/*
		 * If this mapfile entry supplies a definition,
		 * indicate that the symbol is now used.
		 */
		if (ms->ms_shndx != SHN_UNDEF)
			sdp->sd_flags |= FLG_SY_MAPUSED;
	}

	/*
	 * A symbol declaration that defines a size but no
	 * value is processed as a request to create an
	 * associated backing section.  The intent behind this
	 * functionality is to provide OBJT definitions within
	 * filters that are not ABS.  ABS symbols don't allow
	 * copy-relocations to be established to filter OBJT
	 * definitions.
	 */
	if ((ms->ms_shndx == SHN_ABS) && ms->ms_size && !ms->ms_value_set) {
		/* Create backing section if not there */
		if (sdp->sd_isc == NULL) {
			Is_desc	*isp;

			if (ms->ms_type == STT_OBJECT) {
				if ((isp = ld_make_data(ofl, ms->ms_size)) ==
				    (Is_desc *)S_ERROR)
					return (false);
			} else {
				if ((isp = ld_make_text(ofl, ms->ms_size)) ==
				    (Is_desc *)S_ERROR)
					return (false);
			}

			sdp->sd_isc = isp;
			isp->is_file = ld_map_ifl(mf);
		}

		/*
		 * Now that backing storage has been created,
		 * associate the symbol descriptor.  Remove the
		 * symbols special section tag so that it will
		 * be assigned the correct section index as part
		 * of update symbol processing.
		 */
		sdp->sd_flags &= ~FLG_SY_SPECSEC;
		ms->ms_sdflags &= ~FLG_SY_SPECSEC;
	}

	/*
	 * Indicate the new symbols scope.  Although the
	 * symbols st_other field will eventually be updated as
	 * part of writing out the final symbol, update the
	 * st_other field here to trigger better diagnostics
	 * during symbol validation (for example, undefined
	 * references that are defined symbolic in a mapfile).
	 */
	if (mv->mv_scope == FLG_SCOPE_HIDD) {
		/*
		 * This symbol needs to be reduced to local.
		 */
		if (ofl->ofl_flags & FLG_OF_REDLSYM) {
			sdp->sd_flags |= (FLG_SY_HIDDEN | FLG_SY_ELIM);
			sdp->sd_sym->st_other = STV_ELIMINATE;
		} else {
			sdp->sd_flags |= FLG_SY_HIDDEN;
			sdp->sd_sym->st_other = STV_HIDDEN;
		}
	} else if (mv->mv_scope == FLG_SCOPE_ELIM) {
		/*
		 * This symbol needs to be eliminated.  Note,
		 * the symbol is also tagged as local to trigger
		 * any necessary relocation processing prior
		 * to the symbol being eliminated.
		 */
		sdp->sd_flags |= (FLG_SY_HIDDEN | FLG_SY_ELIM);
		sdp->sd_sym->st_other = STV_ELIMINATE;

	} else {
		/*
		 * This symbol is explicitly defined to remain
		 * global.
		 */
		sdp->sd_flags |= ms->ms_sdflags;

		/*
		 * Qualify any global scope.
		 */
		if (mv->mv_scope == FLG_SCOPE_SNGL) {
			sdp->sd_flags |= (FLG_SY_SINGLE | FLG_SY_NDIR);
			sdp->sd_sym->st_other = STV_SINGLETON;
		} else if (mv->mv_scope == FLG_SCOPE_PROT) {
			sdp->sd_flags |= FLG_SY_PROTECT;
			sdp->sd_sym->st_other = STV_PROTECTED;
		} else if (mv->mv_scope == FLG_SCOPE_EXPT) {
			sdp->sd_flags |= FLG_SY_EXPORT;
			sdp->sd_sym->st_other = STV_EXPORTED;
		} else
			sdp->sd_flags |= FLG_SY_DEFAULT;

		/*
		 * Record the present version index for later
		 * potential versioning.
		 */
		if ((sdp->sd_aux->sa_overndx == 0) ||
		    (sdp->sd_aux->sa_overndx == VER_NDX_GLOBAL))
			sdp->sd_aux->sa_overndx = mv->mv_vdp->vd_ndx;
		mv->mv_vdp->vd_flags |= FLG_VER_REFER;
	}

	conflict = NULL;

	/*
	 * Carry out some validity checks to ensure incompatible
	 * symbol characteristics have not been defined.
	 * These checks are carried out after symbols are added
	 * or resolved, to catch single instance, and
	 * multi-instance definition inconsistencies.
	 */
	if ((sdp->sd_flags & (FLG_SY_HIDDEN | FLG_SY_ELIM)) &&
	    ((mv->mv_scope != FLG_SCOPE_HIDD) &&
	    (mv->mv_scope != FLG_SCOPE_ELIM))) {
		conflict = (MSG_MAP_DIFF_SYMLCL);

	} else if ((sdp->sd_flags &
	    (FLG_SY_SINGLE | FLG_SY_EXPORT)) &&
	    ((mv->mv_scope != FLG_SCOPE_DFLT) &&
	    (mv->mv_scope != FLG_SCOPE_EXPT) &&
	    (mv->mv_scope != FLG_SCOPE_SNGL))) {
		conflict = (MSG_MAP_DIFF_SYMGLOB);

	} else if ((sdp->sd_flags & FLG_SY_PROTECT) &&
	    ((mv->mv_scope != FLG_SCOPE_DFLT) &&
	    (mv->mv_scope != FLG_SCOPE_PROT))) {
		conflict = (MSG_MAP_DIFF_SYMPROT);

	} else if ((sdp->sd_flags & FLG_SY_NDIR) &&
	    (mv->mv_scope == FLG_SCOPE_PROT)) {
		conflict = (MSG_MAP_DIFF_PROTNDIR);

	} else if ((sdp->sd_flags & FLG_SY_DIR) &&
	    (mv->mv_scope == FLG_SCOPE_SNGL)) {
		conflict = (MSG_MAP_DIFF_SNGLDIR);
	}

	if (conflict) {
		/*
		 * Select the conflict message from either a
		 * single instance or multi-instance definition.
		 */
		if (sdp->sd_file->ifl_name == mf->mf_name) {
			mf_fatal(mf, (MSG_MAP_SYMDEF2),
			    demangle(ms->ms_name), conflict);
		} else {
			mf_fatal(mf, (MSG_MAP_SYMDEF1),
			    demangle(ms->ms_name),
			    sdp->sd_file->ifl_name, conflict);
		}
		mv->mv_errcnt++;
		return (true);
	}

	/*
	 * Indicate that this symbol has been explicitly
	 * contributed from a mapfile.
	 */
	sdp->sd_flags |= (FLG_SY_MAPFILE | FLG_SY_EXPDEF);

	/*
	 * If we've encountered a symbol definition simulate
	 * that an input file has been processed - this allows
	 * things like filters to be created purely from a
	 * mapfile.
	 */
	if (ms->ms_type != STT_NOTYPE)
		ofl->ofl_objscnt++;
	//DBG_CALL(Dbg_map_symbol(ofl, sdp));

	/*
	 * If this symbol has an associated filtee, record the
	 * filtee string and associate the string index with the
	 * symbol.  This is used later to associate the syminfo
	 * information with the necessary .dynamic entry.
	 */
	if (ms->ms_filtee) {
		Dfltr_desc *	dftp;
		Sfltr_desc	sft;
		size_t		idx, _idx, nitems;

		/*
		 * Make sure we don't duplicate any filtee
		 * strings, and create a new descriptor if
		 * necessary.
		 */
		idx = nitems = alist_nitems(ofl->ofl_dtsfltrs);
		for (ALIST_TRAVERSE(ofl->ofl_dtsfltrs, _idx, dftp)) {
			if ((ms->ms_dft_flag != dftp->dft_flag) ||
			    (strcmp(dftp->dft_str, ms->ms_filtee)))
				continue;
			idx = _idx;
			break;
		}
		if (idx == nitems) {
			Dfltr_desc	dft;

			dft.dft_str = ms->ms_filtee;
			dft.dft_flag = ms->ms_dft_flag;
			dft.dft_ndx = 0;

			/*
			 * The following append puts the new
			 * item at the offset contained in
			 * idx, because we know idx contains
			 * the index of the next available slot.
			 */
			if (alist_append(&ofl->ofl_dtsfltrs, &dft,
			    sizeof (Dfltr_desc), AL_CNT_OFL_DTSFLTRS) == NULL)
				return (false);
		}

		/*
		 * Create a new filter descriptor for this
		 * symbol.
		 */
		sft.sft_sdp = sdp;
		sft.sft_idx = idx;

		if (alist_append(&ofl->ofl_symfltrs, &sft, sizeof (Sfltr_desc),
		    AL_CNT_OFL_SYMFLTRS) == NULL)
			return (false);
	}