/*
 * Decode the specified CTF buffer and optional symbol table and create a new
 * CTF container representing the symbolic debugging information.  This code
 * can be used directly by the debugger, or it can be used as the engine for
 * ctf_fdopen() or ctf_open(), below.
 */
ctf_file_t *
ctf_bufopen(const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
    const ctf_sect_t *strsect, int *errp)
{
	const ctf_preamble_t *pp;
	ctf_header_t hp;
	ctf_file_t *fp;
	void *buf, *base;
	size_t size, hdrsz;
	int err;

	if (ctfsect == NULL || ((symsect == NULL) != (strsect == NULL)))
		return (ctf_set_open_errno(errp, EINVAL));

	if (symsect != NULL && symsect->cts_entsize != sizeof (struct nlist) &&
	    symsect->cts_entsize != sizeof (struct nlist_64))
		return (ctf_set_open_errno(errp, ECTF_SYMTAB));

	if (symsect != NULL && symsect->cts_data == NULL)
		return (ctf_set_open_errno(errp, ECTF_SYMBAD));

	if (strsect != NULL && strsect->cts_data == NULL)
		return (ctf_set_open_errno(errp, ECTF_STRBAD));

	if (ctfsect->cts_size < sizeof (ctf_preamble_t))
		return (ctf_set_open_errno(errp, ECTF_NOCTFBUF));

	pp = (const ctf_preamble_t *)ctfsect->cts_data;

	ctf_dprintf("ctf_bufopen: magic=0x%x version=%u\n",
	    pp->ctp_magic, pp->ctp_version);

	/*
	 * Validate each part of the CTF header (either V1 or V2).
	 * First, we validate the preamble (common to all versions).  At that
	 * point, we know specific header version, and can validate the
	 * version-specific parts including section offsets and alignments.
	 */
	if (pp->ctp_magic != CTF_MAGIC)
		return (ctf_set_open_errno(errp, ECTF_NOCTFBUF));

	if (pp->ctp_version == CTF_VERSION_2) {
		if (ctfsect->cts_size < sizeof (ctf_header_t))
			return (ctf_set_open_errno(errp, ECTF_NOCTFBUF));

		bcopy(ctfsect->cts_data, &hp, sizeof (hp));
		hdrsz = sizeof (ctf_header_t);

	} else if (pp->ctp_version == CTF_VERSION_1) {
		const ctf_header_v1_t *h1p =
		    (const ctf_header_v1_t *)ctfsect->cts_data;

		if (ctfsect->cts_size < sizeof (ctf_header_v1_t))
			return (ctf_set_open_errno(errp, ECTF_NOCTFBUF));

		bzero(&hp, sizeof (hp));
		hp.cth_preamble = h1p->cth_preamble;
		hp.cth_objtoff = h1p->cth_objtoff;
		hp.cth_funcoff = h1p->cth_funcoff;
		hp.cth_typeoff = h1p->cth_typeoff;
		hp.cth_stroff = h1p->cth_stroff;
		hp.cth_strlen = h1p->cth_strlen;

		hdrsz = sizeof (ctf_header_v1_t);
	} else
		return (ctf_set_open_errno(errp, ECTF_CTFVERS));

	size = hp.cth_stroff + hp.cth_strlen;

	ctf_dprintf("ctf_bufopen: uncompressed size=%lu\n", (ulong_t)size);

	if (hp.cth_lbloff > size || hp.cth_objtoff > size ||
	    hp.cth_funcoff > size || hp.cth_typeoff > size ||
	    hp.cth_stroff > size)
		return (ctf_set_open_errno(errp, ECTF_CORRUPT));

	if (hp.cth_lbloff > hp.cth_objtoff ||
	    hp.cth_objtoff > hp.cth_funcoff ||
	    hp.cth_funcoff > hp.cth_typeoff ||
	    hp.cth_typeoff > hp.cth_stroff)
		return (ctf_set_open_errno(errp, ECTF_CORRUPT));

	if ((hp.cth_lbloff & 3) || (hp.cth_objtoff & 1) ||
	    (hp.cth_funcoff & 1) || (hp.cth_typeoff & 3))
		return (ctf_set_open_errno(errp, ECTF_CORRUPT));

	/*
	 * Once everything is determined to be valid, attempt to decompress
	 * the CTF data buffer if it is compressed.  Otherwise we just put
	 * the data section's buffer pointer into ctf_buf, below.
	 */
	if (hp.cth_flags & CTF_F_COMPRESS) {
		size_t srclen, dstlen;
		const void *src;
		int rc = Z_OK;

		if (ctf_zopen(errp) == NULL)
			return (NULL); /* errp is set for us */

		if ((base = ctf_data_alloc(size + hdrsz)) == MAP_FAILED)
			return (ctf_set_open_errno(errp, ECTF_ZALLOC));

		bcopy(ctfsect->cts_data, base, hdrsz);
		((ctf_preamble_t *)base)->ctp_flags &= ~CTF_F_COMPRESS;
		buf = (uchar_t *)base + hdrsz;

		src = (uchar_t *)ctfsect->cts_data + hdrsz;
		srclen = ctfsect->cts_size - hdrsz;
		dstlen = size;

		if ((rc = z_uncompress(buf, &dstlen, src, srclen)) != Z_OK) {
			ctf_dprintf("zlib inflate err: %s\n", z_strerror(rc));
			ctf_data_free(base, size + hdrsz);
			return (ctf_set_open_errno(errp, ECTF_DECOMPRESS));
		}

		if (dstlen != size) {
			ctf_dprintf("zlib inflate short -- got %lu of %lu "
			    "bytes\n", (ulong_t)dstlen, (ulong_t)size);
			ctf_data_free(base, size + hdrsz);
			return (ctf_set_open_errno(errp, ECTF_CORRUPT));
		}

		ctf_data_protect(base, size + hdrsz);

	} else {
		base = (void *)ctfsect->cts_data;
		buf = (uchar_t *)base + hdrsz;
	}

	/*
	 * Once we have uncompressed and validated the CTF data buffer, we can
	 * proceed with allocating a ctf_file_t and initializing it.
	 */
	if ((fp = ctf_alloc(sizeof (ctf_file_t))) == NULL)
		return (ctf_set_open_errno(errp, EAGAIN));

	bzero(fp, sizeof (ctf_file_t));
	fp->ctf_version = hp.cth_version;
	fp->ctf_fileops = &ctf_fileops[hp.cth_version];
	bcopy(ctfsect, &fp->ctf_data, sizeof (ctf_sect_t));

	if (symsect != NULL) {
		bcopy(symsect, &fp->ctf_symtab, sizeof (ctf_sect_t));
		bcopy(strsect, &fp->ctf_strtab, sizeof (ctf_sect_t));
	}

	if (fp->ctf_data.cts_name != NULL)
		fp->ctf_data.cts_name = ctf_strdup(fp->ctf_data.cts_name);
	if (fp->ctf_symtab.cts_name != NULL)
		fp->ctf_symtab.cts_name = ctf_strdup(fp->ctf_symtab.cts_name);
	if (fp->ctf_strtab.cts_name != NULL)
		fp->ctf_strtab.cts_name = ctf_strdup(fp->ctf_strtab.cts_name);

	if (fp->ctf_data.cts_name == NULL)
		fp->ctf_data.cts_name = _CTF_NULLSTR;
	if (fp->ctf_symtab.cts_name == NULL)
		fp->ctf_symtab.cts_name = _CTF_NULLSTR;
	if (fp->ctf_strtab.cts_name == NULL)
		fp->ctf_strtab.cts_name = _CTF_NULLSTR;

	fp->ctf_str[CTF_STRTAB_0].cts_strs = (const char *)buf + hp.cth_stroff;
	fp->ctf_str[CTF_STRTAB_0].cts_len = hp.cth_strlen;

	if (strsect != NULL) {
		fp->ctf_str[CTF_STRTAB_1].cts_strs = strsect->cts_data;
		fp->ctf_str[CTF_STRTAB_1].cts_len = strsect->cts_size;
	}

	fp->ctf_base = base;
	fp->ctf_buf = buf;
	fp->ctf_size = size + hdrsz;

	/*
	 * If we have a parent container name and label, store the relocated
	 * string pointers in the CTF container for easy access later.
	 */
	if (hp.cth_parlabel != 0)
		fp->ctf_parlabel = ctf_strptr(fp, hp.cth_parlabel);
	if (hp.cth_parname != 0)
		fp->ctf_parname = ctf_strptr(fp, hp.cth_parname);

	ctf_dprintf("ctf_bufopen: parent name %s (label %s)\n",
	    fp->ctf_parname ? fp->ctf_parname : "<NULL>",
	    fp->ctf_parlabel ? fp->ctf_parlabel : "<NULL>");

	/*
	 * If we have a symbol table section, allocate and initialize
	 * the symtab translation table, pointed to by ctf_sxlate.
	 */
	if (symsect != NULL) {
		fp->ctf_nsyms = symsect->cts_size / symsect->cts_entsize;
		fp->ctf_sxlate = ctf_alloc(fp->ctf_nsyms * sizeof (uint_t));

		if (fp->ctf_sxlate == NULL) {
			(void) ctf_set_open_errno(errp, EAGAIN);
			goto bad;
		}

		if ((err = init_symtab(fp, &hp, symsect, strsect)) != 0) {
			(void) ctf_set_open_errno(errp, err);
			goto bad;
		}
	}

	if ((err = init_types(fp, &hp)) != 0) {
		(void) ctf_set_open_errno(errp, err);
		goto bad;
	}

	/*
	 * Initialize the ctf_lookup_by_name top-level dictionary.  We keep an
	 * array of type name prefixes and the corresponding ctf_hash to use.
	 * NOTE: This code must be kept in sync with the code in ctf_update().
	 */
	fp->ctf_lookups[0].ctl_prefix = "struct";
	fp->ctf_lookups[0].ctl_len = strlen(fp->ctf_lookups[0].ctl_prefix);
	fp->ctf_lookups[0].ctl_hash = &fp->ctf_structs;
	fp->ctf_lookups[1].ctl_prefix = "union";
	fp->ctf_lookups[1].ctl_len = strlen(fp->ctf_lookups[1].ctl_prefix);
	fp->ctf_lookups[1].ctl_hash = &fp->ctf_unions;
	fp->ctf_lookups[2].ctl_prefix = "enum";
	fp->ctf_lookups[2].ctl_len = strlen(fp->ctf_lookups[2].ctl_prefix);
	fp->ctf_lookups[2].ctl_hash = &fp->ctf_enums;
	fp->ctf_lookups[3].ctl_prefix = _CTF_NULLSTR;
	fp->ctf_lookups[3].ctl_len = strlen(fp->ctf_lookups[3].ctl_prefix);
	fp->ctf_lookups[3].ctl_hash = &fp->ctf_names;
	fp->ctf_lookups[4].ctl_prefix = NULL;
	fp->ctf_lookups[4].ctl_len = 0;
	fp->ctf_lookups[4].ctl_hash = NULL;

	if (symsect != NULL) {
		if (symsect->cts_entsize == sizeof (struct nlist_64))
			(void) ctf_setmodel(fp, CTF_MODEL_LP64);
		else if (symsect->cts_entsize == sizeof (struct nlist))
			(void) ctf_setmodel(fp, CTF_MODEL_ILP32);
		else if (symsect->cts_entsize == sizeof (Elf64_Sym))
			(void) ctf_setmodel(fp, CTF_MODEL_LP64);
		else
			(void) ctf_setmodel(fp, CTF_MODEL_ILP32);
	} else
		(void) ctf_setmodel(fp, CTF_MODEL_NATIVE);

	fp->ctf_refcnt = 1;
	return (fp);

bad:
	ctf_close(fp);
	return (NULL);
}
static int
ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags)
{
	GElf_Ehdr sehdr, dehdr;
	Elf_Scn *sscn, *dscn;
	Elf_Data *sdata, *ddata;
	GElf_Shdr shdr;
	int symtab_idx = -1;
	off_t new_offset = 0;
	off_t ctfnameoff = 0;
	int compress = (flags & CTF_ELFWRITE_F_COMPRESS);
	int *secxlate = NULL;
	int srcidx, dstidx, pad, i;
	int curnmoff = 0;
	int changing = 0;
	int ret;
	size_t nshdr, nphdr, strndx;
	void *strdatabuf = NULL, *symdatabuf = NULL;
	size_t strdatasz = 0, symdatasz = 0;

	void *cdata = NULL;
	size_t elfsize, asize;

	if ((flags & ~(CTF_ELFWRITE_F_COMPRESS)) != 0) {
		ret = ctf_set_errno(fp, EINVAL);
		goto out;
	}

	if (gelf_newehdr(dst, gelf_getclass(src)) == 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	if (gelf_getehdr(src, &sehdr) == NULL) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	(void) memcpy(&dehdr, &sehdr, sizeof (GElf_Ehdr));
	if (gelf_update_ehdr(dst, &dehdr) == 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	/*
	 * Use libelf to get the number of sections and the string section to
	 * deal with ELF files that may have a large number of sections. We just
	 * always use this to make our live easier.
	 */
	if (elf_getphdrnum(src, &nphdr) != 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	if (elf_getshdrnum(src, &nshdr) != 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	if (elf_getshdrstrndx(src, &strndx) != 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	/*
	 * Neither the existing debug sections nor the SUNW_ctf sections (new or
	 * existing) are SHF_ALLOC'd, so they won't be in areas referenced by
	 * program headers.  As such, we can just blindly copy the program
	 * headers from the existing file to the new file.
	 */
	if (nphdr != 0) {
		(void) elf_flagelf(dst, ELF_C_SET, ELF_F_LAYOUT);
		if (gelf_newphdr(dst, nphdr) == 0) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}

		for (i = 0; i < nphdr; i++) {
			GElf_Phdr phdr;

			if (gelf_getphdr(src, i, &phdr) == NULL) {
				ret = ctf_set_errno(fp, ECTF_ELF);
				goto out;
			}
			if (gelf_update_phdr(dst, i, &phdr) == 0) {
				ret = ctf_set_errno(fp, ECTF_ELF);
				goto out;
			}
		}
	}

	secxlate = ctf_alloc(sizeof (int) * nshdr);
	for (srcidx = dstidx = 0; srcidx < nshdr; srcidx++) {
		Elf_Scn *scn = elf_getscn(src, srcidx);
		GElf_Shdr shdr;
		char *sname;

		if (gelf_getshdr(scn, &shdr) == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}
		sname = elf_strptr(src, strndx, shdr.sh_name);
		if (sname == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}

		if (strcmp(sname, CTF_ELF_SCN_NAME) == 0) {
			secxlate[srcidx] = -1;
		} else {
			secxlate[srcidx] = dstidx++;
			curnmoff += strlen(sname) + 1;
		}

		new_offset = (off_t)dehdr.e_phoff;
	}

	for (srcidx = 1; srcidx < nshdr; srcidx++) {
		char *sname;

		sscn = elf_getscn(src, srcidx);
		if (gelf_getshdr(sscn, &shdr) == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}

		if (secxlate[srcidx] == -1) {
			changing = 1;
			continue;
		}

		dscn = elf_newscn(dst);
		if (dscn == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}

		/*
		 * If this file has program headers, we need to explicitly lay
		 * out sections.  If none of the sections prior to this one have
		 * been removed, then we can just use the existing location.  If
		 * one or more sections have been changed, then we need to
		 * adjust this one to avoid holes.
		 */
		if (changing && nphdr != 0) {
			pad = new_offset % shdr.sh_addralign;

			if (pad != 0)
				new_offset += shdr.sh_addralign - pad;
			shdr.sh_offset = new_offset;
		}

		shdr.sh_link = secxlate[shdr.sh_link];

		if (shdr.sh_type == SHT_REL || shdr.sh_type == SHT_RELA)
			shdr.sh_info = secxlate[shdr.sh_info];

		sname = elf_strptr(src, strndx, shdr.sh_name);
		if (sname == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}
		if ((sdata = elf_getdata(sscn, NULL)) == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}
		if ((ddata = elf_newdata(dscn)) == NULL) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}
		bcopy(sdata, ddata, sizeof (Elf_Data));

		if (srcidx == strndx) {
			char seclen = strlen(CTF_ELF_SCN_NAME);

			strdatasz = ddata->d_size + shdr.sh_size +
			    seclen + 1;
			ddata->d_buf = strdatabuf = ctf_alloc(strdatasz);
			if (ddata->d_buf == NULL) {
				ret = ctf_set_errno(fp, ECTF_ELF);
				goto out;
			}
			bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size);
			(void) strcpy((caddr_t)ddata->d_buf + shdr.sh_size,
			    CTF_ELF_SCN_NAME);
			ctfnameoff = (off_t)shdr.sh_size;
			shdr.sh_size += seclen + 1;
			ddata->d_size += seclen + 1;

			if (nphdr != 0)
				changing = 1;
		}

		if (shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
			int nsym = shdr.sh_size / shdr.sh_entsize;

			symtab_idx = secxlate[srcidx];

			symdatasz = shdr.sh_size;
			ddata->d_buf = symdatabuf = ctf_alloc(symdatasz);
			if (ddata->d_buf == NULL) {
				ret = ctf_set_errno(fp, ECTF_ELF);
				goto out;
			}
			(void) bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size);

			for (i = 0; i < nsym; i++) {
				GElf_Sym sym;
				short newscn;

				(void) gelf_getsym(ddata, i, &sym);

				if (sym.st_shndx >= SHN_LORESERVE)
					continue;

				if ((newscn = secxlate[sym.st_shndx]) !=
				    sym.st_shndx) {
					sym.st_shndx =
					    (newscn == -1 ? 1 : newscn);

					if (gelf_update_sym(ddata, i, &sym) ==
					    0) {
						ret = ctf_set_errno(fp,
						    ECTF_ELF);
						goto out;
					}
				}
			}
		}

		if (gelf_update_shdr(dscn, &shdr) == 0) {
			ret = ctf_set_errno(fp, ECTF_ELF);
			goto out;
		}

		new_offset = (off_t)shdr.sh_offset;
		if (shdr.sh_type != SHT_NOBITS)
			new_offset += shdr.sh_size;
	}

	if (symtab_idx == -1) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	/* Add the ctf section */
	if ((dscn = elf_newscn(dst)) == NULL) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	if (gelf_getshdr(dscn, &shdr) == NULL) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	shdr.sh_name = ctfnameoff;
	shdr.sh_type = SHT_PROGBITS;
	shdr.sh_size = fp->ctf_size;
	shdr.sh_link = symtab_idx;
	shdr.sh_addralign = 4;
	if (changing && nphdr != 0) {
		pad = new_offset % shdr.sh_addralign;

		if (pad)
			new_offset += shdr.sh_addralign - pad;

		shdr.sh_offset = new_offset;
		new_offset += shdr.sh_size;
	}

	if ((ddata = elf_newdata(dscn)) == NULL) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	if (compress != 0) {
		int err;

		if (ctf_zopen(&err) == NULL) {
			ret = ctf_set_errno(fp, err);
			goto out;
		}

		if ((err = ctf_compress(fp, &cdata, &asize, &elfsize)) != 0) {
			ret = ctf_set_errno(fp, err);
			goto out;
		}
		ddata->d_buf = cdata;
		ddata->d_size = elfsize;
	} else {
		ddata->d_buf = (void *)fp->ctf_base;
		ddata->d_size = fp->ctf_size;
	}
	ddata->d_align = shdr.sh_addralign;

	if (gelf_update_shdr(dscn, &shdr) == 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	/* update the section header location */
	if (nphdr != 0) {
		size_t align = gelf_fsize(dst, ELF_T_ADDR, 1, EV_CURRENT);
		size_t r = new_offset % align;

		if (r)
			new_offset += align - r;

		dehdr.e_shoff = new_offset;
	}

	/* commit to disk */
	if (sehdr.e_shstrndx == SHN_XINDEX)
		dehdr.e_shstrndx = SHN_XINDEX;
	else
		dehdr.e_shstrndx = secxlate[sehdr.e_shstrndx];
	if (gelf_update_ehdr(dst, &dehdr) == 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}
	if (elf_update(dst, ELF_C_WRITE) < 0) {
		ret = ctf_set_errno(fp, ECTF_ELF);
		goto out;
	}

	ret = 0;

out:
	if (strdatabuf != NULL)
		ctf_free(strdatabuf, strdatasz);
	if (symdatabuf != NULL)
		ctf_free(symdatabuf, symdatasz);
	if (cdata != NULL)
		ctf_data_free(cdata, fp->ctf_size);
	if (secxlate != NULL)
		ctf_free(secxlate, sizeof (int) * nshdr);

	return (ret);
}