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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 * Copyright 2021 Oxide Computer Company
25 */
26
27 /*
28 * PSEUDO-TERMINAL COMMON DATA AND ROUTINES (PTM, PTS)
29 *
30 * This file contains global data and code shared between manager and
31 * subsidiary parts of the pseudo-terminal driver.
32 *
33 * Pseudo-terminals (or ptys for short) are allocated dynamically.
34 * ptys are put in the global ptms_slots array indexed by minor numbers.
35 *
36 * The slots array is initially small (of the size NPTY_MIN). When more ptys are
37 * needed than the slot array size, the larger slot array is allocated and all
38 * opened ptys move to the new one.
39 *
40 *
41 * RESOURCE ALLOCATION
42 *
43 * - pt_ttys structures are allocated via pt_ttys_alloc, which uses
44 * kmem_cache_alloc().
45 * - Minor number space is allocated via vmem_alloc() interface.
46 * - ptms_slots arrays are allocated via kmem_alloc().
47 *
48 * Minors start from 1 instead of 0, because vmem_alloc() returns 0 in case of
49 * failure. Also, in anticipation of removing the clone device interface to
50 * pseudo-terminal subsystem, minor 0 should not be used. (Potential future
51 * development).
52 *
53 * After the table slot size reaches pt_maxdelta, we stop 2^N extension
54 * algorithm and start extending the slot table size by pt_maxdelta.
55 *
56 * Device entries /dev/pts directory are created dynamically by the /dev
57 * filesystem. We no longer call ddi_create_minor_node() on behalf of the
58 * subsidiary driver. The /dev filesystem creates /dev/pts nodes based on the
59 * pt_ttys array.
60 *
61 *
62 * SYNCHRONIZATION
63 *
64 * All global data synchronization between ptm/pts is done via global ptms_lock
65 * mutex which is implicitly initialized by declaring it global.
66 *
67 * Individual fields of pt_ttys structure (except ptm_rdq, pts_rdq and
68 * pt_nullmsg) are protected by pt_ttys.pt_lock mutex.
69 *
70 * PT_ENTER_READ/PT_ENTER_WRITE are reference counter based read-write locks
71 * which allow reader locks to be reacquired by the same thread (usual
72 * reader/writer locks can't be used for that purpose since it is illegal for a
73 * thread to acquire a lock it already holds, even as a reader). The sole
74 * purpose of these macros is to guarantee that the peer queue will not
75 * disappear (due to closing peer) while it is used. It is safe to use
76 * PT_ENTER_READ/PT_EXIT_READ brackets across calls like putq/putnext (since
77 * they are not real locks but reference counts).
78 *
79 * PT_ENTER_WRITE/PT_EXIT_WRITE brackets are used ONLY in manager/subsidiary
80 * open/close paths to modify ptm_rdq and pts_rdq fields. These fields should
81 * be set to appropriate queues *after* qprocson() is called during open (to
82 * prevent peer from accessing the queue with incomplete plumbing) and set to
83 * NULL before qprocsoff() is called during close. Put and service procedures
84 * use PT_ENTER_READ/PT_EXIT_READ to prevent peer closes.
85 *
86 * The pt_nullmsg field is only used in open/close routines and is also
87 * protected by PT_ENTER_WRITE/PT_EXIT_WRITE brackets to avoid extra mutex
88 * holds.
89 *
90 *
91 * LOCK ORDERING
92 *
93 * If both ptms_lock and per-pty lock should be held, ptms_lock should always
94 * be entered first, followed by per-pty lock.
95 *
96 *
97 * GLOBAL FUNCTIONS
98 *
99 * void ptms_init(void);
100 *
101 * Called by pts/ptm _init entry points. It performes one-time
102 * initialization needed for both pts and ptm. This initialization is done
103 * here and not in ptms_initspace because all these data structures are not
104 * needed if pseudo-terminals are not used in the system.
105 *
106 * struct pt_ttys *pt_ttys_alloc(void);
107 *
108 * Allocate new minor number and pseudo-terminal entry. May sleep.
109 * New minor number is recorded in pt_minor field of the entry returned.
110 * This routine also initializes pt_minor and pt_state fields of the new
111 * pseudo-terminal and puts a pointer to it into ptms_slots array.
112 *
113 * struct pt_ttys *ptms_minor2ptty(minor_t minor)
114 *
115 * Find pt_ttys structure by minor number.
116 * Returns NULL when minor is out of range.
117 *
118 * int ptms_minor_valid(minor_t minor, uid_t *ruid, gid_t *rgid)
119 *
120 * Check if minor refers to an allocated pty in the current zone.
121 * Returns
122 * 0 if not allocated or not for this zone.
123 * 1 if an allocated pty in the current zone.
124 * Also returns owner of pty.
125 *
126 * int ptms_minor_exists(minor_t minor)
127 *
128 * Check if minor refers to an allocated pty (in any zone)
129 * Returns
130 * 0 if not an allocated pty
131 * 1 if an allocated pty
132 *
133 * void ptms_set_owner(minor_t minor, uid_t ruid, gid_t rgid)
134 *
135 * Sets the owner associated with a pty.
136 *
137 * void ptms_close(struct pt_ttys *pt, uint_t flags_to_clear);
138 *
139 * Clear flags_to_clear in pt and if no one owns it (PTMOPEN/PTSOPEN not
140 * set) free pt entry and corresponding slot.
141 *
142 *
143 * TUNEABLES AND CONFIGURATION
144 *
145 * pt_cnt: minimum number of pseudo-terminals in the system. The system
146 * should provide at least this number of ptys (provided sufficient
147 * memory is available). It is different from the older semantics
148 * of pt_cnt meaning maximum number of ptys.
149 * Set to 0 by default.
150 *
151 * pt_max_pty: Maximum number of pseudo-terminals in the system. The system
152 * should not allocate more ptys than pt_max_pty (although, it may
153 * impose stricter maximum). Zero value means no user-defined
154 * maximum. This is intended to be used as "denial-of-service"
155 * protection.
156 * Set to 0 by default.
157 *
158 * Both pt_cnt and pt_max_pty may be modified during system
159 * lifetime with their semantics preserved.
160 *
161 * pt_init_cnt: Initial size of ptms_slots array. Set to NPTY_INITIAL.
162 *
163 * pt_ptyofmem: Approximate percentage of system memory that may be
164 * occupied by pty data structures. Initially set to NPTY_PERCENT.
165 * This variable is used once during initialization to estimate
166 * maximum number of ptys in the system. The actual maximum is
167 * determined as minimum of pt_max_pty and calculated value.
168 *
169 * pt_maxdelta: Maximum extension chunk of the slot table.
170 */
171
172
173
174 #include <sys/types.h>
175 #include <sys/param.h>
176 #include <sys/termios.h>
177 #include <sys/stream.h>
178 #include <sys/stropts.h>
179 #include <sys/kmem.h>
180 #include <sys/ptms.h>
181 #include <sys/stat.h>
182 #include <sys/sunddi.h>
183 #include <sys/ddi.h>
184 #include <sys/bitmap.h>
185 #include <sys/sysmacros.h>
186 #include <sys/ddi_impldefs.h>
187 #include <sys/zone.h>
188 #ifdef DEBUG
189 #include <sys/strlog.h>
190 #endif
191
192
193 /* Initial number of ptms slots */
194 #define NPTY_INITIAL 16
195
196 #define NPTY_PERCENT 5
197
198 /* Maximum increment of the slot table size */
199 #define PTY_MAXDELTA 128
200
201 /*
202 * Tuneable variables.
203 */
204 uint_t pt_cnt = 0; /* Minimum number of ptys */
205 size_t pt_max_pty = 0; /* Maximum number of ptys */
206 uint_t pt_init_cnt = NPTY_INITIAL; /* Initial number of ptms slots */
207 uint_t pt_pctofmem = NPTY_PERCENT; /* Percent of memory to use for ptys */
208 uint_t pt_maxdelta = PTY_MAXDELTA; /* Max increment for slot table size */
209
210 /* Other global variables */
211
212 kmutex_t ptms_lock; /* Global data access lock */
213
214 /*
215 * Slot array and its management variables
216 */
217 static struct pt_ttys **ptms_slots = NULL; /* Slots for actual pt structures */
218 static size_t ptms_nslots = 0; /* Size of slot array */
219 static size_t ptms_ptymax = 0; /* Maximum number of ptys */
220 static size_t ptms_inuse = 0; /* # of ptys currently allocated */
221
222 dev_info_t *pts_dip = NULL; /* Set if subsidiary is attached */
223
224 static struct kmem_cache *ptms_cache = NULL; /* pty cache */
225
226 static vmem_t *ptms_minor_arena = NULL; /* Arena for device minors */
227
228 static uint_t ptms_roundup(uint_t);
229 static int ptms_constructor(void *, void *, int);
230 static void ptms_destructor(void *, void *);
231 static minor_t ptms_grow(void);
232
233 /*
234 * Total size occupied by one pty. Each pty manager/subsidiary pair consumes
235 * one pointer for ptms_slots array, one pt_ttys structure, and one empty
236 * message preallocated for pts close.
237 */
238
239 #define PTY_SIZE (sizeof (struct pt_ttys) + \
240 sizeof (struct pt_ttys *) + \
241 sizeof (dblk_t))
242
243 #ifdef DEBUG
244 int ptms_debug = 0;
245 #define PTMOD_ID 5
246 #endif
247
248 /*
249 * Clear all bits of x except the highest bit
250 */
251 #define truncate(x) ((x) <= 2 ? (x) : (1 << (highbit(x) - 1)))
252
253 /*
254 * Roundup the number to the nearest power of 2
255 */
256 static uint_t
ptms_roundup(uint_t x)257 ptms_roundup(uint_t x)
258 {
259 uint_t p = truncate(x); /* x with non-high bits stripped */
260
261 /*
262 * If x is a power of 2, return x, otherwise roundup.
263 */
264 return (p == x ? p : (p * 2));
265 }
266
267 /*
268 * Allocate ptms_slots array and kmem cache for pt_ttys. This initialization is
269 * only called once during system lifetime. Called from ptm or pts _init
270 * routine.
271 */
272 void
ptms_init(void)273 ptms_init(void)
274 {
275 mutex_enter(&ptms_lock);
276
277 if (ptms_slots == NULL) {
278 ptms_slots = kmem_zalloc(pt_init_cnt *
279 sizeof (struct pt_ttys *), KM_SLEEP);
280
281 ptms_cache = kmem_cache_create("pty_map",
282 sizeof (struct pt_ttys), 0, ptms_constructor,
283 ptms_destructor, NULL, NULL, NULL, 0);
284
285 ptms_nslots = pt_init_cnt;
286
287 /* Allocate integer space for minor numbers */
288 ptms_minor_arena = vmem_create("ptms_minor", (void *)1,
289 ptms_nslots, 1, NULL, NULL, NULL, 0,
290 VM_SLEEP | VMC_IDENTIFIER);
291
292 /*
293 * Calculate available number of ptys - how many ptys can we
294 * allocate in pt_pctofmem % of available memory. The value is
295 * rounded up to the nearest power of 2.
296 */
297 ptms_ptymax = ptms_roundup((pt_pctofmem * kmem_maxavail()) /
298 (100 * PTY_SIZE));
299 }
300 mutex_exit(&ptms_lock);
301 }
302
303 /*
304 * This routine attaches the pts dip.
305 */
306 int
ptms_attach_subsidiary(void)307 ptms_attach_subsidiary(void)
308 {
309 if (pts_dip == NULL && i_ddi_attach_pseudo_node("pts") == NULL)
310 return (-1);
311
312 ASSERT(pts_dip);
313 return (0);
314 }
315
316 /*
317 * Called from /dev fs. Checks if dip is attached,
318 * and if it is, returns its major number.
319 */
320 major_t
ptms_subsidiary_attached(void)321 ptms_subsidiary_attached(void)
322 {
323 major_t maj = DDI_MAJOR_T_NONE;
324
325 mutex_enter(&ptms_lock);
326 if (pts_dip)
327 maj = ddi_driver_major(pts_dip);
328 mutex_exit(&ptms_lock);
329
330 return (maj);
331 }
332
333 /*
334 * Allocate new minor number and pseudo-terminal entry. Returns the new entry or
335 * NULL if no memory or maximum number of entries reached.
336 */
337 struct pt_ttys *
pt_ttys_alloc(void)338 pt_ttys_alloc(void)
339 {
340 minor_t dminor;
341 struct pt_ttys *pt = NULL;
342
343 mutex_enter(&ptms_lock);
344
345 /*
346 * Always try to allocate new pty when pt_cnt minimum limit is not
347 * achieved. If it is achieved, the maximum is determined by either
348 * user-specified value (if it is non-zero) or our memory estimations -
349 * whatever is less.
350 */
351 if (ptms_inuse >= pt_cnt) {
352 /*
353 * When system achieved required minimum of ptys, check for the
354 * denial of service limits.
355 *
356 * Since pt_max_pty may be zero, the formula below is used to
357 * avoid conditional expression. It will equal to pt_max_pty if
358 * it is not zero and ptms_ptymax otherwise.
359 */
360 size_t user_max = (pt_max_pty == 0 ? ptms_ptymax : pt_max_pty);
361
362 /* Do not try to allocate more than allowed */
363 if (ptms_inuse >= min(ptms_ptymax, user_max)) {
364 mutex_exit(&ptms_lock);
365 return (NULL);
366 }
367 }
368 ptms_inuse++;
369
370 /*
371 * Allocate new minor number. If this fails, all slots are busy and
372 * we need to grow the hash.
373 */
374 dminor = (minor_t)(uintptr_t)
375 vmem_alloc(ptms_minor_arena, 1, VM_NOSLEEP);
376
377 if (dminor == 0) {
378 /* Grow the cache and retry allocation */
379 dminor = ptms_grow();
380 }
381
382 if (dminor == 0) {
383 /* Not enough memory now */
384 ptms_inuse--;
385 mutex_exit(&ptms_lock);
386 return (NULL);
387 }
388
389 pt = kmem_cache_alloc(ptms_cache, KM_NOSLEEP);
390 if (pt == NULL) {
391 /* Not enough memory - this entry can't be used now. */
392 vmem_free(ptms_minor_arena, (void *)(uintptr_t)dminor, 1);
393 ptms_inuse--;
394 } else {
395 pt->pt_minor = dminor;
396 pt->pt_pid = curproc->p_pid; /* For debugging */
397 pt->pt_state = (PTMOPEN | PTLOCK);
398 pt->pt_zoneid = getzoneid();
399 pt->pt_ruid = 0; /* we don't know uid/gid yet. Report as root */
400 pt->pt_rgid = 0;
401 ASSERT(ptms_slots[dminor - 1] == NULL);
402 ptms_slots[dminor - 1] = pt;
403 }
404
405 mutex_exit(&ptms_lock);
406 return (pt);
407 }
408
409 /*
410 * Get pt_ttys structure by minor number.
411 * Returns NULL when minor is out of range.
412 */
413 struct pt_ttys *
ptms_minor2ptty(minor_t dminor)414 ptms_minor2ptty(minor_t dminor)
415 {
416 struct pt_ttys *pt = NULL;
417
418 ASSERT(mutex_owned(&ptms_lock));
419 if ((dminor >= 1) && (dminor <= ptms_nslots) && ptms_slots != NULL)
420 pt = ptms_slots[dminor - 1];
421
422 return (pt);
423 }
424
425 /*
426 * Invoked in response to chown on /dev/pts nodes to change the
427 * permission on a pty
428 */
429 void
ptms_set_owner(minor_t dminor,uid_t ruid,gid_t rgid)430 ptms_set_owner(minor_t dminor, uid_t ruid, gid_t rgid)
431 {
432 struct pt_ttys *pt;
433
434 if (ruid > MAXUID || rgid > MAXUID)
435 return;
436
437 /*
438 * /dev/pts/0 is not used, but some applications may check it. There
439 * is no pty backing it - so we have nothing to do.
440 */
441 if (dminor == 0)
442 return;
443
444 mutex_enter(&ptms_lock);
445 pt = ptms_minor2ptty(dminor);
446 if (pt != NULL && pt->pt_zoneid == getzoneid()) {
447 pt->pt_ruid = ruid;
448 pt->pt_rgid = rgid;
449 }
450 mutex_exit(&ptms_lock);
451 }
452
453 /*
454 * Given a ptm/pts minor number
455 * returns:
456 * 1 if the pty is allocated to the current zone.
457 * 0 otherwise
458 *
459 * If the pty is allocated to the current zone, it also returns the owner.
460 */
461 int
ptms_minor_valid(minor_t dminor,uid_t * ruid,gid_t * rgid)462 ptms_minor_valid(minor_t dminor, uid_t *ruid, gid_t *rgid)
463 {
464 struct pt_ttys *pt;
465 int ret;
466
467 ASSERT(ruid);
468 ASSERT(rgid);
469
470 *ruid = (uid_t)-1;
471 *rgid = (gid_t)-1;
472
473 /*
474 * /dev/pts/0 is not used, but some applications may check it, so create
475 * it also. Report the owner as root. It belongs to all zones.
476 */
477 if (dminor == 0) {
478 *ruid = 0;
479 *rgid = 0;
480 return (1);
481 }
482
483 ret = 0;
484 mutex_enter(&ptms_lock);
485 pt = ptms_minor2ptty(dminor);
486 if (pt != NULL) {
487 ASSERT(pt->pt_ruid <= MAXUID);
488 ASSERT(pt->pt_rgid <= MAXUID);
489 if (pt->pt_zoneid == getzoneid()) {
490 ret = 1;
491 *ruid = pt->pt_ruid;
492 *rgid = pt->pt_rgid;
493 }
494 }
495 mutex_exit(&ptms_lock);
496
497 return (ret);
498 }
499
500 /*
501 * Given a ptm/pts minor number
502 * returns:
503 * 0 if the pty is not allocated
504 * 1 if the pty is allocated
505 */
506 int
ptms_minor_exists(minor_t dminor)507 ptms_minor_exists(minor_t dminor)
508 {
509 int ret;
510
511 mutex_enter(&ptms_lock);
512 ret = ptms_minor2ptty(dminor) ? 1 : 0;
513 mutex_exit(&ptms_lock);
514
515 return (ret);
516 }
517
518 /*
519 * Close the pt and clear flags_to_clear.
520 * If pt device is not opened by someone else, free it and clear its slot.
521 */
522 void
ptms_close(struct pt_ttys * pt,uint_t flags_to_clear)523 ptms_close(struct pt_ttys *pt, uint_t flags_to_clear)
524 {
525 uint_t flags;
526
527 ASSERT(MUTEX_NOT_HELD(&ptms_lock));
528 ASSERT(pt != NULL);
529
530 mutex_enter(&ptms_lock);
531
532 mutex_enter(&pt->pt_lock);
533 pt->pt_state &= ~flags_to_clear;
534 flags = pt->pt_state;
535 mutex_exit(&pt->pt_lock);
536
537 if (! (flags & (PTMOPEN | PTSOPEN))) {
538 /* No one owns the entry - free it */
539
540 ASSERT(pt->ptm_rdq == NULL);
541 ASSERT(pt->pts_rdq == NULL);
542 ASSERT(pt->pt_nullmsg == NULL);
543 ASSERT(pt->pt_refcnt == 0);
544 ASSERT(pt->pt_minor <= ptms_nslots);
545 ASSERT(ptms_slots[pt->pt_minor - 1] == pt);
546 ASSERT(ptms_inuse > 0);
547
548 ptms_inuse--;
549
550 pt->pt_pid = 0;
551
552 ptms_slots[pt->pt_minor - 1] = NULL;
553 /* Return minor number to the pool of minors */
554 vmem_free(ptms_minor_arena, (void *)(uintptr_t)pt->pt_minor, 1);
555 /* Return pt to the cache */
556 kmem_cache_free(ptms_cache, pt);
557 }
558 mutex_exit(&ptms_lock);
559 }
560
561 /*
562 * Allocate another slot table twice as large as the original one (limited to
563 * global maximum). Migrate all pt to the new slot table and free the original
564 * one. Create more /devices entries for new devices.
565 */
566 static minor_t
ptms_grow()567 ptms_grow()
568 {
569 minor_t old_size = ptms_nslots;
570 minor_t delta = MIN(pt_maxdelta, old_size);
571 minor_t new_size = old_size + delta;
572 struct pt_ttys **ptms_old = ptms_slots;
573 struct pt_ttys **ptms_new;
574 void *vaddr; /* vmem_add return value */
575
576 ASSERT(MUTEX_HELD(&ptms_lock));
577
578 DDBG("ptmopen(%d): need to grow\n", (int)ptms_inuse);
579
580 /* Allocate new ptms array */
581 ptms_new = kmem_zalloc(new_size * sizeof (struct pt_ttys *),
582 KM_NOSLEEP);
583 if (ptms_new == NULL)
584 return ((minor_t)0);
585
586 /* Increase clone index space */
587 vaddr = vmem_add(ptms_minor_arena, (void *)(uintptr_t)(old_size + 1),
588 new_size - old_size, VM_NOSLEEP);
589
590 if (vaddr == NULL) {
591 kmem_free(ptms_new, new_size * sizeof (struct pt_ttys *));
592 return ((minor_t)0);
593 }
594
595 /* Migrate pt entries to a new location */
596 ptms_nslots = new_size;
597 bcopy(ptms_old, ptms_new, old_size * sizeof (struct pt_ttys *));
598 ptms_slots = ptms_new;
599 kmem_free(ptms_old, old_size * sizeof (struct pt_ttys *));
600
601 /* Allocate minor number and return it */
602 return ((minor_t)(uintptr_t)
603 vmem_alloc(ptms_minor_arena, 1, VM_NOSLEEP));
604 }
605
606 /*ARGSUSED*/
607 static int
ptms_constructor(void * maddr,void * arg,int kmflags)608 ptms_constructor(void *maddr, void *arg, int kmflags)
609 {
610 struct pt_ttys *pt = maddr;
611
612 pt->pts_rdq = NULL;
613 pt->ptm_rdq = NULL;
614 pt->pt_nullmsg = NULL;
615 pt->pt_pid = 0;
616 pt->pt_minor = 0;
617 pt->pt_refcnt = 0;
618 pt->pt_state = 0;
619 pt->pt_zoneid = GLOBAL_ZONEID;
620
621 cv_init(&pt->pt_cv, NULL, CV_DEFAULT, NULL);
622 mutex_init(&pt->pt_lock, NULL, MUTEX_DEFAULT, NULL);
623 return (0);
624 }
625
626 /*ARGSUSED*/
627 static void
ptms_destructor(void * maddr,void * arg)628 ptms_destructor(void *maddr, void *arg)
629 {
630 struct pt_ttys *pt = maddr;
631
632 ASSERT(pt->pt_refcnt == 0);
633 ASSERT(pt->pt_state == 0);
634 ASSERT(pt->ptm_rdq == NULL);
635 ASSERT(pt->pts_rdq == NULL);
636
637 mutex_destroy(&pt->pt_lock);
638 cv_destroy(&pt->pt_cv);
639 }
640
641 #ifdef DEBUG
642 void
ptms_log(char * str,uint_t arg)643 ptms_log(char *str, uint_t arg)
644 {
645 if (ptms_debug) {
646 if (ptms_debug & 2)
647 cmn_err(CE_CONT, str, arg);
648 if (ptms_debug & 4)
649 (void) strlog(PTMOD_ID, -1, 0, SL_TRACE | SL_ERROR,
650 str, arg);
651 else
652 (void) strlog(PTMOD_ID, -1, 0, SL_TRACE, str, arg);
653 }
654 }
655
656 void
ptms_logp(char * str,uintptr_t arg)657 ptms_logp(char *str, uintptr_t arg)
658 {
659 if (ptms_debug) {
660 if (ptms_debug & 2)
661 cmn_err(CE_CONT, str, arg);
662 if (ptms_debug & 4)
663 (void) strlog(PTMOD_ID, -1, 0, SL_TRACE | SL_ERROR,
664 str, arg);
665 else
666 (void) strlog(PTMOD_ID, -1, 0, SL_TRACE, str, arg);
667 }
668 }
669 #endif
670