Blame src/driver/slbt_split_argv.c

1ed017
/*******************************************************************/
eac61a
/*  slibtool: a strong libtool implementation, written in C        */
49181b
/*  Copyright (C) 2016--2024  SysDeer Technologies, LLC            */
1ed017
/*  Released under the Standard MIT License; see COPYING.SLIBTOOL. */
1ed017
/*******************************************************************/
1ed017
1ed017
#include <stdint.h>
1ed017
#include <string.h>
1ed017
#include <unistd.h>
1ed017
#include <stdlib.h>
1ed017
#include <stdbool.h>
1ed017
1ed017
#include <slibtool/slibtool.h>
1ed017
#include "slibtool_version.h"
1ed017
#include "slibtool_driver_impl.h"
1ed017
#include "slibtool_objlist_impl.h"
1ed017
#include "slibtool_errinfo_impl.h"
4b56de
#include "slibtool_visibility_impl.h"
4c3fb7
#include "slibtool_stoolie_impl.h"
1ed017
#include "slibtool_ar_impl.h"
1ed017
#include "argv/argv.h"
1ed017
1ed017
static char * slbt_default_cargv[] = {"cc",0};
1ed017
4b56de
slbt_hidden int slbt_split_argv(
1ed017
	char **				argv,
1ed017
	uint64_t			flags,
1ed017
	struct slbt_split_vector *	sargv,
1ed017
	struct slbt_obj_list **		aobjlistv,
1ed017
	int				fderr,
1ed017
	int				fdcwd)
