1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  * Copyright (c) 2018, Joyent, Inc.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/smbios_impl.h>
31 #include <sys/sysmacros.h>
32 #include <sys/stat.h>
33 #include <sys/mman.h>
34 
35 #include <alloca.h>
36 #include <limits.h>
37 #include <unistd.h>
38 #include <strings.h>
39 #include <stdlib.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <libdevinfo.h>
43 
44 #pragma init(smb_init)
45 static void
smb_init(void)46 smb_init(void)
47 {
48 	_smb_debug = getenv("SMB_DEBUG") != NULL;
49 }
50 
51 static smbios_hdl_t *
smb_fileopen(int fd,int version,int flags,int * errp)52 smb_fileopen(int fd, int version, int flags, int *errp)
53 {
54 	smbios_entry_t *ep = alloca(SMB_ENTRY_MAXLEN);
55 	smbios_entry_point_t ep_type;
56 	smbios_hdl_t *shp = NULL;
57 	uint32_t smbe_stlen;
58 	off64_t smbe_staddr;
59 	ssize_t n, elen;
60 	void *stbuf;
61 
62 	if ((n = pread64(fd, ep, sizeof (*ep), 0)) != sizeof (*ep))
63 		return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOHDR));
64 
65 	if (strncmp(ep->ep21.smbe_eanchor, SMB_ENTRY_EANCHOR,
66 	    SMB_ENTRY_EANCHORLEN) == 0) {
67 		ep_type = SMBIOS_ENTRY_POINT_21;
68 		elen = MIN(ep->ep21.smbe_elen, SMB_ENTRY_MAXLEN);
69 	} else if (strncmp(ep->ep30.smbe_eanchor, SMB3_ENTRY_EANCHOR,
70 	    SMB3_ENTRY_EANCHORLEN) == 0) {
71 		ep_type = SMBIOS_ENTRY_POINT_30;
72 		elen = MIN(ep->ep30.smbe_elen, SMB_ENTRY_MAXLEN);
73 	} else {
74 		return (smb_open_error(shp, errp, ESMB_HEADER));
75 	}
76 
77 	if ((n = pread64(fd, ep, elen, 0)) != elen)
78 		return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOHDR));
79 
80 	if (ep_type == SMBIOS_ENTRY_POINT_21) {
81 		smbe_stlen = ep->ep21.smbe_stlen;
82 		smbe_staddr = (off64_t)ep->ep21.smbe_staddr;
83 	} else {
84 		smbe_stlen = ep->ep30.smbe_stlen;
85 		smbe_staddr = (off64_t)ep->ep30.smbe_staddr;
86 	}
87 	stbuf = smb_alloc(smbe_stlen);
88 
89 	if (stbuf == NULL)
90 		return (smb_open_error(shp, errp, ESMB_NOMEM));
91 
92 	if ((n = pread64(fd, stbuf, smbe_stlen, smbe_staddr)) != smbe_stlen) {
93 		smb_free(stbuf, smbe_stlen);
94 		return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOSTAB));
95 	}
96 
97 	shp = smbios_bufopen(ep, stbuf, smbe_stlen, version, flags, errp);
98 
99 	if (shp != NULL)
100 		shp->sh_flags |= SMB_FL_BUFALLOC;
101 	else
102 		smb_free(stbuf, smbe_stlen);
103 
104 	return (shp);
105 }
106 
107 static smbios_hdl_t *
smb_biosopen(int fd,int version,int flags,int * errp)108 smb_biosopen(int fd, int version, int flags, int *errp)
109 {
110 	smbios_entry_t *ep = alloca(SMB_ENTRY_MAXLEN);
111 	smbios_entry_point_t ep_type;
112 	smbios_hdl_t *shp = NULL;
113 	size_t pgsize, pgmask, pgoff;
114 	void *stbuf, *bios, *p, *q;
115 	void *smb2, *smb3;
116 	di_node_t root;
117 	int64_t *val64;
118 	uint32_t smbe_stlen;
119 	off64_t smbe_staddr;
120 
121 	bios = MAP_FAILED;
122 	if ((root = di_init("/", DINFOPROP)) != DI_NODE_NIL) {
123 		if (di_prop_lookup_int64(DDI_DEV_T_ANY, root,
124 		    "smbios-address", &val64) == 1) {
125 			bios = mmap(NULL, SMB_RANGE_LIMIT - SMB_RANGE_START + 1,
126 			    PROT_READ, MAP_SHARED, fd, (off_t)*val64);
127 		}
128 		di_fini(root);
129 	}
130 	if (bios == MAP_FAILED) {
131 		bios = mmap(NULL, SMB_RANGE_LIMIT - SMB_RANGE_START + 1,
132 		    PROT_READ, MAP_SHARED, fd, (uint32_t)SMB_RANGE_START);
133 	}
134 
135 	if (bios == MAP_FAILED)
136 		return (smb_open_error(shp, errp, ESMB_MAPDEV));
137 
138 	q = (void *)((uintptr_t)bios + SMB_RANGE_LIMIT - SMB_RANGE_START + 1);
139 
140 	smb2 = smb3 = NULL;
141 	for (p = bios; p < q; p = (void *)((uintptr_t)p + SMB_SCAN_STEP)) {
142 		if (smb2 != NULL && smb3 != NULL)
143 			break;
144 		if (smb3 == NULL && strncmp(p, SMB3_ENTRY_EANCHOR,
145 		    SMB3_ENTRY_EANCHORLEN) == 0) {
146 			smb3 = p;
147 		} else if (smb2 == NULL && strncmp(p, SMB_ENTRY_EANCHOR,
148 		    SMB_ENTRY_EANCHORLEN) == 0) {
149 			smb2 = p;
150 		}
151 	}
152 
153 	/*
154 	 * While they're not supposed to (as per the SMBIOS 3.2 spec), some
155 	 * vendors end up having a newer version in one of the two entry points
156 	 * than the other. If we found multiple tables then we will prefer the
157 	 * one with the newer version. If they're equivalent, we prefer the
158 	 * 32-bit version. If only one is present, then we use that.
159 	 */
160 	if (smb2 != NULL && smb3 != NULL) {
161 		uint8_t smb2maj, smb2min, smb3maj, smb3min;
162 
163 		bcopy(smb2, ep, sizeof (smbios_entry_t));
164 		smb2maj = ep->ep21.smbe_major;
165 		smb2min = ep->ep21.smbe_minor;
166 		bcopy(smb3, ep, sizeof (smbios_entry_t));
167 		smb3maj = ep->ep30.smbe_major;
168 		smb3min = ep->ep30.smbe_minor;
169 
170 		if (smb3maj > smb2maj ||
171 		    (smb3maj == smb2maj && smb3min > smb2min)) {
172 			ep_type = SMBIOS_ENTRY_POINT_30;
173 			p = smb3;
174 		} else {
175 			ep_type = SMBIOS_ENTRY_POINT_21;
176 			p = smb2;
177 		}
178 	} else if (smb3 != NULL) {
179 		ep_type = SMBIOS_ENTRY_POINT_30;
180 		p = smb3;
181 	} else if (smb2 != NULL) {
182 		ep_type = SMBIOS_ENTRY_POINT_21;
183 		p = smb2;
184 	} else {
185 		(void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1);
186 		return (smb_open_error(NULL, errp, ESMB_NOTFOUND));
187 	}
188 
189 	bcopy(p, ep, sizeof (smbios_entry_t));
190 
191 	switch (ep_type) {
192 	case SMBIOS_ENTRY_POINT_21:
193 		ep->ep21.smbe_elen = MIN(ep->ep21.smbe_elen, SMB_ENTRY_MAXLEN);
194 		bcopy(p, ep, ep->ep21.smbe_elen);
195 		smbe_stlen = ep->ep21.smbe_stlen;
196 		smbe_staddr = ep->ep21.smbe_staddr;
197 		break;
198 	case SMBIOS_ENTRY_POINT_30:
199 		ep->ep30.smbe_elen = MIN(ep->ep30.smbe_elen, SMB_ENTRY_MAXLEN);
200 		bcopy(p, ep, ep->ep30.smbe_elen);
201 		smbe_stlen = ep->ep30.smbe_stlen;
202 		smbe_staddr = ep->ep30.smbe_staddr;
203 		break;
204 	default:
205 		(void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1);
206 		return (smb_open_error(NULL, errp, ESMB_VERSION));
207 	}
208 	(void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1);
209 
210 	pgsize = getpagesize();
211 	pgmask = ~(pgsize - 1);
212 	pgoff = smbe_staddr & ~pgmask;
213 
214 	bios = mmap(NULL, smbe_stlen + pgoff,
215 	    PROT_READ, MAP_SHARED, fd, smbe_staddr & pgmask);
216 
217 	if (bios == MAP_FAILED)
218 		return (smb_open_error(shp, errp, ESMB_MAPDEV));
219 
220 	if ((stbuf = smb_alloc(smbe_stlen)) == NULL) {
221 		(void) munmap(bios, smbe_stlen + pgoff);
222 		return (smb_open_error(shp, errp, ESMB_NOMEM));
223 	}
224 
225 	bcopy((char *)bios + pgoff, stbuf, smbe_stlen);
226 	(void) munmap(bios, smbe_stlen + pgoff);
227 	shp = smbios_bufopen(ep, stbuf, smbe_stlen, version, flags, errp);
228 
229 	if (shp != NULL)
230 		shp->sh_flags |= SMB_FL_BUFALLOC;
231 	else
232 		smb_free(stbuf, smbe_stlen);
233 
234 	return (shp);
235 }
236 
237 smbios_hdl_t *
smbios_fdopen(int fd,int version,int flags,int * errp)238 smbios_fdopen(int fd, int version, int flags, int *errp)
239 {
240 	struct stat64 st1, st2;
241 
242 	if (stat64(SMB_BIOS_DEVICE, &st1) == 0 && fstat64(fd, &st2) == 0 &&
243 	    S_ISCHR(st2.st_mode) && st1.st_rdev == st2.st_rdev)
244 		return (smb_biosopen(fd, version, flags, errp));
245 	else
246 		return (smb_fileopen(fd, version, flags, errp));
247 }
248 
249 smbios_hdl_t *
smbios_open(const char * file,int version,int flags,int * errp)250 smbios_open(const char *file, int version, int flags, int *errp)
251 {
252 	smbios_hdl_t *shp;
253 	int fd;
254 
255 	if ((fd = open64(file ? file : SMB_SMBIOS_DEVICE, O_RDONLY)) == -1) {
256 		if ((errno == ENOENT || errno == ENXIO) &&
257 		    (file == NULL || strcmp(file, SMB_SMBIOS_DEVICE) == 0))
258 			errno = ESMB_NOTFOUND;
259 		return (smb_open_error(NULL, errp, errno));
260 	}
261 
262 	shp = smbios_fdopen(fd, version, flags, errp);
263 	(void) close(fd);
264 	return (shp);
265 }
266 
267 static int
smbios_xwrite(smbios_hdl_t * shp,int fd,const void * buf,size_t buflen)268 smbios_xwrite(smbios_hdl_t *shp, int fd, const void *buf, size_t buflen)
269 {
270 	ssize_t resid = buflen;
271 	ssize_t len;
272 
273 	while (resid != 0) {
274 		if ((len = write(fd, buf, resid)) <= 0)
275 			return (smb_set_errno(shp, errno));
276 		resid -= len;
277 		buf = (uchar_t *)buf + len;
278 	}
279 
280 	return (0);
281 }
282 
283 int
smbios_write(smbios_hdl_t * shp,int fd)284 smbios_write(smbios_hdl_t *shp, int fd)
285 {
286 	smbios_entry_t ep;
287 	off64_t off = lseek64(fd, 0, SEEK_CUR) + P2ROUNDUP(sizeof (ep), 16);
288 
289 	if (off > UINT32_MAX)
290 		return (smb_set_errno(shp, EOVERFLOW));
291 
292 	bcopy(&shp->sh_ent, &ep, sizeof (ep));
293 	if (shp->sh_ent_type == SMBIOS_ENTRY_POINT_21)
294 		ep.ep21.smbe_staddr = (uint32_t)off;
295 	else if (shp->sh_ent_type == SMBIOS_ENTRY_POINT_30)
296 		ep.ep30.smbe_staddr = (uint64_t)off;
297 	else
298 		return (-1);
299 
300 	smbios_checksum(shp, &ep);
301 
302 	if (smbios_xwrite(shp, fd, &ep, sizeof (ep)) == -1 ||
303 	    lseek64(fd, off, SEEK_SET) != off ||
304 	    smbios_xwrite(shp, fd, shp->sh_buf, shp->sh_buflen) == -1)
305 		return (-1);
306 
307 	return (0);
308 }
309