/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved   */

/*
 * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 * Copyright 2022 Oxide Computer Company
 */

#include "inc.h"
#include "conv.h"

/*
 * Forward declarations
 */
static void setup(int, char **, Cmd_info *);
static void setcom(Cmd_info *, Cmd_func);
static void usage(void);
static void sigexit(int sig);
static int notfound(Cmd_info *);
static void check_swap();

const char *
_ar_msg(Msg mid)
{
	return (gettext(MSG_ORIG(mid)));
}


void
establish_sighandler(void (*handler)())
{
	static const int signum[] = {SIGHUP, SIGINT, SIGQUIT, 0};
	int i;

	if (handler == SIG_IGN) {
		/* Ignore all the specified signals */
		for (i = 0; signum[i]; i++)
			(void) signal(signum[i], SIG_IGN);

	} else {
		/*
		 * Set any signal that doesn't default to being ignored
		 * to our signal handler.
		 */
		for (i = 0; signum[i]; i++)
			if (signal(signum[i], SIG_IGN) != SIG_IGN)
				(void) signal(signum[i], handler);
	}
}

int
main(int argc, char **argv, char *envp[])
{
	int fd;
	Cmd_info *cmd_info;
	int ret;
	char *new = NULL;

#ifndef	XPG4
	/*
	 * Check for a binary that better fits this architecture.
	 */
	(void) conv_check_native(argv, envp);
#endif

	/*
	 * Establish locale.
	 */
	(void) setlocale(LC_ALL, MSG_ORIG(MSG_STR_EMPTY));
	(void) textdomain(MSG_ORIG(MSG_SUNW_OST_SGS));

	/* Allow a graceful exit up until we start to write an archive */
	establish_sighandler(sigexit);

	/*
	 * Initialize cmd_info
	 */
	cmd_info = (Cmd_info *)calloc(1, sizeof (Cmd_info));
	if (cmd_info == NULL) {
		int err = errno;
		(void) fprintf(stderr, MSG_INTL(MSG_MALLOC), strerror(err));
		exit(1);
	}

	if (argc < 2)
		usage();

	/*
	 * Option handling.
	 */
	if (argv[1][0] != '-') {
		new = (char *)malloc(strlen(argv[1]) + 2);
		if (new == NULL) {
			int err = errno;
			(void) fprintf(stderr, MSG_INTL(MSG_MALLOC),
			    strerror(err));
			exit(1);
		}
		(void) strcpy(new, MSG_ORIG(MSG_STR_HYPHEN));
		(void) strcat(new, argv[1]);
		argv[1] = new;
	}
	setup(argc, argv, cmd_info);

	/*
	 * Check SWAP
	 */
	if (cmd_info->opt_flgs & z_FLAG)
		check_swap();

	cmd_info->modified = (cmd_info->opt_flgs & s_FLAG);
	fd = getaf(cmd_info);

	if (fd == -1) {
		boolean_t req_arg = (cmd_info->opt_flgs & (d_FLAG | m_FLAG |
		    p_FLAG | t_FLAG | x_FLAG)) != 0;
		boolean_t req_r = (cmd_info->opt_flgs & r_FLAG) &&
		    (cmd_info->opt_flgs & (a_FLAG | b_FLAG));
		boolean_t req_s = (cmd_info->opt_flgs & s_FLAG) &&
		    (cmd_info->opt_flgs & (r_FLAG | q_FLAG)) == 0;

		if (req_arg || req_r || req_s) {
			(void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_AR),
			    cmd_info->arnam);
			exit(1);
		}
	}

	(*cmd_info->comfun)(cmd_info);
	if (cmd_info->modified) {
		writefile(cmd_info);
	} else
		(void) close(fd);

	ret = notfound(cmd_info);

	/*
	 * Check SWAP
	 */
	if (cmd_info->opt_flgs & z_FLAG)
		check_swap();

	free(new);
	free(cmd_info);
	return (ret);

}

/*
 * Option handing function.
 *	Using getopt(), following xcu4 convention.
 */
