/*
 * 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) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <sys/systeminfo.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "prtconf.h"

struct prt_opts	opts;
struct prt_dbg	dbg;
static char	new_path[MAXPATHLEN];

#define	INDENT_LENGTH	4

#ifdef	__x86
static const char *usage = "%s [ -V | -x | -abcdvpPD ] [ <device_path > ]\n";
#else
static const char *usage =
	"%s [ -F | -V | -x | -abcdvpPD ][ <device_path > ]\n";
#endif	/* __x86 */

static void
setpname(const char *name)
{
	char *p;

	if (name == NULL)
		opts.o_progname = "prtconf";
	else if (p = strrchr(name, '/'))
		opts.o_progname = (const char *) p + 1;
	else
		opts.o_progname = name;
}

/*PRINTFLIKE1*/
void
dprintf(const char *fmt, ...)
{
	if (dbg.d_debug) {
		va_list ap;
		va_start(ap, fmt);
		(void) vfprintf(stderr, fmt, ap);
		va_end(ap);
	}
}

void
indent_to_level(int ilev)
{
	(void) printf("%*s", INDENT_LENGTH * ilev, "");
}

static void
cleanup_path(const char *input_path, char *path)
{
	char	*ptr, *ptr2;
	size_t	len;

	if ((input_path == NULL) || (path == NULL))
		return;

	(void) strcpy(path, input_path);

	/*LINTED*/
	while (1) {
		len = strlen(path);
		if (len == 0)
			break;

		/* change substring "//" into "/" */
		if (ptr = strstr(path, "//")) {
			len = strlen(ptr + 1);
			(void) memmove(ptr, ptr + 1, len + 1);
			continue;
		}
		/* change substring "/./" into "/" */
		if (ptr = strstr(path, "/./")) {
			len = strlen(ptr + 2);
			(void) memmove(ptr, ptr + 2, len + 1);
			continue;
		}

		/* change substring "/<foo>/../" into "/" */
		if (ptr = strstr(path, "/../")) {
			len = strlen(ptr + 3);
			*ptr = '\0';
			ptr2 = strrchr(path, (int)'/');
			if (ptr2 == NULL) {
				/* path had a leading "/../" */
				ptr2 = path;
			}
			(void) memmove(ptr2, ptr + 3, len + 1);
			continue;
		}

		/* change trailing "/<foo>/.." into "/" */
		if ((len >= 3) &&
		    (path[len - 3] == '/') &&
		    (path[len - 2] == '.') &&
		    (path[len - 1] == '.')) {
			path[len - 3] = '\0';
			ptr2 = strrchr(path, (int)'/');
			if (ptr2 != NULL) {
				ptr2[1] = '\0';
			} else {
				/* path was "/.." */
				path[0] = '/';
				path[1] = '\0';
			}
			continue;
		}

		/* change trailing "/." into "/" */
		if ((len >= 2) &&
		    (path[len - 2] == '/') &&
		    (path[len - 1] == '.')) {
			path[len - 1] = '\0';
			continue;
		}

		/* remove trailing "/" unless it's the root */
		if ((len > 1) && (path[len - 1] == '/')) {
			path[len - 1] = '\0';
			continue;
		}

		break;
	}
}


/*
 * debug version has two more flags:
 *	-L force load driver
 *	-M: print per driver list
 */

#ifdef	DEBUG
static const char *optstring = "abcdDvVxpPFf:M:dLuC";
#else
static const char *optstring = "abcdDvVxpPFf:uC";
#endif	/* DEBUG */

