/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All rights reserved. */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/tuneable.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/utsname.h>
#include <sys/systeminfo.h>
#include <sys/unistd.h>
#include <sys/debug.h>
#include <sys/bootconf.h>
#include <sys/socket.h>
#include <sys/policy.h>
#include <net/if.h>
#include <sys/sunddi.h>
#include <sys/promif.h>
#include <sys/zone.h>
#include <sys/model.h>
#include <netinet/inetutil.h>
static void get_netif_name(char *, char *);
long
systeminfo(int command, char *buf, long count)
{
int error = 0;
long strcnt, getcnt;
char *kstr;
char hostidp[HW_HOSTID_LEN];
if (count < 0 && command != SI_SET_HOSTNAME &&
command != SI_SET_SRPC_DOMAIN)
return (set_errno(EINVAL));
/*
* Deal with the common "get a string" case first.
*/
switch (command) {
case SI_SYSNAME:
kstr = utsname.sysname;
break;
case SI_HOSTNAME:
kstr = uts_nodename();
break;
case SI_RELEASE:
kstr = utsname.release;
break;
case SI_VERSION:
kstr = utsname.version;
break;
case SI_MACHINE:
kstr = utsname.machine;
break;
#ifdef _LP64
case SI_ARCHITECTURE_64:
case SI_ARCHITECTURE_K:
kstr = architecture;
break;
case SI_ARCHITECTURE_32:
case SI_ARCHITECTURE:
kstr = architecture_32;
break;
case SI_ARCHITECTURE_NATIVE:
kstr = get_udatamodel() == DATAMODEL_NATIVE ?
architecture : architecture_32;
break;
#else
case SI_ARCHITECTURE_K:
case SI_ARCHITECTURE_32:
case SI_ARCHITECTURE:
case SI_ARCHITECTURE_NATIVE:
kstr = architecture;
break;
#endif
case SI_HW_SERIAL:
(void) snprintf(hostidp, sizeof (hostidp), "%u",
zone_get_hostid(curzone));
kstr = hostidp;
break;
case SI_HW_PROVIDER:
kstr = hw_provider;
break;
case SI_SRPC_DOMAIN:
kstr = curproc->p_zone->zone_domain;
break;
case SI_PLATFORM:
kstr = platform;
break;
case SI_ISALIST:
kstr = isa_list;
break;
default:
kstr = NULL;
break;
}
if (kstr != NULL) {
strcnt = strlen(kstr);
if (count > 0) {
if (count <= strcnt) {
getcnt = count - 1;
if (subyte(buf + getcnt, 0) < 0)
return (set_errno(EFAULT));
} else {
getcnt = strcnt + 1;
}
if (copyout(kstr, buf, getcnt))
return (set_errno(EFAULT));
}
return (strcnt + 1);
}
switch (command) {
case SI_DHCP_CACHE:
{
char *tmp;
unsigned int tlen, octlen;
if (dhcack == NULL) {
tmp = "";
strcnt = 0;
} else {
/*
* If the interface didn't have a name (bindable
* driver) to begin with, it might have one now.
* So, re-run strplumb_get_netdev_path() to see
* if one can be established at this time.
*/
if (netdev_path == NULL || netdev_path[0] == '\0') {
netdev_path = strplumb_get_netdev_path();
}
/*
* If the interface name has not yet been resolved
* and a validnetdev_path[] was stashed by
* loadrootmodules in swapgeneric.c, or established
* above, resolve the interface name now.
*/
if (dhcifname[0] == '\0' &&
netdev_path != NULL && netdev_path[0] != '\0') {
get_netif_name(netdev_path, dhcifname);
}
/*
* Form reply:
* IFNAMESIZ array of dhcp i/f
* hexascii representation of dhcp reply
*/
octlen = dhcacklen * 2 + 1;
tlen = octlen + IFNAMSIZ;
tmp = kmem_alloc(tlen, KM_SLEEP);
(void) strncpy(tmp, dhcifname, IFNAMSIZ);
if (octet_to_hexascii(dhcack, dhcacklen,
&tmp[IFNAMSIZ], &octlen) != 0) {
kmem_free(tmp, tlen);
error = EINVAL;
break;
} else {
strcnt = IFNAMSIZ + octlen;
}
}
if (count > 0) {
if (count <= strcnt) {
getcnt = count - 1;
if (subyte((buf + getcnt), 0) < 0)
goto fail;
} else {
getcnt = strcnt + 1;
}
if (copyout(tmp, buf, getcnt))
goto fail;
}
if (strcnt != 0)
kmem_free(tmp, tlen);
return (strcnt + 1);
fail:
if (strcnt != 0)
kmem_free(tmp, tlen);
error = EFAULT;
break;
}
case SI_SET_HOSTNAME:
{
size_t len;
char name[SYS_NMLN];
char *name_to_use;
if ((error = secpolicy_systeminfo(CRED())) != 0)
break;
name_to_use = uts_nodename();
if ((error = copyinstr(buf, name, SYS_NMLN, &len)) != 0)
break;
/*
* Must be non-NULL string and string
* must be less than SYS_NMLN chars.
*/
if (len < 2 || (len == SYS_NMLN && name[SYS_NMLN-1] != '\0')) {
error = EINVAL;
break;
}
/*
* Copy the name into the relevant zone's nodename.
*/
(void) strcpy(name_to_use, name);
/*
* Notify other interested parties that the nodename was set
*/
if (name_to_use == utsname.nodename) /* global zone nodename */
nodename_set();
return (len);
}
case SI_SET_SRPC_DOMAIN:
{
char name[SYS_NMLN];
size_t len;
if ((error = secpolicy_systeminfo(CRED())) != 0)
break;
if ((error = copyinstr(buf, name, SYS_NMLN, &len)) != 0)
break;
/*
* If string passed in is longer than length
* allowed for domain name, fail.
*/
if (len == SYS_NMLN && name[SYS_NMLN-1] != '\0') {
error = EINVAL;
break;
}
(void) strcpy(curproc->p_zone->zone_domain, name);
return (len);
}
default:
error = EINVAL;
break;
}
return (set_errno(error));
}
/*
* i_path_find_node: Internal routine used by path_to_devinfo
* to locate a given nodeid in the device tree.
*/
struct i_path_findnode {
pnode_t nodeid;
dev_info_t *dip;
};
static int
i_path_find_node(dev_info_t *dev, void *arg)
{
struct i_path_findnode *f = (struct i_path_findnode *)arg;
if (ddi_get_nodeid(dev) == (int)f->nodeid) {
f->dip = dev;
return (DDI_WALK_TERMINATE);
}
return (DDI_WALK_CONTINUE);
}
/*
* Return the devinfo node to a boot device
*/
static dev_info_t *
path_to_devinfo(char *path)
{
struct i_path_findnode fn;
extern dev_info_t *top_devinfo;
/*
* Get the nodeid of the given pathname, if such a mapping exists.
*/
fn.dip = NULL;
fn.nodeid = prom_finddevice(path);
if (fn.nodeid != OBP_BADNODE) {
/*
* Find the nodeid in our copy of the device tree and return
* whatever name we used to bind this node to a driver.
*/
ddi_walk_devs(top_devinfo, i_path_find_node, (void *)(&fn));
}
return (fn.dip);
}
/*
* Determine the network interface name from the device path argument.
*/
static void
get_netif_name(char *devname, char *ifname)
{
dev_info_t *dip;
major_t ndev;
char *name;
int unit;
dip = path_to_devinfo(devname);
if (dip == NULL) {
cmn_err(CE_WARN, "get_netif_name: "
"can't bind driver for '%s'\n", devname);
return;
}
ndev = ddi_driver_major(dip);
if (ndev == -1) {
cmn_err(CE_WARN, "get_netif_name: "
"no driver bound to '%s'\n", devname);
return;
}
name = ddi_major_to_name(ndev);
if (name == NULL) {
cmn_err(CE_WARN, "get_netif_name: "
"no name for major number %d\n", ndev);
return;
}
unit = i_ddi_devi_get_ppa(dip);
if (unit < 0) {
cmn_err(CE_WARN, "get_netif_name: "
"illegal unit number %d\n", unit);
return;
}
(void) snprintf(ifname, IFNAMSIZ, "%s%d", name, unit);
}