static void
setup(int argc, char *argv[], Cmd_info *cmd_info)
{
	int Vflag = 0;
	int c;
	int usage_err = 0;

	while ((c = getopt(argc, argv, MSG_ORIG(MSG_STR_OPTIONS))) != -1) {
		switch (c) {
		case 'a': /* position after named archive member file */
			cmd_info->opt_flgs |= a_FLAG;
			cmd_info->ponam = trim(optarg);
			break;
		case 'b': /* position before named archive member file */
		case 'i': /* position before named archive member: same as b */
			cmd_info->opt_flgs |= b_FLAG;
			cmd_info->ponam = trim(optarg);
			break;
		case 'c': /* supress messages */
			cmd_info->opt_flgs |= c_FLAG;
			break;
		case 'd':
			/*
			 * key operation:
			 * delete files from the archive
			 */
			setcom(cmd_info, dcmd);
			cmd_info->opt_flgs |= d_FLAG;
			break;
		case 'l': /* ignored */
			break;
		case 'm':
			/*
			 * key operation:
			 * move files to end of the archive
			 * or as indicated by position flag
			 */
			setcom(cmd_info, mcmd);
			cmd_info->opt_flgs |= m_FLAG;
			break;
		case 'p':
			/*
			 * key operation:
			 * print files in the archive
			 */
			setcom(cmd_info, pcmd);
			cmd_info->opt_flgs |= p_FLAG;
			break;
		case 'q':
			/*
			 * key operation:
			 * quickly append files to end of the archive
			 */
			setcom(cmd_info, qcmd);
			cmd_info->opt_flgs |= q_FLAG;
			break;
		case 'r':
			/*
			 * key operation:
			 * replace or add files to the archive
			 */
			setcom(cmd_info, rcmd);
			cmd_info->opt_flgs |= r_FLAG;
			break;
		case 's': /* force symbol table regeneration */
			cmd_info->opt_flgs |= s_FLAG;
			break;
		case 'S': /* Build SYM64 symbol table */
			cmd_info->opt_flgs |= S_FLAG;
			break;
		case 't':
			/*
			 * key operation:
			 * print table of contents
			 */
			setcom(cmd_info, tcmd);
			cmd_info->opt_flgs |= t_FLAG;
			break;
		case 'u': /* update: change archive dependent on file dates */
			cmd_info->opt_flgs |= u_FLAG;
			break;
		case 'v': /* verbose */
			cmd_info->opt_flgs |= v_FLAG;
			break;
		case 'x':
			/*
			 * key operation:
			 * extract files from the archive
			 */
			setcom(cmd_info, xcmd);
			cmd_info->opt_flgs |= x_FLAG;
			break;
		case 'z':
			cmd_info->opt_flgs |= z_FLAG;
			break;
		case 'V':
			/*
			 * print version information.
			 * adjust command line access accounting
			 */
			if (Vflag == 0) {
				(void) fprintf(stderr,
				    MSG_ORIG(MSG_FMT_VERSION),
				    (const char *)SGU_PKG,
				    (const char *)SGU_REL);
				Vflag++;
			}
			break;
		case 'C':
			cmd_info->opt_flgs |= C_FLAG;
			break;
		case 'M':
			/*
			 * -M was an original undocumented AT&T feature that
			 * would force the use of mmap() instead of read()
			 * for pulling file data into the process before
			 * writing it to the archive. Ignored.
			 */
			break;
		case 'T':
			cmd_info->opt_flgs |= T_FLAG;
			break;
		case ':':
			(void) fprintf(stderr, MSG_INTL(MSG_USAGE_OPERAND),
			    optopt);
			usage_err++;
			break;
		case '?':
			(void) fprintf(stderr, MSG_INTL(MSG_USAGE_OPTION),
			    optopt);
			usage_err++;
			break;
		}
	}

	if (usage_err || argc - optind < 1)
		usage();

	cmd_info->arnam = argv[optind];
	cmd_info->namv = &argv[optind+1];
	cmd_info->namc = argc - optind - 1;

	/*
	 * GNU ar popularized the use of -s on its own which previously used to
	 * require another command function. As such, we don't set a command
	 * function when we encounter the -s flag because that might otherwise
	 * clobber an existing one being set and would interrupt the detection
	 * of multiple flags being used that way.
	 *
	 * If after processing everything, we find there's no command function
	 * set and the -s flag has been set, then we can finally set a command
	 * function. The command function for -t 'tcmd' is used in this case. It
	 * knows to only print out data if -t has been specified.
	 *
	 * While ar has not traditionally been very stringent about using flags
	 * in circumstances they aren't called for, we go ahead and check for
	 * that now for this newer option.
	 */
	if (cmd_info->comfun == NULL) {
		if ((cmd_info->opt_flgs & s_FLAG) != 0) {
			if ((cmd_info->opt_flgs & ~(s_FLAG | v_FLAG)) != 0) {
				(void) fprintf(stderr,
				    MSG_INTL(MSG_USAGE_S_BAD_ARG));
				exit(1);
			}

			if (cmd_info->namc > 0) {
				(void) fprintf(stderr,
				    MSG_INTL(MSG_USAGE_S_EXTRA_AR));
				exit(1);
			}

			setcom(cmd_info, tcmd);
		} else if ((cmd_info->opt_flgs & (d_FLAG | r_FLAG | q_FLAG |
		    s_FLAG | t_FLAG | p_FLAG | m_FLAG | x_FLAG)) == 0) {
			(void) fprintf(stderr, MSG_INTL(MSG_USAGE_REQ_FLAG));
			exit(1);
		}
	}
}


/*
 * Set the function to be called to do the key operation.
 * Check that only one key is indicated.
 */
static void
setcom(Cmd_info *cmd_info, Cmd_func *fun)
{
	if (cmd_info->comfun != NULL) {
		(void) fprintf(stderr, MSG_INTL(MSG_USAGE_TOO_MANY));
		exit(1);
	}
	cmd_info->comfun = fun;
}

static void
usage(void)
{
	(void) fprintf(stderr, MSG_INTL(MSG_USAGE));
	exit(1);
}

/*ARGSUSED0*/
static void
sigexit(int sig)
{
	exit(100);
}

/* tells the user which of the listed files were not found in the archive */

static int
notfound(Cmd_info *cmd_info)
{
	int i, n;

	n = 0;
	for (i = 0; i < cmd_info->namc; i++)
		if (cmd_info->namv[i]) {
			(void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_FILE),
			    cmd_info->namv[i]);
			n++;
		}
	return (n);
}

/*
 * Debugging info
 */
static void
check_swap(void)
{
	(void) system(MSG_ORIG(MSG_CMD_SWAP));
}