1ed017
{
1ed017
	int				i;
1ed017
	int				argc;
1ed017
	int				objc;
1ed017
	const char *			program;
1ed017
	char *				compiler;
1ed017
	char *				csysroot;
1ed017
	char **				dargv;
1ed017
	char **				targv;
1ed017
	char **				cargv;
1ed017
	char **				objp;
1ed017
	struct slbt_obj_list *		objlistv;
1ed017
	struct slbt_obj_list *		objlistp;
1ed017
	char *				dst;
1ed017
	bool				flast;
1ed017
	bool				fcopy;
1ed017
	bool				altmode;
1ed017
	size_t				size;
1ed017
	const char *			base;
1ed017
	struct argv_meta *		meta;
1ed017
	struct argv_entry *		entry;
1ed017
	struct argv_entry *		mode;
1ed017
	struct argv_entry *		help;
1ed017
	struct argv_entry *		version;
c57816
	struct argv_entry *		info;
c1f216
	struct argv_entry *		config;
1ed017
	struct argv_entry *		finish;
1ed017
	struct argv_entry *		features;
1ed017
	struct argv_entry *		ccwrap;
1ed017
	struct argv_entry *		dumpmachine;
805666
	struct argv_entry *		printdir;
1ed017
	struct argv_entry *		aropt;
4c3fb7
	struct argv_entry *		stoolieopt;
1ed017
	const struct argv_option **	popt;
1ed017
	const struct argv_option **	optout;
1ed017
	const struct argv_option *	optv[SLBT_OPTV_ELEMENTS];
1ed017
	struct argv_ctx			ctx = {ARGV_VERBOSITY_NONE,
1ed017
						ARGV_MODE_SCAN,
1ed017
						0,0,0,0,0,0,0};
1ed017
c3d88b
	program = slbt_program_name(argv[0]);
1ed017
1ed017
	/* missing arguments? */
1ed017
	if ((altmode = (flags & SLBT_DRIVER_MODE_AR))) {
c3d88b
		slbt_optv_init(slbt_ar_options,optv);
4c3fb7
	} else if ((altmode = (flags & SLBT_DRIVER_MODE_STOOLIE))) {
4c3fb7
		slbt_optv_init(slbt_stoolie_options,optv);
1ed017
	} else {
c3d88b
		slbt_optv_init(slbt_default_options,optv);
1ed017
	}
1ed017
1ed017
1ed017
	if (!argv[1] && !altmode && (flags & SLBT_DRIVER_VERBOSITY_USAGE))
1ed017
		return slbt_driver_usage(
1ed017
			fderr,program,
1ed017
			0,optv,0,sargv,0,
1ed017
			!!getenv("NO_COLOR"));
1ed017
1ed017
	/* initial argv scan: ... --mode=xxx ... <compiler> ... */
c3d88b
	slbt_argv_scan(argv,optv,&ctx,0);
1ed017
1ed017
	/* invalid slibtool arguments? */
1ed017
	if (ctx.erridx && !ctx.unitidx && altmode) {
1ed017
		if (flags & SLBT_DRIVER_VERBOSITY_ERRORS)
c3d88b
			slbt_argv_get(
1ed017
				argv,optv,
1ed017
				slbt_argv_flags(flags),
1ed017
				fderr);
1ed017
		return -1;
1ed017
	}
1ed017
1ed017
	/* error possibly due to an altmode argument? */
1ed017
	if (ctx.erridx && !ctx.unitidx)
1ed017
		ctx.unitidx = ctx.erridx;
1ed017
1ed017
	/* obtain slibtool's own arguments */
1ed017
	if (ctx.unitidx) {
1ed017
		compiler = argv[ctx.unitidx];
1ed017
		argv[ctx.unitidx] = 0;
1ed017
c3d88b
		meta = slbt_argv_get(argv,optv,ARGV_VERBOSITY_NONE,fderr);
1ed017
		argv[ctx.unitidx] = compiler;
1ed017
	} else {
c3d88b
		meta = slbt_argv_get(argv,optv,ARGV_VERBOSITY_NONE,fderr);
1ed017
	}
1ed017
1ed017
	if (!meta) {
1ed017
		if (flags & SLBT_DRIVER_VERBOSITY_ERRORS)
c3d88b
			slbt_argv_get(
1ed017
				argv,optv,
1ed017
				slbt_argv_flags(flags),
1ed017
				fderr);
1ed017
		return -1;
1ed017
	}
1ed017
c1f216
	/* missing all of --mode, --help, --version, --info, --config, --dumpmachine, --features, and --finish? */
805666
	/* as well as -print-aux-dir and -print-m4-dir? */
805666
	mode = help = version = info = config = finish = features = ccwrap = dumpmachine = printdir = aropt = stoolieopt = 0;
1ed017
1ed017
	for (entry=meta->entries; entry->fopt; entry++)
1ed017
		if (entry->tag == TAG_MODE)
1ed017
			mode = entry;
1ed017
		else if (entry->tag == TAG_HELP)
1ed017
			help = entry;
1ed017
		else if (entry->tag == TAG_VERSION)
1ed017
			version = entry;
c57816
		else if (entry->tag == TAG_INFO)
c57816
			info = entry;
c1f216
		else if (entry->tag == TAG_CONFIG)
c1f216
			config = entry;
1ed017
		else if (entry->tag == TAG_FINISH)
1ed017
			finish = entry;
1ed017
		else if (entry->tag == TAG_FEATURES)
1ed017
			features = entry;
1ed017
		else if (entry->tag == TAG_CCWRAP)
1ed017
			ccwrap = entry;
1ed017
		else if (entry->tag == TAG_DUMPMACHINE)
1ed017
			dumpmachine = entry;
805666
		else if (entry->tag == TAG_PRINT_AUX_DIR)
805666
			printdir = entry;
805666
		else if (entry->tag == TAG_PRINT_M4_DIR)
805666
			printdir = entry;
1ed017
1ed017
	/* alternate execusion mode? */
1ed017
	if (!altmode && mode && !strcmp(mode->arg,"ar"))
1ed017
		aropt = mode;
1ed017
4c3fb7
	if (!altmode && mode && !strcmp(mode->arg,"stoolie"))
4c3fb7
		stoolieopt = mode;
4c3fb7
4c3fb7
	if (!altmode && mode && !strcmp(mode->arg,"slibtoolize"))
4c3fb7
		stoolieopt = mode;
4c3fb7
1ed017
	/* release temporary argv meta context */
c3d88b
	slbt_argv_free(meta);
1ed017
1ed017
	/* error not due to an altmode argument? */
4c3fb7
	if (!aropt && !stoolieopt && ctx.erridx && (ctx.erridx == ctx.unitidx)) {
1ed017
		if (flags & SLBT_DRIVER_VERBOSITY_ERRORS)
c3d88b
			slbt_argv_get(
1ed017
				argv,optv,
1ed017
				slbt_argv_flags(flags),
1ed017
				fderr);
1ed017
		return -1;
1ed017
	}
1ed017
805666
	if (!mode && !help && !version && !info && !config && !finish && !features && !dumpmachine && !printdir && !altmode) {
1ed017
		slbt_dprintf(fderr,
1ed017
			"%s: error: --mode must be specified.\n",
1ed017
			program);
1ed017
		return -1;
1ed017
	}
1ed017
1ed017
	/* missing compiler? */
805666
	if (!ctx.unitidx && !help && !info && !config && !version && !finish && !features && !dumpmachine && !printdir) {
4c3fb7
		if (!altmode && !aropt && !stoolieopt) {
4c3fb7
			if (flags & SLBT_DRIVER_VERBOSITY_ERRORS)
4c3fb7
				slbt_dprintf(fderr,
4c3fb7
					"%s: error: <compiler> is missing.\n",
4c3fb7
					program);
4c3fb7
			return -1;
4c3fb7
		}
1ed017
	}
1ed017
e035ec
	/* clone and normalize the argv vector */
1ed017
	for (argc=0,size=0,dargv=argv; *dargv; argc++,dargv++)
1ed017
		size += strlen(*dargv) + 1;
1ed017
1ed017
	if (!(sargv->dargv = calloc(argc+1,sizeof(char *))))
1ed017
		return -1;
1ed017
1ed017
	else if (!(sargv->dargs = calloc(1,size+1)))
1ed017
		return -1;
1ed017
1ed017
	else if (!(*aobjlistv = calloc(argc,sizeof(**aobjlistv)))) {
1ed017
		free(sargv->dargv);
1ed017
		free(sargv->dargs);
1ed017
		return -1;
1ed017
	}
1ed017
1ed017
	objlistv = *aobjlistv;
1ed017
	objlistp = objlistv;
1ed017
	csysroot = 0;
1ed017
1ed017
	for (i=0,flast=false,dargv=sargv->dargv,dst=sargv->dargs; i
4c3fb7
		if ((fcopy = (flast || altmode || aropt || stoolieopt))) {
1ed017
			(void)0;
1ed017
1ed017
		} else if (!strcmp(argv[i],"--")) {
1ed017
			flast = true;
1ed017
			fcopy = true;
1ed017
e035ec
		} else if (!strcmp(argv[i],"-I")) {
e035ec
			*dargv++ = dst;
e035ec
			*dst++ = '-';
e035ec
			*dst++ = 'I';
e035ec
			strcpy(dst,argv[++i]);
e035ec
			dst += strlen(dst)+1;
e035ec
e035ec
		} else if (!strncmp(argv[i],"-I",2)) {
e035ec
			fcopy = true;
e035ec
1ed017
		} else if (!strcmp(argv[i],"-l")) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'l';
1ed017
			strcpy(dst,argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strncmp(argv[i],"-l",2)) {
1ed017
			fcopy = true;
1ed017
1ed017
		} else if (!strcmp(argv[i],"--library")) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'l';
1ed017
			strcpy(dst,argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strncmp(argv[i],"--library=",10)) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'l';
1ed017
			strcpy(dst,&argv[++i][10]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strcmp(argv[i],"-L")) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'L';
1ed017
			strcpy(dst,argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strncmp(argv[i],"-L",2)) {
1ed017
			fcopy = true;
1ed017
1ed017
		} else if (!strcmp(argv[i],"-Xlinker")) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'W';
1ed017
			*dst++ = 'l';
1ed017
			*dst++ = ',';
1ed017
			strcpy(dst,argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strcmp(argv[i],"--library-path")) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'L';
1ed017
			strcpy(dst,argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strncmp(argv[i],"--library-path=",15)) {
1ed017
			*dargv++ = dst;
1ed017
			*dst++ = '-';
1ed017
			*dst++ = 'L';
1ed017
			strcpy(dst,&argv[i][15]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strcmp(argv[i],"--sysroot") && (i
1ed017
			*dargv++ = dst;
1ed017
			csysroot = dst;
1ed017
			strcpy(dst,argv[i]);
1ed017
			dst[9] = '=';
1ed017
			strcpy(&dst[10],argv[++i]);
1ed017
			dst += strlen(dst)+1;
1ed017
			ctx.unitidx--;
1ed017
1ed017
		} else if (!strncmp(argv[i],"--sysroot=",10) && (i
1ed017
			*dargv++ = dst;
1ed017
			csysroot = dst;
1ed017
			strcpy(dst,argv[i]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
		} else if (!strcmp(argv[i],"-objectlist")) {
1ed017
			*dargv++ = dst;
1ed017
			strcpy(dst,argv[i++]);
1ed017
			dst += strlen(dst)+1;
1ed017
1ed017
			objlistp->name = dst;
1ed017
			objlistp++;
1ed017
			fcopy = true;
1ed017
1ed017
		} else {
1ed017
			fcopy = true;
1ed017
		}
1ed017
1ed017
		if (fcopy) {
1ed017
			*dargv++ = dst;
1ed017
			strcpy(dst,argv[i]);
1ed017
			dst += strlen(dst)+1;
1ed017
		}
1ed017
	}
1ed017
1ed017
	/* update argc,argv */
1ed017
	argc = dargv - sargv->dargv;
1ed017
	argv = sargv->dargv;
1ed017
1ed017
	/* iterate through the object list vector: map, parse, store */
1ed017
	for (objlistp=objlistv; objlistp->name; objlistp++)
1ed017
		if (slbt_objlist_read(fdcwd,objlistp) < 0)
1ed017
			return -1;
1ed017
1ed017
	for (objc=0,objlistp=objlistv; objlistp->name; objlistp++)
1ed017
		objc += objlistp->objc;
1ed017
1ed017
	/* allocate split vectors, account for cargv's added sysroot */
1ed017
	if ((sargv->targv = calloc(objc + 2*(argc+3),sizeof(char *))))
1ed017
		sargv->cargv = sargv->targv + argc + 2;
1ed017
	else
1ed017
		return -1;
1ed017
1ed017
	/* --features and no <compiler>? */
1ed017
	if (ctx.unitidx) {
1ed017
		(void)0;
1ed017
805666
	} else if (help || version || features || info || config || dumpmachine || printdir || altmode) {
1ed017
		for (i=0; i
1ed017
			sargv->targv[i] = argv[i];
1ed017
1ed017
		sargv->cargv = altmode ? sargv->targv : slbt_default_cargv;
1ed017
1ed017
		return 0;
1ed017
	}
1ed017
1ed017
	/* --mode=ar and no ar-specific arguments? */
1ed017
	if (aropt && !ctx.unitidx)
1ed017
		ctx.unitidx = argc;
1ed017
4c3fb7
	/* --mode=slibtoolize and no slibtoolize-specific arguments? */
4c3fb7
	if (stoolieopt && !ctx.unitidx)
4c3fb7
		ctx.unitidx = argc;
4c3fb7
1ed017
	/* split vectors: slibtool's own options */
1ed017
	for (i=0; i
1ed017
		sargv->targv[i] = argv[i];
1ed017
1ed017
	/* split vector marks */
1ed017
	targv = sargv->targv + i;
1ed017
	cargv = sargv->cargv;
1ed017
1ed017
	/* known wrappers */
4c3fb7
	if (ctx.unitidx && !ccwrap && !aropt && !stoolieopt) {
1ed017
		if ((base = strrchr(argv[i],'/')))
1ed017
			base++;
1ed017
		else if ((base = strrchr(argv[i],'\\')))
1ed017
			base++;
1ed017
		else
1ed017
			base = argv[i];
1ed017
1ed017
		if (!strcmp(base,"ccache")
1ed017
				|| !strcmp(base,"distcc")
1ed017
				|| !strcmp(base,"compiler")
1ed017
				|| !strcmp(base,"purify")) {
1ed017
			*targv++ = "--ccwrap";
1ed017
			*targv++ = argv[i++];
1ed017
		}
1ed017
	}
1ed017
1ed017
	/* split vectors: legacy mixture */
1ed017
	for (optout=optv; optout[0] && (optout[0]->tag != TAG_OUTPUT); optout++)
1ed017
		(void)0;
1ed017
1ed017
	/* compiler, archiver, etc. */
1ed017
	if (altmode) {
1ed017
		i = 0;
1ed017
	} else if (aropt) {
1ed017
		*cargv++ = argv[0];
4c3fb7
	} else if (stoolieopt) {
4c3fb7
		*cargv++ = argv[0];
1ed017
	} else {
1ed017
		*cargv++ = argv[i++];
1ed017
	}
1ed017
1ed017
	/* sysroot */
1ed017
	if (csysroot)
1ed017
		*cargv++ = csysroot;
1ed017
1ed017
	/* remaining vector */
1ed017
	for (objlistp=objlistv; i
1ed017
		if (aropt && (i >= ctx.unitidx)) {
1ed017
			*cargv++ = argv[i];
1ed017
4c3fb7
		} else if (stoolieopt && (i >= ctx.unitidx)) {
4c3fb7
			*cargv++ = argv[i];
4c3fb7
1ed017
		} else if (argv[i][0] != '-') {
1ed017
			if (argv[i+1] && (argv[i+1][0] == '+')
1ed017
					&& (argv[i+1][1] == '=')
1ed017
					&& (argv[i+1][2] == 0)
1ed017
					&& !(strrchr(argv[i],'.')))
1ed017
				/* libfoo_la_LDFLAGS += -Wl,.... */
1ed017
				i++;
1ed017
			else
1ed017
				*cargv++ = argv[i];
1ed017
1ed017
		/* must capture -objectlist prior to -o */
1ed017
		} else if (!(strcmp("objectlist",&argv[i][1]))) {
1ed017
			for (objp=objlistp->objv; *objp; objp++)
1ed017
				*cargv++ = *objp;
1ed017
1ed017
			i++;
1ed017
			objlistp++;
1ed017
1ed017
		} else if (argv[i][1] == 'o') {
1ed017
			*targv++ = argv[i];
1ed017
1ed017
			if (argv[i][2] == 0)
1ed017
				*targv++ = argv[++i];
1ed017
		} else if ((argv[i][1] == 'W')  && (argv[i][2] == 'c')) {
1ed017
			*cargv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("Xcompiler",&argv[i][1]))) {
1ed017
			*cargv++ = argv[++i];
1ed017
1ed017
		} else if (!(strcmp("XCClinker",&argv[i][1]))) {
1ed017
			*cargv++ = argv[++i];
1ed017
1ed017
		} else if ((argv[i][1] == 'R')  && (argv[i][2] == 0)) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (argv[i][1] == 'R') {
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strncmp("-target=",&argv[i][1],8))) {
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("-target",&argv[i][1]))) {
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i++];
1ed017
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("target",&argv[i][1]))) {
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i++];
1ed017
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strncmp("-sysroot=",&argv[i][1],9))) {
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("-sysroot",&argv[i][1]))) {
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i++];
1ed017
1ed017
			*cargv++ = argv[i];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("bindir",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("shrext",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("rpath",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("release",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("weak",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("static-libtool-libs",&argv[i][1]))) {
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("export-dynamic",&argv[i][1]))) {
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("export-symbols",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("export-symbols-regex",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("version-info",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
1ed017
		} else if (!(strcmp("version-number",&argv[i][1]))) {
1ed017
			*targv++ = argv[i++];
1ed017
			*targv++ = argv[i];
1ed017
e47cd7
		} else if (!(strcmp("dlopen",&argv[i][1]))) {
e47cd7
			if (!argv[i+1])
e47cd7
				return -1;
e47cd7
e47cd7
			*targv++ = argv[i++];
e47cd7
			*targv++ = argv[i];
e47cd7
1ed017
		} else if (!(strcmp("dlpreopen",&argv[i][1]))) {
8ef9e1
			if (!argv[i+1])
8ef9e1
				return -1;
9da202
8ef9e1
			if (strcmp(argv[i+1],"self") && strcmp(argv[i+1],"force")) {
8ef9e1
				*cargv++ = argv[i];
8ef9e1
				*targv++ = argv[i++];
1ed017
8ef9e1
				*cargv++ = argv[i];
8ef9e1
				*targv++ = argv[i];
8ef9e1
			} else {
8ef9e1
				*targv++ = argv[i++];
8ef9e1
				*targv++ = argv[i];
8ef9e1
			}
1ed017
		} else {
1ed017
			for (popt=optout; popt[0] && popt[0]->long_name; popt++)
1ed017
				if (!(strcmp(popt[0]->long_name,&argv[i][1])))
1ed017
					break;
1ed017
1ed017
			if (popt[0] && popt[0]->long_name)
1ed017
				*targv++ = argv[i];
1ed017
			else
1ed017
				*cargv++ = argv[i];
1ed017
		}
1ed017
	}
1ed017
1ed017
	return 0;
1ed017
}