1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2022 RackTop Systems, Inc.
14 */
15
16 #include <err.h>
17 #include <errno.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/debug.h>
22 #include <sys/list.h>
23 #include <sys/types.h>
24 #include <sys/sysmacros.h>
25 #include <smbsrv/libsmb.h>
26 #include <ofmt.h>
27 #include <libintl.h>
28 #include <limits.h>
29 #include <locale.h>
30 #include <time.h>
31 #include <upanic.h>
32 #include "smbadm.h"
33
34 /*
35 * Share types for shiX_type fields - duplicated from smb.h
36 * Don't want to pull that in, and these are "carved in stone"
37 * (from the SMB protocol definitions)
38 */
39 #ifndef _SHARE_TYPES_DEFINED_
40 #define _SHARE_TYPES_DEFINED_
41 #define STYPE_DISKTREE 0x00000000
42 #define STYPE_PRINTQ 0x00000001
43 #define STYPE_DEVICE 0x00000002
44 #define STYPE_IPC 0x00000003
45 #define STYPE_MASK 0x0000000F
46 #endif /* _SHARE_TYPES_DEFINED_ */
47
48 #define MINS (60U)
49 #define HRS (60 * MINS)
50 #define DAYS (24 * HRS)
51 #define TIME_FMT "%F %T %Z"
52
53 #define _(x) gettext(x)
54
55 struct flag_tbl {
56 uint32_t flag;
57 const char *name;
58 };
59
60 typedef enum user_field {
61 UF_SESS_ID,
62 UF_DOMAIN,
63 UF_ACCOUNT,
64 UF_USER,
65 UF_UID,
66 UF_WORKSTATION,
67 UF_IP,
68 UF_OS,
69 UF_LOGON_TIME,
70 UF_AGE,
71 UF_NUMOPEN,
72 UF_FLAGS,
73 } user_field_t;
74
75 typedef enum tree_field {
76 TF_ID,
77 TF_TYPE,
78 TF_NUMOPEN,
79 TF_NUMUSERS,
80 TF_TIME,
81 TF_AGE,
82 TF_USERNAME,
83 TF_SHARE,
84 } tree_field_t;
85
86 typedef enum netfileinfo_field {
87 NFIF_FID,
88 NFIF_UNIQID,
89 NFIF_PERMS,
90 NFIF_NUMLOCKS,
91 NFIF_PATH,
92 NFIF_USERNAME,
93 } netfileinfo_field_t;
94
95 static ofmt_handle_t cmd_create_handle(int, char **, const char *,
96 ofmt_field_t *);
97
98 static boolean_t fmt_user(ofmt_arg_t *, char *, uint_t);
99 static boolean_t fmt_tree(ofmt_arg_t *, char *, uint_t);
100 static boolean_t fmt_netfileinfo(ofmt_arg_t *, char *, uint_t);
101
102 static void print_str(const char *restrict, char *restrict, uint_t);
103 static void print_u32(uint32_t, char *, uint_t);
104 static void print_age(time_t, char *, uint_t);
105 static void print_time(time_t, const char *, char *, uint_t);
106 static void print_flags(struct flag_tbl *, size_t, uint32_t, char *, uint_t);
107 static void print_perms(struct flag_tbl *, size_t, uint32_t, char *, uint_t);
108
109 static ofmt_field_t smb_user_fields[] = {
110 { "ID", 4, UF_SESS_ID, fmt_user },
111 { "DOMAIN", 32, UF_DOMAIN, fmt_user },
112 { "ACCT", 16, UF_ACCOUNT, fmt_user },
113 { "USER", 32, UF_USER, fmt_user },
114 { "UID", 12, UF_UID, fmt_user },
115 { "COMPUTER", 16, UF_WORKSTATION, fmt_user },
116 { "IP", 15, UF_IP, fmt_user },
117 { "OS", 8, UF_OS, fmt_user },
118 { "LOGON", 24, UF_LOGON_TIME, fmt_user },
119 { "AGE", 16, UF_AGE, fmt_user },
120 { "NOPEN", 5, UF_NUMOPEN, fmt_user },
121 { "FLAGS", 12, UF_FLAGS, fmt_user },
122 { NULL, 0, 0, NULL }
123 };
124
125 static const char default_user_fields[] = "IP,USER,NOPEN,AGE,FLAGS";
126
127 struct flag_tbl user_flag_tbl[] = {
128 { SMB_ATF_GUEST, "GUEST" },
129 { SMB_ATF_ANON, "ANON" },
130 { SMB_ATF_ADMIN, "ADMIN" },
131 { SMB_ATF_POWERUSER, "POWERUSER" },
132 { SMB_ATF_BACKUPOP, "BACKUPOP" },
133 };
134
135 static ofmt_field_t smb_tree_fields[] = {
136 { "ID", 4, TF_ID, fmt_tree },
137 { "TYPE", 6, TF_TYPE, fmt_tree },
138 { "NOPEN", 6, TF_NUMOPEN, fmt_tree },
139 { "NUSER", 6, TF_NUMUSERS, fmt_tree },
140 { "TIME", 24, TF_TIME, fmt_tree },
141 { "AGE", 12, TF_AGE, fmt_tree },
142 { "USER", 32, TF_USERNAME, fmt_tree },
143 { "SHARE", 16, TF_SHARE, fmt_tree },
144 { NULL, 0, 0, NULL }
145 };
146
147 static const char default_tree_fields[] = "TYPE,SHARE,USER,NOPEN,AGE";
148
149 static ofmt_field_t smb_netfileinfo_fields[] = {
150 { "ID", 4, NFIF_FID, fmt_netfileinfo },
151 { "UNIQID", 8, NFIF_UNIQID, fmt_netfileinfo },
152 { "PERM", 15, NFIF_PERMS, fmt_netfileinfo },
153 { "NLOCK", 6, NFIF_NUMLOCKS, fmt_netfileinfo },
154 { "PATH", 32, NFIF_PATH, fmt_netfileinfo },
155 { "USER", 16, NFIF_USERNAME, fmt_netfileinfo },
156 { NULL, 0, 0, NULL }
157 };
158
159 static const char default_netfileinfo_fields[] = "UNIQID,PATH,USER,NLOCK,PERM";
160
161 /*
162 * Flags are the same as "ls -V" and chmod ACLs:
163 * eg: everyone@:rwxpdDaARWcCos:fd----I:allow
164 * See libsec:acltext.c
165 */
166 static struct flag_tbl nfi_perm_tbl[] = {
167 { ACE_READ_DATA, "r" },
168 { ACE_WRITE_DATA, "w" },
169 { ACE_EXECUTE, "x" },
170 { ACE_APPEND_DATA, "p" },
171 { ACE_DELETE, "d" },
172 { ACE_DELETE_CHILD, "D" },
173 { ACE_READ_ATTRIBUTES, "a" },
174 { ACE_WRITE_ATTRIBUTES, "A" },
175 { ACE_READ_NAMED_ATTRS, "R" },
176 { ACE_WRITE_NAMED_ATTRS, "W" },
177 { ACE_READ_ACL, "c" },
178 { ACE_WRITE_ACL, "C" },
179 { ACE_WRITE_OWNER, "o" },
180 { ACE_SYNCHRONIZE, "s" },
181 };
182
183 static int do_enum(smb_svcenum_t *, ofmt_handle_t);
184 static void ofmt_fatal(ofmt_handle_t, ofmt_field_t *, ofmt_status_t)
185 __NORETURN;
186 static void fatal(const char *, ...) __NORETURN;
187
188 time_t now;
189 boolean_t opt_p;
190 boolean_t opt_x;
191
192 int
cmd_list_sess(int argc,char ** argv)193 cmd_list_sess(int argc, char **argv)
194 {
195 ofmt_handle_t hdl;
196 smb_svcenum_t req = {
197 .se_type = SMB_SVCENUM_TYPE_USER,
198 .se_level = 1,
199 .se_nlimit = UINT32_MAX,
200 };
201 int rc;
202
203 hdl = cmd_create_handle(argc, argv, default_user_fields,
204 smb_user_fields);
205 rc = do_enum(&req, hdl);
206 ofmt_close(hdl);
207 return (rc);
208 }
209
210 int
cmd_list_trees(int argc,char ** argv)211 cmd_list_trees(int argc, char **argv)
212 {
213 ofmt_handle_t hdl;
214 smb_svcenum_t req = {
215 .se_type = SMB_SVCENUM_TYPE_TREE,
216 .se_level = 1,
217 .se_nlimit = UINT32_MAX,
218 };
219 int rc;
220
221 hdl = cmd_create_handle(argc, argv, default_tree_fields,
222 smb_tree_fields);
223 rc = do_enum(&req, hdl);
224 ofmt_close(hdl);
225 return (rc);
226 }
227
228 int
cmd_list_ofiles(int argc,char ** argv)229 cmd_list_ofiles(int argc, char **argv)
230 {
231 ofmt_handle_t hdl;
232 smb_svcenum_t req = {
233 .se_type = SMB_SVCENUM_TYPE_FILE,
234 .se_level = 1,
235 .se_nlimit = UINT32_MAX,
236 };
237 int rc;
238
239 hdl = cmd_create_handle(argc, argv, default_netfileinfo_fields,
240 smb_netfileinfo_fields);
241 rc = do_enum(&req, hdl);
242 ofmt_close(hdl);
243 return (rc);
244 }
245
246 static ofmt_handle_t
cmd_create_handle(int argc,char ** argv,const char * def,ofmt_field_t * templ)247 cmd_create_handle(int argc, char **argv, const char *def, ofmt_field_t *templ)
248 {
249 const char *fields = def;
250 ofmt_handle_t hdl;
251 ofmt_status_t status;
252 uint_t flags = 0;
253 int c;
254
255 while ((c = getopt(argc, argv, "Ho:px")) != -1) {
256 switch (c) {
257 case 'H':
258 flags |= OFMT_NOHEADER;
259 break;
260 case 'o':
261 fields = optarg;
262 break;
263 case 'p':
264 opt_p = B_TRUE;
265 flags |= OFMT_PARSABLE;
266 break;
267 case 'x':
268 opt_x = B_TRUE;
269 break;
270 case '?':
271 /* Note: getopt prints an error for us. */
272 return (NULL);
273 }
274 }
275
276 status = ofmt_open(fields, templ, flags, 0, &hdl);
277 if (status != OFMT_SUCCESS)
278 ofmt_fatal(hdl, templ, status);
279
280 return (hdl);
281 }
282
283 int
cmd_close_ofile(int argc,char ** argv)284 cmd_close_ofile(int argc, char **argv)
285 {
286 uint_t errs = 0;
287
288 if (argc < 2) {
289 fprintf(stderr, _("Missing file id\n"));
290 return (2);
291 }
292
293 for (int i = 1; i < argc; i++) {
294 unsigned long ul;
295 int rc;
296
297 errno = 0;
298 ul = strtoul(argv[i], NULL, 0);
299 if (errno != 0) {
300 fprintf(stderr, _("Invalid file id '%s'"), argv[i]);
301 return (2);
302 }
303 #ifdef _LP64
304 if (ul > UINT32_MAX) {
305 fprintf(stderr, _("File id %lu too large"), ul);
306 return (2);
307 }
308 #endif
309
310 /*
311 * See SMB_IOC_FILE_CLOSE (ioc.uniqid)
312 * and smb_server_file_close()
313 */
314 rc = smb_kmod_file_close((uint32_t)ul);
315 if (rc != 0) {
316 /*
317 * Since the user can specify the fid as a decimal
318 * or hex value, we use the string they gave us so
319 * the value displayed matches what we were given.
320 */
321 warnx(_("Closing fid %s failed: %s"), argv[i],
322 strerror(rc));
323 errs++;
324 }
325 }
326
327 if (errs > 0)
328 return (1);
329 return (0);
330 }
331
332 int
cmd_close_sess(int argc,char ** argv)333 cmd_close_sess(int argc, char **argv)
334 {
335 const char *client;
336 const char *user = NULL;
337 int rc;
338
339 if (argc < 2) {
340 fprintf(stderr, _("clientname and username missing\n"));
341 return (2);
342 }
343 client = argv[1];
344 if (argc > 2) {
345 user = argv[2];
346 }
347
348 /*
349 * See SMB_IOC_SESSION_CLOSE (ioc.client, ioc.username)
350 * and smb_server_session_close(). The "client" part
351 * can be EITHER the "workstation" or the IP address,
352 * as shown in the "COMPUTER" and "IP" fields in the
353 * output of "list-sessions". The (optional) "user"
354 * part is as shown in "USER" part of that output.
355 */
356 rc = smb_kmod_session_close(client, user);
357 if (rc != 0) {
358 rc = 1;
359 }
360 return (rc);
361 }
362
363 static boolean_t
fmt_user(ofmt_arg_t * arg,char * buf,uint_t buflen)364 fmt_user(ofmt_arg_t *arg, char *buf, uint_t buflen)
365 {
366 smb_netuserinfo_t *ui = arg->ofmt_cbarg;
367 user_field_t field = (user_field_t)arg->ofmt_id;
368
369 switch (field) {
370 case UF_SESS_ID:
371 (void) snprintf(buf, buflen, "%" PRIu64, ui->ui_session_id);
372 break;
373 case UF_DOMAIN:
374 print_str(ui->ui_domain, buf, buflen);
375 break;
376 case UF_ACCOUNT:
377 print_str(ui->ui_account, buf, buflen);
378 break;
379 case UF_USER:
380 (void) snprintf(buf, buflen, "%s\\%s", ui->ui_domain,
381 ui->ui_account);
382 break;
383 case UF_UID:
384 VERIFY3U(arg->ofmt_width, <, INT_MAX);
385 (void) snprintf(buf, buflen, "%u", ui->ui_posix_uid);
386 break;
387 case UF_WORKSTATION:
388 print_str(ui->ui_workstation, buf, buflen);
389 break;
390 case UF_IP:
391 (void) smb_inet_ntop(&ui->ui_ipaddr, buf, buflen);
392 break;
393 case UF_OS:
394 /* XXX: Lookup string value */
395 (void) snprintf(buf, buflen, "%" PRId32, ui->ui_native_os);
396 break;
397 case UF_LOGON_TIME:
398 print_time(ui->ui_logon_time, TIME_FMT, buf, buflen);
399 break;
400 case UF_AGE:
401 print_age(now - ui->ui_logon_time, buf, buflen);
402 break;
403 case UF_NUMOPEN:
404 print_u32(ui->ui_numopens, buf, buflen);
405 break;
406 case UF_FLAGS:
407 print_flags(user_flag_tbl, ARRAY_SIZE(user_flag_tbl),
408 ui->ui_flags, buf, buflen);
409 break;
410 default:
411 fatal("%s: invalid field %d", __func__, field);
412 }
413
414 return (B_TRUE);
415 }
416
417 static boolean_t
fmt_tree_type(uint32_t type,char * buf,uint_t buflen)418 fmt_tree_type(uint32_t type, char *buf, uint_t buflen)
419 {
420 switch (type & STYPE_MASK) {
421 case STYPE_DISKTREE:
422 (void) strlcpy(buf, "DISK", buflen);
423 break;
424 case STYPE_PRINTQ:
425 (void) strlcpy(buf, "PRINTQ", buflen);
426 break;
427 case STYPE_DEVICE:
428 (void) strlcpy(buf, "DEVICE", buflen);
429 break;
430 case STYPE_IPC:
431 (void) strlcpy(buf, "IPC", buflen);
432 break;
433 default:
434 (void) snprintf(buf, buflen, "%" PRIx32, type & STYPE_MASK);
435 break;
436 }
437
438 return (B_TRUE);
439 }
440
441 static boolean_t
fmt_tree(ofmt_arg_t * arg,char * buf,uint_t buflen)442 fmt_tree(ofmt_arg_t *arg, char *buf, uint_t buflen)
443 {
444 smb_netconnectinfo_t *nc = arg->ofmt_cbarg;
445 tree_field_t field = (tree_field_t)arg->ofmt_id;
446
447 switch (field) {
448 case TF_ID:
449 (void) snprintf(buf, buflen, "%" PRIu32, nc->ci_id);
450 break;
451 case TF_TYPE:
452 return (fmt_tree_type(nc->ci_type, buf, buflen));
453 case TF_NUMOPEN:
454 print_u32(nc->ci_numopens, buf, buflen);
455 break;
456 case TF_NUMUSERS:
457 print_u32(nc->ci_numusers, buf, buflen);
458 break;
459 case TF_TIME:
460 print_time(now - nc->ci_time, TIME_FMT, buf, buflen);
461 break;
462 case TF_AGE:
463 print_age(nc->ci_time, buf, buflen);
464 break;
465 case TF_USERNAME:
466 print_str(nc->ci_username, buf, buflen);
467 break;
468 case TF_SHARE:
469 print_str(nc->ci_share, buf, buflen);
470 break;
471 default:
472 fatal("%s: invalid field %d", __func__, field);
473 }
474
475 return (B_TRUE);
476 }
477
478 static boolean_t
fmt_netfileinfo(ofmt_arg_t * arg,char * buf,uint_t buflen)479 fmt_netfileinfo(ofmt_arg_t *arg, char *buf, uint_t buflen)
480 {
481 smb_netfileinfo_t *fi = arg->ofmt_cbarg;
482 netfileinfo_field_t field = (netfileinfo_field_t)arg->ofmt_id;
483
484 switch (field) {
485 case NFIF_FID:
486 (void) snprintf(buf, buflen, "%" PRIu16, fi->fi_fid);
487 break;
488 case NFIF_UNIQID:
489 (void) snprintf(buf, buflen, "%" PRIu32, fi->fi_uniqid);
490 break;
491 case NFIF_PERMS:
492 print_perms(nfi_perm_tbl, ARRAY_SIZE(nfi_perm_tbl),
493 fi->fi_permissions, buf, buflen);
494 break;
495 case NFIF_NUMLOCKS:
496 print_u32(fi->fi_numlocks, buf, buflen);
497 break;
498 case NFIF_PATH:
499 print_str(fi->fi_path, buf, buflen);
500 break;
501 case NFIF_USERNAME:
502 print_str(fi->fi_username, buf, buflen);
503 break;
504 default:
505 fatal("%s: invalid field %d", __func__, field);
506 }
507
508 return (B_TRUE);
509 }
510
511 static int
do_enum(smb_svcenum_t * req,ofmt_handle_t hdl)512 do_enum(smb_svcenum_t *req, ofmt_handle_t hdl)
513 {
514 smb_netsvc_t *ns;
515 smb_netsvcitem_t *item;
516 uint32_t n = 0;
517 int rc;
518
519 if (hdl == NULL)
520 return (2); /* exit (2) -- usage */
521 now = time(NULL);
522
523 for (;;) {
524 req->se_nskip = n;
525
526 ns = smb_kmod_enum_init(req);
527 if (ns == NULL) {
528 fprintf(stderr, _("SMB enum initialization failure"));
529 return (1);
530 }
531
532 rc = smb_kmod_enum(ns);
533 if (rc != 0) {
534 /*
535 * When the SMB service is not running, expect ENXIO.
536 */
537 if (rc == ENXIO) {
538 fprintf(stderr,
539 _("Kernel SMB server not running"));
540 return (1);
541 }
542 fprintf(stderr, _("SMB enumeration call failed: %s"),
543 strerror(rc));
544 return (1);
545 }
546
547 if (list_is_empty(&ns->ns_list))
548 break;
549
550 for (item = list_head(&ns->ns_list); item != NULL;
551 item = list_next(&ns->ns_list, item)) {
552 ofmt_print(hdl, &item->nsi_un);
553 n++;
554 }
555
556 smb_kmod_enum_fini(ns);
557 }
558 return (0);
559 }
560
561 static void
print_str(const char * restrict src,char * restrict buf,uint_t buflen)562 print_str(const char *restrict src, char *restrict buf, uint_t buflen)
563 {
564 if (src == NULL) {
565 buf[0] = '\0';
566 return;
567 }
568 (void) strlcpy(buf, src, buflen);
569 }
570
571 static void
print_u32(uint32_t val,char * buf,uint_t buflen)572 print_u32(uint32_t val, char *buf, uint_t buflen)
573 {
574 const char *fmt = opt_p ? "%" PRIu32 : "%'" PRIu32;
575
576 (void) snprintf(buf, buflen, fmt, val);
577 }
578
579 static void
print_age(time_t amt,char * buf,uint_t buflen)580 print_age(time_t amt, char *buf, uint_t buflen)
581 {
582 uint32_t days = 0, hours = 0, mins = 0;
583
584 if (opt_p) {
585 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)amt);
586 return;
587 }
588
589 if (amt >= DAYS) {
590 days = amt / DAYS;
591 amt %= DAYS;
592 }
593 if (amt >= HRS) {
594 hours = amt / HRS;
595 amt %= HRS;
596 }
597 if (amt >= MINS) {
598 mins = amt / MINS;
599 amt %= MINS;
600 }
601
602 if (days > 0) {
603 int n = snprintf(buf, buflen, "%" PRIu32 " days%s",
604 days, (hours > 0 || mins > 0 || amt > 0) ? ", " : "");
605
606 VERIFY3U(buflen, >, n);
607
608 buf += n;
609 buflen -= n;
610 }
611
612 (void) snprintf(buf, buflen, "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32,
613 hours, mins, amt);
614 }
615
616 static void
print_time(time_t when,const char * fmt,char * buf,uint_t buflen)617 print_time(time_t when, const char *fmt, char *buf, uint_t buflen)
618 {
619 const struct tm *tm;
620
621 if (opt_p) {
622 (void) snprintf(buf, buflen, "%" PRId64, (int64_t)when);
623 return;
624 }
625
626 tm = localtime(&when);
627 (void) strftime(buf, buflen - 1, fmt, tm);
628 }
629
630 static void
print_flags(struct flag_tbl * tbl,size_t nent,uint32_t val,char * buf,uint_t buflen)631 print_flags(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf,
632 uint_t buflen)
633 {
634 uint_t n = 0;
635 uint_t i;
636
637 if (opt_x) {
638 (void) snprintf(buf, buflen, "%" PRIx32, val);
639 return;
640 }
641
642 for (i = 0; i < nent; i++) {
643 if ((val & tbl[i].flag) == 0)
644 continue;
645 if (n > 0)
646 (void) strlcat(buf, ",", buflen);
647 (void) strlcat(buf, tbl[i].name, buflen);
648 n++;
649 }
650
651 if (n == 0)
652 (void) strlcat(buf, "-", buflen);
653 }
654
655 static void
print_perms(struct flag_tbl * tbl,size_t nent,uint32_t val,char * buf,uint_t buflen)656 print_perms(struct flag_tbl *tbl, size_t nent, uint32_t val, char *buf,
657 uint_t buflen)
658 {
659 uint_t n = 0;
660 uint_t i;
661
662 if (opt_x) {
663 (void) snprintf(buf, buflen, "%" PRIx32, val);
664 return;
665 }
666
667 for (i = 0; i < nent; i++) {
668 if ((val & tbl[i].flag) == 0) {
669 (void) strlcat(buf, "-", buflen);
670 } else {
671 (void) strlcat(buf, tbl[i].name, buflen);
672 }
673 n++;
674 }
675 }
676
677 __NORETURN static void
ofmt_fatal(ofmt_handle_t hdl,ofmt_field_t * templ,ofmt_status_t status)678 ofmt_fatal(ofmt_handle_t hdl, ofmt_field_t *templ, ofmt_status_t status)
679 {
680 char buf[OFMT_BUFSIZE];
681 char *msg = ofmt_strerror(hdl, status, buf, sizeof (buf));
682
683 fprintf(stderr, _("ofmt error: %s\n"), msg);
684
685 if (status == OFMT_EBADFIELDS ||
686 status == OFMT_ENOFIELDS) {
687 ofmt_field_t *f = templ;
688 fprintf(stderr, _("Valid fields are: "));
689 while (f->of_name != NULL) {
690 fprintf(stderr, "%s", f->of_name);
691 f++;
692 if (f->of_name != NULL)
693 fprintf(stderr, ",");
694 }
695 fprintf(stderr, "\n");
696 }
697
698 exit(EXIT_FAILURE);
699 }
700
701 __NORETURN static void
fatal(const char * msg,...)702 fatal(const char *msg, ...)
703 {
704 char buf[128];
705 va_list ap;
706 size_t len;
707
708 va_start(ap, msg);
709 (void) vsnprintf(buf, sizeof (buf), msg, ap);
710 va_end(ap);
711
712 len = strlen(buf);
713 upanic(buf, len);
714 }
715