int
main(int argc, char *argv[])
{
	long pagesize, npages;
	int c, ret;
	char hw_provider[SYS_NMLN];

	setpname(argv[0]);
	opts.o_promdev = "/dev/openprom";

	while ((c = getopt(argc, argv, optstring)) != -1)  {
		switch (c)  {
		case 'a':
			++opts.o_ancestors;
			break;
		case 'b':
			++opts.o_productinfo;
			break;
		case 'c':
			++opts.o_children;
			break;
		case 'd':
			++opts.o_pciid;
			break;
		case 'D':
			++opts.o_drv_name;
			break;
		case 'v':
			++opts.o_verbose;
			break;
		case 'p':
			++opts.o_prominfo;
			break;
		case 'f':
			opts.o_promdev = optarg;
			break;
		case 'V':
			++opts.o_promversion;
			break;
		case 'x':
			++opts.o_prom_ready64;
			break;
		case 'F':
			++opts.o_fbname;
			++opts.o_noheader;
			break;
		case 'P':
			++opts.o_pseudodevs;
			break;
		case 'C':
			++opts.o_forcecache;
			break;
#ifdef	DEBUG
		case 'M':
			dbg.d_drivername = optarg;
			++dbg.d_bydriver;
			break;
		case 'L':
			++dbg.d_forceload;
			break;
#endif	/* DEBUG */

		default:
			(void) fprintf(stderr, usage, opts.o_progname);
			return (1);
		}
	}

	(void) uname(&opts.o_uts);

	if (opts.o_fbname)
		return (do_fbname());

	if (opts.o_promversion)
		return (do_promversion());

	if (opts.o_prom_ready64)
		return (do_prom_version64());

	if (opts.o_productinfo)
		return (do_productinfo());

	opts.o_devices_path = NULL;
	opts.o_devt = DDI_DEV_T_NONE;
	opts.o_target = 0;
	if (optind < argc) {
		struct stat	sinfo;
		char		*path = argv[optind];
		int		error;

		if (opts.o_prominfo) {
			/* PROM tree cannot be used with path */
			(void) fprintf(stderr, "%s: path and -p option are "
			    "mutually exclusive\n", opts.o_progname);
			return (1);
		}

		if (strlen(path) >= MAXPATHLEN) {
			(void) fprintf(stderr, "%s: "
			    "path specified is too long\n", opts.o_progname);
			return (1);
		}

		if (error = stat(path, &sinfo)) {

			/* an invalid path was specified */
			(void) fprintf(stderr, "%s: invalid path specified\n",
			    opts.o_progname);
			return (1);

		} else if (((sinfo.st_mode & S_IFMT) == S_IFCHR) ||
		    ((sinfo.st_mode & S_IFMT) == S_IFBLK)) {

			opts.o_devt = sinfo.st_rdev;
			error = 0;

		} else if ((sinfo.st_mode & S_IFMT) == S_IFDIR) {
			size_t	len, plen;

			/* clean up the path */
			cleanup_path(path, new_path);

			len = strlen(new_path);
			plen = strlen("/devices");
			if (len < plen) {
				/* This is not a valid /devices path */
				error = 1;
			} else if ((len == plen) &&
			    (strcmp(new_path, "/devices") == 0)) {
				/* /devices is the root nexus */
				opts.o_devices_path = "/";
				error = 0;
			} else if (strncmp(new_path, "/devices/", plen + 1)) {
				/* This is not a valid /devices path */
				error = 1;
			} else {
				/* a /devices/ path was specified */
				opts.o_devices_path = new_path + plen;
				error = 0;
			}

		} else {
			/* an invalid device path was specified */
			error = 1;
		}

		if (error) {
			(void) fprintf(stderr, "%s: "
			    "invalid device path specified\n",
			    opts.o_progname);
			return (1);
		}

		opts.o_target = 1;
	}

	if ((opts.o_ancestors || opts.o_children) && (!opts.o_target)) {
		(void) fprintf(stderr, "%s: options require a device path\n",
		    opts.o_progname);
		return (1);
	}

	if (opts.o_target) {
		prtconf_devinfo();
		return (0);
	}

	ret = sysinfo(SI_HW_PROVIDER, hw_provider, sizeof (hw_provider));
	/*
	 * If 0 bytes are returned (the system returns '1', for the \0),
	 * we're probably on x86, default to Oracle.
	 */
	if (ret <= 1) {
		(void) strncpy(hw_provider, "Oracle Corporation",
		    sizeof (hw_provider));
	}
	(void) printf("System Configuration:  %s  %s\n", hw_provider,
	    opts.o_uts.machine);

	pagesize = sysconf(_SC_PAGESIZE);
	npages = sysconf(_SC_PHYS_PAGES);
	(void) printf("Memory size: ");
	if (pagesize == -1 || npages == -1)
		(void) printf("unable to determine\n");
	else {
		const int64_t kbyte = 1024;
		const int64_t mbyte = 1024 * 1024;
		int64_t ii = (int64_t)pagesize * npages;

		if (ii >= mbyte)
			(void) printf("%ld Megabytes\n",
			    (long)((ii+mbyte-1) / mbyte));
		else
			(void) printf("%ld Kilobytes\n",
			    (long)((ii+kbyte-1) / kbyte));
	}

	if (opts.o_prominfo) {
		(void) printf("System Peripherals (PROM Nodes):\n\n");
		if (do_prominfo() == 0)
			return (0);
		(void) fprintf(stderr, "%s: Defaulting to non-PROM mode...\n",
		    opts.o_progname);
	}

	(void) printf("System Peripherals (Software Nodes):\n\n");

	(void) prtconf_devinfo();

	return (0);
}